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', fieldtype: 'Date',
label: __('Date to'), label: __('Date to'),
default: moment().format('YYYY-MM-DD') 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'), primary_action_label: __('Search'),
@ -873,11 +893,20 @@ SalesETaxes.import = {
return; 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 fromDate = moment(values.creationDateFrom).format('DD-MM-YYYY 00:00');
const toDate = moment(values.creationDateTo).format('DD-MM-YYYY 23:59'); const toDate = moment(values.creationDateTo).format('DD-MM-YYYY 23:59');
d.hide(); 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; SalesETaxes.cancelLoading = false;
$(document).off('progress-cancel.etaxes_sales_import'); $(document).off('progress-cancel.etaxes_sales_import');
@ -935,7 +964,7 @@ SalesETaxes.import = {
if (hasMore && currentInvoices.length > 0) { if (hasMore && currentInvoices.length > 0) {
const newOffset = offset + currentInvoices.length; const newOffset = offset + currentInvoices.length;
SalesETaxes.import.loadInvoices(fromDate, toDate, allInvoices, newOffset); SalesETaxes.import.loadInvoices(fromDate, toDate, warehouse, allInvoices, newOffset);
} else { } else {
if (allInvoices.length > 0) { if (allInvoices.length > 0) {
frappe.show_alert({ frappe.show_alert({
@ -943,7 +972,7 @@ SalesETaxes.import = {
indicator: 'blue' indicator: 'blue'
}, 3); }, 3);
SalesETaxes.import.showInvoiceSelection(allInvoices, mainToken); SalesETaxes.import.showInvoiceSelection(allInvoices, mainToken, warehouse);
} else { } else {
frappe.msgprint({ frappe.msgprint({
title: __('Information'), title: __('Information'),
@ -957,7 +986,7 @@ SalesETaxes.import = {
__('Your E-Taxes session has expired. Would you like to authenticate now?'), __('Your E-Taxes session has expired. Would you like to authenticate now?'),
function() { function() {
SalesETaxes.auth.startProcess(function() { SalesETaxes.auth.startProcess(function() {
SalesETaxes.import.loadInvoices(fromDate, toDate); SalesETaxes.import.loadInvoices(fromDate, toDate, warehouse);
}); });
}, },
function() { 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;">'; 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>' + invoiceTable += '<thead><tr>' +
'<th style="width: 6%;"><input type="checkbox" class="select-all-sales-invoices"></th>' + '<th style="width: 6%;"><input type="checkbox" class="select-all-sales-invoices"></th>' +
@ -1046,6 +1075,12 @@ SalesETaxes.import = {
fieldname: 'invoices_html', fieldname: 'invoices_html',
fieldtype: 'HTML', fieldtype: 'HTML',
options: invoiceTable 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'), primary_action_label: __('Load selected'),
@ -1066,7 +1101,7 @@ SalesETaxes.import = {
d.hide(); d.hide();
SalesETaxes.cancelLoading = false; SalesETaxes.cancelLoading = false;
SalesETaxes.import.loadSelectedInvoices(selectedInvoiceIds, token); SalesETaxes.import.loadSelectedInvoices(selectedInvoiceIds, token, warehouse);
}, },
secondary_action_label: __('Cancel'), secondary_action_label: __('Cancel'),
secondary_action: function() { 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) { if (processedCount === 0 && currentIndex === 0) {
SalesETaxes.loadingErrors = []; SalesETaxes.loadingErrors = [];
window.totalSalesInvoicesToProcess = invoiceIds.length; window.totalSalesInvoicesToProcess = invoiceIds.length;
@ -1121,7 +1156,7 @@ SalesETaxes.import = {
if (SalesETaxes.cancelLoading) { if (SalesETaxes.cancelLoading) {
invoiceIds = []; invoiceIds = [];
frappe.hide_progress(); frappe.hide_progress();
SalesETaxes.import.loadSelectedInvoices(invoiceIds, token, processedCount, currentIndex); SalesETaxes.import.loadSelectedInvoices(invoiceIds, token, warehouse, processedCount, currentIndex);
return; return;
} }
@ -1165,11 +1200,12 @@ SalesETaxes.import = {
args: { args: {
'invoice_data': r.message, 'invoice_data': r.message,
'sales_order_name': null, 'sales_order_name': null,
'schedule_date': scheduleDate 'schedule_date': scheduleDate,
'warehouse': warehouse // ДОБАВЛЕНО: передача warehouse
}, },
callback: function(importR) { callback: function(importR) {
SalesETaxes.import._handleImportResult(importR, r.message, invoiceId, scheduleDate, receiverName, total, 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) { error: function(xhr, status, error) {
const errorMessage = 'Network error during import: ' + (error || 'Unknown network error'); const errorMessage = 'Network error during import: ' + (error || 'Unknown network error');
@ -1179,7 +1215,7 @@ SalesETaxes.import = {
xhr_response: xhr.responseText 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') { } else if (r.message && r.message.error === 'unauthorized') {
@ -1193,7 +1229,7 @@ SalesETaxes.import = {
SalesETaxes.utils.getDefaultLogin(function(loginR) { SalesETaxes.utils.getDefaultLogin(function(loginR) {
if (loginR && loginR.found && loginR.main_token) { if (loginR && loginR.found && loginR.main_token) {
invoiceIds.unshift(invoiceId); 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 { } else {
SalesETaxes.errors.add({id: invoiceId}, 'Authentication Error', SalesETaxes.errors.add({id: invoiceId}, 'Authentication Error',
'Failed to get token after authentication'); 'Failed to get token after authentication');
@ -1218,7 +1254,7 @@ SalesETaxes.import = {
server_response: r.message 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) { error: function(xhr, status, error) {
@ -1228,14 +1264,14 @@ SalesETaxes.import = {
xhr_response: xhr.responseText 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, _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) { if (importR.message && importR.message.success) {
frappe.show_alert({ frappe.show_alert({
message: __('Sales invoice successfully imported: ') + (invoiceMessage.serialNumber || ''), message: __('Sales invoice successfully imported: ') + (invoiceMessage.serialNumber || ''),
@ -1243,26 +1279,99 @@ SalesETaxes.import = {
}, 3); }, 3);
processedCount++; 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)) { } else if (importR.message && (importR.message.unmatched_items || importR.message.unmatched_parties || importR.message.unmatched_units)) {
let errorType = ''; let errorType = '';
const additionalData = { invoice_items: invoiceMessage.items || [] }; const additionalData = { invoice_items: invoiceMessage.items || [] };
if (importR.message.unmatched_items) { if (importR.message.unmatched_items) {
errorType = 'Unmapped 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) { } else if (importR.message.unmatched_parties) {
errorType = 'Unmapped 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) { } else if (importR.message.unmatched_units) {
errorType = 'Unmapped 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; additionalData.server_response = importR.message;
SalesETaxes.errors.add(invoiceMessage, errorType, importR.message.message, additionalData); 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 { } else {
const errorMessage = importR.message ? importR.message.message : 'Unknown sales import error'; const errorMessage = importR.message ? importR.message.message : 'Unknown sales import error';
const additionalData = { const additionalData = {
@ -1275,12 +1384,12 @@ SalesETaxes.import = {
} }
SalesETaxes.errors.add(invoiceMessage, 'Sales Import Error', errorMessage, additionalData); 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) { if (isLastInvoice) {
frappe.hide_progress(); frappe.hide_progress();
$(document).off('progress-cancel.loading_sales_invoices'); $(document).off('progress-cancel.loading_sales_invoices');
@ -1300,7 +1409,7 @@ SalesETaxes.import = {
frappe.hide_progress(); frappe.hide_progress();
} }
SalesETaxes.import.loadSelectedInvoices(invoiceIds, token, processedCount, currentIndex); SalesETaxes.import.loadSelectedInvoices(invoiceIds, token, warehouse, processedCount, currentIndex);
}, SalesETaxes.PROGRESS_UPDATE_DELAY); }, 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 Order": "client/purchase_order.js",
"Purchase Invoice": "client/purchase_invoice.js", "Purchase Invoice": "client/purchase_invoice.js",
"Sales Order": "client/sales_order.js", "Sales Order": "client/sales_order.js",
"Sales Invoice": "client/sales_invoice.js", "Sales Invoice": "client/sales_invoice.js"
"Company": "client/company.js"
} }
doctype_list_js = { doctype_list_js = {

View File

@ -1030,4 +1030,4 @@ def process_sales_invoices_batch_for_items(invoices_json, token, batch_size=10):
return { return {
'success': False, 'success': False,
'message': f'Error processing sales invoices batch: {str(e)}' 'message': f'Error processing sales invoices batch: {str(e)}'
} }