from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import AllowAny
from django.conf import settings
import threading
import json
import re
from rest_framework.parsers import MultiPartParser
from django.http import Http404
from .helper import *
from .permissions import *
from users.models import UserSession, Staff, SwitchUserHistory
from .serializers import *
from .models import BulkImportInitiation, BulkTempLoansImport,BulkTempDepositsImports
from questbanker_api.utils import get_current_user,jwt_switched_session_response_handler
import csv
import io
import threading
from datetime import datetime
from savings.models import *
from overdraft.models import OverDrafts,OverDraftPayment
from shares.models import ShareHolders,SharesTransaction,SharesSettings
from ledgers.ledgers_helper import generate_reference_no
from savings.savings_helper import *
from customers.helper import get_customer_next_member_number
from exservices.models import OrganisationSmsSubscription,MemberSmsSubscription, UserSms, SMSTypes
from exservices.exservices_helper import send_customer_sms, is_customer_subscriber, save_user_sms
from loans.helper import calculate_flat_loan_schedule, loan_payments_reminder
from overdraft.serializers import OverDraftsSerializer
from ledgers.ledgers_helper import *
from mmbanking.models import MobileBankingSubscription
from license.helpers import (
    get_license_charts,
    get_organisation_access_state,
    license_payments_reminder,
    sync_organisation_license_status,
)
from license.models import *
import math
from dateutil.relativedelta import relativedelta
from .transaction_soft_delete_view import TransactionSoftDeleteView


class LoanAutoPenaltyCronJobView(APIView):
    permission_classes = [AllowAny, IsPostOnly]
    
    def post(self, request, format=None):

        # auto penalties
        auto_penalties = threading.Thread(target=loan_auto_penalties, args=())
        # starting auto penalties thread 
        auto_penalties.start()

        return Response({"message":"Cron initiated successfully"})

class LoanAutoPaymentsCronJobView(APIView):
    permission_classes = [AllowAny, IsPostOnly]
    
    def post(self, request, format=None):

        # auto payments
        auto_payments = threading.Thread(target=loan_auto_payments, args=())
        # starting auto payments thread 
        auto_payments.start()

        return Response({"message":"Cron initiated successfully"})

class LoanPaymentsReminderCronJobView(APIView):
    permission_classes = [AllowAny, IsPostOnly]
    
    def post(self, request, format=None):

        # auto payments
        auto_payments = threading.Thread(target=loan_payments_reminder, args=())
        # starting auto payments thread 
        auto_payments.start() 

        return Response({"message":"Cron initiated successfully"})

# class ExternalLoanReportCronJobView(APIView):
#     permission_classes = [AllowAny, IsPostOnly]

#     def post(self, request, format=None):

#         id = request.data.get('id', None)
#         # start external loans report in a background thread
#         auto_report_thread = threading.Thread(target=external_loans_report, args=(id,))
#         auto_report_thread.start()

#         return Response({"message":"Cron initiated successfully"})

class SwitchOrganisationBranch(APIView):
    permission_classes = [IsPostOnly]
    
    def post(self, request, format=None):
        branch_id = request.data.get('branch_id')
        if branch_id:
            branch = OrganisationBranch.objects.get(pk=branch_id)
            access_state = get_organisation_access_state(organisation=branch.branch_organisation, force_sync=True)
            if not access_state['allowed']:
                return Response({"message": access_state['message'], "status": "failed"}, status=status.HTTP_403_FORBIDDEN)

            data = {"user": request.user}
            if request.META.get('HTTP_AUTHORIZATION', None):
                token = request.META.get('HTTP_AUTHORIZATION').split(' ')[1]
                data['session_token'] = token

            organisation_settings_list = {}
            organisation_settings = OrganisationSetting.objects.filter(org_setting=branch.branch_organisation).all()
            for organisation_setting in organisation_settings:
                organisation_settings_list[organisation_setting.setting_key] = organisation_setting.setting_value
            
            user_session = UserSession.objects.filter(**data).first()
            if user_session:
                if branch:
                    user_session.data = {"organisation_id":branch.branch_organisation.id, "organisation_branch_id":branch_id}
                    user_session.save()
                    return Response({"message":"Switch successfully","status":"success","data":organisation_settings_list})
        return Response({"message":"Something went wrong","status":"failed"})


class SwitchUserView(APIView):
    permission_classes = [IsPostOnly]
    def post(self, request, format=None):
        status    = request.data.get('status')
        staffid   = request.data.get('staffid')
        if staffid and status == 'switch':
            new_user = User.objects.filter(user_staff__id=staffid).first()
            old_user = request.user
            branch   = new_user.user_organisation_branch
            if new_user:
                access_state = get_organisation_access_state(organisation=branch.branch_organisation, force_sync=True)
                if not access_state['allowed']:
                    return Response({"message": access_state['message'], "status": "failed"}, status=status.HTTP_403_FORBIDDEN)

                if request.META.get('HTTP_AUTHORIZATION', None):
                    old_user_token = request.META.get('HTTP_AUTHORIZATION').split(' ')[1]
                    from rest_framework_jwt.settings import api_settings
                    jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
                    jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
                    # Set updated_password to True to prevent password change prompt
                    if not new_user.updated_password:
                        new_user.updated_password = True
                        new_user.save()
                    payload = jwt_payload_handler(new_user)
                    token = jwt_encode_handler(payload)
                    switched_session = UserSession.objects.create(user=new_user,is_switched=True, session_token = token,allow_access = True, data= {"organisation_id":branch.branch_organisation.id, "organisation_branch_id":branch.id})
                    if switched_session:
                        SwitchUserHistory.objects.create(new_user=new_user,old_user=old_user, new_user_session_token = token,old_user_session_token = old_user_token)
                    response_data = jwt_switched_session_response_handler(token,old_user_token,new_user,old_user)
                    return Response({
                        "message":"User switched successfully",
                        "status":"success",
                        "data":response_data
                    })
        
        if  status == 'switch_back':
            new_user = request.user
            if new_user:
                if request.META.get('HTTP_AUTHORIZATION', None):
                    new_user_token = request.META.get('HTTP_AUTHORIZATION').split(' ')[1]
                    switch_user_history = SwitchUserHistory.objects.filter(new_user=new_user, new_user_session_token = new_user_token).first()
                    if switch_user_history:
                        switch_user_history.switch_out_date = datetime.now()
                        response_data = jwt_switched_session_response_handler(switch_user_history.old_user_session_token,new_user_token,switch_user_history.old_user,new_user)
                        return Response({
                            "message":"User switched successfully",
                            "status":"success",
                            "data":response_data
                        })
        return Response({"message":"Could not switch user","status":"failed"})

class SwitchOrganisation(APIView):
    permission_classes = [IsPostOnly]
    
    def post(self, request, format=None):
        organisation_id = request.data.get('organisation_id')
        if organisation_id:

            data = {"user": request.user}
            if request.META.get('HTTP_AUTHORIZATION', None):
                token = request.META.get('HTTP_AUTHORIZATION').split(' ')[1]
                data['session_token'] = token

            user_session = UserSession.objects.filter(**data).first()
            if user_session:
                organisation = Organisation.objects.get(pk=organisation_id)
                access_state = get_organisation_access_state(organisation=organisation, force_sync=True)
                if not access_state['allowed']:
                    return Response({"message": access_state['message'], "status": "failed"}, status=status.HTTP_403_FORBIDDEN)
                
                organisation_settings_list = {}
                organisation_settings = OrganisationSetting.objects.filter(org_setting__id=organisation_id).all()
                for organisation_setting in organisation_settings:
                    organisation_settings_list[organisation_setting.setting_key] = organisation_setting.setting_value
                
                if organisation:
                    branch = OrganisationBranch.objects.filter(branch_organisation=organisation).first()
                    if branch:
                        organisation_id = organisation_id
                        organisation_branch_id = branch.id
                        user_session.data = {"organisation_id":organisation_id, "organisation_branch_id":organisation_branch_id}
                        user_session.save()
                        return Response({"message":"Switch successfully","status":"success","data":organisation_settings_list})
        return Response({"message":"Something went wrong","status":"failed"})

class BulkSavingImportTransactionsView(APIView):
        
        def get(self, request, format=None):
            reponse_data  = []
            status        = request.GET.get('status', None)
            initiation_id = request.GET.get('initiation_id', None)
            
            if status == 'successful':
                data = BulkTempDepositsImports.objects.filter(initiation__id=initiation_id).all()
                serializer = BulkTempDepositsImportsImportSerializer(data, many=True)
                reponse_data = serializer.data

            if status == 'failed':
                data_import  = BulkImportInitiation.objects.get(pk=initiation_id)
                if data_import:
                    if data_import.failed_transactions and len(data_import.failed_transactions) > 0:
                        for row in data_import.failed_transactions:
                            organisation_branch = OrganisationBranch.objects.get(pk=row['organisation_branch'])
                            row['organisation_branch_name'] = organisation_branch.name
                            row['organisation_name']        = organisation_branch.branch_organisation.name
                            reponse_data.append(row)
            return Response({"count":len(reponse_data), "results":reponse_data})
        
        def put(self, request, format=None):
            current_status = self.request.data.get('status')
            initiation_id  = self.request.data.get('initiation')

            if  current_status == 'delete':
                data_import    = BulkImportInitiation.objects.filter(id=initiation_id).first()
                if data_import:
                    data_import.delete()
                    return Response({"status":"success","message":"Successfully deleted migration."}, status=status.HTTP_200_OK)
                else:
                    return Response({"status":"failed","message":"Failed to delete migration."}, status=status.HTTP_200_OK)
            
            if  current_status == 'push':
                # create push savings transactions to main thread
                migrating_savings = threading.Thread(target=self.push_savings_to_main_thread,args=(initiation_id,))
                # starting upload savings transactions thread 
                migrating_savings.start()

                msg = "Migrating Savings."
                res = "success"
                return Response({"status":res,"message":msg}, status=status.HTTP_200_OK)
        
        def push_savings_to_main_thread(self,initiation_id,):
            records = BulkTempDepositsImports.objects.filter(initiation__id=initiation_id,status = 'Pending').all()
            for record in records:
                organisation_id  = record.organisation_branch.branch_organisation.id
                branch_id        = record.organisation_branch.id
                #Save transactions details
                selected_account = record.customer
                if record.group_customer:
                    selected_account = record.group_customer

                customer_account = SavingAccount.objects.filter(account_customer = selected_account, account_product=record.product).first()
                reference_no     = generate_reference_no(customer_account.account_product.accounts_chart.account_line, organisation_id,'dep')
                amount           = record.amount
                
                if float(amount) > 0:
                    transaction_fields = {
                        "heading":'Deposit: ('+str(amount)+') by ' +record.deposited_by + ' on A/C No: ' +customer_account.account_no,
                        "coment":record.deposited_by +' has deposited: ('+str(amount) +') '+ 'for saving' + ' on A/C No: ' +customer_account.account_no,
                        "amount":amount,
                        "credit_chart":customer_account.account_product.accounts_chart,
                        "debit_chart":record.debit_chart,
                        "reference_no":reference_no,
                        "voucher_no":'',
                        "record_date":record.record_date,
                        "payment_method":record.payment_method,
                        "added_by":record.added_by,
                        "branch":record.organisation_branch,
                    }
                    saved_transaction = SystemTransactions.objects.create(**transaction_fields) 
                    if saved_transaction:
                        # add saving deposit transaction mapping
                        saved_transaction_fields = {
                            "transaction_type":'deposit',
                            "customer_account_id":customer_account.id,
                            "transaction_id":saved_transaction.id
                        }
                        save_trans = SavingAccountTransactions.objects.create(**saved_transaction_fields) 
                        if save_trans:
                            record.status = 'Processed'
                            record.save()

                            # group savings
                            if record.group_customer:
                                membership = GroupMembership.objects.filter(member__id=record.customer.id, group=record.group_customer, active=True).first()
                                if membership:
                                    group_trans_field = {
                                        "membership": membership,
                                        "savings": save_trans
                                    }
                                    GroupSavingTransaction.objects.create(**group_trans_field) 
                        # Process account booking payments
                        #thread_multiple_booking_payments(customer_account, organisation_id, customer_account.customer_branch.id, self.request.user.id)
                
                if float(amount) < 0:
                    transaction_fields = {
                        "heading":'Bookings Migrated: ('+str(amount)+') '+ ' on A/C No: ' +customer_account.account_no,
                        "coment":'Bookings Migrated: ('+str(amount)+') '+ ' on A/C No: ' +customer_account.account_no,
                        "amount":abs(amount),
                        "debit_chart":customer_account.account_product.accounts_chart,
                        "credit_chart":record.debit_chart,
                        "reference_no":reference_no,
                        "voucher_no":'',
                        "record_date":record.record_date,
                        "payment_method":record.payment_method,
                        "added_by":record.added_by,
                        "branch":record.organisation_branch,
                    }
                    saved_transaction = SystemTransactions.objects.create(**transaction_fields) 
                    if saved_transaction:
                        # add saving deposit transaction mapping
                        saved_transaction_fields = {
                            "transaction_type":'withdrawal',
                            "customer_account_id":customer_account.id,
                            "transaction_id":saved_transaction.id
                        }
                        save_trans = SavingAccountTransactions.objects.create(**saved_transaction_fields) 
                        if save_trans:
                            record.status = 'Processed'
                            record.save()

                            # group savings
                            if record.group_customer:
                                membership = GroupMembership.objects.filter(member__id=record.customer.id, group=record.group_customer, active=True).first()
                                if membership:
                                    group_trans_field = {
                                        "membership": membership,
                                        "savings": save_trans
                                    }
                                    GroupSavingTransaction.objects.create(**group_trans_field) 

class BulkClientSavingImportTransactionsView(APIView):
        
        def get(self, request, format=None):
            reponse_data  = []
            status        = request.GET.get('status', None)
            initiation_id = request.GET.get('initiation_id', None)
            
            if status == 'successful':
                data = BulkTempClientDepositsImports.objects.filter(initiation__id=initiation_id).all()
                serializer = BulkTempClientDepositsImportsSerializer(data, many=True)
                reponse_data = serializer.data

            if status == 'failed':
                data_import  = BulkImportInitiation.objects.get(pk=initiation_id)
                if data_import:
                    if data_import.failed_transactions and len(data_import.failed_transactions) > 0:
                        for row in data_import.failed_transactions:
                            organisation_branch = OrganisationBranch.objects.get(pk=row['organisation_branch'])
                            row['organisation_branch_name'] = organisation_branch.name
                            row['organisation_name']        = organisation_branch.branch_organisation.name
                            reponse_data.append(row)
            return Response({"count":len(reponse_data), "results":reponse_data})
        
        def put(self, request, format=None):
            current_status = self.request.data.get('status')
            initiation_id  = self.request.data.get('initiation')

            if  current_status == 'delete':
                data_import    = BulkImportInitiation.objects.filter(id=initiation_id).first()
                if data_import:
                    data_import.delete()
                    return Response({"status":"success","message":"Successfully deleted migration."}, status=status.HTTP_200_OK)
                else:
                    return Response({"status":"failed","message":"Failed to delete migration."}, status=status.HTTP_200_OK)
            
            if  current_status == 'push':
                # create push savings transactions to main thread
                migrating_savings = threading.Thread(target=self.push_client_deposits_to_main_thread,args=(initiation_id,))
                # starting upload savings transactions thread 
                migrating_savings.start()

                msg = "Migrating Savings."
                res = "success"
                return Response({"status":res,"message":msg}, status=status.HTTP_200_OK)
        
        def push_client_deposits_to_main_thread(self,initiation_id,):
            records = BulkTempClientDepositsImports.objects.filter(initiation__id=initiation_id,status = 'Pending').all()
            for record in records:
                organisation_id  = record.organisation_branch.branch_organisation.id
                customer_account = SavingAccount.objects.filter(id = record.account.id).first()
                reference_no     = generate_reference_no(customer_account.account_product.accounts_chart.account_line, organisation_id,'dep')
                amount           = record.amount
                if amount > 0:
                    deposit_details = {
                        "heading":'Deposit: ('+str(amount)+') by ' +record.deposited_by + ' on A/C No: ' +customer_account.account_no,
                        "coment":record.deposited_by +' has deposited: ('+str(amount) +') '+ 'for saving' + ' on A/C No: ' +customer_account.account_no,
                        "amount":amount,
                        "credit_chart":customer_account.account_product.accounts_chart,
                        "debit_chart":record.debit_chart,
                        "reference_no":reference_no,
                        "voucher_no":'',
                        "record_date":record.record_date,
                        "payment_method":record.payment_method,
                        "user":record.added_by,
                        "branch":record.organisation_branch,
                        "customer":record.customer,
                        "customer_account":customer_account,
                        "send_sms":record.send_sms,
                        "charge":record.charge,
                        "is_group_deposit":False
                    }
                    
                    save_trans = process_customer_deposits(deposit_details)
                    if save_trans:
                        record.status = 'Processed'
                        record.save()
        
