from django.shortcuts import render, get_object_or_404
import json
import os
import hashlib
import base64
from datetime import datetime
from io import BytesIO

from django.conf import settings
from django.db import IntegrityError, transaction, connection
from django.db.models import Max, F, Q, Sum
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse, FileResponse
from django.template.loader import get_template
from django.templatetags.static import static
from django.contrib.staticfiles import finders
from django.core.files.storage import default_storage

from xhtml2pdf import pisa
from num2words import num2words
import openpyxl
from openpyxl.styles import Font

from material.models import *
from expenses.models import *
from store.models import *
from company.models import *
from masters.models import *
from supplier.models import *
from common.utils import *
from inventory.models import *
import openpyxl
from openpyxl.styles import Font
from django.http import HttpResponse
from django.core.files.storage import default_storage

#```````````````````````````````````````````````````````````**PURCHASE ORDER**````````````````````````````````````````````````````````````````````````````````

def material_inward(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' )

            # Logic for pending inwards from other branches
            pending_outwards = []
            with connection.cursor() as cursor:
                cursor.execute("""
                    SELECT DISTINCT b.name
                    FROM tm_material_out o
                    INNER JOIN branches b ON o.branch_id = b.id
                    INNER JOIN tx_material_out oi ON o.id = oi.tm_material_id
                    LEFT JOIN (
                        SELECT material_outward_id, 
                               SUM(CASE WHEN status = 1 AND is_active = 1 THEN quantity ELSE 0 END) as inward_qty
                        FROM tx_material_in
                        WHERE status = 1 AND is_active = 1
                        GROUP BY material_outward_id
                    ) i ON o.id = i.material_outward_id
                    WHERE o.supply_branch_id = %s 
                      AND o.status = 1 AND o.is_active = 1
                      AND oi.status = 1 AND oi.is_active = 1
                    GROUP BY o.id, b.name
                    HAVING SUM(oi.quantity) > COALESCE(MAX(i.inward_qty), 0)
                """, [branch_id])
                pending_outwards = [row[0] for row in cursor.fetchall()]

            return render(request, 'material_inward/details.html', {
                'supplier': supplier, 
                'branch': branch,
                'pending_outwards': pending_outwards
            })

        else:
            return HttpResponseRedirect("/")
    else:
        return HttpResponseRedirect("/")


def transfer_inward_add(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':
            company = select_row(company_table, {'id': 1})
            if is_ho == 1:
                supplier = selectList(supplier_table, order_by='name' )
            else:
                supplier = selectList(supplier_table, Q(is_global=1), order_by='name' )
        
            subcategory = selectList(sub_category_table)
            branch = selectList(branch_table, order_by='name').exclude(id=branch_id)
            brand = selectList(brand_table, order_by='name')
            uom = selectList(uom_table)
            item = selectList(item_table)
            employee = employee_list(request)
            fyf_name = request.session.get('fyf')
            financial_year = calculate_financial_year(fyf_name)
            transfer_no = generate_serial_number(
                model=material_inward_table,
                number_field='transfer_no',
                financial_year_field='current_fy',
                financial_year=financial_year,
                branch_id=branch_id
            )
            return render(request, 'material_inward/add.html', {'company': company,'supplier':supplier,'branch':branch,'subcategory':subcategory,'brand':brand,'uom':uom,
                                    'item':item,'employee':employee,'transfer_no':transfer_no})
        else:
            return HttpResponseRedirect("/")
    else:
        return HttpResponseRedirect("/")


def get_po_details_by_pu_ids(pu_ids):
    """
    pu_ids        -> purchase_inward_table.id
    child table   -> tm_pu_id (FK reference)
    returns       -> {
        pu_id: {
            'po_no': 'PO001, PO002',
            'po_date': '01-01-2026, 05-01-2026'
        }
    }
    """

    # 1️⃣ PU → PO mapping from child table
    child_rows = (
        child_material_inward_table.objects
        .filter(tm_material_id__in=pu_ids, material_outward_id__isnull=False)
        .values('tm_material_id', 'material_outward_id')
        .distinct()
    )

    po_ids = {row['material_outward_id'] for row in child_rows}
    if not po_ids:
        return {}

    # 2️⃣ Fetch PO details
    po_map = {
        po.id: {
            'po_no': po.transfer_no,
            'po_date': po.transfer_date
        }
        for po in material_outward_table.objects.filter(id__in=po_ids)
    }

    # 3️⃣ Build PU → PO details
    pu_po_map = {}

    for row in child_rows:
        pu_id = row['tm_material_id']
        po = po_map.get(row['material_outward_id'])
        if not po:
            continue

        pu_po_map.setdefault(pu_id, {
            'po_no': set(),
            'po_date': set()
        })

        pu_po_map[pu_id]['po_no'].add(po['po_no'])

        if po['po_date']:
            pu_po_map[pu_id]['po_date'].add(
                format_date_month_year(po['po_date'])
            )

    # 4️⃣ Convert sets → strings
    return {
        pu_id: {
            'po_no': ', '.join(sorted(v['po_no'])),
            'po_date': ', '.join(sorted(v['po_date']))
        }
        for pu_id, v in pu_po_map.items()
    }


def ajax_material_inward_view(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)
    
    is_ho = request.session.get('is_ho') or 0
    has_access, error_message = check_user_access(role_id, 'transfer_in', "read")

    if not has_access:
        return JsonResponse({'message': 'permission', 'error_message': 'You do not have permission to view transfer inward details'})
    
    supply_branch = request.POST.get('supply_branch')
    from_date = request.POST.get('from_date')
    to_date = request.POST.get('to_date')
    keyword = request.POST.get('keyword_search', '').strip()

    query = Q(status=1,branch_id=branch_id,current_fy=financial_year)

    if supply_branch:
        query &= Q(supply_branch_id=supply_branch)

        
    if from_date and to_date:
        query &= Q(transfer_date__range=[from_date, to_date])

    if keyword:
        query &= Q(transfer_no__icontains=keyword)
       
    
    
    data = list(selectList(material_inward_table, query).values())
    pu_ids = [item['id'] for item in data]

    # 🔹 Fetch PO numbers ONCE (from child table)
    po_details_map = get_po_details_by_pu_ids(pu_ids)


    formatted = []
    for index, item in enumerate(data):       
        branch_name = getItemNameById(branch_table, item['supply_branch_id']) if item['supply_branch_id'] else '-'
        outward_no = po_details_map.get(item['id'], {}).get('po_no', '-') if po_details_map else '-'
        outward_date = po_details_map.get(item['id'], {}).get('po_date', '-') if po_details_map else '-'

        action_buttons = (
            f'<button type="button" onclick=\'view_data({item["id"]}, {json.dumps(branch_name)}, {json.dumps(outward_no)}, {json.dumps(outward_date)})\' class="btn btn-outline-primary btn-xs p-1" title="View">'
            f'<i class="fas fa-eye"></i></button> '
           
        )
        po_details = po_details_map.get(item['id'], {})
        


        formatted.append({
            'id': index + 1,
            'action': action_buttons,
            'outward_no':po_details.get('po_no', '-'),
            'outward_date':po_details.get('po_date', '-'),
            'po_no': item['transfer_no'] if item['transfer_no'] else '-', 
            'po_date': format_date_month_year(item['transfer_date']), 
            'supply_branch': branch_name, 
            'quantity': item['total_quantity'] if item['total_quantity'] else '-', 
            'amount': format_amount(item['total_amount']),
            'cash': format_amount(item['cash']),
            'bank': format_amount(item['bank']),
            'balance': format_amount(item['balance']),            
            
            'remarks': item['remarks'] if item['remarks'] else '-', 
            'status': '<span class="badge text-bg-success">Active</span>' if item['is_active'] else '<span class="badge text-bg-danger">Inactive</span>'
        })

    return JsonResponse({'data': formatted})



def material_inward_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')
            decoded_id = decode_base64_id(encoded_id)

            if not decoded_id:
                return HttpResponse("ID parameter is missing")

            company = select_row(company_table, {'id': 1})
            purchase_order = select_row(material_inward_table, {'id': decoded_id})

            # base service list for the branch
            

            # rest of your lists
            if is_ho == 1:
                supplier = selectList(supplier_table,  order_by='name')
            else:
                supplier = selectList(supplier_table, Q(is_global=1), order_by='name')

            branch = selectList(branch_table, order_by='name').exclude(id=branch_id)
            item = selectList(item_table)
            employee = employee_list(request)
            category = selectList(category_table)
            brand = selectList(brand_table, order_by='name')
            uom = selectList(uom_table)

            return render(request, 'material_inward/edit.html', {
                'company': company,
                'purchase': purchase_order,
                'id': decoded_id,
                'employee': employee,
                'category': category,
                'brand': brand,
                'uom': uom,
                'supplier': supplier,
                'branch': branch,
                'item': item,                
            })

        else:
            return HttpResponseRedirect("/")
    else:
        return HttpResponseRedirect("/")




def delete_tm_material_inward(request):
    if request.method != 'POST':
        return JsonResponse({'status': 'error', 'error_message': 'Invalid request method'})

    data_id = request.POST.get('id')
    role_id = request.session.get('role_id')
    branch_id = request.session.get('branch_id')
    tm_purchase = select_row(purchase_inward_table, {'id': data_id})

    has_access, error_message = check_user_access(role_id, 'transfer_in', "delete")
    if not has_access:
        return JsonResponse({'message': 'permission', 'error_message': 'You do not have permission to delete transfer inward details'})

   

    try:
        if tm_purchase.inward_type == 'service':
            service_rows = child_purchase_inward_table.objects.filter(
                tm_pu_id=data_id,
                inward_type='service',
                status=1,
                branch_id=branch_id
            ).values_list('service_id', flat=True)

            
            if tm_purchase.inward_type == 'accessory':       
            # 🔍 1. Check if purchase return exists
                return_exists = child_purchase_return_table.objects.filter(
                    pu_id=data_id,
                    status=1,
                    branch_id=branch_id
                ).exists()

            if return_exists:
                return JsonResponse({
                    'message': 'warning',
                    'error_message': 'There was a purchase return against this inward, so it cannot be deleted.'
                })

            # 🔍 2. Get all batch numbers from this inward
            inward_batches = child_purchase_inward_table.objects.filter(
                tm_pu_id=data_id,
                status=1,
                branch_id=branch_id
            ).values_list("batch_no", flat=True)

            if inward_batches:
                sales_exists = child_sales_order_table.objects.filter(
                    branch_id=branch_id,
                    status=1,
                    batch_no__in=inward_batches
                ).exists()

            if sales_exists:
                return JsonResponse({
                    'message': 'warning',
                    'error_message': 'There was sales against this purchase inward, so it cannot be deleted.'
                })

            # 🔍 2b. Check sales returns
            sales_return_exists = child_sales_return_table.objects.filter(
                branch_id=branch_id,
                status=1,
                batch_no__in=inward_batches
            ).exists()

            if sales_return_exists:
                return JsonResponse({
                    'message': 'warning',
                    'error_message': 'There was a sales and sales return against this inward, so it cannot be deleted.'
                })

        # ✅ Mark purchase inward and child rows as inactive
        purchase_inward_table.objects.filter(id=data_id).update(status=0, is_active=0)
        child_purchase_inward_table.objects.filter(tm_pu_id=data_id).update(status=0, is_active=0)

        return JsonResponse({'message': 'yes'})

    except Exception as e:
        return JsonResponse({'status': 'error', 'error_message': str(e)})




from django.http import JsonResponse
from django.db.models import Q, Sum
import json

def load_material_outward(request):
    try:
        # 1. Get parameters
        requesting_branch_id = request.GET.get('supply_branch_id')  # The branch that raised the request
        our_branch_id = request.session.get('branch_id')           # Our branch (the supplier/inwarding branch)
        edit_id = int(request.GET.get('edit_id') or 0)

        # 2. Validation: Ensure IDs exist
        if not requesting_branch_id or not our_branch_id:
            return JsonResponse([], safe=False)

        
        target_branch = requesting_branch_id
        source_branch = our_branch_id

        # 3. Filter Material Outward headers
        po_filter = Q(
            status=1,
            is_active=1,
            branch_id=target_branch,
            supply_branch_id=source_branch
        )
        print('po_filter',po_filter)

        pos = material_outward_table.objects.filter(po_filter)
        print('pos',pos)
        valid_pos = []

        # 4. Check each Outward to see if it has pending quantities to be Inwarded
        for po in pos:
            print('po',po.id, po.transfer_no)
            po_items = child_material_outward_table.objects.filter(
                tm_material_id=po.id,
                status=1,
                is_active=1,
                branch_id=target_branch,
                supply_branch_id=source_branch
            )
            print('po_items',po_items)

            include_po = False
            for item in po_items:
                # Calculate how much of this item has already been Inwarded
                inward_filter = Q(
                    material_outward_id=po.id,
                    brand_id=item.brand_id,
                    model_id=item.model_id,
                    variant_id=item.variant_id,
                    color_id=item.color_id,
                    status=1,
                    is_active=1,
                    branch_id=source_branch 
                )

                # If editing an existing inward, exclude the current transaction's quantity
                if edit_id:
                    inward_filter &= ~Q(tm_material_id=edit_id)

                already_inwarded_qty = child_material_inward_table.objects.filter(
                    inward_filter
                ).aggregate(total=Sum('quantity'))['total'] or 0
                print('already_inwarded_qty',already_inwarded_qty)

                # If the outwarded quantity is greater than what we've inwarded, show this PO
                if (item.quantity or 0) > already_inwarded_qty:
                    include_po = True
                    break
            print('include_po',include_po, po.transfer_no)
            if include_po:
                valid_pos.append({
                    "id": str(po.id),
                    "po_no": po.transfer_no # Using the transfer number for selection
                })

        return JsonResponse(valid_pos, safe=False)

    except Exception as e:
        # Catch-all to ensure an HttpResponse is ALWAYS returned even on error
        return JsonResponse({'error': str(e)}, status=500)
from django.db.models import Sum
from django.http import JsonResponse
def load_material_outward_items(request):
    edit_id = int(request.GET.get('edit_id') or 0)
    material_po_id = request.GET.get('material_po_id')

    po_ids = comma_separated(material_po_id) or []
    if not po_ids:
        return JsonResponse({"items": []})

    # 1️⃣ Get outward items FIRST
    outward_items = child_material_outward_table.objects.filter(
        tm_material_id__in=po_ids,
        status=1,
        is_active=1
    )

    final_items = []

    for item in outward_items:

        # 2️⃣ Check already inwarded quantity for THIS outward row
        inward_qs = child_material_inward_table.objects.filter(
            material_outward_id=item.tm_material_id, 
            brand_id=item.brand_id,
            model_id=item.model_id,
            variant_id=item.variant_id,
            color_id=item.color_id,
            status=1,
            is_active=1
        )

        if edit_id:
            inward_qs = inward_qs.exclude(tm_material_id=edit_id)

        inwarded_qty = inward_qs.aggregate(
            total=Sum('quantity')
        )['total'] or 0

        remaining_qty = (item.quantity or 0) - inwarded_qty

        # 3️⃣ Skip fully inwarded rows
        if remaining_qty <= 0:
            continue

        # 4️⃣ Push remaining outward row (NO GROUPING)
        final_items.append({
            "po_id": item.tm_material_id,
            "ean": item.ean or "",
            "hsn_code": item.hsn_code or "",
            "subcategory_id": item.subcategory_id,
            "subcategory_name": getItemNameById(sub_category_table, item.subcategory_id),
            "brand_id": item.brand_id,
            "brand_name": getItemNameById(brand_table, item.brand_id),
            "model_id": item.model_id,
            "model_name": getItemNameById(model_table, item.model_id),
            "variant_id": item.variant_id,
            "variant_name": getItemNameById(variant_table, item.variant_id),
            "color_id": item.color_id,
            "color_name": getItemNameById(color_table, item.color_id),
            "imei_no": (item.imei_no or "").strip(),
            "quantity": remaining_qty,
            "rate": item.rate or 0,
            "wsp_price": item.wsp or 0,
            "fsp_price": item.fsp or 0,
            "mop_price": item.mop or 0,
            "bop_price": item.bop or 0,
            "db_price": item.db_price or 0,
            "mrp": item.mrp or 0,
            "price_list_id": item.price_list_id or 0
        })

    return JsonResponse({"items": final_items})


from django.http import JsonResponse
from django.db.models import Q
from django.http import JsonResponse
from django.http import JsonResponse

def load_material_outward_imei(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')
    
    # material_id arrives as "1,2,3"
    material_id_raw = request.GET.get('material_id')
    edit_id = int(request.GET.get('edit_id') or 0)

    if not all([brand_id, model_id, variant_id, material_id_raw]):
        return JsonResponse({'status': False, 'message': 'Missing parameters'})

    material_ids = [m.strip() for m in material_id_raw.split(',') if m.strip()]

    outward_items = child_material_outward_table.objects.filter(
        tm_material_id__in=material_ids,
        brand_id=brand_id,
        model_id=model_id,
        variant_id=variant_id,
        color_id=color_id,
        status=1,
        is_active=1
    ).exclude(imei_no__isnull=True).exclude(imei_no='')

    if not outward_items.exists():
        return JsonResponse({'status': False, 'message': 'No outward records found'})

    outward_imei_list = list(outward_items.values_list('imei_no', flat=True))

    inward_qs = child_material_inward_table.objects.filter(
        tm_material_id__in=material_ids,
        brand_id=brand_id,
        model_id=model_id,
        variant_id=variant_id,
        color_id=color_id,
        status=1,
        is_active=1
    )

    if edit_id:
        inward_qs = inward_qs.exclude(id=edit_id)

    already_inwarded_imeis = list(inward_qs.values_list('imei_no', flat=True))

    available_imeis = [i for i in outward_imei_list if i not in already_inwarded_imeis]

    first_item = outward_items.first()
    
    return JsonResponse({
        'status': True,
        'item_id': first_item.item_id or 0,
        'ean': first_item.ean or '',
        'price_id': first_item.price_list_id or 0,
        'mop_price': first_item.mop or 0,
        'db_price': first_item.db_price or 0,
        'wsp_price': first_item.wsp or 0,
        'fsp_price': first_item.fsp or 0,
        'available_imeis': ", ".join(available_imeis), 
        'quantity': len(available_imeis)               
    })



def add_material_inward(request):
    if request.method == 'POST':
        try:
            # --- Session Data ---
            company_id = request.session.get('company_id')
            branch_id = request.session.get('branch_id')
            user_id = request.session.get('user_id')
            fyf_name = request.session.get('fyf')
            financial_year = calculate_financial_year(fyf_name)
            role_id = request.session.get('role_id')

            # --- Permission Check ---
            has_access, error_message = check_user_access(role_id, 'transfer_in', "create")
            if not has_access:
                return JsonResponse({
                    'message': 'permission',
                    'error_message': 'You do not have permission to add purchase inward details'
                })

            # --- Get Main Form Data ---
            po_date = request.POST.get('po_date')
            supply_branch_id = request.POST.get('supply_branch_id') or 0
            remarks = request.POST.get('description')
            total_qty = request.POST.get('total_quantity')
            sub_total = request.POST.get('sub_total')
            round_off = float(request.POST.get('round_off') or 0)
            grand_total = request.POST.get('grand_total')
            transfer_no = request.POST.get('transfer_no') or 0

            
            payment = request.POST.get('payment')
            cash = float(request.POST.get('cash') or 0)
            bank = float(request.POST.get('bank') or 0)
            balance = float(request.POST.get('balance') or 0)
            employee_id = request.POST.get('employee_id') or user_id

            # Parse JSON Items from Frontend
            items = json.loads(request.POST.get('items'))

            # --- Validation ---
            if not supply_branch_id:
                return JsonResponse({'message': 'warning', 'error_message': 'Please select a Supply Branch.'})
            if not items:
                return JsonResponse({'message': 'warning', 'error_message': 'Please add at least one item.'})

            now = timezone.localtime(timezone.now())

            with transaction.atomic():
                # 1. Save Main Entry
                main_obj = material_inward_table.objects.create(
                    company_id=company_id,
                    transfer_date=po_date,
                    transfer_no=transfer_no,
                    num_series=generate_num_series(material_inward_table),
                    current_fy=financial_year,
                    branch_id=branch_id,
                    supply_branch_id=supply_branch_id,
                    remarks=remarks,
                    total_quantity=total_qty,
                    sub_total=sub_total,
                    total_amount=grand_total,
                    roundoff=round_off,
                    payment_type=payment,
                    cash=cash,
                    bank=bank,
                    balance=balance,
                    is_active=1,
                    status=1,
                    time=now.strftime('%H:%M:%S'),
                    created_on=now,
                    updated_on=now,
                    created_by=user_id,
                    updated_by=user_id,
                    employee_id=employee_id
                )

                # 2. Save Child Items (One row per IMEI)
                for item in items:
                    rate = float(item['rate'])
                    
                    imei_data = item.get('imei_no', '')
                   
                        
                    if child_material_inward_table.objects.filter(
                        imei_no=imei_data, status=1
                    ).exists():
                        raise IntegrityError(f"Duplicate IMEI detected: {imei_data}")

                    child_material_inward_table.objects.create(
                        company_id=company_id,
                        current_fy=financial_year,
                        branch_id=branch_id,
                        supply_branch_id=supply_branch_id,
                        tm_material_id=main_obj.id,
                        material_outward_id=int(item.get('po_id') or 0),
                        imei_no=imei_data,
                        subcategory_id=int(item.get('subcategory_id') or 0),
                        brand_id=int(item.get('brand_id') or 0),
                        model_id=int(item.get('model_id') or 0),
                        variant_id=int(item.get('variant_id') or 0),
                        color_id=int(item.get('color_id') or 0),
                        ean=item.get('ean', ''),
                        hsn_code=item.get('hsn_code', ''),
                        rate=rate,
                        quantity=1,           # Quantity is always 1 per row
                        amount=round(rate, 2), # Amount is equal to Rate for Qty 1
                        fsp=float(item.get('fsp_price') or 0),
                        wsp=float(item.get('wsp_price') or 0),
                        mop=float(item.get('mop_price') or 0),
                        bop=float(item.get('bop_price') or 0),
                        mrp=float(item.get('mrp') or 0),
                        db_price=float(item.get('db_price') or 0),
                        is_active=1,
                        status=1,
                        created_on=now,
                        updated_on=now,
                        created_by=user_id,
                        updated_by=user_id,
                        price_list_id=int(item.get('price_list_id') or 0)
                    )

            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)

    return JsonResponse({'message': 'error', 'error_message': 'Invalid Request'}, status=400)



def ajax_tx_material_inward_view(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'
        })

    tm_material_id = request.POST.get('po_id')

    # ----------------------------  
    # Sales Items
    # ----------------------------
    items_qs = child_material_inward_table.objects.filter(
        tm_material_id=tm_material_id,
        status=1,
        current_fy=financial_year
    ).values()
    material_ids = (
        child_material_inward_table.objects
        .filter(
            tm_material_id=tm_material_id,
            status=1,
            current_fy=financial_year
        )
        .values_list('material_outward_id', flat=True)
        .distinct()
    )

    print(list(material_ids))

    print(material_ids)
    items = []
    for index, item in enumerate(items_qs):
        items.append({
            'id': index + 1,
            'ean': item['ean'] or '-',
            'hsn_code': item['hsn_code'] or '-',
            'po_id': item['material_outward_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',
            'qty': item['quantity'] or 0,
            'amount': format_amount(item['amount']) if item['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',
            'mrp': format_amount(item['mrp']) if item['mrp'] else '0.00',
            

             
        })

   

    return JsonResponse({
        'items': items,
        'material_ids': list(material_ids)
    })


from django.db import transaction
@transaction.atomic
def update_material_inward(request):
    if request.method != 'POST':
        return JsonResponse({'success': False, 'message': 'Invalid request'})

    try:
        # 1. Environment & Request Setup
        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)
        
        # Use a consistent variable name for time
        current_time = timezone.localtime(timezone.now())

        data = request.POST
        items_json = data.get('items')
        if not items_json:
            return JsonResponse({'success': False, 'message': 'No items provided'})
            
        items = json.loads(items_json)
        tm_id_parent = int(data.get('tm_material_id') or 0)
        
        # Get parent record
        purchase = select_row(material_inward_table, {'id': tm_id_parent})
        if not purchase:
             return JsonResponse({'success': False, 'message': 'Parent record not found'})

        # 2. Soft delete existing children
        child_material_inward_table.objects.filter(
            tm_material_id=tm_id_parent, 
            status=1
        ).update(status=0, updated_on=current_time, updated_by=user_id)

        # 3. Process and Insert Rows
        for item in items:
            rate = float(item.get('rate') or 0)
            imei_data = item.get('imei_no', '')           
           
            if child_material_inward_table.objects.filter(
                imei_no=imei_data, status=1
            ).exists():
                return JsonResponse({'success': False, 'message': f"Duplicate IMEI detected: {imei_data}"}) 

            child_material_inward_table.objects.create(
                    company_id=company_id,
                    current_fy=financial_year,
                    branch_id=branch_id,
                    supply_branch_id=purchase.supply_branch_id,
                    tm_material_id=purchase.id,
                    material_outward_id=int(item.get('po_id') or 0),
                    imei_no=imei_data,
                    subcategory_id=int(item.get('subcategory_id') or 0),
                    brand_id=int(item.get('brand_id') or 0),
                    model_id=int(item.get('model_id') or 0),
                    variant_id=int(item.get('variant_id') or 0),
                    color_id=int(item.get('color_id') or 0),
                    ean=item.get('ean', ''),
                    hsn_code=item.get('hsn_code', ''),
                    rate=rate,
                    quantity=1,           # Quantity is always 1 per row
                    amount=round(rate, 2), # Amount is equal to Rate for Qty 1
                    fsp=float(item.get('fsp_price') or 0),
                    wsp=float(item.get('wsp_price') or 0),
                    mop=float(item.get('mop_price') or 0),
                    bop=float(item.get('bop_price') or 0),
                    mrp=float(item.get('mrp') or 0),
                    db_price=float(item.get('db_price') or 0),
                    is_active=1,
                    status=1,
                    created_on=current_time,
                    updated_on=current_time,
                    created_by=user_id,
                    updated_by=user_id,
                    price_list_id=int(item.get('price_list_id') or 0)
                )

        # 4. Update Parent Totals
        purchase.total_quantity = int(data.get('total_quantity') or 0)
        purchase.sub_total = float(data.get('sub_total') or 0)
        purchase.total_amount = float(data.get('grand_total') or 0)
        purchase.cash = float(data.get('cash') or 0)
        purchase.bank = float(data.get('bank') or 0)
        purchase.balance = float(data.get('balance') or 0)
        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)})


