import frappe
import requests
import json
from frappe.utils import nowdate, now, cint, now_datetime, get_datetime
from frappe.model.document import Document
import re
from difflib import SequenceMatcher
import datetime
import random
import time

# Импорты из модуля аутентификации
from invoice_az.auth import record_etaxes_activity, get_default_asan_login
from .sales_api import get_sales_invoices


@frappe.whitelist()
def get_invoices(token, filters=None):
    """Getting list of invoices with filtering options"""
    # Записываем активность
    record_etaxes_activity()
    
    url = "https://new.e-taxes.gov.az/api/po/invoice/public/v2/invoice/find.inbox"
    
    # Base request parameters - изменено: только approved и approvedBySystem статусы
    payload = {
        "sortBy": "creationDate",
        "sortAsc": True,
        "statuses": ["approved", "approvedBySystem"],  # Только утвержденные документы
        "types": ["current", "corrected"],
        "kinds": ["defaultInvoice", "agent", "resale", "recycling", "taxCodex163", 
                 "taxCodex177_5", "returnInvoice", "returnByAgent", "returnRecycled", 
                 "exportNoteInvoice", "exciseGoodsTransfer", "advanceInvoice"],
        "serialNumber": None,
        "senderTin": None,
        "senderName": None,
        "productName": None,
        "productCode": None,
        "receiverTin": None,
        "receiverName": None,
        "creationDateFrom": "01-01-2024 00:00",
        "creationDateTo": "31-12-2024 23:59",
        "amountFrom": None,
        "amountTo": None,
        "offset": 0,
        "maxCount": 200,
        "actionOwner": None
    }
    
    # Apply custom filters if they are passed
    if filters:
        try:
            filters_dict = json.loads(filters)
            for key, value in filters_dict.items():
                if key in payload and value:  # Update only existing keys and non-empty values
                    payload[key] = value
        except Exception as e:
            frappe.log_error(f"[GET_INVOICES] Error parsing filters: {str(e)}", "Filter Parse Error")
            return
        
    headers = {
        "Accept": "application/json, text/plain, */*",
        "Content-Type": "application/json",
        "Cache-Control": "no-cache",
        "x-authorization": f"Bearer {token}"
    }
    
    try:
        response = requests.post(url, data=json.dumps(payload), headers=headers)

        if response.status_code == 500:
            frappe.log_error(f"[GET_INVOICES] Server error 500", "E-Taxes Server Error")
            return {
                "error": "server_error",
                "message": "Service temporarily unavailable. Please try again in a few minutes.",
                "status_code": 500
            }

        # Check response status to handle 401 error
        if response.status_code == 401:
            frappe.log_error(f"[GET_INVOICES] Unauthorized 401 - token expired", "E-Taxes Auth Error")
            
            # Получаем свежий токен из Asan Login
            asan_login_settings = get_default_asan_login()
            if asan_login_settings.get('found'):
                fresh_token = asan_login_settings['main_token']
                headers["x-authorization"] = f"Bearer {fresh_token}"
                
                frappe.log_error(f"[GET_INVOICES] Retrying with fresh token", "E-Taxes Auth Retry")
                
                # Повторяем запрос с новым токеном
                response = requests.post(url, data=json.dumps(payload), headers=headers)
                
                if response.status_code == 401:
                    frappe.log_error(f"[GET_INVOICES] Still unauthorized after token refresh", "E-Taxes Auth Failed")
                    return {
                        "error": "unauthorized",
                        "message": "Authentication required. Please login again."
                    }
            else:
                frappe.log_error(f"[GET_INVOICES] No Asan Login settings found for token refresh", "E-Taxes Auth Error")
                return {
                    "error": "unauthorized",
                    "message": "Authentication required. Please login again."
                }
        
        response.raise_for_status()
        
        return response.json()
    except requests.exceptions.HTTPError as e:
        frappe.log_error(f"[GET_INVOICES] HTTP error: {e.response.status_code} - {str(e)}", "E-Taxes HTTP Error")
        
        # Check if the error is 401 Unauthorized
        if e.response.status_code == 401:
            frappe.log_error(f"[GET_INVOICES] HTTPError 401 Unauthorized", "E-Taxes Auth Error")
            return {
                "error": "unauthorized",
                "message": "Authentication required. Please login again."
            }
        elif e.response.status_code == 500:
            frappe.log_error(f"[GET_INVOICES] HTTPError 500 Server Error", "E-Taxes Server Error")
            return {
                "error": "server_error",
                "message": "Service temporarily unavailable. Please try again in a few minutes.",
                "status_code": 500
            }
        
        frappe.log_error(f"HTTP error in get_invoices: {str(e)}", "API Error")
        return {"error": "http_error", "message": "An unknown error occurred, please try again in a few minutes."}
    except Exception as e:
        frappe.log_error(f"[GET_INVOICES] Unexpected error: {str(e)}\n{frappe.get_traceback()}", "E-Taxes Error")
        frappe.log_error(f"Error getting invoices: {str(e)}", "API Error")
        return {"error": "unknown_error", "message": "An unknown error occurred, please try again in a few minutes."}

@frappe.whitelist()
def get_invoice_details(token, invoice_id):
    """Getting detailed information about an invoice"""
    # Записываем активность
    record_etaxes_activity()
    
    url = f"https://new.e-taxes.gov.az/api/po/invoice/public/v2/invoice/{invoice_id}?sourceSystem=avis"
    
    headers = {
        "Accept": "application/json, text/plain, */*",
        "Content-Type": "application/json",
        "Cache-Control": "no-cache",
        "x-authorization": f"Bearer {token}"
    }
    
    try:
        response = requests.get(url, headers=headers)
        
        if response.status_code == 500:
            frappe.log_error(f"[GET_INVOICE_DETAILS] Server error 500 for invoice {invoice_id}", "E-Taxes Server Error")
            return {
                "error": "server_error",
                "message": "Service temporarily unavailable. Please try again in a few minutes.",
                "status_code": 500
            }

        # Check for 401 error
        if response.status_code == 401:
            frappe.log_error(f"[GET_INVOICE_DETAILS] Unauthorized 401 for invoice {invoice_id}", "E-Taxes Auth Error")
            
            # Получаем свежий токен из Asan Login
            asan_login_settings = get_default_asan_login()
            if asan_login_settings.get('found'):
                fresh_token = asan_login_settings['main_token']
                headers["x-authorization"] = f"Bearer {fresh_token}"
                
                frappe.log_error(f"[GET_INVOICE_DETAILS] Retrying with fresh token for invoice {invoice_id}", "E-Taxes Auth Retry")
                
                # Повторяем запрос с новым токеном
                response = requests.get(url, headers=headers)
                
                if response.status_code == 401:
                    frappe.log_error(f"[GET_INVOICE_DETAILS] Still unauthorized after token refresh for invoice {invoice_id}", "E-Taxes Auth Failed")
                    return {
                        "error": "unauthorized",
                        "message": "Authentication required. Please login again."
                    }
            else:
                frappe.log_error(f"[GET_INVOICE_DETAILS] No Asan Login settings found for token refresh", "E-Taxes Auth Error")
                return {
                    "error": "unauthorized",
                    "message": "Authentication required. Please login again."
                }
            
        response.raise_for_status()
        
        return response.json()
    except requests.exceptions.HTTPError as e:
        frappe.log_error(f"[GET_INVOICE_DETAILS] HTTP error for invoice {invoice_id}: {e.response.status_code} - {str(e)}", "E-Taxes HTTP Error")
        
        # Check if the error is 401 Unauthorized
        if e.response.status_code == 401:
            frappe.log_error(f"[GET_INVOICE_DETAILS] HTTPError 401 Unauthorized for invoice {invoice_id}", "E-Taxes Auth Error")
            return {
                "error": "unauthorized",
                "message": "Authentication required. Please login again."
            }
        elif e.response.status_code == 500:
            frappe.log_error(f"[GET_INVOICE_DETAILS] HTTPError 500 Server Error for invoice {invoice_id}", "E-Taxes Server Error")
            return {
                "error": "server_error",
                "message": "Service temporarily unavailable. Please try again in a few minutes.",
                "status_code": 500
            }
            
        frappe.log_error(f"HTTP error in get_invoice_details: {str(e)}", "API Error")
        return {"error": "http_error", "message": "An unknown error occurred, please try again in a few minutes."}
    except Exception as e:
        frappe.log_error(f"[GET_INVOICE_DETAILS] Unexpected error for invoice {invoice_id}: {str(e)}\n{frappe.get_traceback()}", "E-Taxes Error")
        frappe.log_error(f"Error getting invoice details: {str(e)}", "API Error")
        return {"error": "unknown_error", "message": "An unknown error occurred, please try again in a few minutes."}
        
@frappe.whitelist()
def get_uom_for_unit(unit_name):
    """Converts unit name from invoice to system UOM with support for mappings"""
    if not unit_name:
        # Получаем default_uom из настроек
        try:
            settings = get_active_settings()
            if settings and hasattr(settings, 'default_uom') and settings.default_uom:
                return settings.default_uom
        except Exception:
            pass
        return "Nos"  # Fallback if no settings
    
    try:
        # Ищем существующую запись E-Taxes Unit
        etaxes_unit = frappe.db.get_value('E-Taxes Unit', 
                                        filters={'etaxes_unit_name': unit_name},
                                        fieldname=['mapped_unit'])
        
        # Если найдена запись и у неё есть mapped_unit - возвращаем его
        if etaxes_unit and etaxes_unit:
            return etaxes_unit
        
        # Если записи нет или mapped_unit пустой - берем default_uom из настроек
        settings = get_active_settings()
        if settings and hasattr(settings, 'default_uom') and settings.default_uom:
            return settings.default_uom
            
    except Exception as e:
        frappe.log_error(f"Error getting UOM mapping: {str(e)}", "UOM Mapping Error")
    
    # Fallback to default
    return "Nos"

@frappe.whitelist()
def load_items_from_invoices(date_from, date_to, max_count=200, offset=0):
    """Loading invoices list for a period - returns invoices for frontend processing"""
    # Записываем активность
    record_etaxes_activity()
    
    try:
        # Get default settings
        asan_login_settings = get_default_asan_login()
        
        if not asan_login_settings.get('found'):
            return {
                'success': False,
                'message': 'No Asan Login settings found'
            }
        
        # Ensure max_count is an integer
        try:
            max_count = int(max_count)
        except (ValueError, TypeError):
            max_count = 200
            
        try:
            offset = int(offset)
        except (ValueError, TypeError):
            offset = 0
        
        # Format filters for the API request
        filters = {
            "actionOwner": None,
            "amountFrom": None,
            "amountTo": None,
            "creationDateFrom": date_from,
            "creationDateTo": date_to,
            "kinds": ["defaultInvoice", "agent", "resale", "recycling", "taxCodex163", "taxCodex177_5", "returnInvoice", "returnByAgent", "returnRecycled", "exportNoteInvoice", "exciseGoodsTransfer", "advanceInvoice"],
            "maxCount": max_count,
            "offset": offset,
            "productCode": None,
            "productName": None,
            "receiverName": None,
            "receiverTin": None,
            "senderName": None,
            "senderTin": None,
            "serialNumber": None,
            "sortAsc": True,
            "sortBy": "creationDate",
            "statuses": ["approved", "approvedBySystem"],
            "types": ["current", "corrected"]
        }
        
        # Загружаем из inbox (входящие инвойсы)
        response_inbox = get_invoices(asan_login_settings['main_token'], json.dumps(filters))
        
        if 'error' in response_inbox:
            return {
                'success': False,
                'error': response_inbox.get('error'),
                'message': response_inbox['message'] if 'message' in response_inbox else 'Failed to retrieve invoices'
            }
        
        invoices_inbox = response_inbox.get('data', []) or response_inbox.get('invoices', [])
        
        # Помечаем источник для каждого инвойса
        for invoice in invoices_inbox:
            invoice['_source'] = 'inbox'
        
        all_invoices = invoices_inbox
        
        # Попытка загрузить из outbox (исходящие инвойсы/продажи)
        try:
            from .sales_api import get_sales_invoices
            response_outbox = get_sales_invoices(asan_login_settings['main_token'], json.dumps(filters))
            
            if 'error' not in response_outbox:
                invoices_outbox = response_outbox.get('data', []) or response_outbox.get('invoices', [])
                
                # Помечаем источник для каждого инвойса
                for invoice in invoices_outbox:
                    invoice['_source'] = 'outbox'
                
                # Объединяем инвойсы из inbox и outbox
                all_invoices = invoices_inbox + invoices_outbox
                
                # Определяем hasMore на основе обоих источников
                hasMore_inbox = response_inbox.get('hasMore', False) or response_inbox.get('total', 0) > offset + max_count
                hasMore_outbox = response_outbox.get('hasMore', False) or response_outbox.get('total', 0) > offset + max_count
                hasMore = hasMore_inbox or hasMore_outbox
            else:
                # Если не удалось загрузить из outbox, используем только inbox
                hasMore = response_inbox.get('hasMore', False) or response_inbox.get('total', 0) > offset + max_count
                
        except ImportError:
            # Если модуль sales_api недоступен, используем только inbox
            hasMore = response_inbox.get('hasMore', False) or response_inbox.get('total', 0) > offset + max_count
        except Exception as e:
            # При любой другой ошибке используем только inbox
            frappe.log_error(f"Error loading from outbox: {str(e)}", "Load Items Outbox Error")
            hasMore = response_inbox.get('hasMore', False) or response_inbox.get('total', 0) > offset + max_count
        
        return {
            'success': True,
            'invoices': all_invoices,
            'hasMore': hasMore,
            'token': asan_login_settings['main_token'],
            'total': len(all_invoices),
            'inbox_count': len(invoices_inbox),
            'outbox_count': len(all_invoices) - len(invoices_inbox)
        }
        
    except Exception as e:
        frappe.log_error(f"Error in load_items_from_invoices: {str(e)}\n{frappe.get_traceback()}", "Load Items Error")
        return {
            'success': False,
            'message': "An unknown error occurred, please try again in a few minutes."
        }

@frappe.whitelist()
def get_default_settings():
    """Getting active E-Taxes settings"""
    try:
        settings_list = frappe.get_all('E-Taxes Settings', 
                                       filters={'is_active': 1}, 
                                       limit=1)
        
        if settings_list:
            settings = frappe.get_doc('E-Taxes Settings', settings_list[0].name)
            return {
                'found': True,
                'settings': settings.as_dict()
            }
        else:
            return {
                'found': False,
                'message': 'No active E-Taxes settings found'
            }
    except Exception as e:
        return {
            'found': False,
            'message': str(e)
        }

@frappe.whitelist()
def normalize_azeri_text(text):
    """Normalization of Azerbaijani text for matching"""
    if not text:
        return text
    
    # Replacements dictionary
    replacements = {
        'ə': 'e',
        'Ə': 'E',
        'ü': 'u',
        'Ü': 'U',
        'ö': 'o',
        'Ö': 'O',
        'ğ': 'g',
        'Ğ': 'G',
        'ı': 'i',
        'I': 'I',
        'ç': 'c',
        'Ç': 'C',
        'ş': 's',
        'Ş': 'S'
    }
    
    # Also add variant ə->a and ş->sh
    replacements_alt = replacements.copy()
    replacements_alt.update({
        'ə': 'a',
        'Ə': 'A',
        'ş': 'sh',
        'Ş': 'SH'
    })
    
    # Create normalized versions
    text_normalized = text
    for azeri_char, latin_char in replacements.items():
        text_normalized = text_normalized.replace(azeri_char, latin_char)
    
    text_normalized_alt = text
    for azeri_char, latin_char in replacements_alt.items():
        text_normalized_alt = text_normalized_alt.replace(azeri_char, latin_char)
    
    return text_normalized, text_normalized_alt

@frappe.whitelist()
def get_unmapped_items():
    """Getting unmapped items from E-Taxes"""
    try:
        # Get active settings
        settings = get_active_settings()
        if not settings:
            return {
                'success': False,
                'message': 'No active E-Taxes settings found'
            }
        
        # Get all unmapped items from E-Taxes (single query)
        etaxes_items = frappe.get_all('E-Taxes Item', 
                                    filters={'status': 'New'},
                                    fields=['name', 'etaxes_item_name', 'etaxes_item_code', 'etaxes_unit', 
                                           'etaxes_price', 'is_service_item', 'is_from_purchase', 'is_from_sales'])
        
        # Build existing mappings dict (single loop)
        existing_mappings = {mapping.etaxes_item_name: mapping.erp_item for mapping in settings.item_mappings}
        
        # Cache UOM mappings to avoid repeated DB queries
        unit_mappings = {}
        if etaxes_items:
            unique_units = {item.etaxes_unit for item in etaxes_items if item.etaxes_unit}
            if unique_units:
                unit_results = frappe.get_all('E-Taxes Unit',
                                            filters={'etaxes_unit_name': ['in', list(unique_units)]},
                                            fields=['etaxes_unit_name', 'mapped_unit'])
                unit_mappings = {r.etaxes_unit_name: r.mapped_unit for r in unit_results if r.mapped_unit}
        
        # Cache item group existence checks
        services_group_exists = frappe.db.exists('Item Group', 'Services')
        services_az_group_exists = frappe.db.exists('Item Group', 'Услуги')
        
        unmapped_items = []
        
        # Process items (optimized loop)
        for item in etaxes_items:
            if item.name not in existing_mappings:
                # Get UOM efficiently
                uom_to_use = unit_mappings.get(item.etaxes_unit, settings.default_uom)
                
                # Extract values once
                is_service = item.get('is_service_item', 0)
                is_from_purchase = item.get('is_from_purchase', 0)
                is_from_sales = item.get('is_from_sales', 0)
                
                # Calculate flags efficiently
                is_stock_item = 0 if is_service else 1
                
                # Purchase item logic
                if is_from_purchase:
                    is_purchase_item = 1
                elif is_service and not is_from_purchase and not is_from_sales:
                    is_purchase_item = 1
                else:
                    is_purchase_item = 0
                
                # Sales item logic
                if is_from_sales:
                    is_sales_item = 1
                elif is_service and not is_from_purchase and not is_from_sales:
                    is_sales_item = 1
                else:
                    is_sales_item = 0
                
                # Item group logic
                item_group = settings.default_item_group
                if is_service:
                    if services_group_exists:
                        item_group = 'Services'
                    elif services_az_group_exists:
                        item_group = 'Услуги'
                
                # Build item data
                item_data = {
                    'name': item.name,
                    'etaxes_item_name': item.etaxes_item_name,
                    'etaxes_item_code': item.etaxes_item_code,
                    'item_group': item_group,
                    'uom': uom_to_use,
                    'is_stock_item': is_stock_item,
                    'is_purchase_item': is_purchase_item,
                    'is_sales_item': is_sales_item,
                    'item_tax_template': settings.default_item_tax_template,
                    'is_service_item': is_service,
                    'is_from_purchase': is_from_purchase,
                    'is_from_sales': is_from_sales
                }
                
                # Add price if exists
                if item.etaxes_price:
                    item_data['standard_rate'] = item.etaxes_price
                
                unmapped_items.append(item_data)
        
        return {
            'success': True,
            'items': unmapped_items
        }
    except Exception as e:
        frappe.log_error(f"Error in get_unmapped_items: {str(e)}\n{frappe.get_traceback()}", "Get Unmapped Items Error")
        return {
            'success': False,
            'message': "An unknown error occurred, please try again in a few minutes."
        }
    
@frappe.whitelist()
def match_similar_items():
    """Matching items by similar names"""
    # Записываем активность
    record_etaxes_activity()
    
    try:
        import re
        from difflib import SequenceMatcher
        
        # Get active settings
        settings = get_active_settings()
        if not settings:
            return {
                'success': False,
                'message': 'No active E-Taxes settings found'
            }
        
        # Get similarity threshold from settings
        similarity_threshold = float(settings.similarity_threshold) / 100.0 if settings.similarity_threshold else 0.8
        consider_azeri = settings.consider_azeri_chars
        
        # Get all items with "New" status from E-Taxes
        etaxes_items = frappe.get_all('E-Taxes Item', 
                                      filters={'status': 'New'},
                                      fields=['name', 'etaxes_item_name', 'etaxes_item_code'])
        
        # Get existing mappings
        existing_mappings = {}
        for mapping in settings.item_mappings:
            existing_mappings[mapping.etaxes_item_name] = mapping.erp_item
        
        # Get items with empty mapping (exist in table but erp_item is None or empty)
        items_with_empty_mapping = []
        for etaxes_id, erp_item in existing_mappings.items():
            if not erp_item:  # If erp_item is None or empty string
                # Find corresponding E-Taxes item
                for item in etaxes_items:
                    if item.name == etaxes_id:
                        items_with_empty_mapping.append(item)
                        break
        
        # Filter only those that are not in mappings
        unmapped_items = []
        for item in etaxes_items:
            if item.name not in existing_mappings:
                unmapped_items.append(item)
        
        # Combine two lists: items without mappings and items with empty mappings
        items_to_process = unmapped_items + items_with_empty_mapping
        
        # Get all items from system
        system_items = frappe.get_all('Item', fields=['name', 'item_name', 'item_code'])
        
        matched_count = 0
        total_processed = len(items_to_process)
        
        # Get fresh copy of settings document
        doc = frappe.get_doc("E-Taxes Settings", settings.name)

        # Process all items
        for etaxes_item in items_to_process:
            # Find similar item
            best_match = None
            best_score = 0
            
            # Normalize E-Taxes item name
            etaxes_name = normalize_string(etaxes_item.etaxes_item_name, consider_azeri)
            
            for item in system_items:
                # Normalize system item name
                item_name = normalize_string(item.item_name, consider_azeri)
                
                # Calculate similarity coefficient
                name_score = SequenceMatcher(None, etaxes_name, item_name).ratio()
                
                # Check if this is a better match
                if name_score > best_score and name_score >= similarity_threshold:
                    best_score = name_score
                    best_match = item
            
            if best_match:
                # IMPORTANT: check if there is already a record for this item in mappings
                existing_row = None
                for idx, mapping in enumerate(doc.item_mappings):
                    if mapping.etaxes_item_name == etaxes_item.name:
                        existing_row = idx
                        break
                
                if existing_row is not None:
                    # If row already exists, update its erp_item value
                    doc.item_mappings[existing_row].erp_item = best_match.name
                    doc.item_mappings[existing_row].mapping_type = 'Automatic'
                else:
                    # If no row, add a new one
                    doc.append('item_mappings', {
                        'etaxes_item_name': etaxes_item.name,
                        'erp_item': best_match.name,
                        'mapping_type': 'Automatic'
                    })
                
                matched_count += 1
                
                # Update E-Taxes Item status only for items that have a match
                item_doc = frappe.get_doc('E-Taxes Item', etaxes_item.name)
                item_doc.status = 'Mapped'
                item_doc.mapped_item = best_match.name
                item_doc.save()

        # Save settings only if matches were found
        if matched_count > 0:
            doc.save()
        
        return {
            'success': True,
            'matched_count': matched_count,
            'total_processed': total_processed,
            'message': f'Matched {matched_count} out of {total_processed} items'
        }
    
    except Exception as e:
        return {
            'success': False,
            'message': "An unknown error occurred, please try again in a few minutes."
        }

@frappe.whitelist()
def create_unmapped_items(settings_name):
    """Creating items for unmapped elements from E-Taxes settings table with EQM Code support"""
    # Записываем активность
    record_etaxes_activity()
    
    try:
        import re
        
        # Get settings
        settings_doc = frappe.get_doc("E-Taxes Settings", settings_name)
        
        # Get unmapped items efficiently
        unmapped_etaxes_items = []
        for mapping_item in settings_doc.item_mappings:
            if not mapping_item.erp_item and frappe.db.exists('E-Taxes Item', mapping_item.etaxes_item_name):
                unmapped_etaxes_items.append(mapping_item.etaxes_item_name)
        
        if not unmapped_etaxes_items:
            return {
                "success": True,
                "created_count": 0,
                "error_count": 0,
                "message": "No unmapped items in table to create"
            }
        
        # Batch load E-Taxes Items с EQM кодом
        etaxes_items_data = frappe.get_all('E-Taxes Item',
                                         filters={'name': ['in', unmapped_etaxes_items]},
                                         fields=['name', 'etaxes_item_name', 'etaxes_item_code', 
                                                'etaxes_product_group_code', 'etaxes_product_group_name',
                                                'is_service_item', 'is_from_purchase', 'is_from_sales',
                                                'eqm_code'])  # НОВОЕ: Добавляем eqm_code
        
        # Build mapping settings dict
        mapping_settings = {}
        for mapping in settings_doc.item_mappings:
            if any([mapping.item_group, mapping.uom, mapping.is_stock_item is not None,
                   mapping.is_purchase_item is not None, mapping.is_sales_item is not None,
                   mapping.item_tax_template]):
                mapping_settings[mapping.etaxes_item_name] = {
                    'item_group': mapping.item_group,
                    'uom': mapping.uom,
                    'is_stock_item': mapping.is_stock_item,
                    'is_purchase_item': mapping.is_purchase_item,
                    'is_sales_item': mapping.is_sales_item,
                    'item_tax_template': mapping.item_tax_template
                }
        
        # Cache existence checks
        services_group_exists = frappe.db.exists('Item Group', 'Services')
        services_az_group_exists = frappe.db.exists('Item Group', 'Услуги')
        default_group_exists = frappe.db.exists('Item Group', settings_doc.default_item_group)
        default_uom_exists = frappe.db.exists('UOM', settings_doc.default_uom)
        
        # Check for custom fields once
        product_group_code_field_exists = frappe.db.exists('Custom Field', {'dt': 'Item', 'fieldname': 'etaxes_product_group_code'})
        product_group_name_field_exists = frappe.db.exists('Custom Field', {'dt': 'Item', 'fieldname': 'etaxes_product_group_name'})
        eqm_code_field_exists = frappe.db.exists('Custom Field', {'dt': 'Item', 'fieldname': 'eqm_code'})  # НОВОЕ: Проверяем поле eqm_code
        item_group_code_field_exists = frappe.db.exists('Custom Field', {'dt': 'Item', 'fieldname': 'product_group_code'})  # НОВОЕ: Проверяем поле product_group_code
        
        product_group_name_field_length = 140
        if product_group_name_field_exists:
            custom_field = frappe.get_doc('Custom Field', {'dt': 'Item', 'fieldname': 'etaxes_product_group_name'})
            product_group_name_field_length = custom_field.length or 140
        
        created_count = 0
        error_count = 0
        
        for etaxes_item_data in etaxes_items_data:
            try:
                # Skip if item already exists
                if frappe.db.exists('Item', {'item_name': etaxes_item_data.etaxes_item_name}):
                    continue
                
                # Create new item
                item_doc = frappe.new_doc("Item")
                
                # Generate unique item_code
                item_code = etaxes_item_data.etaxes_item_code or etaxes_item_data.etaxes_item_name
                item_code = re.sub(r'[^a-zA-Z0-9_.-]', '', item_code)
                if not item_code or len(item_code) < 3:
                    item_code = "ITEM-" + frappe.generate_hash(length=8)
                
                if frappe.db.exists('Item', {'item_code': item_code}):
                    item_code = item_code + "-" + frappe.generate_hash(length=5)
                
                item_doc.item_code = item_code
                item_doc.item_name = etaxes_item_data.etaxes_item_name
                item_doc.description = etaxes_item_data.etaxes_item_name
                
                # Get individual settings
                individual_settings = mapping_settings.get(etaxes_item_data.name, {})
                
                # Set item group
                item_group = individual_settings.get('item_group') or settings_doc.default_item_group
                if not default_group_exists and item_group == settings_doc.default_item_group:
                    item_group = "All Item Groups"
                item_doc.item_group = item_group
                
                # Set UOM
                stock_uom = individual_settings.get('uom') or settings_doc.default_uom
                if not default_uom_exists and stock_uom == settings_doc.default_uom:
                    stock_uom = "Nos"
                item_doc.stock_uom = stock_uom
                
                # Set item flags based on logic
                is_service = etaxes_item_data.get('is_service_item', 0)
                is_from_purchase = etaxes_item_data.get('is_from_purchase', 0)
                is_from_sales = etaxes_item_data.get('is_from_sales', 0)
                
                # is_stock_item
                if individual_settings.get('is_stock_item') is not None:
                    item_doc.is_stock_item = individual_settings['is_stock_item']
                else:
                    item_doc.is_stock_item = 0 if is_service else 1
                
                # is_purchase_item
                if individual_settings.get('is_purchase_item') is not None:
                    item_doc.is_purchase_item = individual_settings['is_purchase_item']
                else:
                    if is_from_purchase or (is_service and not is_from_purchase and not is_from_sales):
                        item_doc.is_purchase_item = 1
                    else:
                        item_doc.is_purchase_item = 0
                
                # is_sales_item
                if individual_settings.get('is_sales_item') is not None:
                    item_doc.is_sales_item = individual_settings['is_sales_item']
                else:
                    if is_from_sales or (is_service and not is_from_purchase and not is_from_sales):
                        item_doc.is_sales_item = 1
                    else:
                        item_doc.is_sales_item = 0
                
                # Auto-set service item group
                if is_service and not individual_settings.get('item_group'):
                    if services_group_exists:
                        item_doc.item_group = 'Services'
                    elif services_az_group_exists:
                        item_doc.item_group = 'Услуги'
                
                # Add product group info to custom fields
                if product_group_code_field_exists and etaxes_item_data.etaxes_product_group_code:
                    item_doc.etaxes_product_group_code = etaxes_item_data.etaxes_product_group_code
                
                if product_group_name_field_exists and etaxes_item_data.etaxes_product_group_name:
                    product_group_name = etaxes_item_data.etaxes_product_group_name
                    if len(product_group_name) > product_group_name_field_length:
                        product_group_name = product_group_name[:product_group_name_field_length]
                    item_doc.etaxes_product_group_name = product_group_name
                
                # НОВОЕ: Добавляем EQM код если поле существует и код есть
                if eqm_code_field_exists and etaxes_item_data.eqm_code:
                    item_doc.eqm_code = etaxes_item_data.eqm_code
                    frappe.log_error(f"Added EQM Code {etaxes_item_data.eqm_code} to item {item_doc.item_name}", "EQM Code Transfer")

                # НОВОЕ: Добавляем product_group_code (Item Group Code) если поле существует и код есть
                if item_group_code_field_exists and etaxes_item_data.etaxes_product_group_code:
                    item_doc.product_group_code = etaxes_item_data.etaxes_product_group_code

                # Add tax template
                tax_template = individual_settings.get('item_tax_template') or settings_doc.default_item_tax_template
                if tax_template and frappe.db.exists('Item Tax Template', tax_template):
                    tax_row = item_doc.append('taxes', {})
                    tax_row.item_tax_template = tax_template
                
                # Save item
                item_doc.insert()
                
                # Update mapping in settings
                for mapping in settings_doc.item_mappings:
                    if mapping.etaxes_item_name == etaxes_item_data.name:
                        mapping.erp_item = item_doc.name
                        mapping.mapping_type = 'Automatic'
                        break
                
                # Update E-Taxes Item status
                frappe.db.set_value('E-Taxes Item', etaxes_item_data.name, {
                    'status': 'Mapped',
                    'mapped_item': item_doc.name
                })
                
                created_count += 1
                
            except Exception as e:
                frappe.log_error(f"Error creating item for {etaxes_item_data.etaxes_item_name}: {str(e)}", "Create Item Error")
                error_count += 1
        
        # Save settings
        if created_count > 0:
            settings_doc.save()
        
        return {
            "success": True,
            "created_count": created_count,
            "error_count": error_count,
            "message": f"Created {created_count} items, errors: {error_count}"
        }
    
    except Exception as e:
        frappe.log_error(f"Error in create_unmapped_items: {str(e)}\n{frappe.get_traceback()}", "Create Items Error")
        return {
            "success": False,
            "message": "An unknown error occurred, please try again in a few minutes."
        }

@frappe.whitelist()
def create_purchase_invoice_from_order(purchase_order_name):
    """Создает Purchase Invoice из Purchase Order используя стандартную функцию ERPNext"""
    try:
        # Импортируем стандартную функцию из модуля Purchase Order
        from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice
        
        # Получаем Purchase Order ДО создания invoice
        po_doc = frappe.get_doc('Purchase Order', purchase_order_name)
        
        # ИСПРАВЛЕНО: Правильная обработка дат
        if po_doc.transaction_date:
            posting_date = po_doc.transaction_date
            # Конвертируем в datetime.date если это строка
            if isinstance(posting_date, str):
                posting_date = frappe.utils.getdate(posting_date)
        else:
            posting_date = frappe.utils.getdate(frappe.utils.nowdate())

        frappe.log_error(f"[DATE DEBUG] PO transaction_date: {po_doc.transaction_date}", "Date Debug")
        frappe.log_error(f"[DATE DEBUG] PI posting_date to set: {posting_date}", "Date Debug")

        # Создаем Purchase Invoice из Purchase Order
        pi_doc = make_purchase_invoice(purchase_order_name)
        
        # ДОБАВЛЕНО: Логируем дату сразу после создания
        frappe.log_error(f"[DATE DEBUG] PI posting_date after make_purchase_invoice: {pi_doc.posting_date}", "Date Debug")
        
        # ИСПРАВЛЕНО: Устанавливаем дату и флаг posting_time ДО других операций
        pi_doc.posting_date = posting_date
        pi_doc.set_posting_time = 1  # Важно! Разрешаем установку даты в прошлом
        
        # ИСПРАВЛЕНО: Убеждаемся что due_date всегда больше posting_date
        due_date = frappe.utils.add_days(posting_date, 30)
        
        # Дополнительная проверка: если due_date в прошлом, используем текущую дату + 30 дней
        today = frappe.utils.getdate(frappe.utils.nowdate())
        if due_date < today:
            due_date = frappe.utils.add_days(today, 30)
            frappe.log_error(f"[DATE DEBUG] Due date was in past, adjusted to: {due_date}", "Date Debug")
        
        pi_doc.due_date = due_date
        
        frappe.log_error(f"[DATE DEBUG] PI posting_date before insert: {pi_doc.posting_date}", "Date Debug")
        frappe.log_error(f"[DATE DEBUG] PI set_posting_time: {pi_doc.set_posting_time}", "Date Debug")
        frappe.log_error(f"[DATE DEBUG] PI due_date before insert: {pi_doc.due_date}", "Date Debug")
        
        # ДОБАВЛЕНО: Устанавливаем галочку is_taxes_doc для Purchase Invoice
        pi_doc.is_taxes_doc = 1
        
        # ДОБАВЛЕНО: Копируем налоги из Purchase Order
        if po_doc.taxes:
            pi_doc.taxes = []
            for tax_row in po_doc.taxes:
                pi_doc.append("taxes", {
                    "account_head": tax_row.account_head,
                    "charge_type": tax_row.charge_type,
                    "rate": tax_row.rate,
                    "tax_amount": tax_row.tax_amount,
                    "description": tax_row.description,
                    "add_deduct_tax": tax_row.add_deduct_tax
                })
        
        # Сохраняем документ
        pi_doc.insert(ignore_permissions=True)
        
        # ДОБАВЛЕНО: Логируем дату после insert
        frappe.log_error(f"[DATE DEBUG] PI posting_date after insert: {pi_doc.posting_date}", "Date Debug")
        frappe.log_error(f"[DATE DEBUG] PI due_date after insert: {pi_doc.due_date}", "Date Debug")
        
        # ДОБАВЛЕНО: Если дата все еще неправильная, принудительно устанавливаем
        if pi_doc.posting_date != posting_date:
            frappe.log_error(f"[DATE DEBUG] Date mismatch detected! Expected: {posting_date}, Got: {pi_doc.posting_date}", "Date Debug")
            
            # Принудительно устанавливаем дату через DB
            frappe.db.set_value('Purchase Invoice', pi_doc.name, {
                'posting_date': posting_date,
                'set_posting_time': 1
            })
            frappe.db.commit()
            
            # Перезагружаем документ
            pi_doc = frappe.get_doc('Purchase Invoice', pi_doc.name)
            frappe.log_error(f"[DATE DEBUG] PI posting_date after DB update: {pi_doc.posting_date}", "Date Debug")
        
        frappe.log_error(f"Purchase Invoice {pi_doc.name} created from Purchase Order {purchase_order_name}", "Purchase Invoice Creation")
        
        # Возвращаем имя созданного Purchase Invoice
        return pi_doc.name
        
    except Exception as e:
        frappe.log_error(f"Error creating Purchase Invoice from PO {purchase_order_name}: {str(e)}\n{frappe.get_traceback()}", "Purchase Invoice Creation Error")
        return None
        
@frappe.whitelist()
def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule_date=None, warehouse=None):
    """Import invoice taking into account NEW supplier mappings"""
    # Записываем активность
    record_etaxes_activity()
    
    try:
        # Deserialize if necessary
        if isinstance(invoice_data, str):
            invoice_data = json.loads(invoice_data)
        
        # ДОБАВЛЕНО: Логируем входящие данные инвойса для отладки дат
        frappe.log_error(f"[DATE DEBUG] Invoice data keys: {list(invoice_data.keys())}", "Date Debug")
        frappe.log_error(f"[DATE DEBUG] createdAt field: {invoice_data.get('createdAt')}", "Date Debug")
        frappe.log_error(f"[DATE DEBUG] createdAt type: {type(invoice_data.get('createdAt'))}", "Date Debug")
        
        # Get active settings
        settings = get_active_settings()
        if not settings:
            return {
                'success': False,
                'message': 'No active E-Taxes settings found'
            }
        
        # Create mapping dictionaries
        item_mappings = {}
        for mapping in settings.item_mappings:
            if mapping.etaxes_item_name and mapping.erp_item:
                item_mappings[mapping.etaxes_item_name] = mapping.erp_item
        
        # ИЗМЕНЕНО: Используем supplier_mappings вместо party_mappings
        supplier_mappings = {}
        for mapping in settings.supplier_mappings:
            if mapping.etaxes_supplier_name and mapping.erp_supplier:
                # ИСПРАВЛЕНИЕ: Обрезаем ключ до 140 символов
                supplier_name = mapping.etaxes_supplier_name[:140] if len(mapping.etaxes_supplier_name) > 140 else mapping.etaxes_supplier_name
                key = f"{supplier_name.lower()}|{mapping.etaxes_tax_id.lower() if mapping.etaxes_tax_id else ''}"
                supplier_mappings[key] = mapping.erp_supplier
        
        # Get default warehouse
        default_warehouse = warehouse
        
        if not default_warehouse:
            if hasattr(settings, 'default_warehouse') and settings.default_warehouse:
                default_warehouse = settings.default_warehouse
            else:
                company = frappe.defaults.get_user_default('Company')
                if company:
                    default_warehouse = frappe.db.get_value('Company', company, 'default_warehouse')
                
                if not default_warehouse:
                    warehouses = frappe.get_all('Warehouse', 
                                              filters={'is_group': 0, 'disabled': 0},
                                              fields=['name'],
                                              limit=1)
                    if warehouses:
                        default_warehouse = warehouses[0].name
        
        if not default_warehouse:
            return {
                'success': False,
                'message': 'Default warehouse not found. Please specify warehouse in E-Taxes settings.'
            }
        
        # ИСПРАВЛЕНО: Определяем дату ДО создания Purchase Order
        invoice_date = None
        created_at_raw = invoice_data.get("createdAt")
        
        frappe.log_error(f"[DATE DEBUG] Raw createdAt value: '{created_at_raw}'", "Date Debug")
        
        if created_at_raw:
            try:
                # Попробуем разные форматы даты
                if isinstance(created_at_raw, str):
                    # Если это строка, попробуем разные форматы
                    frappe.log_error(f"[DATE DEBUG] Trying to parse string date: '{created_at_raw}'", "Date Debug")
                    
                    # Попробуем стандартный парсинг ERPNext
                    invoice_date = frappe.utils.getdate(created_at_raw)
                    frappe.log_error(f"[DATE DEBUG] Successfully parsed date: {invoice_date}", "Date Debug")
                    
                elif hasattr(created_at_raw, 'date'):
                    # Если это datetime объект
                    invoice_date = created_at_raw.date()
                    frappe.log_error(f"[DATE DEBUG] Extracted date from datetime: {invoice_date}", "Date Debug")
                else:
                    # Попробуем привести к строке и парсить
                    date_str = str(created_at_raw)
                    frappe.log_error(f"[DATE DEBUG] Converting to string and parsing: '{date_str}'", "Date Debug")
                    invoice_date = frappe.utils.getdate(date_str)
                    frappe.log_error(f"[DATE DEBUG] Successfully parsed converted date: {invoice_date}", "Date Debug")
                    
            except Exception as e:
                frappe.log_error(f"[DATE DEBUG] Error parsing createdAt '{created_at_raw}': {str(e)}", "Date Debug")
                invoice_date = frappe.utils.today()
                frappe.log_error(f"[DATE DEBUG] Using fallback date: {invoice_date}", "Date Debug")
        else:
            invoice_date = frappe.utils.today()
            frappe.log_error(f"[DATE DEBUG] No createdAt found, using today: {invoice_date}", "Date Debug")

        # ИСПРАВЛЕНО: Определяем date_to_use ДО создания Purchase Order
        # ПРИОРИТЕТ: 1) дата из E-taxes, 2) schedule_date, 3) сегодня
        date_to_use = None
        if invoice_date:
            date_to_use = invoice_date
            frappe.log_error(f"[DATE DEBUG] Using invoice_date from E-taxes for date_to_use: {date_to_use}", "Date Debug")
        elif schedule_date:
            date_to_use = frappe.utils.getdate(schedule_date)
            frappe.log_error(f"[DATE DEBUG] No E-taxes date, using provided schedule_date: {date_to_use}", "Date Debug")
        else:
            date_to_use = frappe.utils.today()
            frappe.log_error(f"[DATE DEBUG] No dates available, using today for date_to_use: {date_to_use}", "Date Debug")

        frappe.log_error(f"[DATE DEBUG] Final date_to_use: {date_to_use}", "Date Debug")
        
        # Get or create Purchase Order
        if purchase_order_name:
            po = frappe.get_doc('Purchase Order', purchase_order_name)
        else:
            po = frappe.new_doc('Purchase Order')
            
            # ИЗМЕНЕНО: Set supplier используя supplier_mappings с обрезкой
            sender = invoice_data.get('sender', {})
            sender_name = sender.get('name', '')
            # ИСПРАВЛЕНИЕ: Обрезаем название поставщика до 140 символов для поиска
            sender_name_truncated = sender_name[:140] if len(sender_name) > 140 else sender_name
            sender_key = f"{sender_name_truncated.lower()}|{sender.get('tin', '').lower()}"
            
            if sender_key in supplier_mappings and supplier_mappings[sender_key]:
                po.supplier = supplier_mappings[sender_key]
            else:
                return {
                    'success': False,
                    'unmatched_suppliers': [{
                        'name': sender.get('name', ''),
                        'tin': sender.get('tin', ''),
                        'type': 'Sender'
                    }],
                    'message': f'No mapping found for supplier: {sender.get("name", "")}'
                }
            
            frappe.log_error(f"[DATE DEBUG] Final invoice_date for PO: {invoice_date}", "Date Debug")
            
            po.transaction_date = invoice_date
            po.schedule_date = date_to_use
            
            frappe.log_error(f"[DATE DEBUG] PO transaction_date set to: {po.transaction_date}", "Date Debug")
            frappe.log_error(f"[DATE DEBUG] PO schedule_date set to: {po.schedule_date}", "Date Debug")
                
            po.company = frappe.defaults.get_user_default('Company')
        
        # Add/update additional fields from invoice
        po.title = f"Invoice {invoice_data.get('serialNumber', '')}"
        po.invoice_serial_number = invoice_data.get("serialNumber") or invoice_data.get("number", "")
        
        # Clear existing items if need to update them completely
        if purchase_order_name and invoice_data.get("items"):
            po.items = []
        
        # Variable for tracking added items
        added_items_count = 0
        unmatched_items = []
        unmatched_units = []
        
        # Add items from invoice
        if invoice_data.get("items"):
            for item in invoice_data.get("items", []):
                # Подготавливаем данные товара
                item_name = item.get("productName", "").strip()
                item_code = item.get("itemId", "") or f"CODE-{invoice_data.get('serialNumber', '')}"
                
                # ИСПРАВЛЕНИЕ: Обрезаем название товара до 140 символов для поиска
                item_name_truncated = item_name[:140] if len(item_name) > 140 else item_name
                
                # Ищем соответствие товара сначала по обрезанному названию
                mapped_item = item_mappings.get(item_name_truncated)

                # Если не найдено, ищем без учета регистра
                if not mapped_item:
                    item_name_lower = item_name_truncated.lower()
                    for mapping_key, mapping_value in item_mappings.items():
                        if mapping_key.lower() == item_name_lower:
                            mapped_item = mapping_value
                            break

                # Если все еще не найдено, ищем с нормализацией азербайджанских символов
                if not mapped_item:
                    normalized_input = normalize_string(item_name_truncated, consider_azeri=True)
                    for mapping_key, mapping_value in item_mappings.items():
                        normalized_key = normalize_string(mapping_key, consider_azeri=True)
                        if normalized_key == normalized_input:
                            mapped_item = mapping_value
                            break

                if not mapped_item:
                    unmatched_items.append({
                        'name': item_name,
                        'code': item_code
                    })
                    continue
                
                # Для единиц измерения ищем в E-Taxes Unit
                unit_name = item.get("unit", "")
                mapped_uom = None
                
                if unit_name:
                    try:
                        mapped_uom = frappe.db.get_value('E-Taxes Unit', 
                                                       filters={'etaxes_unit_name': unit_name},
                                                       fieldname='mapped_unit')
                    except Exception:
                        pass
                
                # Если соответствие единицы не найдено, используем UOM из товара или настроек
                if not mapped_uom:
                    if unit_name:
                        unmatched_units.append({
                            'name': unit_name
                        })
                    
                    try:
                        item_doc = frappe.get_doc('Item', mapped_item)
                        mapped_uom = item_doc.stock_uom
                    except:
                        mapped_uom = settings.default_uom if settings.default_uom else "Nos"
                
                # Add position to PO
                po_item = frappe.new_doc("Purchase Order Item")
                po_item.parent = po.name
                po_item.parenttype = "Purchase Order"
                po_item.parentfield = "items"

                po_item.item_code = mapped_item

                try:
                    item_doc = frappe.get_doc('Item', mapped_item)
                    po_item.item_name = item_doc.item_name
                    po_item.description = item_doc.description or item_doc.item_name
                except:
                    po_item.item_name = item.get("productName", "").strip()
                    po_item.description = item.get("productName", "").strip()

                po_item.qty = item.get("quantity", 0)
                po_item.rate = item.get("pricePerUnit", 0)
                po_item.amount = item.get("cost", 0)
                po_item.uom = mapped_uom
                po_item.schedule_date = date_to_use  # ИСПРАВЛЕНО: Используем правильную дату
                po_item.warehouse = default_warehouse
                
                frappe.log_error(f"[DATE DEBUG] Item {po_item.item_code} schedule_date set to: {po_item.schedule_date}", "Date Debug")
                
                po.append("items", po_item)
                added_items_count += 1
        
        # Check if there are unmapped items
        if unmatched_items:
            return {
                'success': False,
                'unmatched_items': unmatched_items,
                'message': f'No mapping found for {len(unmatched_items)} items'
            }
        
        # Check if there are unmapped units
        if unmatched_units:
            return {
                'success': False,
                'unmatched_units': unmatched_units,
                'message': f'No mapping found for {len(unmatched_units)} units'
            }
            
        # Check that there is at least one element in table
        if added_items_count == 0:
            frappe.log_error(
                f"No items were added to PO. Invoice data: {invoice_data}",
                "Import Invoice Error"
            )
            return {
                'success': False,
                'message': 'Could not add any items to order'
            }
        
        # ДОБАВЛЕНО: Создание строки ƏDV в Purchase Taxes and Charges ТОЛЬКО если есть vat в данных
        try:
            vat_amount = invoice_data.get('vat', 0)
            
            if vat_amount and vat_amount > 0:
                vat_account = frappe.db.get_value('Account', 
                                                filters={'account_number': '521.3'}, 
                                                fieldname='name')
                
                if vat_account:
                    net_total = sum(item.get('cost', 0) for item in invoice_data.get('items', []))
                    vat_rate = (vat_amount / net_total * 100) if net_total > 0 else 0
                    
                    po.append("taxes", {
                        "account_head": vat_account,
                        "charge_type": "On Net Total",
                        "rate": vat_rate,
                        "tax_amount": vat_amount,
                        "description": "ƏDV",
                        "add_deduct_tax": "Add"
                    })
                    
                    frappe.log_error(f"Added ƏDV tax: Amount={vat_amount}, Rate={vat_rate}%", "Purchase Order Tax")
                else:
                    frappe.log_error("ƏDV account (521.3) not found", "Purchase Order Tax Warning")
            else:
                frappe.log_error("No VAT amount in E-Taxes data, skipping ƏDV tax creation", "Purchase Order Tax Info")
                
        except Exception as e:
            frappe.log_error(f"Error adding ƏDV tax: {str(e)}", "Purchase Order Tax Error")
        
        # ДОБАВЛЕНО: Логируем финальные даты перед сохранением
        frappe.log_error(f"[DATE DEBUG] Final PO transaction_date: {po.transaction_date}", "Date Debug")
        frappe.log_error(f"[DATE DEBUG] Final PO schedule_date: {po.schedule_date}", "Date Debug")
        
        # Save document
        po.save()
        
        # ИСПРАВЛЕНО: Убедимся что все schedule_date установлены правильно
        for item in po.items:
            if not item.schedule_date:
                item.schedule_date = date_to_use
                frappe.log_error(f"[DATE DEBUG] Fixed missing schedule_date for item {item.item_code}: {item.schedule_date}", "Date Debug")
            if not item.warehouse:
                item.warehouse = default_warehouse
        
        # Save again to ensure changes are applied
        po.save()
        
        # ДОБАВЛЕНО: Логируем даты после сохранения
        frappe.log_error(f"[DATE DEBUG] After save PO transaction_date: {po.transaction_date}", "Date Debug")
        frappe.log_error(f"[DATE DEBUG] After save PO schedule_date: {po.schedule_date}", "Date Debug")
        
        # Логируем schedule_date каждого item после сохранения
        for item in po.items:
            frappe.log_error(f"[DATE DEBUG] After save item {item.item_code} schedule_date: {item.schedule_date}", "Date Debug")
        
        # ИЗМЕНЕНИЕ: Создаем E-Taxes Purchase и устанавливаем связь ДО submit'а
        invoice_id = invoice_data.get('id', '')
        serial_number = invoice_data.get('serialNumber', '')
        sender_name = invoice_data.get('sender', {}).get('name', '') if invoice_data.get('sender') else ''
        total = invoice_data.get('totalAmount', 0) or invoice_data.get('amount', 0)
        
        # Создаем E-Taxes Purchase
        etaxes_purchase_result = create_etaxes_purchase(invoice_id, date_to_use, sender_name, total)
        if etaxes_purchase_result and etaxes_purchase_result.get('success'):
            # Устанавливаем поля E-Taxes ДО submit'а
            po.is_taxes_doc = 1
            po.taxes_doc = etaxes_purchase_result.get('name')
            # Сохраняем изменения
            po.save()
        
        # ИЗМЕНЕНИЕ: Делаем Submit для Purchase Order
        try:
            po.submit()
            frappe.log_error(f"Purchase Order {po.name} submitted successfully", "Import Invoice Success")
        except Exception as e:
            frappe.log_error(f"Error submitting Purchase Order {po.name}: {str(e)}", "Submit PO Error")
            return {
                'success': False,
                'message': f'Failed to submit Purchase Order: {str(e)}'
            }
        
        # ИЗМЕНЕНИЕ: Создаем Purchase Invoice
        try:
            pi_name = create_purchase_invoice_from_order(po.name)
            if pi_name:
                # Делаем Submit для Purchase Invoice
                pi = frappe.get_doc("Purchase Invoice", pi_name)
                pi.submit()
                frappe.log_error(f"Purchase Invoice {pi_name} created and submitted successfully", "Import Invoice Success")
                
                return {
                    'success': True,
                    'message': 'Invoice data imported successfully. Purchase Order and Purchase Invoice created.',
                    'purchase_order': po.name,
                    'purchase_invoice': pi_name
                }
            else:
                return {
                    'success': True,
                    'message': 'Invoice data imported successfully. Purchase Order created, but Purchase Invoice creation failed.',
                    'purchase_order': po.name
                }
        except Exception as e:
            frappe.log_error(f"Error creating or submitting Purchase Invoice: {str(e)}", "Submit PI Error")
            return {
                'success': True,
                'message': 'Invoice data imported successfully. Purchase Order created, but Purchase Invoice creation failed.',
                'purchase_order': po.name
            }
            
    except Exception as e:
        frappe.log_error(f"Error in import_invoice_with_mapping: {str(e)}\n{frappe.get_traceback()}", "Import Error")
        return {
            'success': False,
            'message': "An unknown error occurred, please try again in a few minutes."
        }

@frappe.whitelist()
def get_etaxes_purchases():
    """Gets list of all E-Taxes Purchase records"""
    try:
        purchases = frappe.get_all('E-Taxes Purchase', 
                                  fields=['name', 'etaxes_id', 'date', 'party', 'total'])
        
        # Debug logging
        frappe.logger().info(f"Retrieved {len(purchases)} E-Taxes Purchase records")
        
        return {
            'success': True,
            'purchases': purchases
        }
    except Exception as e:
        frappe.log_error(f"Error in get_etaxes_purchases: {str(e)}", "API Error")
        return {
            'success': False,
            'message': "An unknown error occurred, please try again in a few minutes."
        }

@frappe.whitelist()
def create_etaxes_purchase(etaxes_id, date, party, total):
    """Creates E-Taxes Purchase record for tracking imported invoices"""
    # Записываем активность
    record_etaxes_activity()
    
    try:
        # Normalize ID for storage
        etaxes_id = str(etaxes_id).strip() if etaxes_id else ""
        
        # Debug logging
        frappe.logger().info(f"Creating E-Taxes Purchase: ID={etaxes_id}, date={date}, party={party}, total={total}")
        
        # Check if record with such etaxes_id already exists
        existing = frappe.db.get_value('E-Taxes Purchase', {'etaxes_id': etaxes_id}, 'name')
        if existing:
            # Debug logging
            frappe.logger().info(f"E-Taxes Purchase already exists: {existing}")
            
            return {
                'success': True,
                'message': 'Record already exists',
                'name': existing
            }
        
        # Create new record
        etaxes_purchase = frappe.new_doc('E-Taxes Purchase')
        etaxes_purchase.etaxes_id = etaxes_id
        etaxes_purchase.date = date
        etaxes_purchase.party = party
        etaxes_purchase.total = total
        
        etaxes_purchase.insert()
        frappe.db.commit()  # Explicit commit
        
        # Debug logging
        frappe.logger().info(f"Created new E-Taxes Purchase: {etaxes_purchase.name}")
        
        return {
            'success': True,
            'name': etaxes_purchase.name
        }
    except Exception as e:
        frappe.log_error(f"Error in create_etaxes_purchase: {str(e)}\n{frappe.get_traceback()}", "API Error")
        return {
            'success': False,
            'message': "An unknown error occurred, please try again in a few minutes."
        }

@frappe.whitelist()
def link_purchase_order_to_etaxes(purchase_order, etaxes_purchase):
    """Sets link between Purchase Order and E-Taxes Purchase"""
    # Записываем активность
    record_etaxes_activity()
    
    try:
        # Debug logging
        frappe.logger().info(f"Linking Purchase Order {purchase_order} to E-Taxes Purchase {etaxes_purchase}")
        
        # Check if both documents exist
        po_exists = frappe.db.exists('Purchase Order', purchase_order)
        etaxes_exists = frappe.db.exists('E-Taxes Purchase', etaxes_purchase)
        
        if not po_exists or not etaxes_exists:
            frappe.log_error(f"Link error: PO exists={po_exists}, E-Taxes exists={etaxes_exists}", "Link Error")
            return {
                'success': False,
                'message': 'One or both documents do not exist'
            }
        
        # Get Purchase Order document
        po_doc = frappe.get_doc('Purchase Order', purchase_order)
        
        # Set link to E-Taxes Purchase
        po_doc.is_taxes_doc = 1
        po_doc.taxes_doc = etaxes_purchase
        
        # Save changes
        po_doc.save()
        frappe.db.commit()  # Explicit commit
        
        # Debug logging
        frappe.logger().info(f"Successfully linked PO and E-Taxes Purchase")
        
        return {
            'success': True
        }
    except Exception as e:
        frappe.log_error(f"Error in link_purchase_order_to_etaxes: {str(e)}\n{frappe.get_traceback()}", "API Error")
        return {
            'success': False,
            'message': "An unknown error occurred, please try again in a few minutes."
        }

@frappe.whitelist()
def on_delete_purchase_order(doc, method):
    """Deletes related E-Taxes Purchase record when Purchase Order is deleted"""
    if doc.is_taxes_doc and doc.taxes_doc:
        try:
            frappe.logger().info(f"Deleting E-Taxes Purchase {doc.taxes_doc} due to Purchase Order {doc.name} deletion")
            
            # Check if E-Taxes Purchase record exists
            if frappe.db.exists("E-Taxes Purchase", doc.taxes_doc):
                frappe.delete_doc('E-Taxes Purchase', doc.taxes_doc, ignore_permissions=True, force=True)
                frappe.db.commit()
                frappe.logger().info(f"Successfully deleted E-Taxes Purchase {doc.taxes_doc}")
        except Exception as e:
            frappe.log_error(f"Error deleting E-Taxes Purchase {doc.taxes_doc}: {str(e)}\n{frappe.get_traceback()}", 
                           "Purchase Order Delete Error")

@frappe.whitelist()
def get_unmapped_units():
    """Getting unmapped units from E-Taxes"""
    try:
        # Get active settings
        settings = get_active_settings()
        if not settings:
            return {
                'success': False,
                'message': 'No active E-Taxes settings found'
            }
        
        # Get all unmapped units from E-Taxes
        unmapped_units = []
        
        # 1. Get all units with 'New' status
        etaxes_units = frappe.get_all('E-Taxes Unit', 
                                    filters={'status': 'New'},
                                    fields=['name', 'etaxes_unit_name', 'etaxes_unit_code'])
        
        # 2. Check if they are already in settings
        existing_mappings = {}
        for mapping in settings.unit_mappings:
            existing_mappings[mapping.etaxes_unit_name] = mapping.erp_unit
        
        # 3. Add only those that are not in settings
        for unit in etaxes_units:
            if unit.name not in existing_mappings:
                # ПРАВИЛЬНО: возвращаем объект с нужными полями для Link форматера
                unit_data = {
                    'value': unit.name,  # ID документа для Link поля
                    'label': unit.etaxes_unit_name,  # Отображаемое имя
                    'etaxes_unit_name': unit.etaxes_unit_name,  # Для link_formatter
                    'name': unit.name,
                    'etaxes_unit_code': unit.etaxes_unit_code
                }
                
                unmapped_units.append(unit_data)
        
        return {
            'success': True,
            'units': unmapped_units
        }
    except Exception as e:
        return {
            'success': False,
            'message': "An unknown error occurred, please try again in a few minutes."
        }

@frappe.whitelist()
def match_similar_units():
    """Matching units by similar names"""
    # Записываем активность
    record_etaxes_activity()
    
    try:
        import re
        from difflib import SequenceMatcher
        
        # Get active settings
        settings = get_active_settings()
        if not settings:
            return {
                'success': False,
                'message': 'No active E-Taxes settings found'
            }
        
        # Get similarity threshold from settings
        similarity_threshold = float(settings.similarity_threshold) / 100.0 if settings.similarity_threshold else 0.8
        consider_azeri = settings.consider_azeri_chars
        
        # Get all units with "New" status from E-Taxes
        etaxes_units = frappe.get_all('E-Taxes Unit', 
                                      filters={'status': 'New'},
                                      fields=['name', 'etaxes_unit_name', 'etaxes_unit_code'])
        
        # Get existing mappings
        existing_mappings = {}
        for mapping in settings.unit_mappings:
            existing_mappings[mapping.etaxes_unit_name] = mapping.erp_unit
        
        # Get units with empty mapping (exist in table but erp_unit is None or empty)
        units_with_empty_mapping = []
        for etaxes_id, erp_unit in existing_mappings.items():
            if not erp_unit:  # If erp_unit is None or empty string
                # Find corresponding E-Taxes unit
                for unit in etaxes_units:
                    if unit.name == etaxes_id:
                        units_with_empty_mapping.append(unit)
                        break
        
        # Filter only those that are not in mappings
        unmapped_units = []
        for unit in etaxes_units:
            if unit.name not in existing_mappings:
                unmapped_units.append(unit)
        
        # Combine two lists: units without mappings and units with empty mappings
        units_to_process = unmapped_units + units_with_empty_mapping
        
        # Get all UOMs from system
        system_units = frappe.get_all('UOM', fields=['name', 'uom_name'])
        
        matched_count = 0
        total_processed = len(units_to_process)
        
        # Get fresh copy of settings document
        doc = frappe.get_doc("E-Taxes Settings", settings.name)
        
        # Process all units
        for etaxes_unit in units_to_process:
            # Find similar unit
            best_match = None
            best_score = 0
            
            # Normalize E-Taxes unit name
            etaxes_name = normalize_string(etaxes_unit.etaxes_unit_name, consider_azeri)
            
            for unit in system_units:
                # Normalize system unit name
                unit_name = normalize_string(unit.uom_name, consider_azeri)
                
                # Calculate similarity coefficient
                name_score = SequenceMatcher(None, etaxes_name, unit_name).ratio()
                
                # Check if this is a better match
                if name_score > best_score and name_score >= similarity_threshold:
                    best_score = name_score
                    best_match = unit
            
            if best_match:
                # IMPORTANT: check if there is already a record for this unit in mappings
                existing_row = None
                for idx, mapping in enumerate(doc.unit_mappings):
                    if mapping.etaxes_unit_name == etaxes_unit.name:
                        existing_row = idx
                        break
                
                if existing_row is not None:
                    # If row already exists, update its erp_unit value
                    doc.unit_mappings[existing_row].erp_unit = best_match.name
                    doc.unit_mappings[existing_row].mapping_type = 'Automatic'
                else:
                    # If no row, add a new one
                    doc.append('unit_mappings', {
                        'etaxes_unit_name': etaxes_unit.name,
                        'erp_unit': best_match.name,
                        'mapping_type': 'Automatic'
                    })
                
                matched_count += 1
                
                # Update E-Taxes Unit status only for units that have a match
                unit_doc = frappe.get_doc('E-Taxes Unit', etaxes_unit.name)
                unit_doc.status = 'Mapped'
                unit_doc.mapped_unit = best_match.name
                unit_doc.save()

        # Save settings only if matches were found
        if matched_count > 0:
            doc.save()
        
        return {
            'success': True,
            'matched_count': matched_count,
            'total_processed': total_processed,
            'message': f'Matched {matched_count} out of {total_processed} units'
        }
    
    except Exception as e:
        return {
            'success': False,
            'message': "An unknown error occurred, please try again in a few minutes."
        }

@frappe.whitelist()
def create_unmapped_units(settings_name):
    """Creating units for unmapped elements from E-Taxes settings table"""
    # Записываем активность
    record_etaxes_activity()
    
    try:
        import re
        
        # Get settings
        settings_doc = frappe.get_doc("E-Taxes Settings", settings_name)
        
        # Get elements from table that have no mapping
        unmapped_units = []
        
        for mapping_unit in settings_doc.unit_mappings:
            # Check that unit has no mapping or it's empty
            if not mapping_unit.erp_unit:
                # Get E-Taxes Unit document for this record
                if frappe.db.exists('E-Taxes Unit', mapping_unit.etaxes_unit_name):
                    etaxes_unit = frappe.get_doc('E-Taxes Unit', mapping_unit.etaxes_unit_name)
                    unmapped_units.append(etaxes_unit)
        
        if len(unmapped_units) == 0:
            return {
                "success": True,
                "created_count": 0,
                "error_count": 0,
                "message": "No unmapped units in table to create"
            }
        
        created_count = 0
        error_count = 0
        
        for etaxes_unit in unmapped_units:
            try:
                # Check if a UOM with this name already exists
                uom_exists = frappe.db.exists('UOM', {'uom_name': etaxes_unit.etaxes_unit_name})
                
                if uom_exists:
                    continue
                
                # Create new UOM
                uom_doc = frappe.new_doc("UOM")
                
                # Standard fields for UOM
                uom_doc.uom_name = etaxes_unit.etaxes_unit_name
                uom_doc.enabled = 1
                
                # Set a default conversion factor if needed
                uom_doc.must_be_whole_number = 0
                
                # Save UOM
                uom_doc.insert()
                
                # Update mapping in settings
                for mapping in settings_doc.unit_mappings:
                    if mapping.etaxes_unit_name == etaxes_unit.name:
                        mapping.erp_unit = uom_doc.name
                        mapping.mapping_type = 'Automatic'
                        break
                
                # Update E-Taxes Unit status
                etaxes_unit.status = 'Mapped'
                etaxes_unit.mapped_unit = uom_doc.name
                etaxes_unit.save()
                
                created_count += 1
                
            except Exception as e:
                error_count += 1
        
        # Save settings after adding all mappings
        if created_count > 0:
            settings_doc.save()
        
        return {
            "success": True,
            "created_count": created_count,
            "error_count": error_count,
            "message": f"Created {created_count} units, errors: {error_count}"
        }
    
    except Exception as e:
        return {
            "success": False,
            "message": "An unknown error occurred, please try again in a few minutes."
        }

@frappe.whitelist()
def load_units_from_invoices(date_from, date_to, max_count=200, offset=0):
    """Loading invoices list for a period - returns invoices for frontend processing"""
    # Записываем активность
    record_etaxes_activity()
    
    try:
        # Get default settings
        asan_login_settings = get_default_asan_login()
        
        if not asan_login_settings.get('found'):
            return {
                'success': False,
                'message': 'No Asan Login settings found'
            }
        
        # Ensure max_count is an integer
        try:
            max_count = int(max_count)
        except (ValueError, TypeError):
            max_count = 200
            
        try:
            offset = int(offset)
        except (ValueError, TypeError):
            offset = 0
        
        # Format filters for the API request
        filters = {
            "actionOwner": None,
            "amountFrom": None,
            "amountTo": None,
            "creationDateFrom": date_from,
            "creationDateTo": date_to,
            "kinds": ["defaultInvoice", "agent", "resale", "recycling", "taxCodex163", "taxCodex177_5", "returnInvoice", "returnByAgent", "returnRecycled", "exportNoteInvoice", "exciseGoodsTransfer", "advanceInvoice"],
            "maxCount": max_count,
            "offset": offset,
            "productCode": None,
            "productName": None,
            "receiverName": None,
            "receiverTin": None,
            "senderName": None,
            "senderTin": None,
            "serialNumber": None,
            "sortAsc": True,
            "sortBy": "creationDate",
            "statuses": ["approved", "approvedBySystem"],
            "types": ["current", "corrected"]
        }
        
        # Загружаем из inbox (входящие инвойсы)
        response_inbox = get_invoices(asan_login_settings['main_token'], json.dumps(filters))
        
        if 'error' in response_inbox:
            return {
                'success': False,
                'error': response_inbox.get('error'),
                'message': response_inbox['message'] if 'message' in response_inbox else 'Failed to retrieve invoices'
            }
        
        invoices_inbox = response_inbox.get('data', []) or response_inbox.get('invoices', [])
        
        # Помечаем источник для каждого инвойса из inbox
        for invoice in invoices_inbox:
            invoice['_source'] = 'inbox'
        
        # Инициализируем all_invoices с inbox инвойсами
        all_invoices = invoices_inbox[:]  # Создаем копию списка
        
        # Попытка загрузить из outbox (исходящие инвойсы/продажи)
        try:
            from .sales_api import get_sales_invoices
            response_outbox = get_sales_invoices(asan_login_settings['main_token'], json.dumps(filters))
            
            if 'error' not in response_outbox:
                invoices_outbox = response_outbox.get('data', []) or response_outbox.get('invoices', [])
                
                # Помечаем источник для каждого инвойса из outbox
                for invoice in invoices_outbox:
                    invoice['_source'] = 'outbox'
                
                # Добавляем outbox инвойсы к inbox инвойсам
                all_invoices.extend(invoices_outbox)
                
                # Определяем hasMore на основе обоих источников
                hasMore_inbox = response_inbox.get('hasMore', False) or response_inbox.get('total', 0) > offset + max_count
                hasMore_outbox = response_outbox.get('hasMore', False) or response_outbox.get('total', 0) > offset + max_count
                hasMore = hasMore_inbox or hasMore_outbox
            else:
                # Если не удалось загрузить из outbox, используем только inbox
                hasMore = response_inbox.get('hasMore', False) or response_inbox.get('total', 0) > offset + max_count
                
        except ImportError:
            # Если модуль sales_api недоступен, используем только inbox
            hasMore = response_inbox.get('hasMore', False) or response_inbox.get('total', 0) > offset + max_count
        except Exception as e:
            # При любой другой ошибке используем только inbox
            frappe.log_error(f"Error loading from outbox: {str(e)}", "Load Units Outbox Error")
            hasMore = response_inbox.get('hasMore', False) or response_inbox.get('total', 0) > offset + max_count
        
        return {
            'success': True,
            'invoices': all_invoices,
            'hasMore': hasMore,
            'token': asan_login_settings['main_token'],
            'total': len(all_invoices),
            'inbox_count': len(invoices_inbox),
            'outbox_count': len(all_invoices) - len(invoices_inbox)
        }
        
    except Exception as e:
        frappe.log_error(f"Error in load_units_from_invoices: {str(e)}\n{frappe.get_traceback()}", "Load Units Error")
        return {
            'success': False,
            'message': "An unknown error occurred, please try again in a few minutes."
        }

@frappe.whitelist()
def update_mapped_statuses(doc, method=None):
    """Update mapped statuses in E-Taxes DocTypes when settings are saved"""
    try:
        # 1. Handle E-Taxes Unit mappings
        if hasattr(doc, 'unit_mappings'):
            # Создаем словарь текущих соответствий
            current_unit_mappings = {}
            for mapping in doc.unit_mappings:
                if mapping.etaxes_unit_name:
                    # Проверяем что erp_unit не None и не пустая строка
                    erp_unit = getattr(mapping, 'erp_unit', None)
                    current_unit_mappings[mapping.etaxes_unit_name] = erp_unit if erp_unit else None
            
            # Обновляем статусы для всех единиц в mappings
            for etaxes_unit_name, erp_unit in current_unit_mappings.items():
                try:
                    if frappe.db.exists('E-Taxes Unit', etaxes_unit_name):
                        if erp_unit:  # Если есть соответствие
                            frappe.db.set_value('E-Taxes Unit', etaxes_unit_name, {
                                'status': 'Mapped',
                                'mapped_unit': erp_unit
                            }, update_modified=False)
                        else:  # Если соответствие пустое
                            frappe.db.set_value('E-Taxes Unit', etaxes_unit_name, {
                                'status': 'New',
                                'mapped_unit': None
                            }, update_modified=False)
                except Exception as e:
                    frappe.log_error(f"Error updating E-Taxes Unit {etaxes_unit_name}: {str(e)}", "Status Update Error")
            
            # Сбрасываем статус для единиц, которые были удалены из mappings
            try:
                mapped_units = frappe.get_all('E-Taxes Unit', 
                                            filters={'status': 'Mapped'},
                                            fields=['name', 'mapped_unit'])
                
                for unit in mapped_units:
                    if unit.name not in current_unit_mappings:
                        frappe.db.set_value('E-Taxes Unit', unit.name, {
                            'status': 'New',
                            'mapped_unit': None
                        }, update_modified=False)
            except Exception as e:
                frappe.log_error(f"Error resetting unmapped units: {str(e)}", "Status Update Error")
        
        # 2. Handle E-Taxes Item mappings
        if hasattr(doc, 'item_mappings'):
            # Создаем словарь текущих соответствий
            current_item_mappings = {}
            for mapping in doc.item_mappings:
                if mapping.etaxes_item_name:
                    # Проверяем что erp_item не None и не пустая строка
                    erp_item = getattr(mapping, 'erp_item', None)
                    current_item_mappings[mapping.etaxes_item_name] = erp_item if erp_item else None
            
            # Обновляем статусы для всех товаров в mappings
            for etaxes_item_name, erp_item in current_item_mappings.items():
                try:
                    if frappe.db.exists('E-Taxes Item', etaxes_item_name):
                        if erp_item:  # Если есть соответствие
                            frappe.db.set_value('E-Taxes Item', etaxes_item_name, {
                                'status': 'Mapped',
                                'mapped_item': erp_item
                            }, update_modified=False)
                        else:  # Если соответствие пустое
                            frappe.db.set_value('E-Taxes Item', etaxes_item_name, {
                                'status': 'New',
                                'mapped_item': None
                            }, update_modified=False)
                except Exception as e:
                    frappe.log_error(f"Error updating E-Taxes Item {etaxes_item_name}: {str(e)}", "Status Update Error")
            
            # Сбрасываем статус для товаров, которые были удалены из mappings
            try:
                mapped_items = frappe.get_all('E-Taxes Item', 
                                            filters={'status': 'Mapped'},
                                            fields=['name', 'mapped_item'])
                
                for item in mapped_items:
                    if item.name not in current_item_mappings:
                        frappe.db.set_value('E-Taxes Item', item.name, {
                            'status': 'New',
                            'mapped_item': None
                        }, update_modified=False)
            except Exception as e:
                frappe.log_error(f"Error resetting unmapped items: {str(e)}", "Status Update Error")
        
        # 3. Handle E-Taxes Customers mappings
        if hasattr(doc, 'customer_mappings'):
            # Создаем словарь текущих соответствий
            current_customer_mappings = {}
            for mapping in doc.customer_mappings:
                if mapping.etaxes_customer_name:
                    # Проверяем что erp_customer не None и не пустая строка
                    erp_customer = getattr(mapping, 'erp_customer', None)
                    current_customer_mappings[mapping.etaxes_customer_name] = erp_customer if erp_customer else None
            
            # Обновляем статусы для всех customers в mappings
            for etaxes_customer_name, erp_customer in current_customer_mappings.items():
                try:
                    if frappe.db.exists('E-Taxes Customers', etaxes_customer_name):
                        if erp_customer:  # Если есть соответствие
                            frappe.db.set_value('E-Taxes Customers', etaxes_customer_name, {
                                'status': 'Mapped',
                                'mapped_customer': erp_customer
                            }, update_modified=False)
                        else:  # Если соответствие пустое
                            frappe.db.set_value('E-Taxes Customers', etaxes_customer_name, {
                                'status': 'New',
                                'mapped_customer': None
                            }, update_modified=False)
                except Exception as e:
                    frappe.log_error(f"Error updating E-Taxes Customers {etaxes_customer_name}: {str(e)}", "Status Update Error")
            
            # Сбрасываем статус для customers, которые были удалены из mappings
            try:
                mapped_customers = frappe.get_all('E-Taxes Customers', 
                                                filters={'status': 'Mapped'},
                                                fields=['name', 'mapped_customer'])
                
                for customer in mapped_customers:
                    if customer.etaxes_party_name not in current_customer_mappings:
                        frappe.db.set_value('E-Taxes Customers', customer.name, {
                            'status': 'New',
                            'mapped_customer': None
                        }, update_modified=False)
            except Exception as e:
                frappe.log_error(f"Error resetting unmapped customers: {str(e)}", "Status Update Error")
        
        # 4. Handle E-Taxes Suppliers mappings
        if hasattr(doc, 'supplier_mappings'):
            # Создаем словарь текущих соответствий
            current_supplier_mappings = {}
            for mapping in doc.supplier_mappings:
                if mapping.etaxes_supplier_name:
                    # Проверяем что erp_supplier не None и не пустая строка
                    erp_supplier = getattr(mapping, 'erp_supplier', None)
                    current_supplier_mappings[mapping.etaxes_supplier_name] = erp_supplier if erp_supplier else None
            
            # Обновляем статусы для всех suppliers в mappings
            for etaxes_supplier_name, erp_supplier in current_supplier_mappings.items():
                try:
                    if frappe.db.exists('E-Taxes Suppliers', etaxes_supplier_name):
                        if erp_supplier:  # Если есть соответствие
                            frappe.db.set_value('E-Taxes Suppliers', etaxes_supplier_name, {
                                'status': 'Mapped',
                                'mapped_supplier': erp_supplier
                            }, update_modified=False)
                        else:  # Если соответствие пустое
                            frappe.db.set_value('E-Taxes Suppliers', etaxes_supplier_name, {
                                'status': 'New',
                                'mapped_supplier': None
                            }, update_modified=False)
                except Exception as e:
                    frappe.log_error(f"Error updating E-Taxes Suppliers {etaxes_supplier_name}: {str(e)}", "Status Update Error")
            
            # Сбрасываем статус для suppliers, которые были удалены из mappings
            try:
                mapped_suppliers = frappe.get_all('E-Taxes Suppliers', 
                                                filters={'status': 'Mapped'},
                                                fields=['name', 'mapped_supplier'])
                
                for supplier in mapped_suppliers:
                    if supplier.etaxes_party_name not in current_supplier_mappings:
                        frappe.db.set_value('E-Taxes Suppliers', supplier.name, {
                            'status': 'New',
                            'mapped_supplier': None
                        }, update_modified=False)
            except Exception as e:
                frappe.log_error(f"Error resetting unmapped suppliers: {str(e)}", "Status Update Error")
        
        # Явно коммитим изменения
        frappe.db.commit()
    
    except Exception as e:
        frappe.log_error(f"Error updating mapped statuses: {str(e)}\n{frappe.get_traceback()}", 
                        "E-Taxes Settings Error")

@frappe.whitelist()
def refresh_item_mappings_display(settings_name):
    """Операция-пустышка для обновления отображения item mappings"""
    try:
        # Получаем документ настроек
        settings_doc = frappe.get_doc("E-Taxes Settings", settings_name)
        
        # Подготавливаем данные для обновления отображения на клиенте
        updated_mappings = []
        
        for mapping in settings_doc.item_mappings:
            if mapping.etaxes_item_name:
                # Получаем display name для Link поля
                display_name = frappe.db.get_value('E-Taxes Item', mapping.etaxes_item_name, 'etaxes_item_name')
                
                updated_mappings.append({
                    'idx': mapping.idx,
                    'name': mapping.name,
                    'etaxes_item_name_id': mapping.etaxes_item_name,
                    'etaxes_item_name_display': display_name or mapping.etaxes_item_name
                })
        
        return {
            'success': True,
            'message': 'Display refreshed successfully',
            'updated_mappings': updated_mappings
        }
        
    except Exception as e:
        frappe.log_error(f"Error refreshing display: {str(e)}", "Display Refresh Error")
        return {
            'success': False,
            'message': "An error occurred while refreshing display"
        }

@frappe.whitelist()
def process_single_invoice_for_items(token, invoice_id, source_type='purchase'):
    """Processing a single invoice for item extraction with EQM Code assignment"""
    # Записываем активность
    record_etaxes_activity()
    
    try:
        created_count = 0
        skipped_count = 0
        failed_count = 0
        updated_count = 0
        
        # Получаем детали инвойса
        invoice_details = get_invoice_details(token, invoice_id)
        
        # Проверяем на ошибки
        if isinstance(invoice_details, dict) and 'error' in invoice_details:
            return {
                'success': False,
                'error': invoice_details.get('error'),
                'message': invoice_details.get('message', 'Failed to get invoice details')
            }
        
        serial_number = invoice_details.get('serialNumber', '')
        items = invoice_details.get('items', [])
        
        # Определяем тип источника
        is_from_purchase = 1 if source_type == 'purchase' else 0
        is_from_sales = 1 if source_type == 'sales' else 0
        
        # Словарь для предотвращения дубликатов в пределах одного инвойса
        unique_items = {}
        
        for item in items:
            item_name = item.get('productName', '').strip()
            item_code = item.get('itemId', '') or f"CODE-{serial_number}"
            
            # Пропускаем пустые товары
            if not item_name:
                failed_count += 1
                continue
            
            # ИСПРАВЛЕНИЕ: Просто обрезаем до 140 символов
            if len(item_name) > 140:
                item_name = item_name[:140]
            
            # Проверяем существование по обрезанному имени
            existing_item = frappe.db.get_value('E-Taxes Item', item_name, 
                                              ['name', 'is_from_purchase', 'is_from_sales'], as_dict=True)
            
            if existing_item:
                # Если товар уже существует, обновляем флаги источника при необходимости
                need_update = False
                update_data = {}
                
                if is_from_purchase and not existing_item.is_from_purchase:
                    update_data['is_from_purchase'] = 1
                    need_update = True
                
                if is_from_sales and not existing_item.is_from_sales:
                    update_data['is_from_sales'] = 1
                    need_update = True
                
                if need_update:
                    try:
                        frappe.db.set_value('E-Taxes Item', item_name, update_data)
                        updated_count += 1
                    except Exception as e:
                        frappe.log_error(f"Error updating E-Taxes Item {item_name}: {str(e)}", "Update Item Source Error")
                
                skipped_count += 1
                continue
            
            # Используем обрезанное имя товара как ключ
            if item_name in unique_items:
                continue
            
            # Обрабатываем информацию о группе товаров
            product_group = item.get('productGroup', {})
            product_group_code = product_group.get('code', '') if product_group else ''
            product_group_type = product_group.get('type', '') if product_group else ''
            
            # Получаем название группы товаров (предпочитаем азербайджанский язык)
            product_group_name = ''
            if product_group and product_group.get('name'):
                names = product_group.get('name', {})
                if isinstance(names, dict):
                    product_group_name = names.get('az', '') or names.get('en', '') or names.get('ru', '')
                elif isinstance(names, str):
                    product_group_name = names
            
            # НОВОЕ: Обрезаем название группы товаров до 1000 символов
            if len(product_group_name) > 1000:
                product_group_name = product_group_name[:1000]
                frappe.log_error(f"Product group name truncated for item {item_name}: original length {len(product_group.get('name', {}).get('az', ''))}", "Product Group Name Truncation")
            
            # НОВОЕ: Поиск EQM Code по коду группы товаров - возвращаем name для Link поля
            eqm_code = None
            if product_group_code:
                try:
                    frappe.log_error(f"[EQM DEBUG] Searching for product_group_code: '{product_group_code}'", "EQM Code Search")
                    
                    # Ищем запись и получаем и name, и eqm_name
                    eqm_record = frappe.db.sql("""
                        SELECT name, eqm_name 
                        FROM `tabEQM Codes` 
                        WHERE eqm_name LIKE %s
                        LIMIT 1
                    """, (f"{product_group_code} -%",), as_dict=True)
                    
                    frappe.log_error(f"[EQM DEBUG] SQL search result: {eqm_record}", "EQM Code Search")
                    
                    if eqm_record:
                        # ИСПРАВЛЕНИЕ: Сохраняем name (ID записи) для Link поля, а не eqm_name
                        eqm_code = eqm_record[0].get('name')  # Изменено с eqm_name на name
                        frappe.log_error(f"[EQM DEBUG] Found EQM Code ID: '{eqm_code}' (description: '{eqm_record[0].get('eqm_name')}') for product group '{product_group_code}' in item {item_name}", "EQM Code Assignment")
                    else:
                        frappe.log_error(f"[EQM DEBUG] EQM Code not found for product group '{product_group_code}' in item {item_name}", "EQM Code Not Found")
                            
                except Exception as e:
                    frappe.log_error(f"[EQM DEBUG] Error searching EQM Code for '{product_group_code}': {str(e)}", "EQM Code Search Error")

            # Логируем финальное значение
            frappe.log_error(f"[EQM DEBUG] Final eqm_code ID to save: '{eqm_code}' for item {item_name}", "EQM Code Final")

            # Логируем финальное значение
            frappe.log_error(f"[EQM DEBUG] Final eqm_code value to save: '{eqm_code}' for item {item_name}", "EQM Code Final")


            # Определяем, является ли товар услугой
            is_service = 1 if product_group_type == 'service' else 0
            
            # Добавляем в словарь уникальных товаров
            unique_items[item_name] = {
                'etaxes_item_name': item_name,
                'etaxes_item_code': item_code,
                'etaxes_unit': item.get('unit', ''),
                'etaxes_price': item.get('pricePerUnit', 0),
                'etaxes_product_group_code': product_group_code,
                'etaxes_product_group_name': product_group_name,
                'etaxes_product_group_type': product_group_type,
                'is_service_item': is_service,
                'source_invoice': serial_number,
                'is_from_purchase': is_from_purchase,
                'is_from_sales': is_from_sales,
                'status': 'New',
                'eqm_code': eqm_code  # НОВОЕ: Добавляем EQM код
            }
        
        # Создаём записи для уникальных товаров
        for item_name, item_data in unique_items.items():
            try:
                # Финальная проверка перед созданием
                existing_check = frappe.db.exists('E-Taxes Item', item_name)
                
                if existing_check:
                    skipped_count += 1
                    continue
                
                doc = frappe.get_doc({
                    'doctype': 'E-Taxes Item',
                    **item_data
                })
                doc.insert(ignore_permissions=True, ignore_if_duplicate=True)
                created_count += 1
                
            except frappe.DuplicateEntryError:
                skipped_count += 1
            except Exception as e:
                frappe.log_error(f"Error creating E-Taxes Item {item_data['etaxes_item_name']}: {str(e)}", "Process Single Invoice Error")
                failed_count += 1
        
        return {
            'success': True,
            'created_count': created_count,
            'updated_count': updated_count,
            'skipped_count': skipped_count,
            'failed_count': failed_count,
            'invoice_id': invoice_id,
            'serial_number': serial_number,
            'source_type': source_type
        }
        
    except Exception as e:
        frappe.log_error(f"Error in process_single_invoice_for_items: {str(e)}\n{frappe.get_traceback()}", "Process Single Invoice Error")
        return {
            'success': False,
            'message': "An unknown error occurred processing this invoice"
        }

@frappe.whitelist()
def process_single_invoice_for_units(token, invoice_id, source_type='purchase'):
    """Processing a single invoice for unit extraction with progress support"""
    # Записываем активность
    record_etaxes_activity()
    
    try:
        created_count = 0
        skipped_count = 0
        failed_count = 0
        updated_count = 0
        
        # Получаем детали инвойса
        invoice_details = get_invoice_details(token, invoice_id)
        
        # Проверяем на ошибки
        if isinstance(invoice_details, dict) and 'error' in invoice_details:
            return {
                'success': False,
                'error': invoice_details.get('error'),
                'message': invoice_details.get('message', 'Failed to get invoice details')
            }
        
        serial_number = invoice_details.get('serialNumber', '')
        items = invoice_details.get('items', [])
        
        # Определяем тип источника
        source_type_text = 'Purchase' if source_type == 'purchase' else 'Sales'
        
        # Словарь для предотвращения дубликатов в пределах одного инвойса
        unique_units = {}
        
        for item in items:
            unit_name = item.get('unit', '')
            
            # Пропускаем пустые единицы
            if not unit_name:
                continue
            
            # ИСПРАВЛЕНО: Проверяем существование по name документа (unit_name)
            # так как autoname = "field:etaxes_unit_name"
            existing_check = frappe.db.exists('E-Taxes Unit', unit_name)
            
            if existing_check:
                skipped_count += 1
                continue
            
            # Используем имя единицы как ключ для предотвращения дубликатов в одном инвойсе
            if unit_name in unique_units:
                continue
            
            # Generate a consistent code for the unit
            unit_code = unit_name
            
            unique_units[unit_name] = {
                'etaxes_unit_name': unit_name,
                'etaxes_unit_code': unit_code,
                'source_invoice': serial_number,
                'source_type': source_type_text,
                'status': 'New'
            }
        
        # Создаём записи для уникальных единиц
        for unit_name, unit_data in unique_units.items():
            try:
                # Финальная проверка перед созданием (защита от race condition)
                # ИСПРАВЛЕНО: Проверяем по name документа
                existing_check = frappe.db.exists('E-Taxes Unit', unit_name)
                
                if existing_check:
                    skipped_count += 1
                    continue
                
                # ИСПРАВЛЕНО: Используем insert с ignore_if_duplicate=True
                doc = frappe.get_doc({
                    'doctype': 'E-Taxes Unit',
                    **unit_data
                })
                doc.insert(ignore_permissions=True, ignore_if_duplicate=True)
                created_count += 1
                
            except frappe.DuplicateEntryError:
                # Если всё-таки произошло дублирование, считаем как пропущенный
                skipped_count += 1
            except Exception as e:
                frappe.log_error(f"Error creating E-Taxes Unit {unit_name}: {str(e)}", "Process Single Invoice Error")
                failed_count += 1
        
        return {
            'success': True,
            'created_count': created_count,
            'updated_count': updated_count,
            'skipped_count': skipped_count,
            'failed_count': failed_count,
            'invoice_id': invoice_id,
            'serial_number': serial_number,
            'unique_units': len(unique_units),
            'source_type': source_type
        }
        
    except Exception as e:
        frappe.log_error(f"Error in process_single_invoice_for_units: {str(e)}\n{frappe.get_traceback()}", "Process Single Invoice Error")
        return {
            'success': False,
            'message': "An unknown error occurred processing this invoice"
        }
    
@frappe.whitelist()
def process_invoice_parties_from_list(invoice_list_data, token):
    """Обрабатывает контрагентов из списка инвойсов с разделением на customers и suppliers"""
    try:
        if isinstance(invoice_list_data, str):
            invoices = json.loads(invoice_list_data)
        else:
            invoices = invoice_list_data
        
        total_customers_created = 0
        total_suppliers_created = 0
        total_skipped = 0
        total_failed = 0
        processed_invoices = 0
        
        # Для отслеживания уникальных контрагентов в рамках батча
        batch_unique_customers = {}
        batch_unique_suppliers = {}
        
        for invoice in invoices:
            try:
                # Извлекаем данные напрямую из списка инвойсов
                serial_number = invoice.get('serialNumber', invoice.get('id', ''))
                source = invoice.get('_source', 'inbox')
                
                # Обработка отправителя
                sender = invoice.get('sender', {})
                if sender:
                    sender_name = (sender.get('name') or '').strip()
                    sender_tin = (sender.get('tin') or '').strip()
                    sender_address = (sender.get('address') or '').strip()
                    
                    sender_name = ' '.join(sender_name.split())
                    
                    if sender_name and sender_tin:
                        # Логика определения типа:
                        # Purchase invoices (inbox): Sender -> Supplier
                        # Sales invoices (outbox): Sender -> Customer
                        
                        if source == 'inbox':
                            # Inbox: Sender = Supplier
                            if sender_name not in batch_unique_suppliers:
                                existing = frappe.db.exists('E-Taxes Suppliers', sender_name)
                                
                                if not existing:
                                    batch_unique_suppliers[sender_name] = {
                                        'etaxes_party_name': sender_name,
                                        'etaxes_tax_id': sender_tin,
                                        'etaxes_address': sender_address,
                                        'etaxes_party_type': 'Sender',
                                        'source_invoice': serial_number,
                                        'status': 'New'
                                    }
                        else:
                            # Outbox: Sender = Customer
                            if sender_name not in batch_unique_customers:
                                existing = frappe.db.exists('E-Taxes Customers', sender_name)
                                
                                if not existing:
                                    batch_unique_customers[sender_name] = {
                                        'etaxes_party_name': sender_name,
                                        'etaxes_tax_id': sender_tin,
                                        'etaxes_address': sender_address,
                                        'etaxes_party_type': 'Sender',
                                        'source_invoice': serial_number,
                                        'status': 'New'
                                    }
                
                # Обработка получателя
                receiver = invoice.get('receiver', {})
                if receiver:
                    receiver_name = (receiver.get('name') or '').strip()
                    receiver_tin = (receiver.get('tin') or '').strip()
                    receiver_address = (receiver.get('address') or '').strip()
                    
                    receiver_name = ' '.join(receiver_name.split())
                    
                    if receiver_name and receiver_tin:
                        # Логика: Receiver всегда Customer (и для inbox, и для outbox)
                        if receiver_name not in batch_unique_customers:
                            existing = frappe.db.exists('E-Taxes Customers', receiver_name)
                            
                            if not existing:
                                batch_unique_customers[receiver_name] = {
                                    'etaxes_party_name': receiver_name,
                                    'etaxes_tax_id': receiver_tin,
                                    'etaxes_address': receiver_address,
                                    'etaxes_party_type': 'Receiver',
                                    'source_invoice': serial_number,
                                    'status': 'New'
                                }
                
                processed_invoices += 1
                
            except Exception:
                total_failed += 1
                processed_invoices += 1
        
        # Создание записей для всех уникальных customers в батче
        for customer_name, customer_data in batch_unique_customers.items():
            try:
                existing = frappe.db.exists('E-Taxes Customers', customer_name)
                
                if existing:
                    total_skipped += 1
                    continue
                
                doc = frappe.get_doc({
                    'doctype': 'E-Taxes Customers',
                    **customer_data
                })
                
                doc.insert(ignore_permissions=True, ignore_if_duplicate=True)
                total_customers_created += 1
            except frappe.DuplicateEntryError:
                total_skipped += 1
            except Exception:
                total_failed += 1
        
        # Создание записей для всех уникальных suppliers в батче
        for supplier_name, supplier_data in batch_unique_suppliers.items():
            try:
                existing = frappe.db.exists('E-Taxes Suppliers', supplier_name)
                
                if existing:
                    total_skipped += 1
                    continue
                
                doc = frappe.get_doc({
                    'doctype': 'E-Taxes Suppliers',
                    **supplier_data
                })
                
                doc.insert(ignore_permissions=True, ignore_if_duplicate=True)
                total_suppliers_created += 1
            except frappe.DuplicateEntryError:
                total_skipped += 1
            except Exception:
                total_failed += 1
        
        return {
            'success': True,
            'customers_created': total_customers_created,
            'suppliers_created': total_suppliers_created,
            'skipped_count': total_skipped,
            'failed_count': total_failed,
            'processed_invoices': processed_invoices,
            'total_invoices': len(invoices),
            'unique_customers_found': len(batch_unique_customers),
            'unique_suppliers_found': len(batch_unique_suppliers)
        }
        
    except Exception as e:
        frappe.log_error(f"Error in process_invoice_parties_from_list: {str(e)}", "Process Parties Error")
        return {
            'success': False,
            'message': "An unknown error occurred processing invoice list"
        }