class BulkSharesImportTransactionsView(APIView):
        
    def get(self, request, format=None):
        reponse_data  = []
        status        = request.GET.get('status', None)
        initiation_id = request.GET.get('initiation_id', None)
        
        if status == 'successful':
            data = BulkTempSharesImports.objects.filter(initiation__id=initiation_id).all()
            serializer = BulkTempSharesImportsSerializer(data, many=True)
            reponse_data = serializer.data

        if status == 'failed':
            data_import  = BulkImportInitiation.objects.get(pk=initiation_id)
            if data_import:
                if data_import.failed_transactions and len(data_import.failed_transactions) > 0:
                    for row in data_import.failed_transactions:
                        organisation_branch = OrganisationBranch.objects.get(pk=row['organisation_branch'])
                        row['organisation_branch_name'] = organisation_branch.name
                        row['organisation_name']        = organisation_branch.branch_organisation.name
                        reponse_data.append(row)
        return Response({"count":len(reponse_data), "results":reponse_data})
    
    def put(self, request, format=None):
        current_status = self.request.data.get('status')
        initiation_id  = self.request.data.get('initiation')
        
        if  current_status == 'delete':
            data_import    = BulkImportInitiation.objects.filter(id=initiation_id).first()
            if data_import:
                data_import.delete()
                return Response({"status":"success","message":"Successfully deleted migration."}, status=status.HTTP_200_OK)
            else:
                return Response({"status":"failed","message":"Failed to delete migration."}, status=status.HTTP_200_OK)
        
        if  current_status == 'push':
            # create push savings transactions to main thread
            migrating_savings = threading.Thread(target=self.push_shares_to_main_thread,args=(initiation_id,))
            # starting upload savings transactions thread 
            migrating_savings.start()

            msg = "Migrating Shares."
            res = "success"
            return Response({"status":res,"message":msg}, status=status.HTTP_200_OK)
        
        if  current_status == 'failed':
            msg = "Transaction successfully updated"
            res = "success"
            index          = self.request.data.get('id')
            data_import    = BulkImportInitiation.objects.filter(id=initiation_id).first()
            organisation_branch_id = get_current_user(request, 'organisation_branch_id',None) 
            organisation_branch    = OrganisationBranch.objects.get(pk=organisation_branch_id)
            customer = Customer.objects.filter(member_number=self.request.data.get('customer'),customer_branch__branch_organisation = organisation_branch.branch_organisation).first()
            
            if int(self.request.data.get('amount'))/int(self.request.data.get('cur_share_value')) < 1:
                msg = "Insufficient share ammount to purchase atleast one share."
                res = "failed"

            if not customer:
                msg = "Invalid member number"
                res = "failed"

            if customer and  res != "failed":
                if data_import:
                    transaction = data_import.failed_transactions[index]
                    share_fields = {
                        'amount':self.request.data.get('amount'),
                        'cur_share_value':self.request.data.get('cur_share_value'),
                        'purchased_by':self.request.data.get('purchased_by'),
                        'record_date':self.request.data.get('record_date'),
                        'added_by':request.user,
                        'initiation':data_import,
                        'customer':customer,
                        'status':'Pending',
                        'debit_chart':OrganisationSubAccount.objects.get(pk=transaction["debit_chart"]),
                        'heading': 'SharePurchase: ('+ customer.member_number +')',
                        'comment': 'SharePurchase: ('+ customer.member_number +')',
                        'organisation_branch':organisation_branch,
                        'payment_method':transaction["payment_method"]
                    }
                    savedtrans = BulkTempSharesImports.objects.create(**share_fields)
                    if savedtrans:
                        msg = "Transaction successfully updated."
                        res = "success"
                        updatedList = data_import.failed_transactions
                        del updatedList[index]
                        data_import.failed_transactions = updatedList
                        data_import.save()

                    if  not savedtrans:
                        msg = "Failed to update tansaction."
                        res = "failed"
            return Response({"status":res,"message":msg}, status=status.HTTP_200_OK)
            
    def push_shares_to_main_thread(self,initiation_id,):
        
        # process shares purchase
        records = BulkTempSharesImports.objects.filter(initiation__id=initiation_id,status = 'Pending').all()
        for record in records:
            organisation_id = record.organisation_branch.branch_organisation.id
            amount       = record.amount
            customer     = record.customer
            share_holder = ShareHolders.objects.filter(customer=customer).first()

            if not share_holder:
                share_holder = ShareHolders.objects.create(customer=customer, share_holder_added_by=record.added_by)
        
            # credit manual member shares chart of account
            credit_chart_code = "sys-311"
            credit_chart = OrganisationSubAccount.objects.filter(account_code=credit_chart_code,account_organisation=record.organisation_branch.branch_organisation).first()
            # Generate reference number
            reference_no = generate_reference_no(record.debit_chart.account_line, organisation_id, 'p-sh')
            # make the transaction to update the ledger
            transaction_fields = {
                "heading":'SharePurchase: ('+ customer.member_number +')',
                "coment":'SharePurchase: ('+ customer.member_number +')',
                "amount":amount,
                "credit_chart":credit_chart,
                "debit_chart":record.debit_chart,
                "reference_no":reference_no,
                "voucher_no":'',
                "record_date":record.record_date,
                "payment_method":record.payment_method,
                "added_by":record.added_by,
                "branch":record.organisation_branch,
            } 
            saved_transaction = SystemTransactions.objects.create(**transaction_fields) 
            if saved_transaction:
                # add share transaction mapping
                share_transaction = { 
                    "shareholder":share_holder, "share_holder_trans_added_by":record.added_by,
                    "no_of_shares":float(amount/record.cur_share_value), "current_share_value":record.cur_share_value,
                    "transaction_type":"purchase", "organisation_branch":record.organisation_branch, "system_transaction":saved_transaction}
                
                share_purchase = SharesTransaction.objects.create(**share_transaction)
                if share_purchase:
                    record.status = 'Processed'
                    record.save()

class BulkSmsImportTransactionsView(APIView):
        
    def get(self, request, format=None):
        reponse_data  = []
        status        = request.GET.get('status', None)
        initiation_id = request.GET.get('initiation_id', None)
        
        if status == 'successful':
            data = BulkTempSmsSubscriptonImports.objects.filter(initiation__id=initiation_id).all()
            serializer = BulkTempSmsSubscriptonImportsSerializer(data, many=True)
            reponse_data = serializer.data

        if status == 'failed':
            data_import  = BulkImportInitiation.objects.get(pk=initiation_id)
            if data_import:
                if data_import.failed_transactions and len(data_import.failed_transactions) > 0:
                    for row in data_import.failed_transactions:
                        reponse_data.append(row)
        return Response({"count":len(reponse_data), "results":reponse_data})
    
    def put(self, request, format=None):
        current_status = self.request.data.get('status')
        initiation_id  = self.request.data.get('initiation')

        if  current_status == 'delete':
            data_import    = BulkImportInitiation.objects.filter(id=initiation_id).first()
            if data_import:
                data_import.delete()
                return Response({"status":"success","message":"Successfully deleted migration."}, status=status.HTTP_200_OK)
            else:
                return Response({"status":"failed","message":"Failed to delete migration."}, status=status.HTTP_200_OK)
        
        if  current_status == 'push':
            # create push transactions to main thread
            migrating_savings = threading.Thread(target=self.push_sms_sub_to_main_thread,args=(initiation_id,))
            # starting upload transactions thread 
            migrating_savings.start()

            msg = "Migrating Members' sms subscriptions."
            res = "success"
            return Response({"status":res,"message":msg}, status=status.HTTP_200_OK)
        
    def push_sms_sub_to_main_thread(self,initiation_id,):
        records = BulkTempSmsSubscriptonImports.objects.filter(initiation__id=initiation_id,status = 'Pending').all()
        for record in records:
            #Save subscription details
            org_subScritpion = OrganisationSmsSubscription.objects.filter(sms_type__sms_type_key=record.sms_key,organisation=record.organisation).first()
            member_sms_sub = MemberSmsSubscription.objects.filter(org_subscription=org_subScritpion,customer=record.customer).first()
            if member_sms_sub:
                member_sms_sub.is_subscribed = True
                member_sms_sub.save()
                record.save()
            else:
                data = {"customer":record.customer,"org_subscription":org_subScritpion,"sub_added_by":record.added_by, "sub_last_updated_by":record.added_by,"is_subscribed":True}
                member_sms_sub = MemberSmsSubscription.objects.create(**data)
                if member_sms_sub:
                    record.status = 'Processed'
                    record.save()

class BulkMMBankingImportTransactionsView(APIView):
        
    def get(self, request, format=None):
        reponse_data  = []
        status        = request.GET.get('status', None)
        initiation_id = request.GET.get('initiation_id', None)
        
        if status == 'successful':
            data = BulkTempMMBankingSubscriptonImports.objects.filter(initiation__id=initiation_id).all()
            serializer = BulkTempMMBankingSubscriptonImportsSerializer(data, many=True)
            reponse_data = serializer.data

        if status == 'failed':
            data_import  = BulkImportInitiation.objects.get(pk=initiation_id)
            if data_import:
                if data_import.failed_transactions and len(data_import.failed_transactions) > 0:
                    for row in data_import.failed_transactions:
                        reponse_data.append(row)
        return Response({"count":len(reponse_data), "results":reponse_data})
    
    def put(self, request, format=None):
        current_status = self.request.data.get('status')
        initiation_id  = self.request.data.get('initiation')

        if  current_status == 'delete':
            data_import    = BulkImportInitiation.objects.filter(id=initiation_id).first()
            if data_import:
                data_import.delete()
                return Response({"status":"success","message":"Successfully deleted migration."}, status=status.HTTP_200_OK)
            else:
                return Response({"status":"failed","message":"Failed to delete migration."}, status=status.HTTP_200_OK)
        
        if  current_status == 'push':
            # create push transactions to main thread
            migrating_savings = threading.Thread(target=self.push_mm_sub_to_main_thread,args=(initiation_id,))
            # starting upload transactions thread 
            migrating_savings.start()

            msg = "Migrating Members' MM Banking subscriptions."
            res = "success"
            return Response({"status":res,"message":msg}, status=status.HTTP_200_OK)
        
    def push_mm_sub_to_main_thread(self,initiation_id,):
        records = BulkTempMMBankingSubscriptonImports.objects.filter(initiation__id=initiation_id,status = 'Pending').all()
        for record in records:
            #Save subscription details
            member_mm_sub = MobileBankingSubscription.objects.filter(customer=record.customer).first()
            if member_mm_sub:
                member_mm_sub.active = True
                member_mm_sub.telephone_no = record.telephone
                member_mm_sub.pin = record.pin
                member_mm_sub.save()
                record.status = 'Processed'
                record.save()
            else:
                data = {"customer":record.customer, "telephone_no": record.telephone,"pin":record.pin,"active":True,"added_by":record.added_by}
                subscription = MobileBankingSubscription.objects.create(**data)
                if subscription:
                    record.status = 'Processed'
                    record.save()

class BulkClientPhotosImportTransactionsView(APIView):
        
    def get(self, request, format=None):
        reponse_data  = []
        status        = request.GET.get('status', None)
        initiation_id = request.GET.get('initiation_id', None)
        
        if status == 'successful':
            data = BulkTempClientPhotosImports.objects.filter(initiation__id=initiation_id).all()
            serializer = BulkTempClientPhotosImportsSerializer(data, many=True)
            reponse_data = serializer.data

        if status == 'failed':
            data_import  = BulkImportInitiation.objects.get(pk=initiation_id)
            if data_import:
                if data_import.failed_transactions and len(data_import.failed_transactions) > 0:
                    for row in data_import.failed_transactions:
                        reponse_data.append(row)
        return Response({"count":len(reponse_data), "results":reponse_data})
    
    def put(self, request, format=None):
        current_status = self.request.data.get('status')
        initiation_id  = self.request.data.get('initiation')

        if  current_status == 'delete':
            data_import    = BulkImportInitiation.objects.filter(id=initiation_id).first()
            if data_import:
                data_import.delete()
                return Response({"status":"success","message":"Successfully deleted migration."}, status=status.HTTP_200_OK)
            else:
                return Response({"status":"failed","message":"Failed to delete migration."}, status=status.HTTP_200_OK)
        
        if  current_status == 'push':
            # create push transactions to main thread
            migrating_savings = threading.Thread(target=self.push_client_photos_to_main_thread,args=(initiation_id,))
            migrating_savings.start()

            msg = "Migrating Clients' photos"
            res = "success"
            return Response({"status":res,"message":msg}, status=status.HTTP_200_OK)
        
    def push_client_photos_to_main_thread(self,initiation_id,):
        records = BulkTempClientPhotosImports.objects.filter(initiation__id=initiation_id,status = 'Pending').all()
        for record in records:
            if len(record.profile_url) > 0:
                #Save photo details
                current = CustomerFiles.objects.filter(customer=record.customer)
                for file in current:
                    file.delete()
                
                profile_pic = CustomerFiles.objects.filter(customer=record.customer,file_type="profile_pic").first()
                if profile_pic:
                    profile_pic.url = record.profile_url
                    profile_pic.save()
                else:
                    data = {"customer":record.customer,"file_type":"profile_pic","added_by":record.added_by, "url":record.profile_url}
                    profile_pic = CustomerFiles.objects.create(**data)
                if profile_pic:  
                    record.status = 'Processed'
                    record.save()

            if len(record.siginature_url) > 0:
                #Save custmer signature details
                signature_pic = CustomerFiles.objects.filter(customer=record.customer,file_type="signature_pic").first()
                if signature_pic:
                    signature_pic.url = record.siginature_url
                    signature_pic.save()
                else:
                    data = {"customer":record.customer,"file_type":"signature_pic","added_by":record.added_by, "url":record.siginature_url}
                    signature_pic = CustomerFiles.objects.create(**data)
                if signature_pic:  
                    record.status = 'Processed'
                    record.save()
                  

class BulkCustomerFieldsImportTransactionsView(APIView):
        
    def get(self, request, format=None):
        reponse_data  = []
        status        = request.GET.get('status', None)
        initiation_id = request.GET.get('initiation_id', None)
        
        if status == 'successful':
            data = BulkTempCustomerFieldsImport.objects.filter(initiation__id=initiation_id).all()
            serializer = BulkTempCustomerFieldsImportSerializer(data, many=True)
            reponse_data = serializer.data

        if status == 'failed':
            data_import  = BulkImportInitiation.objects.get(pk=initiation_id)
            if data_import:
                if data_import.failed_transactions and len(data_import.failed_transactions) > 0:
                    for row in data_import.failed_transactions:
                        reponse_data.append(row)
        return Response({"count":len(reponse_data), "results":reponse_data})
    
    def put(self, request, format=None):
        current_status = self.request.data.get('status')
        initiation_id  = self.request.data.get('initiation')

        if  current_status == 'delete':
            data_import    = BulkImportInitiation.objects.filter(id=initiation_id).first()
            if data_import:
                data_import.delete()
                return Response({"status":"success","message":"Successfully deleted migration."}, status=status.HTTP_200_OK)
            else:
                return Response({"status":"failed","message":"Failed to delete migration."}, status=status.HTTP_200_OK)
        
        if  current_status == 'push':
            # create push transactions to main thread
            migrating_savings = threading.Thread(target=self.push_customer_fields_to_main_thread,args=(initiation_id,))
            migrating_savings.start()

            msg = "Migrating Clients' photos"
            res = "success"
            return Response({"status":res,"message":msg}, status=status.HTTP_200_OK)
        
    def push_customer_fields_to_main_thread(self,initiation_id,):
        records = BulkTempCustomerFieldsImport.objects.filter(initiation__id=initiation_id,status = 'Pending').all()
        for record in records:
            customer_meta_fields = json.loads(record.value)
            saved_status = False
            if isinstance(customer_meta_fields, list):
                for customer_meta_field in customer_meta_fields:
                    value    = customer_meta_field["value"]
                    field_id = customer_meta_field["field_id"]
                    customer_field_type = CustomerTypeField.objects.get(pk=field_id)
                    if len(value) > 0 and  customer_field_type.customer_reg_field.field_type == 'checkbox':
                        options = str(value).split(",")
                        new_options =[]
                        for option in options:
                            new_options.append(option.strip())
                        value =  json.dumps(new_options)
                    customer_field = CustomerFieldMeta.objects.filter(customer_field__id=field_id,customer=record.customer).first()
                    if customer_field:
                        customer_field.value = value
                        customer_field.save()
                    else:
                        customer_field = CustomerFieldMeta.objects.create(
                            customer_field = CustomerTypeField.objects.get(pk=field_id),
                            value = value, 
                            customer = record.customer, 
                            customer_field_added_by = record.added_by
                        )
                    if customer_field:  
                        saved_status = True

            # If Atleast one meta field has been saved
            if saved_status:  
                record.status = 'Processed'
                record.save()
   
class BulkSavingImportView(APIView):
        parser_classes = (MultiPartParser,)
        
        def allowed_file(self, filename):
            return '.' in filename.name and \
                filename.name.split('.')[1].lower() in ["csv"]
        
        def post(self, request, format=None):
            debit_chart_id = self.request.data.get('debit_chart')
            payment_method = self.request.data.get('payment_method')
            file_obj = request.FILES["file"]
            organisation_branch_id = get_current_user(request, 'organisation_branch_id',None) 
            organisation_branch = OrganisationBranch.objects.get(pk=organisation_branch_id)
            debit_chart         = OrganisationSubAccount.objects.get(pk=debit_chart_id)
            record_count = 0

            if file_obj and self.allowed_file(file_obj):
                # save savings upload
                # current dateTime
                now = datetime.now()
                date_time_str = now.strftime("%m%Y%d%H%M%S")
                # row count 
                file_obj.seek(0)
                reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
                for row in reader:
                    value = list(row.values())
                    if value[0]:
                        record_count = record_count + 1

                data = { "total": record_count,"initiation": 'Savings-'+ date_time_str, "description":"Savings upload", "narration":"Savings upload", "import_type":"savings", "initiation_added_by":request.user, "organisation_branch":organisation_branch, "status":'in-progress'}
                initiation = BulkImportInitiation.objects.create(**data)
               
                # create upload thread
                upload_savings = threading.Thread(target=self.upload_savings_thread, args=(request.user,debit_chart,payment_method, file_obj, initiation,organisation_branch))
                # starting upload_savings thread 
                upload_savings.start()
            else:
                print('Invalid file type')
                return Response({"message":"Invalid file type"})

            return Response({"message":"success"})

        def upload_savings_thread(self,user,debit_chart,payment_method, file_obj, initiation,organisation_branch):
            try:
                
                failed_transactions = []
                file_obj.seek(0)
                reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
                for row in reader:
                    value = list(row.values())
                    msg    = ''
                    status = 'Pending'
                    group_customer = None
                    if value[0]:
                        data = {
                            "customer":value[0],
                            "amount":value[2],
                            "payment_method":payment_method,
                            "teller_assign_id":user.id,
                            "debit_chart":debit_chart,
                            "deposited_by":value[1],
                            "added_by":user,
                            "record_date":value[3],
                            "organisation_branch":organisation_branch,
                            "initiation":initiation,
                            "status":'Pending',
                            "heading":"",
                            "comment":"",
                            "product":value[4],
                            "msg":msg
                            }

                        if value[2]:
                            value[2] = value[2].replace(",", "")
                            data["amount"] = float(value[2])
                            '''if float(value[2]) < 1:
                                data["amount"] = 0
                                msg += " @Deposit amount missing"
                                status = "Failed" '''
    
                        if not value[2]:
                            data["amount"] = 0
                            msg += " @Deposit amount missing"
                            status = "Failed"
                        
                        date_data   = value[3].split("/")
                        if len(date_data) != 3:
                            msg += " @Invalid date"
                            status = "Failed"
                        
                        if value[5] and value[5] != '0':
                            group_customer = Customer.objects.filter(member_number=value[5],customer_branch__branch_organisation=organisation_branch.branch_organisation, is_deleted=False ).first()
                            data["group_customer"] = group_customer
                            if not group_customer:
                                msg = " @Invalid group with this member number"
                                status = "Failed"

                        #validate member number
                        customer = Customer.objects.filter(member_number=value[0],customer_branch__branch_organisation=organisation_branch.branch_organisation, is_deleted=False ).first()
                        if not customer:
                            msg = " @Invalid member number"
                            status = "Failed"
                        #validate savings product id
                        account_product = SavingsProduct.objects.filter(id=value[4],saving_product_org = organisation_branch.branch_organisation).first()
                        if not account_product:
                            msg += " @Invalid savings product id"
                            status = "Failed"
                        if  account_product:
                            data["product"] = account_product
                        #validate savings account
                        if  customer:
                            data["customer"] = customer
                            if account_product:
                                selected_account = customer
                                if group_customer:
                                    selected_account = group_customer
                                
                                saving_account = SavingAccount.objects.filter(account_customer = selected_account, account_product=account_product).first()
                                if not saving_account:
                                    msg = "@Invalid saving account number"
                                    status = "Failed"

                                if saving_account:
                                    data["heading"] =  'Deposit: ('+str(data["amount"]) +') by ' + str(data["deposited_by"]) + ' on A/C No: ' +str(saving_account.account_no)
                                    data["comment"] = data["deposited_by"] +' has deposited: ('+str(data["amount"]) +') '+ 'for saving' + ' on A/C No: ' +str(saving_account.account_no)
                        
                        data["msg"] = msg
                        data["status"] = status
                        if data["status"] == 'Pending': 
                            string_date = date_data[2] + '-' + date_data[1] + '-' + date_data[0]
                            data["record_date"] = datetime.strptime(string_date, '%Y-%m-%d')
                            BulkTempDepositsImports.objects.create(**data)
                        else:
                            data["debit_chart"]    = data["debit_chart"].id
                            data["initiation"]     = data["initiation"].id
                            data["added_by"]       = data["added_by"].id
                            data["customer"]       = value[0]
                            data["product"]        = value[4]
                            data["group_customer"] = value[5]
                            data["organisation_branch"] = data["organisation_branch"].id
                            failed_transactions.append(data)
                if len(failed_transactions) > 0:
                    initiation.failed_transactions = failed_transactions
                    initiation.save()

            except Exception as e:
                print(e)
                raise Http404
            
class BulkSalaryImportView(APIView):
    parser_classes = (MultiPartParser,)

    def allowed_file(self, file_obj):
        return file_obj and '.' in file_obj.name and file_obj.name.split('.')[-1].lower() == "csv"

    def post(self, request, format=None):
        debit_chart_id = request.data.get('debit_chart')
        payment_method = request.data.get('payment_method')
        narration = request.data.get('narration', '')
        send_sms = request.data.get('send_sms', 'False') == 'True'

        file_obj = request.FILES.get("file")
        organisation_branch_id = get_current_user(request, 'organisation_branch_id', None)
        organisation_branch = OrganisationBranch.objects.get(pk=organisation_branch_id)
        debit_chart = OrganisationSubAccount.objects.get(pk=debit_chart_id)

        if not self.allowed_file(file_obj):
            return Response({"message": "Invalid file type"})

        # Count valid rows
        record_count = 0
        file_obj.seek(0)
        reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
        for row in reader:
            if list(row.values())[0]:
                record_count += 1

        now = datetime.now()
        date_time_str = now.strftime("%m%Y%d%H%M%S")
        initiation_data = {
            "total": record_count,
            "initiation": f"Salary-{date_time_str}",
            "description": "Salary upload",
            "narration": narration,
            "import_type": "salary",
            "send_sms": send_sms,
            "initiation_added_by": request.user,
            "organisation_branch": organisation_branch,
            "status": "in-progress"
        }
        initiation = BulkImportInitiation.objects.create(**initiation_data)

        # Start upload thread
        thread = threading.Thread(
            target=self.upload_salary_thread,
            args=(request.user, debit_chart, payment_method, file_obj, initiation, organisation_branch)
        )
        thread.start()

        return Response({"message": "success"})

    def upload_salary_thread(self, user, debit_chart, payment_method, file_obj, initiation, organisation_branch):
        failed_transactions = []
        try:
            file_obj.seek(0)
            reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))

            for row in reader:
                value = list(row.values())
                if not value[0]:
                    continue

                member_no = value[0]
                member_name = value[1] if len(value) > 1 else ""
                account_no = value[2] if len(value) > 2 else ""
                date_str = value[3] if len(value) > 3 else ""
                amount_str = value[4] if len(value) > 4 else "0"
                narration_text = value[5] if len(value) > 5 else ""

                status = "Pending"
                msg = ""
                record_date = datetime.now()
                amount = 0

                # Validate amount
                try:
                    amount = float(amount_str.replace(",", "").strip())
                    if amount <= 0:
                        raise ValueError()
                except:
                    status = "Failed"
                    msg += " @Invalid amount"

                # Validate date
                try:
                    d = date_str.split("/")
                    if len(d) == 3:
                        record_date = datetime.strptime(f"{d[2]}-{d[1]}-{d[0]}", "%Y-%m-%d")
                except:
                    status = "Failed"
                    msg += " @Invalid date"

                # Validate customer
                customer = Customer.objects.filter(
                    member_number=member_no,
                    customer_branch__branch_organisation=organisation_branch.branch_organisation,
                    is_deleted=False
                ).first()
                if not customer:
                    status = "Failed"
                    msg += " @Invalid member number"

                # Validate savings account
                saving_account = None
                if customer:
                    saving_account = SavingAccount.objects.filter(
                        account_no=account_no.replace('"', '') if account_no else "",
                        account_customer=customer
                    ).first()
                    if not saving_account:
                        status = "Failed"
                        msg += " @Invalid savings account number"

                # Save valid row
                if status == "Pending" and customer and saving_account:
                    BulkTempSalaryImports.objects.create(
                        customer=customer,
                        salary_account=account_no,
                        amount=amount,
                        payment_method=payment_method,
                        debit_chart=debit_chart,
                        initiation=initiation,
                        organisation_branch=organisation_branch,
                        status=status,
                        record_date=record_date,
                        heading=f"Salary Credit: ({amount}) to {customer.name} A/C {saving_account.account_no}",
                        comment=f"{customer.name} received salary: ({amount}) on A/C {saving_account.account_no}",
                        msg=msg,
                        added_by=user
                    )
                else:
                    # Append failed row for frontend
                    failed_transactions.append({
                        "member_number": member_no,
                        "member_name": member_name,
                        "salary_account": account_no,
                        "amount": amount_str,
                        "payment_method": payment_method,
                        "record_date": date_str,
                        "narration": narration_text,
                        "msg": msg or " @Failed validation",
                        "organisation_branch": organisation_branch.id
                    })

            if failed_transactions:
                initiation.failed_transactions = failed_transactions
                initiation.save()

        except Exception as e:
            print("Salary Upload Error:", e)
            raise Http404

class BulkSalaryImportTransactionsView(APIView):
    def get(self, request, format=None):
        response_data = []
        status_filter = request.GET.get('status')
        initiation_id = request.GET.get('initiation_id')

        if not initiation_id:
            return Response({"count": 0, "results": []})

        # --- Successful transactions ---
        if status_filter == 'successful':
            # Include both Pending and Processed
            records = BulkTempSalaryImports.objects.filter(
                initiation__id=initiation_id, status__in=['Pending', 'Processed']
            ).select_related('customer', 'organisation_branch')
            response_data = []

            for rec in records:
                response_data.append({
                    "member_number": rec.customer.member_number if rec.customer else None,
                    "member_name": rec.customer.name if rec.customer else None,
                    "salary_account": rec.salary_account,
                    "amount": rec.amount,
                    "record_date": rec.record_date,
                    "payment_method": rec.payment_method,
                    "narration": f"Salary Payment",
                    "msg": rec.msg,
                    "status": rec.status,
                    "organisation_branch_name": rec.organisation_branch.name if rec.organisation_branch else 'N/A',
                    "organisation_name": rec.organisation_branch.branch_organisation.name if rec.organisation_branch else 'N/A',
                })

        # --- Failed transactions ---
        elif status_filter == 'failed':
            initiation = BulkImportInitiation.objects.filter(pk=initiation_id).first()
            records = BulkTempSalaryImports.objects.filter(
                initiation__id=initiation_id, status__in=['Failed']
            ).select_related('customer', 'organisation_branch')
            response_data = []
            if initiation and initiation.failed_transactions:
                for row in initiation.failed_transactions:
                    branch_name = 'N/A'
                    org_name = 'N/A'
                    try:
                        branch = OrganisationBranch.objects.get(pk=row.get('organisation_branch'))
                        branch_name = branch.name
                        org_name = branch.branch_organisation.name
                    except OrganisationBranch.DoesNotExist:
                        pass

                    response_data.append({
                        "member_number": row.get("member_number"),
                        "member_name": row.get("member_name"),
                        "salary_account": row.get("salary_account"),
                        "amount": row.get("amount"),
                        "record_date": row.get("record_date"),
                        "payment_method": row.get("payment_method"),
                        "narration": f"Salary Payment",
                        "msg": row.get("msg"),
                        "organisation_branch_name": branch_name,
                        "organisation_name": org_name,
                    })

        return Response({"count": len(response_data), "results": response_data})

    def put(self, request, format=None):
        current_status = request.data.get('status')
        initiation_id = request.data.get('initiation')

        if current_status == 'delete':
            initiation = BulkImportInitiation.objects.filter(id=initiation_id).first()
            if initiation:
                initiation.delete()
                return Response({"status": "success", "message": "Successfully deleted migration."})
            return Response({"status": "failed", "message": "Failed to delete migration."})

        elif current_status == 'push':
            thread = threading.Thread(
                target=self.push_salaries_to_main_thread,
                args=(initiation_id,)
            )
            thread.start()
            return Response({"status": "success", "message": "Processing salaries..."})

        else:
            return Response({"status": "failed", "message": "Invalid status provided."}, status=400)

    def push_salaries_to_main_thread(self, initiation_id):
        initiation = BulkImportInitiation.objects.get(pk=initiation_id)
        narration = initiation.narration or ''
        send_sms = initiation.send_sms
        records = BulkTempSalaryImports.objects.filter(
            initiation__id=initiation_id, status='Pending'
        ).select_related('customer', 'organisation_branch')

        for record in records:
            customer_account = SavingAccount.objects.filter(
                account_no=record.salary_account,
                account_customer__member_number=record.customer.member_number
            ).first()

            if not customer_account:
                record.status = 'Failed'
                record.msg = (record.msg or "") + " @Invalid customer salary account"
                record.save()
                continue

            debit_chart = record.debit_chart
            credit_chart = customer_account.account_product.accounts_chart
            amount = record.amount

            # --- Handle receivables: post as negative if funding from receivables ---
            if debit_chart.account_code.startswith('sys-113'):  # Receivables code
                debit_amount = -amount  # Money taken from receivables
                credit_amount = amount
            else:
                debit_amount = amount
                credit_amount = amount

            transaction_details = {
                "heading": f"{narration} for {customer_account.account_customer.name} on A/C {customer_account.account_no}",
                "coment": f"{narration} for {customer_account.account_customer.name} on A/C {customer_account.account_no}",
                "amount": amount,
                "debit_amount": debit_amount,
                "credit_amount": credit_amount,
                "credit_chart": credit_chart,
                "debit_chart": debit_chart,
                "reference_no": generate_reference_no(
                    credit_chart.account_line,
                    record.organisation_branch.branch_organisation.id,
                    'salary'
                ),
                "record_date": record.record_date,
                "payment_method": record.payment_method,
                "user": record.added_by,
                "branch": record.organisation_branch,
                "salary_account": customer_account,
                "customer_account": customer_account
            }

            saved_transaction = process_salary_transaction(transaction_details)
            if saved_transaction:
                record.status = 'Processed'
                record.save()
                if send_sms:
                    try:
                        customer = customer_account.account_customer
                        branch_id = record.organisation_branch.id
                        sms_type = SMSTypes.objects.filter(sms_type_key='salary_upload_sms', status='active').first()
                        if sms_type:
                            # Build SMS message
                            from savings.serializers import SavingAccountSerializer
                            account_balance_data = SavingAccountSerializer(SavingAccount.objects.get(pk=customer_account.id)).data
                            balance_actual = round(account_balance_data['account_balance']['balance_actual'], 0)
                            sms_msg = (
                                f"Dear {customer.name.capitalize()}, "
                                f"UGX {amount:,.0f} has been credited "
                                f"to your A/C: {customer_account.account_no}. "
                                f"Reason: {narration}. "
                                f"Balance UGX: {balance_actual:,}. "
                                f"Thanks for saving with {record.organisation_branch.branch_organisation.name}"
                            )

                            # Check subscription
                            subscriber_data = is_customer_subscriber('salary_upload_sms', customer, branch_id)

                            if subscriber_data['is_subscribed']:
                                # Subscribed - send SMS via normal flow
                                subscriber_data["branch"] = record.organisation_branch
                                subscriber_data["customer_account"] = customer_account
                                subscriber_data["message"] = sms_msg
                                subscriber_data["sent_by"] = record.added_by
                                subscriber_data["sms_unique_key"] = ''
                                if subscriber_data['charged_to'] == 'member':
                                    subscriber_data["debit_chart"] = customer_account.account_product.accounts_chart
                                save_user_sms(subscriber_data)
                            else:
                                # Not subscribed - save to outbox as not sent
                                UserSms.objects.create(
                                    message=sms_msg,
                                    is_sent=False,
                                    sms_cost=0,
                                    base_cost=0,
                                    telephone=customer.telephone or '',
                                    reciever_name=customer.name,
                                    recieved_by=customer.id,
                                    recieved_by_type='customer',
                                    is_free=True,
                                    sms_unique_key='',
                                    sms_type=sms_type,
                                    branch=record.organisation_branch
                                )
                    except Exception as e:
                        print(f"SMS failed for {customer_account.account_no}: {e}")
            else:
                record.status = 'Failed'
                record.save()
            
class BulkSalaryUploadInitiationView(APIView):
    def get(self, request, format=None):
        page = int(request.GET.get('page', 1))
        page_size = int(request.GET.get('page_size', 10))

        # Get current user's organisation branch
        organisation_branch_id = get_current_user(request, 'organisation_branch_id', None)

        # Filter salary initiations by the user's organisation branch
        initiations = BulkImportInitiation.objects.filter(
            import_type="salary",
            organisation_branch__id=organisation_branch_id
        ).order_by('-date_added')

        # Pagination
        start = (page - 1) * page_size
        end = start + page_size
        paginated = initiations[start:end]

        serializer = BulkImportInitiationSerializer(paginated, many=True)
        return Response({
            "count": initiations.count(),
            "results": serializer.data
        })



class BulkSalaryTemplateView(APIView):
    def get(self, request, format=None):
        organisation_branch_id = get_current_user(request, 'organisation_branch_id', None)
        organisation_branch = OrganisationBranch.objects.get(pk=organisation_branch_id)

        customers = Customer.objects.filter(
            customer_branch__branch_organisation=organisation_branch.branch_organisation,
            is_deleted=False,
            status='active'
        ).order_by('member_number')

        results = []
        for customer in customers:
            first_account = SavingAccount.objects.filter(
                account_customer=customer, deleted=False
            ).order_by('date_added').values_list('account_no', flat=True).first()

            if first_account:
                results.append({
                    'member_number': customer.member_number,
                    'member_name': customer.name,
                    'salary_account': first_account,
                })

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


class BulkClientSavingDepositImportView(APIView):
        parser_classes = (MultiPartParser,)
        
        def allowed_file(self, filename):
            return '.' in filename.name and \
                filename.name.split('.')[1].lower() in ["csv"]
        
        def post(self, request, format=None):
            debit_chart_id = self.request.data.get('debit_chart')
            payment_method = self.request.data.get('payment_method')
            narration      = self.request.data.get('narration')
            send_sms       = self.request.data.get('send_sms',False)

            file_obj = request.FILES["file"]
            organisation_branch_id = get_current_user(request, 'organisation_branch_id',None) 
            organisation_branch = OrganisationBranch.objects.get(pk=organisation_branch_id)
            debit_chart         = OrganisationSubAccount.objects.get(pk=debit_chart_id)
            record_count = 0

            if file_obj and self.allowed_file(file_obj):
                # save savings upload
                # current dateTime
                now = datetime.now()
                date_time_str = now.strftime("%m%Y%d%H%M%S")
                # row count 
                file_obj.seek(0)
                reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
                for row in reader:
                    value = list(row.values())
                    if value[0]:
                        record_count = record_count + 1

                data = { "total": record_count,"initiation": 'Savings-'+ date_time_str, "description":"Savings upload", "narration":narration, "import_type":"client-deposits", "initiation_added_by":request.user, "organisation_branch":organisation_branch, "status":'in-progress'}
                initiation = BulkImportInitiation.objects.create(**data)
               
                # create upload thread
                upload_savings = threading.Thread(target=self.upload_client_savings_thread, args=(request.user,debit_chart,payment_method,send_sms, file_obj, initiation,organisation_branch))
                # starting upload_savings thread 
                upload_savings.start()
            else:
                print('Invalid file type')
                return Response({"message":"Invalid file type"})

            return Response({"message":"success"})

        def upload_client_savings_thread(self,user,debit_chart,payment_method,send_sms, file_obj, initiation,organisation_branch):
            try:
                
                failed_transactions = []
                file_obj.seek(0)
                reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
                for row in reader:
                    value = list(row.values())
                    msg    = ''
                    status = 'Pending'
                    if value[0]:
                        data = {
                            "deposited_by":value[0],
                            "customer":value[1],
                            "account":value[2],
                            "record_date":value[3],
                            "amount":value[4],
                            "charge":value[5],
                            "payment_method":payment_method,
                            "teller_assign_id":user.id,
                            "debit_chart":debit_chart,
                            "added_by":user,
                            "organisation_branch":organisation_branch,
                            "initiation":initiation,
                            "status":'Pending',
                            "heading":"",
                            "comment":"",
                            "msg":msg,
                            "send_sms":send_sms
                        }
                       
                        if value[4]:
                            value[4] = value[4].replace(",", "")
                            value[4] = value[4].replace(".", "")
                            data["amount"] = value[4]
                            if float(value[4]) < 1:
                                data["amount"] = 0
                                msg += " @Deposit amount missing"
                                status = "Failed"
    
                        if not value[4]:
                            data["amount"] = 0
                            msg += " @Deposit amount missing"
                            status = "Failed"
                        
                        if value[5]:
                            value[5] = value[5].replace(",", "")
                            value[5] = value[5].replace(".", "")
                        
                        if not value[5]:
                            value[5] = 0

                        date_data   = value[3].split("/")
                        if len(date_data) != 3:
                            msg += " @Invalid date"
                            status = "Failed"

                        #validate member number
                        customer = Customer.objects.filter(member_number=value[1],customer_branch__branch_organisation=organisation_branch.branch_organisation, is_deleted=False ).first()
                        if not customer:
                            msg = " @Invalid member number"
                            status = "Failed"

                        #validate savings account number id
                        if not value[2]:
                                value[2] = ""

                        saving_account = SavingAccount.objects.filter(account_no=value[2].replace('"', ''),account_product__saving_product_org = organisation_branch.branch_organisation).first()
                        if not saving_account:
                            msg += " @Invalid savings account number"
                            status = "Failed"
                        if  saving_account:
                            data["account"] = saving_account
                        #validate savings account
                        if  customer:
                            data["customer"] = customer
                            if saving_account:
                                data["heading"] =  'Deposit: ('+str(data["amount"]) +') by ' + str(data["deposited_by"]) + ' on A/C No: ' +str(saving_account.account_no)
                                data["comment"] = data["deposited_by"] +' has deposited: ('+str(data["amount"]) +') '+ 'for saving' + ' on A/C No: ' +str(saving_account.account_no)
                    
                        data["msg"] = msg
                        data["status"] = status
                        if data["status"] == 'Pending': 
                            string_date = date_data[2] + '-' + date_data[1] + '-' + date_data[0]
                            data["record_date"] = datetime.strptime(string_date, '%Y-%m-%d')
                            BulkTempClientDepositsImports.objects.create(**data)
                        else:
                            data["debit_chart"]    = data["debit_chart"].id
                            data["initiation"]     = data["initiation"].id
                            data["added_by"]       = data["added_by"].id
                            data["customer"]       = value[1]
                            data["account"]        = value[2]
                            data["organisation_branch"] = data["organisation_branch"].id
                            failed_transactions.append(data)
                if len(failed_transactions) > 0:
                    initiation.failed_transactions = failed_transactions
                    initiation.save()

            except Exception as e:
                print(e)
                raise Http404


class BulkFixedDepositImportView(APIView):
        parser_classes = (MultiPartParser,)
        
        def allowed_file(self, filename):
            return '.' in filename.name and \
                filename.name.split('.')[1].lower() in ["csv"]
        
        def post(self, request, format=None):
            debit_chart_id = self.request.data.get('debit_chart')
            file_obj = request.FILES["file"]
            organisation_branch_id = get_current_user(request, 'organisation_branch_id',None) 
            organisation_branch = OrganisationBranch.objects.get(pk=organisation_branch_id)
            debit_chart         = OrganisationSubAccount.objects.get(pk=debit_chart_id)
            record_count = 0

            if file_obj and self.allowed_file(file_obj):
                # save fixed deposits upload
                # current dateTime
                now = datetime.now()
                date_time_str = now.strftime("%m%Y%d%H%M%S")
                # row count 
                file_obj.seek(0)
                reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
                for row in reader:
                    value = list(row.values())
                    if value[0]:
                        record_count = record_count + 1

                data = { "total": record_count,"initiation": 'fixed-deposits-'+ date_time_str, "description":"Fixed Deposits upload", "narration":"Fixed Deposits upload", "import_type":"fixed-deposits", "initiation_added_by":request.user, "organisation_branch":organisation_branch, "status":'in-progress'}
                initiation = BulkImportInitiation.objects.create(**data)
               
                # create upload thread
                upload_fixed_deposits = threading.Thread(target=self.upload_fixed_deposit_thread, args=(request.user,debit_chart,file_obj, initiation,organisation_branch))
                # starting upload_fixed_deposits thread 
                upload_fixed_deposits.start()
            else:
                print('Invalid file type')
                return Response({"message":"Invalid file type"})

            return Response({"message":"success"})

        def upload_fixed_deposit_thread(self,user,debit_chart,file_obj, initiation,organisation_branch):
            try:
                
                failed_transactions = []
                file_obj.seek(0)
                reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
                for row in reader:
                    value  = list(row.values())
                    msg    = ''
                    status = 'Pending'
                    customer = None

                    if  not value[9] or value[9] == 'FALSE':
                        value[9] = False
                    if  not value[10] or value[10] == 'FALSE':
                        value[10] = False
                    if  not value[11] or value[11] == 'FALSE':
                        value[11] = False

                    if  value[9] == 'TRUE':
                        value[9] = True
                    if  value[10] == 'TRUE':
                        value[10] = True
                    if  value[11] == 'TRUE': 
                        value[11] = True 

                    data = {
                        "customer":value[0],
                        "deposited_by":value[1],
                        "amount":value[2],
                        "period":value[3],
                        "period_type":value[4],
                        "frequency":value[5],
                        "interest":value[6],
                        "payment_method":'cash',
                        "teller_assign_id":user.id,
                        "debit_chart":debit_chart,
                        "added_by":user,
                        "record_date":value[7],
                        "organisation_branch":organisation_branch,
                        "initiation":initiation,
                        "status":'Pending',
                        "heading":"",
                        "comment":"",
                        "msg":"",
                        "fd_status":value[8],
                        "auto_close":value[9],
                        "auto_payments":value[10],
                        "withholding_tax":value[11],
                        "product":value[12],
                        "amount_paid":value[13],
                    }

                    #validate member number
                    if value[0]:
                        customer = Customer.objects.filter(member_number=value[0],customer_branch__branch_organisation=organisation_branch.branch_organisation, is_deleted=False ).first()
                        if customer:
                            data["customer"] = customer
                        else:
                            msg = " @Invalid member number"
                            status = "Failed"
                    else:
                        msg += " @Member number missing"
                        status = "Failed"
                    #validate savings product
                    if value[12]:
                        saving_product = SavingsProduct.objects.filter(id=value[12], saving_product_org=organisation_branch.branch_organisation).first()
                        if saving_product:
                            data["product"] = saving_product
                        if not saving_product:
                            status = "Failed"
                            msg += ' @No savings product found'
                        if saving_product and customer:
                            #validate member savings account number
                            saving_account = SavingAccount.objects.filter(account_customer = customer,account_product=saving_product,account_customer__customer_branch__branch_organisation=organisation_branch.branch_organisation, deleted=False).first()
                            if not saving_account:
                                msg   += '@No savings product found with this savings product id'
                                status = "Failed"
                    else:
                        msg += " @Saving product missing"
                        status = "Failed"
                    
                    #validate FD amount
                    if value[2]:
                            value[2] = value[2].replace(",", "")
                            value[2] = value[2].replace(".", "")
                            data["amount"] = value[2]

                            if float(value[2]) < 1:
                                data["amount"] = 0
                                msg = "@Invalid FD amount"
                                status = "Failed"
                    else:
                       msg = "@Deposit amount missing"
                       status = "Failed"
                    
                    #validate FD Period 
                    if not value[3]:
                        msg += " @Invalid FD Period"
                        status = "Failed"

                    #validate FD Period Type
                    if  value[4] not in ["d","m","q","y"]:
                        msg += " @Invalid FD Period Type"
                        status = "Failed"

                    #validate FD Frequency
                    if not value[5]:
                        msg += " @Invalid FD Frequency"
                        status = "Failed"

                    #validate FD Interest Rate
                    if not value[6]:
                        msg += " @Invalid FD Interest Rate"
                        status = "Failed"

                    #validate FD Record Date
                    if not value[7]:
                        msg += " @Invalid FD Record Date"
                        status = "Failed"
                    if value[7]:
                        date_data   = value[7].split("/")
                        if len(date_data) != 3:
                            data["record_date"] = value[7]
                            msg += " @Invalid FD Record Date"
                            status = "Failed"
                    
                    if not value[8]:
                        msg += " @Invalid FD Status"
                        status = "Failed"

                    if  value[8] not in ["pending","in-progress","closed"]:
                        msg += " @Invalid FD Status"
                        status = "Failed"

                    data["msg"] = msg
                    data["status"] = status

                    if data["status"] == 'Pending':
                        date_data   = value[7].split("/")
                        string_date = date_data[2] + '-' + date_data[1] + '-' + date_data[0]
                        data["record_date"] = datetime.strptime(string_date, '%Y-%m-%d')
                        data["heading"] = 'Fixed Deposit by ' + str(data["deposited_by"]) + ' for ' +str(saving_account.account_customer.member_number)+ " : " +str(saving_account.account_customer.name)
                        data["comment"] = 'Fixed Deposit by ' + str(data["deposited_by"]) + ' for ' +str(saving_account.account_customer.member_number)+ " : " +str(saving_account.account_customer.name)
                        BulkTempFixedDepositsImports.objects.create(**data)
                    else:
                        data["debit_chart"]  = data["debit_chart"].id
                        data["initiation"]   = data["initiation"].id
                        data["added_by"]     = data["added_by"].id
                        data["deposited_by"] = value[1]
                        data["customer"]     = value[0]
                        data["product"]      = value[12]
                        data["organisation_branch"] = data["organisation_branch"].id
                        failed_transactions.append(data)

                if len(failed_transactions) > 0:
                    initiation.failed_transactions = failed_transactions
                    initiation.save()
            except Exception as e:
                print(e)
                raise Http404

class BulkFixedDepositImportTransactionsView(APIView):
        
        def get(self, request, format=None):
            reponse_data  = []
            status        = request.GET.get('status', None)
            initiation_id = request.GET.get('initiation_id', None)

            if status == 'successful':
                data = BulkTempFixedDepositsImports.objects.filter(initiation__id=initiation_id).all()
                serializer = BulkTempFixedDepositsImportsSerializer(data, many=True)
                reponse_data = serializer.data
            if status == 'failed':
                data_import  = BulkImportInitiation.objects.get(pk=initiation_id)
                if data_import:
                    if data_import.failed_transactions and len(data_import.failed_transactions) > 0:
                        for row in data_import.failed_transactions:
                            organisation_branch = OrganisationBranch.objects.get(pk=row['organisation_branch'])
                            row['organisation_branch_name'] = organisation_branch.name
                            row['organisation_name']        = organisation_branch.branch_organisation.name
                            reponse_data.append(row)
            return Response({"count":len(reponse_data), "results":reponse_data})
        
        def put(self, request, format=None):
            current_status = self.request.data.get('status')
            initiation_id  = self.request.data.get('initiation')

            if  current_status == 'delete':
                data_import    = BulkImportInitiation.objects.filter(id=initiation_id).first()
                if data_import:
                    data_import.delete()
                    return Response({"status":"success","message":"Successfully deleted migration."}, status=status.HTTP_200_OK)
                else:
                    return Response({"status":"failed","message":"Failed to delete migration."}, status=status.HTTP_200_OK)
            if  current_status == 'push':
                migrating_fixed_deposits = threading.Thread(target=self.push_fixed_deposits_to_main_thread,args=(initiation_id,request,))
                migrating_fixed_deposits.start()
                msg = "Migrating Fixed Deposits."
                res = "success"
                return Response({"status":res,"message":msg}, status=status.HTTP_200_OK)
         
        def push_fixed_deposits_to_main_thread(self,initiation_id,request,):
            records = BulkTempFixedDepositsImports.objects.filter(initiation__id=initiation_id,status = 'Pending').all()
            for record in records:
                organisation_id  = record.organisation_branch.branch_organisation.id
                customer_account = SavingAccount.objects.filter(account_customer = record.customer,account_product=record.product,account_customer__customer_branch__branch_organisation__id=organisation_id, deleted=False).first()
                
                # Deduct money from selected teller cash 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')
                transaction = SystemTransactions.objects.create(amount=float(record.amount), heading=record.heading, record_date=record.record_date, payment_method=record.payment_method, reference_no=reference_no, credit_chart=fixed_deposit_ledger[0], debit_chart=record.debit_chart, branch_id=record.organisation_branch.id, added_by=record.added_by)
                if transaction:
                    record.status = 'Processed'
                    record.save()
                    fixed_deposit_data = {
                        "auto_close":record.auto_close,
                        "auto_payments":record.auto_payments,
                        "withholding_tax":record.withholding_tax,
                        "amount":float(record.amount),
                        "period":record.period,
                        "period_type":record.period_type,
                        "interest":float(record.interest),
                        "frequency":int(record.frequency),
                        "record_date":record.record_date,
                        "status":record.fd_status,
                        "branch":record.organisation_branch,
                        "saving_account":customer_account,
                        "fixed_deposit_added_by":record.added_by,
                        "fixed_deposit_last_updated_by":record.added_by,
                        "reference_transaction":transaction,
                    }
                    fixed_deposit = FixedDeposit.objects.create(**fixed_deposit_data)
                    if fixed_deposit:
                        # Generate schedule
                        schedule_data = {
                            "loan_amount" : float(record.amount),
                            "period_type": record.period_type,
                            "loan_period": int(record.period),
                            "int_rate": float(record.interest),
                            "frequency": int(record.frequency),
                            "loan_start_date":record.record_date.strftime('%Y-%m-%d'),
                            "grace_period": 0,
                            "grace_period_type": 'd'
                        }
                        schedules = calculate_flat_loan_schedule(request=request, loan_data=schedule_data)
                        # save FD schedule
                        for schedule in schedules:
                            entry = FixedDepositSchedule(fixed_deposit=fixed_deposit, expected_date=schedule['expected_date'], interest=schedule['interest_expected'], fixed_deposit_added_by=record.added_by)
                            entry.save()
                        
                        # process fixed deposits payments
                        if record.amount_paid > 0:
                            amount_paid = record.amount_paid
                            schedules = FixedDepositSchedule.objects.filter(fixed_deposit=fixed_deposit).order_by('id')
                            for schedule in schedules:
                                schedule_amount = schedule.interest
                                if amount_paid < schedule_amount:
                                    break

                                credit_chart = get_chart_of_account_by_code('sys-5113', record.organisation_branch.branch_organisation)
                                transaction_details = {
                                    "heading": "Fixed Deposit Interest Payment: " + customer_account.account_customer.name,
                                    "amount": schedule.interest,
                                    "record_date":  timezone.now(),
                                    "debit_chart_id": record.debit_chart.id,
                                    "credit_chart_id": credit_chart.id,
                                    "payment_method": 'cash',
                                    "voucher_no": "",
                                    "ref_no_prefix": 'fx-py',
                                    "organisation_id": organisation_id,
                                    "branch_id": record.organisation_branch.id,
                                    "user_id": request.user.id
                                }

                                # Save general transaction
                                payment = post_transaction(transaction_details)

                                if payment:
                                    # Link payment to schedule
                                    schedule.reference_transaction = payment
                                    schedule.status = 'paid'
                                    schedule.save()

                                    # update amount paid
                                    amount_paid = amount_paid - schedule_amount


class BulkSmsSubscriptionImportView(APIView):
        parser_classes = (MultiPartParser,)
        
        def allowed_file(self, filename):
            return '.' in filename.name and \
                filename.name.split('.')[1].lower() in ["csv"]
        
        def post(self, request, format=None):
            file_obj = request.FILES["file"]
            organisation_id        = get_current_user(request, 'organisation_id',None)
            organisation_branch_id = get_current_user(request, 'organisation_branch_id',None)  
            organisation        = Organisation.objects.get(pk=organisation_id)
            organisation_branch = OrganisationBranch.objects.get(pk=organisation_branch_id)
            record_count = 0
    
            if file_obj and self.allowed_file(file_obj):
                now = datetime.now()
                date_time_str = now.strftime("%m%Y%d%H%M%S")
                file_obj.seek(0)
                reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
                for row in reader:
                    value = list(row.values())
                    if value[0]:
                        record_count = record_count + 1
                data = { "total": record_count,"initiation": 'Sms-subscriptions-'+ date_time_str, "description":"Sms Subscriptions upload", "narration":"Sms Subscriptions upload", "import_type":"sms-subscriptions", "initiation_added_by":request.user, "organisation_branch":organisation_branch, "status":'in-progress'}
                initiation = BulkImportInitiation.objects.create(**data)
                upload_sms_subscriptions  = threading.Thread(target=self.upload_sms_subscriptions_thread, args=(request.user,file_obj, initiation,organisation))
                upload_sms_subscriptions.start()
            else:
                print('Invalid file type')
                return Response({"message":"Invalid file type"})
            return Response({"message":"success"})

        def upload_sms_subscriptions_thread(self,user, file_obj, initiation,organisation):
            try:
                failed_transactions = []
                file_obj.seek(0)
                reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
                for row in reader:
                    value = list(row.values())
                    msg    = ''
                    status = 'Pending'
                    data = {
                        "sms_key":value[2],
                        "customer":value[0],
                        "name":value[1],
                        "charge_to":value[3],
                        "added_by":user,
                        "initiation":initiation,
                        "status":'Pending',
                        "organisation":organisation,
                        "msg":msg,
                    }
                    #validate member number
                    customer = Customer.objects.filter(member_number=value[0].strip(),customer_branch__branch_organisation=organisation, is_deleted=False ).first()
                    if customer:
                        data["customer"] = customer
                    else:
                        msg    += " Invalid member number."
                        status = "Failed"
                    #validate organisation Subscription
                    subScritpion = OrganisationSmsSubscription.objects.filter(sms_type__sms_type_key=value[2],organisation=organisation).first()
                    if not subScritpion:
                        msg    += " Organisation not subscribed to "+value[1]
                        status  = "Failed"
                    data["msg"]    = msg
                    data["status"] = status
                    if data["status"] == 'Pending':
                        BulkTempSmsSubscriptonImports.objects.create(**data)
                    else:
                        data["initiation"]     = data["initiation"].id
                        data["customer"]       = value[0]
                        data["added_by"]       = data["added_by"].id
                        data["organisation"]   = data["organisation"].id
                        failed_transactions.append(data)
                if len(failed_transactions) > 0:
                    initiation.failed_transactions = failed_transactions
                    initiation.save()

            except Exception as e:
                print(e)
                raise Http404

class BulkMMSubscriptionImportView(APIView):
        parser_classes = (MultiPartParser,)
        
        def allowed_file(self, filename):
            return '.' in filename.name and \
                filename.name.split('.')[1].lower() in ["csv"]
        
        def post(self, request, format=None):
            file_obj = request.FILES["file"]
            organisation_id        = get_current_user(request, 'organisation_id',None)
            organisation_branch_id = get_current_user(request, 'organisation_branch_id',None)  
            organisation        = Organisation.objects.get(pk=organisation_id)
            organisation_branch = OrganisationBranch.objects.get(pk=organisation_branch_id)
            record_count = 0
    
            if file_obj and self.allowed_file(file_obj):
                now = datetime.now()
                date_time_str = now.strftime("%m%Y%d%H%M%S")
                file_obj.seek(0)
                reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
                for row in reader:
                    value = list(row.values())
                    if value[0]:
                        record_count = record_count + 1
                data = { "total": record_count,"initiation": 'MM-subscriptions-'+ date_time_str, "description":"MM Banking Subscriptions upload", "narration":"MM Subscriptions upload", "import_type":"mm-banking-subscriptions", "initiation_added_by":request.user, "organisation_branch":organisation_branch, "status":'pending'}
                initiation = BulkImportInitiation.objects.create(**data)
                upload_sms_subscriptions  = threading.Thread(target=self.upload_mmbanking_subscriptions_thread, args=(request.user,file_obj, initiation,organisation))
                upload_sms_subscriptions.start()
            else:
                print('Invalid file type')
                return Response({"message":"Invalid file type"})
            return Response({"message":"success"})

        def upload_mmbanking_subscriptions_thread(self,user, file_obj, initiation,organisation):
            try:
                failed_transactions = []
                file_obj.seek(0)
                reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
                for row in reader:
                    value = list(row.values())
                    msg    = ''
                    status = 'Pending'
                    data = {
                        "customer":value[0],
                        "name":value[1],
                        "telephone":value[2],
                        "pin":value[3],
                        "added_by":user,
                        "initiation":initiation,
                        "status":'Pending',
                        "organisation":organisation,
                        "msg":msg,
                    }
                    #validate member number
                    customer = Customer.objects.filter(member_number=value[0].strip(),customer_branch__branch_organisation=organisation, is_deleted=False ).first()
                    if customer:
                        data["customer"] = customer
                    else:
                        msg    += " Invalid member number."
                        status = "Failed"

                    data["msg"]    = msg
                    data["status"] = status
                    if data["status"] == 'Pending':
                        BulkTempMMBankingSubscriptonImports.objects.create(**data)
                    else:
                        data["initiation"]     = data["initiation"].id
                        data["customer"]       = value[0]
                        data["added_by"]       = data["added_by"].id
                        data["organisation"]   = data["organisation"].id
                        failed_transactions.append(data)
                if len(failed_transactions) > 0:
                    initiation.failed_transactions = failed_transactions
                    initiation.save()

            except Exception as e:
                print(e)
                raise Http404
            
