feat(etaxes): classification code + explanation custom fields; quieter asset-category logs

Add read-only custom fields etaxes_classification_code (Təsnifat Kodu)
and etaxes_explanation on Journal Entry for E-Taxes VAT imports, with
matching translation markers.

Move asset-category creation logging from frappe.log_error (which spammed
the Error Log DocType) to a file logger; keep log_error only for real
creation failures. Drop the manual frappe.db.commit inside the
Company.on_update path so the framework keeps the save atomic.

Reuse the realtime progress-bar race fix in currency_exchange_list.js.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ali 2026-06-12 16:19:22 +00:00
parent 4e8a151851
commit 4926d5c637
4 changed files with 99 additions and 42 deletions

View File

@ -1,6 +1,16 @@
import frappe
def _logger():
"""Файловый логгер для процесса создания категорий активов.
Используем frappe.logger() для info/debug-сообщений (пишутся в
logs/asset_categories.log), а frappe.log_error() оставляем только
для реальных ошибок (записи в Error Log DocType).
"""
return frappe.logger("asset_categories", allow_site=True, file_count=20)
def get_system_asset_categories():
"""
Возвращает список системных категорий активов
@ -79,25 +89,24 @@ def create_asset_categories_on_company_update(doc, method):
# Проверяем первую категорию из списка
if frappe.db.exists("Asset Category", {"asset_category_name": system_categories[0]}):
# Категории уже созданы (они создаются для всех компаний сразу)
frappe.log_error(
f"Asset categories already exist, skipping creation\nCompany: {company_name}\nFirst category found: {system_categories[0]}",
"AC Skip Existing"
_logger().info(
f"Asset categories already exist, skipping creation. "
f"Company: {company_name}, first category found: {system_categories[0]}"
)
return
# Логируем начало создания
frappe.log_error(
f"Creating asset categories automatically\nTriggered by: Company.on_update\nCompany: {company_name}",
"AC Auto Create"
_logger().info(
f"Creating asset categories automatically (Company.on_update). Company: {company_name}"
)
try:
# Создаем категории
_create_categories_for_company(company_name)
frappe.log_error(
f"Asset categories created successfully\nCompany: {company_name}\nCategories: {len(system_categories)}",
"AC Created"
_logger().info(
f"Asset categories created successfully. "
f"Company: {company_name}, categories: {len(system_categories)}"
)
except Exception as e:
import traceback
@ -116,10 +125,7 @@ def create_asset_categories_for_company(company_name):
Args:
company_name: Название компании
"""
frappe.log_error(
f"Function started\nCompany: {company_name}",
"AC Create Start"
)
_logger().info(f"create_asset_categories_for_company started. Company: {company_name}")
try:
# Проверяем, что компания существует
@ -132,10 +138,7 @@ def create_asset_categories_for_company(company_name):
)
return
frappe.log_error(
f"Company exists in database\nCompany: {company_name}",
"AC Company OK"
)
_logger().debug(f"Company exists in database. Company: {company_name}")
# Проверка идемпотентности: если категории уже есть, не создаем
system_categories = get_system_asset_categories()
@ -144,28 +147,27 @@ def create_asset_categories_for_company(company_name):
filters={"asset_category_name": ["in", system_categories]}
)
frappe.log_error(
f"Idempotency check\nSystem categories: {len(system_categories)}\nExisting categories: {existing_categories}\nCategories list: {system_categories}",
"AC Check Existing"
_logger().debug(
f"Idempotency check. System categories: {len(system_categories)}, "
f"existing: {existing_categories}"
)
if existing_categories > 0:
frappe.log_error(
f"Categories already exist, skipping creation\nFound: {existing_categories} of {len(system_categories)}",
"AC Already Exist"
_logger().info(
f"Categories already exist, skipping creation. "
f"Found: {existing_categories} of {len(system_categories)}"
)
return
# Создаем категории
frappe.log_error(
f"Starting category creation\nCompany: {company_name}\nCategories to create: {len(system_categories)}",
"AC Creating"
_logger().info(
f"Starting category creation. Company: {company_name}, "
f"categories to create: {len(system_categories)}"
)
_create_categories_for_company(company_name)
frappe.log_error(
f"Asset categories creation completed successfully\nCompany: {company_name}",
"AC Complete"
_logger().info(
f"Asset categories creation completed successfully. Company: {company_name}"
)
except Exception as e:
@ -185,10 +187,7 @@ def _create_categories_for_company(company_name):
asset_categories = get_system_asset_categories()
# Находим нужные счета
frappe.log_error(
f"Searching for required accounts\nCompany: {company_name}\nAccounts needed: 3",
"AC Search Accounts"
)
_logger().debug(f"Searching for required accounts. Company: {company_name}, needed: 3")
try:
accumulated_depreciation = get_account_by_name(
@ -204,9 +203,10 @@ def _create_categories_for_company(company_name):
company_name
)
frappe.log_error(
f"All required accounts found\nCompany: {company_name}\n\nAccounts:\n1. Accumulated Depreciation: {accumulated_depreciation}\n2. Depreciation Expense: {depreciation_expense}\n3. Fixed Asset: {fixed_asset}",
"AC Accounts OK"
_logger().debug(
f"All required accounts found. Company: {company_name}, "
f"fixed_asset: {fixed_asset}, accumulated_depreciation: {accumulated_depreciation}, "
f"depreciation_expense: {depreciation_expense}"
)
except Exception as e:
@ -250,14 +250,22 @@ def _create_categories_for_company(company_name):
f"AC Cat ERROR {idx}"
)
frappe.db.commit()
# NB: commit намеренно НЕ вызываем. В активном пути (Company.on_update)
# транзакцию коммитит сам фреймворк; ручной commit внутри хука разрывает
# атомарность сохранения Company. В фоновой задаче (frappe.enqueue) и в
# патчах commit тоже выполняется обёрткой после успешного завершения.
# Финальный отчет
status = "SUCCESS" if created_count > 0 else ("SKIP" if skipped_count > 0 else "ERROR")
frappe.log_error(
f"Category creation finished\nCompany: {company_name}\nCreated: {created_count}\nSkipped: {skipped_count}\nErrors: {len(errors)}\n\nError details:\n{chr(10).join(errors) if errors else 'No errors'}",
f"AC {status}"
summary = (
f"Category creation finished [{status}]. Company: {company_name}, "
f"created: {created_count}, skipped: {skipped_count}, errors: {len(errors)}"
)
if errors:
# Реальные ошибки создания категорий — в Error Log, чтобы были заметны.
frappe.log_error(summary + "\n\n" + "\n".join(errors), f"AC {status}")
else:
_logger().info(summary)
def prevent_delete_system_asset_categories(doc, method):

View File

@ -1054,6 +1054,29 @@ def create_custom_fields():
hidden=1,
read_only=1,
no_copy=1
),
dict(
fieldname='etaxes_classification_code',
label='Təsnifat Kodu',
fieldtype='Link',
options='Classification code',
insert_after='loaded_from_etaxes',
read_only=1,
no_copy=1,
translatable=0,
description='Təsnifat kodu (taxCodeInfo.code) imported from E-Taxes'
),
dict(
fieldname='etaxes_explanation',
label='Explanation',
fieldtype='Small Text',
insert_after='etaxes_classification_code',
in_list_view=1,
columns=3,
read_only=1,
no_copy=1,
translatable=0,
description='Explanation imported from E-Taxes VAT operation'
)
],
"Bank": [

View File

@ -50,10 +50,31 @@ function _show_import_dialog(available_currencies) {
d.hide();
// Защита от гонки realtime: 'complete' вызывает hide_progress() (обнуляет
// frappe.cur_progress). Опоздавший 'progress' иначе пересоздаёт модал, который
// уже никто не закрывает. Флаг делает обработчик прогресса no-op после finish.
let importFinished = false;
// Надёжное закрытие прогресс-бара: frappe.hide_progress() в этом окружении
// (jey_theme/Bootstrap) не всегда убирает модал — он зависает. Принудительно.
const closeProgress = function () {
if (frappe.cur_progress) {
try {
frappe.cur_progress.$wrapper.modal("hide");
frappe.cur_progress.$wrapper.remove();
} catch (e) {}
frappe.cur_progress = null;
}
if ($(".modal:visible").length === 0) {
$(".modal-backdrop").remove();
$("body").removeClass("modal-open");
}
};
frappe.realtime.off("cbar_import_progress");
frappe.realtime.off("cbar_import_complete");
frappe.realtime.on("cbar_import_progress", function (data) {
if (importFinished) return;
frappe.show_progress(
__("Importing CBAR Rates"),
data.current,
@ -63,9 +84,10 @@ function _show_import_dialog(available_currencies) {
});
frappe.realtime.on("cbar_import_complete", function (data) {
importFinished = true;
frappe.realtime.off("cbar_import_progress");
frappe.realtime.off("cbar_import_complete");
frappe.hide_progress();
closeProgress();
const { created, updated, skipped_dates } = data;
let msg = `<b>${__("Import complete")}</b><br>${__("Created")}: ${created}<br>${__("Updated")}: ${updated}`;
@ -89,7 +111,7 @@ function _show_import_dialog(available_currencies) {
error: function () {
frappe.realtime.off("cbar_import_progress");
frappe.realtime.off("cbar_import_complete");
frappe.hide_progress();
closeProgress();
}
});
}

View File

@ -77,6 +77,8 @@ _lt("Employer Name")
_lt("Employer Position")
_lt("Enter the tax-exempt area value")
_lt("Expense/Income")
_lt("Explanation")
_lt("Explanation imported from E-Taxes VAT operation")
_lt("FIN")
_lt("First Name")
_lt("For agricultural: hectares. For industrial: square meters.")
@ -178,6 +180,8 @@ _lt("Taxable Assets Information")
_lt("Taxation system")
_lt("Taxpayer Activity Group")
_lt("Type of act for e-taxes")
_lt("Təsnifat Kodu")
_lt("Təsnifat kodu (taxCodeInfo.code) imported from E-Taxes")
_lt("Used as the Expense Account when this service item is selected in a Landed Cost Voucher.")
_lt("Uçot metodu (Accounting Method)")
_lt("VAT 0% with amount")