def ajax_pending_inward_approvals(request):
    """
    Fetch pending material outward requests targeted at the current branch.
    """
    try:
        branch_id = request.session.get('branch_id')
        role_id = request.session.get('role_id')
        
        has_access, error_message = check_user_access(role_id, 'transfer_in', "read")
        if not has_access:
            return JsonResponse({'message': 'permission', 'error_message': 'You do not have permission.'})

        # Get all outwards targeted at our branch (Exclude Rejected)
        all_outwards = material_outward_table.objects.filter(
            supply_branch_id=branch_id,
            status=1,
            is_active=1
        ).exclude(outward_status='rejected').order_by('-created_on')

        data = []
        for po in all_outwards:
            # 1. Total Outward Stats
            total_outward_qty = po.total_quantity or 0
            total_outward_amt = po.total_amount or 0

            # 2. Total Inwarded Stats for this Outward
            inward_stats = child_material_inward_table.objects.filter(
                material_outward_id=po.id,
                status=1,
                is_active=1
            ).aggregate(
                total_qty=Sum('quantity'),
                total_amt=Sum('amount')
            )

            inward_qty = inward_stats['total_qty'] or 0
            inward_amt = inward_stats['total_amt'] or 0

            # 3. Calculate Remainder
            remaining_qty = total_outward_qty - inward_qty
            remaining_amt = total_outward_amt - inward_amt

            # Only show if there's something left to inward
            if remaining_qty > 0:
                sender_branch = select_row(branch_table, {'id': po.branch_id})
                sender_name = sender_branch.name if sender_branch else 'Unknown'

                data.append({
                    'id': po.id,
                    'outward_no': po.transfer_no,
                    'outward_date': format_date_month_year(po.transfer_date),
                    'branch_name': sender_name,
                    'total_qty': remaining_qty,
                    'total_amount': format_amount(remaining_amt),
                    'status': po.outward_status if po.outward_status else 'Pending'
                })

        return JsonResponse({'data': data})

    except Exception as e:
        return JsonResponse({'data': [], 'error': str(e)})


def get_outward_transfer_items(request):
    """
    Fetch items of a specific Material Outward request for the View Modal.
    """
    po_id = request.POST.get('po_id')
    try:
        # 1. Fetch all items from Outward
        outward_items = child_material_outward_table.objects.filter(
            tm_material_id=po_id,
            status=1,
            is_active=1
        )

        # 2. Fetch all already Inwarded IMEIs for this Outward
        inwarded_imeis = set(
            child_material_inward_table.objects.filter(
                material_outward_id=po_id,
                status=1,
                is_active=1
            ).exclude(imei_no__isnull=True)
             .exclude(imei_no='')
             .values_list('imei_no', flat=True)
        )

        # 3. Handle non-serialized (accessory) items - Pool quantities by unique attributes
        non_serialized_inwarded = {}
        inwarded_pool = child_material_inward_table.objects.filter(
            material_outward_id=po_id,
            status=1,
            is_active=1
        ).filter(Q(imei_no__isnull=True) | Q(imei_no='')).values(
            'brand_id', 'model_id', 'variant_id', 'color_id', 'rate'
        ).annotate(total_qty=Sum('quantity'))

        for row in inwarded_pool:
            key = (row['brand_id'], row['model_id'], row['variant_id'], row['color_id'], row['rate'])
            non_serialized_inwarded[key] = row['total_qty']

        data = []
        for item in outward_items:
            imei = (item.imei_no or "").strip()
            
            if imei:
                # Case A: Serialized item - Filter by specific IMEI
                if imei in inwarded_imeis:
                    continue  # Already inwarded
                
                remaining_qty = 1
                calculated_amount = item.rate or 0
            else:
                # Case B: Non-serialized item - Filter by pooled quantity
                key = (item.brand_id, item.model_id, item.variant_id, item.color_id, item.rate)
                already_inwarded = non_serialized_inwarded.get(key, 0)
                
                outward_qty = item.quantity or 0
                remaining_qty = outward_qty - already_inwarded
                
                if remaining_qty <= 0:
                    if already_inwarded > outward_qty:
                        non_serialized_inwarded[key] = already_inwarded - outward_qty
                    else:
                        non_serialized_inwarded[key] = 0
                    continue
                
                non_serialized_inwarded[key] = 0
                calculated_amount = remaining_qty * (item.rate or 0)

            # Fetch names
            sub_cat = select_row(sub_category_table, {'id': item.subcategory_id})
            brand = select_row(brand_table, {'id': item.brand_id})
            model = select_row(model_table, {'id': item.model_id})
            variant = select_row(variant_table, {'id': item.variant_id})
            color = select_row(color_table, {'id': item.color_id})

            data.append({
                'subcategory': sub_cat.name if sub_cat else '-',
                'brand': brand.name if brand else '-',
                'model': model.name if model else '-',
                'variant': variant.name if variant else '-',
                'color': color.name if color else '-',
                'imei': imei if imei else '-',
                'rate': item.rate,
                'quantity': remaining_qty,
                'amount': calculated_amount
            })
        
        return JsonResponse({'data': data})
    except Exception as e:
        return JsonResponse({'data': [], 'error': str(e)})


def approve_outward_transfer(request):
    """
    Approve an Outward Request:
    1. Verify if all items are already inwarded (Data-driven check).
    2. Identify items yet to be inwarded.
    3. Create tm_material_in record (Header).
    4. Create tx_material_in records for remaining items.
    5. Update tm_material_out status to 'Approved'.
    """
    try:
        outward_id = request.POST.get('id')
        user_id = request.session.get('user_id')
        company_id = request.session.get('company_id')
        branch_id = request.session.get('branch_id') # Receiver (Us)
        
        fyf_name = request.session.get('fyf')
        financial_year = calculate_financial_year(fyf_name)
        now = timezone.localtime(timezone.now())

        with transaction.atomic():
            outward_obj = material_outward_table.objects.select_for_update().get(id=outward_id)
            
            # --- START DATA-DRIVEN CHECK ---
            
            # 1. Fetch all items from Outward
            outward_items = child_material_outward_table.objects.filter(
                tm_material_id=outward_id,
                status=1,
                is_active=1
            )

            # 2. Fetch all already Inwarded IMEIs for this Outward
            inwarded_imeis = set(
                child_material_inward_table.objects.filter(
                    material_outward_id=outward_id,
                    status=1,
                    is_active=1
                ).exclude(imei_no__isnull=True)
                 .exclude(imei_no='')
                 .values_list('imei_no', flat=True)
            )

            # 3. Handle non-serialized (accessory) items - Pool quantities by unique attributes
            non_serialized_inwarded = {}
            inwarded_pool = child_material_inward_table.objects.filter(
                material_outward_id=outward_id,
                status=1,
                is_active=1
            ).filter(Q(imei_no__isnull=True) | Q(imei_no='')).values(
                'brand_id', 'model_id', 'variant_id', 'color_id', 'rate'
            ).annotate(total_qty=Sum('quantity'))

            for row in inwarded_pool:
                key = (row['brand_id'], row['model_id'], row['variant_id'], row['color_id'], row['rate'])
                non_serialized_inwarded[key] = row['total_qty']

            items_to_inward = []
            final_inward_qty = 0
            final_inward_amt = 0

            for item in outward_items:
                imei = (item.imei_no or "").strip()
                
                if imei:
                    # Serialized item
                    if imei in inwarded_imeis:
                        continue  # Already inwarded
                    
                    items_to_inward.append(item)
                    final_inward_qty += 1
                    final_inward_amt += (item.rate or 0)
                else:
                    # Non-serialized item
                    key = (item.brand_id, item.model_id, item.variant_id, item.color_id, item.rate)
                    already_inwarded = non_serialized_inwarded.get(key, 0)
                    
                    outward_qty = item.quantity or 0
                    remaining_qty = outward_qty - already_inwarded
                    
                    if remaining_qty <= 0:
                        # Update pool if over-inwarded (though unlikely)
                        if already_inwarded > outward_qty:
                            non_serialized_inwarded[key] = already_inwarded - outward_qty
                        else:
                            non_serialized_inwarded[key] = 0
                        continue
                    
                    # Store how much we are inwarding now
                    item.remaining_to_inward = remaining_qty
                    items_to_inward.append(item)
                    final_inward_qty += remaining_qty
                    final_inward_amt += (remaining_qty * (item.rate or 0))
                    
                    # Zero out for next potential outward row matching same key
                    non_serialized_inwarded[key] = 0

            if not items_to_inward:
                return JsonResponse({'status': 'warning', 'message': 'This request is already approved.'})

            # --- END DATA-DRIVEN CHECK ---

            # 4. Fetch is_demo status for each item from source branch stock
            for item in items_to_inward:
                imei = (item.imei_no or "").strip()
                if imei:
                    print(f"🔍 DEBUG: Checking IMEI {imei} in Source Branch {outward_obj.branch_id}")

                    # Check in Purchase Inward
                    purchase_item = child_purchase_inward_table.objects.filter(
                        imei_no=imei, branch_id=outward_obj.branch_id, is_active=1
                    ).order_by('-created_on').first()
                    
                    if purchase_item:
                        print(f"👉 Found in Purchase Inward: ID={purchase_item.id}, is_demo={getattr(purchase_item, 'is_demo', 'MISSING')}")
                        if getattr(purchase_item, 'is_demo', 0) == 1:
                            item.is_demo = 1
                            continue
                    else:
                        print("❌ Not found in Purchase Inward")

                    # Check in Material Inward
                    material_item = child_material_inward_table.objects.filter(
                        imei_no=imei, branch_id=outward_obj.branch_id, is_active=1
                    ).order_by('-created_on').first()

                    if material_item:
                        print(f"👉 Found in Material Inward: ID={material_item.id}, is_demo={getattr(material_item, 'is_demo', 'MISSING')}")
                        if getattr(material_item, 'is_demo', 0) == 1:
                            item.is_demo = 1
                            continue
                    else:
                        print("❌ Not found in Material Inward")

                    # Check in Opening Stock
                    opening_item = opening_stock_table.objects.filter(
                        imei_no=imei, branch_id=outward_obj.branch_id, is_active=1
                    ).order_by('-created_on').first()

                    if opening_item:
                        print(f"👉 Found in Opening Stock: ID={opening_item.id}, is_demo={getattr(opening_item, 'is_demo', 'MISSING')}")
                        if getattr(opening_item, 'is_demo', 0) == 1:
                            item.is_demo = 1
                            continue
                    else:
                        print("❌ Not found in Opening Stock")
                
                # Default to 0 if not found or not demo
                item.is_demo = 0
                print(f"🏁 Final Decision for IMEI {imei}: is_demo={item.is_demo}")

            # 1. Create Inward Header
            transfer_no = generate_serial_number(
                model=material_inward_table,
                number_field='transfer_no',
                financial_year_field='current_fy',
                financial_year=financial_year,
                branch_id=branch_id
            )

            # Inward Table: branch_id is US. Supply Branch is THEM.
            inward_header = material_inward_table.objects.create(
                company_id=company_id,
                transfer_date=now.date(),
                transfer_no=transfer_no,
                num_series=generate_num_series(material_inward_table),
                current_fy=financial_year,
                branch_id=branch_id,                # Us
                supply_branch_id=outward_obj.branch_id, # Sender
                remarks=f"Auto-approved from Outward {outward_obj.transfer_no}",
                total_quantity=final_inward_qty,
                sub_total=final_inward_amt,
                total_amount=final_inward_amt,
                roundoff=0,
                payment_type=outward_obj.payment_type,
                cash=0,
                bank=0,
                balance=final_inward_amt,
                is_active=1,
                status=1,
                created_on=now,
                updated_on=now,
                created_by=user_id,
                updated_by=user_id,
                employee_id=user_id,
                pu_status='approved',
                time=now.time(),
            )

            # 2. Process Items
            for item in items_to_inward:
                qty = getattr(item, 'remaining_to_inward', item.quantity)
                amt = qty * (item.rate or 0)

                child_material_inward_table.objects.create(
                    company_id=company_id,
                    current_fy=financial_year,
                    branch_id=branch_id,
                    supply_branch_id=outward_obj.branch_id,
                    tm_material_id=inward_header.id,
                    material_outward_id=outward_obj.id,
                    imei_no=item.imei_no,
                    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,
                    ean=item.ean,
                    hsn_code=item.hsn_code,
                    rate=item.rate,
                    quantity=qty,
                    amount=amt,
                    fsp=item.fsp,
                    wsp=item.wsp,
                    mop=item.mop,
                    bop=item.bop,
                    db_price=item.db_price,
                    mrp=item.mrp,
                    is_active=1,
                    status=1,
                    created_on=now,
                    updated_on=now,
                    created_by=user_id,
                    updated_by=user_id,
                    price_list_id=item.price_list_id,
                    is_demo=1 if getattr(item, 'is_demo', 0) else 0
                )

            # 3. Update Outward Status (Always mark as Approved if we processed something or if it's already done)
            outward_obj.outward_status = 'Approved'
            outward_obj.save()

            return JsonResponse({'status': 'success', 'message': 'Transfer Inward Approved Successfully'})

    except Exception as e:
        return JsonResponse({'status': 'error', 'message': str(e)})


def reject_outward_transfer(request):
    """
    Reject an Outward Request.
    Reverts stock by marking child records as status=0.
    Updates rejection metadata in the main table.
    """
    if request.method != 'POST':
        return JsonResponse({'status': 'error', 'message': 'Invalid request method'})

    try:
        data_id = request.POST.get('id')
        user_id = request.session.get('user_id')
        remarks = request.POST.get('remarks', '').strip()
        now = timezone.localtime(timezone.now())

        with transaction.atomic():
            # 1. Fetch and Lock the outward record
            outward_obj = material_outward_table.objects.select_for_update().get(id=data_id)

            # 2. Validation: Cannot reject if already approved or inwarded
            if str(outward_obj.outward_status).lower() in ['approved', 'partial']:
                 return JsonResponse({'status': 'warning', 'message': f'Cannot reject transfer that is already {outward_obj.outward_status}.'})

            # 3. Update Main Table Status and Rejection Metadata
            material_outward_table.objects.filter(id=data_id).update(
                outward_status='rejected',
                rejected_remarks=remarks,
                rejected_on=format_datetime(now),
                rejected_by=user_id,
                updated_on=now,
                updated_by=user_id
            )

            # 4. Stock Reversal: Mark all child items (IMEIs) as inactive (status=0)
            # This releases the IMEI back to 'available' in dynamic stock calculations.
            child_material_outward_table.objects.filter(tm_material_id=data_id, status=1).update(
                status=1,
                is_active=1,
                updated_on=now,
                updated_by=user_id
            )

            # TOD0: Send notification to source branch
            # sendMessage(outward_obj.branch_id, "Outward Transfer Rejected", f"Transfer #{outward_obj.transfer_no} was rejected. Stock returned to branch.")

            return JsonResponse({'status': 'success', 'message': 'Transfer Request Rejected successfully. Stock has been returned to the source branch.'})

    except material_outward_table.DoesNotExist:
        return JsonResponse({'status': 'error', 'message': 'Outward transfer record not found'})
    except Exception as e:
        return JsonResponse({'status': 'error', 'message': str(e)})