class BulkClientPhotosImportView(APIView):
    parser_classes = (MultiPartParser,)
    
    def allowed_file(self, filename):
        return '.' in filename.name and \
            filename.name.split('.')[1].lower() in ["csv"]
    
    def post(self, request, format=None):
        file_obj = request.FILES["file"]
        organisation_id        = get_current_user(request, 'organisation_id',None)
        organisation_branch_id = get_current_user(request, 'organisation_branch_id',None)  
        organisation        = Organisation.objects.get(pk=organisation_id)
        organisation_branch = OrganisationBranch.objects.get(pk=organisation_branch_id)
        record_count = 0

        if file_obj and self.allowed_file(file_obj):
            now = datetime.now()
            date_time_str = now.strftime("%m%Y%d%H%M%S")
            file_obj.seek(0)
            reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
            for row in reader:
                value = list(row.values())
                if value[0]:
                    record_count = record_count + 1
            data = { "total": record_count,"initiation": 'client-photos-'+ date_time_str, "description":"Client photos upload", "narration":"Client photos upload", "import_type":"client-photos", "initiation_added_by":request.user, "organisation_branch":organisation_branch, "status":'in-progress'}
            initiation = BulkImportInitiation.objects.create(**data)
            upload_sms_subscriptions  = threading.Thread(target=self.upload_client_photos_thread, args=(request.user,file_obj, initiation,organisation))
            upload_sms_subscriptions.start()
        else:
            print('Invalid file type')
            return Response({"message":"Invalid file type"})
        return Response({"message":"success"})

    def upload_client_photos_thread(self,user, file_obj, initiation,organisation):
        try:
            failed_transactions = []
            file_obj.seek(0)
            reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
            for row in reader:
                value = list(row.values())
                msg    = ''
                status = 'Pending'
                data = {
                    "customer":value[0],
                    "name":value[1],
                    "profile_url":value[2],
                    "siginature_url":value[3],
                    "added_by":user,
                    "initiation":initiation,
                    "status":'Pending',
                    "organisation":organisation,
                    "msg":msg,
                }
                #validate member number
                customer = Customer.objects.filter(member_number=value[0].strip(),customer_branch__branch_organisation=organisation, is_deleted=False ).first()
                if customer:
                    data["customer"] = customer
                else:
                    msg    += " Invalid member number."
                    status = "Failed"
                data["status"] = status
                data["msg"]    = msg
                if data["status"] == 'Pending':
                    BulkTempClientPhotosImports.objects.create(**data)
                else:
                    data["initiation"]     = data["initiation"].id
                    data["customer"]       = value[0]
                    data["added_by"]       = data["added_by"].id
                    data["organisation"]   = data["organisation"].id
                    failed_transactions.append(data)
            if len(failed_transactions) > 0:
                initiation.failed_transactions = failed_transactions
                initiation.save()

        except Exception as e:
            print(e)
            raise Http404

class BulkCustomerFieldsImportView(APIView):
    parser_classes = (MultiPartParser,)
    
    def allowed_file(self, filename):
        return '.' in filename.name and \
            filename.name.split('.')[1].lower() in ["csv"]
    
    def post(self, request, format=None):
        file_obj = request.FILES["file"]
        field_ids = self.request.data.get('field_ids')
        organisation_id        = get_current_user(request, 'organisation_id',None)
        organisation_branch_id = get_current_user(request, 'organisation_branch_id',None)  
        organisation        = Organisation.objects.get(pk=organisation_id)
        organisation_branch = OrganisationBranch.objects.get(pk=organisation_branch_id)
        record_count = 0

        if file_obj and self.allowed_file(file_obj):
            now = datetime.now()
            date_time_str = now.strftime("%m%Y%d%H%M%S")
            file_obj.seek(0)
            reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
            for row in reader:
                value = list(row.values())
                if value[0]:
                    record_count = record_count + 1
            data = { "total": record_count,"initiation": 'customer-fields-'+ date_time_str, "description":"Customer fields upload", "narration":"Customer fields upload", "import_type":"customer-fields", "initiation_added_by":request.user, "organisation_branch":organisation_branch, "status":'in-progress'}
            initiation = BulkImportInitiation.objects.create(**data)
            upload_sms_subscriptions  = threading.Thread(target=self.upload_customer_fields_thread, args=(request.user,file_obj,field_ids,initiation,organisation))
            upload_sms_subscriptions.start()
        else:
            print('Invalid file type')
            return Response({"message":"Invalid file type"})
        return Response({"message":"success"})

    def upload_customer_fields_thread(self,user, file_obj,field_ids,initiation,organisation):
        try:
            failed_transactions = []
            file_obj.seek(0)
            reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
            for row in reader:
                value = list(row.values())
                msg    = ''
                status = 'Pending'
                value_keys    = []
                data = {
                    "customer":value[0],
                    "name":value[1],
                    "value":'',
                    "field":'',
                    "added_by":user,
                    "initiation":initiation,
                    "status":'Pending',
                    "organisation":organisation,
                    "msg":msg,
                }
                field_values = value[2:]
                if not isinstance(field_ids, list):
                    field_ids = list(field_ids.split(","))
                for index in range(0, len(field_values)):
                    if len(field_values[index]) > 0:
                        label = ''
                        customer_type_field = CustomerTypeField.objects.filter(pk=field_ids[index]).first()
                        if customer_type_field:
                            if customer_type_field:
                                label = customer_type_field.org_field_label
                            else:
                                label = customer_type_field.customer_reg_field.field_label

                        value_keys.append({"field_id":field_ids[index],"field_label":label,"value":field_values[index]})
                data['value'] = json.dumps(value_keys)
                data['field'] = json.dumps(field_ids)

                #validate member number
                customer = Customer.objects.filter(member_number=value[0].strip(),customer_branch__branch_organisation=organisation, is_deleted=False ).first()
                if customer:
                    data["customer"] = customer
                else:
                    msg    += " Invalid member number."
                    status = "Failed"
                data["status"] = status
                data["msg"]    = msg
                if data["status"] == 'Pending':
                    BulkTempCustomerFieldsImport.objects.create(**data)
                else:
                    data["initiation"]   = data["initiation"].id
                    data["customer"]     = value[0]
                    data["added_by"]     = data["added_by"].id
                    data["organisation"] = data["organisation"].id
                    failed_transactions.append(data)
            if len(failed_transactions) > 0:
                initiation.failed_transactions = failed_transactions
                initiation.save()

        except Exception as e:
            print(e)
            raise Http404
        
class BulkMemberEmailImportView(APIView):
    parser_classes = (MultiPartParser,)

    def allowed_file(self, filename):
        return '.' in filename.name and \
            filename.name.split('.')[1].lower() in ['csv']

    def post(self, request, format=None):
        file_obj = request.FILES['file']
        organisation_id        = get_current_user(request, 'organisation_id', None)
        organisation_branch_id = get_current_user(request, 'organisation_branch_id', None)
        organisation        = Organisation.objects.get(pk=organisation_id)
        organisation_branch = OrganisationBranch.objects.get(pk=organisation_branch_id)
        record_count = 0

        if file_obj and self.allowed_file(file_obj):
            now = datetime.now()
            date_time_str = now.strftime('%m%Y%d%H%M%S')
            file_obj.seek(0)
            reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
            for row in reader:
                value = list(row.values())
                if value[0]:
                    record_count += 1
            data = {
                'total': record_count,
                'initiation': 'member-email-' + date_time_str,
                'description': 'Member email update upload',
                'narration': 'Member email update upload',
                'import_type': 'member-email-update',
                'initiation_added_by': request.user,
                'organisation_branch': organisation_branch,
                'status': 'in-progress',
            }
            initiation = BulkImportInitiation.objects.create(**data)
            t = threading.Thread(
                target=self.upload_member_emails_thread,
                args=(request.user, file_obj, initiation, organisation)
            )
            t.start()
        else:
            return Response({'message': 'Invalid file type'})
        return Response({'message': 'success'})

    def upload_member_emails_thread(self, user, file_obj, initiation, organisation):
        try:
            failed_transactions = []
            file_obj.seek(0)
            reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
            for row in reader:
                value = list(row.values())
                member_number = value[0].strip() if value[0] else ''
                email         = value[1].strip() if len(value) > 1 and value[1] else ''
                msg    = ''
                status = 'Pending'

                customer = Customer.objects.filter(
                    member_number=member_number,
                    customer_branch__branch_organisation=organisation,
                    is_deleted=False
                ).first()

                if not customer:
                    msg    = 'Invalid member number.'
                    status = 'Failed'

                if status == 'Pending':
                    BulkTempMemberEmailImport.objects.create(
                        status=status,
                        member_number=member_number,
                        email=email,
                        msg=msg,
                        customer=customer,
                        initiation=initiation,
                        organisation=organisation,
                        added_by=user,
                    )
                else:
                    failed_transactions.append({
                        'member_number': member_number,
                        'email': email,
                        'msg': msg,
                        'status': status,
                    })

            if failed_transactions:
                initiation.failed_transactions = failed_transactions
                initiation.save()

        except Exception as e:
            print(e)
            raise Http404


class BulkMemberEmailImportTransactionsView(APIView):

    def get(self, request, format=None):
        response_data = []
        status_param  = request.GET.get('status', None)
        initiation_id = request.GET.get('initiation_id', None)

        if status_param == 'successful':
            data = BulkTempMemberEmailImport.objects.filter(initiation__id=initiation_id).all()
            serializer = BulkTempMemberEmailImportSerializer(data, many=True)
            response_data = serializer.data

        if status_param == 'failed':
            data_import = BulkImportInitiation.objects.get(pk=initiation_id)
            if data_import and data_import.failed_transactions:
                response_data = data_import.failed_transactions

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

    def put(self, request, format=None):
        current_status = self.request.data.get('status')
        initiation_id  = self.request.data.get('initiation')

        if current_status == 'delete':
            data_import = BulkImportInitiation.objects.filter(id=initiation_id).first()
            if data_import:
                data_import.delete()
                return Response({'status': 'success', 'message': 'Successfully deleted migration.'}, status=status.HTTP_200_OK)
            return Response({'status': 'failed', 'message': 'Failed to delete migration.'}, status=status.HTTP_200_OK)

        if current_status == 'push':
            t = threading.Thread(
                target=self.push_member_emails_thread,
                args=(initiation_id,)
            )
            t.start()
            return Response({'status': 'success', 'message': 'Migrating member emails.'}, status=status.HTTP_200_OK)

    def push_member_emails_thread(self, initiation_id):
        records = BulkTempMemberEmailImport.objects.filter(
            initiation__id=initiation_id, status='Pending'
        ).all()
        for record in records:
            if record.customer and record.email:
                record.customer.email = record.email
                record.customer.save()
                record.status = 'Processed'
                record.save()


class BulkShareImportView(APIView):
    parser_classes = (MultiPartParser,)
    
    def allowed_file(self, filename):
        return '.' in filename.name and \
            filename.name.split('.')[1].lower() in ["csv"]
    
    def post(self, request, format=None):
        debit_chart_id = self.request.data.get('debit_chart')
        payment_method = self.request.data.get('payment_method')
        file_obj = request.FILES["file"]
        organisation_id = get_current_user(self.request, 'organisation_id', None)
        organisation_branch_id = get_current_user(request, 'organisation_branch_id',None) 
        organisation_branch = OrganisationBranch.objects.get(pk=organisation_branch_id)
        debit_chart         = OrganisationSubAccount.objects.get(pk=debit_chart_id)
        record_count  = 0
        share_value   = 0
        share_setting = SharesSettings.objects.filter(organisation__id=organisation_id).first()
        if share_setting:
            share_value = share_setting.share_value

        if file_obj and self.allowed_file(file_obj):
            now = datetime.now()
            date_time_str = now.strftime("%m%Y%d%H%M%S")
            # row count 
            file_obj.seek(0)
            reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
            for row in reader:
                value = list(row.values())
                if value[0]:
                    record_count = record_count + 1
            data = { "total": record_count,"initiation": 'Shares-'+ date_time_str, "description":"Shares upload", "narration":"Shares upload", "import_type":"shares", "initiation_added_by":request.user, "organisation_branch":organisation_branch, "status":'in-progress'}
            initiation = BulkImportInitiation.objects.create(**data)
            # create upload thread
            upload_savings = threading.Thread(target=self.upload_shares_thread, args=(request.user,debit_chart,payment_method, file_obj, initiation,organisation_branch,share_value))
            # starting upload_savings thread 
            upload_savings.start()
        else:
            print('Invalid file type')
            return Response({"message":"Invalid file type"})
        return Response({"message":"success"})

    def upload_shares_thread(self,user,debit_chart,payment_method, file_obj, initiation,organisation_branch,share_value):
        try:
            failed_transactions = []
            file_obj.seek(0)
            reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
            for row in reader:
                value = list(row.values())
                amount = value[2]
                msg    = ''
                status = 'Pending'
                if value[0]:
                    #validate old member number
                    customer = Customer.objects.filter(member_number=value[1],customer_branch__branch_organisation = organisation_branch.branch_organisation).first()
                    date_data   = value[3].split("/")
                    string_date = date_data[2] + '-' + date_data[1] + '-' + date_data[0]
                    data = {
                        "amount":value[2],
                        "cur_share_value":share_value,
                        "payment_method":payment_method,
                        "teller_assign_id":user.id,
                        "debit_chart":debit_chart,
                        "purchased_by":value[0],
                        "added_by":user,
                        "record_date": datetime.strptime(string_date, '%Y-%m-%d'),
                        "organisation_branch":organisation_branch,
                        "initiation":initiation,
                        "status":'Pending',
                        "customer":customer,
                        "heading":"",
                        "comment":"",
                        "msg":msg,
                    }

                    if float(share_value) < 0:
                        data["cur_share_value"] = 0
                        if len(msg) < 1:
                            msg = "Share value missing"
                        else:
                            msg += "@Share value missing"
                        status = "Failed"

                    if amount:
                        amount = amount.replace(",", "")
                        data["amount"] = amount
                   
                    if float(amount) < 1:
                        data["amount"] = 0
                        if len(msg) < 1:
                                msg = "Share amount missing"
                        else:
                                msg += "@Share amount missing"
                        status = "Failed"
                    
                    if customer:
                        data["customer"] = customer
                        data["heading"] = 'Purchased share: ('+str(data["amount"])+') by ' +customer.name 
                        data["comment"] = 'Purchased share: ('+str(data["amount"])+') by ' +customer.name 
                    else:
                        if len(msg) < 1:
                            msg = "Invalid member number"
                        else:
                            msg += "@Invalid member number"
                        status = "Failed"
                    
                    data["msg"] = msg
                    data["status"] = status
                   
                    if data["status"] == 'Pending':
                        BulkTempSharesImports.objects.create(**data)
                    else:
                        data["debit_chart"]    = data["debit_chart"].id
                        data["initiation"]     = data["initiation"].id
                        data["added_by"]       = data["added_by"].id
                        data["purchased_by"]   = value[0]
                        data["customer"]       = value[1]
                        data["record_date"]    = data["record_date"].strftime('%Y-%m-%d')
                        data["organisation_branch"] = data["organisation_branch"].id
                        failed_transactions.append(data)

            if len(failed_transactions) > 0:
                initiation.failed_transactions = failed_transactions
                initiation.save()
        except Exception as e:
            print(e)
            raise Http404

class BulkImportInitiationView(APIView):
    parser_classes = (MultiPartParser,)

    def allowed_file(self, filename):
        return '.' in filename.name and \
            filename.name.split('.')[1].lower() in ["csv"]

    def get(self, request, format=None):
        filter_type = request.GET.get('import_type', None)
        initiation_id = request.GET.get('initiation_id', None)
        organisation_branch_id = get_current_user(request, 'organisation_branch_id',None) 

        if initiation_id and filter_type == 'loans':
            data = BulkTempLoansImport.objects.filter(loan_initiation__id=initiation_id).all().order_by('-id')
            serializer = BulkTempLoansImportSerializer(data, many=True)
            response = serializer.data
        elif initiation_id and filter_type == 'loans-sec':
            data = BulkTempLoanSecurityImport.objects.filter(initiation__id=initiation_id).all().order_by('-id')
            serializer = BulkTempLoanSecurityImportSerializer(data, many=True)
            response = serializer.data
        elif initiation_id and filter_type == 'members':
            data = BulkTempMembersImport.objects.filter(member_initiation__id=initiation_id).all().order_by('-id')
            serializer = BulkTempMembersImportSerializer(data, many=True)
            response = serializer.data
        elif initiation_id and filter_type == 'accounts':
            data = BulkTempAccountsImport.objects.filter(member_initiation__id=initiation_id).all().order_by('-id')
            serializer = BulkTempAccountsImportSerializer(data, many=True)
            response = serializer.data
        elif initiation_id and filter_type == 'member-numbers':
            data = BulkTempMemberNumberImport.objects.filter(member_initiation__id=initiation_id).all().order_by('-id')
            serializer = BulkTempMemberNumberImportSerializer(data, many=True)
            response = serializer.data
        elif initiation_id and filter_type == 'sms-subscriptions':
            data = BulkTempSmsSubscriptonImports.objects.filter(member_initiation__id=initiation_id).all().order_by('-id')
            serializer = BulkTempSmsSubscriptonImportsSerializer(data, many=True)
            response = serializer.data
        elif initiation_id and filter_type == 'mm-banking-subscriptions':
            data = BulkTempMMBankingSubscriptonImports.objects.filter(member_initiation__id=initiation_id).all().order_by('-id')
            serializer = BulkTempMMBankingSubscriptonImportsSerializer(data, many=True)
            response = serializer.data
        elif initiation_id and filter_type == 'overdrafts':
            data = BulkTempOverDraftsImport.objects.filter(initiation__id=initiation_id).all().order_by('-id')
            serializer = BulkTempOverDraftsImportSerializer(data,many=True)
            response = serializer.data

        else:
            if filter_type:
                data = BulkImportInitiation.objects.filter(organisation_branch__id=organisation_branch_id, import_type=filter_type).order_by('-id')
            else:
                data = BulkImportInitiation.objects.filter(organisation_branch__id=organisation_branch_id).order_by('-id')

            serializer = BulkImportInitiationSerializer(data, many=True)
            response = serializer.data
        return Response({"count":len(response), "results":response})

    def post(self, request, format=None):

        filter_type = request.GET.get('import_type', None)
        initiation_id = request.GET.get('initiation_id', None)

        if filter_type == 'loans' and initiation_id:
            # migrate loans to main database
            initiation = BulkImportInitiation.objects.get(pk=initiation_id)
            if not initiation or initiation.status != 'in-progress':
                return Response({"message":"Failed"})
            
            initiation.status = 'pending'
            initiation.save()

            push_loans = threading.Thread(target=migrate_loans_to_main_database, args=(request, initiation_id))
            # starting push_loans thread 
            push_loans.start()

            return Response({"message":"Push Initiated"})
        
        if filter_type == 'loans-sec' and initiation_id:
            # migrate loans to main database
            initiation = BulkImportInitiation.objects.get(pk=initiation_id)
            if not initiation or initiation.status != 'in-progress':
                return Response({"message":"Failed"})
            
            initiation.status = 'pending'
            initiation.save()

            push_loans = threading.Thread(target=migrate_loans_security_to_main_database, args=(request, initiation_id))
            # starting push_loans thread 
            push_loans.start()

            return Response({"message":"Push Initiated"})

        if filter_type == 'members' and initiation_id:
            
            # migrate push_members to main database
            initiation = BulkImportInitiation.objects.get(pk=initiation_id)
            if not initiation or initiation.status != 'in-progress':
                return Response({"message":"Failed"})
            
            initiation.status = 'pending'
            initiation.save()


            push_accounts = threading.Thread(target=migrate_members_to_main_database, args=(request, initiation_id))
            # starting push members thread 
            push_accounts.start()
            return Response({"message":"Push Initiated"})
        if filter_type == 'accounts' and initiation_id:
            # migrate push_members to main database
            push_accounts = threading.Thread(target=migrate_accounts_to_main_database, args=(request, initiation_id))
            # starting push members thread 
            push_accounts.start()
            return Response({"message":"Push Initiated"})
        
        if filter_type == 'member-numbers' and initiation_id:
            # migrate push_members to main database
            update_numbers = threading.Thread(target=migrate_member_number_updates_to_main_database, args=(request, initiation_id))
            # starting push members thread 
            update_numbers.start()

            return Response({"message":"Push Initiated"})
        
        elif filter_type == 'loans' and initiation_id is None:
            account = self.request.data.get('account')
            payment_method = self.request.data.get('payment_method')
            as_at = self.request.data.get('as_at')
            file_obj = request.FILES["file"]
            organisation_branch_id = get_current_user(request, 'organisation_branch_id',None) 
            organisation_branch = OrganisationBranch.objects.get(pk=organisation_branch_id)

            if file_obj and self.allowed_file(file_obj):
                # save loan upload
                # current dateTime
                file_obj.seek(0)
                reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
                rows_count = list(reader)
                total_rows = len(rows_count)

                # BulkImportInitiation.objects.filter(organisation_branch=organisation_branch).all().delete()

                now = datetime.now()
                date_time_str = now.strftime("%m%Y%d%H%M%S")
                data = {"total":total_rows, "initiation": 'Loans-'+ date_time_str, "description":"Loans upload", "narration":"Loans upload", "import_type":"loans", "initiation_added_by":request.user, "organisation_branch":organisation_branch, "status":'in-progress', "as_at":as_at}
                initiation = BulkImportInitiation.objects.create(**data)
                # create upload thread
                upload_accounts = threading.Thread(target=self.upload_loans_thread, args=(request, file_obj, initiation, payment_method, account))
                # starting upload_loans thread 
                upload_accounts.start()
            else:
                print('Invalid file type')
                return Response({"message":"Invalid file type"})            
        
        elif filter_type == 'members' and initiation_id is None:
            file_obj = request.FILES["file"]
            organisation_branch_id = get_current_user(request, 'organisation_branch_id',None) 
            organisation_branch = OrganisationBranch.objects.get(pk=organisation_branch_id)

            if file_obj and self.allowed_file(file_obj):
                # save loan upload
                # current dateTime

                file_obj.seek(0)
                reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
                rows_count = list(reader)
                total_rows = len(rows_count)

                now = datetime.now()
                date_time_str = now.strftime("%m%Y%d%H%M%S")
                data = {"total":total_rows, "initiation": 'Members-'+ date_time_str, "description":"Members upload", "narration":"Members upload", "import_type":"members", "initiation_added_by":request.user, "organisation_branch":organisation_branch, "status":'in-progress'}
                initiation = BulkImportInitiation.objects.create(**data)

                # create upload thread
                upload_accounts = threading.Thread(target=self.upload_membrs_thread, args=(request, file_obj, initiation))
                # starting upload_loans thread 
                upload_accounts.start()

            else:
                print('Invalid file type')
                return Response({"message":"Invalid file type"})

        elif filter_type == 'accounts' and initiation_id is None:
            file_obj = request.FILES["file"]
            organisation_branch_id = get_current_user(request, 'organisation_branch_id',None) 
            organisation_branch = OrganisationBranch.objects.get(pk=organisation_branch_id)

            if file_obj and self.allowed_file(file_obj):
                # save loan upload
                # current dateTime

                file_obj.seek(0)
                reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
                rows_count = list(reader)
                total_rows = len(rows_count)

                now = datetime.now()
                date_time_str = now.strftime("%m%Y%d%H%M%S")
                data = {"total":total_rows, "initiation": 'Accounts-'+ date_time_str, "description":"Accounts bulk creation", "narration":"Accounts bulk creation", "import_type":"accounts", "initiation_added_by":request.user, "organisation_branch":organisation_branch, "status":'in-progress'}
                initiation = BulkImportInitiation.objects.create(**data)

                # create upload thread
                upload_accounts = threading.Thread(target=self.upload_accounts_thread, args=(request, file_obj, initiation))
                # starting upload_loans thread 
                upload_accounts.start()

            else:
                print('Invalid file type')
                return Response({"message":"Invalid file type"})
        elif filter_type == 'member-numbers' and initiation_id is None:
            file_obj = request.FILES["file"]
            organisation_branch_id = get_current_user(request, 'organisation_branch_id',None) 
            organisation_branch = OrganisationBranch.objects.get(pk=organisation_branch_id)

            if file_obj and self.allowed_file(file_obj):
                # save loan upload
                # current dateTime

                file_obj.seek(0)
                reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
                rows_count = list(reader)
                total_rows = len(rows_count)

                now = datetime.now()
                date_time_str = now.strftime("%m%Y%d%H%M%S")
                data = {"total":total_rows, "initiation": 'Member-Number-Updates-'+ date_time_str, "description":"Member Number Bulk Updates", "narration":"Member Number Bulk Updates", "import_type":"member-numbers", "initiation_added_by":request.user, "organisation_branch":organisation_branch, "status":'in-progress'}
                initiation = BulkImportInitiation.objects.create(**data)

                # create upload thread
                upload_member_numbers = threading.Thread(target=self.upload_bulk_member_number_thread, args=(request, file_obj, initiation))
                # # starting upload_loans thread 
                upload_member_numbers.start()

            else:
                print('Invalid file type')
                return Response({"message":"Invalid file type"})

        elif filter_type == 'loans-sec' and initiation_id is None:
            file_obj = request.FILES["file"]
            organisation_branch_id = get_current_user(request, 'organisation_branch_id',None) 
            organisation_branch = OrganisationBranch.objects.get(pk=organisation_branch_id)

            if file_obj and self.allowed_file(file_obj):
                # save loan upload
                # current dateTime
                file_obj.seek(0)
                reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
                rows_count = list(reader)
                total_rows = len(rows_count)

                now = datetime.now()
                date_time_str = now.strftime("%m%Y%d%H%M%S")
                data = {"total":total_rows, "initiation": 'Loans security-'+ date_time_str, "description":"Loans security upload", "narration":"Loans security upload", "import_type":"loans-sec", "initiation_added_by":request.user, "organisation_branch":organisation_branch, "status":'in-progress'}
                initiation = BulkImportInitiation.objects.create(**data)
                # create upload thread
                upload_loan_sec = threading.Thread(target=self.upload_loans_security_thread, args=(request, file_obj, initiation))
                # starting upload_loans thread 
                upload_loan_sec.start()
            else:
                print('Invalid file type')
                return Response({"message":"Invalid file type"})
        return Response({"message":"success"})

    def upload_membrs_thread(self, request, file_obj, initiation):
        try:
            file_obj.seek(0)
            reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))

            failed_transactions = []
            for row in reader:
                values = list(row.values())
                member_name = values[0]
                member_number = values[1]
                old_member_number = values[2]
                region = values[3]
                address = values[4]
                customer_type_id = values[5]
                branch_id = values[6]
                company_id = values[7]
                phone = values[8]
                gender = values[9]
                saving_product_id = values[10]
                group_member_number = values[11]

                # Optional fields (columns 12-15)
                age_raw = values[12].strip() if len(values) > 12 and values[12] else None
                nationality = values[13].strip() if len(values) > 13 and values[13] else None
                disability_raw = values[14].strip() if len(values) > 14 and values[14] else None
                district = values[15].strip() if len(values) > 15 and values[15] else None

                # Parse age
                age = None
                if age_raw:
                    try:
                        age = int(age_raw)
                    except ValueError:
                        pass

                # Parse disability boolean
                disability = None
                if disability_raw is not None:
                    disability = disability_raw.strip().lower() in ['true', '1', 'yes']

                # validation
                valid = True
                message = ""

                customer = Customer.objects.filter(member_number=old_member_number, customer_branch__branch_organisation=initiation.organisation_branch.branch_organisation).first()
                if customer:
                    valid = False
                    message += '@Old Member Number Already Exists'
                    
                branch = OrganisationBranch.objects.filter(id=branch_id).first()
                if not branch:
                    valid = False
                    message += '@No branch found with this branch id'
                
                company = Organisation.objects.filter(id=company_id).first()
                if not company:
                    valid = False
                    message += '@No company found with this company id'

                if not gender in ['M', 'F', 'O']:
                    valid = False
                    message += '@No gender found'

                customer_type = CustomerType.objects.filter(id=customer_type_id, organisation__id=company_id).first()
                if not customer_type:
                    valid = False
                    message += '@No customer type found'
                
                saving_product = SavingsProduct.objects.filter(id=saving_product_id, saving_product_org__id=company_id).first()
                if not saving_product:
                    valid = False
                    message += '@No savings product found'
                
                if not member_name:
                    valid = False
                    message += '@No customer names found'
                
                if group_member_number:
                    group_customer = Customer.objects.filter(member_number=group_member_number, customer_branch__branch_organisation=initiation.organisation_branch.branch_organisation).first()
                    if not group_customer:
                        valid = False
                        message += '@No group found with member number:' + str(group_member_number)
                
                if not valid:
                    # failed loan transaction
                    failed_transaction = {"member_name":member_name, "member_number":member_number, "old_member_number":old_member_number,
                        "saving_product_id":saving_product_id, "region":region, "address":address, "customer_type_id":customer_type_id, "branch_id":branch_id,
                        "company_id":company_id, "phone":phone, "gender":gender, "age":age_raw, "nationality":nationality,
                        "disability":disability_raw, "district":district, "messages":message}
                    
                    failed_transactions.append(failed_transaction)

                if valid:
                    # successfull
                    customer_member_number = member_number
                    if not customer_member_number:
                        customer_member_number = get_customer_next_member_number(branch_id)

                    customer_member_number = re.sub(r'\s+', '',customer_member_number)
                    old_member_number = re.sub(r'\s+', '',old_member_number)

                    data = {"name":member_name, "member_number":customer_member_number, "old_member_number":old_member_number, "saving_product_id":saving_product_id,
                    "region":region, "address":address, "client_type":customer_type_id, "phone":phone, "gender":gender, "age":age, "nationality":nationality,
                    "disability":disability, "district":district, "member_initiation":initiation, "member_initiation_added_by":request.user,
                    "organisation_branch":branch, "status":"in-progress", "group_member_number":group_member_number}

                    BulkTempMembersImport.objects.create(**data)

                if len(failed_transactions) > 0:
                    initiation.failed_transactions = failed_transactions
                    initiation.save()

        except Exception as e:
            
            failed_transaction = [{"member_name":member_name, "member_number":member_number, "old_member_number":old_member_number,
            "saving_product_id":saving_product_id, "region":region, "address":address, "customer_type_id":customer_type_id, "branch_id":branch_id,
            "company_id":company_id, "phone":phone, "gender":gender, "messages":str(e)}]
            initiation.failed_transactions = failed_transaction
            initiation.save()

        return True

    def upload_accounts_thread(self, request, file_obj, initiation):
        try:
            file_obj.seek(0)
            reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))

            failed_transactions = []
            for row in reader:
                member_name = list(row.values())[0]
                member_number = list(row.values())[1]
                account_product_id = list(row.values())[2]

                # validation
                valid = True
                message = ""

                customer = Customer.objects.filter(member_number=member_number, customer_branch=initiation.organisation_branch).first()
                if not customer:
                    valid = False
                    message += '@No customer found with this member number'
                
                account_product = SavingsProduct.objects.filter(id=account_product_id).first()
                if not account_product:
                    valid = False
                    message += '@No savings product found with this savings product id'

                if not valid:
                    # failed loan transaction
                    failed_transaction = {"member_name":member_name, "member_number":member_number, "saving_product_id":account_product_id, "messages":message}
                    failed_transactions.append(failed_transaction)

                if valid:
                    # successfull
                    data = {"name":member_name, "member_number":member_number, "saving_product_id":account_product_id, "customer_id":customer.id, "member_initiation":initiation, "member_initiation_added_by":request.user, "status":"in-progress"}
                    BulkTempAccountsImport.objects.create(**data)

                if len(failed_transactions) > 0:
                    initiation.failed_transactions = failed_transactions
                    initiation.save()

        except Exception as e:
            failed_transaction = [{"member_name":member_name, "member_number":member_number, "saving_product_id":account_product_id, "messages":str(e)}]
            initiation.failed_transactions = failed_transaction
            initiation.save()

        return True
    
    def upload_loans_thread(self, request, file_obj, initiation, payment_method, account):
        try:
            file_obj.seek(0)
            reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))

            failed_transactions = []
            for row in reader:
                member_number = list(row.values())[1]
                loan_amount = list(row.values())[2]
                loan_period = list(row.values())[3]
                loan_period_type = list(row.values())[4]
                int_rate = list(row.values())[5]
                int_method = list(row.values())[6]
                loan_date = list(row.values())[7]
                loan_officer = list(row.values())[8]
                loan_product = list(row.values())[9]
                loan_sector = list(row.values())[10]
                princ_bal = list(row.values())[11]
                int_bal = list(row.values())[12]
                grace_period = list(row.values())[13]
                grace_period_type = list(row.values())[14]
                penalty = list(row.values())[15]
                reference_id = list(row.values())[16]
                payment_frequency = list(row.values())[17]
                disbursement_date = list(row.values())[18]
                group_number = list(row.values())[19]

                # validation
                valid = True
                message = ""
                group_customer = None

                if not disbursement_date or disbursement_date == '':
                    disbursement_date = loan_date
                
                customer = Customer.objects.filter(member_number=member_number, customer_branch__branch_organisation=initiation.organisation_branch.branch_organisation).first()
                if not customer:
                    valid = False
                    message += '@No customer found with this member number'
                if not loan_amount.isdigit() or not loan_period.isdigit() or not princ_bal.isdigit() or not int_bal.isdigit(): 
                    valid = False
                    message += '@Invalid data type, expected numbers of [loan_amount, loan_period, princ_bal, int_bal ]'
                
                if loan_period_type not in ['d', 'm', 'w', 'bw', 'q', 'y']:
                    valid = False
                    message += '@Invalid loan period type'
                if int_method not in ['flat', 'declining', 'amortization']:
                    valid = False
                    message += '@Invalid Interest method'

                officer = Staff.objects.filter(id=loan_officer, staff_organisation=initiation.organisation_branch.branch_organisation,is_active=True).first()
                if not officer:
                    valid = False
                    message += '@No staff found'

                product = LoanProduct.objects.filter(id=loan_product, organisation=initiation.organisation_branch.branch_organisation).first()
                if not product:
                    valid = False
                    message += '@No loan product found'

                sector = LoanSectors.objects.filter(id=loan_sector, organisation=initiation.organisation_branch.branch_organisation).first()
                if not sector:
                    valid = False
                    message += '@No loan sector found'
                
                if group_number and group_number != '':
                    group_customer = Customer.objects.filter((Q(member_number=group_number) | Q(old_member_number=group_number)), customer_branch__branch_organisation=initiation.organisation_branch.branch_organisation).first()
                    if not group_customer:
                        valid = False
                        message += '@No group found with this member number'

                if not payment_frequency or int(payment_frequency) < 1:
                    payment_frequency = 1

                if not valid:
                    # failed loan transaction
                    failed_transaction = {"member_number":member_number, "customer_name":customer.name if customer else '', "loan_amount":loan_amount, "loan_period":loan_period,
                    "loan_period_type":loan_period_type, "int_rate":int_rate, "int_method":int_method, "loan_date":loan_date,"disbursement_date":disbursement_date,
                    "loan_officer":loan_officer, "loan_product":loan_product, "loan_sector":loan_sector, "princ_bal":princ_bal, "int_bal":int_bal, "messages":message,
                    "organisation_branch_id":initiation.organisation_branch.id, "organisation_id":initiation.organisation_branch.branch_organisation.id,
                    "account_id":account, "payment_method":payment_method, "grace_period":grace_period, "grace_period_type":grace_period_type, "penalty":penalty, "group_number":group_number }

                    failed_transactions.append(failed_transaction)

                if valid:
                    # successfull
                    data = {"customer":customer, "loan_amount":loan_amount, "loan_period":loan_period, "period_type":loan_period_type, "int_rate":int_rate, "int_method":int_method,
                    "loan_officer":officer, "loan_application_product":product, "princ_bal":princ_bal, "int_bal":int_bal, "payment_method":payment_method, "account_id":account, 
                    "loan_initiation":initiation, "teller":request.user, "loan_initiation_added_by":request.user, "organisation_branch":initiation.organisation_branch,
                    "status":'in-progress', "loan_date":loan_date, "disbursement_date":disbursement_date, "loan_sector":sector, "grace_period":grace_period, "grace_period_type":grace_period_type,
                    "penalty":penalty, "reference_id": reference_id, "payment_frequency": payment_frequency, "group_customer_number":group_customer}

                    loan_transaction = BulkTempLoansImport.objects.create(**data)
                    
            if len(failed_transactions) > 0:
                initiation.failed_transactions = failed_transactions
                initiation.save()
                
        except Exception as e:
            failed_transaction = [{"member_number":member_number, "customer_name":customer.name if customer else '', "loan_amount":loan_amount, "loan_period":loan_period,
                    "loan_period_type":loan_period_type, "int_rate":int_rate, "int_method":int_method, "loan_date":loan_date,"disbursement_date":disbursement_date,
                    "loan_officer":loan_officer, "loan_product":loan_product, "loan_sector":loan_sector, "princ_bal":princ_bal, "int_bal":int_bal, "messages":str(e),
                    "organisation_branch_id":initiation.organisation_branch.id, "organisation_id":initiation.organisation_branch.branch_organisation.id,
                    "account_id":account, "payment_method":payment_method, "grace_period":grace_period, "grace_period_type":grace_period_type, "penalty":penalty, "group_number":group_number }]
            initiation.failed_transactions = failed_transaction
            initiation.save()
            print(e)
        
        return True

    def upload_bulk_member_number_thread(self, request, file_obj, initiation):
        try:
            file_obj.seek(0)
            reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))

            failed_transactions = []
            for row in reader:
                member_number = list(row.values())[0]
                new_member_number = list(row.values())[1]
                telephone = list(row.values())[2]
                physical_address = list(row.values())[3]
                region = list(row.values())[4]
                
                if not new_member_number:
                    continue
                
                # validation
                valid = True
                message = ""

                customer = Customer.objects.filter(member_number=member_number, customer_branch=initiation.organisation_branch).first()
                if not customer:
                    valid = False
                    message += '@No customer found with this member number'

                if not valid:
                    # failed loan transaction
                    failed_transaction = {"member_number":member_number, "messages":message}
                    failed_transactions.append(failed_transaction)

                if valid:
                    # successfull
                    data = {"member_number":member_number, "telephone":telephone, "physical_address":physical_address, "region":region, "new_member_number":new_member_number, "customer_id":customer.id, "member_initiation":initiation, "member_initiation_added_by":request.user, "status":"in-progress"}
                    BulkTempMemberNumberImport.objects.create(**data)

                if len(failed_transactions) > 0:
                    initiation.failed_transactions = failed_transactions
                    initiation.save()

        except Exception as e:
            failed_transaction = [{"member_number":member_number, "messages":str(e)}]
            initiation.failed_transactions = failed_transaction
            initiation.save()

        return True

    def upload_loans_security_thread(self, request, file_obj, initiation):
        try:
            file_obj.seek(0)
            reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))

            failed_transactions = []
            for row in reader:
                name = list(row.values())[0]
                member_number = list(row.values())[1]
                savings_held = list(row.values())[2]
                shares_held = list(row.values())[3]
                date_withheld = list(row.values())[4]
                loan_id = list(row.values())[5]

                # validation
                valid = True
                message = ""

                customer = Customer.objects.filter(member_number=member_number, customer_branch__branch_organisation__id=initiation.organisation_branch.branch_organisation.id).first()
                if not customer:
                    valid = False
                    message += '@No customer found with this member number'

                if not savings_held.isdigit() or not shares_held.isdigit() or not loan_id.isdigit(): 
                    valid = False
                    message += '@Invalid data type, expected numbers of [Savings held, Shares held, loan ID]'
                
                if not valid:
                    # failed loan transaction
                    failed_transaction = {"member_number":member_number, "messages":message}
                    failed_transactions.append(failed_transaction)

                loan_obj = LoanMigrationHistory.objects.filter(loan_number=loan_id, loan__organisation_branch__branch_organisation__id=initiation.organisation_branch.branch_organisation.id).order_by('-id').first()
                if not loan_obj or not loan_obj.loan:
                    valid = False
                    message += '@No loan found with this loan ID'

                if valid:
                    # successfull
                    data = {"member_number":member_number, "initiation":initiation, "status":"in-progress", "name":name, "savings_held":savings_held, "shares_held":shares_held, "loan_id":loan_obj.loan.id, "date_withheld":date_withheld}
                    BulkTempLoanSecurityImport.objects.create(**data)

                if len(failed_transactions) > 0:
                    initiation.failed_transactions = failed_transactions
                    initiation.save()

        except Exception as e:
            failed_transaction = [{"member_number":member_number, "messages":str(e)}]
            initiation.failed_transactions = failed_transaction
            initiation.save()

        return True

class FixedDepositCronJobView(APIView):
    permission_classes = [AllowAny, IsPostOnly]
    
    def post(self, request, format=None):

        # Auto FD Payments
        fixed_deposit_payment = threading.Thread(target=process_bulk_fixed_deposit_payment, args=())
        
        # starting auto penalties thread 
        fixed_deposit_payment.start()

        return Response({"message":"Cron initiated successfully"})
    
class FixedDepositAutoClosureCronJobView(APIView):
    permission_classes = [AllowAny, IsPostOnly]
    
    def post(self, request, format=None):

        # Auto FD Payments
        fixed_deposit_closure = threading.Thread(target=process_bulk_fixed_deposit_closure, args=())
        
        # starting auto penalties thread 
        fixed_deposit_closure.start()

        return Response({"message":"Cron initiated successfully"})

class AutoUnblockSavingCronJobView(APIView):
    permission_classes = [AllowAny, IsPostOnly]
    
    def post(self, request, format=None):
        savings_account_blocked_amounts = threading.Thread(target=process_bulk_savings_amount_unblocking, args=())
        savings_account_blocked_amounts.start()
        return Response({"message":"Cron initiated successfully"})

class UpdateSavingAccountStatusCronJobView(APIView):
    permission_classes = [AllowAny, IsPostOnly]
    
    def post(self, request, format=None):
        update_savings_account_status_thread = threading.Thread(target=update_savings_account_statuses, args=())
        update_savings_account_status_thread.start()
        return Response({"message":"Cron initiated successfully"})

class AutoDBBackUpCronJobView(APIView):
    permission_classes = [AllowAny, IsPostOnly]
    
    def post(self, request, format=None):
        bd_cron_backup = threading.Thread(target=process_db_cron_back_up, args=())
        bd_cron_backup.start()
        return Response({"message":"Cron initiated successfully"})
    
class SavingsProductQueueInterestPaymentCronJobView(APIView):
    permission_classes = [AllowAny, IsPostOnly]
    
    def post(self, request, format=None):
        interest_payments = threading.Thread(target=queue_saving_products_interest_payments, args=())
        interest_payments.start()
        return Response({"message":"Cron initiated successfully"})
    
class BulkOverDraftImportView(APIView):
        parser_classes = (MultiPartParser,)
        
        def allowed_file(self, filename):
            return '.' in filename.name and \
                filename.name.split('.')[1].lower() in ["csv"]
        
        def get(self, request, format=None):
            reponse_data  = []
            status        = request.GET.get('status', None)
            initiation_id = request.GET.get('initiation_id', None)
            
            if status == 'successful':
                data = BulkTempOverDraftsImport.objects.filter(initiation__id=initiation_id).all()
                serializer = BulkTempOverDraftsImportSerializer(data, many=True)
                reponse_data = serializer.data

            if status == 'failed':
                data_import  = BulkImportInitiation.objects.get(pk=initiation_id)
                if data_import:
                    if data_import.failed_transactions and len(data_import.failed_transactions) > 0:
                        for row in data_import.failed_transactions:
                            organisation_branch = OrganisationBranch.objects.get(pk=row['organisation_branch'])
                            row['organisation_branch_name'] = organisation_branch.name
                            row['organisation_name']        = organisation_branch.branch_organisation.name
                            reponse_data.append(row)
            return Response({"count":len(reponse_data), "results":reponse_data})
        
        def post(self, request, format=None):
            credit_chart_id = self.request.data.get('credit_chart')
            payment_method = self.request.data.get('payment_method')
            file_obj = request.FILES["file"]
            organisation_branch_id = get_current_user(request, 'organisation_branch_id',None) 
            organisation_branch = OrganisationBranch.objects.get(pk=organisation_branch_id)
            credit_chart        = OrganisationSubAccount.objects.get(pk=credit_chart_id)
            record_count = 0

            if file_obj and self.allowed_file(file_obj):
                # save savings upload
                # current dateTime
                now = datetime.now()
                date_time_str = now.strftime("%m%Y%d%H%M%S")
                # row count 
                file_obj.seek(0)
                reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
                for row in reader:
                    value = list(row.values())
                    if value[0]:
                        record_count = record_count + 1

                data = { "total": record_count,"initiation": 'Over Drafts-'+ date_time_str, "description":"Over Drafts upload", "narration":"Over Drafts upload", "import_type":"overdrafts", "initiation_added_by":request.user, "organisation_branch":organisation_branch, "status":'in-progress'}
                initiation = BulkImportInitiation.objects.create(**data)
               
                # create upload thread
                upload_over_drafts = threading.Thread(target=self.upload_over_drafts_thread, args=(request.user,credit_chart,payment_method, file_obj, initiation,organisation_branch))
                # starting upload_savings thread 
                upload_over_drafts.start()
            else:
                print('Invalid file type')
                return Response({"message":"Invalid file type"})

            return Response({"message":"success"})

        def upload_over_drafts_thread(self,user,credit_chart,payment_method,file_obj, initiation,organisation_branch):
            try:
                
                failed_transactions = []
                file_obj.seek(0)
                reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
                for row in reader:
                    value = list(row.values())
                    msg    = ''
                    status = 'Pending'
                    if value[0]:
                        data = {
                            "customer":value[0],
                            "member_number":value[1],
                            "over_draft_period":value[2],
                            "amount":value[3],
                            "principal_paid":value[4],
                            "interest_paid":value[5],
                            "penalty_paid":value[6],
                            "charge_rate":value[7],
                            "charge_type":value[8],
                            "penalty_rate":value[9],
                            "penalty_type":value[10],
                            "penalty_interval":value[11],
                            "penalty_grace_period":value[12],
                            "withdraw_allowance_period":value[13],
                            "record_date":value[14],
                            "over_draft_product":value[15],
                            "saving_product":value[16],
                            "auto_close":value[17],
                            "auto_payments":value[18],
                            "organisation_branch":value[19],
                            "credit_chart":credit_chart.id,
                            "payment_method":payment_method,
                            "payment_method":'offset',
                            "added_by":user,
                            "initiation":initiation,
                            "status":'Pending',
                            "heading":"",
                            "comment":"",
                            "msg":msg
                            }
                        
                        if value[3]:
                            value[3] = value[3].replace(",", "")
                            value[3] = value[3].replace(".", "")
                            data["amount"] = value[3]
                            if float(value[3]) < 1:
                                data["amount"] = 0
                                msg += " @Over Draft amount missing"
                                status = "Failed"
    
                        if not value[3]:
                            data["amount"] = 0
                            msg += " @Over Draft amount missing"
                            status = "Failed"
                        
                        date_data   = value[14]
                        if len(date_data) < 1:
                            msg += " @Invalid date"
                            status = "Failed"
                        
                        #validate member number
                        customer = Customer.objects.filter((Q(member_number=value[1]) | Q(old_member_number=value[1])),customer_branch__branch_organisation=organisation_branch.branch_organisation, is_deleted=False ).first()
                        if not customer:
                            msg = " @Invalid member number"
                            status = "Failed"
                        else:
                            data["customer"] = customer

                        #validate savings product id
                        over_draft_product = OverDraftProducts.objects.filter(id=value[15],organisation = organisation_branch.branch_organisation).first()
                        if not over_draft_product:
                            msg += " @Invalid Over Draft product id"
                            status = "Failed"
                        if  over_draft_product:
                            data["over_draft_product"] = over_draft_product

                        #validate savings account
                        if  customer:
                            data["customer"] = customer
                            if over_draft_product:
                                selected_account = customer

                                account_product = SavingsProduct.objects.filter(id=value[16],saving_product_org = organisation_branch.branch_organisation).first()
                               
                                saving_account = SavingAccount.objects.filter(account_customer = selected_account, account_product=account_product).first()
                                if not saving_account:
                                    msg = "@No saving account number for the selected product"
                                    status = "Failed"
                                else:
                                    data["saving_product"] = account_product

                                if saving_account:
                                    heading = 'Over Draft Application for ' + saving_account.account_customer.name + '-' + saving_account.account_customer.member_number 

                                    data["heading"] =  heading
                                    # data["comment"] = data["added_by"] +' has deposited: ('+str(data["amount"]) +') '+ 'for over draft' + ' on A/C No: ' +str(saving_account.account_no)
                        data["msg"]    = msg
                        data["status"] = status
                        branch = OrganisationBranch.objects.get(pk=value[19])
                        if branch:
                            data['organisation_branch'] = branch
                        else:
                            msg = "@Invalid branch"
                            status = "Failed"

                        if data["status"] == 'Pending': 
                            if data[ "auto_close"] == 'on':
                                data["auto_close"] = True
                            else:
                                data["auto_close"] = False
                            if data["auto_payments"] == 'on':
                                data["auto_payments"] = True
                            else:
                                data["auto_payments"] = False
                            del data["msg"]

                            data["record_date"] = make_aware(datetime.strptime(data["record_date"], '%Y-%m-%d'))
                           
                            BulkTempOverDraftsImport.objects.create(**data)
                        else:
                            data["initiation"]     = data["initiation"].id
                            data["added_by"]       = data["added_by"].id
                            data["customer"]       = value[0]
                            data["over_draft_product"] = value[15]
                            data["saving_product"] = value[16]
                            data["organisation_branch"] = value[19]
                            failed_transactions.append(data)
                if len(failed_transactions) > 0:
                    initiation.failed_transactions = failed_transactions
                    initiation.save()

            except Exception as e:
                print(e)
                raise Http404

class BulkOverDraftImportTransactionsView(APIView):
        
        def get(self, request, format=None):
            reponse_data  = []
            status        = request.GET.get('status', None)
            initiation_id = request.GET.get('initiation_id', None)
            
            if status == 'successful':
                data = BulkTempOverDraftsImport.objects.filter(initiation__id=initiation_id).all()
                serializer = BulkTempOverDraftsImportSerializer(data, many=True)
                reponse_data = serializer.data

            if status == 'failed':
                data_import  = BulkImportInitiation.objects.get(pk=initiation_id)
                if data_import:
                    if data_import.failed_transactions and len(data_import.failed_transactions) > 0:
                        for row in data_import.failed_transactions:
                            organisation_branch = OrganisationBranch.objects.get(pk=row['organisation_branch'])
                            row['organisation_branch_name'] = organisation_branch.name
                            row['organisation_name']        = organisation_branch.branch_organisation.name
                            reponse_data.append(row)
            return Response({"count":len(reponse_data), "results":reponse_data})
        
        def post(self, request, format=None):
            current_status = self.request.data.get('transaction_type')
            initiation_id  = self.request.data.get('initiation_id')

            if  current_status == 'delete':
                data_import    = BulkImportInitiation.objects.filter(id=initiation_id).first()
                if data_import:
                    data_import.delete()
                    return Response({"status":"success","message":"Successfully deleted migration."}, status=status.HTTP_200_OK)
                else:
                    return Response({"status":"failed","message":"Failed to delete migration."}, status=status.HTTP_200_OK)
            
            if  current_status == 'push_to_db':
                # create push savings transactions to main thread
                migrating_savings = threading.Thread(target=self.push_over_drafts_to_main_thread,args=(initiation_id,))
                # starting upload savings transactions thread 
                migrating_savings.start()

                msg = "Migrating Savings."
                res = "success"
                return Response({"status":res,"message":msg}, status=status.HTTP_200_OK)
        
        def push_over_drafts_to_main_thread(self,initiation_id,):
            initiation_obj = BulkImportInitiation.objects.get(pk=initiation_id)
            records = BulkTempOverDraftsImport.objects.filter(initiation__id=initiation_id,status = 'Pending').all()
            debit_chart = get_chart_of_account_by_code( "sys-116", initiation_obj.organisation_branch.branch_organisation) 

            if len(records) > 0:
                for record in records:
                    organisation_id  = record.organisation_branch.branch_organisation.id
                    #Save transactions details
                    selected_account = record.customer
                    # if record.group_customer:
                    #     selected_account = record.group_customer

                    customer_account = SavingAccount.objects.filter(account_customer = selected_account, account_product=record.saving_product).first()
                    reference_no     = generate_reference_no(customer_account.account_product.accounts_chart.account_line, organisation_id,'dep')
                    amount           = record.amount
                    if amount > 0:
                        transaction_fields = {
                            "heading": 'Over Draft Application for ' + customer_account.account_customer.name + '-' + customer_account.account_customer.member_number,
                            "coment":'Over Draft Application for ' + customer_account.account_customer.name + '-' + customer_account.account_customer.member_number,
                            "amount":amount,
                            "debit_chart":debit_chart,
                            "credit_chart":OrganisationSubAccount.objects.get(pk=record.credit_chart),
                            "reference_no":reference_no,
                            "voucher_no":'',
                            "record_date":record.record_date,
                            "payment_method":record.payment_method,
                            "added_by":record.added_by,
                            "branch":record.organisation_branch,
                        }
                        saved_transaction = SystemTransactions.objects.create(**transaction_fields) 
                        if saved_transaction:
                            overdraft_fields = {
                                "auto_close":record.auto_close,
                                "auto_payments":record.auto_payments,
                                "amount":amount,
                                "over_draft_product":record.over_draft_product,
                                "over_draft_period":record.over_draft_period,
                                "withdraw_allowance_period":record.withdraw_allowance_period,
                                "charge_rate":record.charge_rate,
                                "charge_type":record.charge_type,
                                "penalty_rate":record.penalty_rate,
                                "penalty_type":record.penalty_type,
                                "penalty_interval":record.penalty_interval,
                                "penalty_grace_period":record.penalty_grace_period,
                                "saving_account":customer_account,
                                "reference_transaction":saved_transaction,
                                "transaction_date":record.record_date,
                                "over_draft_added_by":record.added_by,
                                "branch":record.organisation_branch,
                            }
                            save_overdraft = OverDrafts.objects.create(**overdraft_fields) 
                            if save_overdraft:
                                record.status = 'Processed'
                                record.save()
                                #Process over draft payments 
                                over_draft_details = OverDraftsSerializer(save_overdraft).data
                                princ_to_pay   = record.principal_paid
                                int_to_pay     = record.interest_paid
                                pen_to_pay     = record.penalty_paid
                              
                                if pen_to_pay > 0:
                                    int_transaction_details = {
                                        "heading": "Over Draft Penalty Payment: " + save_overdraft.saving_account.account_customer.name + " - " + save_overdraft.saving_account.account_no,
                                        "amount": pen_to_pay,
                                        "record_date":  timezone.now(),
                                        "debit_chart_id": record.credit_chart,
                                        "credit_chart_id":save_overdraft.over_draft_product.penalty_income_chart.id,
                                        "payment_method": 'offset',
                                        "voucher_no": "",
                                        "ref_no_prefix": 'ov-pen-py-t',
                                        "organisation_id": save_overdraft.branch.branch_organisation.id,
                                        "branch_id":save_overdraft.branch.id,
                                        "user_id": record.added_by.id
                                    }

                                    # Save deduction transaction
                                    pen_transaction = post_transaction(int_transaction_details)
                                    if pen_transaction:
                                        over_draft_details = {
                                            "principal_paid":0,
                                            "interest_paid": 0,
                                            "penalty_paid":pen_to_pay,
                                            "over_draft": save_overdraft,
                                            "transaction":pen_transaction,
                                            "date_added":timezone.now(),
                                        }
                                        OverDraftPayment.objects.create(**over_draft_details)
                                
                                if int_to_pay > 0:
                                    int_transaction_details = {
                                        "heading": "Over Draft Interest Payment: " + save_overdraft.saving_account.account_customer.name + " - " + save_overdraft.saving_account.account_no,
                                        "amount": int_to_pay,
                                        "record_date":  timezone.now(),
                                        "debit_chart_id":record.credit_chart,
                                        "credit_chart_id":save_overdraft.over_draft_product.interest_income_chart.id,
                                        "payment_method": 'offset',
                                        "voucher_no": "",
                                        "ref_no_prefix": 'ov-drf-int-py-t',
                                        "organisation_id": save_overdraft.branch.branch_organisation.id,
                                        "branch_id":save_overdraft.branch.id,
                                        "user_id": record.added_by.id
                                    }

                                    # Save deduction transaction
                                    interest_transaction = post_transaction(int_transaction_details)
                                    if interest_transaction:
                                        over_draft_details = {
                                            "principal_paid":0,
                                            "interest_paid": int_to_pay,
                                            "penalty_paid":0,
                                            "over_draft": save_overdraft,
                                            "transaction":interest_transaction,
                                            "date_added":timezone.now(),
                                        }
                                        OverDraftPayment.objects.create(**over_draft_details)
            
                                if princ_to_pay > 0:
                                    credit_chart_code = "sys-116"
                                    credit_chart = get_chart_of_account_by_code(credit_chart_code, save_overdraft.branch.branch_organisation)
                                    int_transaction_details = {
                                        "heading": "Over Draft Principal Payment: " + save_overdraft.saving_account.account_customer.name + " - " + save_overdraft.saving_account.account_no,
                                        "amount": princ_to_pay,
                                        "record_date":  timezone.now(),
                                        "debit_chart_id": record.credit_chart,
                                        "credit_chart_id":credit_chart.id,
                                        "payment_method": 'offset',
                                        "voucher_no": "",
                                        "ref_no_prefix": 'ov-princ-py-t',
                                        "organisation_id": save_overdraft.branch.branch_organisation.id,
                                        "branch_id":save_overdraft.branch.id,
                                        "user_id":record.added_by.id
                                    }

                                    # Save deduction transaction
                                    princ_transaction = post_transaction(int_transaction_details)
                                    if princ_transaction:
                                        over_draft_details = {
                                            "principal_paid":princ_to_pay,
                                            "interest_paid": 0,
                                            "penalty_paid":0,
                                            "over_draft": save_overdraft,
                                            "transaction":princ_transaction,
                                            "date_added":timezone.now(),
                                        }
                                        OverDraftPayment.objects.create(**over_draft_details)
                records = BulkTempOverDraftsImport.objects.filter(initiation__id=initiation_id,status = 'Pending').all()
                if len(records) < 1:
                    initiation_obj.status='Processed'
                    initiation_obj.save()
            
            else:
                initiation_obj.status='Processed'
                initiation_obj.save()


class BulkNegativeSavingsImportView(APIView):
        parser_classes = (MultiPartParser,)
        
        def allowed_file(self, filename):
            return '.' in filename.name and \
                filename.name.split('.')[1].lower() in ["csv"]
        
        def get(self, request, format=None):
            organisation_branch_id = get_current_user(request, 'organisation_branch_id',None) 
            data = BulkImportInitiation.objects.filter(import_type='neg-savings', organisation_branch__id=organisation_branch_id).order_by('-id')
            serializer = BulkImportInitiationSerializer(data, many=True)
            reponse_data = serializer.data

            return Response({"count": len(reponse_data), "results": reponse_data})

        def post(self, request, format=None):
            initiation_id = request.data.get('id', None)
            if initiation_id:
                #migrating_savings = threading.Thread(target=push_negative_savings_to_main_db, args=(initiation_id,))
                # starting upload savings transactions thread 
                #migrating_savings.start()
                push_negative_savings_to_main_db(initiation_id)
            else:
                credit_chart_id = self.request.data.get('credit_chart')
                credit_chart         = OrganisationSubAccount.objects.get(pk=credit_chart_id)
                file_obj = request.FILES["file"]
                organisation_branch_id = get_current_user(request, 'organisation_branch_id',None) 
                organisation_branch = OrganisationBranch.objects.get(pk=organisation_branch_id)
                record_count = 0

                if file_obj and self.allowed_file(file_obj):
                    # save savings upload
                    # current dateTime
                    now = datetime.now()
                    date_time_str = now.strftime("%m%Y%d%H%M%S")
                    # row count 
                    file_obj.seek(0)
                    reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
                    for row in reader:
                        value = list(row.values())
                        if value[0]:
                            record_count = record_count + 1

                    data = { "total": record_count, "initiation": 'Negative Savings-'+ date_time_str, "description":"Negative Savings upload", "narration":"Negative Savings upload", "import_type":"neg-savings", "initiation_added_by":request.user, "organisation_branch":organisation_branch, "status":'in-progress'}
                    initiation = BulkImportInitiation.objects.create(**data)
                
                    # create upload thread
                    upload_savings = threading.Thread(target=self.upload_savings_thread, args=(request.user, file_obj, initiation,organisation_branch,credit_chart))
                    # starting upload_savings thread 
                    upload_savings.start()
                else:
                    print('Invalid file type')
                    return Response({"message":"Invalid file type"})

            return Response({"message":"success"})

        def upload_savings_thread(self,user, file_obj, initiation,organisation_branch,credit_chart):
            try:
                
                failed_transactions = []
                file_obj.seek(0)
                reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
                for row in reader:
                    value = list(row.values())
                    msg    = ''
                    status = 'Pending'
                    if value[0]:
                        data = {
                            "customer":value[0],
                            "amount":value[2],
                            "teller_assign_id":user.id,
                            "deposited_by":value[1],
                            "added_by":user,
                            "record_date":value[3],
                            "organisation_branch":organisation_branch,
                            "credit_chart":credit_chart,
                            "initiation":initiation,
                            "status":'Pending',
                            "heading":"",
                            "comment":"",
                            "product":value[4],
                            "msg":msg
                            }

                        if value[2]:
                            value[2] = value[2].replace(",", "")
                            data["amount"] = float(value[2])
                            '''if float(value[2]) < 1:
                                data["amount"] = 0
                                msg += " @Deposit amount missing"
                                status = "Failed" '''
    
                        if not value[2]:
                            data["amount"] = 0
                            msg += " @Deposit amount missing"
                            status = "Failed"
                        
                        date_data   = value[3].split("/")
                        if len(date_data) != 3:
                            msg += " @Invalid date"
                            status = "Failed"

                        #validate member number
                        customer = Customer.objects.filter(member_number=value[0],customer_branch__branch_organisation=organisation_branch.branch_organisation, is_deleted=False ).first()
                        if not customer:
                            msg = " @Invalid member number"
                            status = "Failed"
                        #validate savings product id
                        account_product = SavingsProduct.objects.filter(id=value[4],saving_product_org = organisation_branch.branch_organisation).first()
                        if not account_product:
                            msg += " @Invalid savings product id"
                            status = "Failed"
                        if  account_product:
                            data["product"] = account_product
                        #validate savings account
                        if  customer:
                            data["customer"] = customer
                            if account_product:
                                selected_account = customer
                                
                                saving_account = SavingAccount.objects.filter(account_customer = selected_account, account_product=account_product).first()
                                if not saving_account:
                                    msg = "@Invalid saving account number"
                                    status = "Failed"

                                if saving_account:
                                    data["heading"] =  'Migrated savings: (-'+str(data["amount"]) +') for ' + str(data["deposited_by"]) + ' on A/C No: ' +str(saving_account.account_no)
                                    data["comment"] = data["deposited_by"] +' has deposited migrated: (-'+str(data["amount"]) +') '+ 'for saving' + ' on A/C No: ' +str(saving_account.account_no)
                        
                        data["msg"] = msg
                        data["status"] = status
                        if data["status"] == 'Pending': 
                            string_date = date_data[2] + '-' + date_data[1] + '-' + date_data[0]
                            data["record_date"] = datetime.strptime(string_date, '%Y-%m-%d')
                            BulkTempNegativeSavingsImports.objects.create(**data)
                        else:
                            data["initiation"]     = data["initiation"].id
                            data["added_by"]       = data["added_by"].id
                            data["customer"]       = value[0]
                            data["product"]        = value[4]
                            data["organisation_branch"] = data["organisation_branch"].id
                            failed_transactions.append(data)
                if len(failed_transactions) > 0:
                    initiation.failed_transactions = failed_transactions
                    initiation.save()

            except Exception as e:
                print(e)
                raise Http404
            
class LicensePaymentsReminderCronJobView(APIView):
    permission_classes = [AllowAny, IsPostOnly]
    
    def post(self, request, format=None):

        # auto payments
        license_reminders = threading.Thread(target=license_payments_reminder, args=())
        # starting auto payments thread 
        license_reminders.start() 

        return Response({"message":"Cron initiated successfully"})


class OrganisationLicenseStatusCronJobView(APIView):
    permission_classes = [AllowAny, IsPostOnly]

    def post(self, request, format=None):
        organisation_id = request.data.get('organisation_id')
        if organisation_id not in [None, '']:
            organisation_id = int(organisation_id)

        license_status_sync = threading.Thread(
            target=sync_organisation_license_status,
            args=(organisation_id,),
        )
        license_status_sync.start()

        return Response({"message": "Organisation license status sync initiated successfully"})


class BulkLicensesImportView(APIView):
        parser_classes = (MultiPartParser,)
        
        def allowed_file(self, filename):
            return '.' in filename.name and \
                filename.name.split('.')[1].lower() in ["csv"]
        
        def get(self, request, format=None):
            reponse_data  = []
            status        = request.GET.get('status', None)
            initiation_id = request.GET.get('initiation_id', None)
            
            if status == 'successful':
                data = BulkTempLicenseImport.objects.filter(initiation__id=initiation_id).all()
                serializer = BulkTempLicenseImportSerializer(data, many=True)
                reponse_data = serializer.data

            if status == 'failed':
                data_import  = BulkImportInitiation.objects.get(pk=initiation_id)
                if data_import:
                    if data_import.failed_transactions and len(data_import.failed_transactions) > 0:
                        for row in data_import.failed_transactions:
                            organisation_branch = OrganisationBranch.objects.get(pk=row['organisation_branch'])
                            row['organisation_branch_name'] = organisation_branch.name
                            row['organisation_name']        = organisation_branch.branch_organisation.name
                            reponse_data.append(row)
            return Response({"count":len(reponse_data), "results":reponse_data})
        
        def post(self, request, format=None):
            # credit_chart_id = self.request.data.get('credit_chart')
            organisation_id = self.request.data.get('organisation_id')
            file_obj = request.FILES["file"]
            organisation_branch_id = get_current_user(request, 'organisation_branch_id',None) 
            # organisation = OrganisationBranch.objects.get(pk=organisation_id)

            # credit_chart        = OrganisationSubAccount.objects.get(pk=credit_chart_id)
            record_count = 0

            if file_obj and self.allowed_file(file_obj):
                # save savings upload
                # current dateTime
                now = datetime.now()
                date_time_str = now.strftime("%m%Y%d%H%M%S")
                # row count 
                file_obj.seek(0)
                reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
                for row in reader:
                    value = list(row.values())
                    if value[0]:
                        record_count = record_count + 1

                data = { "total": record_count,"initiation": 'Organisation Licenses-'+ date_time_str, "description":"Organisation Licenses upload", "narration":"Organisation Licenses upload", "import_type":"licenses", "initiation_added_by":request.user, "organisation":organisation_branch_id, "status":'in-progress'}
                initiation = BulkImportInitiation.objects.create(**data)
               
                # create upload thread
                upload_licenses = threading.Thread(target=self.upload_license_thread, args=(request.user, file_obj, initiation,organisation_id))
                # starting upload_savings thread 
                upload_licenses.start()
            else:
                print('Invalid file type')
                return Response({"message":"Invalid file type"})

            return Response({"message":"success"})

        def upload_license_thread(self,user,file_obj, initiation,organisation_id):
            try:
                
                failed_transactions = []
                file_obj.seek(0)
                reader = csv.DictReader(io.StringIO(file_obj.read().decode('utf-8')))
                for row in reader:
                    value = list(row.values())
                    msg    = ''
                    status = 'Pending'
                    if value[0]:
                        data = {
                            "calculation_types":value[0],
                            "amount":value[1],
                            "period":value[2],
                            "period_type":value[3],
                            "grace_period":value[4],
                            "grace_period_type":value[5],
                            "package":value[6],
                            "record_date":value[7],
                            "organisation":value[8],
                            "license_added_by":user,
                            "initiation":initiation,
                            "status":'active',
                            "heading":"",
                            "comment":"",
                            "msg":msg,
                            "payment_method":value[9],
                            "selected_account":value[10]
                            }
                        
                        if value[1]:
                            value[1] = value[1].replace(",", "")
                            value[1] = value[1].replace(".", "")
                            data["amount"] = value[1]
                            if float(value[1]) < 1:
                                data["amount"] = 0
                                msg += " @License amount missing"
                                status = "Failed"
    
                        if not value[1]:
                            data["amount"] = 0
                            msg += " @License amount missing"
                            status = "Failed"
                        
                        date_data   = value[7]
                        if len(date_data) < 1:
                            msg += " @Invalid date"
                            status = "Failed"

                        if data["heading"]:
                            organisation_details = Organisation.objects.get(pk=organisation_id)
                            heading = 'License for ' + organisation_details.name + 'successfully created'

                        data["heading"] =  heading
                        data["msg"]    = msg
                        data["status"] = status
                      
                        if data["status"] == 'active': 
                            data["record_date"] = make_aware(datetime.strptime(data["record_date"], '%Y-%m-%d'))
                           
                            BulkTempLicenseImport.objects.create(**data)
                        else:
                            data["initiation"]       = data["initiation"].id
                            data["license_added_by"] = data["license_added_by"].id
                            data["organisation"]     = value[8]
                            failed_transactions.append(data)
                if len(failed_transactions) > 0:
                    initiation.failed_transactions = failed_transactions
                    initiation.save()

            except Exception as e:
                print(e)
                raise Http404
            
class BulkLicenseImportTransactionsView(APIView):
        
        def get(self, request, format=None):
            reponse_data  = []
            status        = request.GET.get('status', None)
            initiation_id = request.GET.get('initiation_id', None)
            
            if status == 'successful':
                data = BulkTempLicenseImport.objects.filter(initiation__id=initiation_id).all()
                serializer = BulkTempLicenseImportSerializer(data, many=True)
                reponse_data = serializer.data

            if status == 'failed':
                data_import  = BulkImportInitiation.objects.get(pk=initiation_id)
                if data_import:
                    if data_import.failed_transactions and len(data_import.failed_transactions) > 0:
                        for row in data_import.failed_transactions:
                            organisation_branch = OrganisationBranch.objects.get(pk=row['organisation_branch'])
                            row['organisation_branch_name'] = organisation_branch.name
                            row['organisation_name']        = organisation_branch.branch_organisation.name
                            reponse_data.append(row)
            return Response({"count":len(reponse_data), "results":reponse_data})
        
        def post(self, request, format=None):
            current_status = self.request.data.get('transaction_type')
            initiation_id  = self.request.data.get('initiation_id')
            organisation_branch = get_current_user(request, 'organisation_branch_id',None)

            if  current_status == 'delete':
                data_import    = BulkImportInitiation.objects.filter(id=initiation_id).first()
                if data_import:
                    data_import.delete()
                    return Response({"status":"success","message":"Successfully deleted migration."}, status=status.HTTP_200_OK)
                else:
                    return Response({"status":"failed","message":"Failed to delete migration."}, status=status.HTTP_200_OK)
            
            if  current_status == 'push_to_db':
                # create push savings transactions to main thread
                migrating_savings = threading.Thread(target=self.push_licenses_to_main_thread,args=(initiation_id,organisation_branch))
                # starting upload savings transactions thread 
                migrating_savings.start()

                msg = "Migrating Savings."
                res = "success"
                return Response({"status":res,"message":msg}, status=status.HTTP_200_OK)
        
        def push_licenses_to_main_thread(self,initiation_id,organisation_branch):
            initiation_obj = BulkImportInitiation.objects.get(pk=initiation_id)
            records = BulkTempLicenseImport.objects.filter(initiation__id=initiation_id).all()

            if len(records) > 0:
                for record in records:
                    organisation_id  = record.organisation.id
                    #Save transactions details
                    selected_account = record.selected_account
                    payment_method   = record.payment_method
                    # if record.group_customer:
                    #     selected_account = record.group_customer
                    credit_chart_code = "4257"
                    credit_chart = OrganisationSubAccount.objects.filter(account_code=credit_chart_code, account_organisation=organisation_id).first()
                    if credit_chart is None:
                        credit_chart = get_license_charts(organisation_id) 
                    debit_chart_chart = OrganisationSubAccount.objects.get(pk=selected_account) 
                    reference_no = generate_reference_no(credit_chart.account_line, organisation_id)
                    customer_account = SavingAccount.objects.filter(account_customer = selected_account, account_product=record.saving_product).first()
                    amount           = record.amount
                    if amount > 0:
                        transaction_fields = {
                            "heading": 'Over Draft Application for ' + customer_account.account_customer.name + '-' + customer_account.account_customer.member_number,
                            "coment":'Over Draft Application for ' + customer_account.account_customer.name + '-' + customer_account.account_customer.member_number,
                            "amount":amount,
                            "debit_chart":debit_chart_chart,
                            "credit_chart":credit_chart,
                            "reference_no":reference_no,
                            "voucher_no":'',
                            "record_date":record.subcription_date,
                            "payment_method":payment_method,
                            "added_by":record.license_added_by,
                            "branch":organisation_branch,
                        }
                        saved_transaction = SystemTransactions.objects.create(**transaction_fields) 
                        if saved_transaction:
                            license_period = math.floor(int(record.period))
                            end_date_str = None
                            if record.period_type == 'm':
                                end_date_str = record.subcription_date + relativedelta(months=license_period)
                            elif record.period_type == 'd':
                                end_date_str = record.subcription_date + relativedelta(days=license_period)
                            end_date = end_date_str.strftime("%Y-%m-%d %H:%M:%S.%f")
                            license_fields={
                              "period_type":record.period_type,
                               "package":record.package,
                               "calculation_types":record.calculation_types,
                               "period":record.period,
                               "grace_period":record.grace_period,
                               "amount":record.amount,
                               "organisation":Organisation.objects.get(pk=record.organisation),
                               "license_added_by":self.request.user,
                               "record_date":record.subcription_date,
                               "parent_org":Organisation.objects.get(pk=organisation_id),
                               "grace_period_type":record.grace_period_type,
                               "selected_account":selected_account,
                               "payment_method":payment_method,
                               "end_date":end_date,
                               "transaction":saved_transaction
                            }
                            LicenseSubscription.objects.create(**license_fields) 
                            
                records = BulkTempLicenseImport.objects.filter(initiation__id=initiation_id,status = 'Pending').all()
                if len(records) < 1:
                    initiation_obj.status='Processed'
                    initiation_obj.save()
            
            else:
                initiation_obj.status='Processed'
                initiation_obj.save()

class CustomerObligationsTransactionsCronJobView(APIView):
    permission_classes = [AllowAny, IsPostOnly]
    
    def post(self, request, format=None):

        # auto payments
        auto_payments = threading.Thread(target=customer_obligation_transactions, args=())
        # starting auto payments thread
        auto_payments.start() 

        return Response({"message":"Cron initiated successfully"})
