Everything load from outbox and inbox

This commit is contained in:
Ali 2025-06-12 15:01:34 +04:00
parent 3893f44be1
commit 7e4a7afdc8
10 changed files with 3943 additions and 4726 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -852,6 +852,26 @@ SalesETaxes.import = {
fieldtype: 'Date',
label: __('Date to'),
default: moment().format('YYYY-MM-DD')
},
{
fieldname: 'settings_section',
fieldtype: 'Section Break',
label: __('Settings')
},
{
fieldname: 'warehouse',
fieldtype: 'Link',
label: __('Delivery warehouse'),
options: 'Warehouse',
reqd: true,
get_query: function() {
return {
filters: {
'is_group': 0,
'disabled': 0
}
};
}
}
],
primary_action_label: __('Search'),
@ -873,11 +893,20 @@ SalesETaxes.import = {
return;
}
if (!values.warehouse) {
frappe.msgprint({
title: __('Warning'),
indicator: 'orange',
message: __('Please select a delivery warehouse')
});
return;
}
const fromDate = moment(values.creationDateFrom).format('DD-MM-YYYY 00:00');
const toDate = moment(values.creationDateTo).format('DD-MM-YYYY 23:59');
d.hide();
SalesETaxes.import.loadInvoices(fromDate, toDate);
SalesETaxes.import.loadInvoices(fromDate, toDate, values.warehouse);
}
});
@ -886,7 +915,7 @@ SalesETaxes.import = {
},
// Загрузить инвойсы с фильтрацией дубликатов
loadInvoices: function(fromDate, toDate, accumulatedInvoices = [], offset = 0) {
loadInvoices: function(fromDate, toDate, warehouse, accumulatedInvoices = [], offset = 0) {
SalesETaxes.cancelLoading = false;
$(document).off('progress-cancel.etaxes_sales_import');
@ -935,7 +964,7 @@ SalesETaxes.import = {
if (hasMore && currentInvoices.length > 0) {
const newOffset = offset + currentInvoices.length;
SalesETaxes.import.loadInvoices(fromDate, toDate, allInvoices, newOffset);
SalesETaxes.import.loadInvoices(fromDate, toDate, warehouse, allInvoices, newOffset);
} else {
if (allInvoices.length > 0) {
frappe.show_alert({
@ -943,7 +972,7 @@ SalesETaxes.import = {
indicator: 'blue'
}, 3);
SalesETaxes.import.showInvoiceSelection(allInvoices, mainToken);
SalesETaxes.import.showInvoiceSelection(allInvoices, mainToken, warehouse);
} else {
frappe.msgprint({
title: __('Information'),
@ -957,7 +986,7 @@ SalesETaxes.import = {
__('Your E-Taxes session has expired. Would you like to authenticate now?'),
function() {
SalesETaxes.auth.startProcess(function() {
SalesETaxes.import.loadInvoices(fromDate, toDate);
SalesETaxes.import.loadInvoices(fromDate, toDate, warehouse);
});
},
function() {
@ -995,7 +1024,7 @@ SalesETaxes.import = {
},
// Показать диалог выбора инвойсов
showInvoiceSelection: function(invoices, token) {
showInvoiceSelection: function(invoices, token, warehouse) {
let invoiceTable = '<div style="max-height: 500px; overflow-y: auto;"><table class="table table-bordered etaxes-sales-invoices-table" style="width: 100%; table-layout: fixed;">';
invoiceTable += '<thead><tr>' +
'<th style="width: 6%;"><input type="checkbox" class="select-all-sales-invoices"></th>' +
@ -1046,6 +1075,12 @@ SalesETaxes.import = {
fieldname: 'invoices_html',
fieldtype: 'HTML',
options: invoiceTable
},
{
fieldname: 'warehouse_display',
fieldtype: 'HTML',
options: '<div class="row"><div class="col-xs-12"><div class="alert alert-info">' +
__('Selected delivery warehouse: ') + '<strong>' + warehouse + '</strong></div></div></div>'
}
],
primary_action_label: __('Load selected'),
@ -1066,7 +1101,7 @@ SalesETaxes.import = {
d.hide();
SalesETaxes.cancelLoading = false;
SalesETaxes.import.loadSelectedInvoices(selectedInvoiceIds, token);
SalesETaxes.import.loadSelectedInvoices(selectedInvoiceIds, token, warehouse);
},
secondary_action_label: __('Cancel'),
secondary_action: function() {
@ -1094,7 +1129,7 @@ SalesETaxes.import = {
},
// Загрузка выбранных инвойсов
loadSelectedInvoices: function(invoiceIds, token, processedCount = 0, currentIndex = 0) {
loadSelectedInvoices: function(invoiceIds, token, warehouse, processedCount = 0, currentIndex = 0) {
if (processedCount === 0 && currentIndex === 0) {
SalesETaxes.loadingErrors = [];
window.totalSalesInvoicesToProcess = invoiceIds.length;
@ -1121,7 +1156,7 @@ SalesETaxes.import = {
if (SalesETaxes.cancelLoading) {
invoiceIds = [];
frappe.hide_progress();
SalesETaxes.import.loadSelectedInvoices(invoiceIds, token, processedCount, currentIndex);
SalesETaxes.import.loadSelectedInvoices(invoiceIds, token, warehouse, processedCount, currentIndex);
return;
}
@ -1165,11 +1200,12 @@ SalesETaxes.import = {
args: {
'invoice_data': r.message,
'sales_order_name': null,
'schedule_date': scheduleDate
'schedule_date': scheduleDate,
'warehouse': warehouse // ДОБАВЛЕНО: передача warehouse
},
callback: function(importR) {
SalesETaxes.import._handleImportResult(importR, r.message, invoiceId, scheduleDate, receiverName, total,
invoiceIds, token, processedCount, currentIndex, isLastInvoice);
invoiceIds, token, warehouse, processedCount, currentIndex, isLastInvoice);
},
error: function(xhr, status, error) {
const errorMessage = 'Network error during import: ' + (error || 'Unknown network error');
@ -1179,7 +1215,7 @@ SalesETaxes.import = {
xhr_response: xhr.responseText
});
SalesETaxes.import._continueOrFinish(invoiceIds, token, processedCount, currentIndex, isLastInvoice);
SalesETaxes.import._continueOrFinish(invoiceIds, token, warehouse, processedCount, currentIndex, isLastInvoice);
}
});
} else if (r.message && r.message.error === 'unauthorized') {
@ -1193,7 +1229,7 @@ SalesETaxes.import = {
SalesETaxes.utils.getDefaultLogin(function(loginR) {
if (loginR && loginR.found && loginR.main_token) {
invoiceIds.unshift(invoiceId);
SalesETaxes.import.loadSelectedInvoices(invoiceIds, loginR.main_token, processedCount, currentIndex - 1);
SalesETaxes.import.loadSelectedInvoices(invoiceIds, loginR.main_token, warehouse, processedCount, currentIndex - 1);
} else {
SalesETaxes.errors.add({id: invoiceId}, 'Authentication Error',
'Failed to get token after authentication');
@ -1218,7 +1254,7 @@ SalesETaxes.import = {
server_response: r.message
});
SalesETaxes.import._continueOrFinish(invoiceIds, token, processedCount, currentIndex, isLastInvoice);
SalesETaxes.import._continueOrFinish(invoiceIds, token, warehouse, processedCount, currentIndex, isLastInvoice);
}
},
error: function(xhr, status, error) {
@ -1228,14 +1264,14 @@ SalesETaxes.import = {
xhr_response: xhr.responseText
});
SalesETaxes.import._continueOrFinish(invoiceIds, token, processedCount, currentIndex, isLastInvoice);
SalesETaxes.import._continueOrFinish(invoiceIds, token, warehouse, processedCount, currentIndex, isLastInvoice);
}
});
},
// Обработка результата импорта
_handleImportResult: function(importR, invoiceMessage, invoiceId, scheduleDate, receiverName, total,
invoiceIds, token, processedCount, currentIndex, isLastInvoice) {
invoiceIds, token, warehouse, processedCount, currentIndex, isLastInvoice) {
if (importR.message && importR.message.success) {
frappe.show_alert({
message: __('Sales invoice successfully imported: ') + (invoiceMessage.serialNumber || ''),
@ -1243,26 +1279,99 @@ SalesETaxes.import = {
}, 3);
processedCount++;
SalesETaxes.import._continueOrFinish(invoiceIds, token, processedCount, currentIndex, isLastInvoice);
SalesETaxes.import._continueOrFinish(invoiceIds, token, warehouse, processedCount, currentIndex, isLastInvoice);
} else if (importR.message && (importR.message.unmatched_items || importR.message.unmatched_parties || importR.message.unmatched_units)) {
let errorType = '';
const additionalData = { invoice_items: invoiceMessage.items || [] };
if (importR.message.unmatched_items) {
errorType = 'Unmapped Items';
additionalData.unmatched_items = importR.message.unmatched_items;
additionalData.unmatched_items = importR.message.unmatched_items.map(function(item, index) {
let itemName = '';
let itemCode = '';
if (typeof item === 'string') {
itemName = item;
} else if (item && typeof item === 'object') {
itemName = item.name || item.productName || item.item_name || '';
itemCode = item.code || item.itemId || item.item_code || '';
} else {
if (invoiceMessage.items && invoiceMessage.items[index]) {
const originalItem = invoiceMessage.items[index];
itemName = originalItem.productName || originalItem.name || '';
itemCode = originalItem.itemId || originalItem.code || '';
}
}
if (!itemName) {
itemName = 'Item ' + (index + 1) + ' (name not available)';
}
return {
name: itemName,
code: itemCode
};
});
} else if (importR.message.unmatched_parties) {
errorType = 'Unmapped Parties';
additionalData.unmatched_parties = importR.message.unmatched_parties;
additionalData.unmatched_parties = importR.message.unmatched_parties.map(function(party, index) {
let partyName = '';
let partyTin = '';
let partyType = '';
if (typeof party === 'string') {
partyName = party;
} else if (party && typeof party === 'object') {
partyName = party.name || party.party_name || '';
partyTin = party.tin || party.tax_id || '';
partyType = party.type || party.party_type || '';
} else {
if (invoiceMessage.receiver) {
partyName = invoiceMessage.receiver.name || '';
partyTin = invoiceMessage.receiver.tin || '';
partyType = 'Receiver';
}
}
if (!partyName) {
partyName = 'Party ' + (index + 1) + ' (name not available)';
}
return {
name: partyName,
tin: partyTin,
type: partyType
};
});
} else if (importR.message.unmatched_units) {
errorType = 'Unmapped Units';
additionalData.unmatched_units = importR.message.unmatched_units;
additionalData.unmatched_units = importR.message.unmatched_units.map(function(unit, index) {
let unitName = '';
if (typeof unit === 'string') {
unitName = unit;
} else if (unit && typeof unit === 'object') {
unitName = unit.name || unit.unit_name || '';
} else {
if (invoiceMessage.items && invoiceMessage.items[index] && invoiceMessage.items[index].unit) {
unitName = invoiceMessage.items[index].unit;
}
}
if (!unitName) {
unitName = 'Unit ' + (index + 1) + ' (name not available)';
}
return {
name: unitName
};
});
}
additionalData.server_response = importR.message;
SalesETaxes.errors.add(invoiceMessage, errorType, importR.message.message, additionalData);
SalesETaxes.import._continueOrFinish(invoiceIds, token, processedCount, currentIndex, isLastInvoice);
SalesETaxes.import._continueOrFinish(invoiceIds, token, warehouse, processedCount, currentIndex, isLastInvoice);
} else {
const errorMessage = importR.message ? importR.message.message : 'Unknown sales import error';
const additionalData = {
@ -1275,12 +1384,12 @@ SalesETaxes.import = {
}
SalesETaxes.errors.add(invoiceMessage, 'Sales Import Error', errorMessage, additionalData);
SalesETaxes.import._continueOrFinish(invoiceIds, token, processedCount, currentIndex, isLastInvoice);
SalesETaxes.import._continueOrFinish(invoiceIds, token, warehouse, processedCount, currentIndex, isLastInvoice);
}
},
// Продолжить или завершить обработку
_continueOrFinish: function(invoiceIds, token, processedCount, currentIndex, isLastInvoice) {
_continueOrFinish: function(invoiceIds, token, warehouse, processedCount, currentIndex, isLastInvoice) {
if (isLastInvoice) {
frappe.hide_progress();
$(document).off('progress-cancel.loading_sales_invoices');
@ -1300,7 +1409,7 @@ SalesETaxes.import = {
frappe.hide_progress();
}
SalesETaxes.import.loadSelectedInvoices(invoiceIds, token, processedCount, currentIndex);
SalesETaxes.import.loadSelectedInvoices(invoiceIds, token, warehouse, processedCount, currentIndex);
}, SalesETaxes.PROGRESS_UPDATE_DELAY);
}
};

View File

@ -1,175 +0,0 @@
import frappe
import requests
import json
from frappe.utils import nowdate, now, cint, now_datetime, get_datetime
from frappe.model.document import Document
import time
# Константы
BASE_URL = "https://new.e-taxes.gov.az/api/po"
PROFILE_URL = f"{BASE_URL}/profile/public/v2/profile"
DEFAULT_HEADERS = {
"Accept": "application/json, text/plain, */*",
"Accept-Language": "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7",
"Cache-Control": "no-cache",
"Pragma": "no-cache",
"Referer": "https://new.e-taxes.gov.az/eportal/az/login/asan"
}
@frappe.whitelist()
def get_company_profile():
"""Получает профиль компании из E-Taxes"""
# Записываем активность
try:
from invoice_az.api import record_etaxes_activity
record_etaxes_activity()
except:
pass
try:
# Получаем настройки по умолчанию
from invoice_az.auth import get_default_asan_login
default_settings = get_default_asan_login()
if not default_settings.get('found'):
return {
'success': False,
'message': 'No Asan Login settings found'
}
# Проверяем валидность токена
from invoice_az.auth import check_token_validity
token_check = check_token_validity(default_settings.get('name'))
if not token_check.get('valid'):
return {
'success': False,
'error': 'unauthorized',
'message': token_check.get('message', 'Token is not valid')
}
# Получаем main_token
main_token = default_settings.get('main_token')
if not main_token:
return {
'success': False,
'error': 'unauthorized',
'message': 'Main token not found'
}
# Подготавливаем заголовки запроса
headers = DEFAULT_HEADERS.copy()
headers["x-authorization"] = f"Bearer {main_token}"
# Отправляем запрос
response = requests.get(PROFILE_URL, headers=headers, timeout=10)
# Проверяем статус ответа
if response.status_code == 500:
return {
"success": False,
"error": "server_error",
"message": "Service temporarily unavailable. Please try again in a few minutes.",
"status_code": 500
}
if response.status_code == 401:
return {
"success": False,
"error": "unauthorized",
"message": "Authentication required. Please login again.",
"status_code": 401
}
response.raise_for_status()
# Получаем данные профиля
profile_data = response.json()
return {
'success': True,
'message': 'Company profile retrieved successfully',
'profile_data': profile_data,
'raw_response': response.text,
'status_code': response.status_code,
'headers': dict(response.headers)
}
except requests.exceptions.HTTPError as e:
# Проверяем HTTP ошибки
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
}
frappe.log_error(f"HTTP error in get_company_profile: {str(e)}", "API Error")
return {
"success": False,
"error": "http_error",
"message": "An unknown error occurred, please try again in a few minutes."
}
except requests.RequestException as e:
frappe.log_error(f"Network error in get_company_profile: {str(e)}", "API Error")
return {
"success": False,
"error": "network_error",
"message": f"Network error: {str(e)}"
}
except Exception as e:
frappe.log_error(f"Error in get_company_profile: {str(e)}\n{frappe.get_traceback()}", "Company Profile Error")
return {
"success": False,
"error": "unknown_error",
"message": "An unknown error occurred, please try again in a few minutes."
}
@frappe.whitelist()
def test_profile_endpoint():
"""Тестирует доступность endpoint'а профиля"""
try:
from invoice_az.api import record_etaxes_activity
record_etaxes_activity()
except:
pass
try:
# Получаем настройки по умолчанию
from invoice_az.auth import get_default_asan_login
default_settings = get_default_asan_login()
if not default_settings.get('found'):
return {
'success': False,
'message': 'No Asan Login settings found'
}
asan_login_name = default_settings.get('name')
# Используем тестовую функцию из auth.py
from invoice_az.auth import test_api_endpoints
result = test_api_endpoints(
asan_login_name=asan_login_name,
endpoint_path="profile/public/v2/profile",
method="GET"
)
return result
except Exception as e:
frappe.log_error(f"Error in test_profile_endpoint: {str(e)}", "Profile Test Error")
return {
"success": False,
"message": f"Error testing endpoint: {str(e)}"
}

View File

@ -9,8 +9,7 @@ doctype_js = {
"Purchase Order": "client/purchase_order.js",
"Purchase Invoice": "client/purchase_invoice.js",
"Sales Order": "client/sales_order.js",
"Sales Invoice": "client/sales_invoice.js",
"Company": "client/company.js"
"Sales Invoice": "client/sales_invoice.js"
}
doctype_list_js = {