Fixed bug with reports not working in formulas
This commit is contained in:
parent
da3f4e75aa
commit
9fd6393388
|
|
@ -4,6 +4,7 @@ import copy
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import getdate, nowdate, flt
|
from frappe.utils import getdate, nowdate, flt
|
||||||
import re
|
import re
|
||||||
|
import importlib
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_doctype_fields(doctype):
|
def get_doctype_fields(doctype):
|
||||||
|
|
@ -26,7 +27,6 @@ def get_doctype_fields(doctype):
|
||||||
child_meta = frappe.get_meta(child.options)
|
child_meta = frappe.get_meta(child.options)
|
||||||
for sub_field in child_meta.fields:
|
for sub_field in child_meta.fields:
|
||||||
if sub_field.fieldtype in ["Int", "Float", "Currency"]:
|
if sub_field.fieldtype in ["Int", "Float", "Currency"]:
|
||||||
# Обычные поля табличной части (для суммирования)
|
|
||||||
fields.append({
|
fields.append({
|
||||||
"fieldname": f"{child.fieldname}_{sub_field.fieldname}",
|
"fieldname": f"{child.fieldname}_{sub_field.fieldname}",
|
||||||
"label": f"{child.label} → {sub_field.label}",
|
"label": f"{child.label} → {sub_field.label}",
|
||||||
|
|
@ -65,44 +65,262 @@ def get_all_doctype_fields(doctype):
|
||||||
|
|
||||||
return fields
|
return fields
|
||||||
|
|
||||||
|
def get_report_module(report_name):
|
||||||
|
"""Универсальное получение модуля отчета"""
|
||||||
|
try:
|
||||||
|
# Получаем документ отчета
|
||||||
|
report_doc = frappe.get_doc("Report", report_name)
|
||||||
|
|
||||||
|
if report_doc.report_type != "Script Report":
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Пробуем найти модуль отчета через различные пути
|
||||||
|
module_name = report_name.replace(" ", "_").replace("-", "_").lower()
|
||||||
|
|
||||||
|
# Возможные пути к модулям отчетов
|
||||||
|
possible_paths = [
|
||||||
|
# ERPNext модули
|
||||||
|
f"erpnext.accounts.report.{module_name}.{module_name}",
|
||||||
|
f"erpnext.selling.report.{module_name}.{module_name}",
|
||||||
|
f"erpnext.buying.report.{module_name}.{module_name}",
|
||||||
|
f"erpnext.stock.report.{module_name}.{module_name}",
|
||||||
|
f"erpnext.manufacturing.report.{module_name}.{module_name}",
|
||||||
|
f"erpnext.projects.report.{module_name}.{module_name}",
|
||||||
|
f"erpnext.hr.report.{module_name}.{module_name}",
|
||||||
|
f"erpnext.crm.report.{module_name}.{module_name}",
|
||||||
|
f"erpnext.assets.report.{module_name}.{module_name}",
|
||||||
|
f"erpnext.setup.report.{module_name}.{module_name}",
|
||||||
|
f"erpnext.utilities.report.{module_name}.{module_name}",
|
||||||
|
|
||||||
|
# Frappe модули
|
||||||
|
f"frappe.desk.query_report.{module_name}",
|
||||||
|
f"frappe.core.report.{module_name}.{module_name}",
|
||||||
|
|
||||||
|
# Пользовательские приложения - получаем список всех установленных приложений
|
||||||
|
]
|
||||||
|
|
||||||
|
# Добавляем пути для всех установленных приложений
|
||||||
|
apps = frappe.get_installed_apps()
|
||||||
|
for app in apps:
|
||||||
|
if app not in ['frappe', 'erpnext']: # уже добавлены выше
|
||||||
|
possible_paths.extend([
|
||||||
|
f"{app}.{app}.report.{module_name}.{module_name}",
|
||||||
|
f"{app}.report.{module_name}.{module_name}",
|
||||||
|
f"{app}.reports.{module_name}.{module_name}",
|
||||||
|
])
|
||||||
|
|
||||||
|
# Пробуем импортировать модуль
|
||||||
|
for path in possible_paths:
|
||||||
|
try:
|
||||||
|
module = importlib.import_module(path)
|
||||||
|
return module
|
||||||
|
except ImportError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(f"Error getting report module for {report_name}: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def on_trash(doc, method=None):
|
def get_report_filters_dynamic(report_name):
|
||||||
"""Удаляет клиентские скрипты, созданные для DocType при удалении Formula Editor"""
|
"""Универсальное получение фильтров отчета"""
|
||||||
if not doc.target_doctype:
|
if not report_name:
|
||||||
return
|
return []
|
||||||
|
|
||||||
# Ищем клиентские скрипты, созданные для целевого DocType
|
try:
|
||||||
client_scripts = frappe.get_all(
|
# Способ 1: Пробуем получить через модуль отчета
|
||||||
"Client Script",
|
module = get_report_module(report_name)
|
||||||
filters={
|
if module and hasattr(module, 'get_filters'):
|
||||||
"dt": doc.target_doctype,
|
try:
|
||||||
"name": ["like", "Formula_Editor_Script_for_%"]
|
filters_meta = module.get_filters() or []
|
||||||
},
|
if filters_meta:
|
||||||
fields=["name"]
|
return format_filters(filters_meta)
|
||||||
)
|
except Exception as e:
|
||||||
|
frappe.log_error(f"Error calling get_filters for {report_name}: {str(e)}")
|
||||||
|
|
||||||
|
# Способ 2: Получаем из метаданных отчета
|
||||||
|
report_doc = frappe.get_doc("Report", report_name)
|
||||||
|
if report_doc.report_type != "Script Report":
|
||||||
|
frappe.throw("Выбранный отчет должен быть типа 'Script Report'")
|
||||||
|
|
||||||
|
filters_meta = report_doc.get('filters') or []
|
||||||
|
if filters_meta:
|
||||||
|
return format_filters(filters_meta)
|
||||||
|
|
||||||
|
# Способ 3: Выполняем отчет с пустыми фильтрами и анализируем ошибки
|
||||||
|
# (этот способ сложен в реализации, поэтому возвращаем базовые фильтры)
|
||||||
|
return get_default_report_filters()
|
||||||
|
|
||||||
# Удаляем найденные скрипты
|
except Exception as e:
|
||||||
for script in client_scripts:
|
frappe.log_error(f"Error in get_report_filters_dynamic: {str(e)}")
|
||||||
|
return get_default_report_filters()
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_report_fields(report_name, filters=None):
|
||||||
|
"""Универсальное получение полей отчета"""
|
||||||
|
if not report_name:
|
||||||
|
frappe.throw("Не указано имя отчета")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Обрабатываем фильтры
|
||||||
|
if filters and isinstance(filters, str):
|
||||||
|
try:
|
||||||
|
filters = json.loads(filters)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
frappe.throw(f"Ошибка при разборе JSON фильтров: {filters}")
|
||||||
|
|
||||||
|
if not filters:
|
||||||
|
filters = get_basic_filters()
|
||||||
|
|
||||||
|
# Способ 1: Пробуем получить через модуль отчета
|
||||||
|
module = get_report_module(report_name)
|
||||||
|
if module and hasattr(module, 'get_columns'):
|
||||||
|
try:
|
||||||
|
columns = module.get_columns(filters) or []
|
||||||
|
if columns:
|
||||||
|
return format_columns(columns)
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(f"Error calling get_columns for {report_name}: {str(e)}")
|
||||||
|
|
||||||
|
# Способ 2: Выполняем отчет для получения колонок
|
||||||
|
from frappe.desk.query_report import run
|
||||||
|
|
||||||
|
# Добавляем базовые фильтры если их нет
|
||||||
|
if not filters.get('company'):
|
||||||
|
filters['company'] = frappe.defaults.get_user_default("Company") or frappe.db.get_single_value("Global Defaults", "default_company")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
frappe.delete_doc("Client Script", script.name, force=True)
|
result = run(report_name, filters=filters)
|
||||||
frappe.db.commit()
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass
|
# Если отчет не выполняется с фильтрами, пробуем с базовыми
|
||||||
|
try:
|
||||||
|
basic_filters = get_basic_filters()
|
||||||
|
result = run(report_name, filters=basic_filters)
|
||||||
|
except Exception as e2:
|
||||||
|
frappe.throw(f"Не удалось выполнить отчет: {str(e2)}")
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
frappe.throw("Отчет не вернул результатов")
|
||||||
|
|
||||||
|
columns = result.get('columns', [])
|
||||||
|
if not columns:
|
||||||
|
frappe.throw("В результатах отчета отсутствуют колонки")
|
||||||
|
|
||||||
|
return format_columns(columns)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.throw(f"Ошибка при получении полей отчета: {str(e)}")
|
||||||
|
|
||||||
|
def format_filters(filters_meta):
|
||||||
|
"""Форматирует фильтры в унифицированный формат"""
|
||||||
|
formatted_filters = []
|
||||||
|
|
||||||
|
for filter_meta in filters_meta:
|
||||||
|
# Обработка фильтров в формате списка [fieldname, label, fieldtype, options, default, reqd]
|
||||||
|
if isinstance(filter_meta, list) and len(filter_meta) > 0:
|
||||||
|
filter_data = {
|
||||||
|
'fieldname': filter_meta[0],
|
||||||
|
'label': filter_meta[1] if len(filter_meta) > 1 else filter_meta[0],
|
||||||
|
'fieldtype': filter_meta[2] if len(filter_meta) > 2 else 'Data',
|
||||||
|
'options': filter_meta[3] if len(filter_meta) > 3 else None,
|
||||||
|
'default': filter_meta[4] if len(filter_meta) > 4 else None,
|
||||||
|
'reqd': filter_meta[5] if len(filter_meta) > 5 else 0
|
||||||
|
}
|
||||||
|
formatted_filters.append(filter_data)
|
||||||
|
|
||||||
|
# Обработка фильтров в формате словаря
|
||||||
|
elif isinstance(filter_meta, dict) and filter_meta.get('fieldname'):
|
||||||
|
filter_data = {
|
||||||
|
'fieldname': filter_meta.get('fieldname'),
|
||||||
|
'label': filter_meta.get('label') or filter_meta.get('fieldname'),
|
||||||
|
'fieldtype': filter_meta.get('fieldtype') or 'Data',
|
||||||
|
'options': filter_meta.get('options'),
|
||||||
|
'default': filter_meta.get('default'),
|
||||||
|
'reqd': filter_meta.get('reqd') or 0,
|
||||||
|
'description': filter_meta.get('description')
|
||||||
|
}
|
||||||
|
formatted_filters.append(filter_data)
|
||||||
|
|
||||||
|
return formatted_filters
|
||||||
|
|
||||||
|
def format_columns(columns):
|
||||||
|
"""Форматирует колонки в унифицированный формат"""
|
||||||
|
fields = []
|
||||||
|
|
||||||
|
for i, col in enumerate(columns):
|
||||||
|
field_data = {}
|
||||||
|
|
||||||
|
if isinstance(col, dict):
|
||||||
|
field_data = {
|
||||||
|
"fieldname": col.get('fieldname') or f"column_{i}",
|
||||||
|
"label": col.get('label') or f"Column {i+1}",
|
||||||
|
"fieldtype": col.get('fieldtype') or "Data"
|
||||||
|
}
|
||||||
|
elif isinstance(col, str):
|
||||||
|
field_data = {
|
||||||
|
"fieldname": f"column_{i}",
|
||||||
|
"label": col,
|
||||||
|
"fieldtype": "Data"
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
field_data = {
|
||||||
|
"fieldname": f"column_{i}",
|
||||||
|
"label": f"Column {i+1}",
|
||||||
|
"fieldtype": "Data"
|
||||||
|
}
|
||||||
|
|
||||||
|
fields.append(field_data)
|
||||||
|
|
||||||
|
return fields
|
||||||
|
|
||||||
|
def get_basic_filters():
|
||||||
|
"""Возвращает базовые фильтры для выполнения отчетов"""
|
||||||
|
return {
|
||||||
|
'company': frappe.defaults.get_user_default("Company") or frappe.db.get_single_value("Global Defaults", "default_company"),
|
||||||
|
'from_date': frappe.utils.get_first_day(frappe.utils.nowdate()),
|
||||||
|
'to_date': frappe.utils.nowdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_default_report_filters():
|
||||||
|
"""Возвращает базовые фильтры по умолчанию для интерфейса"""
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'fieldname': 'company',
|
||||||
|
'label': 'Company',
|
||||||
|
'fieldtype': 'Link',
|
||||||
|
'options': 'Company',
|
||||||
|
'default': frappe.defaults.get_user_default("Company"),
|
||||||
|
'reqd': 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'fieldname': 'from_date',
|
||||||
|
'label': 'From Date',
|
||||||
|
'fieldtype': 'Date',
|
||||||
|
'default': frappe.utils.get_first_day(frappe.utils.nowdate()),
|
||||||
|
'reqd': 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'fieldname': 'to_date',
|
||||||
|
'label': 'To Date',
|
||||||
|
'fieldtype': 'Date',
|
||||||
|
'default': frappe.utils.nowdate(),
|
||||||
|
'reqd': 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def run_report_for_formula(report_name, filters, field_name=None):
|
def run_report_for_formula(report_name, filters, field_name=None):
|
||||||
"""Выполняет кастомный отчет с указанными фильтрами и возвращает результат для формулы"""
|
"""Универсальное выполнение отчета для формулы"""
|
||||||
try:
|
try:
|
||||||
# Парсим фильтры, если они переданы как строка
|
# Парсим фильтры, если они переданы как строка
|
||||||
if isinstance(filters, str):
|
if isinstance(filters, str):
|
||||||
filters = json.loads(filters)
|
filters = json.loads(filters)
|
||||||
|
|
||||||
# Обрабатываем специальные значения в фильтрах
|
|
||||||
processed_filters = process_filters_for_custom_reports(filters)
|
|
||||||
|
|
||||||
# Выполняем отчет
|
# Выполняем отчет
|
||||||
from frappe.desk.query_report import run
|
from frappe.desk.query_report import run
|
||||||
report_result = run(report_name, filters=processed_filters)
|
report_result = run(report_name, filters=filters)
|
||||||
|
|
||||||
# Проверяем результат на ошибки
|
# Проверяем результат на ошибки
|
||||||
if isinstance(report_result, dict) and report_result.get('message'):
|
if isinstance(report_result, dict) and report_result.get('message'):
|
||||||
|
|
@ -125,241 +343,8 @@ def run_report_for_formula(report_name, filters, field_name=None):
|
||||||
frappe.log_error(f"Error in run_report_for_formula: {str(e)}")
|
frappe.log_error(f"Error in run_report_for_formula: {str(e)}")
|
||||||
return {"error": str(e)}
|
return {"error": str(e)}
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_report_fields(report_name, filters=None):
|
|
||||||
"""Получает поля из кастомного отчета для использования в Formula Editor"""
|
|
||||||
if not report_name:
|
|
||||||
frappe.throw("Не указано имя отчета")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Если переданы фильтры, обрабатываем их
|
|
||||||
if filters and isinstance(filters, str):
|
|
||||||
try:
|
|
||||||
filters = json.loads(filters)
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
frappe.throw(f"Ошибка при разборе JSON фильтров: {filters}")
|
|
||||||
|
|
||||||
# Получаем метаданные отчета
|
|
||||||
report_doc = frappe.get_doc("Report", report_name)
|
|
||||||
|
|
||||||
# Проверяем, что это Script Report
|
|
||||||
if report_doc.report_type != "Script Report":
|
|
||||||
frappe.throw("Выбранный отчет должен быть типа 'Script Report'")
|
|
||||||
|
|
||||||
# Подготавливаем базовые фильтры
|
|
||||||
base_filters = prepare_base_filters(filters)
|
|
||||||
|
|
||||||
# Выполняем отчет для получения колонок
|
|
||||||
from frappe.desk.query_report import run
|
|
||||||
try:
|
|
||||||
result = run(report_name, filters=base_filters)
|
|
||||||
except Exception as e:
|
|
||||||
frappe.throw(f"Ошибка при выполнении отчета: {str(e)}")
|
|
||||||
|
|
||||||
if not result:
|
|
||||||
frappe.throw("Отчет не вернул результатов")
|
|
||||||
|
|
||||||
if not result.get('columns'):
|
|
||||||
frappe.throw("В результатах отчета отсутствуют колонки")
|
|
||||||
|
|
||||||
# Получаем колонки отчета
|
|
||||||
columns = result.get('columns', [])
|
|
||||||
fields = []
|
|
||||||
|
|
||||||
# Преобразуем колонки в удобный формат
|
|
||||||
for i, col in enumerate(columns):
|
|
||||||
field_data = {}
|
|
||||||
|
|
||||||
if isinstance(col, dict):
|
|
||||||
field_data = {
|
|
||||||
"fieldname": col.get('fieldname') or f"column_{i}",
|
|
||||||
"label": col.get('label') or f"Column {i+1}",
|
|
||||||
"fieldtype": col.get('fieldtype') or "Data"
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
# Для случая, когда колонка просто строка
|
|
||||||
field_data = {
|
|
||||||
"fieldname": f"column_{i}",
|
|
||||||
"label": col,
|
|
||||||
"fieldtype": "Data"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Добавляем только числовые поля и поля, которые могут быть преобразованы в числа
|
|
||||||
if is_numeric_field(field_data):
|
|
||||||
fields.append(field_data)
|
|
||||||
|
|
||||||
# Если нет явно числовых полей, анализируем данные
|
|
||||||
if not fields:
|
|
||||||
data = result.get('result', [])
|
|
||||||
fields = find_numeric_fields_in_data(data, columns)
|
|
||||||
|
|
||||||
if not fields:
|
|
||||||
frappe.throw(f"В отчете не найдено числовых полей. Пожалуйста, убедитесь, что в отчете есть поля с типом Currency, Float или Int.")
|
|
||||||
|
|
||||||
return fields
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
frappe.throw(f"Ошибка при получении полей отчета: {str(e)}")
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_report_filters_dynamic(report_name):
|
|
||||||
"""Получить доступные фильтры для указанного кастомного отчета"""
|
|
||||||
if not report_name:
|
|
||||||
return []
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Получаем документ отчета
|
|
||||||
report_doc = frappe.get_doc("Report", report_name)
|
|
||||||
|
|
||||||
# Проверяем, что отчет - script report
|
|
||||||
if report_doc.report_type != "Script Report":
|
|
||||||
frappe.throw("Выбранный отчет должен быть типа 'Script Report'")
|
|
||||||
|
|
||||||
# Получаем фильтры из метаданных отчета
|
|
||||||
filters_meta = report_doc.get('filters') or []
|
|
||||||
|
|
||||||
# Преобразуем фильтры в формат, удобный для создания полей
|
|
||||||
formatted_filters = []
|
|
||||||
|
|
||||||
for filter_meta in filters_meta:
|
|
||||||
# Обработка фильтров в формате списка
|
|
||||||
if isinstance(filter_meta, list) and len(filter_meta) > 0:
|
|
||||||
filter_data = {
|
|
||||||
'fieldname': filter_meta[0],
|
|
||||||
'label': filter_meta[1] if len(filter_meta) > 1 else filter_meta[0],
|
|
||||||
'fieldtype': filter_meta[2] if len(filter_meta) > 2 else 'Data',
|
|
||||||
'options': filter_meta[3] if len(filter_meta) > 3 else None,
|
|
||||||
'default': filter_meta[4] if len(filter_meta) > 4 else None,
|
|
||||||
'reqd': filter_meta[5] if len(filter_meta) > 5 else 0
|
|
||||||
}
|
|
||||||
formatted_filters.append(filter_data)
|
|
||||||
|
|
||||||
# Обработка фильтров в формате словаря
|
|
||||||
elif isinstance(filter_meta, dict) and filter_meta.get('fieldname'):
|
|
||||||
filter_data = {
|
|
||||||
'fieldname': filter_meta.get('fieldname'),
|
|
||||||
'label': filter_meta.get('label') or filter_meta.get('fieldname'),
|
|
||||||
'fieldtype': filter_meta.get('fieldtype') or 'Data',
|
|
||||||
'options': filter_meta.get('options'),
|
|
||||||
'default': filter_meta.get('default'),
|
|
||||||
'reqd': filter_meta.get('reqd') or 0,
|
|
||||||
'description': filter_meta.get('description')
|
|
||||||
}
|
|
||||||
formatted_filters.append(filter_data)
|
|
||||||
|
|
||||||
# Если фильтров нет в метаданных, добавляем базовые
|
|
||||||
if not formatted_filters:
|
|
||||||
formatted_filters = get_default_filters()
|
|
||||||
|
|
||||||
# Обеспечиваем наличие базовых фильтров
|
|
||||||
ensure_basic_filters(formatted_filters)
|
|
||||||
|
|
||||||
return formatted_filters
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
frappe.log_error(f"Error in get_report_filters_dynamic: {str(e)}")
|
|
||||||
return get_default_filters()
|
|
||||||
|
|
||||||
def process_filters_for_custom_reports(filters):
|
|
||||||
"""Обрабатывает фильтры специально для кастомных отчетов"""
|
|
||||||
processed_filters = {}
|
|
||||||
|
|
||||||
for key, value in filters.items():
|
|
||||||
# Обработка специального значения компании
|
|
||||||
if value == "$DefaultCompany":
|
|
||||||
processed_filters[key] = (
|
|
||||||
frappe.defaults.get_user_default("Company") or
|
|
||||||
frappe.db.get_single_value("Global Defaults", "default_company")
|
|
||||||
)
|
|
||||||
# Специальные значения дат и ссылки на поля документа обрабатываются на клиенте
|
|
||||||
elif value in ["$AyIlSpecial", "$RubIlSpecial"]:
|
|
||||||
# Эти значения должны быть уже обработаны на клиенте
|
|
||||||
continue
|
|
||||||
elif isinstance(value, str) and value.startswith("doc."):
|
|
||||||
# Ссылки на поля документа также обрабатываются на клиенте
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
# Обычные значения
|
|
||||||
processed_filters[key] = value
|
|
||||||
|
|
||||||
return processed_filters
|
|
||||||
|
|
||||||
def prepare_base_filters(filters):
|
|
||||||
"""Подготавливает базовые фильтры с значениями по умолчанию"""
|
|
||||||
if not filters:
|
|
||||||
filters = {}
|
|
||||||
|
|
||||||
# Автоматическая подстановка компании
|
|
||||||
if not filters.get("company"):
|
|
||||||
filters["company"] = (
|
|
||||||
frappe.defaults.get_user_default("Company") or
|
|
||||||
frappe.db.get_single_value("Global Defaults", "default_company")
|
|
||||||
)
|
|
||||||
|
|
||||||
# Автоматическая подстановка дат
|
|
||||||
if not filters.get("from_date"):
|
|
||||||
filters["from_date"] = frappe.utils.get_first_day(frappe.utils.nowdate())
|
|
||||||
|
|
||||||
if not filters.get("to_date"):
|
|
||||||
filters["to_date"] = frappe.utils.nowdate()
|
|
||||||
|
|
||||||
# Преобразование строковых дат в объекты дат
|
|
||||||
for date_field in ["from_date", "to_date"]:
|
|
||||||
if isinstance(filters.get(date_field), str):
|
|
||||||
try:
|
|
||||||
filters[date_field] = getdate(filters[date_field])
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return filters
|
|
||||||
|
|
||||||
def is_numeric_field(field_data):
|
|
||||||
"""Проверяет, является ли поле числовым"""
|
|
||||||
# Явно числовые типы
|
|
||||||
if field_data.get('fieldtype') in ['Int', 'Float', 'Currency', 'Percent']:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Поля с числовыми названиями
|
|
||||||
label = field_data.get('label', '').lower()
|
|
||||||
numeric_keywords = [
|
|
||||||
'amount', 'total', 'sum', 'qty', 'quantity', 'price', 'rate',
|
|
||||||
'cost', 'value', 'balance', 'count', 'number', 'percent',
|
|
||||||
'debit', 'credit', 'profit', 'loss', 'margin'
|
|
||||||
]
|
|
||||||
|
|
||||||
return any(keyword in label for keyword in numeric_keywords)
|
|
||||||
|
|
||||||
def find_numeric_fields_in_data(data, columns):
|
|
||||||
"""Ищет числовые поля в данных отчета"""
|
|
||||||
fields = []
|
|
||||||
|
|
||||||
if data and isinstance(data, list) and len(data) > 0:
|
|
||||||
# Если первая строка является словарем
|
|
||||||
if isinstance(data[0], dict):
|
|
||||||
for key, value in data[0].items():
|
|
||||||
if isinstance(value, (int, float)):
|
|
||||||
fields.append({
|
|
||||||
"fieldname": key,
|
|
||||||
"label": key.replace('_', ' ').title(),
|
|
||||||
"fieldtype": "Currency" if any(term in key.lower() for term in ['amount', 'total', 'balance']) else "Float"
|
|
||||||
})
|
|
||||||
# Если первая строка является списком
|
|
||||||
elif isinstance(data[0], list) and len(data[0]) == len(columns):
|
|
||||||
for i, value in enumerate(data[0]):
|
|
||||||
if isinstance(value, (int, float)) and i < len(columns):
|
|
||||||
col_name = f"column_{i}"
|
|
||||||
col_label = columns[i] if isinstance(columns[i], str) else (columns[i].get('label') or f"Column {i+1}")
|
|
||||||
|
|
||||||
fields.append({
|
|
||||||
"fieldname": col_name,
|
|
||||||
"label": col_label,
|
|
||||||
"fieldtype": "Currency" if any(term in col_label.lower() for term in ['amount', 'total', 'balance']) else "Float"
|
|
||||||
})
|
|
||||||
|
|
||||||
return fields
|
|
||||||
|
|
||||||
def extract_field_from_report(data, columns, field_name):
|
def extract_field_from_report(data, columns, field_name):
|
||||||
"""Извлекает значение конкретного поля из результатов отчета (упрощенная версия для кастомных отчетов)"""
|
"""Универсальное извлечение значения поля из результатов отчета"""
|
||||||
if not data:
|
if not data:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
@ -374,7 +359,7 @@ def extract_field_from_report(data, columns, field_name):
|
||||||
column_idx = i
|
column_idx = i
|
||||||
break
|
break
|
||||||
|
|
||||||
# Если колонка не найдена, ищем ее по любым подстрокам в имени
|
# Если колонка не найдена, ищем по частичному совпадению
|
||||||
if column_idx is None:
|
if column_idx is None:
|
||||||
for i, col in enumerate(columns):
|
for i, col in enumerate(columns):
|
||||||
col_name = ""
|
col_name = ""
|
||||||
|
|
@ -383,17 +368,14 @@ def extract_field_from_report(data, columns, field_name):
|
||||||
elif isinstance(col, str):
|
elif isinstance(col, str):
|
||||||
col_name = col
|
col_name = col
|
||||||
|
|
||||||
# Проверка на частичное совпадение
|
|
||||||
if field_name.lower() in col_name.lower():
|
if field_name.lower() in col_name.lower():
|
||||||
column_idx = i
|
column_idx = i
|
||||||
break
|
break
|
||||||
|
|
||||||
# Если колонка найдена, ищем итоговую строку
|
|
||||||
if column_idx is not None:
|
if column_idx is not None:
|
||||||
# Определяем возможные маркеры итоговых строк
|
# Ищем итоговые строки сначала
|
||||||
total_markers = ['total', 'итого', 'grand total', 'общий итог', 'итого по', 'total row']
|
total_markers = ['total', 'итого', 'grand total', 'общий итог', 'итого по', 'total row']
|
||||||
|
|
||||||
# Ищем итоговые строки
|
|
||||||
for row in data:
|
for row in data:
|
||||||
is_total_row = False
|
is_total_row = False
|
||||||
|
|
||||||
|
|
@ -446,23 +428,17 @@ def extract_field_from_report(data, columns, field_name):
|
||||||
count = 0
|
count = 0
|
||||||
|
|
||||||
for row in data:
|
for row in data:
|
||||||
|
value = None
|
||||||
if isinstance(row, dict) and field_name in row:
|
if isinstance(row, dict) and field_name in row:
|
||||||
value = row[field_name]
|
value = row[field_name]
|
||||||
elif not isinstance(row, dict) and column_idx < len(row):
|
elif isinstance(row, dict):
|
||||||
value = row[column_idx]
|
|
||||||
else:
|
|
||||||
# Поиск поля с игнорированием регистра
|
# Поиск поля с игнорированием регистра
|
||||||
if isinstance(row, dict):
|
for key in row:
|
||||||
found = False
|
if key.lower() == field_name.lower():
|
||||||
for key in row:
|
value = row[key]
|
||||||
if key.lower() == field_name.lower():
|
break
|
||||||
value = row[key]
|
elif isinstance(row, list) and column_idx < len(row):
|
||||||
found = True
|
value = row[column_idx]
|
||||||
break
|
|
||||||
if not found:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Преобразуем в число
|
# Преобразуем в число
|
||||||
try:
|
try:
|
||||||
|
|
@ -475,79 +451,31 @@ def extract_field_from_report(data, columns, field_name):
|
||||||
|
|
||||||
return total
|
return total
|
||||||
|
|
||||||
# Если колонка не найдена, возвращаем 0
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def get_default_filters():
|
@frappe.whitelist()
|
||||||
"""Возвращает базовые фильтры по умолчанию"""
|
def on_trash(doc, method=None):
|
||||||
return [
|
"""Удаляет клиентские скрипты, созданные для DocType при удалении Formula Editor"""
|
||||||
{
|
if not doc.target_doctype:
|
||||||
'fieldname': 'company',
|
return
|
||||||
'label': 'Company',
|
|
||||||
'fieldtype': 'Link',
|
# Ищем клиентские скрипты, созданные для целевого DocType
|
||||||
'options': 'Company',
|
client_scripts = frappe.get_all(
|
||||||
'default': frappe.defaults.get_user_default("Company"),
|
"Client Script",
|
||||||
'reqd': 1,
|
filters={
|
||||||
'hidden': 1
|
"dt": doc.target_doctype,
|
||||||
|
"name": ["like", "Formula_Editor_Script_for_%"]
|
||||||
},
|
},
|
||||||
{
|
fields=["name"]
|
||||||
'fieldname': 'from_date',
|
)
|
||||||
'label': 'From Date',
|
|
||||||
'fieldtype': 'Date',
|
|
||||||
'default': frappe.utils.get_first_day(frappe.utils.nowdate()),
|
|
||||||
'reqd': 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'fieldname': 'to_date',
|
|
||||||
'label': 'To Date',
|
|
||||||
'fieldtype': 'Date',
|
|
||||||
'default': frappe.utils.nowdate(),
|
|
||||||
'reqd': 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
def ensure_basic_filters(formatted_filters):
|
|
||||||
"""Обеспечивает наличие базовых фильтров"""
|
|
||||||
existing_fieldnames = [f['fieldname'] for f in formatted_filters]
|
|
||||||
|
|
||||||
# Добавляем компанию, если её нет
|
# Удаляем найденные скрипты
|
||||||
if 'company' not in existing_fieldnames:
|
for script in client_scripts:
|
||||||
formatted_filters.append({
|
try:
|
||||||
'fieldname': 'company',
|
frappe.delete_doc("Client Script", script.name, force=True)
|
||||||
'label': 'Company',
|
frappe.db.commit()
|
||||||
'fieldtype': 'Link',
|
except Exception as e:
|
||||||
'options': 'Company',
|
pass
|
||||||
'default': frappe.defaults.get_user_default("Company"),
|
|
||||||
'reqd': 1,
|
|
||||||
'hidden': 1 # Скрываем, так как обрабатывается автоматически
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
# Если компания есть, делаем её скрытой
|
|
||||||
for f in formatted_filters:
|
|
||||||
if f['fieldname'] == 'company':
|
|
||||||
f['hidden'] = 1
|
|
||||||
f['reqd'] = 0
|
|
||||||
if not f.get('default'):
|
|
||||||
f['default'] = frappe.defaults.get_user_default("Company")
|
|
||||||
|
|
||||||
# Добавляем даты, если их нет
|
|
||||||
if 'from_date' not in existing_fieldnames:
|
|
||||||
formatted_filters.append({
|
|
||||||
'fieldname': 'from_date',
|
|
||||||
'label': 'From Date',
|
|
||||||
'fieldtype': 'Date',
|
|
||||||
'default': frappe.utils.get_first_day(frappe.utils.nowdate()),
|
|
||||||
'reqd': 1
|
|
||||||
})
|
|
||||||
|
|
||||||
if 'to_date' not in existing_fieldnames:
|
|
||||||
formatted_filters.append({
|
|
||||||
'fieldname': 'to_date',
|
|
||||||
'label': 'To Date',
|
|
||||||
'fieldtype': 'Date',
|
|
||||||
'default': frappe.utils.nowdate(),
|
|
||||||
'reqd': 1
|
|
||||||
})
|
|
||||||
|
|
||||||
# Генерируем функцию для работы с отчетами для клиентского скрипта
|
# Генерируем функцию для работы с отчетами для клиентского скрипта
|
||||||
def generateReportFieldsFunction(report_fields):
|
def generateReportFieldsFunction(report_fields):
|
||||||
|
|
@ -556,9 +484,6 @@ def generateReportFieldsFunction(report_fields):
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
return f"""
|
return f"""
|
||||||
window.notifiedAboutAyIl = false;
|
|
||||||
window.notifiedAboutRubIl = false;
|
|
||||||
|
|
||||||
async function getReportFieldValue(frm, reportFieldName) {{
|
async function getReportFieldValue(frm, reportFieldName) {{
|
||||||
try {{
|
try {{
|
||||||
// Получаем информацию о поле отчета
|
// Получаем информацию о поле отчета
|
||||||
|
|
@ -566,6 +491,7 @@ async function getReportFieldValue(frm, reportFieldName) {{
|
||||||
const fieldInfo = reportFields[reportFieldName];
|
const fieldInfo = reportFields[reportFieldName];
|
||||||
|
|
||||||
if (!fieldInfo) {{
|
if (!fieldInfo) {{
|
||||||
|
console.warn('Report field not found:', reportFieldName);
|
||||||
return 0;
|
return 0;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
|
@ -574,92 +500,16 @@ async function getReportFieldValue(frm, reportFieldName) {{
|
||||||
try {{
|
try {{
|
||||||
filters = JSON.parse(fieldInfo.filters_json || '{{}}');
|
filters = JSON.parse(fieldInfo.filters_json || '{{}}');
|
||||||
}} catch (e) {{
|
}} catch (e) {{
|
||||||
|
console.error('Error parsing filters JSON:', e);
|
||||||
return 0;
|
return 0;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
// Проверка необходимости полей для специальных значений
|
// Обрабатываем фильтры с возможными ссылками на поля документа
|
||||||
if (Object.values(filters).some(v => v === "$AyIlSpecial")) {{
|
|
||||||
if (!frm.doc["ay"] || !frm.doc["il"]) {{
|
|
||||||
if (!window.notifiedAboutAyIl) {{
|
|
||||||
frappe.msgprint({{
|
|
||||||
title: "Not enough data",
|
|
||||||
message: "Please fill in the <b>ay</b> and <b>il</b> fields",
|
|
||||||
indicator: "orange"
|
|
||||||
}});
|
|
||||||
window.notifiedAboutAyIl = true;
|
|
||||||
}}
|
|
||||||
return 0;
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
|
|
||||||
if (Object.values(filters).some(v => v === "$RubIlSpecial")) {{
|
|
||||||
if (!frm.doc["rüb"] || !frm.doc["il"]) {{
|
|
||||||
if (!window.notifiedAboutRubIl) {{
|
|
||||||
frappe.msgprint({{
|
|
||||||
title: "Not enough data",
|
|
||||||
message: "Please fill in the <b>rüb</b> and <b>il</b> fields",
|
|
||||||
indicator: "orange"
|
|
||||||
}});
|
|
||||||
window.notifiedAboutRubIl = true;
|
|
||||||
}}
|
|
||||||
return 0;
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
|
|
||||||
// Обрабатываем фильтры
|
|
||||||
const processedFilters = {{}};
|
const processedFilters = {{}};
|
||||||
|
|
||||||
for (const key in filters) {{
|
for (const key in filters) {{
|
||||||
let value = filters[key];
|
let value = filters[key];
|
||||||
|
|
||||||
// Специальная обработка для $DefaultCompany
|
|
||||||
if (value === "$DefaultCompany") {{
|
|
||||||
processedFilters[key] = frm.doc.company || frappe.defaults.get_user_default("Company") || "";
|
|
||||||
continue;
|
|
||||||
}}
|
|
||||||
|
|
||||||
// Специальная обработка для $AyIlSpecial
|
|
||||||
if (value === "$AyIlSpecial") {{
|
|
||||||
if (frm.doc["ay"] !== undefined && frm.doc["il"] !== undefined) {{
|
|
||||||
const dateRange = processAzerbayjanDateField({{
|
|
||||||
ay: frm.doc["ay"],
|
|
||||||
il: frm.doc["il"]
|
|
||||||
}});
|
|
||||||
|
|
||||||
if (dateRange) {{
|
|
||||||
if (key.includes('from_date')) {{
|
|
||||||
processedFilters[key] = dateRange.start;
|
|
||||||
}} else if (key.includes('to_date')) {{
|
|
||||||
processedFilters[key] = dateRange.end;
|
|
||||||
}} else {{
|
|
||||||
processedFilters[key] = dateRange.start;
|
|
||||||
}}
|
|
||||||
continue;
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
|
|
||||||
// Специальная обработка для $RubIlSpecial
|
|
||||||
if (value === "$RubIlSpecial") {{
|
|
||||||
if (frm.doc["rüb"] !== undefined && frm.doc["il"] !== undefined) {{
|
|
||||||
const dateRange = processQuarterDateField({{
|
|
||||||
rüb: frm.doc["rüb"],
|
|
||||||
il: frm.doc["il"]
|
|
||||||
}});
|
|
||||||
|
|
||||||
if (dateRange) {{
|
|
||||||
if (key.includes('from_date')) {{
|
|
||||||
processedFilters[key] = dateRange.start;
|
|
||||||
}} else if (key.includes('to_date')) {{
|
|
||||||
processedFilters[key] = dateRange.end;
|
|
||||||
}} else {{
|
|
||||||
processedFilters[key] = dateRange.start;
|
|
||||||
}}
|
|
||||||
continue;
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
|
|
||||||
// Проверяем, является ли значение ссылкой на поле документа
|
// Проверяем, является ли значение ссылкой на поле документа
|
||||||
if (typeof value === 'string' && value.startsWith('doc.')) {{
|
if (typeof value === 'string' && value.startsWith('doc.')) {{
|
||||||
const docField = value.substring(4);
|
const docField = value.substring(4);
|
||||||
|
|
@ -696,8 +546,9 @@ async function getReportFieldValue(frm, reportFieldName) {{
|
||||||
freeze: false
|
freeze: false
|
||||||
}});
|
}});
|
||||||
|
|
||||||
if (result && result.message) {{
|
if (result && result.message !== undefined) {{
|
||||||
if (result.message.error) {{
|
if (result.message.error) {{
|
||||||
|
console.error('Report error:', result.message.error);
|
||||||
return 0;
|
return 0;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -125,7 +125,7 @@ function generateTransformExpressionFunction(universal_fields, report_fields) {
|
||||||
needsAsync = true;
|
needsAsync = true;
|
||||||
|
|
||||||
// Создаем уникальный плейсхолдер
|
// Создаем уникальный плейсхолдер
|
||||||
const placeholder = \`__REPORT_\${reportCalls.length}__\`;
|
const placeholder = \`__REPORT_PLACEHOLDER_\${reportCalls.length}__\`;
|
||||||
|
|
||||||
// Ищем соответствующее поле отчета по имени
|
// Ищем соответствующее поле отчета по имени
|
||||||
${Object.keys(report_fields).map(fieldId => {
|
${Object.keys(report_fields).map(fieldId => {
|
||||||
|
|
@ -179,7 +179,7 @@ function generateTransformExpressionFunction(universal_fields, report_fields) {
|
||||||
}).join('')}
|
}).join('')}
|
||||||
}
|
}
|
||||||
|
|
||||||
// === ИСПРАВЛЕНО: Обработка table(...) ===
|
// === Обработка table(...) ===
|
||||||
const tableRegex = /table\\(([^,)]+),\\s*([^,)]+),\\s*([^)]+)\\)/g;
|
const tableRegex = /table\\(([^,)]+),\\s*([^,)]+),\\s*([^)]+)\\)/g;
|
||||||
let tableMatch;
|
let tableMatch;
|
||||||
while ((tableMatch = tableRegex.exec(expression)) !== null) {
|
while ((tableMatch = tableRegex.exec(expression)) !== null) {
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1169,7 +1169,7 @@ function generateTableRowFormulasHandlers(table_row_formulas, functions, target_
|
||||||
|
|
||||||
functions.push(initFunction);
|
functions.push(initFunction);
|
||||||
|
|
||||||
// ИСПРАВЛЕННАЯ: Создаем функцию для расчета значения поля строки таблицы
|
// Создаем функцию для расчета значения поля строки таблицы
|
||||||
let calculateFunction = `
|
let calculateFunction = `
|
||||||
// Функция для расчета значения поля в строке табличной части
|
// Функция для расчета значения поля в строке табличной части
|
||||||
async function calculate_table_row_field(frm, tableName, rowIndex, fieldName, formula, triggerField) {
|
async function calculate_table_row_field(frm, tableName, rowIndex, fieldName, formula, triggerField) {
|
||||||
|
|
@ -1183,98 +1183,60 @@ async function calculate_table_row_field(frm, tableName, rowIndex, fieldName, fo
|
||||||
const cdt = frm.fields_dict[tableName].grid.doctype;
|
const cdt = frm.fields_dict[tableName].grid.doctype;
|
||||||
const cdn = row.name;
|
const cdn = row.name;
|
||||||
|
|
||||||
// === Обработка report() ===
|
// === ЕДИНАЯ ТОЧКА ОБРАБОТКИ ВСЕХ ФУНКЦИЙ ===
|
||||||
let processedFormula = formula;
|
let finalFormula, reportCalls;
|
||||||
if (formula.includes("report(")) {
|
|
||||||
// Регулярное выражение для поиска вызовов report()
|
|
||||||
const reportRegex = /report\\\\(([^)]+)\\\\)/g;
|
|
||||||
let match;
|
|
||||||
const reportPromises = [];
|
|
||||||
const reportPlaceholders = [];
|
|
||||||
|
|
||||||
// Находим все вызовы report() и заменяем их на плейсхолдеры
|
|
||||||
while ((match = reportRegex.exec(formula)) !== null) {
|
|
||||||
const fullMatch = match[0]; // report(fieldName)
|
|
||||||
const fieldName = match[1]; // fieldName
|
|
||||||
|
|
||||||
// Создаем уникальный плейсхолдер
|
|
||||||
const placeholder = \`__REPORT_PLACEHOLDER_\${reportPromises.length}__\`;
|
|
||||||
|
|
||||||
// Ищем данные отчета в хранилище или используем предопределенный отчет
|
|
||||||
let reportName, reportFilters, reportField, reportAggregation;
|
|
||||||
|
|
||||||
if (fieldName === "testt") {
|
|
||||||
reportName = "General Ledger";
|
|
||||||
reportFilters = {"company":"$DefaultCompany","from_date":"$AyIlSpecial","to_date":"$AyIlSpecial"};
|
|
||||||
reportField = "debit";
|
|
||||||
reportAggregation = "sum";
|
|
||||||
} else {
|
|
||||||
// Ищем в хранилище полей отчетов
|
|
||||||
const reportFields = JSON.parse(frm.doc.report_fields_storage || "{}");
|
|
||||||
const reportObj = Object.values(reportFields).find(f => f.name === fieldName);
|
|
||||||
|
|
||||||
if (reportObj) {
|
|
||||||
reportName = reportObj.report;
|
|
||||||
reportFilters = JSON.parse(reportObj.filters_json || "{}");
|
|
||||||
reportField = reportObj.field_name;
|
|
||||||
reportAggregation = reportObj.aggregation || 'sum';
|
|
||||||
} else {
|
|
||||||
processedFormula = processedFormula.replace(fullMatch, "0");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Добавляем промис запроса к отчету
|
|
||||||
reportPromises.push(
|
|
||||||
getReportFieldValue(
|
|
||||||
frm,
|
|
||||||
reportName,
|
|
||||||
reportFilters,
|
|
||||||
reportField,
|
|
||||||
reportAggregation
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Запоминаем соответствие плейсхолдера и вызова
|
|
||||||
reportPlaceholders.push({
|
|
||||||
placeholder: placeholder,
|
|
||||||
original: fullMatch
|
|
||||||
});
|
|
||||||
|
|
||||||
// Заменяем вызов отчета на плейсхолдер
|
|
||||||
processedFormula = processedFormula.replace(fullMatch, placeholder);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Если нашли вызовы отчетов, ждем выполнения всех запросов
|
|
||||||
if (reportPromises.length > 0) {
|
|
||||||
const reportResults = await Promise.all(reportPromises);
|
|
||||||
|
|
||||||
// Заменяем плейсхолдеры на результаты
|
|
||||||
for (let i = 0; i < reportPlaceholders.length; i++) {
|
|
||||||
// Убедимся, что результат - числовое значение
|
|
||||||
const reportValue = reportResults[i] !== undefined ? Number(reportResults[i]) : 0;
|
|
||||||
processedFormula = processedFormula.replace(
|
|
||||||
reportPlaceholders[i].placeholder,
|
|
||||||
reportValue
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// === ИСПРАВЛЕНИЕ: Используем transform_expression для обработки всех функций включая table() ===
|
|
||||||
let finalFormula = processedFormula;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Сначала применяем transform_expression для обработки table() и других вызовов
|
// Используем transform_expression как единственную точку преобразования
|
||||||
const transformResult = transform_expression(processedFormula, true, tableName);
|
const transformResult = transform_expression(formula, true, tableName);
|
||||||
if (transformResult && transformResult.transformed) {
|
|
||||||
|
if (transformResult) {
|
||||||
finalFormula = transformResult.transformed;
|
finalFormula = transformResult.transformed;
|
||||||
|
reportCalls = transformResult.reportCalls || [];
|
||||||
|
} else {
|
||||||
|
finalFormula = formula;
|
||||||
|
reportCalls = [];
|
||||||
}
|
}
|
||||||
} catch (transformError) {
|
} catch (transformError) {
|
||||||
console.error('Ошибка transform_expression:', transformError);
|
console.error('Ошибка transform_expression:', transformError);
|
||||||
|
finalFormula = formula;
|
||||||
|
reportCalls = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Вычисление выражения с доступом к объекту row
|
// === ОБРАБОТКА АСИНХРОННЫХ ВЫЗОВОВ ОТЧЕТОВ ===
|
||||||
|
if (reportCalls && reportCalls.length > 0) {
|
||||||
|
try {
|
||||||
|
// Выполняем все асинхронные запросы к отчетам параллельно
|
||||||
|
const reportResults = await Promise.all(
|
||||||
|
reportCalls.map(call =>
|
||||||
|
getReportFieldValue(
|
||||||
|
frm,
|
||||||
|
call.reportName,
|
||||||
|
call.filters,
|
||||||
|
call.fieldName,
|
||||||
|
call.aggregation
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Заменяем плейсхолдеры на реальные значения
|
||||||
|
for (let i = 0; i < reportCalls.length; i++) {
|
||||||
|
const reportValue = reportResults[i] !== undefined ? Number(reportResults[i]) : 0;
|
||||||
|
finalFormula = finalFormula.replace(
|
||||||
|
reportCalls[i].placeholder,
|
||||||
|
reportValue
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (reportError) {
|
||||||
|
console.error('Ошибка получения данных отчетов:', reportError);
|
||||||
|
// В случае ошибки заменяем все плейсхолдеры на 0
|
||||||
|
for (const call of reportCalls) {
|
||||||
|
finalFormula = finalFormula.replace(call.placeholder, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === ВЫЧИСЛЕНИЕ ФИНАЛЬНОЙ ФОРМУЛЫ ===
|
||||||
let result = 0;
|
let result = 0;
|
||||||
try {
|
try {
|
||||||
// Создаем функцию с доступом к контексту row и frm
|
// Создаем функцию с доступом к контексту row и frm
|
||||||
|
|
@ -1298,7 +1260,6 @@ async function calculate_table_row_field(frm, tableName, rowIndex, fieldName, fo
|
||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
|
||||||
functions.push(calculateFunction);
|
functions.push(calculateFunction);
|
||||||
|
|
||||||
// Функция для получения индекса строки таблицы
|
// Функция для получения индекса строки таблицы
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue