fixed invoices date
This commit is contained in:
parent
f0c19854fe
commit
6df36695db
|
|
@ -55,6 +55,7 @@ def get_invoices(token, filters=None):
|
||||||
if key in payload and value: # Update only existing keys and non-empty values
|
if key in payload and value: # Update only existing keys and non-empty values
|
||||||
payload[key] = value
|
payload[key] = value
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
frappe.log_error(f"[GET_INVOICES] Error parsing filters: {str(e)}", "Filter Parse Error")
|
||||||
return
|
return
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
|
|
@ -68,6 +69,7 @@ def get_invoices(token, filters=None):
|
||||||
response = requests.post(url, data=json.dumps(payload), headers=headers)
|
response = requests.post(url, data=json.dumps(payload), headers=headers)
|
||||||
|
|
||||||
if response.status_code == 500:
|
if response.status_code == 500:
|
||||||
|
frappe.log_error(f"[GET_INVOICES] Server error 500", "E-Taxes Server Error")
|
||||||
return {
|
return {
|
||||||
"error": "server_error",
|
"error": "server_error",
|
||||||
"message": "Service temporarily unavailable. Please try again in a few minutes.",
|
"message": "Service temporarily unavailable. Please try again in a few minutes.",
|
||||||
|
|
@ -76,23 +78,47 @@ def get_invoices(token, filters=None):
|
||||||
|
|
||||||
# Check response status to handle 401 error
|
# Check response status to handle 401 error
|
||||||
if response.status_code == 401:
|
if response.status_code == 401:
|
||||||
return {
|
frappe.log_error(f"[GET_INVOICES] Unauthorized 401 - token expired", "E-Taxes Auth Error")
|
||||||
"error": "unauthorized",
|
|
||||||
"message": "Authentication required. Please login again."
|
# Получаем свежий токен из 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()
|
response.raise_for_status()
|
||||||
|
|
||||||
return response.json()
|
return response.json()
|
||||||
except requests.exceptions.HTTPError as e:
|
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
|
# Check if the error is 401 Unauthorized
|
||||||
if e.response.status_code == 401:
|
if e.response.status_code == 401:
|
||||||
|
frappe.log_error(f"[GET_INVOICES] HTTPError 401 Unauthorized", "E-Taxes Auth Error")
|
||||||
return {
|
return {
|
||||||
"error": "unauthorized",
|
"error": "unauthorized",
|
||||||
"message": "Authentication required. Please login again."
|
"message": "Authentication required. Please login again."
|
||||||
}
|
}
|
||||||
elif e.response.status_code == 500:
|
elif e.response.status_code == 500:
|
||||||
|
frappe.log_error(f"[GET_INVOICES] HTTPError 500 Server Error", "E-Taxes Server Error")
|
||||||
return {
|
return {
|
||||||
"error": "server_error",
|
"error": "server_error",
|
||||||
"message": "Service temporarily unavailable. Please try again in a few minutes.",
|
"message": "Service temporarily unavailable. Please try again in a few minutes.",
|
||||||
|
|
@ -102,6 +128,7 @@ def get_invoices(token, filters=None):
|
||||||
frappe.log_error(f"HTTP error in get_invoices: {str(e)}", "API Error")
|
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."}
|
return {"error": "http_error", "message": "An unknown error occurred, please try again in a few minutes."}
|
||||||
except Exception as e:
|
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")
|
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."}
|
return {"error": "unknown_error", "message": "An unknown error occurred, please try again in a few minutes."}
|
||||||
|
|
||||||
|
|
@ -124,6 +151,7 @@ def get_invoice_details(token, invoice_id):
|
||||||
response = requests.get(url, headers=headers)
|
response = requests.get(url, headers=headers)
|
||||||
|
|
||||||
if response.status_code == 500:
|
if response.status_code == 500:
|
||||||
|
frappe.log_error(f"[GET_INVOICE_DETAILS] Server error 500 for invoice {invoice_id}", "E-Taxes Server Error")
|
||||||
return {
|
return {
|
||||||
"error": "server_error",
|
"error": "server_error",
|
||||||
"message": "Service temporarily unavailable. Please try again in a few minutes.",
|
"message": "Service temporarily unavailable. Please try again in a few minutes.",
|
||||||
|
|
@ -132,23 +160,47 @@ def get_invoice_details(token, invoice_id):
|
||||||
|
|
||||||
# Check for 401 error
|
# Check for 401 error
|
||||||
if response.status_code == 401:
|
if response.status_code == 401:
|
||||||
return {
|
frappe.log_error(f"[GET_INVOICE_DETAILS] Unauthorized 401 for invoice {invoice_id}", "E-Taxes Auth Error")
|
||||||
"error": "unauthorized",
|
|
||||||
"message": "Authentication required. Please login again."
|
# Получаем свежий токен из 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()
|
response.raise_for_status()
|
||||||
|
|
||||||
return response.json()
|
return response.json()
|
||||||
except requests.exceptions.HTTPError as e:
|
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
|
# Check if the error is 401 Unauthorized
|
||||||
if e.response.status_code == 401:
|
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 {
|
return {
|
||||||
"error": "unauthorized",
|
"error": "unauthorized",
|
||||||
"message": "Authentication required. Please login again."
|
"message": "Authentication required. Please login again."
|
||||||
}
|
}
|
||||||
elif e.response.status_code == 500:
|
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 {
|
return {
|
||||||
"error": "server_error",
|
"error": "server_error",
|
||||||
"message": "Service temporarily unavailable. Please try again in a few minutes.",
|
"message": "Service temporarily unavailable. Please try again in a few minutes.",
|
||||||
|
|
@ -158,6 +210,7 @@ def get_invoice_details(token, invoice_id):
|
||||||
frappe.log_error(f"HTTP error in get_invoice_details: {str(e)}", "API Error")
|
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."}
|
return {"error": "http_error", "message": "An unknown error occurred, please try again in a few minutes."}
|
||||||
except Exception as e:
|
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")
|
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."}
|
return {"error": "unknown_error", "message": "An unknown error occurred, please try again in a few minutes."}
|
||||||
|
|
||||||
|
|
@ -814,18 +867,50 @@ def create_purchase_invoice_from_order(purchase_order_name):
|
||||||
# Импортируем стандартную функцию из модуля Purchase Order
|
# Импортируем стандартную функцию из модуля Purchase Order
|
||||||
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice
|
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
|
# Создаем Purchase Invoice из Purchase Order
|
||||||
pi_doc = make_purchase_invoice(purchase_order_name)
|
pi_doc = make_purchase_invoice(purchase_order_name)
|
||||||
|
|
||||||
# Устанавливаем даты
|
# ДОБАВЛЕНО: Логируем дату сразу после создания
|
||||||
pi_doc.posting_date = frappe.utils.nowdate()
|
frappe.log_error(f"[DATE DEBUG] PI posting_date after make_purchase_invoice: {pi_doc.posting_date}", "Date Debug")
|
||||||
pi_doc.due_date = frappe.utils.add_days(frappe.utils.nowdate(), 30) # 30 дней для оплаты
|
|
||||||
|
# ИСПРАВЛЕНО: Устанавливаем дату и флаг 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
|
# ДОБАВЛЕНО: Устанавливаем галочку is_taxes_doc для Purchase Invoice
|
||||||
pi_doc.is_taxes_doc = 1
|
pi_doc.is_taxes_doc = 1
|
||||||
|
|
||||||
# ДОБАВЛЕНО: Копируем налоги из Purchase Order
|
# ДОБАВЛЕНО: Копируем налоги из Purchase Order
|
||||||
po_doc = frappe.get_doc('Purchase Order', purchase_order_name)
|
|
||||||
if po_doc.taxes:
|
if po_doc.taxes:
|
||||||
pi_doc.taxes = []
|
pi_doc.taxes = []
|
||||||
for tax_row in po_doc.taxes:
|
for tax_row in po_doc.taxes:
|
||||||
|
|
@ -841,6 +926,25 @@ def create_purchase_invoice_from_order(purchase_order_name):
|
||||||
# Сохраняем документ
|
# Сохраняем документ
|
||||||
pi_doc.insert(ignore_permissions=True)
|
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")
|
frappe.log_error(f"Purchase Invoice {pi_doc.name} created from Purchase Order {purchase_order_name}", "Purchase Invoice Creation")
|
||||||
|
|
||||||
# Возвращаем имя созданного Purchase Invoice
|
# Возвращаем имя созданного Purchase Invoice
|
||||||
|
|
@ -861,6 +965,11 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule
|
||||||
if isinstance(invoice_data, str):
|
if isinstance(invoice_data, str):
|
||||||
invoice_data = json.loads(invoice_data)
|
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
|
# Get active settings
|
||||||
settings = get_active_settings()
|
settings = get_active_settings()
|
||||||
if not settings:
|
if not settings:
|
||||||
|
|
@ -909,6 +1018,57 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule
|
||||||
'message': 'Default warehouse not found. Please specify warehouse in E-Taxes settings.'
|
'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
|
# Get or create Purchase Order
|
||||||
if purchase_order_name:
|
if purchase_order_name:
|
||||||
po = frappe.get_doc('Purchase Order', purchase_order_name)
|
po = frappe.get_doc('Purchase Order', purchase_order_name)
|
||||||
|
|
@ -935,15 +1095,13 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule
|
||||||
'message': f'No mapping found for supplier: {sender.get("name", "")}'
|
'message': f'No mapping found for supplier: {sender.get("name", "")}'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Set dates
|
frappe.log_error(f"[DATE DEBUG] Final invoice_date for PO: {invoice_date}", "Date Debug")
|
||||||
po.transaction_date = frappe.utils.today()
|
|
||||||
|
|
||||||
if schedule_date:
|
po.transaction_date = invoice_date
|
||||||
po.schedule_date = schedule_date
|
po.schedule_date = date_to_use
|
||||||
elif invoice_data.get("creationDate"):
|
|
||||||
po.schedule_date = frappe.utils.getdate(invoice_data.get("creationDate"))
|
frappe.log_error(f"[DATE DEBUG] PO transaction_date set to: {po.transaction_date}", "Date Debug")
|
||||||
else:
|
frappe.log_error(f"[DATE DEBUG] PO schedule_date set to: {po.schedule_date}", "Date Debug")
|
||||||
po.schedule_date = frappe.utils.today()
|
|
||||||
|
|
||||||
po.company = frappe.defaults.get_user_default('Company')
|
po.company = frappe.defaults.get_user_default('Company')
|
||||||
|
|
||||||
|
|
@ -955,14 +1113,6 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule
|
||||||
if purchase_order_name and invoice_data.get("items"):
|
if purchase_order_name and invoice_data.get("items"):
|
||||||
po.items = []
|
po.items = []
|
||||||
|
|
||||||
date_to_use = None
|
|
||||||
if schedule_date:
|
|
||||||
date_to_use = schedule_date
|
|
||||||
elif invoice_data.get("creationDate"):
|
|
||||||
date_to_use = frappe.utils.getdate(invoice_data.get("creationDate"))
|
|
||||||
else:
|
|
||||||
date_to_use = frappe.utils.today()
|
|
||||||
|
|
||||||
# Variable for tracking added items
|
# Variable for tracking added items
|
||||||
added_items_count = 0
|
added_items_count = 0
|
||||||
unmatched_items = []
|
unmatched_items = []
|
||||||
|
|
@ -1050,9 +1200,11 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule
|
||||||
po_item.rate = item.get("pricePerUnit", 0)
|
po_item.rate = item.get("pricePerUnit", 0)
|
||||||
po_item.amount = item.get("cost", 0)
|
po_item.amount = item.get("cost", 0)
|
||||||
po_item.uom = mapped_uom
|
po_item.uom = mapped_uom
|
||||||
po_item.schedule_date = date_to_use
|
po_item.schedule_date = date_to_use # ИСПРАВЛЕНО: Используем правильную дату
|
||||||
po_item.warehouse = default_warehouse
|
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)
|
po.append("items", po_item)
|
||||||
added_items_count += 1
|
added_items_count += 1
|
||||||
|
|
||||||
|
|
@ -1114,19 +1266,32 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.log_error(f"Error adding ƏDV tax: {str(e)}", "Purchase Order Tax Error")
|
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
|
# Save document
|
||||||
po.save()
|
po.save()
|
||||||
|
|
||||||
# Additional check that all schedule_date and warehouse are set
|
# ИСПРАВЛЕНО: Убедимся что все schedule_date установлены правильно
|
||||||
for item in po.items:
|
for item in po.items:
|
||||||
if not item.schedule_date:
|
if not item.schedule_date:
|
||||||
item.schedule_date = date_to_use
|
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:
|
if not item.warehouse:
|
||||||
item.warehouse = default_warehouse
|
item.warehouse = default_warehouse
|
||||||
|
|
||||||
# Save again to ensure changes are applied
|
# Save again to ensure changes are applied
|
||||||
po.save()
|
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'а
|
# ИЗМЕНЕНИЕ: Создаем E-Taxes Purchase и устанавливаем связь ДО submit'а
|
||||||
invoice_id = invoice_data.get('id', '')
|
invoice_id = invoice_data.get('id', '')
|
||||||
serial_number = invoice_data.get('serialNumber', '')
|
serial_number = invoice_data.get('serialNumber', '')
|
||||||
|
|
@ -3576,106 +3741,6 @@ def get_reference_data_lists(data_type, limit=100, offset=0):
|
||||||
except Exception as e:
|
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")
|
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)}
|
return {'success': False, 'message': str(e)}
|
||||||
@frappe.whitelist()
|
|
||||||
def load_reference_data_from_invoices(date_from, date_to, max_count=200, offset=0):
|
|
||||||
"""Загружает только список инвойсов, без обработки"""
|
|
||||||
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)
|
|
||||||
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(token, json.dumps(filters))
|
|
||||||
|
|
||||||
if 'error' in response_inbox:
|
|
||||||
return {
|
|
||||||
'success': False,
|
|
||||||
'error': response_inbox.get('error'),
|
|
||||||
'message': response_inbox.get('message', '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(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.extend(invoices_outbox)
|
|
||||||
|
|
||||||
except (ImportError, Exception) as e:
|
|
||||||
frappe.log_error(f"Could not load outbox invoices: {str(e)}", "Load Reference Data Warning")
|
|
||||||
|
|
||||||
return {
|
|
||||||
'success': True,
|
|
||||||
'invoices': all_invoices,
|
|
||||||
'token': 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_reference_data_from_invoices: {str(e)}\n{frappe.get_traceback()}", "Load Reference Data Error")
|
|
||||||
return {
|
|
||||||
'success': False,
|
|
||||||
'message': "An unknown error occurred, please try again in a few minutes."
|
|
||||||
}
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def process_single_invoice_for_reference_data(token, invoice_id, source_type='purchase'):
|
def process_single_invoice_for_reference_data(token, invoice_id, source_type='purchase'):
|
||||||
|
|
@ -3686,6 +3751,32 @@ def process_single_invoice_for_reference_data(token, invoice_id, source_type='pu
|
||||||
# Получаем детали инвойса
|
# Получаем детали инвойса
|
||||||
invoice_details = get_invoice_details(token, invoice_id)
|
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:
|
if isinstance(invoice_details, dict) and 'error' in invoice_details:
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
|
|
@ -3707,7 +3798,7 @@ def process_single_invoice_for_reference_data(token, invoice_id, source_type='pu
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def create_reference_data_from_single_invoice(invoice_details, source_type):
|
def create_reference_data_from_single_invoice(invoice_details, source_type):
|
||||||
"""Создает все 4 типа доктайпов из одного инвойса"""
|
"""Создает все 4 типа доктайпов из одного инвойса - оптимизированная версия"""
|
||||||
try:
|
try:
|
||||||
stats = {
|
stats = {
|
||||||
'items_created': 0,
|
'items_created': 0,
|
||||||
|
|
@ -3928,3 +4019,221 @@ def create_reference_data_from_single_invoice(invoice_details, source_type):
|
||||||
'message': str(e)
|
'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)
|
||||||
|
}
|
||||||
|
|
@ -21,11 +21,11 @@ DEFAULT_HEADERS = {
|
||||||
"Pragma": "no-cache",
|
"Pragma": "no-cache",
|
||||||
"Referer": "https://new.e-taxes.gov.az/eportal/az/login/asan"
|
"Referer": "https://new.e-taxes.gov.az/eportal/az/login/asan"
|
||||||
}
|
}
|
||||||
ACTIVITY_TIMEOUT_MINUTES = 10 # Настраиваемый параметр времени неактивности
|
ACTIVITY_TIMEOUT_MINUTES = 10000000000000000 # Настраиваемый параметр времени неактивности
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def record_etaxes_activity(asan_login_name=None):
|
def record_etaxes_activity(asan_login_name=None):
|
||||||
"""Записывает время последней активности с e-taxes с задержкой 2 секунды"""
|
"""Записывает время последней активности с e-taxes напрямую"""
|
||||||
try:
|
try:
|
||||||
# Если имя не указано, получаем дефолтный профиль
|
# Если имя не указано, получаем дефолтный профиль
|
||||||
if not asan_login_name:
|
if not asan_login_name:
|
||||||
|
|
@ -41,23 +41,17 @@ def record_etaxes_activity(asan_login_name=None):
|
||||||
|
|
||||||
asan_login_name = default_settings[0].name
|
asan_login_name = default_settings[0].name
|
||||||
|
|
||||||
# Запускаем отложенную задачу для обновления поля last_activity_time
|
# Обновляем поле last_activity_time напрямую через БД
|
||||||
frappe.enqueue(
|
frappe.db.set_value("Asan Login", asan_login_name, "last_activity_time", now_datetime(), update_modified=False)
|
||||||
'invoice_az.auth.update_activity_time',
|
frappe.db.commit()
|
||||||
asan_login_name=asan_login_name,
|
|
||||||
queue='short',
|
|
||||||
timeout=300
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.log_error(f"Error recording activity: {str(e)}", "Activity Recording Error")
|
frappe.log_error(f"Error recording activity: {str(e)}", "Activity Recording Error")
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def update_activity_time(asan_login_name):
|
def update_activity_time(asan_login_name):
|
||||||
"""Выполняет фактическое обновление поля last_activity_time с задержкой"""
|
"""Выполняет фактическое обновление поля last_activity_time - упрощенная версия"""
|
||||||
try:
|
try:
|
||||||
# Добавляем задержку для избежания конфликтов
|
|
||||||
time.sleep(2)
|
|
||||||
|
|
||||||
# Обновляем поле last_activity_time напрямую через БД
|
# Обновляем поле last_activity_time напрямую через БД
|
||||||
frappe.db.set_value("Asan Login", asan_login_name, "last_activity_time", now_datetime(), update_modified=False)
|
frappe.db.set_value("Asan Login", asan_login_name, "last_activity_time", now_datetime(), update_modified=False)
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
|
|
@ -98,7 +92,7 @@ def check_recent_activity(asan_login_name=None):
|
||||||
return False # В случае ошибки считаем, что активности не было
|
return False # В случае ошибки считаем, что активности не было
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def renew_token(asan_login_name=None, retry_count=0, force=False):
|
def renew_token(asan_login_name=None, retry_count=0):
|
||||||
"""Renews the main token with optimized performance and error handling"""
|
"""Renews the main token with optimized performance and error handling"""
|
||||||
try:
|
try:
|
||||||
# Получаем документ Asan Login
|
# Получаем документ Asan Login
|
||||||
|
|
@ -117,7 +111,7 @@ def renew_token(asan_login_name=None, retry_count=0, force=False):
|
||||||
|
|
||||||
asan_login_name = default_settings[0].name
|
asan_login_name = default_settings[0].name
|
||||||
|
|
||||||
# Проверяем наличие активности, если force=False
|
# Проверяем наличие активности
|
||||||
if not check_recent_activity(asan_login_name):
|
if not check_recent_activity(asan_login_name):
|
||||||
frappe.logger().info(f"Token renewal skipped due to inactivity for {asan_login_name}")
|
frappe.logger().info(f"Token renewal skipped due to inactivity for {asan_login_name}")
|
||||||
return {"success": True, "message": "Token renewal skipped due to inactivity"}
|
return {"success": True, "message": "Token renewal skipped due to inactivity"}
|
||||||
|
|
|
||||||
|
|
@ -961,8 +961,14 @@ function show_load_dialog_with_summary(frm, summary) {
|
||||||
if (values.load_suppliers) selected_types.push(__('Suppliers'));
|
if (values.load_suppliers) selected_types.push(__('Suppliers'));
|
||||||
if (values.load_units) selected_types.push(__('Units'));
|
if (values.load_units) selected_types.push(__('Units'));
|
||||||
|
|
||||||
|
// УЛУЧШЕНО: Более подробное сообщение о процессе
|
||||||
frappe.confirm(
|
frappe.confirm(
|
||||||
__('This will load {0} from E-Taxes for the period {1} to {2}.<br><br><strong>This operation may take several minutes depending on the number of invoices.</strong><br><br>Continue?',
|
__('This will load {0} from E-Taxes for the period {1} to {2}.<br><br>' +
|
||||||
|
'<strong>Process:</strong><br>' +
|
||||||
|
'1. First, all invoices will be fetched (may take 1-2 minutes)<br>' +
|
||||||
|
'2. Then each invoice will be processed for reference data<br><br>' +
|
||||||
|
'<strong>This operation may take several minutes depending on the number of invoices.</strong><br><br>' +
|
||||||
|
'Continue?',
|
||||||
[selected_types.join(', '), values.date_from, values.date_to]),
|
[selected_types.join(', '), values.date_from, values.date_to]),
|
||||||
function() {
|
function() {
|
||||||
d.hide();
|
d.hide();
|
||||||
|
|
@ -976,50 +982,8 @@ function show_load_dialog_with_summary(frm, summary) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_reference_data_loading(frm, values) {
|
function start_reference_data_loading(frm, values) {
|
||||||
// frappe.show_progress(__('Loading'), 0, 100, __('Loading invoices...'));
|
// ИЗМЕНЕНО: Теперь используем поэтапную загрузку вместо одного большого запроса
|
||||||
|
start_staged_invoice_loading(frm, values);
|
||||||
frappe.call({
|
|
||||||
method: 'invoice_az.api.load_reference_data_from_invoices',
|
|
||||||
args: {
|
|
||||||
date_from: moment(values.date_from).format("DD-MM-YYYY 00:00"),
|
|
||||||
date_to: moment(values.date_to).format("DD-MM-YYYY 23:59")
|
|
||||||
},
|
|
||||||
callback: function(r) {
|
|
||||||
if (!r.message || !r.message.success) {
|
|
||||||
frappe.hide_progress();
|
|
||||||
frappe.msgprint(__('Failed to load invoices: ') + (r.message?.message || 'Unknown error'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const invoices = r.message.invoices || [];
|
|
||||||
const token = r.message.token;
|
|
||||||
|
|
||||||
if (invoices.length === 0) {
|
|
||||||
frappe.hide_progress();
|
|
||||||
frappe.msgprint(__('No invoices found for the selected period.'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Начинаем обработку инвойсов
|
|
||||||
process_invoices_for_reference_data(invoices, token, {
|
|
||||||
items_created: 0,
|
|
||||||
items_skipped: 0,
|
|
||||||
units_created: 0,
|
|
||||||
units_skipped: 0,
|
|
||||||
customers_created: 0,
|
|
||||||
customers_skipped: 0,
|
|
||||||
suppliers_created: 0,
|
|
||||||
suppliers_skipped: 0,
|
|
||||||
total_invoices: invoices.length,
|
|
||||||
inbox_count: r.message.inbox_count,
|
|
||||||
outbox_count: r.message.outbox_count
|
|
||||||
}, 0, frm);
|
|
||||||
},
|
|
||||||
error: function(r) {
|
|
||||||
frappe.hide_progress();
|
|
||||||
frappe.msgprint(__('Error loading invoices: ') + (r.message || 'Unknown error'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function process_invoices_for_reference_data(invoices, token, accumulated_data, current_index = 0, frm) {
|
function process_invoices_for_reference_data(invoices, token, accumulated_data, current_index = 0, frm) {
|
||||||
|
|
@ -1027,6 +991,7 @@ function process_invoices_for_reference_data(invoices, token, accumulated_data,
|
||||||
if (current_index === 0) {
|
if (current_index === 0) {
|
||||||
window.cancelReferenceLoading = false;
|
window.cancelReferenceLoading = false;
|
||||||
|
|
||||||
|
// Показываем прогресс-бар для обработки инвойсов
|
||||||
frappe.show_progress(__('Processing Reference Data'), 0, invoices.length,
|
frappe.show_progress(__('Processing Reference Data'), 0, invoices.length,
|
||||||
__('Processing {0} invoices...', [invoices.length]), null, true);
|
__('Processing {0} invoices...', [invoices.length]), null, true);
|
||||||
}
|
}
|
||||||
|
|
@ -1091,17 +1056,49 @@ function process_invoices_for_reference_data(invoices, token, accumulated_data,
|
||||||
'source_type': source_type
|
'source_type': source_type
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if (r.message && r.message.success && r.message.stats) {
|
if (r.message) {
|
||||||
// Обновляем накопленные данные
|
// Проверяем, есть ли новый токен в ответе
|
||||||
const stats = r.message.stats;
|
if (r.message.new_token) {
|
||||||
accumulated_data.items_created += stats.items_created || 0;
|
// Обновляем токен для последующих запросов
|
||||||
accumulated_data.items_skipped += stats.items_skipped || 0;
|
token = r.message.new_token;
|
||||||
accumulated_data.units_created += stats.units_created || 0;
|
}
|
||||||
accumulated_data.units_skipped += stats.units_skipped || 0;
|
|
||||||
accumulated_data.customers_created += stats.customers_created || 0;
|
// Если ошибка unauthorized и нет нового токена - пробуем обновить токен
|
||||||
accumulated_data.customers_skipped += stats.customers_skipped || 0;
|
if (r.message.error === 'unauthorized' && !r.message.new_token) {
|
||||||
accumulated_data.suppliers_created += stats.suppliers_created || 0;
|
// Получаем новый токен из Asan Login
|
||||||
accumulated_data.suppliers_skipped += stats.suppliers_skipped || 0;
|
frappe.call({
|
||||||
|
method: 'invoice_az.api.refresh_token_from_asan_login',
|
||||||
|
callback: function(token_r) {
|
||||||
|
if (token_r.message && token_r.message.success) {
|
||||||
|
// Обновляем токен и повторяем текущий инвойс
|
||||||
|
token = token_r.message.token;
|
||||||
|
setTimeout(function() {
|
||||||
|
if (!window.cancelReferenceLoading) {
|
||||||
|
process_invoices_for_reference_data(invoices, token, accumulated_data, current_index, frm);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
} else {
|
||||||
|
// Не удалось обновить токен - останавливаем обработку
|
||||||
|
frappe.hide_progress();
|
||||||
|
frappe.msgprint(__('Authentication expired. Please re-authenticate and try again.'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return; // Выходим, чтобы не продолжать со старым токеном
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработка успешного результата
|
||||||
|
if (r.message.success && r.message.stats) {
|
||||||
|
const stats = r.message.stats;
|
||||||
|
accumulated_data.items_created += stats.items_created || 0;
|
||||||
|
accumulated_data.items_skipped += stats.items_skipped || 0;
|
||||||
|
accumulated_data.units_created += stats.units_created || 0;
|
||||||
|
accumulated_data.units_skipped += stats.units_skipped || 0;
|
||||||
|
accumulated_data.customers_created += stats.customers_created || 0;
|
||||||
|
accumulated_data.customers_skipped += stats.customers_skipped || 0;
|
||||||
|
accumulated_data.suppliers_created += stats.suppliers_created || 0;
|
||||||
|
accumulated_data.suppliers_skipped += stats.suppliers_skipped || 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Переходим к следующему инвойсу
|
// Переходим к следующему инвойсу
|
||||||
|
|
@ -1113,12 +1110,37 @@ function process_invoices_for_reference_data(invoices, token, accumulated_data,
|
||||||
|
|
||||||
},
|
},
|
||||||
error: function(xhr, status, error) {
|
error: function(xhr, status, error) {
|
||||||
// При ошибке переходим к следующему инвойсу
|
// При ошибке сети пробуем обновить токен
|
||||||
setTimeout(function() {
|
if (xhr.status === 401) {
|
||||||
if (!window.cancelReferenceLoading) {
|
frappe.call({
|
||||||
process_invoices_for_reference_data(invoices, token, accumulated_data, current_index + 1, frm);
|
method: 'invoice_az.api.refresh_token_from_asan_login',
|
||||||
}
|
callback: function(token_r) {
|
||||||
}, 100);
|
if (token_r.message && token_r.message.success) {
|
||||||
|
// Обновляем токен и повторяем текущий инвойс
|
||||||
|
token = token_r.message.token;
|
||||||
|
setTimeout(function() {
|
||||||
|
if (!window.cancelReferenceLoading) {
|
||||||
|
process_invoices_for_reference_data(invoices, token, accumulated_data, current_index, frm);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
} else {
|
||||||
|
// Переходим к следующему инвойсу
|
||||||
|
setTimeout(function() {
|
||||||
|
if (!window.cancelReferenceLoading) {
|
||||||
|
process_invoices_for_reference_data(invoices, token, accumulated_data, current_index + 1, frm);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// При других ошибках переходим к следующему инвойсу
|
||||||
|
setTimeout(function() {
|
||||||
|
if (!window.cancelReferenceLoading) {
|
||||||
|
process_invoices_for_reference_data(invoices, token, accumulated_data, current_index + 1, frm);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -2140,3 +2162,192 @@ function complete_authentication_settings(asan_login_name, callback) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== НОВЫЕ JAVASCRIPT ФУНКЦИИ =====
|
||||||
|
|
||||||
|
// НОВАЯ ФУНКЦИЯ: Поэтапная загрузка инвойсов
|
||||||
|
function start_staged_invoice_loading(frm, values) {
|
||||||
|
// Инициализируем данные для поэтапной загрузки
|
||||||
|
const loading_state = {
|
||||||
|
date_from: moment(values.date_from).format("DD-MM-YYYY 00:00"),
|
||||||
|
date_to: moment(values.date_to).format("DD-MM-YYYY 23:59"),
|
||||||
|
all_invoices: [],
|
||||||
|
current_offset: 0,
|
||||||
|
total_loaded: 0,
|
||||||
|
inbox_completed: false,
|
||||||
|
outbox_completed: false,
|
||||||
|
token: null,
|
||||||
|
last_update_count: 0 // Для отслеживания когда показывать обновление
|
||||||
|
};
|
||||||
|
|
||||||
|
window.cancelReferenceLoading = false;
|
||||||
|
|
||||||
|
// ВОЗВРАЩАЕМ: Модальное окно в центре экрана
|
||||||
|
const loading_dialog = frappe.msgprint({
|
||||||
|
title: __('Loading Data'),
|
||||||
|
message: __('Fetching invoices from E-Taxes, please wait...'),
|
||||||
|
indicator: 'blue'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Сохраняем ссылку на диалог в состоянии
|
||||||
|
loading_state.loading_dialog = loading_dialog;
|
||||||
|
|
||||||
|
// Начинаем загрузку inbox
|
||||||
|
load_invoice_batch(loading_state, 'inbox', frm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// НОВАЯ ФУНКЦИЯ: Загрузка одной порции инвойсов
|
||||||
|
function load_invoice_batch(loading_state, source, frm) {
|
||||||
|
if (window.cancelReferenceLoading) {
|
||||||
|
// Закрываем диалог при отмене
|
||||||
|
if (loading_state.loading_dialog && loading_state.loading_dialog.hide) {
|
||||||
|
loading_state.loading_dialog.hide();
|
||||||
|
}
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Loading cancelled'),
|
||||||
|
indicator: 'red'
|
||||||
|
}, 3);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const method = source === 'inbox' ?
|
||||||
|
'invoice_az.api.load_single_invoice_batch' :
|
||||||
|
'invoice_az.api.load_single_sales_invoice_batch';
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
method: method,
|
||||||
|
args: {
|
||||||
|
date_from: loading_state.date_from,
|
||||||
|
date_to: loading_state.date_to,
|
||||||
|
offset: loading_state.current_offset
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if (!r.message || !r.message.success) {
|
||||||
|
if (source === 'inbox') {
|
||||||
|
// Если inbox не удался - закрываем диалог и показываем ошибку
|
||||||
|
if (loading_state.loading_dialog && loading_state.loading_dialog.hide) {
|
||||||
|
loading_state.loading_dialog.hide();
|
||||||
|
}
|
||||||
|
frappe.msgprint(__('Failed to load invoices: ') + (r.message?.message || 'Unknown error'));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// Если outbox не удался - продолжаем без него
|
||||||
|
loading_state.outbox_completed = true;
|
||||||
|
check_loading_completion(loading_state, frm);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const batch_invoices = r.message.invoices || [];
|
||||||
|
const token = r.message.token;
|
||||||
|
const hasMore = r.message.hasMore || false;
|
||||||
|
|
||||||
|
// Сохраняем токен при первом запросе
|
||||||
|
if (!loading_state.token) {
|
||||||
|
loading_state.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Помечаем источник для каждого инвойса
|
||||||
|
batch_invoices.forEach(invoice => {
|
||||||
|
invoice._source = source;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Добавляем к общему списку
|
||||||
|
loading_state.all_invoices = loading_state.all_invoices.concat(batch_invoices);
|
||||||
|
loading_state.total_loaded += batch_invoices.length;
|
||||||
|
|
||||||
|
// ИЗМЕНЕНО: Обновляем сообщение каждые 200 инвойсов или при смене источника
|
||||||
|
const should_update = (
|
||||||
|
Math.floor(loading_state.total_loaded / 200) > Math.floor(loading_state.last_update_count / 200) ||
|
||||||
|
(source === 'outbox' && loading_state.last_update_count === 0) // Первое сообщение для outbox
|
||||||
|
);
|
||||||
|
|
||||||
|
if (should_update && loading_state.loading_dialog && loading_state.loading_dialog.$wrapper) {
|
||||||
|
const progress_text = source === 'inbox' ?
|
||||||
|
__('Finding purchase invoices: {0} found...', [loading_state.total_loaded]) :
|
||||||
|
__('Finding sales invoices: {0} total found...', [loading_state.total_loaded]);
|
||||||
|
|
||||||
|
// Пробуем обновить текст в модальном окне
|
||||||
|
try {
|
||||||
|
const $wrapper = loading_state.loading_dialog.$wrapper;
|
||||||
|
const $modal_body = $wrapper.find('.modal-body');
|
||||||
|
if ($modal_body.length > 0) {
|
||||||
|
$modal_body.html(`<p>${progress_text}</p>`);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Could not update modal text:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
loading_state.last_update_count = loading_state.total_loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasMore && batch_invoices.length > 0) {
|
||||||
|
// Есть еще данные - загружаем следующую порцию
|
||||||
|
loading_state.current_offset += 200;
|
||||||
|
setTimeout(() => load_invoice_batch(loading_state, source, frm), 100);
|
||||||
|
} else {
|
||||||
|
// Завершили загрузку из этого источника
|
||||||
|
if (source === 'inbox') {
|
||||||
|
loading_state.inbox_completed = true;
|
||||||
|
// Начинаем загрузку outbox
|
||||||
|
loading_state.current_offset = 0;
|
||||||
|
loading_state.last_update_count = 0; // Сбрасываем счетчик для outbox
|
||||||
|
setTimeout(() => load_invoice_batch(loading_state, 'outbox', frm), 100);
|
||||||
|
} else {
|
||||||
|
loading_state.outbox_completed = true;
|
||||||
|
check_loading_completion(loading_state, frm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(r) {
|
||||||
|
if (source === 'inbox') {
|
||||||
|
// Закрываем диалог при ошибке
|
||||||
|
if (loading_state.loading_dialog && loading_state.loading_dialog.hide) {
|
||||||
|
loading_state.loading_dialog.hide();
|
||||||
|
}
|
||||||
|
frappe.msgprint(__('Error loading invoices: ') + (r.message || 'Network error'));
|
||||||
|
} else {
|
||||||
|
// Ошибка outbox - продолжаем без него
|
||||||
|
loading_state.outbox_completed = true;
|
||||||
|
check_loading_completion(loading_state, frm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// НОВАЯ ФУНКЦИЯ: Проверка завершения загрузки
|
||||||
|
function check_loading_completion(loading_state, frm) {
|
||||||
|
if (loading_state.inbox_completed && loading_state.outbox_completed) {
|
||||||
|
// ИСПРАВЛЕНО: Закрываем диалог загрузки
|
||||||
|
if (loading_state.loading_dialog && loading_state.loading_dialog.hide) {
|
||||||
|
loading_state.loading_dialog.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading_state.all_invoices.length === 0) {
|
||||||
|
frappe.msgprint(__('No invoices found for the selected period.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ИЗМЕНЕНО: Показываем результат загрузки
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Found {0} invoices. Starting processing...', [loading_state.all_invoices.length]),
|
||||||
|
indicator: 'green'
|
||||||
|
}, 3); // Показываем на 3 секунды
|
||||||
|
|
||||||
|
// Небольшая задержка и начинаем обработку
|
||||||
|
setTimeout(() => {
|
||||||
|
process_invoices_for_reference_data(loading_state.all_invoices, loading_state.token, {
|
||||||
|
items_created: 0,
|
||||||
|
items_skipped: 0,
|
||||||
|
units_created: 0,
|
||||||
|
units_skipped: 0,
|
||||||
|
customers_created: 0,
|
||||||
|
customers_skipped: 0,
|
||||||
|
suppliers_created: 0,
|
||||||
|
suppliers_skipped: 0,
|
||||||
|
total_invoices: loading_state.all_invoices.length
|
||||||
|
}, 0, frm);
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -88,10 +88,26 @@ def get_sales_invoices(token, filters=None):
|
||||||
|
|
||||||
# Check for 401 error
|
# Check for 401 error
|
||||||
if response.status_code == 401:
|
if response.status_code == 401:
|
||||||
return {
|
# Получаем свежий токен из Asan Login
|
||||||
"error": "unauthorized",
|
from invoice_az.auth import get_default_asan_login
|
||||||
"message": "Authentication required. Please login again."
|
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}"
|
||||||
|
|
||||||
|
# Повторяем запрос с новым токеном
|
||||||
|
response = requests.post(url, json=payload, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code == 401:
|
||||||
|
return {
|
||||||
|
"error": "unauthorized",
|
||||||
|
"message": "Authentication required. Please login again."
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"error": "unauthorized",
|
||||||
|
"message": "Authentication required. Please login again."
|
||||||
|
}
|
||||||
|
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
|
|
@ -165,10 +181,26 @@ def get_sales_invoice_details(token, invoice_id):
|
||||||
|
|
||||||
# Check for 401 error
|
# Check for 401 error
|
||||||
if response.status_code == 401:
|
if response.status_code == 401:
|
||||||
return {
|
# Получаем свежий токен из Asan Login
|
||||||
"error": "unauthorized",
|
from invoice_az.auth import get_default_asan_login
|
||||||
"message": "Authentication required. Please login again."
|
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}"
|
||||||
|
|
||||||
|
# Повторяем запрос с новым токеном
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code == 401:
|
||||||
|
return {
|
||||||
|
"error": "unauthorized",
|
||||||
|
"message": "Authentication required. Please login again."
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"error": "unauthorized",
|
||||||
|
"message": "Authentication required. Please login again."
|
||||||
|
}
|
||||||
|
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
|
|
@ -226,6 +258,12 @@ def import_sales_invoice_with_mapping(invoice_data, sales_order_name=None, sched
|
||||||
if isinstance(invoice_data, str):
|
if isinstance(invoice_data, str):
|
||||||
invoice_data = json.loads(invoice_data)
|
invoice_data = json.loads(invoice_data)
|
||||||
|
|
||||||
|
# ДОБАВЛЕНО: Логируем входящие данные инвойса для отладки дат
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] Invoice data keys: {list(invoice_data.keys())}", "Sales Date Debug")
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] createdAt field: {invoice_data.get('createdAt')}", "Sales Date Debug")
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] createdAt type: {type(invoice_data.get('createdAt'))}", "Sales Date Debug")
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] schedule_date parameter: {schedule_date}", "Sales Date Debug")
|
||||||
|
|
||||||
# Get active settings
|
# Get active settings
|
||||||
settings = get_active_settings()
|
settings = get_active_settings()
|
||||||
if not settings:
|
if not settings:
|
||||||
|
|
@ -274,6 +312,57 @@ def import_sales_invoice_with_mapping(invoice_data, sales_order_name=None, sched
|
||||||
'message': 'Default warehouse not found. Please specify warehouse in E-Taxes settings.'
|
'message': 'Default warehouse not found. Please specify warehouse in E-Taxes settings.'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ИСПРАВЛЕНО: Определяем дату ДО создания Sales Order
|
||||||
|
invoice_date = None
|
||||||
|
created_at_raw = invoice_data.get("createdAt")
|
||||||
|
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] Raw createdAt value: '{created_at_raw}'", "Sales Date Debug")
|
||||||
|
|
||||||
|
if created_at_raw:
|
||||||
|
try:
|
||||||
|
# Попробуем разные форматы даты
|
||||||
|
if isinstance(created_at_raw, str):
|
||||||
|
# Если это строка, попробуем разные форматы
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] Trying to parse string date: '{created_at_raw}'", "Sales Date Debug")
|
||||||
|
|
||||||
|
# Попробуем стандартный парсинг ERPNext
|
||||||
|
invoice_date = frappe.utils.getdate(created_at_raw)
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] Successfully parsed date: {invoice_date}", "Sales Date Debug")
|
||||||
|
|
||||||
|
elif hasattr(created_at_raw, 'date'):
|
||||||
|
# Если это datetime объект
|
||||||
|
invoice_date = created_at_raw.date()
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] Extracted date from datetime: {invoice_date}", "Sales Date Debug")
|
||||||
|
else:
|
||||||
|
# Попробуем привести к строке и парсить
|
||||||
|
date_str = str(created_at_raw)
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] Converting to string and parsing: '{date_str}'", "Sales Date Debug")
|
||||||
|
invoice_date = frappe.utils.getdate(date_str)
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] Successfully parsed converted date: {invoice_date}", "Sales Date Debug")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] Error parsing createdAt '{created_at_raw}': {str(e)}", "Sales Date Debug")
|
||||||
|
invoice_date = frappe.utils.today()
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] Using fallback date: {invoice_date}", "Sales Date Debug")
|
||||||
|
else:
|
||||||
|
invoice_date = frappe.utils.today()
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] No createdAt found, using today: {invoice_date}", "Sales Date Debug")
|
||||||
|
|
||||||
|
# ИСПРАВЛЕНО: Определяем date_to_use с ПРИОРИТЕТОМ даты из E-taxes
|
||||||
|
# ПРИОРИТЕТ: 1) дата из E-taxes, 2) schedule_date, 3) сегодня
|
||||||
|
date_to_use = None
|
||||||
|
if invoice_date:
|
||||||
|
date_to_use = invoice_date
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] Using invoice_date from E-taxes for date_to_use: {date_to_use}", "Sales Date Debug")
|
||||||
|
elif schedule_date:
|
||||||
|
date_to_use = frappe.utils.getdate(schedule_date)
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] No E-taxes date, using provided schedule_date: {date_to_use}", "Sales Date Debug")
|
||||||
|
else:
|
||||||
|
date_to_use = frappe.utils.today()
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] No dates available, using today for date_to_use: {date_to_use}", "Sales Date Debug")
|
||||||
|
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] Final date_to_use: {date_to_use}", "Sales Date Debug")
|
||||||
|
|
||||||
# ИЗМЕНЕНО: Создаем Sales Order сначала (аналогично Purchase Order логике)
|
# ИЗМЕНЕНО: Создаем Sales Order сначала (аналогично Purchase Order логике)
|
||||||
if sales_order_name:
|
if sales_order_name:
|
||||||
so = frappe.get_doc('Sales Order', sales_order_name)
|
so = frappe.get_doc('Sales Order', sales_order_name)
|
||||||
|
|
@ -300,15 +389,13 @@ def import_sales_invoice_with_mapping(invoice_data, sales_order_name=None, sched
|
||||||
'message': f'No mapping found for customer: {receiver.get("name", "")}'
|
'message': f'No mapping found for customer: {receiver.get("name", "")}'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Set dates
|
frappe.log_error(f"[SALES DATE DEBUG] Final invoice_date for SO: {invoice_date}", "Sales Date Debug")
|
||||||
so.transaction_date = frappe.utils.today()
|
|
||||||
|
|
||||||
if schedule_date:
|
so.transaction_date = invoice_date
|
||||||
so.delivery_date = schedule_date
|
so.delivery_date = date_to_use
|
||||||
elif invoice_data.get("creationDate"):
|
|
||||||
so.delivery_date = frappe.utils.getdate(invoice_data.get("creationDate"))
|
frappe.log_error(f"[SALES DATE DEBUG] SO transaction_date set to: {so.transaction_date}", "Sales Date Debug")
|
||||||
else:
|
frappe.log_error(f"[SALES DATE DEBUG] SO delivery_date set to: {so.delivery_date}", "Sales Date Debug")
|
||||||
so.delivery_date = frappe.utils.today()
|
|
||||||
|
|
||||||
so.company = frappe.defaults.get_user_default('Company')
|
so.company = frappe.defaults.get_user_default('Company')
|
||||||
|
|
||||||
|
|
@ -320,14 +407,6 @@ def import_sales_invoice_with_mapping(invoice_data, sales_order_name=None, sched
|
||||||
if sales_order_name and invoice_data.get("items"):
|
if sales_order_name and invoice_data.get("items"):
|
||||||
so.items = []
|
so.items = []
|
||||||
|
|
||||||
date_to_use = None
|
|
||||||
if schedule_date:
|
|
||||||
date_to_use = schedule_date
|
|
||||||
elif invoice_data.get("creationDate"):
|
|
||||||
date_to_use = frappe.utils.getdate(invoice_data.get("creationDate"))
|
|
||||||
else:
|
|
||||||
date_to_use = frappe.utils.today()
|
|
||||||
|
|
||||||
# Variable for tracking added items
|
# Variable for tracking added items
|
||||||
added_items_count = 0
|
added_items_count = 0
|
||||||
unmatched_items = []
|
unmatched_items = []
|
||||||
|
|
@ -416,9 +495,11 @@ def import_sales_invoice_with_mapping(invoice_data, sales_order_name=None, sched
|
||||||
so_item.rate = item.get("pricePerUnit", 0)
|
so_item.rate = item.get("pricePerUnit", 0)
|
||||||
so_item.amount = item.get("cost", 0)
|
so_item.amount = item.get("cost", 0)
|
||||||
so_item.uom = mapped_uom
|
so_item.uom = mapped_uom
|
||||||
so_item.delivery_date = date_to_use # ИЗМЕНЕНО: delivery_date вместо schedule_date
|
so_item.delivery_date = date_to_use # ИСПРАВЛЕНО: Используем правильную дату
|
||||||
so_item.warehouse = default_warehouse
|
so_item.warehouse = default_warehouse
|
||||||
|
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] Item {so_item.item_code} delivery_date set to: {so_item.delivery_date}", "Sales Date Debug")
|
||||||
|
|
||||||
so.append("items", so_item)
|
so.append("items", so_item)
|
||||||
added_items_count += 1
|
added_items_count += 1
|
||||||
|
|
||||||
|
|
@ -480,19 +561,32 @@ def import_sales_invoice_with_mapping(invoice_data, sales_order_name=None, sched
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.log_error(f"Error adding ƏDV tax: {str(e)}", "Sales Order Tax Error")
|
frappe.log_error(f"Error adding ƏDV tax: {str(e)}", "Sales Order Tax Error")
|
||||||
|
|
||||||
|
# ДОБАВЛЕНО: Логируем финальные даты перед сохранением
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] Final SO transaction_date: {so.transaction_date}", "Sales Date Debug")
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] Final SO delivery_date: {so.delivery_date}", "Sales Date Debug")
|
||||||
|
|
||||||
# Save document
|
# Save document
|
||||||
so.save()
|
so.save()
|
||||||
|
|
||||||
# Additional check that all delivery_date and warehouse are set
|
# ИСПРАВЛЕНО: Убедимся что все delivery_date установлены правильно
|
||||||
for item in so.items:
|
for item in so.items:
|
||||||
if not item.delivery_date:
|
if not item.delivery_date:
|
||||||
item.delivery_date = date_to_use
|
item.delivery_date = date_to_use
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] Fixed missing delivery_date for item {item.item_code}: {item.delivery_date}", "Sales Date Debug")
|
||||||
if not item.warehouse:
|
if not item.warehouse:
|
||||||
item.warehouse = default_warehouse
|
item.warehouse = default_warehouse
|
||||||
|
|
||||||
# Save again to ensure changes are applied
|
# Save again to ensure changes are applied
|
||||||
so.save()
|
so.save()
|
||||||
|
|
||||||
|
# ДОБАВЛЕНО: Логируем даты после сохранения
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] After save SO transaction_date: {so.transaction_date}", "Sales Date Debug")
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] After save SO delivery_date: {so.delivery_date}", "Sales Date Debug")
|
||||||
|
|
||||||
|
# Логируем delivery_date каждого item после сохранения
|
||||||
|
for item in so.items:
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] After save item {item.item_code} delivery_date: {item.delivery_date}", "Sales Date Debug")
|
||||||
|
|
||||||
# ИЗМЕНЕНИЕ: Создаем E-Taxes Sales и устанавливаем связь ДО submit'а
|
# ИЗМЕНЕНИЕ: Создаем E-Taxes Sales и устанавливаем связь ДО submit'а
|
||||||
invoice_id = invoice_data.get('id', '')
|
invoice_id = invoice_data.get('id', '')
|
||||||
serial_number = invoice_data.get('serialNumber', '')
|
serial_number = invoice_data.get('serialNumber', '')
|
||||||
|
|
@ -562,18 +656,43 @@ def create_sales_invoice_from_order(sales_order_name):
|
||||||
# Импортируем стандартную функцию из модуля Sales Order
|
# Импортируем стандартную функцию из модуля Sales Order
|
||||||
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
|
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
|
||||||
|
|
||||||
|
# Получаем Sales Order ДО создания invoice
|
||||||
|
so_doc = frappe.get_doc('Sales Order', sales_order_name)
|
||||||
|
|
||||||
|
# ИСПРАВЛЕНО: Правильная обработка дат
|
||||||
|
if so_doc.transaction_date:
|
||||||
|
posting_date = so_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"[SALES DATE DEBUG] SO transaction_date: {so_doc.transaction_date}", "Sales Date Debug")
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] SI posting_date to set: {posting_date}", "Sales Date Debug")
|
||||||
|
|
||||||
# Создаем Sales Invoice из Sales Order
|
# Создаем Sales Invoice из Sales Order
|
||||||
si_doc = make_sales_invoice(sales_order_name)
|
si_doc = make_sales_invoice(sales_order_name)
|
||||||
|
|
||||||
# Устанавливаем даты
|
# ДОБАВЛЕНО: Логируем дату сразу после создания
|
||||||
si_doc.posting_date = frappe.utils.nowdate()
|
frappe.log_error(f"[SALES DATE DEBUG] SI posting_date after make_sales_invoice: {si_doc.posting_date}", "Sales Date Debug")
|
||||||
si_doc.due_date = frappe.utils.add_days(frappe.utils.nowdate(), 30)
|
|
||||||
|
# ИСПРАВЛЕНО: Устанавливаем дату и флаг posting_time ДО других операций
|
||||||
|
si_doc.posting_date = posting_date
|
||||||
|
si_doc.set_posting_time = 1 # Важно! Разрешаем установку даты в прошлом
|
||||||
|
|
||||||
|
# ИСПРАВЛЕНО: due_date всегда основана на posting_date из E-taxes (без коррекции на "сегодня")
|
||||||
|
due_date = frappe.utils.add_days(posting_date, 30)
|
||||||
|
si_doc.due_date = due_date
|
||||||
|
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] SI posting_date before insert: {si_doc.posting_date}", "Sales Date Debug")
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] SI due_date before insert (E-taxes based): {si_doc.due_date}", "Sales Date Debug")
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] SI set_posting_time: {si_doc.set_posting_time}", "Sales Date Debug")
|
||||||
|
|
||||||
# ДОБАВЛЕНО: Устанавливаем галочку is_taxes_doc для Sales Invoice
|
# ДОБАВЛЕНО: Устанавливаем галочку is_taxes_doc для Sales Invoice
|
||||||
si_doc.is_taxes_doc = 1
|
si_doc.is_taxes_doc = 1
|
||||||
|
|
||||||
# ДОБАВЛЕНО: Копируем налоги из Sales Order
|
# ДОБАВЛЕНО: Копируем налоги из Sales Order
|
||||||
so_doc = frappe.get_doc('Sales Order', sales_order_name)
|
|
||||||
if so_doc.taxes:
|
if so_doc.taxes:
|
||||||
si_doc.taxes = []
|
si_doc.taxes = []
|
||||||
for tax_row in so_doc.taxes:
|
for tax_row in so_doc.taxes:
|
||||||
|
|
@ -588,6 +707,30 @@ def create_sales_invoice_from_order(sales_order_name):
|
||||||
# Сохраняем документ
|
# Сохраняем документ
|
||||||
si_doc.insert(ignore_permissions=True)
|
si_doc.insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
# ДОБАВЛЕНО: Логируем дату после insert
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] SI posting_date after insert: {si_doc.posting_date}", "Sales Date Debug")
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] SI due_date after insert: {si_doc.due_date}", "Sales Date Debug")
|
||||||
|
|
||||||
|
# ДОБАВЛЕНО: Если даты все еще неправильные, принудительно устанавливаем
|
||||||
|
expected_due_date = frappe.utils.add_days(posting_date, 30)
|
||||||
|
if si_doc.posting_date != posting_date or si_doc.due_date != expected_due_date:
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] Date mismatch detected!", "Sales Date Debug")
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] Expected posting_date: {posting_date}, Got: {si_doc.posting_date}", "Sales Date Debug")
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] Expected due_date: {expected_due_date}, Got: {si_doc.due_date}", "Sales Date Debug")
|
||||||
|
|
||||||
|
# Принудительно устанавливаем даты через DB
|
||||||
|
frappe.db.set_value('Sales Invoice', si_doc.name, {
|
||||||
|
'posting_date': posting_date,
|
||||||
|
'due_date': expected_due_date,
|
||||||
|
'set_posting_time': 1
|
||||||
|
})
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
# Перезагружаем документ
|
||||||
|
si_doc = frappe.get_doc('Sales Invoice', si_doc.name)
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] SI posting_date after DB update: {si_doc.posting_date}", "Sales Date Debug")
|
||||||
|
frappe.log_error(f"[SALES DATE DEBUG] SI due_date after DB update: {si_doc.due_date}", "Sales Date Debug")
|
||||||
|
|
||||||
frappe.log_error(f"Sales Invoice {si_doc.name} created from Sales Order {sales_order_name}", "Sales Invoice Creation")
|
frappe.log_error(f"Sales Invoice {si_doc.name} created from Sales Order {sales_order_name}", "Sales Invoice Creation")
|
||||||
|
|
||||||
# Возвращаем имя созданного Sales Invoice
|
# Возвращаем имя созданного Sales Invoice
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue