from django.shortcuts import render
import json
from django.conf import settings
from django.shortcuts import render
from sales.models import *
from expenses.models import *
from store.models import *
from masters.models import *
from stock.models import *
from material.models import *
from wholesale.models import *
from collections import defaultdict
from sales.forms import *
from django.db import IntegrityError, transaction
import hashlib
import os
from django.conf import settings
from datetime import datetime
from django.utils import timezone
from django.db.models import Max, F, Q,Sum
import base64
from django.shortcuts import render
from io import BytesIO
from django.conf import settings
from django.template.loader import get_template
from django.http import HttpResponse,HttpResponseRedirect,JsonResponse
from django.shortcuts import get_object_or_404
from django.templatetags.static import static
from xhtml2pdf import pisa
from num2words import num2words
from django.contrib.staticfiles import finders
from common.utils import *
from django.views.decorators.csrf import csrf_exempt
from supplier.models import *
from django.db import connection
from customer.models import *
from django.db import transaction as db_transaction
from scheme.models import *
from wholesale.models import *
#```````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````

def sales_order(request):
    if 'user_id' in request.session:
        user_type = request.session.get('user_type')
        branch_id = request.session.get('branch_id')
        is_ho = request.session.get('is_ho')
        if user_type == 'stores':
            branch = selectList(branch_table, order_by='name').exclude(id=branch_id)
            if is_ho == 1:
                supplier = selectList(supplier_table, order_by='name' )
            else:
                supplier = selectList(supplier_table, Q(is_global=1) , order_by='name' )
            encoded_id = request.GET.get('id') or 0
            if encoded_id != 0:
                decoded_id = decode_base64_id(encoded_id) or 0
            else:
                decoded_id = 0

            return render(request, 'sales_order/details.html',{'supplier':supplier,'branch':branch,'decoded_id':decoded_id})
        elif user_type == 'wholesale':
            branch = selectList(branch_table, {'is_wholesale':1}, order_by='name').exclude(id=branch_id)
            if is_ho == 1:
                supplier = selectList(supplier_table, order_by='name' )
            else:
                supplier = selectList(supplier_table, Q(is_global=1) , order_by='name' )
            encoded_id = request.GET.get('id') or 0
            if encoded_id != 0:
                decoded_id = decode_base64_id(encoded_id) or 0
            else:
                decoded_id = 0

            return render(request, 'internal_wholesale/sales/details.html',{'supplier':supplier,'branch':branch,'decoded_id':decoded_id})
        else:
            return HttpResponseRedirect("/")
    else:
        return HttpResponseRedirect("/")
    


from django.db.models import Sum, Q

def ajax_sales_view(request):  
    role_id         = request.session.get('role_id')
    user_type         = request.session.get('user_type')
    branch_id       = request.session.get('branch_id')
    fyf_name        = request.session.get('fyf')
    financial_year  = calculate_financial_year(fyf_name)
    keyword         = request.POST.get('keyword_search', '').strip()
    search_type     = request.POST.get('search_type')
    from_date       = request.POST.get('from_date')
    to_date         = request.POST.get('to_date')
    store_id        = request.POST.get('store_id')
    sales_id        = request.POST.get('sales_id')
    filter_sales_status = request.POST.get('filter_sales_status')

   


    has_access, error_message = check_user_access(role_id, 'sales', "read")    
    if not has_access:
        return JsonResponse({'message': 'permission', 'error_message': 'You do not have permission to view sales orders.'})

    query = Q(status=1, branch_id=branch_id, current_fy=financial_year)

    if sales_id:
        query &= Q(id=sales_id)
        
    if from_date and to_date:
        query &= Q(inv_date__range=[from_date, to_date])
    if store_id:
        query &= Q(store_id=store_id)
    if filter_sales_status:
        query &= Q(sales_status=filter_sales_status)

    if keyword:
        if search_type == 'name': query &= Q(customer_name__icontains=keyword)
        elif search_type == 'phone': query &= Q(phone__icontains=keyword)
        elif search_type == 'sales_no': query &= Q(inv_no__icontains=keyword)
    
    # Get main sales records
    data = list(selectList(sales_order_table, query).values())
   
    formatted = []
    for index, item in enumerate(data):
        sales_id = item['id']

        cash_total = transaction_table.objects.filter(
            tm_sales_id=sales_id, 
            status=1,
            payment_type='cash'
        ).aggregate(
            total_cash=Sum('amount'),
        )

        bank_total = transaction_table.objects.filter(
            tm_sales_id=sales_id, 
            status=1,
            payment_type='bank'
        ).aggregate(
            total_bank=Sum('amount'),
        )
        finance_total = transaction_table.objects.filter(
            tm_sales_id=sales_id, 
            status=1,
            payment_type='finance'
        ).aggregate(
            total_finance=Sum('amount'),
        )
        card_total = transaction_table.objects.filter(
            tm_sales_id=sales_id, 
            status=1,
            payment_type='card'
        ).aggregate(
            total_card=Sum('amount'),
        )



        # Exchange is usually filtered by payment_type or a specific logic
        txn_totals = transaction_table.objects.filter(
            tm_sales_id=sales_id, 
            status=1
        ).aggregate(
            total_collected=Sum('collect_amount'),
            total_exchange=Sum('amount', filter=Q(payment_type__iexact='exchange'))
        )

        # 2. Get Sum of Claim Amount from Child Sales (tx_sales)
        claim_totals = child_sales_order_table.objects.filter(
            tm_sales_id=sales_id, 
            status=1
        ).aggregate(
            total_claim=Sum('claim_amount')
        )

        # Set default values to 0.00 if None
        collected = txn_totals['total_collected'] or 0.00
        exchange  = txn_totals['total_exchange'] or 0.00
        claim     = claim_totals['total_claim'] or 0.00

        # Action Buttons Logic
        if item.get('sales_status') == 'rejected':
            action_html = (
                '<div class="d-flex gap-1 justify-content-center align-items-center">'
                '<button type="button" onclick="showRejectionDetails(\'{}\')" class="btn btn-outline-warning btn-xs p-1" title="Rejection Info"><i class="fas fa-info-circle"></i></button>'
                '<button type="button" onclick="view_data(\'{}\')" class="btn btn-outline-primary btn-xs p-1" title="View Details"><i class="fas fa-eye"></i></button>'
                '</div>'
            ).format(sales_id, sales_id)
        else:
            action_html = (
                '<div class="d-flex gap-1 justify-content-center align-items-center">'
                '<button type="button" onclick="edit_data(\'{}\')" class="btn btn-outline-success btn-xs p-1"><i class="fas fa-edit"></i></button>'
                '<button type="button" onclick="delete_data(\'{}\')" class="btn btn-outline-danger btn-xs p-1"><i class="fas fa-trash-alt"></i></button>'
                '<button type="button" onclick="print_data(\'{}\')" class="btn btn-outline-info btn-xs p-1" title="Print"><i class="fas fa-print"></i></button>'
                '<button type="button" onclick="thermal_print_data(\'{}\')" class="btn btn-outline-primary btn-xs p-1" title="Thermal Print"><i class="fas fa-file-invoice"></i></button>'
                '</div>'
            ).format(sales_id, sales_id, sales_id, sales_id)

        formatted.append({
            'id': index + 1,
            'action': action_html,
            'date'      : format_date_month_year(item['inv_date']) if item['inv_date'] else '-', 
            'inv_no'     : item['inv_no'] if item['inv_no'] else '-', 
            'customer'  : item['customer_name'] if item['customer_name'] else '-', 
            'phone'     : item['customer_phone'] if item['customer_phone'] else '-', 
            'customer_type'     : item['customer_type'] if item['customer_type'] else '-', 
            'quantity'  : item['total_quantity'] if item['total_quantity'] else '0', 
            'amount'    : format_amount(item['total_amount']), 
            'claim'     : format_amount(claim), 
            'cash'      : format_amount(cash_total['total_cash'] or 0.00), 
            'bank'      : format_amount(bank_total['total_bank'] or 0.00), 
            'finance'      : format_amount(finance_total['total_finance'] or 0.00), 
            'card'      : format_amount(card_total['total_card'] or 0.00), 
            'collection': format_amount(collected), 
            'exchange'  : format_amount(exchange), 
            'balance'   : format_amount(item['balance']), 
            'remarks'   : item['remarks'] if item['remarks'] else '-',    
            'status': format_badge(
                item['sales_status'],
                mapping={
                    'pending': 'badge text-bg-info',
                    'approved': 'badge text-bg-success',
                    'rejected': 'badge text-bg-danger'
                },
                label_mapping={
                    'pending': 'Pending',
                    'approved': 'Approved',
                    'rejected': 'Rejected'
                }
            ),
            'rejected_by' : getItemNameById(employee_table, item['rejected_by']) if item.get('rejected_by') else '-',
            'rejected_on' : format_datetime(item['rejected_on']) if item.get('rejected_on') else '-',
            'rejected_remarks' : item.get('rejected_remarks') or '-',
        })

    return JsonResponse({'data': formatted})



def sales_order_add(request):
    if 'user_id' in request.session:
        user_type = request.session.get('user_type')
        branch_id = request.session.get('branch_id')
        if user_type == 'stores':
            customer = selectList(customer_table, {'branch_id':branch_id} ,order_by='name')
            branch = selectList(branch_table, order_by='name').exclude(id=branch_id)
            item = selectList(item_table)
            brand= selectList(brand_table, order_by='name')
            employee = employee_list(request)
            subcategory = selectList(sub_category_table, order_by='name')
            is_ho = request.session.get('is_ho')
            if is_ho == 1:
                bank = selectList(bank_table, {'is_default':0}, order_by='name')
            else:
                bank = selectList(bank_table, {'is_default':1}, order_by='name')
            finance = selectList(finance_table,{'f_type': 'finance'}, order_by='name')
            card = selectList(finance_table,{'f_type': 'card'}, order_by='name')
            
            return render(request, 'sales_order/add.html',{'customer':customer,'branch':branch,'item':item,'brand':brand,'employee':employee,'subcategory':subcategory,'bank':bank,'finance':finance,'card':card})
        if user_type == 'wholesale':
            customer = selectList(customer_table, {'branch_id':branch_id} ,order_by='name')
            branch = selectList(branch_table, order_by='name').exclude(id=branch_id)
            item = selectList(item_table)
            brand= selectList(brand_table, order_by='name')
            employee = employee_list(request)
            subcategory = selectList(sub_category_table, order_by='name')
            is_ho = request.session.get('is_ho')            
            bank = selectList(bank_table, {'is_default':1}, order_by='name')
            finance = selectList(finance_table,{'f_type': 'finance'}, order_by='name')
            card = selectList(finance_table,{'f_type': 'card'}, order_by='name')
            return render(request, 'internal_wholesale/sales/add.html',{'customer':customer,'branch':branch,'item':item,'brand':brand,'employee':employee,'subcategory':subcategory,'bank':bank,'finance':finance,'card':card} )
        else:
            return HttpResponseRedirect("/")
    else:
        return HttpResponseRedirect("/")
    