@frappe.whitelist()
def load_combined_data_from_etaxes(date_from, date_to, load_items=False, load_parties=False, max_count=200):
    """Загружает комбинированные данные из E-Taxes (товары, единицы, контрагенты) за один заход"""
    # Записываем активность
    record_etaxes_activity()
    
    try:
        # Преобразуем строковые параметры в boolean
        load_items = load_items in [True, 'true', '1', 1]
        load_parties = load_parties in [True, 'true', '1', 1]
        
        frappe.log_error(f"Combined load started: items={load_items}, parties={load_parties}, period={date_from} to {date_to}", "Combined Load Debug")
        
        if not load_items and not load_parties:
            return {
                'success': False,
                'message': 'No data types selected for loading'
            }
        
        # Получаем настройки для токена
        asan_login_settings = get_default_asan_login()
        
        if not asan_login_settings.get('found'):
            return {
                'success': False,
                'message': 'No Asan Login settings found'
            }
        
        token = asan_login_settings['main_token']
        frappe.log_error(f"Token obtained: {token[:20]}...", "Combined Load Debug")
        
        # Инициализируем статистику
        total_stats = {
            'items_created': 0,
            'items_skipped': 0,
            'items_failed': 0,
            'units_created': 0,
            'units_skipped': 0,
            'units_failed': 0,
            'parties_created': 0,
            'parties_skipped': 0,
            'parties_failed': 0,
            'invoices_processed': 0,
            'invoices_failed': 0
        }
        
        # Загружаем список инвойсов (используем существующую функцию)
        frappe.log_error(f"Loading invoices list...", "Combined Load Debug")
        invoices_result = load_items_from_invoices(date_from, date_to, max_count, 0)
        
        if not invoices_result.get('success'):
            frappe.log_error(f"Failed to load invoices: {invoices_result}", "Combined Load Error")
            return {
                'success': False,
                'message': invoices_result.get('message', 'Failed to load invoices'),
                'error': invoices_result.get('error')
            }
        
        invoices = invoices_result.get('invoices', [])
        frappe.log_error(f"Loaded {len(invoices)} invoices", "Combined Load Debug")
        
        if not invoices:
            return {
                'success': True,
                'message': 'No invoices found for the selected period',
                'stats': total_stats
            }
        
        # Если выбрана загрузка контрагентов - обрабатываем весь список сразу
        if load_parties:
            frappe.log_error(f"Processing parties from {len(invoices)} invoices", "Combined Load Debug")
            try:
                parties_result = process_invoice_parties_from_list(invoices, token)
                
                if parties_result.get('success'):
                    total_stats['parties_created'] = parties_result.get('created_count', 0)
                    total_stats['parties_skipped'] = parties_result.get('skipped_count', 0)
                    total_stats['parties_failed'] = parties_result.get('failed_count', 0)
                    frappe.log_error(f"Parties processed: {total_stats['parties_created']} created, {total_stats['parties_skipped']} skipped", "Combined Load Debug")
                else:
                    total_stats['parties_failed'] = len(invoices)
                    frappe.log_error(f"Parties processing failed: {parties_result}", "Combined Load Error")
                    
            except Exception as e:
                frappe.log_error(f"Error processing parties: {str(e)}\n{frappe.get_traceback()}", "Combined Load Error")
                total_stats['parties_failed'] = len(invoices)
        
        # Если выбрана загрузка товаров - обрабатываем каждый инвойс отдельно
        if load_items:
            frappe.log_error(f"Processing items from {len(invoices)} invoices", "Combined Load Debug")
            processed_invoices = 0
            
            for i, invoice in enumerate(invoices):
                invoice_id = invoice.get('id')
                if not invoice_id:
                    total_stats['invoices_failed'] += 1
                    continue
                
                # Логируем каждые 10 инвойсов
                if i % 10 == 0:
                    frappe.log_error(f"Processing invoice {i+1}/{len(invoices)}: {invoice_id}", "Combined Load Progress")
                
                try:
                    # Обрабатываем товары из инвойса
                    items_result = process_single_invoice_for_items(token, invoice_id)
                    
                    if items_result.get('success'):
                        total_stats['items_created'] += items_result.get('created_count', 0)
                        total_stats['items_skipped'] += items_result.get('skipped_count', 0)
                        total_stats['items_failed'] += items_result.get('failed_count', 0)
                    else:
                        # Проверяем на ошибку аутентификации
                        if items_result.get('error') == 'unauthorized':
                            frappe.log_error(f"Unauthorized error during items processing at invoice {i+1}", "Combined Load Error")
                            return {
                                'success': False,
                                'error': 'unauthorized',
                                'message': 'Authentication required. Please login again.',
                                'partial_stats': total_stats
                            }
                        total_stats['items_failed'] += 1
                    
                    # Обрабатываем единицы измерения из того же инвойса (автоматически)
                    units_result = process_single_invoice_for_units(token, invoice_id)
                    
                    if units_result.get('success'):
                        total_stats['units_created'] += units_result.get('created_count', 0)
                        total_stats['units_skipped'] += units_result.get('skipped_count', 0)
                        total_stats['units_failed'] += units_result.get('failed_count', 0)
                    else:
                        # Проверяем на ошибку аутентификации
                        if units_result.get('error') == 'unauthorized':
                            frappe.log_error(f"Unauthorized error during units processing at invoice {i+1}", "Combined Load Error")
                            return {
                                'success': False,
                                'error': 'unauthorized', 
                                'message': 'Authentication required. Please login again.',
                                'partial_stats': total_stats
                            }
                        total_stats['units_failed'] += 1
                    
                    processed_invoices += 1
                    total_stats['invoices_processed'] = processed_invoices
                    
                    # Коммитим каждые 20 инвойсов для сохранения прогресса
                    if processed_invoices % 20 == 0:
                        frappe.db.commit()
                        frappe.log_error(f"Progress saved: {processed_invoices} invoices processed", "Combined Load Progress")
                    
                except Exception as e:
                    frappe.log_error(f"Error processing invoice {invoice_id}: {str(e)}", "Combined Load Error")
                    total_stats['invoices_failed'] += 1
        
        # Финальный коммит
        frappe.db.commit()
        
        # Формируем сообщение с результатами
        result_messages = []
        
        if load_items:
            result_messages.append(f"Items: {total_stats['items_created']} created, {total_stats['items_skipped']} skipped")
            result_messages.append(f"Units: {total_stats['units_created']} created, {total_stats['units_skipped']} skipped")
        
        if load_parties:
            result_messages.append(f"Parties: {total_stats['parties_created']} created, {total_stats['parties_skipped']} skipped")
        
        result_message = "; ".join(result_messages)
        
        frappe.log_error(f"Combined load completed successfully: {result_message}", "Combined Load Debug")
        
        return {
            'success': True,
            'message': f'Data loading completed. {result_message}',
            'stats': total_stats
        }
        
    except Exception as e:
        frappe.log_error(f"Error in load_combined_data_from_etaxes: {str(e)}\n{frappe.get_traceback()}", "Combined Load Error")
        return {
            'success': False,
            'message': "An unknown error occurred, please try again in a few minutes."
        }

@frappe.whitelist()
def get_unmapped_customers():
    """Getting unmapped customers from E-Taxes Customers"""
    try:
        # Get active settings
        settings = get_active_settings()
        if not settings:
            return {
                'success': False,
                'message': 'No active E-Taxes settings found'
            }
        
        # Get all unmapped customers from E-Taxes Customers
        unmapped_customers = []
        
        # Get list of existing mappings
        existing_mappings = {}
        for mapping in settings.customer_mappings:
            existing_mappings[mapping.etaxes_customer_name] = mapping.erp_customer
        
        # Get unmapped customers from E-Taxes Customers
        etaxes_customers = frappe.get_all('E-Taxes Customers', 
                                        filters={'status': 'New'},
                                        fields=['name', 'etaxes_party_name', 'etaxes_tax_id', 
                                                'etaxes_party_type', 'etaxes_address', 'source_invoice'])
        
        # Filter only those that are not in mappings
        for customer in etaxes_customers:
            if customer.etaxes_party_name not in existing_mappings:
                # Create customer object with default settings
                customer_data = {
                    'name': customer.name,
                    'etaxes_party_name': customer.etaxes_party_name,
                    'etaxes_tax_id': customer.etaxes_tax_id,
                    'etaxes_party_type': customer.etaxes_party_type,
                    # Common settings
                    'customer_group': settings.default_customer_group,
                    'territory': 'All Territories',
                    'payment_terms': settings.default_payment_terms
                }
                
                # Add address if it exists
                if customer.etaxes_address:
                    customer_data['address'] = customer.etaxes_address
                
                # Add source if it exists
                if customer.source_invoice:
                    customer_data['source'] = customer.source_invoice
                
                unmapped_customers.append(customer_data)
        
        return {
            'success': True,
            'customers': unmapped_customers
        }
    except Exception as e:
        return {
            'success': False,
            'message': "An unknown error occurred, please try again in a few minutes."
        }

@frappe.whitelist()
def get_unmapped_suppliers():
    """Getting unmapped suppliers from E-Taxes Suppliers"""
    try:
        # Get active settings
        settings = get_active_settings()
        if not settings:
            return {
                'success': False,
                'message': 'No active E-Taxes settings found'
            }
        
        # Get all unmapped suppliers from E-Taxes Suppliers
        unmapped_suppliers = []
        
        # Get list of existing mappings
        existing_mappings = {}
        for mapping in settings.supplier_mappings:
            existing_mappings[mapping.etaxes_supplier_name] = mapping.erp_supplier
        
        # Get unmapped suppliers from E-Taxes Suppliers
        etaxes_suppliers = frappe.get_all('E-Taxes Suppliers', 
                                        filters={'status': 'New'},
                                        fields=['name', 'etaxes_party_name', 'etaxes_tax_id', 
                                                'etaxes_party_type', 'etaxes_address', 'source_invoice'])
        
        # Filter only those that are not in mappings
        for supplier in etaxes_suppliers:
            if supplier.etaxes_party_name not in existing_mappings:
                # Create supplier object with default settings
                supplier_data = {
                    'name': supplier.name,
                    'etaxes_party_name': supplier.etaxes_party_name,
                    'etaxes_tax_id': supplier.etaxes_tax_id,
                    'etaxes_party_type': supplier.etaxes_party_type,
                    # Common settings
                    'supplier_group': settings.default_supplier_group,
                    'payment_terms': settings.default_payment_terms
                }
                
                # Add address if it exists
                if supplier.etaxes_address:
                    supplier_data['address'] = supplier.etaxes_address
                
                # Add source if it exists
                if supplier.source_invoice:
                    supplier_data['source'] = supplier.source_invoice
                
                unmapped_suppliers.append(supplier_data)
        
        return {
            'success': True,
            'suppliers': unmapped_suppliers
        }
    except Exception as e:
        return {
            'success': False,
            'message': "An unknown error occurred, please try again in a few minutes."
        }

@frappe.whitelist()
def match_similar_customers():
    """Matching customers by similar names"""
    # Записываем активность
    record_etaxes_activity()
    
    try:
        import re
        from difflib import SequenceMatcher
        
        # Get active settings
        settings = get_active_settings()
        if not settings:
            return {
                'success': False,
                'message': 'No active E-Taxes settings found'
            }
        
        # Get similarity threshold from settings
        similarity_threshold = float(settings.similarity_threshold) / 100.0 if settings.similarity_threshold else 0.8
        consider_azeri = settings.consider_azeri_chars
        
        # Get all customers with "New" status from E-Taxes Customers
        etaxes_customers = frappe.get_all('E-Taxes Customers', 
                                        filters={'status': 'New'},
                                        fields=['name', 'etaxes_party_name', 'etaxes_tax_id'])
        
        # Get existing mappings
        existing_mappings = {}
        for mapping in settings.customer_mappings:
            existing_mappings[mapping.etaxes_customer_name] = mapping.erp_customer
        
        # Get customers with empty mapping (exist in table but erp_customer is None or empty)
        customers_with_empty_mapping = []
        for etaxes_id, erp_customer in existing_mappings.items():
            if not erp_customer:  # If erp_customer is None or empty string
                # Find corresponding E-Taxes customer
                for customer in etaxes_customers:
                    if customer.etaxes_party_name == etaxes_id:
                        customers_with_empty_mapping.append(customer)
                        break
        
        # Filter only those that are not in mappings
        unmapped_customers = []
        for customer in etaxes_customers:
            if customer.etaxes_party_name not in existing_mappings:
                unmapped_customers.append(customer)
        
        # Combine two lists: customers without mappings and customers with empty mappings
        customers_to_process = unmapped_customers + customers_with_empty_mapping
        
        # Get all customers from system
        system_customers = frappe.get_all('Customer', fields=['name', 'customer_name'])
        
        matched_count = 0
        total_processed = len(customers_to_process)
        
        # Get fresh copy of settings document
        doc = frappe.get_doc("E-Taxes Settings", settings.name)

        # Process all customers
        for etaxes_customer in customers_to_process:
            # Find similar customer
            best_match = None
            best_score = 0
            
            # Normalize E-Taxes customer name
            etaxes_name = normalize_string(etaxes_customer.etaxes_party_name, consider_azeri)
            
            for customer in system_customers:
                # Normalize system customer name
                customer_name = normalize_string(customer.customer_name, consider_azeri)
                
                # Calculate similarity coefficient
                name_score = SequenceMatcher(None, etaxes_name, customer_name).ratio()
                
                # Check if this is a better match
                if name_score > best_score and name_score >= similarity_threshold:
                    best_score = name_score
                    best_match = customer
            
            if best_match:
                # IMPORTANT: check if there is already a record for this customer in mappings
                existing_row = None
                for idx, mapping in enumerate(doc.customer_mappings):
                    if mapping.etaxes_customer_name == etaxes_customer.etaxes_party_name:
                        existing_row = idx
                        break
                
                if existing_row is not None:
                    # If row already exists, update its erp_customer value
                    doc.customer_mappings[existing_row].erp_customer = best_match.name
                    doc.customer_mappings[existing_row].mapping_type = 'Automatic'
                else:
                    # If no row, add a new one
                    doc.append('customer_mappings', {
                        'etaxes_customer_name': etaxes_customer.etaxes_party_name,
                        'etaxes_tax_id': etaxes_customer.etaxes_tax_id,
                        'erp_customer': best_match.name,
                        'mapping_type': 'Automatic'
                    })
                
                matched_count += 1
                
                # Update E-Taxes Customers status only for customers that have a match
                customer_doc = frappe.get_doc('E-Taxes Customers', etaxes_customer.name)
                customer_doc.status = 'Mapped'
                customer_doc.mapped_customer = best_match.name
                customer_doc.save()

        # Save settings only if matches were found
        if matched_count > 0:
            doc.save()
        
        return {
            'success': True,
            'matched_count': matched_count,
            'total_processed': total_processed,
            'message': f'Matched {matched_count} out of {total_processed} customers'
        }
    
    except Exception as e:
        return {
            'success': False,
            'message': "An unknown error occurred, please try again in a few minutes."
        }

@frappe.whitelist()
def match_similar_suppliers():
    """Matching suppliers by similar names"""
    # Записываем активность
    record_etaxes_activity()
    
    try:
        import re
        from difflib import SequenceMatcher
        
        # Get active settings
        settings = get_active_settings()
        if not settings:
            return {
                'success': False,
                'message': 'No active E-Taxes settings found'
            }
        
        # Get similarity threshold from settings
        similarity_threshold = float(settings.similarity_threshold) / 100.0 if settings.similarity_threshold else 0.8
        consider_azeri = settings.consider_azeri_chars
        
        # Get all suppliers with "New" status from E-Taxes Suppliers
        etaxes_suppliers = frappe.get_all('E-Taxes Suppliers', 
                                        filters={'status': 'New'},
                                        fields=['name', 'etaxes_party_name', 'etaxes_tax_id'])
        
        # Get existing mappings
        existing_mappings = {}
        for mapping in settings.supplier_mappings:
            existing_mappings[mapping.etaxes_supplier_name] = mapping.erp_supplier
        
        # Get suppliers with empty mapping (exist in table but erp_supplier is None or empty)
        suppliers_with_empty_mapping = []
        for etaxes_id, erp_supplier in existing_mappings.items():
            if not erp_supplier:  # If erp_supplier is None or empty string
                # Find corresponding E-Taxes supplier
                for supplier in etaxes_suppliers:
                    if supplier.etaxes_party_name == etaxes_id:
                        suppliers_with_empty_mapping.append(supplier)
                        break
        
        # Filter only those that are not in mappings
        unmapped_suppliers = []
        for supplier in etaxes_suppliers:
            if supplier.etaxes_party_name not in existing_mappings:
                unmapped_suppliers.append(supplier)
        
        # Combine two lists: suppliers without mappings and suppliers with empty mappings
        suppliers_to_process = unmapped_suppliers + suppliers_with_empty_mapping
        
        # Get all suppliers from system
        system_suppliers = frappe.get_all('Supplier', fields=['name', 'supplier_name'])
        
        matched_count = 0
        total_processed = len(suppliers_to_process)
        
        # Get fresh copy of settings document
        doc = frappe.get_doc("E-Taxes Settings", settings.name)

        # Process all suppliers
        for etaxes_supplier in suppliers_to_process:
            # Find similar supplier
            best_match = None
            best_score = 0
            
            # Normalize E-Taxes supplier name
            etaxes_name = normalize_string(etaxes_supplier.etaxes_party_name, consider_azeri)
            
            for supplier in system_suppliers:
                # Normalize system supplier name
                supplier_name = normalize_string(supplier.supplier_name, consider_azeri)
                
                # Calculate similarity coefficient
                name_score = SequenceMatcher(None, etaxes_name, supplier_name).ratio()
                
                # Check if this is a better match
                if name_score > best_score and name_score >= similarity_threshold:
                    best_score = name_score
                    best_match = supplier
            
            if best_match:
                # IMPORTANT: check if there is already a record for this supplier in mappings
                existing_row = None
                for idx, mapping in enumerate(doc.supplier_mappings):
                    if mapping.etaxes_supplier_name == etaxes_supplier.etaxes_party_name:
                        existing_row = idx
                        break
                
                if existing_row is not None:
                    # If row already exists, update its erp_supplier value
                    doc.supplier_mappings[existing_row].erp_supplier = best_match.name
                    doc.supplier_mappings[existing_row].mapping_type = 'Automatic'
                else:
                    # If no row, add a new one
                    doc.append('supplier_mappings', {
                        'etaxes_supplier_name': etaxes_supplier.etaxes_party_name,
                        'etaxes_tax_id': etaxes_supplier.etaxes_tax_id,
                        'erp_supplier': best_match.name,
                        'mapping_type': 'Automatic'
                    })
                
                matched_count += 1
                
                # Update E-Taxes Suppliers status only for suppliers that have a match
                supplier_doc = frappe.get_doc('E-Taxes Suppliers', etaxes_supplier.name)
                supplier_doc.status = 'Mapped'
                supplier_doc.mapped_supplier = best_match.name
                supplier_doc.save()

        # Save settings only if matches were found
        if matched_count > 0:
            doc.save()
        
        return {
            'success': True,
            'matched_count': matched_count,
            'total_processed': total_processed,
            'message': f'Matched {matched_count} out of {total_processed} suppliers'
        }
    
    except Exception as e:
        return {
            'success': False,
            'message': "An unknown error occurred, please try again in a few minutes."
        }

@frappe.whitelist()
def create_unmapped_customers(settings_name):
    """Creating customers for unmapped elements from E-Taxes settings table"""
    # Записываем активность
    record_etaxes_activity()
    
    try:
        # Get settings
        settings_doc = frappe.get_doc("E-Taxes Settings", settings_name)
        
        # Get elements from table that have no mapping
        unmapped_customers = []
        
        for mapping_customer in settings_doc.customer_mappings:
            # Check that customer has no mapping or it's empty
            if not mapping_customer.erp_customer:
                # Get E-Taxes Customers document for this record
                if frappe.db.exists('E-Taxes Customers', mapping_customer.etaxes_customer_name):
                    etaxes_customer = frappe.get_doc('E-Taxes Customers', mapping_customer.etaxes_customer_name)
                    unmapped_customers.append(etaxes_customer)
        
        if len(unmapped_customers) == 0:
            return {
                "success": True,
                "created_count": 0,
                "error_count": 0,
                "message": "No unmapped customers in table to create"
            }
        
        created_count = 0
        error_count = 0
        
        # Collect individual settings for each element
        mapping_settings = {}
        for mapping in settings_doc.customer_mappings:
            customer_settings = {}
            if mapping.customer_group:
                customer_settings['customer_group'] = mapping.customer_group
            if mapping.territory:
                customer_settings['territory'] = mapping.territory
            if mapping.payment_terms:
                customer_settings['payment_terms'] = mapping.payment_terms
            
            if customer_settings:
                mapping_settings[mapping.etaxes_customer_name] = customer_settings
        
        for etaxes_customer in unmapped_customers:
            try:
                # Check if such customer with this name already exists
                customer_exists = frappe.db.exists('Customer', {'customer_name': etaxes_customer.etaxes_party_name})
                
                if customer_exists:
                    continue
                
                # Create new customer
                customer_doc = frappe.new_doc("Customer")
                
                # Get individual settings
                individual_settings = mapping_settings.get(etaxes_customer.etaxes_party_name, {})
                
                # Set basic fields
                customer_doc.customer_name = etaxes_customer.etaxes_party_name
                customer_doc.customer_type = "Company"  # Can be configured if needed
                
                # Apply settings considering individual priorities
                customer_group = individual_settings.get('customer_group', settings_doc.default_customer_group)
                if not frappe.db.exists('Customer Group', customer_group):
                    customer_group = "All Customer Groups"
                customer_doc.customer_group = customer_group
                
                # Territory
                territory = individual_settings.get('territory', "All Territories")
                if not frappe.db.exists('Territory', territory):
                    territory = "All Territories"
                customer_doc.territory = territory
                
                # If tax_id specified, add it
                if etaxes_customer.etaxes_tax_id:
                    customer_doc.tax_id = etaxes_customer.etaxes_tax_id
                
                # If payment terms specified, add them
                if 'payment_terms' in individual_settings:
                    customer_doc.payment_terms = individual_settings['payment_terms']
                elif hasattr(settings_doc, 'default_payment_terms') and settings_doc.default_payment_terms:
                    customer_doc.payment_terms = settings_doc.default_payment_terms
                
                # Save customer
                customer_doc.insert()
                
                # Update mapping in settings
                for mapping in settings_doc.customer_mappings:
                    if mapping.etaxes_customer_name == etaxes_customer.etaxes_party_name:
                        mapping.erp_customer = customer_doc.name
                        mapping.mapping_type = 'Automatic'
                        break
                
                # Update E-Taxes Customers status
                etaxes_customer.status = 'Mapped'
                etaxes_customer.mapped_customer = customer_doc.name
                etaxes_customer.save()
                
                created_count += 1
                
            except Exception as e:
                error_count += 1
        
        # Save settings after adding all mappings
        if created_count > 0:
            settings_doc.save()
        
        return {
            "success": True,
            "created_count": created_count,
            "error_count": error_count,
            "message": f"Created {created_count} customers, errors: {error_count}"
        }
    
    except Exception as e:
        return {
            "success": False,
            "message": "An unknown error occurred, please try again in a few minutes."
        }

@frappe.whitelist()
def create_unmapped_suppliers(settings_name):
    """Creating suppliers for unmapped elements from E-Taxes settings table"""
    # Записываем активность
    record_etaxes_activity()
    
    try:
        # Get settings
        settings_doc = frappe.get_doc("E-Taxes Settings", settings_name)
        
        # Get elements from table that have no mapping
        unmapped_suppliers = []
        
        for mapping_supplier in settings_doc.supplier_mappings:
            # Check that supplier has no mapping or it's empty
            if not mapping_supplier.erp_supplier:
                # Get E-Taxes Suppliers document for this record
                if frappe.db.exists('E-Taxes Suppliers', mapping_supplier.etaxes_supplier_name):
                    etaxes_supplier = frappe.get_doc('E-Taxes Suppliers', mapping_supplier.etaxes_supplier_name)
                    unmapped_suppliers.append(etaxes_supplier)
        
        if len(unmapped_suppliers) == 0:
            return {
                "success": True,
                "created_count": 0,
                "error_count": 0,
                "message": "No unmapped suppliers in table to create"
            }
        
        created_count = 0
        error_count = 0
        
        # Collect individual settings for each element
        mapping_settings = {}
        for mapping in settings_doc.supplier_mappings:
            supplier_settings = {}
            if mapping.supplier_group:
                supplier_settings['supplier_group'] = mapping.supplier_group
            if mapping.payment_terms:
                supplier_settings['payment_terms'] = mapping.payment_terms
            
            if supplier_settings:
                mapping_settings[mapping.etaxes_supplier_name] = supplier_settings
        
        for etaxes_supplier in unmapped_suppliers:
            try:
                # ИСПРАВЛЕНИЕ: Обрезаем название поставщика до 140 символов
                supplier_name = etaxes_supplier.etaxes_party_name
                if len(supplier_name) > 140:
                    supplier_name = supplier_name[:140]
                
                # Check if such supplier with this name already exists
                supplier_exists = frappe.db.exists('Supplier', {'supplier_name': supplier_name})
                
                if supplier_exists:
                    continue
                
                # Create new supplier
                supplier_doc = frappe.new_doc("Supplier")
                
                # Get individual settings
                individual_settings = mapping_settings.get(etaxes_supplier.etaxes_party_name, {})
                
                # Set basic fields
                supplier_doc.supplier_name = supplier_name
                supplier_doc.supplier_type = "Company"  # Can be configured if needed
                
                # Apply settings considering individual priorities
                supplier_group = individual_settings.get('supplier_group', settings_doc.default_supplier_group)
                if not frappe.db.exists('Supplier Group', supplier_group):
                    supplier_group = "All Supplier Groups"
                supplier_doc.supplier_group = supplier_group
                
                # If tax_id specified, add it
                if etaxes_supplier.etaxes_tax_id:
                    supplier_doc.tax_id = etaxes_supplier.etaxes_tax_id
                
                # If payment terms specified, add them
                if 'payment_terms' in individual_settings:
                    supplier_doc.payment_terms = individual_settings['payment_terms']
                elif hasattr(settings_doc, 'default_payment_terms') and settings_doc.default_payment_terms:
                    supplier_doc.payment_terms = settings_doc.default_payment_terms
                
                # Save supplier
                supplier_doc.insert()
                
                # Update mapping in settings
                for mapping in settings_doc.supplier_mappings:
                    if mapping.etaxes_supplier_name == etaxes_supplier.etaxes_party_name:
                        mapping.erp_supplier = supplier_doc.name
                        mapping.mapping_type = 'Automatic'
                        break
                
                # Update E-Taxes Suppliers status
                etaxes_supplier.status = 'Mapped'
                etaxes_supplier.mapped_supplier = supplier_doc.name
                etaxes_supplier.save()
                
                created_count += 1
                
            except Exception as e:
                error_count += 1
        
        # Save settings after adding all mappings
        if created_count > 0:
            settings_doc.save()
        
        return {
            "success": True,
            "created_count": created_count,
            "error_count": error_count,
            "message": f"Created {created_count} suppliers, errors: {error_count}"
        }
    
    except Exception as e:
        return {
            "success": False,
            "message": "An unknown error occurred, please try again in a few minutes."
        }

@frappe.whitelist()
def create_unmapped_customers(settings_name):
    """Creating customers for unmapped elements from E-Taxes settings table"""
    # Записываем активность
    record_etaxes_activity()
    
    try:
        # Get settings
        settings_doc = frappe.get_doc("E-Taxes Settings", settings_name)
        
        # Get elements from table that have no mapping
        unmapped_customers = []
        
        for mapping_customer in settings_doc.customer_mappings:
            # Check that customer has no mapping or it's empty
            if not mapping_customer.erp_customer:
                # Get E-Taxes Customers document for this record
                if frappe.db.exists('E-Taxes Customers', mapping_customer.etaxes_customer_name):
                    etaxes_customer = frappe.get_doc('E-Taxes Customers', mapping_customer.etaxes_customer_name)
                    unmapped_customers.append(etaxes_customer)
        
        if len(unmapped_customers) == 0:
            return {
                "success": True,
                "created_count": 0,
                "error_count": 0,
                "message": "No unmapped customers in table to create"
            }
        
        created_count = 0
        error_count = 0
        
        # Collect individual settings for each element
        mapping_settings = {}
        for mapping in settings_doc.customer_mappings:
            customer_settings = {}
            if mapping.customer_group:
                customer_settings['customer_group'] = mapping.customer_group
            if mapping.territory:
                customer_settings['territory'] = mapping.territory
            if mapping.payment_terms:
                customer_settings['payment_terms'] = mapping.payment_terms
            
            if customer_settings:
                mapping_settings[mapping.etaxes_customer_name] = customer_settings
        
        for etaxes_customer in unmapped_customers:
            try:
                # ИСПРАВЛЕНИЕ: Обрезаем название клиента до 140 символов
                customer_name = etaxes_customer.etaxes_party_name
                if len(customer_name) > 140:
                    customer_name = customer_name[:140]
                
                # Check if such customer with this name already exists
                customer_exists = frappe.db.exists('Customer', {'customer_name': customer_name})
                
                if customer_exists:
                    continue
                
                # Create new customer
                customer_doc = frappe.new_doc("Customer")
                
                # Get individual settings
                individual_settings = mapping_settings.get(etaxes_customer.etaxes_party_name, {})
                
                # Set basic fields
                customer_doc.customer_name = customer_name
                customer_doc.customer_type = "Company"  # Can be configured if needed
                
                # Apply settings considering individual priorities
                customer_group = individual_settings.get('customer_group', settings_doc.default_customer_group)
                if not frappe.db.exists('Customer Group', customer_group):
                    customer_group = "All Customer Groups"
                customer_doc.customer_group = customer_group
                
                # Territory
                territory = individual_settings.get('territory', "All Territories")
                if not frappe.db.exists('Territory', territory):
                    territory = "All Territories"
                customer_doc.territory = territory
                
                # If tax_id specified, add it
                if etaxes_customer.etaxes_tax_id:
                    customer_doc.tax_id = etaxes_customer.etaxes_tax_id
                
                # If payment terms specified, add them
                if 'payment_terms' in individual_settings:
                    customer_doc.payment_terms = individual_settings['payment_terms']
                elif hasattr(settings_doc, 'default_payment_terms') and settings_doc.default_payment_terms:
                    customer_doc.payment_terms = settings_doc.default_payment_terms
                
                # Save customer
                customer_doc.insert()
                
                # Update mapping in settings
                for mapping in settings_doc.customer_mappings:
                    if mapping.etaxes_customer_name == etaxes_customer.etaxes_party_name:
                        mapping.erp_customer = customer_doc.name
                        mapping.mapping_type = 'Automatic'
                        break
                
                # Update E-Taxes Customers status
                etaxes_customer.status = 'Mapped'
                etaxes_customer.mapped_customer = customer_doc.name
                etaxes_customer.save()
                
                created_count += 1
                
            except Exception as e:
                error_count += 1
        
        # Save settings after adding all mappings
        if created_count > 0:
            settings_doc.save()
        
        return {
            "success": True,
            "created_count": created_count,
            "error_count": error_count,
            "message": f"Created {created_count} customers, errors: {error_count}"
        }
    
    except Exception as e:
        return {
            "success": False,
            "message": "An unknown error occurred, please try again in a few minutes."
        }
    
# Helper functions

@frappe.whitelist()
def get_active_settings():
    """Getting active E-Taxes settings"""
    settings_list = frappe.get_all('E-Taxes Settings', 
                                  filters={'is_active': 1},
                                  order_by='creation desc',
                                  limit=1)
    
    if not settings_list:
        return None
    
    return frappe.get_doc('E-Taxes Settings', settings_list[0].name)

@frappe.whitelist()
def normalize_string(text, consider_azeri=True):
    """Normalizing string for comparison"""
    import re
    
    if not text:
        return ''
    
    # ДОБАВЛЕНО: Сначала заменяем азербайджанские/турецкие символы ДО приведения к нижнему регистру
    if consider_azeri:
        azeri_replacements = {
            # Заглавные буквы
            'Ə': 'E',
            'Ü': 'U', 
            'Ö': 'O',
            'Ğ': 'G',
            'İ': 'I',
            'Ç': 'C',
            'Ş': 'S',
            # Строчные буквы
            'ə': 'e',
            'ü': 'u',
            'ö': 'o',
            'ğ': 'g',
            'ı': 'i',
            'ç': 'c',
            'ş': 's'
        }
        
        for azeri_char, latin_char in azeri_replacements.items():
            text = text.replace(azeri_char, latin_char)
    
    # Convert to lowercase ПОСЛЕ замены символов
    text = text.lower()
    
    # Remove all special characters and numbers
    text = re.sub(r'[^\w\s]', '', text)
    text = re.sub(r'\d+', '', text)
    
    # Remove extra spaces
    text = ' '.join(text.split())
    
    return text

@frappe.whitelist()
def load_parties_from_invoices(date_from, date_to, max_count=200, offset=0, invoice_type="purchase"):
    """Загружает контрагентов из инвойсов"""
    record_etaxes_activity()
    
    try:
        asan_login_settings = get_default_asan_login()
        
        if not asan_login_settings.get('found'):
            return {'success': False, 'message': 'No Asan Login settings found'}
        
        try:
            max_count = int(max_count)
        except (ValueError, TypeError):
            max_count = 200
            
        try:
            offset = int(offset)
        except (ValueError, TypeError):
            offset = 0
        
        filters = {
            "actionOwner": None,
            "amountFrom": None,
            "amountTo": None,
            "creationDateFrom": date_from,
            "creationDateTo": date_to,
            "kinds": ["defaultInvoice", "agent", "resale", "recycling", "taxCodex163", "taxCodex177_5", "returnInvoice", "returnByAgent", "returnRecycled", "exportNoteInvoice", "exciseGoodsTransfer", "advanceInvoice"],
            "maxCount": max_count,
            "offset": offset,
            "productCode": None,
            "productName": None,
            "receiverName": None,
            "receiverTin": None,
            "senderName": None,
            "senderTin": None,
            "serialNumber": None,
            "sortAsc": True,
            "sortBy": "creationDate",
            "statuses": ["approved", "approvedBySystem"],
            "types": ["current", "corrected"]
        }
        
        # Загружаем из inbox
        response_inbox = get_invoices(asan_login_settings['main_token'], json.dumps(filters))
        
        if 'error' in response_inbox:
            return {'success': False, **response_inbox}
        
        invoices_inbox = response_inbox.get('data', []) or response_inbox.get('invoices', [])
        
        for invoice in invoices_inbox:
            invoice['_source'] = 'inbox'
        
        all_invoices = invoices_inbox
        
        # Попытка загрузить из outbox (если доступно)
        try:
            from .sales_api import get_sales_invoices
            response_outbox = get_sales_invoices(asan_login_settings['main_token'], json.dumps(filters))
            
            if 'error' not in response_outbox:
                invoices_outbox = response_outbox.get('data', []) or response_outbox.get('invoices', [])
                
                for invoice in invoices_outbox:
                    invoice['_source'] = 'outbox'
                
                all_invoices = invoices_inbox + invoices_outbox
                
                hasMore_inbox = response_inbox.get('hasMore', False) or response_inbox.get('total', 0) > offset + max_count
                hasMore_outbox = response_outbox.get('hasMore', False) or response_outbox.get('total', 0) > offset + max_count
                hasMore = hasMore_inbox or hasMore_outbox
            else:
                hasMore = response_inbox.get('hasMore', False) or response_inbox.get('total', 0) > offset + max_count
                
        except ImportError:
            hasMore = response_inbox.get('hasMore', False) or response_inbox.get('total', 0) > offset + max_count
        except Exception:
            hasMore = response_inbox.get('hasMore', False) or response_inbox.get('total', 0) > offset + max_count
        
        return {
            'success': True,
            'invoices': all_invoices,
            'hasMore': hasMore,
            'token': asan_login_settings['main_token'],
            'total': len(all_invoices)
        }
        
    except Exception as e:
        frappe.log_error(f"Error in load_parties_from_invoices: {str(e)}", "Load Parties Error")
        return {
            'success': False, 
            'message': "An unknown error occurred, please try again in a few minutes."
        }

@frappe.whitelist() 
def get_reference_data_summary():
    """Get summary of all reference data for E-Taxes Settings dashboard"""
    try:
        summary = {}
        
        # Define DocTypes to check
        doctypes_to_check = {
            'items': 'E-Taxes Item',
            'customers': 'E-Taxes Customers',
            'suppliers': 'E-Taxes Suppliers',
            'units': 'E-Taxes Unit'
        }
        
        for key, doctype in doctypes_to_check.items():
            try:
                # Check if DocType exists
                if frappe.db.exists('DocType', doctype):
                    total = int(frappe.db.count(doctype))
                    
                    # Check if status field exists before counting mapped items
                    meta = frappe.get_meta(doctype)
                    has_status_field = any(df.fieldname == 'status' for df in meta.fields)
                    
                    if has_status_field:
                        mapped = int(frappe.db.count(doctype, {'status': 'Mapped'}))
                    else:
                        mapped = 0
                    
                    summary[key] = {'total': total, 'mapped': mapped}
                else:
                    summary[key] = {'total': 0, 'mapped': 0}
                    
            except Exception as e:
                frappe.log_error(f"Error getting summary for {doctype}: {str(e)}", "Reference Data Summary Error")
                summary[key] = {'total': 0, 'mapped': 0}
        
        return {
            'success': True,
            'summary': summary
        }
        
    except Exception as e:
        frappe.log_error(f"Error getting reference data summary: {str(e)}\n{frappe.get_traceback()}", "Reference Data Summary Error")
        return {'success': False, 'message': str(e)}
    
@frappe.whitelist()
def get_reference_data_lists(data_type, limit=100, offset=0):
    """Get paginated list of reference data"""
    try:
        # Convert limit and offset to integers
        try:
            limit = int(limit)
            offset = int(offset)
        except (ValueError, TypeError):
            limit = 100
            offset = 0
        
        valid_types = ['items', 'customers', 'suppliers', 'units']
        if data_type not in valid_types:
            return {'success': False, 'message': 'Invalid data type'}
            
        doctype_map = {
            'items': 'E-Taxes Item',
            'customers': 'E-Taxes Customers', 
            'suppliers': 'E-Taxes Suppliers',
            'units': 'E-Taxes Unit'
        }
        
        doctype = doctype_map[data_type]
        
        # Check if DocType exists
        if not frappe.db.exists('DocType', doctype):
            return {
                'success': True,
                'data': [],
                'total_count': 0,
                'has_more': False,
                'message': f'DocType {doctype} does not exist'
            }
        
        # Get DocType meta to check available fields
        meta = frappe.get_meta(doctype)
        available_fields = [df.fieldname for df in meta.fields] + ['name', 'creation', 'modified']
        
        # Define base fields that should exist
        base_fields = ['name', 'creation']
        
        # Define fields map with fallbacks
        fields_map = {
            'items': {
                'required': base_fields + ['etaxes_item_name'],
                'optional': ['etaxes_item_code', 'status', 'mapped_item', 'is_service_item']
            },
            'customers': {
                'required': base_fields + ['etaxes_party_name'],
                'optional': ['etaxes_tax_id', 'status', 'mapped_customer']
            },
            'suppliers': {
                'required': base_fields + ['etaxes_party_name'],
                'optional': ['etaxes_tax_id', 'status', 'mapped_supplier']
            },
            'units': {
                'required': base_fields + ['etaxes_unit_name'],
                'optional': ['status', 'mapped_unit']
            }
        }
        
        # Build fields list based on what's actually available
        field_config = fields_map[data_type]
        fields_to_fetch = []
        
        # Add required fields
        for field in field_config['required']:
            if field in available_fields:
                fields_to_fetch.append(field)
        
        # Add optional fields if they exist
        for field in field_config['optional']:
            if field in available_fields:
                fields_to_fetch.append(field)
        
        if not fields_to_fetch:
            return {
                'success': True,
                'data': [],
                'total_count': 0,
                'has_more': False,
                'message': f'No valid fields found for {doctype}'
            }
        
        # Get total count first
        total_count = frappe.db.count(doctype)
        
        # Get data with pagination
        data = frappe.get_all(
            doctype,
            fields=fields_to_fetch,
            order_by='creation desc',
            limit=limit,
            start=offset
        )
        
        # Calculate has_more (ensure all are integers)
        has_more = (int(offset) + int(limit)) < int(total_count)
        
        return {
            'success': True,
            'data': data,
            'total_count': int(total_count),
            'has_more': has_more
        }
        
    except Exception as e:
        frappe.log_error(f"Error getting reference data list for {data_type}: {str(e)}\n{frappe.get_traceback()}", "Reference Data List Error")
        return {'success': False, 'message': str(e)}
    
@frappe.whitelist()
def process_single_invoice_for_reference_data(token, invoice_id, source_type='purchase'):
    """Обрабатывает один инвойс и создает все 4 типа доктайпов"""
    record_etaxes_activity()
    
    try:
        # Получаем детали инвойса
        invoice_details = get_invoice_details(token, invoice_id)
        
        # Проверяем на ошибку unauthorized и пробуем обновить токен
        if isinstance(invoice_details, dict) and invoice_details.get('error') == 'unauthorized':
            # Пробуем получить новый токен из Asan Login
            token_result = refresh_token_from_asan_login()
            if token_result.get('success'):
                new_token = token_result.get('token')
                # Повторяем запрос с новым токеном
                invoice_details = get_invoice_details(new_token, invoice_id)
                
                # Если все еще ошибка - возвращаем с обновленным токеном для клиента
                if isinstance(invoice_details, dict) and 'error' in invoice_details:
                    return {
                        'success': False,
                        'error': invoice_details.get('error'),
                        'message': invoice_details.get('message', 'Failed to get invoice details'),
                        'new_token': new_token  # Передаем новый токен клиенту
                    }
            else:
                # Не удалось обновить токен
                return {
                    'success': False,
                    'error': 'unauthorized',
                    'message': 'Authentication required. Please login again.'
                }

        # Если другая ошибка (не unauthorized)
        if isinstance(invoice_details, dict) and 'error' in invoice_details:
            return {
                'success': False,
                'error': invoice_details.get('error'),
                'message': invoice_details.get('message', 'Failed to get invoice details')
            }

        # Создаем все доктайпы из одного инвойса
        result = create_reference_data_from_single_invoice(invoice_details, source_type)
        
        return result
        
    except Exception as e:
        frappe.log_error(f"Error in process_single_invoice_for_reference_data: {str(e)}", "Process Single Invoice Error")
        return {
            'success': False,
            'message': "An unknown error occurred processing this invoice"
        }
        
@frappe.whitelist()
def create_reference_data_from_single_invoice(invoice_details, source_type):
    """Создает все 4 типа доктайпов из одного инвойса с EQM кодами - оптимизированная версия"""
    try:
        stats = {
            'items_created': 0,
            'items_skipped': 0,
            'units_created': 0, 
            'units_skipped': 0,
            'customers_created': 0,
            'customers_skipped': 0,
            'suppliers_created': 0,
            'suppliers_skipped': 0
        }
        
        serial_number = invoice_details.get('serialNumber', '')
        items = invoice_details.get('items', [])
        sender = invoice_details.get('sender', {})
        receiver = invoice_details.get('receiver', {})
        
        # === СОЗДАНИЕ ТОВАРОВ С EQM КОДАМИ ===
        processed_items = set()
        for item in items:
            item_name = item.get('productName', '').strip()
            if not item_name:
                continue
                
            # Обрезаем до 140 символов
            if len(item_name) > 140:
                item_name = item_name[:140]
            
            if item_name in processed_items:
                continue
                
            processed_items.add(item_name)
            
            # Проверяем существование
            if frappe.db.exists('E-Taxes Item', item_name):
                stats['items_skipped'] += 1
                continue
            
            try:
                # Создаем товар
                item_code = item.get('itemId', '') or f"CODE-{serial_number}"
                
                # Обрабатываем группу товаров
                product_group = item.get('productGroup', {})
                product_group_code = product_group.get('code', '') if product_group else ''
                product_group_type = product_group.get('type', '') if product_group else ''
                
                # Получаем название группы товаров
                product_group_name = ''
                if product_group and product_group.get('name'):
                    names = product_group.get('name', {})
                    if isinstance(names, dict):
                        product_group_name = names.get('az', '') or names.get('en', '') or names.get('ru', '')
                    elif isinstance(names, str):
                        product_group_name = names
                
                # Обрезаем название группы товаров до 1000 символов
                if len(product_group_name) > 1000:
                    product_group_name = product_group_name[:1000]
                
                # НОВОЕ: Поиск EQM Code по коду группы товаров - возвращаем name для Link поля
                eqm_code = None
                if product_group_code:
                    try:
                        frappe.log_error(f"[EQM DEBUG] Searching for product_group_code: '{product_group_code}'", "EQM Code Search")
                        
                        # Ищем запись и получаем и name, и eqm_name
                        eqm_record = frappe.db.sql("""
                            SELECT name, eqm_name 
                            FROM `tabEQM Codes` 
                            WHERE eqm_name LIKE %s
                            LIMIT 1
                        """, (f"{product_group_code} -%",), as_dict=True)
                        
                        frappe.log_error(f"[EQM DEBUG] SQL search result: {eqm_record}", "EQM Code Search")
                        
                        if eqm_record:
                            # ИСПРАВЛЕНИЕ: Сохраняем name (ID записи) для Link поля, а не eqm_name
                            eqm_code = eqm_record[0].get('name')  # Изменено с eqm_name на name
                            frappe.log_error(f"[EQM DEBUG] Found EQM Code ID: '{eqm_code}' (description: '{eqm_record[0].get('eqm_name')}') for product group '{product_group_code}' in item {item_name}", "EQM Code Assignment")
                        else:
                            frappe.log_error(f"[EQM DEBUG] EQM Code not found for product group '{product_group_code}' in item {item_name}", "EQM Code Not Found")
                                
                    except Exception as e:
                        frappe.log_error(f"[EQM DEBUG] Error searching EQM Code for '{product_group_code}': {str(e)}", "EQM Code Search Error")

                # Логируем финальное значение
                frappe.log_error(f"[EQM DEBUG] Final eqm_code ID to save: '{eqm_code}' for item {item_name}", "EQM Code Final")

                # Логируем финальное значение
                frappe.log_error(f"[EQM DEBUG] Final eqm_code value to save: '{eqm_code}' for item {item_name}", "EQM Code Final")
                
                # Определяем, является ли товар услугой
                is_service = 1 if product_group_type == 'service' else 0
                
                # Определяем источники
                is_from_purchase = 1 if source_type == 'purchase' else 0
                is_from_sales = 1 if source_type == 'sales' else 0
                
                # Создаем документ с EQM кодом
                doc_data = {
                    'doctype': 'E-Taxes Item',
                    'etaxes_item_name': item_name,
                    'etaxes_item_code': item_code,
                    'etaxes_unit': item.get('unit', ''),
                    'etaxes_price': item.get('pricePerUnit', 0),
                    'etaxes_product_group_code': product_group_code,
                    'etaxes_product_group_name': product_group_name,
                    'etaxes_product_group_type': product_group_type,
                    'is_service_item': is_service,
                    'source_invoice': serial_number,
                    'is_from_purchase': is_from_purchase,
                    'is_from_sales': is_from_sales,
                    'status': 'New'
                }
                
                # НОВОЕ: Добавляем EQM код если найден
                if eqm_code:
                    doc_data['eqm_code'] = eqm_code
                
                doc = frappe.get_doc(doc_data)
                doc.insert(ignore_permissions=True, ignore_if_duplicate=True)
                stats['items_created'] += 1
                
            except frappe.DuplicateEntryError:
                stats['items_skipped'] += 1
            except Exception as e:
                frappe.log_error(f"Error creating E-Taxes Item {item_name}: {str(e)}", "Create Item Error")
        
        # === СОЗДАНИЕ ЕДИНИЦ ===
        processed_units = set()
        for item in items:
            unit_name = item.get('unit', '').strip()
            if not unit_name or unit_name in processed_units:
                continue
                
            processed_units.add(unit_name)
            
            # Проверяем существование
            if frappe.db.exists('E-Taxes Unit', unit_name):
                stats['units_skipped'] += 1
                continue
            
            try:
                source_type_text = 'Purchase' if source_type == 'purchase' else 'Sales'
                
                doc = frappe.get_doc({
                    'doctype': 'E-Taxes Unit',
                    'etaxes_unit_name': unit_name,
                    'etaxes_unit_code': unit_name,
                    'source_invoice': serial_number,
                    'source_type': source_type_text,
                    'status': 'New'
                })
                doc.insert(ignore_permissions=True, ignore_if_duplicate=True)
                stats['units_created'] += 1
                
            except frappe.DuplicateEntryError:
                stats['units_skipped'] += 1
            except Exception as e:
                frappe.log_error(f"Error creating E-Taxes Unit {unit_name}: {str(e)}", "Create Unit Error")
        
        # === СОЗДАНИЕ КЛИЕНТОВ (RECEIVER) ===
        if receiver:
            receiver_name = (receiver.get('name') or '').strip()
            receiver_tin = (receiver.get('tin') or '').strip()
            receiver_address = (receiver.get('address') or '').strip()
            
            receiver_name = ' '.join(receiver_name.split())  # Убираем лишние пробелы
            
            if receiver_name and receiver_tin:
                if not frappe.db.exists('E-Taxes Customers', receiver_name):
                    try:
                        doc = frappe.get_doc({
                            'doctype': 'E-Taxes Customers',
                            'etaxes_party_name': receiver_name,
                            'etaxes_tax_id': receiver_tin,
                            'etaxes_address': receiver_address,
                            'etaxes_party_type': 'Receiver',
                            'source_invoice': serial_number,
                            'status': 'New'
                        })
                        doc.insert(ignore_permissions=True, ignore_if_duplicate=True)
                        stats['customers_created'] += 1
                        
                    except frappe.DuplicateEntryError:
                        stats['customers_skipped'] += 1
                    except Exception as e:
                        frappe.log_error(f"Error creating E-Taxes Customers {receiver_name}: {str(e)}", "Create Customer Error")
                else:
                    stats['customers_skipped'] += 1
        
        # === СОЗДАНИЕ ПОСТАВЩИКОВ/КЛИЕНТОВ (SENDER) ===
        if sender:
            sender_name = (sender.get('name') or '').strip()
            sender_tin = (sender.get('tin') or '').strip()
            sender_address = (sender.get('address') or '').strip()
            
            sender_name = ' '.join(sender_name.split())  # Убираем лишние пробелы
            
            if sender_name and sender_tin:
                if source_type == 'purchase':
                    # Sender = Supplier для purchase инвойсов
                    if not frappe.db.exists('E-Taxes Suppliers', sender_name):
                        try:
                            doc = frappe.get_doc({
                                'doctype': 'E-Taxes Suppliers',
                                'etaxes_party_name': sender_name,
                                'etaxes_tax_id': sender_tin,
                                'etaxes_address': sender_address,
                                'etaxes_party_type': 'Sender',
                                'source_invoice': serial_number,
                                'status': 'New'
                            })
                            doc.insert(ignore_permissions=True, ignore_if_duplicate=True)
                            stats['suppliers_created'] += 1
                            
                        except frappe.DuplicateEntryError:
                            stats['suppliers_skipped'] += 1
                        except Exception as e:
                            frappe.log_error(f"Error creating E-Taxes Suppliers {sender_name}: {str(e)}", "Create Supplier Error")
                    else:
                        stats['suppliers_skipped'] += 1
                else:
                    # Sender = Customer для sales инвойсов
                    if not frappe.db.exists('E-Taxes Customers', sender_name):
                        try:
                            doc = frappe.get_doc({
                                'doctype': 'E-Taxes Customers',
                                'etaxes_party_name': sender_name,
                                'etaxes_tax_id': sender_tin,
                                'etaxes_address': sender_address,
                                'etaxes_party_type': 'Sender',
                                'source_invoice': serial_number,
                                'status': 'New'
                            })
                            doc.insert(ignore_permissions=True, ignore_if_duplicate=True)
                            stats['customers_created'] += 1
                            
                        except frappe.DuplicateEntryError:
                            stats['customers_skipped'] += 1
                        except Exception as e:
                            frappe.log_error(f"Error creating E-Taxes Customers {sender_name}: {str(e)}", "Create Customer Error")
                    else:
                        stats['customers_skipped'] += 1
        
        return {
            'success': True,
            'stats': stats,
            'serial_number': serial_number
        }
        
    except Exception as e:
        frappe.log_error(f"Error creating reference data: {str(e)}\n{frappe.get_traceback()}", "Create Reference Data Error")
        return {
            'success': False,
            'message': str(e)
        }

# ===== НОВЫЕ PYTHON ФУНКЦИИ =====

@frappe.whitelist()
def load_single_invoice_batch(date_from, date_to, offset=0, max_count=200):
    """Загружает одну порцию инвойсов из inbox"""
    record_etaxes_activity()
    
    try:
        # Получаем настройки токена
        asan_login_settings = get_default_asan_login()
        if not asan_login_settings.get('found'):
            return {
                'success': False,
                'message': 'No Asan Login settings found'
            }

        token = asan_login_settings['main_token']
        
        # Преобразуем параметры
        try:
            max_count = int(max_count)
            if max_count != 200:  # Принудительно устанавливаем 200
                max_count = 200
        except (ValueError, TypeError):
            max_count = 200
            
        try:
            offset = int(offset)
        except (ValueError, TypeError):
            offset = 0

        # Настройки фильтров
        filters = {
            "actionOwner": None,
            "amountFrom": None,
            "amountTo": None,
            "creationDateFrom": date_from,
            "creationDateTo": date_to,
            "kinds": ["defaultInvoice", "agent", "resale", "recycling", "taxCodex163", 
                     "taxCodex177_5", "returnInvoice", "returnByAgent", "returnRecycled", 
                     "exportNoteInvoice", "exciseGoodsTransfer", "advanceInvoice"],
            "maxCount": max_count,
            "offset": offset,
            "productCode": None,
            "productName": None,
            "receiverName": None,
            "receiverTin": None,
            "senderName": None,
            "senderTin": None,
            "serialNumber": None,
            "sortAsc": True,
            "sortBy": "creationDate",
            "statuses": ["approved", "approvedBySystem"],
            "types": ["current", "corrected"]
        }

        # Получаем порцию инвойсов из inbox
        response = get_invoices(token, json.dumps(filters))
        
        if 'error' in response:
            return {
                'success': False,
                'error': response.get('error'),
                'message': response.get('message', 'Failed to retrieve invoices')
            }

        invoices = response.get('data', []) or response.get('invoices', [])
        hasMore = response.get('hasMore', False)
        total = response.get('total', 0)
        
        # Дополнительная проверка hasMore
        if not hasMore and total > 0:
            hasMore = (offset + max_count) < total

        return {
            'success': True,
            'invoices': invoices,
            'hasMore': hasMore,
            'token': token,
            'offset': offset,
            'total': total,
            'loaded': len(invoices)
        }

    except Exception as e:
        frappe.log_error(f"Error in load_single_invoice_batch: {str(e)}\n{frappe.get_traceback()}", "Load Invoice Batch Error")
        return {
            'success': False,
            'message': "An unknown error occurred, please try again in a few minutes."
        }

@frappe.whitelist()
def load_single_sales_invoice_batch(date_from, date_to, offset=0, max_count=200):
    """Загружает одну порцию инвойсов из outbox (sales)"""
    record_etaxes_activity()
    
    try:
        # Получаем настройки токена
        asan_login_settings = get_default_asan_login()
        if not asan_login_settings.get('found'):
            return {
                'success': False,
                'message': 'No Asan Login settings found'
            }

        token = asan_login_settings['main_token']
        
        # Преобразуем параметры
        try:
            max_count = int(max_count)
            if max_count != 200:  # Принудительно устанавливаем 200
                max_count = 200
        except (ValueError, TypeError):
            max_count = 200
            
        try:
            offset = int(offset)
        except (ValueError, TypeError):
            offset = 0

        # Настройки фильтров
        filters = {
            "actionOwner": None,
            "amountFrom": None,
            "amountTo": None,
            "creationDateFrom": date_from,
            "creationDateTo": date_to,
            "kinds": ["defaultInvoice", "agent", "resale", "recycling", "taxCodex163", 
                     "taxCodex177_5", "returnInvoice", "returnByAgent", "returnRecycled", 
                     "exportNoteInvoice", "exciseGoodsTransfer", "advanceInvoice"],
            "maxCount": max_count,
            "offset": offset,
            "productCode": None,
            "productName": None,
            "receiverName": None,
            "receiverTin": None,
            "senderName": None,
            "senderTin": None,
            "serialNumber": None,
            "sortAsc": True,
            "sortBy": "creationDate",
            "statuses": ["approved", "approvedBySystem"],
            "types": ["current", "corrected"]
        }

        # Получаем порцию инвойсов из outbox
        try:
            from .sales_api import get_sales_invoices
            response = get_sales_invoices(token, json.dumps(filters))
            
            if 'error' in response:
                return {
                    'success': False,
                    'error': response.get('error'),
                    'message': response.get('message', 'Failed to retrieve sales invoices')
                }

            invoices = response.get('data', []) or response.get('invoices', [])
            hasMore = response.get('hasMore', False)
            total = response.get('total', 0)
            
            # Дополнительная проверка hasMore
            if not hasMore and total > 0:
                hasMore = (offset + max_count) < total

            return {
                'success': True,
                'invoices': invoices,
                'hasMore': hasMore,
                'token': token,
                'offset': offset,
                'total': total,
                'loaded': len(invoices)
            }
            
        except ImportError:
            # Sales API недоступен
            return {
                'success': True,
                'invoices': [],
                'hasMore': False,
                'token': token,
                'offset': offset,
                'total': 0,
                'loaded': 0
            }

    except Exception as e:
        frappe.log_error(f"Error in load_single_sales_invoice_batch: {str(e)}\n{frappe.get_traceback()}", "Load Sales Invoice Batch Error")
        return {
            'success': False,
            'message': "An unknown error occurred, please try again in a few minutes."
        }

@frappe.whitelist()
def refresh_token_from_asan_login():
    """Обновляет токен из Asan Login"""
    try:
        asan_login = frappe.get_all('Asan Login',
                                   filters={'is_default': 1},
                                   fields=['name', 'main_token'],
                                   limit=1)

        if asan_login and asan_login[0].main_token:
            return {
                'success': True,
                'token': asan_login[0].main_token
            }
        else:
            return {
                'success': False,
                'message': 'No valid token found in Asan Login'
            }
    except Exception as e:
        return {
            'success': False,
            'message': str(e)
        }

@frappe.whitelist()
def test_product_groups_api(product_type="service"):
    """Test API request to get product groups from e-taxes

    Args:
        product_type: Type of products to fetch - "service" or "good"
    """
    # Записываем активность
    record_etaxes_activity()

    # Получаем токен из настроек
    asan_login_settings = get_default_asan_login()
    if not asan_login_settings.get('found'):
        return {
            'success': False,
            'message': 'No Asan Login settings found. Please configure authentication first.'
        }

    token = asan_login_settings.get('main_token')
    if not token:
        return {
            'success': False,
            'message': 'No authentication token found. Please authenticate first.'
        }

    # Валидация типа продукта
    if product_type not in ['service', 'good']:
        product_type = 'service'

    # URL для запроса
    url = f"https://new.e-taxes.gov.az/api/po/dictionary/public/v1/productGroups/find?queryText=&type={product_type}&scope=ALL"

    # Заголовки запроса
    headers = {
        "Accept": "application/json, text/plain, */*",
        "Content-Type": "application/json",
        "Cache-Control": "no-cache",
        "x-authorization": f"Bearer {token}"
    }

    try:
        # Выполняем GET запрос
        response = requests.get(url, headers=headers, timeout=30)

        # Проверяем статус код 500
        if response.status_code == 500:
            frappe.log_error(f"[TEST_PRODUCT_GROUPS] Server error 500", "E-Taxes Server Error")
            return {
                'success': False,
                'error': 'server_error',
                'message': 'Service temporarily unavailable. Please try again in a few minutes.',
                'status_code': 500
            }

        # Проверяем статус код 401
        if response.status_code == 401:
            frappe.log_error(f"[TEST_PRODUCT_GROUPS] Unauthorized 401", "E-Taxes Auth Error")
            return {
                'success': False,
                'error': 'unauthorized',
                'message': 'Authentication required. Please login again.',
                'status_code': 401
            }

        # Проверяем успешность запроса
        response.raise_for_status()

        # Получаем JSON данные
        data = response.json()

        # Логируем успешный ответ
        frappe.log_error(
            f"[TEST_PRODUCT_GROUPS] Success: received {len(data) if isinstance(data, list) else 'data'} items for type={product_type}",
            "E-Taxes Test API Success"
        )

        return {
            'success': True,
            'message': f'Successfully retrieved product groups (type: {product_type})',
            'data': data,
            'count': len(data) if isinstance(data, list) else None,
            'product_type': product_type
        }

    except requests.exceptions.HTTPError as e:
        frappe.log_error(
            f"[TEST_PRODUCT_GROUPS] HTTP error: {e.response.status_code} - {str(e)}",
            "E-Taxes Test API Error"
        )

        if e.response.status_code == 401:
            return {
                'success': False,
                'error': 'unauthorized',
                'message': 'Authentication required. Please login again.',
                'status_code': 401
            }
        elif e.response.status_code == 500:
            return {
                'success': False,
                'error': 'server_error',
                'message': 'Service temporarily unavailable. Please try again in a few minutes.',
                'status_code': 500
            }

        return {
            'success': False,
            'error': 'http_error',
            'message': f'HTTP Error: {str(e)}'
        }

    except requests.exceptions.Timeout:
        frappe.log_error(f"[TEST_PRODUCT_GROUPS] Request timeout", "E-Taxes Test API Error")
        return {
            'success': False,
            'error': 'timeout',
            'message': 'Request timeout. Please try again.'
        }

    except Exception as e:
        frappe.log_error(
            f"[TEST_PRODUCT_GROUPS] Unexpected error: {str(e)}\n{frappe.get_traceback()}",
            "E-Taxes Test API Error"
        )
        return {
            'success': False,
            'error': 'unknown_error',
            'message': f'An error occurred: {str(e)}'
        }
