added pull from e taxes site to company (first page)

This commit is contained in:
Ali 2025-06-09 23:19:39 +04:00
parent e6a3dac967
commit 3893f44be1
14 changed files with 5596 additions and 3663 deletions

File diff suppressed because it is too large Load Diff

629
invoice_az/auth.py Normal file
View File

@ -0,0 +1,629 @@
import frappe
import requests
import json
from frappe.utils import nowdate, now, cint, now_datetime, get_datetime
import random
import time
# ======= КОНСТАНТЫ =======
RENEW_URL = "https://new.e-taxes.gov.az/api/po/auth/public/v1/renew"
BASE_URL = "https://new.e-taxes.gov.az/api/po"
MAX_RETRY_COUNT = 3
ACTIVITY_TIMEOUT_MINUTES = 30
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"
}
# ======= ОБЩИЕ УТИЛИТЫ =======
def handle_api_response(response):
"""Общая обработка ответов API"""
if response.status_code == 500:
return {
"error": "server_error",
"message": "Service temporarily unavailable. Please try again in a few minutes.",
"status_code": 500
}
elif response.status_code == 401:
return {
"error": "unauthorized",
"message": "Authentication required. Please login again.",
"status_code": 401
}
return None
def make_api_request(url, method="GET", headers=None, data=None, timeout=10):
"""Общая функция для API запросов с обработкой ошибок"""
try:
if method.upper() == "POST":
response = requests.post(url, headers=headers, data=data, timeout=timeout)
else:
response = requests.get(url, headers=headers, timeout=timeout)
# Проверка стандартных ошибок
error_response = handle_api_response(response)
if error_response:
return error_response
response.raise_for_status()
return response
except requests.RequestException as e:
if hasattr(e, 'response') and e.response:
error_response = handle_api_response(e.response)
if error_response:
return error_response
frappe.log_error(f"API request error: {str(e)}", "API Error")
return {"error": "network_error", "message": "An unknown error occurred, please try again in a few minutes."}
def log_error_safe(message, title):
"""Безопасное логирование ошибок"""
try:
frappe.log_error(message, title)
except:
pass
# ======= УПРАВЛЕНИЕ АКТИВНОСТЬЮ =======
@frappe.whitelist()
def record_etaxes_activity(asan_login_name=None):
"""Записывает время последней активности с e-taxes"""
try:
if not asan_login_name:
default_settings = frappe.get_all(
"Asan Login",
filters={"is_default": 1},
fields=["name"],
limit=1
)
if not default_settings:
return
asan_login_name = default_settings[0].name
frappe.enqueue(
'invoice_az.auth.update_activity_time',
asan_login_name=asan_login_name,
queue='short',
timeout=300
)
except Exception as e:
log_error_safe(f"Error recording activity: {str(e)}", "Activity Recording Error")
@frappe.whitelist()
def update_activity_time(asan_login_name):
"""Обновляет время активности"""
try:
time.sleep(2)
frappe.db.set_value("Asan Login", asan_login_name, "last_activity_time", now_datetime(), update_modified=False)
frappe.db.commit()
except Exception as e:
log_error_safe(f"Error updating activity time: {str(e)}", "Activity Update Error")
def check_recent_activity(asan_login_name=None):
"""Проверяет наличие недавней активности"""
try:
if not asan_login_name:
default_settings = frappe.get_all(
"Asan Login",
filters={"is_default": 1},
fields=["name"],
limit=1
)
if not default_settings:
return False
asan_login_name = default_settings[0].name
last_activity = frappe.db.get_value("Asan Login", asan_login_name, "last_activity_time")
if not last_activity:
return False
time_diff = now_datetime() - get_datetime(last_activity)
minutes_diff = time_diff.total_seconds() / 60
return minutes_diff <= ACTIVITY_TIMEOUT_MINUTES
except Exception as e:
log_error_safe(f"Error checking activity: {str(e)}", "Activity Check Error")
return False
# ======= УПРАВЛЕНИЕ ТОКЕНАМИ =======
@frappe.whitelist()
def renew_token(asan_login_name=None, retry_count=0, force=False):
"""Обновляет главный токен"""
try:
record_etaxes_activity(asan_login_name)
if not asan_login_name:
default_settings = frappe.get_all(
"Asan Login",
filters={"is_default": 1},
fields=["name"],
limit=1
)
if not default_settings:
return {"success": False, "message": "No default Asan Login found"}
asan_login_name = default_settings[0].name
if not check_recent_activity(asan_login_name):
return {"success": True, "message": "Token renewal skipped due to inactivity"}
asan_login = frappe.get_doc("Asan Login", asan_login_name)
if not asan_login.main_token:
return {"success": False, "message": "No main token found"}
headers = DEFAULT_HEADERS.copy()
headers["x-authorization"] = f"Bearer {asan_login.main_token}"
response = make_api_request(RENEW_URL, "POST", headers)
if isinstance(response, dict) and "error" in response:
if response["error"] == "unauthorized":
frappe.db.set_value("Asan Login", asan_login_name, "auth_status", "Not Authenticated")
frappe.db.commit()
return response
new_token = response.headers.get('x-authorization')
if new_token and new_token != asan_login.main_token:
frappe.db.set_value("Asan Login", asan_login_name, "main_token", new_token)
frappe.db.commit()
return {"success": True, "message": "Token renewed successfully"}
except Exception as e:
if retry_count < MAX_RETRY_COUNT:
wait_time = (2 ** retry_count) * (0.5 + random.random())
time.sleep(wait_time)
return renew_token(asan_login_name, retry_count + 1)
log_error_safe(f"Token renewal error: {str(e)}", f"Token Renewal for {asan_login_name}")
return {"success": False, "message": "Token renewal failed"}
@frappe.whitelist()
def setup_token_renewal():
"""Настраивает автоматическое обновление токенов"""
try:
job_exists = frappe.db.exists("Scheduled Job Type", "renew_etaxes_token")
if not job_exists:
doc = frappe.new_doc("Scheduled Job Type")
doc.update({
"name": "renew_etaxes_token",
"method": "invoice_az.auth.renew_token",
"frequency": "Cron",
"cron_format": "*/4 * * * *",
"create_log": 1,
"execute_alone": 1,
"enabled": 1
})
doc.insert(ignore_permissions=True)
frappe.db.commit()
return {"success": True, "message": "Scheduler job created"}
return {"success": True, "message": "Scheduler job already exists"}
except Exception as e:
log_error_safe(f"Error setting up token renewal: {str(e)}", "Token Renewal Setup Error")
return {"success": False, "message": str(e)}
@frappe.whitelist()
def check_token_validity(asan_login_name=None):
"""Проверяет валидность токена"""
record_etaxes_activity()
try:
if not asan_login_name:
default_settings = get_default_asan_login()
if not default_settings.get("found", False):
return {"valid": False, "message": "No Asan Login found."}
asan_login_name = default_settings.get("name")
asan_login = frappe.get_doc("Asan Login", asan_login_name)
if not asan_login.main_token:
return {"valid": False, "message": "No main token found."}
if not check_recent_activity(asan_login_name):
return {"valid": True, "message": "Token renewal skipped due to inactivity"}
# Импортируем функцию get_invoices из основного модуля
from .api import get_invoices
minimal_filter = {"maxCount": 1, "offset": 0}
invoices_result = get_invoices(asan_login.main_token, json.dumps(minimal_filter))
if invoices_result.get("error") == "unauthorized":
asan_login.auth_status = "Not Authenticated"
asan_login.save(ignore_permissions=True)
frappe.db.commit()
return {"valid": False, "message": "Authentication required. Main token is expired."}
return {"valid": True, "message": "Main token is valid."}
except Exception as e:
log_error_safe(f"Error checking token validity: {str(e)}", "Token Validity Check")
return {"valid": False, "message": "An unknown error occurred"}
# ======= АУТЕНТИФИКАЦИЯ =======
@frappe.whitelist()
def get_auth_token(phone, user_id):
"""Получает токен авторизации"""
record_etaxes_activity()
url = f"{BASE_URL}/auth/public/v1/asanImza/start"
headers = DEFAULT_HEADERS.copy()
headers["Content-Type"] = "application/json"
payload = {"phone": phone, "userId": user_id}
response = make_api_request(url, "POST", headers, json.dumps(payload))
if isinstance(response, dict) and "error" in response:
return response
bearer_token = response.headers.get('x-authorization', '')
resp_data = response.json()
return {'bearer_token': bearer_token, 'response_data': resp_data}
@frappe.whitelist()
def check_auth_status(bearer_token):
"""Проверяет статус авторизации"""
record_etaxes_activity()
url = f"{BASE_URL}/auth/public/v1/asanImza/status"
headers = DEFAULT_HEADERS.copy()
if bearer_token:
headers["x-authorization"] = f"Bearer {bearer_token}"
response = make_api_request(url, "GET", headers)
if isinstance(response, dict) and "error" in response:
return response
return response.json()
@frappe.whitelist()
def get_certificates(bearer_token):
"""Получает доступные сертификаты"""
record_etaxes_activity()
url = f"{BASE_URL}/auth/public/v1/asanImza/certificates"
headers = DEFAULT_HEADERS.copy()
headers["x-authorization"] = f"Bearer {bearer_token}"
response = make_api_request(url, "GET", headers)
if isinstance(response, dict) and "error" in response:
return response
return response.json()
@frappe.whitelist()
def choose_taxpayer(bearer_token, owner_type, tin):
"""Выбирает налогоплательщика"""
record_etaxes_activity()
url = f"{BASE_URL}/auth/public/v1/asanImza/chooseTaxpayer"
headers = DEFAULT_HEADERS.copy()
headers["Content-Type"] = "application/json"
headers["x-authorization"] = f"Bearer {bearer_token}"
payload = {
"ownerType": owner_type,
"legalTin": tin if owner_type == "legal" else None,
"individualFin": tin if owner_type == "individual" else None
}
payload = {k: v for k, v in payload.items() if v is not None}
response = make_api_request(url, "POST", headers, json.dumps(payload))
if isinstance(response, dict) and "error" in response:
return response
main_token = response.headers.get('x-authorization', '')
resp_data = response.json()
return {'main_token': main_token, 'response_data': resp_data}
# ======= ВЫСОКОУРОВНЕВЫЕ ФУНКЦИИ АУТЕНТИФИКАЦИИ =======
@frappe.whitelist()
def get_default_asan_login():
"""Получает настройки аутентификации по умолчанию"""
try:
default_login = frappe.get_list(
"Asan Login",
filters={"is_default": 1},
fields=["name"]
)
if default_login:
asan_login = frappe.get_doc("Asan Login", default_login[0].name)
return {
"found": True,
"name": asan_login.name,
"phone": asan_login.phone,
"user_id": asan_login.user_id,
"auth_status": asan_login.auth_status,
"bearer_token": asan_login.bearer_token,
"main_token": asan_login.main_token,
"selected_certificate": asan_login.selected_certificate
}
any_login = frappe.get_list("Asan Login", fields=["name"])
if any_login:
asan_login = frappe.get_doc("Asan Login", any_login[0].name)
return {
"found": True,
"name": asan_login.name,
"phone": asan_login.phone,
"user_id": asan_login.user_id,
"auth_status": asan_login.auth_status,
"bearer_token": asan_login.bearer_token,
"main_token": asan_login.main_token,
"selected_certificate": asan_login.selected_certificate
}
return {"found": False, "message": "No Asan Login settings found."}
except Exception as e:
return {"found": False, "error": str(e)}
@frappe.whitelist()
def handle_authentication(asan_login_name=None):
"""Обрабатывает процесс аутентификации"""
record_etaxes_activity()
try:
if not asan_login_name:
default_settings = get_default_asan_login()
if not default_settings.get("found", False):
return {"success": False, "message": "No Asan Login settings found."}
asan_login_name = default_settings.get("name")
asan_login = frappe.get_doc("Asan Login", asan_login_name)
if not asan_login.phone or not asan_login.user_id:
return {"success": False, "message": "Phone and User ID are required."}
auth_result = get_auth_token(asan_login.phone, asan_login.user_id)
if auth_result and auth_result.get("error"):
return {"success": False, "message": auth_result.get("message")}
if auth_result and auth_result.get("bearer_token"):
asan_login.bearer_token = auth_result.get("bearer_token")
asan_login.auth_status = "Waiting for confirmation"
asan_login.save()
verification_code = None
if auth_result.get("response_data") and isinstance(auth_result.get("response_data"), dict):
verification_code = auth_result.get("response_data").get("verificationCode", None)
return {
"success": True,
"message": "Authentication initiated. Please confirm on your phone.",
"bearer_token": auth_result.get("bearer_token"),
"asan_login_name": asan_login_name,
"verification_code": verification_code
}
return {"success": False, "message": "Failed to get authentication token."}
except Exception as e:
log_error_safe(f"Error in handle_authentication: {str(e)}", "Authentication Error")
return {"success": False, "message": "An unknown error occurred"}
@frappe.whitelist()
def poll_auth_status(asan_login_name, bearer_token):
"""Проверяет статус аутентификации"""
record_etaxes_activity()
try:
status_result = check_auth_status(bearer_token)
if status_result and status_result.get("error"):
return {"success": False, "message": status_result.get("message")}
asan_login = frappe.get_doc("Asan Login", asan_login_name)
if status_result and status_result.get("successful") == True:
asan_login.auth_status = "Authenticated"
if status_result.get("bearer_token"):
asan_login.bearer_token = status_result.get("bearer_token")
asan_login.save()
return {"success": True, "authenticated": True, "message": "Authentication successful."}
return {"success": True, "authenticated": False, "message": "Waiting for confirmation."}
except Exception as e:
return {"success": False, "authenticated": False, "message": "An unknown error occurred"}
@frappe.whitelist()
def get_auth_certificates(asan_login_name):
"""Получает сертификаты для указанного Asan Login"""
record_etaxes_activity()
try:
asan_login = frappe.get_doc("Asan Login", asan_login_name)
if asan_login.auth_status not in ["Authenticated", "Fully Authenticated"]:
return {"success": False, "message": "Not authenticated. Please login first."}
certificates_result = get_certificates(asan_login.bearer_token)
if certificates_result.get("error"):
return {"success": False, **certificates_result}
if certificates_result and certificates_result.get("certificates"):
asan_login.certificates_json = json.dumps(certificates_result.get("certificates"))
asan_login.save()
return {
"success": True,
"message": "Certificates retrieved successfully.",
"certificates": certificates_result.get("certificates")
}
return {"success": False, "message": "No certificates found."}
except Exception as e:
return {"success": False, "message": "An unknown error occurred"}
@frappe.whitelist()
def select_certificate(asan_login_name, certificate_data, certificate_name):
"""Выбирает сертификат"""
record_etaxes_activity()
try:
asan_login = frappe.get_doc("Asan Login", asan_login_name)
if asan_login.auth_status not in ["Authenticated", "Fully Authenticated"]:
return {"success": False, "message": "Not authenticated. Please login first."}
if isinstance(certificate_data, str):
certificate_data = json.loads(certificate_data)
asan_login.selected_certificate = certificate_name
asan_login.selected_certificate_json = json.dumps(certificate_data)
asan_login.save()
return {"success": True, "message": "Certificate selected successfully."}
except Exception as e:
log_error_safe(f"Error in select_certificate: {str(e)}", "Select Certificate Error")
return {"success": False, "message": "An unknown error occurred"}
@frappe.whitelist()
def select_taxpayer(asan_login_name):
"""Выбирает налогоплательщика"""
record_etaxes_activity()
try:
asan_login = frappe.get_doc("Asan Login", asan_login_name)
if asan_login.auth_status not in ["Authenticated", "Fully Authenticated"]:
return {"success": False, "message": "Not authenticated. Please login first."}
if not asan_login.selected_certificate_json:
return {"success": False, "message": "No certificate selected."}
cert_data = json.loads(asan_login.selected_certificate_json)
owner_type = cert_data.get("taxpayerType", "")
tin = ""
if owner_type == "individual" and cert_data.get("individualInfo"):
tin = cert_data.get("individualInfo", {}).get("fin", "")
owner_type = "individual"
elif cert_data.get("legalInfo"):
owner_type = "legal"
tin = cert_data.get("legalInfo", {}).get("tin", "") or cert_data.get("legalInfo", {}).get("voen", "")
if not owner_type or not tin:
return {"success": False, "message": "Invalid certificate data. Cannot determine taxpayer type or ID."}
taxpayer_result = choose_taxpayer(asan_login.bearer_token, owner_type, tin)
if taxpayer_result.get("error"):
return {"success": False, **taxpayer_result}
if taxpayer_result and taxpayer_result.get("main_token"):
asan_login.main_token = taxpayer_result.get("main_token")
asan_login.choose_taxpayer_response = json.dumps(taxpayer_result.get("response_data", {}))
asan_login.auth_status = "Fully Authenticated"
asan_login.save()
return {"success": True, "message": "Taxpayer selected successfully."}
return {"success": False, "message": "Failed to select taxpayer."}
except Exception as e:
log_error_safe(f"Error in select_taxpayer: {str(e)}", "Choose Taxpayer Exception")
return {"success": False, "message": "An unknown error occurred"}
@frappe.whitelist()
def test_api_endpoints(asan_login_name, endpoint_path, method="GET", payload=None):
"""Тестирует API endpoint'ы с разными токенами"""
record_etaxes_activity()
try:
if not asan_login_name:
default_settings = get_default_asan_login()
if not default_settings.get("found", False):
return {"success": False, "message": "No Asan Login settings found."}
asan_login_name = default_settings.get("name")
asan_login = frappe.get_doc("Asan Login", asan_login_name)
# Строим полный URL
if endpoint_path.startswith('http'):
url = endpoint_path
else:
endpoint_path = endpoint_path.lstrip('/')
url = f"{BASE_URL}/{endpoint_path}"
results = []
# Пробуем с main_token
if asan_login.main_token:
result = try_api_call(url, method, asan_login.main_token, payload, "main_token")
results.append(result)
# Пробуем с bearer_token
if asan_login.bearer_token:
result = try_api_call(url, method, asan_login.bearer_token, payload, "bearer_token")
results.append(result)
# Пробуем без токена
result = try_api_call(url, method, None, payload, "no_token")
results.append(result)
return {
"success": True,
"url": url,
"method": method.upper(),
"results": results
}
except Exception as e:
log_error_safe(f"Error in test_api_endpoints: {str(e)}", "API Test Error")
return {"success": False, "message": f"Error: {str(e)}"}
def try_api_call(url, method, token, payload, token_type):
"""Пробует API вызов с определенным токеном"""
try:
headers = DEFAULT_HEADERS.copy()
if token:
headers["x-authorization"] = f"Bearer {token}"
if method.upper() == "POST" and payload:
headers["Content-Type"] = "application/json"
# Делаем запрос
if method.upper() == "POST":
response = requests.post(url, headers=headers, data=payload, timeout=10)
else:
response = requests.get(url, headers=headers, timeout=10)
# Получаем данные ответа
try:
response_data = response.json()
except:
response_data = {"raw_response": response.text[:500]} # Первые 500 символов
return {
"token_type": token_type,
"status_code": response.status_code,
"success": response.status_code < 400,
"headers": dict(response.headers),
"data": response_data,
"token_preview": f"{token[:10]}...{token[-5:]}" if token else "No token"
}
except requests.RequestException as e:
return {
"token_type": token_type,
"status_code": None,
"success": False,
"error": str(e),
"token_preview": f"{token[:10]}...{token[-5:]}" if token else "No token"
}

1329
invoice_az/client/company.js Normal file

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

@ -1,26 +1,29 @@
/**
* Purchase Invoice Form and List Configuration
* Optimized version using ETaxes common module patterns
*/
frappe.ui.form.on('Purchase Invoice', {
refresh: function(frm) {
// Проверяем, установлена ли галочка is_taxes_doc
// Check if is_taxes_doc flag is set
if (frm.doc.is_taxes_doc) {
// Делаем все поля read-only
// Make all fields read-only
frm.set_read_only(true);
// Альтернативно, можно добавить визуальное предупреждение
// Add visual indicator
frm.page.set_indicator(__('Tax Document'), 'blue');
}
},
// Делаем поле is_taxes_doc тоже read-only при открытии документа
// Make is_taxes_doc field read-only when document loads
onload: function(frm) {
frm.set_df_property('is_taxes_doc', 'read_only', 1);
}
});
frappe.listview_settings['Purchase Invoice'] = {
onload: function(listview) {
// Добавляем поле как колонку (правильный способ)
// Add field as column (proper way)
listview.columns.push({
type: 'Check',
df: {
@ -30,16 +33,16 @@ frappe.listview_settings['Purchase Invoice'] = {
width: 120
});
// Принудительно обновляем список с новой колонкой
// Force refresh list with new column
listview.refresh();
},
// Форматируем отображение поля is_taxes_doc
// Format is_taxes_doc field display
formatters: {
is_taxes_doc: function(value) {
return value ?
`<span class="indicator-pill green"></span>` :
`<span class="indicator-pill gray"></span>`;
`<span class="indicator-pill green" title="${__('Tax Document')}"></span>` :
`<span class="indicator-pill gray" title="${__('Regular Document')}"></span>`;
}
}
};

View File

@ -31,7 +31,7 @@ ETaxes.utils = {
}
frappe.call({
method: 'invoice_az.api.get_default_asan_login',
method: 'invoice_az.auth.get_default_asan_login',
callback: function(r) {
if (r.message) {
ETaxes.cache.defaultLogin = r.message;
@ -48,7 +48,7 @@ ETaxes.utils = {
// Проверка валидности токена
checkTokenValidity: function(callback) {
frappe.call({
method: 'invoice_az.api.check_token_validity',
method: 'invoice_az.auth.check_token_validity',
callback: function(r) {
callback(r.message && r.message.valid);
},
@ -260,7 +260,7 @@ ETaxes.auth = {
);
frappe.call({
method: 'invoice_az.api.handle_authentication',
method: 'invoice_az.auth.handle_authentication',
args: { 'asan_login_name': asanLoginName },
callback: function(r) {
if (r.message && r.message.success) {
@ -330,7 +330,7 @@ ETaxes.auth = {
);
frappe.call({
method: 'invoice_az.api.poll_auth_status',
method: 'invoice_az.auth.poll_auth_status',
args: {
'asan_login_name': asanLoginName,
'bearer_token': bearerToken
@ -377,7 +377,7 @@ ETaxes.auth = {
);
frappe.call({
method: 'invoice_az.api.get_auth_certificates',
method: 'invoice_az.auth.get_auth_certificates',
args: { 'asan_login_name': asanLoginName },
callback: function(certR) {
if (certR.message && certR.message.success) {
@ -430,7 +430,7 @@ ETaxes.auth = {
// Выбор сертификата
_selectCertificate: function(asanLoginName, certData, certName, callback) {
frappe.call({
method: 'invoice_az.api.select_certificate',
method: 'invoice_az.auth.select_certificate',
args: {
'asan_login_name': asanLoginName,
'certificate_data': certData,
@ -445,7 +445,7 @@ ETaxes.auth = {
);
frappe.call({
method: 'invoice_az.api.select_taxpayer',
method: 'invoice_az.auth.select_taxpayer',
args: { 'asan_login_name': asanLoginName },
callback: function(r) {
if (r.message && r.message.success) {

View File

@ -1,26 +1,29 @@
/**
* Sales Invoice Form and List Configuration
* Optimized version using ETaxes common module patterns
*/
frappe.ui.form.on('Sales Invoice', {
refresh: function(frm) {
// Проверяем, установлена ли галочка is_taxes_doc
// Check if is_taxes_doc flag is set
if (frm.doc.is_taxes_doc) {
// Делаем все поля read-only
// Make all fields read-only
frm.set_read_only(true);
// Альтернативно, можно добавить визуальное предупреждение
// Add visual indicator
frm.page.set_indicator(__('Tax Document'), 'blue');
}
},
// Делаем поле is_taxes_doc тоже read-only при открытии документа
// Make is_taxes_doc field read-only when document loads
onload: function(frm) {
frm.set_df_property('is_taxes_doc', 'read_only', 1);
}
});
frappe.listview_settings['Sales Invoice'] = {
onload: function(listview) {
// Добавляем поле как колонку (правильный способ)
// Add field as column (proper way)
listview.columns.push({
type: 'Check',
df: {
@ -30,16 +33,16 @@ frappe.listview_settings['Sales Invoice'] = {
width: 120
});
// Принудительно обновляем список с новой колонкой
// Force refresh list with new column
listview.refresh();
},
// Форматируем отображение поля is_taxes_doc
// Format is_taxes_doc field display
formatters: {
is_taxes_doc: function(value) {
return value ?
`<span class="indicator-pill green"></span>` :
`<span class="indicator-pill gray"></span>`;
`<span class="indicator-pill green" title="${__('Tax Document')}"></span>` :
`<span class="indicator-pill gray" title="${__('Regular Document')}"></span>`;
}
}
};

View File

@ -31,7 +31,7 @@ SalesETaxes.utils = {
}
frappe.call({
method: 'invoice_az.api.get_default_asan_login',
method: 'invoice_az.auth.get_default_asan_login',
callback: function(r) {
if (r.message) {
SalesETaxes.cache.defaultLogin = r.message;
@ -48,7 +48,7 @@ SalesETaxes.utils = {
// Проверка валидности токена
checkTokenValidity: function(callback) {
frappe.call({
method: 'invoice_az.api.check_token_validity',
method: 'invoice_az.auth.check_token_validity',
callback: function(r) {
callback(r.message && r.message.valid);
},
@ -260,7 +260,7 @@ SalesETaxes.auth = {
);
frappe.call({
method: 'invoice_az.api.handle_authentication',
method: 'invoice_az.auth.handle_authentication',
args: { 'asan_login_name': asanLoginName },
callback: function(r) {
if (r.message && r.message.success) {
@ -330,7 +330,7 @@ SalesETaxes.auth = {
);
frappe.call({
method: 'invoice_az.api.poll_auth_status',
method: 'invoice_az.auth.poll_auth_status',
args: {
'asan_login_name': asanLoginName,
'bearer_token': bearerToken
@ -377,7 +377,7 @@ SalesETaxes.auth = {
);
frappe.call({
method: 'invoice_az.api.get_auth_certificates',
method: 'invoice_az.auth.get_auth_certificates',
args: { 'asan_login_name': asanLoginName },
callback: function(certR) {
if (certR.message && certR.message.success) {
@ -430,7 +430,7 @@ SalesETaxes.auth = {
// Выбор сертификата
_selectCertificate: function(asanLoginName, certData, certName, callback) {
frappe.call({
method: 'invoice_az.api.select_certificate',
method: 'invoice_az.auth.select_certificate',
args: {
'asan_login_name': asanLoginName,
'certificate_data': certData,
@ -445,7 +445,7 @@ SalesETaxes.auth = {
);
frappe.call({
method: 'invoice_az.api.select_taxpayer',
method: 'invoice_az.auth.select_taxpayer',
args: { 'asan_login_name': asanLoginName },
callback: function(r) {
if (r.message && r.message.success) {

View File

@ -0,0 +1,175 @@
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,7 +9,8 @@ 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"
"Sales Invoice": "client/sales_invoice.js",
"Company": "client/company.js"
}
doctype_list_js = {
@ -36,13 +37,13 @@ doc_events = {
scheduler_events = {
"cron": {
"*/4 * * * *": [
"invoice_az.api.renew_token"
"invoice_az.auth.renew_token"
]
}
}
after_install = "invoice_az.api.setup_token_renewal"
after_migrate = "invoice_az.api.setup_token_renewal"
after_install = "invoice_az.auth.setup_token_renewal"
after_migrate = "invoice_az.auth.setup_token_renewal"
# Apps
# ------------------

View File

@ -22,7 +22,7 @@ frappe.ui.form.on('Asan Login', {
// Отправляем запрос для начала процесса аутентификации
frappe.call({
method: 'invoice_az.api.handle_authentication',
method: 'invoice_az.auth.handle_authentication',
args: {
'asan_login_name': frm.doc.name
},
@ -66,7 +66,7 @@ frappe.ui.form.on('Asan Login', {
if (frm.doc.auth_status === 'Authenticated' || frm.doc.auth_status === 'Fully Authenticated') {
frm.add_custom_button(__('Get Certificates'), function() {
frappe.call({
method: 'invoice_az.api.get_auth_certificates',
method: 'invoice_az.auth.get_auth_certificates',
args: {
'asan_login_name': frm.doc.name
},
@ -107,7 +107,7 @@ frappe.ui.form.on('Asan Login', {
frm.doc.selected_certificate) {
frm.add_custom_button(__('Choose Taxpayer'), function() {
frappe.call({
method: 'invoice_az.api.select_taxpayer',
method: 'invoice_az.auth.select_taxpayer',
args: {
'asan_login_name': frm.doc.name
},
@ -301,7 +301,7 @@ function startPollingWithTimeout(frm, successCallback) {
// Проверяем статус авторизации
frappe.call({
method: 'invoice_az.api.poll_auth_status',
method: 'invoice_az.auth.poll_auth_status',
args: {
'asan_login_name': frm.doc.name,
'bearer_token': frm.doc.bearer_token
@ -473,7 +473,7 @@ function showCertificatesDialog(certificates, frm) {
// Отправляем запрос на выбор сертификата
frappe.call({
method: 'invoice_az.api.select_certificate',
method: 'invoice_az.auth.select_certificate',
args: {
'asan_login_name': frm.doc.name,
'certificate_data': cert,
@ -490,7 +490,7 @@ function showCertificatesDialog(certificates, frm) {
// После успешного выбора сертификата автоматически вызываем выбор налогоплательщика
frappe.call({
method: 'invoice_az.api.select_taxpayer',
method: 'invoice_az.auth.select_taxpayer',
args: {
'asan_login_name': frm.doc.name
},
@ -563,7 +563,7 @@ function showAuthDialog(frm) {
// Вызываем процесс аутентификации
frappe.call({
method: 'invoice_az.api.handle_authentication',
method: 'invoice_az.auth.handle_authentication',
args: {
'asan_login_name': frm.doc.name
},
@ -593,7 +593,7 @@ function showAuthDialog(frm) {
// Сначала восстанавливаем сертификат
frappe.call({
method: 'invoice_az.api.select_certificate',
method: 'invoice_az.auth.select_certificate',
args: {
'asan_login_name': frm.doc.name,
'certificate_data': certData,
@ -603,7 +603,7 @@ function showAuthDialog(frm) {
if (r.message && r.message.success) {
// Затем восстанавливаем налогоплательщика
frappe.call({
method: 'invoice_az.api.select_taxpayer',
method: 'invoice_az.auth.select_taxpayer',
args: {
'asan_login_name': frm.doc.name
},
@ -742,7 +742,7 @@ function pollAuthenticationStatus(frm, successCallback) {
// Проверяем статус авторизации
frappe.call({
method: 'invoice_az.api.poll_auth_status',
method: 'invoice_az.auth.poll_auth_status',
args: {
'asan_login_name': frm.doc.name,
'bearer_token': frm.doc.bearer_token