def add_sales_order(request):
    if request.method != 'POST':
        return JsonResponse({'message': 'Invalid request'}, status=400)

    try:
        company_id      = request.session.get('company_id')
        branch_id       = request.session.get('branch_id')
        user_id         = request.session.get('user_id')
        role_id         = request.session.get('role_id')
        fyf_name        = request.session.get('fyf')
        financial_year  = calculate_financial_year(fyf_name)
        po_date         = request.POST.get('sales_date')
        customer_name   = request.POST.get('customer_name')
        customer_type   = request.POST.get('customer_type') or 'retail'
        customer_id     = int(request.POST.get('customer_id') or 0)
        phone           = request.POST.get('customer_phone') or None
        customer_type   = request.POST.get('customer_type') or None
        remarks         = request.POST.get('description')
        total_qty       = float(request.POST.get('total_quantity') or 0)
        sub_total       = float(request.POST.get('sub_total') or 0)
        total_amount    = float(request.POST.get('total_payable') or 0)
        total_paid      = float(request.POST.get('total_paid') or 0)
        balance         = float(request.POST.get('balance') or 0)
        charges         = float(request.POST.get('charges') or 0)
        total_discount  = float(request.POST.get('total_discount') or 0)
        total_tax_cgst  = float(request.POST.get('total_tax_cgst') or 0)
        total_tax_sgst  = float(request.POST.get('total_tax_sgst') or 0)
        employee_id     = request.POST.get('employee_id') or user_id
        sales_channel   = request.POST.get('sales_channel') or 'retail'
        ws_po_id        = int(request.POST.get('ws_po_id') or 0)
        ws_po_no        = None
        now             = timezone.localtime(timezone.now())

        if not check_user_access(role_id, 'sales', "create"):
            return JsonResponse({'message': 'permission', 'error_message': 'Access denied'})
        # -----------------------------
        # Create customer if needed
        # -----------------------------
       
        # Items
        items       = json.loads(request.POST.get('items') or '[]')
        if not items:
            return JsonResponse({'message': 'warning', 'error_message': 'No items added'})

        # Payments
        payments    = json.loads(request.POST.get('payments') or '[]')

        inv_no = generate_serial_number(
            model                 =sales_order_table,
            number_field          ='inv_no',
            financial_year_field  ='current_fy',
            financial_year        = financial_year,
            branch_id             = branch_id,
            prefix                ="INV_"
        )

        if ws_po_id > 0:
            ws_po_no = (
                wholesale_po_table.objects
                .filter(id=ws_po_id, status=1, is_active=1)
                .values_list('po_no', flat=True)
                .first()
            )

        with db_transaction.atomic():            
            main = sales_order_table.objects.create(
                company_id      =company_id,
                branch_id       =branch_id,
                current_fy      =financial_year,
                inv_no          =inv_no,
                num_series      =generate_num_series(sales_order_table),
                inv_date        =po_date,
                time            = now.time(),
                customer_id     =customer_id,
                customer_name   =customer_name,
                customer_phone  =phone,
                customer_type   =customer_type,
                ws_po_id        =ws_po_id if ws_po_id > 0 else None,
                ws_po_no        =ws_po_no if ws_po_no else None,
                total_quantity  =total_qty,
                total_amount    =total_amount,
                sub_total       =sub_total,
                total_paid      =total_paid,
                total_discount  = total_discount,
                charges         = charges,
                balance         =balance,
                remarks         =remarks,
                employee_id     =employee_id,
                created_on      =now,
                updated_on      =now,
                created_by      =user_id,
                updated_by      =user_id,
                status          =1,
                is_active       =1,
                total_cgst      =total_tax_cgst,
                total_sgst      =total_tax_sgst,                
                sales_status    ='pending' if sales_channel == 'wholesale_internal' else 'approved',
                sales_channel   =sales_channel,
            )

            tm_has_ws_po = False
            tx_has_ws_po = False
            tm_has_ws_po_no = False
            tx_has_ws_po_no = False
            if ws_po_id > 0:
                with connection.cursor() as cursor:
                    tm_cols = [col.name for col in connection.introspection.get_table_description(cursor, 'tm_sales')]
                    tx_cols = [col.name for col in connection.introspection.get_table_description(cursor, 'tx_sales')]
                    tm_has_ws_po = 'ws_po_id' in tm_cols
                    tx_has_ws_po = 'ws_po_id' in tx_cols
                    tm_has_ws_po_no = 'ws_po_no' in tm_cols
                    tx_has_ws_po_no = 'ws_po_no' in tx_cols

            # Persist selected wholesale PO reference when column exists.
            if ws_po_id > 0 and tm_has_ws_po:
                with connection.cursor() as cursor:
                    cursor.execute("UPDATE tm_sales SET ws_po_id = %s WHERE id = %s", [ws_po_id, main.id])
            if ws_po_id > 0 and ws_po_no and tm_has_ws_po_no:
                with connection.cursor() as cursor:
                    cursor.execute("UPDATE tm_sales SET ws_po_no = %s WHERE id = %s", [ws_po_no, main.id])

            # Sales Items
            for item in items:
                child_obj = child_sales_order_table.objects.create(
                    company_id      =company_id,
                    current_fy      =financial_year,
                    tm_sales_id     =main.id,
                    ws_po_id        =ws_po_id if ws_po_id > 0 else None,
                    branch_id       =branch_id,
                    customer_id     =customer_id,
                    item_id         =item['item_id'],
                    imei_no         =item['imei'],
                    ean_number      =item['ean'],
                    subcategory_id  =item['subcategory_id'],
                    brand_id        =item['brand_id'],
                    model_id        =item['model_id'],
                    variant_id      =item['variant_id'],
                    color_id        =item['color_id'],
                    price_id        =item['price_id'],
                    quantity        =1,
                    rate            =item['rate'],
                    amount          =item['net_rate'],
                    tax_percent     =item['tax_percent'],
                    tax_amount      =item['tax_amount'],
                    tax_cgst        =item['tax_cgst'],
                    tax_sgst        =item['tax_sgst'],
                    discount_percent=item['discount_percent'],
                    discount_amount=item['discount_amount'],
                    mop             = item['mop_price'],
                    bop             = item['bop_price'],
                    wsp             = item['wsp_price'],
                    fsp             = item['fsp_price'],
                    db_price        = item['db_price'],
                    mrp             = item['rate'],
                    source          =item['source'],
                    upgrade_id      =item['upgrade_id'],
                    upgrade_amount  =item['upgrade_amount'],
                    special_id      =item['special_id'],
                    special_amount  =item['special_amount'],
                    purchase_price  =item['purchase_price'],
                    created_on      =now,
                    updated_on      =now,
                    created_by      =user_id,
                    updated_by      =user_id,
                    status          =1,
                    is_active       =1,
                    sales_channel   =sales_channel

                )

                if ws_po_id > 0 and tx_has_ws_po:
                    with connection.cursor() as cursor:
                        cursor.execute("UPDATE tx_sales SET ws_po_id = %s WHERE id = %s", [ws_po_id, child_obj.id])
                if ws_po_id > 0 and ws_po_no and tx_has_ws_po_no:
                    with connection.cursor() as cursor:
                        cursor.execute("UPDATE tx_sales SET ws_po_no = %s WHERE id = %s", [ws_po_no, child_obj.id])
            print(payments)
            # -----------------------------
            # Payments (MULTI ROW)
            # -----------------------------
            for pay in payments:
                transaction_table.objects.create(
                    tm_sales_id=main.id,
                    branch_id=branch_id,
                    current_fy=financial_year,
                    date=po_date,
                    time=now.time(),
                    finance_customer=pay.get('customer', ''),
                    payment_type=pay['type'],
                    total_amount=total_amount,
                    amount=float(pay['amount']),
                    finance_id=pay.get('id', 0) if pay['type'] == 'finance' else 0,
                    card_id=pay.get('id', 0) if pay['type'] == 'card' else 0,
                    bank_id=pay.get('id', 0) if pay['type'] == 'bank' else 0,
                    charges=float(pay.get('charges', 0)),
                    reference_no=pay.get('details', ''),
                    cashback=float(pay.get('cashback', 0)),
                    charge_mode=pay.get('mode', ''),
                    charge_bank_id=pay.get('bank_id', 0),
                    balance=balance,
                    created_on=now,
                    updated_on=now,
                    created_by=user_id,
                    updated_by=user_id,
                    status=1,
                    is_active=1,
                    collect_status='pending',
                    charge_status='pending',
                    exchange_status ='pending',
                )

        return JsonResponse({'message': 'success'})

    except IntegrityError as e:
        return JsonResponse({'message': 'exception', 'error': str(e)}, status=200)
    except Exception as e:
        return JsonResponse({'message': 'exception', 'error': f'Unexpected error: {e}'}, status=500)

def sales_order_edit(request):
    if 'user_id' in request.session:
        user_type = request.session.get('user_type')
        branch_id = request.session.get('branch_id')
        is_ho = request.session.get('is_ho')
        if user_type == 'stores':
            encoded_id = request.GET.get('id', None)
            decoded_id = decode_base64_id(encoded_id)
            if decoded_id:
                company = select_row(company_table, {'id': 1})  
                sales_order = select_row(sales_order_table, {'id':decoded_id})
                branch = selectList(branch_table, order_by='name').exclude(id=branch_id)
                subcategory = selectList(sub_category_table, order_by='name')
                brand = selectList(brand_table, order_by='name')               
                employee = employee_list(request)
                finance = selectList(finance_table,{'f_type': 'finance'}, order_by='name')
                card = selectList(finance_table,{'f_type': 'card'}, order_by='name')
                customer = selectList(customer_table,{'branch_id': branch_id} ,order_by='name')
                if is_ho == 1:
                    bank = selectList(bank_table, {'is_default':0}, order_by='name')
                else:
                    bank = selectList(bank_table, {'is_default':1}, order_by='name')

            else:
                return HttpResponse("ID parameter is missing")
            
            return render(request, 'sales_order/edit.html', {'customer':customer,'company': company,'sales':sales_order,'id':decoded_id,'branch':branch,'subcategory':subcategory,'employee':employee,'brand':brand,'finance':finance,'card':card,'bank':bank})

        if user_type == 'wholesale':
            encoded_id = request.GET.get('id', None)
            decoded_id = decode_base64_id(encoded_id)
            if decoded_id:
                company = select_row(company_table, {'id': 1})  
                sales_order = select_row(sales_order_table, {'id':decoded_id})
                branch = selectList(branch_table, order_by='name').exclude(id=branch_id)
                subcategory = selectList(sub_category_table, order_by='name')
                brand = selectList(brand_table, order_by='name')               
                employee = employee_list(request)
                finance = selectList(finance_table,{'f_type': 'finance'}, order_by='name')
                card = selectList(finance_table,{'f_type': 'card'}, order_by='name')
                customer = selectList(customer_table,{'branch_id': branch_id} ,order_by='name')
                if is_ho == 1:
                    bank = selectList(bank_table, {'is_default':0}, order_by='name')
                else:
                    bank = selectList(bank_table, {'is_default':1}, order_by='name')

            else:
                return HttpResponse("ID parameter is missing")
            
            return render(request, 'internal_wholesale/sales/edit.html', {'customer':customer,'company': company,'sales':sales_order,'id':decoded_id,'branch':branch,'subcategory':subcategory,'employee':employee,'brand':brand,'finance':finance,'card':card,'bank':bank})
        else:
            return HttpResponseRedirect("/")
    else:
        return HttpResponseRedirect("/")



def ajax_sales_edit(request):
    role_id = request.session.get('role_id')
    branch_id = request.session.get('branch_id')
    fyf_name = request.session.get('fyf')
    financial_year = calculate_financial_year(fyf_name)

    has_access, error_message = check_user_access(role_id, 'sales', "read")
    if not has_access:
        return JsonResponse({
            'message': 'permission',
            'error_message': 'You do not have permission to view the sales details'
        })

    sales_id = request.POST.get('sales_id')

    # ----------------------------  
    # Sales Items
    # ----------------------------
    items_qs = child_sales_order_table.objects.filter(
        tm_sales_id=sales_id,
        status=1,
        current_fy=financial_year
    ).values()

    items = []
    for index, item in enumerate(items_qs):
        items.append({
            'id': index + 1,
            'ean': item['ean_number'] or '-',
            'item_id': item['item_id'] or 0,
            'subcategory_id': item['subcategory_id'] or 0,
            'subcategory_name': getItemNameById(sub_category_table, item['subcategory_id']) if item['subcategory_id'] else '-',

            'brand_id': item['brand_id'] or 0,
            'brand_name': getItemNameById(brand_table, item['brand_id']) if item['brand_id'] else '-',

            'model_id': item['model_id'] or 0,
            'model_name': getItemNameById(model_table, item['model_id']) if item['model_id'] else '-',

            'variant_id': item['variant_id'] or 0,
            'variant_name': getItemNameById(variant_table, item['variant_id']) if item['variant_id'] else '-',


            'color_id': item['color_id'] or 0,
            'color_name': getItemNameById(color_table, item['color_id']) if item['color_id'] else '-',

            'imei_no': item['imei_no'] or '-',

            'rate': format_amount(item['rate']) if item['rate'] else '0.00',
            'quantity': item['quantity'] or 0,
            'amount': format_amount(item['amount']) if item['amount'] else '0.00',

            'source': item['source'] or 0,

            'special_id': item['special_id'] or 0,
            'price_id': item['price_id'] or 0,
            'special_amount': format_amount(item['special_amount']) if item['special_amount'] else '0.00',

            'bop_price': format_amount(item['bop']) if item['bop'] else '0.00',
            'mop_price': format_amount(item['mop']) if item['mop'] else '0.00',
            'wsp_price': format_amount(item['wsp']) if item['wsp'] else '0.00',
            'fsp_price': format_amount(item['fsp']) if item['fsp'] else '0.00',
            'db_price': format_amount(item['db_price']) if item['db_price'] else '0.00',
            'purchase_price': format_amount(item['purchase_price']) if item['purchase_price'] else '0.00',

            'tm_sales_id': item['tm_sales_id'],
            'tx_sales_id': item.get('id', 0),

            'tax_percent': item['tax_percent'] or 0,
            'tax_amount': format_amount(item['tax_amount']) if item['tax_amount'] else '0.00',
            'tax_cgst': format_amount(item['tax_cgst']) if item['tax_cgst'] else '0.00',
            'tax_sgst': format_amount(item['tax_sgst']) if item['tax_sgst'] else '0.00',

            'discount_amount': format_amount(item['discount_amount']) if item['discount_amount'] else '0.00',
            'discount_percent': item['discount_percent'] or 0,
            
            #upgrade
            'upgrade_id': item['upgrade_id'] or 0,
            'upgrade_amount': item['upgrade_amount'] if item['upgrade_amount'] else '0.00',
            'is_upgrade_approved': item['is_upgrade_approved'] or 0,
            'upgrade_approved_on': format_datetime(item['upgrade_approved_on']) or '',
            'upgrade_approved_by': item['upgrade_approved_by'] or 0,
            'upgrade_collect': item['upgrade_collect'] or 0,
            'upgrade_status': item['upgrade_status'] or 'pending',
            'upgrade_rejected_on': format_datetime(item['upgrade_rejected_on']) or '',
            'upgrade_rejected_by': item['upgrade_rejected_by'] or 0,
            'upgrade_rejected_remarks': item['upgrade_rejected_remarks'] or '',


            # special
            'is_claim': item['is_claim'] or 0,
            'claim_amount': item['claim_amount'] if item['claim_amount'] else '0.00',
            'claimed_on': format_datetime(item['claimed_on']) or '',


             
        })

    # ----------------------------
    # Sales Transactions (Payments)
    # ----------------------------
    tx_qs = transaction_table.objects.filter(
        tm_sales_id=sales_id,
        status=1,
        is_active=1
    ).values()

    tx_transaction = []
    for tx in tx_qs:
        tx_transaction.append({
            'payment_type': tx['payment_type'],
            'amount': format_amount(tx['amount']) if tx['amount'] else '0.00',
            'finance_id': tx['finance_id'] or 0,
            'bank_id': tx['bank_id'] or 0,
            'bank_name': getItemNameById(bank_table, tx['bank_id']) if tx['bank_id'] else '-',
            'card_id': tx['card_id'] or 0,
            'reference_no': tx['reference_no'] or '',
            'finance_customer': tx['finance_customer'] or '',
            'balance': format_amount(tx['balance']) if tx['balance'] else '0.00',
            'is_collect': tx['is_collect'] or 0,
            'collected_on': format_datetime(tx['collected_on']) or '',
            'collect_amount':tx['collect_amount'] or 0.00,
            'collect_status': tx['collect_status'] or 0,
            'cashback': tx['cashback'] or 0.00,
            'charges': tx['charges'] or 0.00,
            'charge_mode': tx['charge_mode'] or '',
            'charge_bank_id': tx['charge_bank_id'] or 0,
            'charge_bank_name': getItemNameById(bank_table, tx['charge_bank_id']) if tx['charge_bank_id'] else '-',
            'charge_status': tx['charge_status'] or '',
            'charge_amount_collect': tx['charge_amount_collect'] or 0.00,
            'charge_collect_on': format_datetime(tx['charge_collect_on']) or '',
            'exchange_status': tx['exchange_status'] or 'pending',
            'exchange_claim': tx['exchange_claim'] or 0.00,
            'id': tx['id'],



        })

    return JsonResponse({
        'items': items,
        'tx_transaction': tx_transaction
    })



def safe_int(value, default=0):
    try:
        return int(value)
    except (TypeError, ValueError):
        return default

import json
from django.db import transaction
from django.utils import timezone
from django.http import JsonResponse

def tm_sales_delete(request):
    if request.method == 'POST':
        data_id = request.POST.get('id')   
        role_id = request.session.get('role_id')
        has_access, error_message = check_user_access(role_id, 'sales', "delete")

        
       
        if not has_access:
            return JsonResponse({'message': 'permission', 'error_message': 'You do not have permission to delete cash sales details'}) 
        
           
        try:
            linked_sales_returns = child_sales_return_table.objects.filter(
                sales_id=data_id,
                status=1,
                is_active=1
            ).exists()

            if linked_sales_returns:
                return JsonResponse({
                    'message': 'infos',
                    'error_message': 'Cannot delete. Sales return exists against this sales details.'
                })
            
            updated = sales_order_table.objects.filter(id=data_id).update(status=0,is_active=0)
            updated = child_sales_order_table.objects.filter(tm_sales_id=data_id).update(status=0,is_active=0)

            if updated:
                return JsonResponse({'message': 'yes'})
            else:
                return JsonResponse({'message': 'no such data'})
        except Exception as e:
            return JsonResponse({'message': 'exception', 'error': str(e)})

    return JsonResponse({'message': 'Invalid request method'})



#```````````````````````````````````````````````````````````**QUICK SALES**````````````````````````````````````````````````````````````````````````````````

from django.db.models import Sum, F
from django.http import JsonResponse
from django.utils import timezone

def delete_tx_sales(request):
    if request.method != 'POST':
        return JsonResponse({'success': False, 'message': 'Invalid request'})

    try:
        tx_id = request.POST.get('tx_id')
        branch_id = request.session.get('branch_id')
        user_id = request.user.id  # assuming user is logged in

        # 🔹 Fetch transaction row
        item_row = child_sales_order_table.objects.filter(id=tx_id, status=1, branch_id=branch_id).first()
        if not item_row:
            return JsonResponse({'success': False, 'message': 'Item not found or already deleted.'})

        # 🔹 Check linked sales return
        return_exists = child_sales_return_table.objects.filter(
            sales_id=item_row.tm_sales_id,
            item_id=item_row.item_id,
            status=1,
            branch_id=branch_id,
            batch_no=item_row.batch_no
        ).exists()
        if return_exists:
            return JsonResponse({'success': False, 'message': 'This item has a sales return linked to it. Cannot delete.'})

        # 🔹 Calculate new totals after deletion
        totals = child_sales_order_table.objects.filter(
            tm_sales_id=item_row.tm_sales_id,
            status=1
        ).exclude(id=tx_id).aggregate(
            total_qty=Sum('quantity') or 0,
            total_amount=Sum('total_amount') or 0,
            total_profit=Sum('profit') or 0
        )

        # 🔹 Fetch parent sale
        parent_sale = sales_order_table.objects.filter(id=item_row.tm_sales_id, status=1, branch_id=branch_id).first()
        if not parent_sale:
            return JsonResponse({'success': False, 'message': 'Parent sale not found.'})

        # 🔹 Check balance consistency
        total_paid = (parent_sale.cash or 0) + (parent_sale.bank or 0) + (parent_sale.upi_amount or 0)
        new_balance = totals['total_amount'] - total_paid
        if new_balance != parent_sale.balance:
            return JsonResponse({
                'success': False,
                'message': 'Balance mismatch! Adjust cash/bank/UPI before deleting this item.'
            })

        # 🔹 Soft delete the tx row
        item_row.status = 0
        item_row.is_active = 0
        item_row.updated_on = timezone.now()
        item_row.updated_by = user_id
        item_row.save(update_fields=['status', 'is_active', 'updated_on', 'updated_by'])

        # 🔹 Update parent sale totals
        parent_sale.total_quantity = totals['total_qty'] or 0
        parent_sale.total_amount = totals['total_amount'] or 0
        parent_sale.profit = totals['total_profit'] or 0
        parent_sale.balance = new_balance
        parent_sale.updated_on = timezone.now()
        parent_sale.updated_by = user_id
        parent_sale.save(update_fields=['total_quantity', 'total_amount', 'profit', 'balance', 'updated_on', 'updated_by'])

        return JsonResponse({'success': True, 'message': 'Item deleted and parent totals updated successfully.'})

    except Exception as e:
        return JsonResponse({'success': False, 'message': str(e)})



#```````````````````````````````````````````````````````````**INVOICE PRINT**````````````````````````````````````````````````````````````````````````````````
@csrf_exempt
def sales_invoice(request):
    po_id = request.GET.get('k')
    if not po_id:
        return JsonResponse({'error': 'Missing encoded order ID'}, status=400)

    try:
        order_id = int(base64.b64decode(po_id).decode())
    except Exception:
        return JsonResponse({'error': 'Invalid order ID'}, status=400)

    branch_id = request.session.get('branch_id')
    branch = select_row(branch_table, {'id': branch_id})
    sales = select_row(sales_order_table, {'id': order_id})
    is_internal_wholesale_invoice = False

    if not sales:
        # Fallback for internal wholesale sales invoice.
        sales = select_row(wholesale_sales_order_table, {
            'id': order_id,
            'wholesale_type': 'internal',
            'status': 1,
            'is_active': 1
        })
        is_internal_wholesale_invoice = bool(sales)

    if not sales:
        return JsonResponse({'error': 'Sales order not found'}, status=404)

    # --- Customer or Store Info ---
    customer_name = sales.customer_name or '-'
    customer_phone = sales.customer_phone or '-'

    # store = select_row(branch_table, {'id': sales.supply_branch_id})
    # store_name = store.name if store else '-'
    # store_phone = store.phone if store else '-'

    # --- Child items ---
    if is_internal_wholesale_invoice:
        tx_data = list(wholesale_child_sales_order_table.objects.filter(tm_sales_id=order_id, status=1, is_active=1))
        item_ids = [tx.sales_id for tx in tx_data if tx.sales_id]
    else:
        tx_data = list(child_sales_order_table.objects.filter(tm_sales_id=order_id, status=1))
        item_ids = [tx.item_id for tx in tx_data if tx.item_id]

    item_dict = {
        i.id: i.sku for i in item_table.objects.filter(id__in=item_ids,status=1)
    }

    # combine item info
    for tx in tx_data:
        item_ref_id = tx.sales_id if is_internal_wholesale_invoice else tx.item_id
        item_name = item_dict.get(item_ref_id, '-')
        tx.display_name = item_name        
            
    image_url = 'https://biglitz.com/static/assets/img/logo/boys-logo.png'
    total_amount = sum(tx.amount or 0 for tx in tx_data)
    context = {
        'company': company_table.objects.filter(status=1).first(),
        'image_url': image_url,
        'branch': branch,
        'sales': sales,
        'customer_name': customer_name,
        'customer_phone': customer_phone,
        # 'store_name': store_name,
        # 'store_phone': store_phone,
        'tx_data': tx_data,
        'total_amount':total_amount
    }

    return render(request, 'sales_order/invoice.html', context)





@csrf_exempt
def sales_invoice_thermal(request):
    if request.method not in ["GET", "POST"]:
        return JsonResponse({'status': 'error', 'message': 'Invalid request method'})

    po_id = request.GET.get('k')
    if not po_id:
        return JsonResponse({'error': 'Missing encoded order ID'}, status=400)

    try:
        order_id = int(base64.b64decode(po_id).decode())
    except Exception:
        return JsonResponse({'error': 'Invalid order ID'}, status=400)

    branch_id = request.session.get('branch_id')
    branch = select_row(branch_table, {'id': branch_id})
    sales = select_row(sales_order_table, {'id': order_id})
    is_internal_wholesale_invoice = False

    if not sales:
        # Fallback for internal wholesale sales thermal invoice.
        sales = select_row(wholesale_sales_order_table, {
            'id': order_id,
            'wholesale_type': 'internal',
            'status': 1,
            'is_active': 1
        })
        is_internal_wholesale_invoice = bool(sales)

    if not sales:
        return JsonResponse({'error': 'Sales order not found'}, status=404)

    # --- Customer or Store Info ---
    customer_name = sales.customer_name or '-'
    customer_phone = sales.customer_phone or '-'

    # store = select_row(branch_table, {'id': sales.supply_branch_id})
    # store_name = store.name if store else '-'
    # store_phone = store.phone if store else '-'

    # --- Child items ---
    if is_internal_wholesale_invoice:
        tx_data = list(wholesale_child_sales_order_table.objects.filter(tm_sales_id=order_id, status=1, is_active=1))
        item_ids = [tx.sales_id for tx in tx_data if tx.sales_id]
    else:
        tx_data = list(child_sales_order_table.objects.filter(tm_sales_id=order_id, status=1))
        item_ids = [tx.item_id for tx in tx_data if tx.item_id]

    item_dict = {
        i.id: i.name for i in item_table.objects.filter(id__in=item_ids,status=1)
    }

    # combine item info
    for tx in tx_data:
        item_ref_id = tx.sales_id if is_internal_wholesale_invoice else tx.item_id
        item_name = item_dict.get(item_ref_id, '-')
        tx.display_name = item_name        
            
    image_url = 'https://biglitz.com/static/assets/img/logo/boys-logo.png'
    total_amount = sum(tx.amount or 0 for tx in tx_data)
    context = {
        'company': company_table.objects.filter(status=1).first(),
        'image_url': image_url,
        'branch': branch,
        'sales': sales,
        'customer_name': customer_name,
        'customer_phone': customer_phone,
        # 'store_name': store_name,
        # 'store_phone': store_phone,
        'tx_data': tx_data,
        'total_amount':total_amount
    }


    return render(request, 'sales_order/invoice_thermal.html', context)


from django.http import JsonResponse
from masters.models import finance_table

# def finance_autocomplete(request):
#     term = request.GET.get('term', '')
#     finances = finance_table.objects.filter(name__icontains=term, is_active=1).values('id', 'name')[:10]
#     results = [{'label': f['name'], 'value': f['name'], 'id': f['id']} for f in finances]
#     return JsonResponse(results, safe=False)




def finance_autocomplete(request):
    query = request.GET.get('term', '').strip()
    
    customers = finance_table.objects.filter(
        Q(name__icontains=query),
        status=1,
        f_type='finance'
    )

    data = [
        {
            'id': c.id,
            'name': c.name,
        }
        for c in customers
    ]
    return JsonResponse(data, safe=False)


def card_autocomplete(request):
    query = request.GET.get('term', '').strip()
    
    customers = finance_table.objects.filter(
        Q(name__icontains=query),
        status=1,
        f_type='card'
    )

    data = [
        {
            'id': c.id,
            'name': c.name,
        }
        for c in customers
    ]
    return JsonResponse(data, safe=False)


def load_ean_number(request):
    query = request.GET.get('term', '').strip()
    
    customers = item_table.objects.filter(
        Q(ean__icontains=query),
        status=1,
        is_active=1
    )

    data = [
        {
            'id': c.id,
            'name': c.ean,
        }
        for c in customers
    ]
    return JsonResponse(data, safe=False)



from django.http import JsonResponse
from datetime import date
# utils/scheme_utils.py
def get_applicable_schemes(brand_id, model_id, variant_id):
    today = date.today()

    return item_scheme_table.objects.filter(
        brand_id=brand_id,
        model_id=model_id,
        variant_id=variant_id,
        is_active=1,
        status=1
    ).filter(
        models.Q(
            scheme_type__in=['upgrade', 'special'],
            from_date__lte=today,
            to_date__gte=today
        ) |
        models.Q(
            scheme_type='price_drop',
            from_date__lte=today
        )
    ).values('id', 'scheme_type', 'mop', 'name')
def get_item_by_ean(request):
    ean = request.POST.get('ean')
    if not ean:
        return JsonResponse({'status': False, 'message': 'EAN missing'})

    item = item_table.objects.filter(ean=ean, is_active=1).first()
    if not item:
        return JsonResponse({'status': False, 'message': 'Item not found'})

    # ---- scheme util ----
    schemes_qs = get_applicable_schemes(
        item.brand_id,
        item.model_id,
        item.variant_id
    )

    # ---- defaults (NON-NEGOTIABLE) ----
    upgrade_ids = []
    special_ids = []
    price_ids = []

    upgrade_total = 0
    special_total = 0
    price_drop_total = 0

    for s in schemes_qs:
        mop = s.get('mop') or 0
        scheme_id = s.get('id') or 0

        if s['scheme_type'] == 'upgrade':
            upgrade_ids.append(scheme_id)
            upgrade_total += mop

        elif s['scheme_type'] == 'special':
            special_ids.append(scheme_id)
            special_total += mop

        elif s['scheme_type'] == 'price_drop':
            price_ids.append(scheme_id)
            price_drop_total += mop

    total_deduction = upgrade_total + special_total + price_drop_total

    mop_price = item.mop_price or 0
    final_price = max(mop_price - total_deduction, 0)

    brand = list(
        brand_table.objects.filter(id=item.brand_id, status=1)
        .values('id', 'name')
    )
    model = list(
        model_table.objects.filter(id=item.model_id, status=1)
        .values('id', 'name')
    )
    variant = list(
        variant_table.objects.filter(id=item.variant_id, status=1)
        .values('id', 'name')
    )
    color = list(
        color_table.objects.filter(id=item.color_id, status=1)
        .values('id', 'name')
    )

    return JsonResponse({
        'status': True,
        'item_id': item.id,

        # pricing
        'mop_price': mop_price,
        'final_price': final_price,

        # raw schemes (can be empty list)
        'schemes': list(schemes_qs) if schemes_qs else [],
        'brand_id': item.brand_id,
        'model_id': item.model_id,
        'variant_id': item.variant_id,
        'color_id': item.color_id,

        # master data
        'brand': brand,
        'model': model,
        'variant': variant,
        'color': color,

        # scheme summary (ALWAYS SAFE)
        'upgrade_ids': upgrade_ids,
        'upgrade_amount': upgrade_total,

        'special_ids': special_ids,
        'special_amount': special_total,

        'price_ids': price_ids,
        'price_amount': price_drop_total,
    })



from django.http import JsonResponse
def load_sale_details(request):
    brand_id = request.GET.get('brand_id')
    model_id = request.GET.get('model_id')
    variant_id = request.GET.get('variant_id')
    color_id = request.GET.get('color_id')

    if not all([brand_id, model_id, variant_id, color_id]):
        return JsonResponse({
            'status': False,
            'message': 'Missing parameters'
        })

    item = item_table.objects.filter(
        brand_id=brand_id,
        model_id=model_id,
        variant_id=variant_id,
        color_id=color_id,
        status=1,
        is_active=1
    ).first()

    if not item:
        return JsonResponse({
            'status': False,
            'message': 'Item not found'
        })

    schemes = get_applicable_schemes(
        item.brand_id,
        item.model_id,
        item.variant_id
    )

    # ---- defaults (IMPORTANT) ----
    upgrade_id = special_id = price_id = 0
    upgrade_amount = special_amount = price_amount = 0

    for s in schemes:
        mop = s['mop'] or 0

        if s['scheme_type'] == 'upgrade' and upgrade_id == 0:
            upgrade_id = s['id']
            upgrade_amount = mop

        elif s['scheme_type'] == 'special' and special_id == 0:
            special_id = s['id']
            special_amount = mop

        elif s['scheme_type'] == 'price_drop' and price_id == 0:
            price_id = s['id']
            price_amount = mop

    total_deduction = upgrade_amount + special_amount + price_amount

    mop_price = item.mop_price or 0
    final_price = max(mop_price - total_deduction, 0)

    return JsonResponse({
        'status': True,
        

        # item info
        'item_id': item.id,
        'ean': item.ean,
        'schemes': list(schemes) if schemes else [],

        # pricing
        'mop_price': mop_price,
        'final_price': final_price,

        # scheme response (always numeric)
        'upgrade_id': upgrade_id,
        'upgrade_amount': upgrade_amount,

        'special_id': special_id,
        'special_amount': special_amount,

        'price_id': price_id,
        'price_amount': price_amount,
    })

from django.db import connection
from django.http import JsonResponse
from django.db import connection
def get_branch_available_imeis(request):    
    branch_id = request.session.get('branch_id')

    brand_id   = request.POST.get('brand_id')
    model_id   = request.POST.get('model_id')
    variant_id = request.POST.get('variant_id')
    color_id   = request.POST.get('color_id')
    sales_id   = request.POST.get('sales_id') or 0

    if not branch_id:
        return JsonResponse({
            'status': False,
            'message': 'Branch ID not found',
            'available_imeis': [],
            'count': 0
        })

    from collections import Counter
    from django.db.models import Q

    inwards_count = Counter()
    outwards_count = Counter()

    # --- 1. SET UP FILTERS ---
    base_filter = Q(branch_id=branch_id, status=1)
    if brand_id:
        base_filter &= Q(brand_id=brand_id)
    if model_id:
        base_filter &= Q(model_id=model_id)
    if variant_id:
        base_filter &= Q(variant_id=variant_id)
    if color_id:
        base_filter &= Q(color_id=color_id)

    # --- 2. COUNT ALL INWARDS ---
    # a. Purchase Inwards
    purchases = child_purchase_inward_table.objects.filter(base_filter).exclude(imei_no__isnull=True).exclude(imei_no='').values_list('imei_no', flat=True)
    for imei in purchases: inwards_count[imei] += 1

    # b. Material Inwards
    mat_in = child_material_inward_table.objects.filter(base_filter).exclude(imei_no__isnull=True).exclude(imei_no='').values_list('imei_no', flat=True)
    for imei in mat_in: inwards_count[imei] += 1

    # c. Opening Stock
    opening = opening_stock_table.objects.filter(base_filter, is_active=1).exclude(imei_no__isnull=True).exclude(imei_no='').values_list('imei_no', flat=True)
    for imei in opening: inwards_count[imei] += 1

    # d. Sales Returns
    returns = child_sales_return_table.objects.filter(base_filter).exclude(imei_no__isnull=True).exclude(imei_no='').values_list('imei_no', flat=True)
    for imei in returns: inwards_count[imei] += 1

    # --- 3. COUNT ALL OUTWARDS ---
    # a. Sales (Exclude current sales if editing)
    sales_filter = Q(branch_id=branch_id, status=1)
    if sales_id:
        sales_filter &= ~Q(tm_sales_id=sales_id)
    
    sales = child_sales_order_table.objects.filter(sales_filter).exclude(imei_no__isnull=True).exclude(imei_no='').values_list('imei_no', flat=True)
    for imei in sales: outwards_count[imei] += 1

    # b. Material Outwards (Exclude rejected)
    mat_out = child_material_outward_table.objects.filter(
        branch_id=branch_id, status=1,
        tm_material_id__in=material_outward_table.objects.exclude(outward_status='rejected').values_list('id', flat=True)
    ).exclude(imei_no__isnull=True).exclude(imei_no='').values_list('imei_no', flat=True)
    for imei in mat_out: outwards_count[imei] += 1

    # c. Purchase Returns (Exclude reversed)
    pur_ret = child_purchase_return_table.objects.filter(
        branch_id=branch_id, status=1,
        tm_return_id__in=purchase_return_table.objects.exclude(pr_status__iexact='reversed').values_list('id', flat=True)
    ).exclude(imei_no__isnull=True).exclude(imei_no='').values_list('imei_no', flat=True)
    for imei in pur_ret: outwards_count[imei] += 1

    # --- 4. CALCULATE AVAILABLE ---
    available_imeis = []
    all_seen_imeis = set(inwards_count.keys())
    
    for imei in all_seen_imeis:
        if (inwards_count[imei] - outwards_count[imei]) > 0:
            available_imeis.append(imei)

    available_imeis.sort()

    return JsonResponse({
        'status': True,
        'available_imeis': available_imeis,
        'count': len(available_imeis)
    })


from django.db import connection
from django.http import JsonResponse
from django.db import connection
from django.http import JsonResponse
from django.db import connection
from django.http import JsonResponse

def debug_imei_status(imei_no):
    """Helper to print what's actually in the database for this IMEI"""
    print(f"\n--- DATABASE LOOKUP FOR IMEI: {imei_no} ---")
    with connection.cursor() as cursor:
        # Check Sales
        cursor.execute("SELECT id, status FROM tx_sales WHERE imei_no = %s", [imei_no])
        sales = cursor.fetchall()
        print(f"Sales Records: {sales}")

        # Check Returns
        cursor.execute("SELECT id, tm_return_id, status FROM tx_sales_return WHERE imei_no = %s", [imei_no])
        returns = cursor.fetchall()
        print(f"Return Records: {returns}")
        
        # Check Transfers
        cursor.execute("SELECT id, status FROM tx_material_out WHERE imei_no = %s", [imei_no])
        outward = cursor.fetchall()
        print(f"Outward/Transfer Records: {outward}")
from django.db import connection
from django.http import JsonResponse
def fetch_imei_from_inward_material(imei_no, branch_id, sales_id):
    query = """
        SELECT 
            incoming.subcategory_id, incoming.brand_id, incoming.model_id, 
            incoming.variant_id, incoming.color_id, incoming.ean, incoming.rate, 
            incoming.mop, incoming.is_demo
        FROM (
            SELECT subcategory_id, brand_id, model_id, variant_id, color_id, ean, rate, mop, imei_no, branch_id, is_demo, created_on 
            FROM tx_purchase_inward WHERE status = 1
            UNION ALL
            SELECT subcategory_id, brand_id, model_id, variant_id, color_id, ean, rate, mop, imei_no, branch_id, is_demo, created_on 
            FROM tx_material_in WHERE status = 1
        ) incoming
        WHERE incoming.imei_no = %s AND incoming.branch_id = %s
        -- 🚨 MATERIAL OUT: EXCLUDE only if NOT REJECTED AND LATEST MOVEMENT IS AWAY FROM US
        AND NOT EXISTS (
            SELECT 1 FROM tx_material_out mo 
            JOIN tm_material_out tmo ON mo.tm_material_id = tmo.id
            WHERE mo.imei_no = incoming.imei_no 
              AND mo.status = 1
              AND mo.branch_id = incoming.branch_id
              AND tmo.outward_status != 'rejected'
              AND mo.created_on > incoming.created_on
              AND NOT EXISTS (
                  SELECT 1 FROM tx_material_out mo_in
                  JOIN tm_material_out tmo_in ON mo_in.tm_material_id = tmo_in.id
                  WHERE mo_in.imei_no = mo.imei_no
                    AND mo_in.status = 1
                    AND tmo_in.supply_branch_id = incoming.branch_id
                    AND mo_in.created_on > mo.created_on
                    AND tmo_in.outward_status != 'rejected'
              )
        )
        AND NOT EXISTS (
            SELECT 1 FROM tx_purchase_return pr 
            JOIN tm_purchase_return tpr ON pr.tm_return_id = tpr.id
            WHERE pr.imei_no = incoming.imei_no AND pr.status = 1
              AND tpr.pr_status != 'reversed'
              AND pr.created_on > incoming.created_on
        )
        AND NOT EXISTS (
            SELECT 1 FROM tx_sales s
            WHERE s.imei_no = incoming.imei_no AND s.status = 1 AND s.id != %s
              AND s.created_on > incoming.created_on
            AND NOT EXISTS (
                SELECT 1 FROM tx_sales_return sr 
                WHERE (sr.tm_return_id = s.id OR sr.imei_no = s.imei_no) AND sr.status = 1
                  AND sr.created_on > s.created_on
            )
        )
        ORDER BY incoming.created_on DESC
        LIMIT 1;
    """
    with connection.cursor() as cursor:
        cursor.execute(query, [imei_no, branch_id, sales_id])
        return cursor.fetchone()




def fetch_imei_from_opening_stock(imei_no, branch_id, sales_id):
    query = """
        SELECT 
            os.subcategory_id, os.brand_id, os.model_id, os.variant_id, os.color_id, 
            os.ean, os.bop AS rate, os.mop, os.is_demo
        FROM opening_stock os
        WHERE os.imei_no = %s AND os.branch_id = %s AND os.status = 1 AND os.is_active = 1
        -- 🚨 MATERIAL OUT: EXCLUDE only if NOT REJECTED AND LATEST MOVEMENT IS AWAY FROM US
        AND NOT EXISTS (
            SELECT 1 FROM tx_material_out mo 
            JOIN tm_material_out tmo ON mo.tm_material_id = tmo.id
            WHERE mo.imei_no = os.imei_no 
              AND mo.status = 1
              AND mo.branch_id = os.branch_id
              AND tmo.outward_status != 'rejected'
              AND mo.created_on > os.created_on
              AND NOT EXISTS (
                  SELECT 1 FROM tx_material_out mo_in
                  JOIN tm_material_out tmo_in ON mo_in.tm_material_id = tmo_in.id
                  WHERE mo_in.imei_no = mo.imei_no
                    AND mo_in.status = 1
                    AND tmo_in.supply_branch_id = os.branch_id
                    AND mo_in.created_on > mo.created_on
                    AND tmo_in.outward_status != 'rejected'
              )
        )
        AND NOT EXISTS (
            SELECT 1 FROM tx_purchase_return pr 
            JOIN tm_purchase_return tpr ON pr.tm_return_id = tpr.id
            WHERE pr.imei_no = os.imei_no AND pr.status = 1
              AND tpr.pr_status != 'reversed'
              AND pr.created_on > os.created_on
        )
        AND NOT EXISTS (
            SELECT 1 FROM tx_sales s
            WHERE s.imei_no = os.imei_no AND s.status = 1 AND s.id != %s
              AND s.created_on > os.created_on
            AND NOT EXISTS (
                SELECT 1 FROM tx_sales_return sr 
                WHERE (sr.tm_return_id = s.id OR sr.imei_no = s.imei_no) AND sr.status = 1
                  AND sr.created_on > s.created_on
            )
        )
        LIMIT 1;
    """
    with connection.cursor() as cursor:
        cursor.execute(query, [imei_no, branch_id, sales_id])
        return cursor.fetchone()


def fetch_imei_from_internal_wholesale_stock(imei_no, branch_id, sales_id=0, req_is_demo=0):
    """
    Fetch IMEI from internal wholesale inward stock (tx_wholesale_in) for wholesale users.
    Excludes IMEIs already consumed in wholesale sales, except current sales document (edit mode).
    """
    inward_qs = wholesale_child_material_inward_table.objects.filter(
        imei_no__iexact=imei_no,
        branch_id=branch_id,
        status=1,
        is_active=1
    )

    # Keep demo/live behavior consistent with existing IMEI lookup flow.
    if req_is_demo in (0, 1):
        inward_qs = inward_qs.filter(is_demo=req_is_demo)

    sold_qs = wholesale_child_sales_order_table.objects.filter(
        imei_no__iexact=imei_no,
        status=1,
        is_active=1
    )
    if sales_id:
        sold_qs = sold_qs.exclude(tm_sales_id=sales_id)

    if sold_qs.exists():
        return None

    return inward_qs.order_by('-created_on', '-id').first()


def get_imei_details(request):
    imei_no   = request.POST.get('imei_no')
    sales_id  = int(request.POST.get('sales_id') or 0)
    branch_id = request.session.get('branch_id')
    user_type = request.session.get('user_type')
    customer_id = request.session.get('customer_id') or request.session.get('user_id')
    customer_type = request.POST.get('customer_type') or 'retail'
    # imei_type removed from frontend, relying on is_demo

    # Receive is_demo from request (frontend passes 1 or 0)
    try:
        req_is_demo = int(request.POST.get('is_demo', 0))
    except (ValueError, TypeError):
        req_is_demo = 0

    if not imei_no:
        return JsonResponse({'status': False, 'message': 'Missing IMEI'})

    # ========================================
    # INTERNAL WHOLESALE STOCK VALIDATION
    # ========================================
    if user_type == 'wholesale':
        wholesale_item = fetch_imei_from_internal_wholesale_stock(
            imei_no=imei_no,
            branch_id=branch_id,
            sales_id=sales_id,
            req_is_demo=req_is_demo
        )

        if not wholesale_item:
            meta_msg = 'IMEI not found in demo wholesale stock.' if req_is_demo == 1 else 'IMEI not available in wholesale stock.'
            return JsonResponse({
                'status': False,
                'message': meta_msg
            })

        return JsonResponse({
            'status': True,
            'data': {
                'subcategory_id': wholesale_item.subcategory_id,
                'subcategory_name': getItemNameById(sub_category_table, wholesale_item.subcategory_id),
                'brand_id': wholesale_item.brand_id,
                'brand_name': getItemNameById(brand_table, wholesale_item.brand_id),
                'model_id': wholesale_item.model_id,
                'model_name': getItemNameById(model_table, wholesale_item.model_id),
                'variant_id': wholesale_item.variant_id,
                'variant_name': getItemNameById(variant_table, wholesale_item.variant_id),
                'color_id': wholesale_item.color_id,
                'color_name': getItemNameById(color_table, wholesale_item.color_id),
                'ean': wholesale_item.ean,
                'rate': float(wholesale_item.rate) if wholesale_item.rate else 0,
                'mop': float(wholesale_item.mop) if wholesale_item.mop else 0,
                'imei_no': imei_no,
                'customer_type': customer_type,
                'sales_id': wholesale_item.id
            }
        })

    # ========================================
    # WHOLESALE CUSTOMER STOCK VALIDATION
    # ========================================
    if user_type == 'wholesale_customer':
        from wholesale.external import get_wholesale_customer_available_stock
        
        # Get available stock for this wholesale customer, excluding current sales session if editing
        available_items = get_wholesale_customer_available_stock(customer_id, exclude_tm_sales_id=sales_id)
        
        # Check if IMEI exists in their available stock
        imei_item = available_items.filter(imei_no__iexact=imei_no).first()
        
        if not imei_item:
            return JsonResponse({
                'status': False,
                'message': 'IMEI not available in your stock'
            })
        
        # Return wholesale customer's stock details
        return JsonResponse({
            'status': True,
            'data': {
                'subcategory_id': imei_item.subcategory_id,
                'subcategory_name': getItemNameById(sub_category_table, imei_item.subcategory_id),
                'brand_id': imei_item.brand_id,
                'brand_name': getItemNameById(brand_table, imei_item.brand_id),
                'model_id': imei_item.model_id,
                'model_name': getItemNameById(model_table, imei_item.model_id),
                'variant_id': imei_item.variant_id,
                'variant_name': getItemNameById(variant_table, imei_item.variant_id),
                'color_id': imei_item.color_id,
                'color_name': getItemNameById(color_table, imei_item.color_id),
                'ean': imei_item.ean_number,
                'rate': float(imei_item.rate) if imei_item.rate else 0,
                'mop': float(imei_item.mop) if imei_item.mop else 0,
                'imei_no': imei_no,
                'customer_type': customer_type,
                'sales_id': imei_item.id
            }
        })

    # ========================================
    # REGULAR STORE STOCK VALIDATION
    # ========================================
    if not branch_id:
        return JsonResponse({'status': False, 'message': 'Missing branch data'})

    # Try finding the record in Opening Stock or Inward
    row = fetch_imei_from_opening_stock(imei_no, branch_id, sales_id)
    if not row:
        row = fetch_imei_from_inward_material(imei_no, branch_id, sales_id)
    
    if not row:
        meta_msg = 'IMEi not available in demo stock.' if req_is_demo == 1 else 'IMEI not available in live stock.'
        return JsonResponse({
            'status': False,
            'message': meta_msg
        })

    # row[8] is the is_demo field from the STOCK tables
    stock_is_demo = int(row[8] or 0)
    
    # ? STRICT CHECK per requirement 4
    if stock_is_demo != req_is_demo:
        meta_msg = 'IMEI not found in demo stock.' if req_is_demo == 1 else 'IMEI not available in live stock.'
        return JsonResponse({
            'status': False,
            'message': meta_msg
        })

    # Fetch names from master tables
    subcategory_name = ''
    brand_name = ''
    model_name = ''
    variant_name = ''
    color_name = ''

    # Get subcategory name
    if row[0]:
        subcategory = sub_category_table.objects.filter(id=row[0], status=1).first()
        if subcategory:
            subcategory_name = subcategory.name

    # Get brand name
    if row[1]:
        brand = brand_table.objects.filter(id=row[1], status=1).first()
        if brand:
            brand_name = brand.name

    # Get model name
    if row[2]:
        model = model_table.objects.filter(id=row[2], status=1).first()
        if model:
            model_name = model.name

    # Get variant name
    if row[3]:
        variant = variant_table.objects.filter(id=row[3], status=1).first()
        if variant:
            variant_name = variant.name

    # Get color name
    if row[4]:
        color = color_table.objects.filter(id=row[4], status=1).first()
        if color:
            color_name = color.name

    return JsonResponse({
        'status': True,
        'data': {
            'subcategory_id': row[0],
            'subcategory_name': subcategory_name,
            'brand_id': row[1],
            'brand_name': brand_name,
            'model_id': row[2],
            'model_name': model_name,
            'variant_id': row[3],
            'variant_name': variant_name,
            'color_id': row[4],
            'color_name': color_name,
            'ean': row[5],
            'rate': row[6],
            'mop': row[7],
            'imei_no': imei_no,
            'customer_type': customer_type
        }
    })

from django.http import JsonResponse
from django.utils import timezone
from django.db.models import Q

from django.db import transaction
@transaction.atomic

def update_sales_order(request):
    role_id = request.session.get('role_id')
    has_access, error_message = check_user_access(role_id, 'sales', "update")
    if not has_access:
        return JsonResponse({'message': 'permission', 'error_message': 'You do not have permission to update sales details'})
    
    if request.method != 'POST':
        return JsonResponse({'success': False, 'message': 'Invalid request'})

    try:
        user_id         = request.session.get('user_id')
        company_id      = request.session.get('company_id')
        branch_id       = request.session.get('branch_id')
        fyf_name        = request.session.get('fyf')
        financial_year  = calculate_financial_year(fyf_name)        
        current_time    = timezone.localtime(timezone.now())
        total_qty       = float(request.POST.get('total_quantity') or 0)
        sub_total       = float(request.POST.get('sub_total') or 0)
        total_amount    = float(request.POST.get('total_payable') or 0)
        total_paid      = float(request.POST.get('total_paid') or 0)
        balance         = float(request.POST.get('balance') or 0)
        charges         = float(request.POST.get('charges') or 0)
        total_discount  = float(request.POST.get('total_discount') or 0)
        total_tax_cgst  = float(request.POST.get('total_tax_cgst') or 0)
        total_tax_sgst  = float(request.POST.get('total_tax_sgst') or 0)

        data = request.POST
        items_json = data.get('items')
        try:
            items = json.loads(items_json) if items_json else []
            payments = json.loads(request.POST.get('payments') or '[]')
        except json.JSONDecodeError:
            return JsonResponse({"status": "error", "message": "Invalid JSON data"})

        if not items_json:
            return JsonResponse({'success': False, 'message': 'No items provided'})
            
        items = json.loads(items_json)
        tm_id_parent = int(data.get('tm_sales_id') or 0)
        
        # Get parent record
        purchase = select_row(sales_order_table, {'id': tm_id_parent})
        if not purchase:
             return JsonResponse({'success': False, 'message': 'Parent record not found'})

        child_sales_order_table.objects.filter(
            tm_sales_id=tm_id_parent, 
            status=1
        ).update(status=0, updated_on=current_time, updated_by=user_id)

        transaction_table.objects.filter(tm_sales_id=tm_id_parent).update(
            status=0, updated_on=current_time, updated_by=user_id
        )

        # 3. Process and Insert Rows
        for item in items:
            # Preserve claim and upgrade status from the old record
            old_tx_id = item.get('tx_id')
            extra_fields = {}
            if old_tx_id:
                old_item = child_sales_order_table.objects.filter(id=old_tx_id).first()
                if old_item:
                    extra_fields = {
                        'is_upgrade_approved': old_item.is_upgrade_approved,
                        'upgrade_approved_on': old_item.upgrade_approved_on,
                        'upgrade_approved_by': old_item.upgrade_approved_by,
                        'upgrade_collect': old_item.upgrade_collect,
                        'upgrade_status': old_item.upgrade_status,
                        'upgrade_rejected_on': old_item.upgrade_rejected_on,
                        'upgrade_rejected_by': old_item.upgrade_rejected_by,
                        'upgrade_rejected_remarks': old_item.upgrade_rejected_remarks,
                        'is_claim': old_item.is_claim,
                        'claim_amount': old_item.claim_amount,
                        'claimed_on': old_item.claimed_on,
                        'price_id': old_item.price_id,
                    }

            child_sales_order_table.objects.create(
                company_id          =company_id,
                current_fy          =financial_year,
                tm_sales_id         =tm_id_parent,
                branch_id           =branch_id,
                customer_id         =purchase.customer_id,
                item_id             =item['item_id'],
                imei_no             =item['imei'],
                ean_number          =item['ean'],
                subcategory_id      =item['subcategory_id'],
                brand_id            =item['brand_id'],
                model_id            =item['model_id'],
                variant_id          =item['variant_id'],
                color_id            =item['color_id'],
                quantity            =1,
                rate                =item['rate'],
                amount              =item['net_rate'],
                tax_percent         =item['tax_percent'],
                tax_amount          =item['tax_amount'],
                tax_cgst            =item['tax_cgst'],
                tax_sgst            =item['tax_sgst'],
                discount_percent    =item['discount_percent'],
                discount_amount     =item['discount_amount'],
                mop                 = item['mop_price'],
                bop                 = item['bop_price'],
                wsp                 = item['wsp_price'],
                fsp                 = item['fsp_price'],
                db_price            = item['db_price'],
                mrp                 = item['rate'],
                source              =item['source'],
                upgrade_id          =item['upgrade_id'],
                upgrade_amount      =item['upgrade_amount'],
                special_id          =item['special_id'],
                special_amount      =item['special_amount'],
                purchase_price      =item['purchase_price'],                                
                created_on          =current_time,
                updated_on          =current_time,
                created_by          =user_id,
                updated_by          =user_id,
                status              =1,
                is_active           =1,
                **extra_fields
            )
        print(payments)
        for pay in payments:
            # Preserve collection status from the old record
            old_tx_id = pay.get('tx_id')
            pay_extra_fields = {}
            if old_tx_id:
                old_pay = transaction_table.objects.filter(id=old_tx_id).first()
                if old_pay:
                    pay_extra_fields = {
                        'charge_status': old_pay.charge_status,
                        'charge_amount_collect': old_pay.charge_amount_collect,
                        'charge_collect_on': old_pay.charge_collect_on,
                        'is_collect': old_pay.is_collect,
                        'collect_amount': old_pay.collect_amount,
                        'collect_status': old_pay.collect_status,
                        'collected_on': old_pay.collected_on,
                        'exchange_status': old_pay.exchange_status,
                        'exchange_claim': old_pay.exchange_claim,
                    }

            transaction_table.objects.create(
                tm_sales_id=tm_id_parent,
                branch_id=branch_id,
                current_fy=financial_year,
                date=current_time.date(),
                time=current_time.time(),
                payment_type=pay['type'],
                total_amount=total_amount,
                amount=float(pay['amount']),
                finance_id=pay.get('id', 0) if pay['type'] == 'finance' else 0,
                card_id=pay.get('id', 0) if pay['type'] == 'card' else 0,
                bank_id=pay.get('id', 0) if pay['type'] == 'bank' else 0,
                reference_no=pay.get('details', ''),
                cashback=float(pay.get('cashback', 0)),
                charges=float(pay.get('charges', 0)),
                charge_mode = pay.get('mode', ''),
                charge_bank_id= pay.get('charge_bank_id', 0),
                balance=balance,
                created_on=current_time,
                updated_on=current_time,
                created_by=user_id,
                updated_by=user_id,
                status=1,
                is_active=1,
                **pay_extra_fields
                )
            
        # 4. Update Parent Totals
        purchase.total_quantity = total_qty
        purchase.total_amount = total_amount
        purchase.sub_total = sub_total
        purchase.total_discount = total_discount
        purchase.total_cgst = total_tax_cgst
        # Auto-approve on update and clear rejection
        purchase.sales_status = 'approved'
        purchase.rejected_remarks = ''
        purchase.rejected_on = None
        purchase.rejected_by = 0
        purchase.total_sgst = total_tax_sgst
        purchase.total_paid = total_paid
        purchase.balance = balance
        purchase.charges = charges
        purchase.updated_on = current_time
        purchase.updated_by = user_id
        purchase.save()

        return JsonResponse({'success': True, 'message': 'success'})

    except Exception as e:
        return JsonResponse({'success': False, 'message': str(e)})


from django.http import JsonResponse

def ajax_load_customers(request):
    branch_id = request.session.get('branch_id')
    user_type = (request.session.get('user_type') or '').strip().lower()
    customer_source = (request.GET.get('customer_source') or 'b2b_customer').strip().lower()

    data = []

    if customer_source == 'b2b_boys':
        boys_branches = branch_table.objects.filter(
            status=1,
            is_active=1,
            is_wholesale=1,
            store_type='wholesale'
        ).order_by('name')

        for b in boys_branches:
            data.append({
                'id': b.id,
                'name': b.name,
                'phone': b.phone or '',
                'customer_type': 'b2b_boys',
                'c_type': 'B2B BOYS',
            })
    else:
        customer_query = Q(
            status=1,
            is_active=1,
            branch_id=branch_id
        )

        # Wholesale user -> only B2C customer records marked as wholesale in same branch.
        if user_type == 'wholesale':
            customer_query &= Q(c_type__iexact='b2c')
            customer_query &= Q(is_wholesale=1)

        customers = customer_table.objects.filter(customer_query).order_by('name')

        for c in customers:
            data.append({
                'id': c.id,
                'name': c.name,
                'phone': c.mobile or c.phone,
                'customer_type': (c.customer_type or '').title(),
                'c_type': (c.c_type or '').upper(),

            })

    return JsonResponse({'data': data})


def ajax_load_sales_requests(request):
    branch_id = request.session.get('branch_id')
    customer_id = int(request.GET.get('customer_id') or 0)

    if customer_id <= 0:
        return JsonResponse({'data': []})

    approved_pos = list(
        wholesale_po_table.objects.filter(
            status=1,
            is_active=1,
            po_status__iexact='approved',
            branch_id=customer_id,
            supply_branch_id=branch_id
        ).values('id', 'po_no')
    )

    if not approved_pos:
        return JsonResponse({'data': []})

    po_ids = [row['id'] for row in approved_pos]

    requested_qty_rows = (
        wholesale_tx_po_table.objects
        .filter(status=1, tm_po_id__in=po_ids)
        .values('tm_po_id')
        .annotate(requested_qty=Sum('quantity'))
    )
    requested_qty_map = {
        int(row['tm_po_id']): float(row['requested_qty'] or 0)
        for row in requested_qty_rows
    }

    sold_qty_map = {po_id: 0.0 for po_id in po_ids}
    with connection.cursor() as cursor:
        tx_cols = [col.name for col in connection.introspection.get_table_description(cursor, 'tx_sales')]
        tm_cols = [col.name for col in connection.introspection.get_table_description(cursor, 'tm_sales')]

        if 'ws_po_id' in tx_cols:
            placeholders = ','.join(['%s'] * len(po_ids))
            sql = (
                f"SELECT ws_po_id, COALESCE(SUM(quantity), 0) AS sold_qty "
                f"FROM tx_sales "
                f"WHERE status = 1 AND branch_id = %s AND ws_po_id IN ({placeholders}) "
                f"GROUP BY ws_po_id"
            )
            cursor.execute(sql, [branch_id, *po_ids])
            for ws_po_id, sold_qty in cursor.fetchall():
                sold_qty_map[int(ws_po_id)] = float(sold_qty or 0)
        elif 'ws_po_id' in tm_cols:
            placeholders = ','.join(['%s'] * len(po_ids))
            sql = (
                f"SELECT ws_po_id, COALESCE(SUM(total_quantity), 0) AS sold_qty "
                f"FROM tm_sales "
                f"WHERE status = 1 AND branch_id = %s AND ws_po_id IN ({placeholders}) "
                f"GROUP BY ws_po_id"
            )
            cursor.execute(sql, [branch_id, *po_ids])
            for ws_po_id, sold_qty in cursor.fetchall():
                sold_qty_map[int(ws_po_id)] = float(sold_qty or 0)

    unique = {}
    for po in approved_pos:
        po_id = int(po['id'])
        requested = requested_qty_map.get(po_id, 0.0)
        sold = sold_qty_map.get(po_id, 0.0)
        remaining = requested - sold
        if remaining > 0 and po_id not in unique:
            unique[po_id] = {
                'id': po_id,
                'po_no': po.get('po_no') or '-',
            }

    return JsonResponse({'data': list(unique.values())})


# *******************************************************************************************************************************



# *******************************************************************************************************************************
def fetch_imei_data(imei_no, branch_id, is_demo=0):
    candidate_records = []

    # 1. OPENING STOCK
    opening_stock = opening_stock_table.objects.filter(
        imei_no=imei_no, status=1, is_active=1, branch_id=branch_id, is_demo=is_demo
    ).order_by('-created_on').first()
    if opening_stock:
        candidate_records.append((opening_stock, "opening_stock"))

    # 2. PURCHASE INWARD
    child_pu = child_purchase_inward_table.objects.filter(
        imei_no=imei_no, status=1, branch_id=branch_id, is_demo=is_demo
    ).order_by('-created_on').first()
    if child_pu and purchase_inward_table.objects.filter(
        id=child_pu.tm_pu_id, status=1, branch_id=branch_id
    ).exists():
        candidate_records.append((child_pu, "inward"))

    # 3. TRANSFER IN
    child_mat_in = child_material_inward_table.objects.filter(
        imei_no=imei_no, status=1, branch_id=branch_id, is_demo=is_demo
    ).order_by('-created_on').first()
    if child_mat_in and material_inward_table.objects.filter(
        id=child_mat_in.tm_material_id, status=1, branch_id=branch_id
    ).exists():
        candidate_records.append((child_mat_in, "transfer_in"))

    if not candidate_records:
        return None, None

    # Sort candidates by created_on DESC and return the latest
    candidate_records.sort(key=lambda x: x[0].created_on, reverse=True)
    return candidate_records[0]

def check_sold_or_transferred(imei_no, branch_id, sales_id=None, since_date=None):
    """
    Check if IMEI is already sold, transferred out, or purchase returned.
    Uses Counter logic (Inwards vs Outwards) for 100% accuracy with movements.
    """
    from collections import Counter
    
    inwards = 0
    outwards = 0
    
    # --- 1. COUNT INWARDS ---
    # a. Purchase Inwards
    inwards += child_purchase_inward_table.objects.filter(imei_no=imei_no, branch_id=branch_id, status=1).count()
    # b. Material Inwards (Transfers Received)
    inwards += child_material_inward_table.objects.filter(imei_no=imei_no, branch_id=branch_id, status=1).count()
    # c. Opening Stock
    inwards += opening_stock_table.objects.filter(imei_no=imei_no, branch_id=branch_id, status=1, is_active=1).count()
    # d. Sales Returns
    inwards += child_sales_return_table.objects.filter(imei_no=imei_no, branch_id=branch_id, status=1).count()
    
    # --- 2. COUNT OUTWARDS ---
    # a. Sales
    sales_qs = child_sales_order_table.objects.filter(imei_no=imei_no, branch_id=branch_id, status=1)
    if sales_id:
        sales_qs = sales_qs.exclude(tm_sales_id=sales_id)
    outwards += sales_qs.count()
    
    # b. Material Outwards (Transfers Sent)
    outwards += child_material_outward_table.objects.filter(
        imei_no=imei_no, branch_id=branch_id, status=1,
        tm_material_id__in=material_outward_table.objects.exclude(outward_status='rejected').values_list('id', flat=True)
    ).count()
    
    # c. Purchase Returns (Returned to Supplier)
    outwards += child_purchase_return_table.objects.filter(
        imei_no=imei_no, branch_id=branch_id, status=1,
        tm_return_id__in=purchase_return_table.objects.exclude(pr_status__iexact='reversed').values_list('id', flat=True)
    ).count()

    # --- 3. FINAL DECISION ---
    if (inwards - outwards) <= 0:
        # Determine specific reason for debugging
        if child_sales_order_table.objects.filter(imei_no=imei_no, branch_id=branch_id, status=1).exclude(tm_sales_id=sales_id).exists():
            return "sold"
        if child_purchase_return_table.objects.filter(imei_no=imei_no, branch_id=branch_id, status=1).exists():
            return "purchase_returned"
        return "transferred_out"

    return None

def get_active_schemes(item):
    """Return active schemes for a given item."""
    today = timezone.now().date()

    schemes = item_scheme_table.objects.filter(
        brand_id=item.brand_id,
        model_id=item.model_id,
        variant_id=item.variant_id,
        status=1,
        from_date__lte=today,
        to_date__gte=today
    )

    scheme_info = {
        'special_id': 0,
        'special_scheme_amount': 0,
        'upgrade_id': 0,
        'upgrade_amount': 0,
        'total_scheme_amount': 0,
        'mop_after_drop': 0,
        'bop_drop': 0,
        'scheme_list': []
    }

    for sch in schemes:
        stype  = str(sch.scheme_type).lower()
        amount = float(sch.bop_drop or 0)

        if amount <= 0:
            continue

        print(f"Scheme Found ➜ {sch.name} | Type: {stype} | Amount: {amount}")

        if stype == 'special':
            scheme_info['special_id'] = sch.id
            scheme_info['special_scheme_amount'] = amount

        elif stype == 'upgrade':
            scheme_info['upgrade_id'] = sch.id
            scheme_info['upgrade_amount'] = amount

        # store last known drops (usually same across schemes)
        scheme_info['mop_after_drop'] = float(sch.mop_drop or 0)
        scheme_info['bop_drop']       = float(sch.bop_drop or 0)

        scheme_info['scheme_list'].append({
            'id': sch.id,
            'name': sch.name,
            'type': sch.scheme_type,
            'amount': amount
        })

    # 🔥 FINAL CALCULATION — THE IMPORTANT PART
    scheme_info['total_scheme_amount'] = (
        scheme_info['special_scheme_amount']
        + scheme_info['upgrade_amount']
    )

    print(
        f"Final Scheme Calculation ➜ "
        f"Special: {scheme_info['special_scheme_amount']} | "
        f"Upgrade: {scheme_info['upgrade_amount']} | "
        f"Total Deduction: {scheme_info['total_scheme_amount']}"
    )

    return scheme_info

from django.utils import timezone


# Main View
def get_imei_price_details(request):
    imei_no = request.POST.get('imei_no', '')
    sales_id = request.POST.get('sales_id')
    customer_type = request.POST.get('customer_type') or 'retail'
    customer_source_mode = request.POST.get('customer_source_mode') or ''
    branch_id = request.session.get('branch_id')
    user_type = request.session.get('user_type')
    customer_id = request.session.get('customer_id') or request.session.get('user_id')
    
    # Receive is_demo
    try:
        req_is_demo = int(request.POST.get('is_demo', 0))
    except (ValueError, TypeError):
        req_is_demo = 0

    print('customer_type', customer_type)

    if not imei_no:
        return JsonResponse({'status': 'error', 'message': 'IMEI number is required'}, status=400)

    # ========================================
    # INTERNAL WHOLESALE STOCK VALIDATION
    # ========================================
    if user_type == 'wholesale':
        data = fetch_imei_from_internal_wholesale_stock(
            imei_no=imei_no,
            branch_id=branch_id,
            sales_id=sales_id,
            req_is_demo=req_is_demo
        )

        if not data:
            meta_msg = 'IMEI not found in demo wholesale stock.' if req_is_demo == 1 else 'IMEI not available in wholesale stock.'
            return JsonResponse({
                'status': 'error',
                'message': meta_msg
            }, status=404)

        if req_is_demo == 1:
            scheme_info = {
                'special_id': 0,
                'special_scheme_amount': 0,
                'upgrade_id': 0,
                'upgrade_amount': 0,
                'total_scheme_amount': 0,
                'mop_after_drop': 0,
                'bop_drop': 0,
                'scheme_list': []
            }
        else:
            scheme_info = get_active_schemes(data)

        return JsonResponse(prepare_response(data, scheme_info, customer_type, customer_source_mode, source='internal_wholesale_stock'))

    # ========================================
    # WHOLESALE CUSTOMER STOCK VALIDATION
    # ========================================
    if user_type == 'wholesale_customer':
        from wholesale.external import get_wholesale_customer_available_stock
        
        # Get available stock for this wholesale customer, excluding current sales session if editing
        try:
            exclude_id = int(sales_id) if sales_id else None
        except (ValueError, TypeError):
            exclude_id = None
            
        available_items = get_wholesale_customer_available_stock(customer_id, exclude_tm_sales_id=exclude_id)
        
        # Check if IMEI exists in their available stock
        data = available_items.filter(imei_no__iexact=imei_no).first()
        
        if not data:
            return JsonResponse({
                'status': 'error',
                'message': 'IMEI not available in your stock'
            }, status=404)
        
        # For wholesale customers, fetch schemes based on their stock
        # Skip schemes for demo items
        if req_is_demo == 1:
            scheme_info = {
                'special_id': 0,
                'special_scheme_amount': 0,
                'upgrade_id': 0,
                'upgrade_amount': 0,
                'total_scheme_amount': 0,
                'mop_after_drop': 0,
                'bop_drop': 0,
                'scheme_list': []
            }
        else:
            scheme_info = get_active_schemes(data)
        
        # Prepare and return response for wholesale customer
        return JsonResponse(prepare_response(data, scheme_info, customer_type, customer_source_mode, source='wholesale_stock'))

    # ========================================
    # REGULAR STORE STOCK VALIDATION
    # ========================================
    if not branch_id:
        return JsonResponse({'status': 'error', 'message': 'Branch information required'}, status=400)

    # Fetch IMEI with is_demo
    data, source = fetch_imei_data(imei_no, branch_id, req_is_demo)
    print('data', data, 'source', source)
    if not data:
        meta_msg = 'IMEI not found in demo stock.' if req_is_demo == 1 else 'IMEI not in stock'
        return JsonResponse({'status': 'error', 'message': meta_msg}, status=404)

    # Check sold or transferred
    status_check = check_sold_or_transferred(imei_no, branch_id, sales_id, since_date=data.created_on)

    if status_check == "transferred_out":
        return JsonResponse({'status': 'error', 'message': 'IMEI already transferred out'}, status=400)

    if status_check == "sold":
        return JsonResponse({'status': 'error', 'message': 'IMEI already sold'}, status=400)

    if status_check == "purchase_returned":
        return JsonResponse({'status': 'error', 'message': 'IMEI already returned to supplier'}, status=400)

    # Fetch schemes - Skip schemes for demo items
    if req_is_demo == 1:
        scheme_info = {
            'special_id': 0,
            'special_scheme_amount': 0,
            'upgrade_id': 0,
            'upgrade_amount': 0,
            'total_scheme_amount': 0,
            'mop_after_drop': 0,
            'bop_drop': 0,
            'scheme_list': []
        }
    else:
        scheme_info = get_active_schemes(data)

    # Prepare response
    return JsonResponse(prepare_response(data, scheme_info, customer_type, customer_source_mode, source))


from django.utils import timezone
from decimal import Decimal
from django.utils import timezone
from decimal import Decimal
from django.utils import timezone

def get_price_drop_for_imei(imei_no=None, subcategory_id=None, brand_id=None, model_id=None, variant_id=None,
                            mrp=0.00, bop=0.00, mop=0.00, wsp=0.00, fsp=0.00):
    today = timezone.now().date()
    price_data = {
        'bop': Decimal(bop),
        'mop': Decimal(mop),
        'wsp': Decimal(wsp),
        'fsp': Decimal(fsp),
        'bop_drop': Decimal('0.00'),
        'mop_after_drop': Decimal('0.00'),
        'mrp': Decimal(mrp)
    }

    # 1️⃣ Item-Level TM Price
    if subcategory_id and brand_id and model_id and variant_id:
        price_hist = price_history_table.objects.filter(
            subcategory_id=subcategory_id,
            brand_id=brand_id,
            model_id=model_id,
            variant_id=variant_id,
            from_date__lte=today,
            is_active=1,
            status=1
        ).order_by('id').first()

        if price_hist:   
            tx_price = tx_price_history_table.objects.filter(
                imei_no=imei_no,
                status=1
            ).order_by('-id').first()

            if tx_price:
                price_data.update({
                    'bop': Decimal(tx_price.bop_after_discount or tx_price.bop or 0),
                    'mop': Decimal(tx_price.mop_after_discount or tx_price.mop or 0),
                    'wsp': Decimal(tx_price.wsp_after_discount or tx_price.wsp or 0),
                    'fsp': Decimal(tx_price.fsp_after_discount or tx_price.fsp or 0),
                    'mrp': Decimal(mrp)
                })
    else:
        price_data.update({
            'bop': Decimal(bop),
            'mop': Decimal(mop),
            'wsp': Decimal(wsp),
            'fsp': Decimal(fsp),
        })
    print('**************')
    print(price_data)
    print('**************')
    return price_data



def prepare_response(data, scheme_info, customer_type, customer_source_mode="", source=""):

    """
    Prepare price details response for IMEI or item with scheme and discount applied.
    Includes detailed, user-friendly debug print statements.
    """

    print("\n" + "="*60)
    print("📦 START IMEI PRICE CALCULATION")
    print(f"Source        : {source}")
    print(f"Customer Type : {customer_type}")
    print(f"Customer Mode : {customer_source_mode}")
    print(f"IMEI No       : {getattr(data, 'imei_no', '-')}")
    print("="*60 + "\n")
    if source == 'opening_stock':
        price = data.db_price or 0
    else:
        price = data.amount or 0

    price_drop = get_price_drop_for_imei(
        imei_no=getattr(data, 'imei_no', None),
        subcategory_id=getattr(data, 'subcategory_id', None),
        brand_id=getattr(data, 'brand_id', None),
        model_id=getattr(data, 'model_id', None),
        variant_id=getattr(data, 'variant_id', None),
        mrp=getattr(data, 'fsp', 0.00),
        bop=getattr(data, 'bop', 0.00),
        mop=getattr(data, 'mop', 0.00),
        wsp=getattr(data, 'wsp', 0.00),
        fsp=getattr(data, 'fsp', 0.00),


    )

    base_mrp = Decimal(getattr(data, 'mrp', 0) or 0)
    base_bop = Decimal(price_drop.get('bop', 0) or 0)
    base_mop = Decimal(price_drop.get('mop', 0) or 0)
    base_wsp = Decimal(price_drop.get('wsp', 0) or 0)
    base_fsp = Decimal(price_drop.get('fsp', 0) or 0)

    print("💰 Base Prices (After Price Drop)")
    print(f"  BOP : {base_bop}")
    print(f"  MOP : {base_mop}")
    print(f"  WSP : {base_wsp}")
    print(f"  FSP : {base_fsp}")
    print(f"  MRP : {base_mrp}\n")

    total_scheme_amount = Decimal(
        scheme_info.get('total_scheme_amount', 0) or 0
    )

    print("🎁 Applying Scheme Drops (Combined)")
    print(f"  Original BOP          : {base_bop}")
    print(f"  Original MOP          : {base_mop}")
    print(f"  Total Scheme Deduction: {total_scheme_amount}")

    if total_scheme_amount > 0:
        base_bop = max(base_bop, Decimal(0))
        base_mop = max(base_mop - total_scheme_amount, Decimal(0))

    print(f"  ▶ BOP After Scheme : {base_bop}")
    print(f"  ▶ MOP After Scheme : {base_mop}\n")


   
    if str(customer_source_mode).lower() == 'b2b_boys':
        discount_mode = "B2B Boys (MRP − FSP)"
        discount_amount = base_mrp - base_fsp
        selling_rate = base_fsp
    elif customer_type.lower() == 'retail':
        discount_mode = "Retail (MRP − BOP)"
        discount_amount = base_mrp - base_bop
        selling_rate = base_bop
    else:
        discount_mode = "Wholesale (MRP − WSP)"
        discount_amount = base_mrp - base_wsp
        selling_rate = base_wsp

    discount_percent = (discount_amount / base_mrp * Decimal(100)) if base_mrp > 0 else Decimal(0)

    print("📊 Discount Calculation")
    print(f"  Mode            : {discount_mode}")
    print(f"  Selling Price   : {selling_rate}")
    print(f"  Discount Amount : {discount_amount:.2f}")
    print(f"  Discount Percent: {discount_percent:.2f}%\n")

    # ─────────────────────────────────────
    # 4️⃣ GST Calculation (Inclusive 18%)
    # ─────────────────────────────────────
    taxable_value = base_bop / Decimal('1.18')
    total_tax_amount = base_bop - taxable_value
    cgst_amount = total_tax_amount / Decimal('2')
    sgst_amount = total_tax_amount / Decimal('2')

    print("🧾 GST Breakdown (Inclusive 18%)")
    print(f"  Taxable Value : {taxable_value:.2f}")
    print(f"  Total GST     : {total_tax_amount:.2f}")
    print(f"  CGST (9%)     : {cgst_amount:.2f}")
    print(f"  SGST (9%)     : {sgst_amount:.2f}\n")

    # ─────────────────────────────────────
    # 5️⃣ Scheme Summary
    # ─────────────────────────────────────
    has_scheme = len(scheme_info.get('scheme_list', [])) > 0

    print("📦 Scheme Summary")
    print(f"  Has Scheme      : {'Yes' if has_scheme else 'No'}")
    print(f"  Schemes Applied : {len(scheme_info.get('scheme_list', []))}")
    for s in scheme_info.get('scheme_list', []):
        print(f"    - {s.get('name')} ({s.get('type')}) Amount: {s.get('amount')}")
    print("="*60 + "\n")

    # ─────────────────────────────────────
    # 6️⃣ Prepare Final JSON Response
    # ─────────────────────────────────────
    return {
        'status': 'success',
        'source': source,
        'imei_no': getattr(data, 'imei_no', ''),
        'item_id': getattr(data, 'item_id', 0),
        'model_id': getattr(data, 'model_id', 0),
        'brand_id': getattr(data, 'brand_id', 0),
        'variant_id': getattr(data, 'variant_id', 0),
        'db_price': float(data.db_price if data.db_price else 0),
        'color_id': getattr(data, 'color_id', 0),
        'ean': getattr(data, 'ean', ''),
        'rate': float(base_mrp),

        'discount_percent': float(round(discount_percent, 2)),
        'discount_amount': float(round(discount_amount, 2)),

        'taxable_value': float(round(taxable_value, 2)),
        'tax_amount': float(round(total_tax_amount, 2)),
        'cgst': float(round(cgst_amount, 2)),
        'sgst': float(round(sgst_amount, 2)),
        'tax_percent': 18,

        'wsp': float(base_wsp),
        'fsp': float(base_fsp),
        'mop': float(base_mop),
        'bop': float(base_bop),

        'purchase_price': float(price),
        'sales_id': data.id if hasattr(data, 'id') else 0,
        

        'special_id': scheme_info.get('special_id', 0),
        'special_scheme_amount': float(scheme_info.get('special_scheme_amount', 0)),
        'upgrade_id': scheme_info.get('upgrade_id', 0),
        'upgrade_amount': float(scheme_info.get('upgrade_amount', 0)),
        'has_scheme': has_scheme,
        'schemes': scheme_info.get('scheme_list', []),
    }

@transaction.atomic
def reject_sales_order(request):
    if request.method != 'POST':
        return JsonResponse({'status': 'error', 'message': 'Invalid request'})
    
    role_id = request.session.get('role_id')
    # Using 'sales' 'update' permission as we are modifying sales order
    has_access, error_message = check_user_access(role_id, 'sales', "update")
    if not has_access:
        return JsonResponse({'status': 'error', 'message': 'Permission denied'})

    try:
        sales_id = request.POST.get('id')
        remarks = request.POST.get('remarks')
        user_id = request.session.get('user_id')
        now = timezone.localtime(timezone.now())

        if not sales_id:
             return JsonResponse({'status': 'error', 'message': 'Sales ID missing'})

        sales_order = sales_order_table.objects.get(id=sales_id)
        sales_order.sales_status = 'rejected'
        sales_order.rejected_remarks = remarks
        sales_order.rejected_on = now
        sales_order.rejected_by = user_id
        sales_order.save()
        
        return JsonResponse({'status': 'success', 'message': 'Sales Order Rejected Successfully'})
    except sales_order_table.DoesNotExist:
        return JsonResponse({'status': 'error', 'message': 'Sales Order not found'})
    except Exception as e:
        return JsonResponse({'status': 'error', 'message': str(e)})

