mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-15 15:38:39 +00:00
feat(general-ledger): implement execute_synced_report with full parity to normal report
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
(cherry picked from commit 6b4895bcc9)
This commit is contained in:
@@ -17,7 +17,7 @@
|
|||||||
"generate_csv": 0,
|
"generate_csv": 0,
|
||||||
"idx": 4,
|
"idx": 4,
|
||||||
"is_standard": "Yes",
|
"is_standard": "Yes",
|
||||||
"modified": "2026-06-18 11:53:29.057634",
|
"modified": "2026-06-22 11:50:08.020553",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "General Ledger",
|
"name": "General Ledger",
|
||||||
|
|||||||
@@ -820,10 +820,286 @@ def get_columns(filters):
|
|||||||
return columns
|
return columns
|
||||||
|
|
||||||
|
|
||||||
def execute_duckdb(filters, duckdb_conn):
|
def execute_synced_report(filters):
|
||||||
print(filters)
|
from frappe.database.duckdb.database import get_latest_sync
|
||||||
conn = duckdb_conn
|
|
||||||
columns = get_columns(filters)
|
|
||||||
res = []
|
|
||||||
|
|
||||||
|
if conn := get_latest_sync("GL Entry"):
|
||||||
|
return _execute_with_duckdb_conn(filters, conn)
|
||||||
|
|
||||||
|
frappe.throw(_("General Ledger requires {0} to be synced to DuckDB").format(frappe.bold("GL Entry")))
|
||||||
|
|
||||||
|
|
||||||
|
def _execute_with_duckdb_conn(filters, conn):
|
||||||
|
if not filters:
|
||||||
|
return [], []
|
||||||
|
|
||||||
|
account_details = {}
|
||||||
|
|
||||||
|
if filters.get("print_in_account_currency") and not filters.get("account"):
|
||||||
|
frappe.throw(_("Select an account to print in account currency"))
|
||||||
|
|
||||||
|
for acc in frappe.get_all("Account", fields=["name", "is_group"]):
|
||||||
|
account_details.setdefault(acc.name, acc)
|
||||||
|
|
||||||
|
if filters.get("party"):
|
||||||
|
filters.party = frappe.parse_json(filters.get("party"))
|
||||||
|
|
||||||
|
validate_filters(filters, account_details)
|
||||||
|
validate_party(filters)
|
||||||
|
filters = set_account_currency(filters)
|
||||||
|
columns = get_columns(filters)
|
||||||
|
res = get_result_duckdb(filters, account_details, conn)
|
||||||
return columns, res
|
return columns, res
|
||||||
|
|
||||||
|
|
||||||
|
def get_result_duckdb(filters, account_details, conn):
|
||||||
|
accounting_dimensions = []
|
||||||
|
if filters.get("include_dimensions"):
|
||||||
|
accounting_dimensions = get_accounting_dimensions()
|
||||||
|
|
||||||
|
gl_entries = get_gl_entries_duckdb(filters, accounting_dimensions, conn)
|
||||||
|
data = get_data_with_opening_closing(filters, account_details, accounting_dimensions, gl_entries)
|
||||||
|
return get_result_as_list(data, filters)
|
||||||
|
|
||||||
|
|
||||||
|
def get_gl_entries_duckdb(filters, accounting_dimensions, conn):
|
||||||
|
currency_map = get_currency(filters)
|
||||||
|
|
||||||
|
col_names = [
|
||||||
|
"gl_entry",
|
||||||
|
"posting_date",
|
||||||
|
"account",
|
||||||
|
"party_type",
|
||||||
|
"party",
|
||||||
|
"voucher_type",
|
||||||
|
"voucher_subtype",
|
||||||
|
"voucher_no",
|
||||||
|
"cost_center",
|
||||||
|
"project",
|
||||||
|
"against_voucher_type",
|
||||||
|
"against_voucher",
|
||||||
|
"account_currency",
|
||||||
|
"against",
|
||||||
|
"is_opening",
|
||||||
|
"creation",
|
||||||
|
"debit",
|
||||||
|
"credit",
|
||||||
|
"debit_in_account_currency",
|
||||||
|
"credit_in_account_currency",
|
||||||
|
]
|
||||||
|
select_exprs = [
|
||||||
|
"name",
|
||||||
|
"posting_date",
|
||||||
|
"account",
|
||||||
|
"party_type",
|
||||||
|
"party",
|
||||||
|
"voucher_type",
|
||||||
|
"voucher_subtype",
|
||||||
|
"voucher_no",
|
||||||
|
"cost_center",
|
||||||
|
"project",
|
||||||
|
"against_voucher_type",
|
||||||
|
"against_voucher",
|
||||||
|
"account_currency",
|
||||||
|
"against",
|
||||||
|
"is_opening",
|
||||||
|
"creation",
|
||||||
|
"debit",
|
||||||
|
"credit",
|
||||||
|
"debit_in_account_currency",
|
||||||
|
"credit_in_account_currency",
|
||||||
|
]
|
||||||
|
|
||||||
|
if filters.get("show_remarks"):
|
||||||
|
remarks_length = frappe.get_single_value("Accounts Settings", "general_ledger_remarks_length")
|
||||||
|
if remarks_length:
|
||||||
|
select_exprs.append(f"substr(remarks, 1, {int(remarks_length)})")
|
||||||
|
else:
|
||||||
|
select_exprs.append("remarks")
|
||||||
|
col_names.append("remarks")
|
||||||
|
|
||||||
|
if filters.get("add_values_in_transaction_currency"):
|
||||||
|
select_exprs += [
|
||||||
|
"debit_in_transaction_currency",
|
||||||
|
"credit_in_transaction_currency",
|
||||||
|
"transaction_currency",
|
||||||
|
]
|
||||||
|
col_names += [
|
||||||
|
"debit_in_transaction_currency",
|
||||||
|
"credit_in_transaction_currency",
|
||||||
|
"transaction_currency",
|
||||||
|
]
|
||||||
|
|
||||||
|
if accounting_dimensions:
|
||||||
|
select_exprs += accounting_dimensions
|
||||||
|
col_names += accounting_dimensions
|
||||||
|
|
||||||
|
order_by = "posting_date, account, creation"
|
||||||
|
if filters.get("include_dimensions"):
|
||||||
|
order_by = "posting_date, creation"
|
||||||
|
if filters.get("categorize_by") == "Categorize by Voucher":
|
||||||
|
order_by = "posting_date, voucher_type, voucher_no"
|
||||||
|
if filters.get("categorize_by") == "Categorize by Account":
|
||||||
|
order_by = "account, posting_date, creation"
|
||||||
|
|
||||||
|
if filters.get("include_default_book_entries"):
|
||||||
|
filters["company_fb"] = frappe.get_cached_value(
|
||||||
|
"Company", filters.get("company"), "default_finance_book"
|
||||||
|
)
|
||||||
|
|
||||||
|
conditions, params = _build_gl_conditions_duckdb(filters)
|
||||||
|
select_clause = ", ".join(select_exprs)
|
||||||
|
sql = f'SELECT {select_clause} FROM "tabGL Entry" WHERE {" AND ".join(conditions)} ORDER BY {order_by}'
|
||||||
|
|
||||||
|
rows = conn.execute(sql, params).fetchall()
|
||||||
|
gl_entries = [frappe._dict(zip(col_names, row, strict=False)) for row in rows]
|
||||||
|
|
||||||
|
party_name_map = get_party_name_map()
|
||||||
|
for gl_entry in gl_entries:
|
||||||
|
if gl_entry.party_type and gl_entry.party:
|
||||||
|
gl_entry.party_name = party_name_map.get(gl_entry.party_type, {}).get(gl_entry.party)
|
||||||
|
|
||||||
|
if filters.get("presentation_currency"):
|
||||||
|
return convert_to_presentation_currency(gl_entries, currency_map, filters)
|
||||||
|
return gl_entries
|
||||||
|
|
||||||
|
|
||||||
|
def _build_gl_conditions_duckdb(filters):
|
||||||
|
ignore_is_opening = frappe.get_single_value("Accounts Settings", "ignore_is_opening_check_for_reporting")
|
||||||
|
|
||||||
|
conditions = ["company = ?"]
|
||||||
|
params = [filters.company]
|
||||||
|
|
||||||
|
if filters.get("account"):
|
||||||
|
filters.account = get_accounts_with_children(filters.account)
|
||||||
|
if filters.account:
|
||||||
|
conditions.append(f"account IN ({', '.join(['?'] * len(filters.account))})")
|
||||||
|
params.extend(filters.account)
|
||||||
|
|
||||||
|
if filters.get("cost_center"):
|
||||||
|
filters.cost_center = get_cost_centers_with_children(filters.cost_center)
|
||||||
|
conditions.append(f"cost_center IN ({', '.join(['?'] * len(filters.cost_center))})")
|
||||||
|
params.extend(filters.cost_center)
|
||||||
|
|
||||||
|
if filters.get("voucher_no"):
|
||||||
|
conditions.append("voucher_no = ?")
|
||||||
|
params.append(filters.voucher_no)
|
||||||
|
|
||||||
|
if filters.get("against_voucher_no"):
|
||||||
|
conditions.append("against_voucher = ?")
|
||||||
|
params.append(filters.against_voucher_no)
|
||||||
|
|
||||||
|
if filters.get("ignore_err"):
|
||||||
|
err_journals = frappe.db.get_all(
|
||||||
|
"Journal Entry",
|
||||||
|
filters={
|
||||||
|
"company": filters.get("company"),
|
||||||
|
"docstatus": 1,
|
||||||
|
"voucher_type": ("in", ["Exchange Rate Revaluation", "Exchange Gain Or Loss"]),
|
||||||
|
},
|
||||||
|
pluck="name",
|
||||||
|
)
|
||||||
|
if err_journals:
|
||||||
|
filters.update({"voucher_no_not_in": err_journals})
|
||||||
|
|
||||||
|
if filters.get("ignore_cr_dr_notes"):
|
||||||
|
system_generated = frappe.db.get_all(
|
||||||
|
"Journal Entry",
|
||||||
|
filters={
|
||||||
|
"company": filters.get("company"),
|
||||||
|
"docstatus": 1,
|
||||||
|
"voucher_type": ("in", ["Credit Note", "Debit Note"]),
|
||||||
|
"is_system_generated": 1,
|
||||||
|
},
|
||||||
|
pluck="name",
|
||||||
|
)
|
||||||
|
if system_generated:
|
||||||
|
vouchers_to_ignore = (filters.get("voucher_no_not_in") or []) + system_generated
|
||||||
|
filters.update({"voucher_no_not_in": vouchers_to_ignore})
|
||||||
|
|
||||||
|
if filters.get("voucher_no_not_in"):
|
||||||
|
vouchers = filters.voucher_no_not_in
|
||||||
|
conditions.append(f"voucher_no NOT IN ({', '.join(['?'] * len(vouchers))})")
|
||||||
|
params.extend(vouchers)
|
||||||
|
|
||||||
|
if filters.get("categorize_by") == "Categorize by Party" and not filters.get("party_type"):
|
||||||
|
conditions.append("party_type IN ('Customer', 'Supplier')")
|
||||||
|
|
||||||
|
if filters.get("party_type"):
|
||||||
|
conditions.append("party_type = ?")
|
||||||
|
params.append(filters.party_type)
|
||||||
|
|
||||||
|
if filters.get("party"):
|
||||||
|
conditions.append(f"party IN ({', '.join(['?'] * len(filters.party))})")
|
||||||
|
params.extend(filters.party)
|
||||||
|
|
||||||
|
# from_date: skip when filtering by account/party to allow opening balance calc in Python
|
||||||
|
if filters.get("disable_opening_balance_calculation"):
|
||||||
|
if not ignore_is_opening:
|
||||||
|
conditions.append("(posting_date >= ? OR is_opening = 'Yes')")
|
||||||
|
else:
|
||||||
|
conditions.append("posting_date >= ?")
|
||||||
|
params.append(filters.from_date)
|
||||||
|
elif not (
|
||||||
|
filters.get("account")
|
||||||
|
or filters.get("party")
|
||||||
|
or filters.get("categorize_by") in ["Categorize by Account", "Categorize by Party"]
|
||||||
|
):
|
||||||
|
if not ignore_is_opening:
|
||||||
|
conditions.append("(posting_date >= ? OR is_opening = 'Yes')")
|
||||||
|
else:
|
||||||
|
conditions.append("posting_date >= ?")
|
||||||
|
params.append(filters.from_date)
|
||||||
|
|
||||||
|
if not ignore_is_opening:
|
||||||
|
conditions.append("(posting_date <= ? OR is_opening = 'Yes')")
|
||||||
|
else:
|
||||||
|
conditions.append("posting_date <= ?")
|
||||||
|
params.append(filters.to_date)
|
||||||
|
|
||||||
|
if filters.get("project"):
|
||||||
|
conditions.append(f"project IN ({', '.join(['?'] * len(filters.project))})")
|
||||||
|
params.extend(filters.project)
|
||||||
|
|
||||||
|
company_fb = filters.get("company_fb") or frappe.get_cached_value(
|
||||||
|
"Company", filters.company, "default_finance_book"
|
||||||
|
)
|
||||||
|
if filters.get("include_default_book_entries"):
|
||||||
|
if filters.get("finance_book"):
|
||||||
|
if company_fb and cstr(filters.finance_book) != cstr(company_fb):
|
||||||
|
frappe.throw(
|
||||||
|
_("To use a different finance book, please uncheck 'Include Default FB Entries'")
|
||||||
|
)
|
||||||
|
fb_vals = [cstr(filters.finance_book), ""]
|
||||||
|
else:
|
||||||
|
fb_vals = [cstr(company_fb), ""]
|
||||||
|
conditions.append(f"(finance_book IN ({', '.join(['?'] * len(fb_vals))}) OR finance_book IS NULL)")
|
||||||
|
params.extend(fb_vals)
|
||||||
|
else:
|
||||||
|
if filters.get("finance_book"):
|
||||||
|
conditions.append("(finance_book IN (?, '') OR finance_book IS NULL)")
|
||||||
|
params.append(cstr(filters.finance_book))
|
||||||
|
else:
|
||||||
|
conditions.append("(finance_book IN ('') OR finance_book IS NULL)")
|
||||||
|
|
||||||
|
if not filters.get("show_cancelled_entries"):
|
||||||
|
conditions.append("is_cancelled = 0")
|
||||||
|
|
||||||
|
accounting_dimensions_list = get_accounting_dimensions(as_list=False)
|
||||||
|
if accounting_dimensions_list:
|
||||||
|
for dimension in accounting_dimensions_list:
|
||||||
|
if not dimension.disabled and dimension.document_type != "Finance Book":
|
||||||
|
if filters.get(dimension.fieldname):
|
||||||
|
if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
|
||||||
|
filters[dimension.fieldname] = get_dimension_with_children(
|
||||||
|
dimension.document_type, filters.get(dimension.fieldname)
|
||||||
|
)
|
||||||
|
vals = (
|
||||||
|
filters[dimension.fieldname]
|
||||||
|
if isinstance(filters[dimension.fieldname], list)
|
||||||
|
else [filters[dimension.fieldname]]
|
||||||
|
)
|
||||||
|
conditions.append(f"{dimension.fieldname} IN ({', '.join(['?'] * len(vals))})")
|
||||||
|
params.extend(vals)
|
||||||
|
|
||||||
|
return conditions, params
|
||||||
|
|||||||
Reference in New Issue
Block a user