mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-29 14:48:26 +00:00
refactor(postgres): port GL & ledger-core queries to the query builder
Pure MariaDB-identical conversion (raw frappe.db.sql -> frappe.qb / portable functions) for Postgres compatibility. Split out of #56082. general_ledger, gl_entry, gl_validator, period_closing_voucher, deferred_revenue, process_payment_reconciliation + their tests. No behaviour change on MariaDB; verified equivalent and the suites pass on both engines. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.email import sendmail_to_system_managers
|
from frappe.email import sendmail_to_system_managers
|
||||||
|
from frappe.query_builder.functions import IfNull, Sum
|
||||||
from frappe.utils import (
|
from frappe.utils import (
|
||||||
add_days,
|
add_days,
|
||||||
add_months,
|
add_months,
|
||||||
@@ -53,20 +54,24 @@ def validate_service_stop_date(doc):
|
|||||||
|
|
||||||
|
|
||||||
def build_conditions(process_type, account, company):
|
def build_conditions(process_type, account, company):
|
||||||
conditions = ""
|
if process_type == "Income":
|
||||||
deferred_account = (
|
item = frappe.qb.DocType("Sales Invoice Item")
|
||||||
"item.deferred_revenue_account" if process_type == "Income" else "item.deferred_expense_account"
|
parent = frappe.qb.DocType("Sales Invoice")
|
||||||
)
|
deferred_account = item.deferred_revenue_account
|
||||||
|
else:
|
||||||
|
item = frappe.qb.DocType("Purchase Invoice Item")
|
||||||
|
parent = frappe.qb.DocType("Purchase Invoice")
|
||||||
|
deferred_account = item.deferred_expense_account
|
||||||
|
|
||||||
if account:
|
if account:
|
||||||
conditions += f"AND {deferred_account}={frappe.db.escape(account)}"
|
return deferred_account == account
|
||||||
elif company:
|
elif company:
|
||||||
conditions += f"AND p.company = {frappe.db.escape(company)}"
|
return parent.company == company
|
||||||
|
|
||||||
return conditions
|
return None
|
||||||
|
|
||||||
|
|
||||||
def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_date=None, conditions=""):
|
def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_date=None, conditions=None):
|
||||||
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
|
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
|
||||||
|
|
||||||
if not start_date:
|
if not start_date:
|
||||||
@@ -75,17 +80,25 @@ def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_d
|
|||||||
end_date = add_days(today(), -1)
|
end_date = add_days(today(), -1)
|
||||||
|
|
||||||
# check for the purchase invoice for which GL entries has to be done
|
# check for the purchase invoice for which GL entries has to be done
|
||||||
invoices = frappe.db.sql_list(
|
item = frappe.qb.DocType("Purchase Invoice Item")
|
||||||
f"""
|
parent = frappe.qb.DocType("Purchase Invoice")
|
||||||
select distinct item.parent
|
query = (
|
||||||
from `tabPurchase Invoice Item` item, `tabPurchase Invoice` p
|
frappe.qb.from_(item)
|
||||||
where item.service_start_date<=%s and item.service_end_date>=%s
|
.inner_join(parent)
|
||||||
and item.enable_deferred_expense = 1 and item.parent=p.name
|
.on(item.parent == parent.name)
|
||||||
and item.docstatus = 1 and ifnull(item.amount, 0) > 0
|
.select(item.parent)
|
||||||
{conditions}
|
.distinct()
|
||||||
""",
|
.where(
|
||||||
(end_date, start_date),
|
(item.service_start_date <= end_date)
|
||||||
) # nosec
|
& (item.service_end_date >= start_date)
|
||||||
|
& (item.enable_deferred_expense == 1)
|
||||||
|
& (item.docstatus == 1)
|
||||||
|
& (IfNull(item.amount, 0) > 0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if conditions is not None:
|
||||||
|
query = query.where(conditions)
|
||||||
|
invoices = query.run(pluck=True)
|
||||||
|
|
||||||
# For each invoice, book deferred expense
|
# For each invoice, book deferred expense
|
||||||
for invoice in invoices:
|
for invoice in invoices:
|
||||||
@@ -96,7 +109,7 @@ def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_d
|
|||||||
send_mail(deferred_process)
|
send_mail(deferred_process)
|
||||||
|
|
||||||
|
|
||||||
def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_date=None, conditions=""):
|
def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_date=None, conditions=None):
|
||||||
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
|
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
|
||||||
|
|
||||||
if not start_date:
|
if not start_date:
|
||||||
@@ -105,17 +118,25 @@ def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_da
|
|||||||
end_date = add_days(today(), -1)
|
end_date = add_days(today(), -1)
|
||||||
|
|
||||||
# check for the sales invoice for which GL entries has to be done
|
# check for the sales invoice for which GL entries has to be done
|
||||||
invoices = frappe.db.sql_list(
|
item = frappe.qb.DocType("Sales Invoice Item")
|
||||||
f"""
|
parent = frappe.qb.DocType("Sales Invoice")
|
||||||
select distinct item.parent
|
query = (
|
||||||
from `tabSales Invoice Item` item, `tabSales Invoice` p
|
frappe.qb.from_(item)
|
||||||
where item.service_start_date<=%s and item.service_end_date>=%s
|
.inner_join(parent)
|
||||||
and item.enable_deferred_revenue = 1 and item.parent=p.name
|
.on(item.parent == parent.name)
|
||||||
and item.docstatus = 1 and ifnull(item.amount, 0) > 0
|
.select(item.parent)
|
||||||
{conditions}
|
.distinct()
|
||||||
""",
|
.where(
|
||||||
(end_date, start_date),
|
(item.service_start_date <= end_date)
|
||||||
) # nosec
|
& (item.service_end_date >= start_date)
|
||||||
|
& (item.enable_deferred_revenue == 1)
|
||||||
|
& (item.docstatus == 1)
|
||||||
|
& (IfNull(item.amount, 0) > 0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if conditions is not None:
|
||||||
|
query = query.where(conditions)
|
||||||
|
invoices = query.run(pluck=True)
|
||||||
|
|
||||||
for invoice in invoices:
|
for invoice in invoices:
|
||||||
doc = frappe.get_doc("Sales Invoice", invoice)
|
doc = frappe.get_doc("Sales Invoice", invoice)
|
||||||
@@ -136,26 +157,39 @@ def get_booking_dates(doc, item, posting_date=None, prev_posting_date=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not prev_posting_date:
|
if not prev_posting_date:
|
||||||
prev_gl_entry = frappe.db.sql(
|
prev_gl_entry = frappe.get_all(
|
||||||
"""
|
"GL Entry",
|
||||||
select name, posting_date from `tabGL Entry` where company=%s and account=%s and
|
filters={
|
||||||
voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
|
"company": doc.company,
|
||||||
and is_cancelled = 0
|
"account": item.get(deferred_account),
|
||||||
order by posting_date desc limit 1
|
"voucher_type": doc.doctype,
|
||||||
""",
|
"voucher_no": doc.name,
|
||||||
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
|
"voucher_detail_no": item.name,
|
||||||
as_dict=True,
|
"is_cancelled": 0,
|
||||||
|
},
|
||||||
|
fields=["name", "posting_date"],
|
||||||
|
order_by="posting_date desc",
|
||||||
|
limit=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
prev_gl_via_je = frappe.db.sql(
|
je = frappe.qb.DocType("Journal Entry")
|
||||||
"""
|
jea = frappe.qb.DocType("Journal Entry Account")
|
||||||
SELECT p.name, p.posting_date FROM `tabJournal Entry` p, `tabJournal Entry Account` c
|
prev_gl_via_je = (
|
||||||
WHERE p.name = c.parent and p.company=%s and c.account=%s
|
frappe.qb.from_(je)
|
||||||
and c.reference_type=%s and c.reference_name=%s
|
.inner_join(jea)
|
||||||
and c.reference_detail_no=%s and c.docstatus < 2 order by posting_date desc limit 1
|
.on(je.name == jea.parent)
|
||||||
""",
|
.select(je.name, je.posting_date)
|
||||||
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
|
.where(
|
||||||
as_dict=True,
|
(je.company == doc.company)
|
||||||
|
& (jea.account == item.get(deferred_account))
|
||||||
|
& (jea.reference_type == doc.doctype)
|
||||||
|
& (jea.reference_name == doc.name)
|
||||||
|
& (jea.reference_detail_no == item.name)
|
||||||
|
& (jea.docstatus < 2)
|
||||||
|
)
|
||||||
|
.orderby(je.posting_date, order=frappe.qb.desc)
|
||||||
|
.limit(1)
|
||||||
|
.run(as_dict=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
if prev_gl_via_je:
|
if prev_gl_via_je:
|
||||||
@@ -277,26 +311,47 @@ def get_already_booked_amount(doc, item):
|
|||||||
total_credit_debit, total_credit_debit_currency = "credit", "credit_in_account_currency"
|
total_credit_debit, total_credit_debit_currency = "credit", "credit_in_account_currency"
|
||||||
deferred_account = "deferred_expense_account"
|
deferred_account = "deferred_expense_account"
|
||||||
|
|
||||||
gl_entries_details = frappe.db.sql(
|
gle = frappe.qb.DocType("GL Entry")
|
||||||
"""
|
gl_entries_details = (
|
||||||
select sum({}) as total_credit, sum({}) as total_credit_in_account_currency, voucher_detail_no
|
frappe.qb.from_(gle)
|
||||||
from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
|
.select(
|
||||||
and is_cancelled = 0
|
Sum(gle[total_credit_debit]).as_("total_credit"),
|
||||||
group by voucher_detail_no
|
Sum(gle[total_credit_debit_currency]).as_("total_credit_in_account_currency"),
|
||||||
""".format(total_credit_debit, total_credit_debit_currency),
|
gle.voucher_detail_no,
|
||||||
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
|
)
|
||||||
as_dict=True,
|
.where(
|
||||||
|
(gle.company == doc.company)
|
||||||
|
& (gle.account == item.get(deferred_account))
|
||||||
|
& (gle.voucher_type == doc.doctype)
|
||||||
|
& (gle.voucher_no == doc.name)
|
||||||
|
& (gle.voucher_detail_no == item.name)
|
||||||
|
& (gle.is_cancelled == 0)
|
||||||
|
)
|
||||||
|
.groupby(gle.voucher_detail_no)
|
||||||
|
.run(as_dict=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
journal_entry_details = frappe.db.sql(
|
je = frappe.qb.DocType("Journal Entry")
|
||||||
"""
|
jea = frappe.qb.DocType("Journal Entry Account")
|
||||||
SELECT sum(c.{}) as total_credit, sum(c.{}) as total_credit_in_account_currency, reference_detail_no
|
journal_entry_details = (
|
||||||
FROM `tabJournal Entry` p , `tabJournal Entry Account` c WHERE p.name = c.parent and
|
frappe.qb.from_(je)
|
||||||
p.company = %s and c.account=%s and c.reference_type=%s and c.reference_name=%s and c.reference_detail_no=%s
|
.inner_join(jea)
|
||||||
and p.docstatus < 2 group by reference_detail_no
|
.on(je.name == jea.parent)
|
||||||
""".format(total_credit_debit, total_credit_debit_currency),
|
.select(
|
||||||
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
|
Sum(jea[total_credit_debit]).as_("total_credit"),
|
||||||
as_dict=True,
|
Sum(jea[total_credit_debit_currency]).as_("total_credit_in_account_currency"),
|
||||||
|
jea.reference_detail_no,
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(je.company == doc.company)
|
||||||
|
& (jea.account == item.get(deferred_account))
|
||||||
|
& (jea.reference_type == doc.doctype)
|
||||||
|
& (jea.reference_name == doc.name)
|
||||||
|
& (jea.reference_detail_no == item.name)
|
||||||
|
& (je.docstatus < 2)
|
||||||
|
)
|
||||||
|
.groupby(jea.reference_detail_no)
|
||||||
|
.run(as_dict=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
already_booked_amount = gl_entries_details[0].total_credit if gl_entries_details else 0
|
already_booked_amount = gl_entries_details[0].total_credit if gl_entries_details else 0
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from frappe import _
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.model.meta import get_field_precision
|
from frappe.model.meta import get_field_precision
|
||||||
from frappe.model.naming import set_name_from_naming_options
|
from frappe.model.naming import set_name_from_naming_options
|
||||||
|
from frappe.query_builder.functions import Sum
|
||||||
from frappe.utils import create_batch, flt, fmt_money, now
|
from frappe.utils import create_batch, flt, fmt_money, now
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
@@ -331,10 +332,12 @@ def validate_balance_type(account, adv_adj=False):
|
|||||||
if not adv_adj and account:
|
if not adv_adj and account:
|
||||||
balance_must_be = frappe.get_cached_value("Account", account, "balance_must_be")
|
balance_must_be = frappe.get_cached_value("Account", account, "balance_must_be")
|
||||||
if balance_must_be:
|
if balance_must_be:
|
||||||
balance = frappe.db.sql(
|
gle = frappe.qb.DocType("GL Entry")
|
||||||
"""select sum(debit) - sum(credit)
|
balance = (
|
||||||
from `tabGL Entry` where is_cancelled = 0 and account = %s""",
|
frappe.qb.from_(gle)
|
||||||
account,
|
.select(Sum(gle.debit) - Sum(gle.credit))
|
||||||
|
.where((gle.is_cancelled == 0) & (gle.account == account))
|
||||||
|
.run()
|
||||||
)[0][0]
|
)[0][0]
|
||||||
|
|
||||||
if (balance_must_be == "Debit" and flt(balance) < 0) or (
|
if (balance_must_be == "Debit" and flt(balance) < 0) or (
|
||||||
@@ -348,44 +351,48 @@ def validate_balance_type(account, adv_adj=False):
|
|||||||
def update_outstanding_amt(
|
def update_outstanding_amt(
|
||||||
account, party_type, party, against_voucher_type, against_voucher, on_cancel=False
|
account, party_type, party, against_voucher_type, against_voucher, on_cancel=False
|
||||||
):
|
):
|
||||||
|
gle = frappe.qb.DocType("GL Entry")
|
||||||
|
|
||||||
|
conditions = (
|
||||||
|
(gle.against_voucher_type == against_voucher_type)
|
||||||
|
& (gle.against_voucher == against_voucher)
|
||||||
|
& (gle.voucher_type != "Invoice Discounting")
|
||||||
|
)
|
||||||
if party_type and party:
|
if party_type and party:
|
||||||
party_condition = " and party_type={} and party={}".format(
|
conditions &= (gle.party_type == party_type) & (gle.party == party)
|
||||||
frappe.db.escape(party_type), frappe.db.escape(party)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
party_condition = ""
|
|
||||||
|
|
||||||
if against_voucher_type == "Sales Invoice":
|
if against_voucher_type == "Sales Invoice":
|
||||||
party_account = frappe.get_cached_value(against_voucher_type, against_voucher, "debit_to")
|
party_account = frappe.get_cached_value(against_voucher_type, against_voucher, "debit_to")
|
||||||
account_condition = f"and account in ({frappe.db.escape(account)}, {frappe.db.escape(party_account)})"
|
conditions &= gle.account.isin([account, party_account])
|
||||||
else:
|
else:
|
||||||
account_condition = f" and account = {frappe.db.escape(account)}"
|
conditions &= gle.account == account
|
||||||
|
|
||||||
# get final outstanding amt
|
# get final outstanding amt
|
||||||
bal = flt(
|
bal = flt(
|
||||||
frappe.db.sql(
|
frappe.qb.from_(gle)
|
||||||
f"""
|
.select(Sum(gle.debit_in_account_currency) - Sum(gle.credit_in_account_currency))
|
||||||
select sum(debit_in_account_currency) - sum(credit_in_account_currency)
|
.where(conditions)
|
||||||
from `tabGL Entry`
|
.run()[0][0]
|
||||||
where against_voucher_type=%s and against_voucher=%s
|
|
||||||
and voucher_type != 'Invoice Discounting'
|
|
||||||
{party_condition} {account_condition}""",
|
|
||||||
(against_voucher_type, against_voucher),
|
|
||||||
)[0][0]
|
|
||||||
or 0.0
|
or 0.0
|
||||||
)
|
)
|
||||||
|
|
||||||
if against_voucher_type == "Purchase Invoice":
|
if against_voucher_type == "Purchase Invoice":
|
||||||
bal = -bal
|
bal = -bal
|
||||||
elif against_voucher_type == "Journal Entry":
|
elif against_voucher_type == "Journal Entry":
|
||||||
|
je_conditions = (
|
||||||
|
(gle.voucher_type == "Journal Entry")
|
||||||
|
& (gle.voucher_no == against_voucher)
|
||||||
|
& (gle.account == account)
|
||||||
|
& (gle.against_voucher.isnull() | (gle.against_voucher == ""))
|
||||||
|
)
|
||||||
|
if party_type and party:
|
||||||
|
je_conditions &= (gle.party_type == party_type) & (gle.party == party)
|
||||||
|
|
||||||
against_voucher_amount = flt(
|
against_voucher_amount = flt(
|
||||||
frappe.db.sql(
|
frappe.qb.from_(gle)
|
||||||
f"""
|
.select(Sum(gle.debit_in_account_currency) - Sum(gle.credit_in_account_currency))
|
||||||
select sum(debit_in_account_currency) - sum(credit_in_account_currency)
|
.where(je_conditions)
|
||||||
from `tabGL Entry` where voucher_type = 'Journal Entry' and voucher_no = %s
|
.run()[0][0]
|
||||||
and account = %s and (against_voucher is null or against_voucher='') {party_condition}""",
|
|
||||||
(against_voucher, account),
|
|
||||||
)[0][0]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not against_voucher_amount:
|
if not against_voucher_amount:
|
||||||
@@ -480,10 +487,14 @@ def rename_temporarily_named_docs(doctype):
|
|||||||
oldname = doc.name
|
oldname = doc.name
|
||||||
set_name_from_naming_options(autoname, doc)
|
set_name_from_naming_options(autoname, doc)
|
||||||
newname = doc.name
|
newname = doc.name
|
||||||
frappe.db.sql(
|
dt = frappe.qb.DocType(doctype)
|
||||||
f"UPDATE `tab{doctype}` SET name = %s, to_rename = 0, modified = %s where name = %s",
|
(
|
||||||
(newname, now(), oldname),
|
frappe.qb.update(dt)
|
||||||
)
|
.set(dt.name, newname)
|
||||||
|
.set(dt.to_rename, 0)
|
||||||
|
.set(dt.modified, now())
|
||||||
|
.where(dt.name == oldname)
|
||||||
|
).run()
|
||||||
|
|
||||||
for hook_type in ("on_gle_rename", "on_sle_rename"):
|
for hook_type in ("on_gle_rename", "on_sle_rename"):
|
||||||
for hook in frappe.get_hooks(hook_type):
|
for hook in frappe.get_hooks(hook_type):
|
||||||
|
|||||||
@@ -26,12 +26,17 @@ class TestGLEntry(ERPNextTestSuite):
|
|||||||
jv.flags.ignore_validate = True
|
jv.flags.ignore_validate = True
|
||||||
jv.submit()
|
jv.submit()
|
||||||
|
|
||||||
round_off_entry = frappe.db.sql(
|
round_off_entry = frappe.get_all(
|
||||||
"""select name from `tabGL Entry`
|
"GL Entry",
|
||||||
where voucher_type='Journal Entry' and voucher_no = %s
|
filters={
|
||||||
and account='_Test Write Off - _TC' and cost_center='_Test Cost Center - _TC'
|
"voucher_type": "Journal Entry",
|
||||||
and debit = 0 and credit = '.01'""",
|
"voucher_no": jv.name,
|
||||||
jv.name,
|
"account": "_Test Write Off - _TC",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"debit": 0,
|
||||||
|
"credit": 0.01,
|
||||||
|
},
|
||||||
|
pluck="name",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertTrue(round_off_entry)
|
self.assertTrue(round_off_entry)
|
||||||
@@ -55,8 +60,9 @@ class TestGLEntry(ERPNextTestSuite):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertTrue(all(entry.to_rename == 1 for entry in gl_entries))
|
self.assertTrue(all(entry.to_rename == 1 for entry in gl_entries))
|
||||||
old_naming_series_current_value = frappe.db.sql(
|
series = frappe.qb.DocType("Series")
|
||||||
"SELECT current from tabSeries where name = %s", naming_series
|
old_naming_series_current_value = (
|
||||||
|
frappe.qb.from_(series).select(series["current"]).where(series.name == naming_series).run()
|
||||||
)[0][0]
|
)[0][0]
|
||||||
|
|
||||||
rename_gle_sle_docs()
|
rename_gle_sle_docs()
|
||||||
@@ -73,8 +79,8 @@ class TestGLEntry(ERPNextTestSuite):
|
|||||||
all(new.name != old.name for new, old in zip(gl_entries, new_gl_entries, strict=False))
|
all(new.name != old.name for new, old in zip(gl_entries, new_gl_entries, strict=False))
|
||||||
)
|
)
|
||||||
|
|
||||||
new_naming_series_current_value = frappe.db.sql(
|
new_naming_series_current_value = (
|
||||||
"SELECT current from tabSeries where name = %s", naming_series
|
frappe.qb.from_(series).select(series["current"]).where(series.name == naming_series).run()
|
||||||
)[0][0]
|
)[0][0]
|
||||||
self.assertEqual(old_naming_series_current_value + 2, new_naming_series_current_value)
|
self.assertEqual(old_naming_series_current_value + 2, new_naming_series_current_value)
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,10 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
if not previous_fiscal_year:
|
if not previous_fiscal_year:
|
||||||
return
|
return
|
||||||
|
|
||||||
previous_fiscal_year_start_date = previous_fiscal_year[0][1]
|
# get_fiscal_year() returns a single (name, start_date, end_date) tuple, so the start date
|
||||||
|
# is [1]; the old [0][1] read the 2nd char of the name ('T'), which MariaDB silently
|
||||||
|
# coerced to NULL but postgres rejects as an invalid date.
|
||||||
|
previous_fiscal_year_start_date = previous_fiscal_year[1]
|
||||||
previous_fiscal_year_closed = frappe.db.exists(
|
previous_fiscal_year_closed = frappe.db.exists(
|
||||||
"Period Closing Voucher",
|
"Period Closing Voucher",
|
||||||
{
|
{
|
||||||
@@ -287,41 +290,44 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
self.accounting_dimension_fields = default_dimensions + get_accounting_dimensions()
|
self.accounting_dimension_fields = default_dimensions + get_accounting_dimensions()
|
||||||
|
|
||||||
def get_gl_entries_for_current_period(self, report_type, only_opening_entries=False, as_iterator=False):
|
def get_gl_entries_for_current_period(self, report_type, only_opening_entries=False, as_iterator=False):
|
||||||
date_condition = ""
|
gle = frappe.qb.DocType("GL Entry")
|
||||||
if only_opening_entries:
|
account = frappe.qb.DocType("Account")
|
||||||
date_condition = "is_opening = 'Yes'"
|
|
||||||
else:
|
|
||||||
date_condition = f"posting_date BETWEEN '{self.period_start_date}' AND '{self.period_end_date}' and is_opening = 'No'"
|
|
||||||
|
|
||||||
# nosemgrep
|
fields = [
|
||||||
return frappe.db.sql(
|
gle.name,
|
||||||
"""
|
gle.posting_date,
|
||||||
SELECT
|
gle.account,
|
||||||
name,
|
gle.account_currency,
|
||||||
posting_date,
|
gle.debit_in_account_currency,
|
||||||
account,
|
gle.credit_in_account_currency,
|
||||||
account_currency,
|
gle.debit,
|
||||||
debit_in_account_currency,
|
gle.credit,
|
||||||
credit_in_account_currency,
|
]
|
||||||
debit,
|
fields += [gle[dimension] for dimension in self.accounting_dimension_fields]
|
||||||
credit,
|
|
||||||
{}
|
query = (
|
||||||
FROM `tabGL Entry`
|
frappe.qb.from_(gle)
|
||||||
WHERE
|
.select(*fields)
|
||||||
{}
|
.where(
|
||||||
AND company = %s
|
(gle.company == self.company)
|
||||||
AND voucher_type != 'Period Closing Voucher'
|
& (gle.voucher_type != "Period Closing Voucher")
|
||||||
AND EXISTS(SELECT name FROM `tabAccount` WHERE name = account AND report_type = %s)
|
& (gle.is_cancelled == 0)
|
||||||
AND is_cancelled = 0
|
& gle.account.isin(
|
||||||
""".format(
|
frappe.qb.from_(account).select(account.name).where(account.report_type == report_type)
|
||||||
", ".join(self.accounting_dimension_fields),
|
)
|
||||||
date_condition,
|
)
|
||||||
),
|
|
||||||
(self.company, report_type),
|
|
||||||
as_dict=1,
|
|
||||||
as_iterator=as_iterator,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if only_opening_entries:
|
||||||
|
query = query.where(gle.is_opening == "Yes")
|
||||||
|
else:
|
||||||
|
query = query.where(
|
||||||
|
gle.posting_date.between(self.period_start_date, self.period_end_date)
|
||||||
|
& (gle.is_opening == "No")
|
||||||
|
)
|
||||||
|
|
||||||
|
return query.run(as_dict=1, as_iterator=as_iterator)
|
||||||
|
|
||||||
def set_account_balance_dict(self, gle, acc_bal_dict):
|
def set_account_balance_dict(self, gle, acc_bal_dict):
|
||||||
key = self.get_key(gle)
|
key = self.get_key(gle)
|
||||||
|
|
||||||
|
|||||||
@@ -55,15 +55,19 @@ class TestPeriodClosingVoucher(ERPNextTestSuite):
|
|||||||
("Sales - TPC", 400.0, 0.0),
|
("Sales - TPC", 400.0, 0.0),
|
||||||
)
|
)
|
||||||
|
|
||||||
pcv_gle = frappe.db.sql(
|
pcv_gle = [
|
||||||
"""
|
tuple(row)
|
||||||
select account, debit, credit from `tabGL Entry` where voucher_no=%s order by account
|
for row in frappe.get_all(
|
||||||
""",
|
"GL Entry",
|
||||||
(pcv.name),
|
filters={"voucher_no": pcv.name},
|
||||||
)
|
fields=["account", "debit", "credit"],
|
||||||
|
order_by="account",
|
||||||
|
as_list=True,
|
||||||
|
)
|
||||||
|
]
|
||||||
pcv.reload()
|
pcv.reload()
|
||||||
self.assertEqual(pcv.gle_processing_status, "Completed")
|
self.assertEqual(pcv.gle_processing_status, "Completed")
|
||||||
self.assertEqual(pcv_gle, expected_gle)
|
self.assertEqual(tuple(pcv_gle), expected_gle)
|
||||||
|
|
||||||
def test_cost_center_wise_posting(self):
|
def test_cost_center_wise_posting(self):
|
||||||
surplus_account = create_account()
|
surplus_account = create_account()
|
||||||
@@ -106,14 +110,16 @@ class TestPeriodClosingVoucher(ERPNextTestSuite):
|
|||||||
("Sales - TPC", 200.0, 0.0, cost_center2),
|
("Sales - TPC", 200.0, 0.0, cost_center2),
|
||||||
)
|
)
|
||||||
|
|
||||||
pcv_gle = frappe.db.sql(
|
pcv_gle = [
|
||||||
"""
|
tuple(row)
|
||||||
select account, debit, credit, cost_center
|
for row in frappe.get_all(
|
||||||
from `tabGL Entry` where voucher_no=%s
|
"GL Entry",
|
||||||
order by account, cost_center
|
filters={"voucher_no": pcv.name},
|
||||||
""",
|
fields=["account", "debit", "credit", "cost_center"],
|
||||||
(pcv.name),
|
order_by="account, cost_center",
|
||||||
)
|
as_list=True,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
self.assertSequenceEqual(pcv_gle, expected_gle)
|
self.assertSequenceEqual(pcv_gle, expected_gle)
|
||||||
|
|
||||||
@@ -166,16 +172,19 @@ class TestPeriodClosingVoucher(ERPNextTestSuite):
|
|||||||
("Sales - TPC", 400.0, 0.0, jv.finance_book),
|
("Sales - TPC", 400.0, 0.0, jv.finance_book),
|
||||||
)
|
)
|
||||||
|
|
||||||
pcv_gle = frappe.db.sql(
|
pcv_gle = [
|
||||||
"""
|
tuple(row)
|
||||||
select account, debit, credit, finance_book
|
for row in frappe.get_all(
|
||||||
from `tabGL Entry` where voucher_no=%s
|
"GL Entry",
|
||||||
order by account, finance_book
|
filters={"voucher_no": pcv.name},
|
||||||
""",
|
fields=["account", "debit", "credit", "finance_book"],
|
||||||
(pcv.name),
|
order_by="account, finance_book",
|
||||||
)
|
as_list=True,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
self.assertSequenceEqual(pcv_gle, expected_gle)
|
# compare order-independently: postgres and MariaDB order NULL finance_book differently
|
||||||
|
self.assertSequenceEqual(sorted(pcv_gle, key=str), sorted(expected_gle, key=str))
|
||||||
|
|
||||||
def test_gl_entries_restrictions(self):
|
def test_gl_entries_restrictions(self):
|
||||||
cost_center = create_cost_center("Test Cost Center 1")
|
cost_center = create_cost_center("Test Cost Center 1")
|
||||||
@@ -358,14 +367,10 @@ class TestPeriodClosingVoucher(ERPNextTestSuite):
|
|||||||
posting_date="2022-01-01",
|
posting_date="2022-01-01",
|
||||||
)
|
)
|
||||||
|
|
||||||
totals_after_cancel = frappe.db.sql(
|
totals_after_cancel = frappe.get_all(
|
||||||
"""
|
"GL Entry",
|
||||||
select sum(debit) as total_debit, sum(credit) as total_credit
|
filters={"voucher_type": "Journal Entry", "voucher_no": jv.name, "is_cancelled": 0},
|
||||||
from `tabGL Entry`
|
fields=[{"SUM": "debit", "as": "total_debit"}, {"SUM": "credit", "as": "total_credit"}],
|
||||||
where voucher_type=%s and voucher_no=%s and is_cancelled=0
|
|
||||||
""",
|
|
||||||
("Journal Entry", jv.name),
|
|
||||||
as_dict=True,
|
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
self.assertEqual(totals_after_cancel.total_debit, totals_after_cancel.total_credit)
|
self.assertEqual(totals_after_cancel.total_debit, totals_after_cancel.total_credit)
|
||||||
|
|||||||
@@ -431,7 +431,9 @@ def reconcile(doc: None | str = None) -> None:
|
|||||||
# Update reconciled flag
|
# Update reconciled flag
|
||||||
allocation_names = [x.name for x in allocations]
|
allocation_names = [x.name for x in allocations]
|
||||||
ppa = qb.DocType("Process Payment Reconciliation Log Allocations")
|
ppa = qb.DocType("Process Payment Reconciliation Log Allocations")
|
||||||
qb.update(ppa).set(ppa.reconciled, True).where(ppa.name.isin(allocation_names)).run()
|
qb.update(ppa).set(ppa.reconciled, 1).where(
|
||||||
|
ppa.name.isin(allocation_names)
|
||||||
|
).run() # smallint, not bool
|
||||||
|
|
||||||
# Update reconciled count
|
# Update reconciled count
|
||||||
reconciled_count = frappe.db.count(
|
reconciled_count = frappe.db.count(
|
||||||
|
|||||||
@@ -672,7 +672,7 @@ def make_reverse_gl_entries(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not immutable_ledger_enabled:
|
if not immutable_ledger_enabled:
|
||||||
query = query.set(gle.is_cancelled, True)
|
query = query.set(gle.is_cancelled, 1) # smallint column; postgres rejects boolean true
|
||||||
|
|
||||||
query.run()
|
query.run()
|
||||||
else:
|
else:
|
||||||
@@ -683,12 +683,14 @@ def make_reverse_gl_entries(
|
|||||||
if not all(gle_names):
|
if not all(gle_names):
|
||||||
set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
|
set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
|
||||||
else:
|
else:
|
||||||
frappe.db.sql(
|
gle = frappe.qb.DocType("GL Entry")
|
||||||
"""UPDATE `tabGL Entry` SET is_cancelled = 1,
|
(
|
||||||
modified=%s, modified_by=%s
|
frappe.qb.update(gle)
|
||||||
where name in %s and is_cancelled = 0""",
|
.set(gle.is_cancelled, 1)
|
||||||
(now(), frappe.session.user, tuple(gle_names)),
|
.set(gle.modified, now())
|
||||||
)
|
.set(gle.modified_by, frappe.session.user)
|
||||||
|
.where(gle.name.isin(gle_names) & (gle.is_cancelled == 0))
|
||||||
|
).run()
|
||||||
|
|
||||||
for entry in gl_entries:
|
for entry in gl_entries:
|
||||||
new_gle = copy.deepcopy(entry)
|
new_gle = copy.deepcopy(entry)
|
||||||
@@ -725,9 +727,11 @@ def set_as_cancel(voucher_type, voucher_no):
|
|||||||
"""
|
"""
|
||||||
Set is_cancelled=1 in all original gl entries for the voucher
|
Set is_cancelled=1 in all original gl entries for the voucher
|
||||||
"""
|
"""
|
||||||
frappe.db.sql(
|
gle = frappe.qb.DocType("GL Entry")
|
||||||
"""UPDATE `tabGL Entry` SET is_cancelled = 1,
|
(
|
||||||
modified=%s, modified_by=%s
|
frappe.qb.update(gle)
|
||||||
where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
|
.set(gle.is_cancelled, 1)
|
||||||
(now(), frappe.session.user, voucher_type, voucher_no),
|
.set(gle.modified, now())
|
||||||
)
|
.set(gle.modified_by, frappe.session.user)
|
||||||
|
.where((gle.voucher_type == voucher_type) & (gle.voucher_no == voucher_no) & (gle.is_cancelled == 0))
|
||||||
|
).run()
|
||||||
|
|||||||
@@ -134,17 +134,17 @@ class TestGeneralLedger(ERPNextTestSuite):
|
|||||||
revaluation_jv.submit()
|
revaluation_jv.submit()
|
||||||
|
|
||||||
# check the balance of the account
|
# check the balance of the account
|
||||||
balance = frappe.db.sql(
|
balance = frappe.get_all(
|
||||||
"""
|
"GL Entry",
|
||||||
select sum(debit_in_account_currency) - sum(credit_in_account_currency)
|
filters={"account": account.name},
|
||||||
from `tabGL Entry`
|
fields=[
|
||||||
where account = %s
|
{"SUM": "debit_in_account_currency", "as": "debit"},
|
||||||
group by account
|
{"SUM": "credit_in_account_currency", "as": "credit"},
|
||||||
""",
|
],
|
||||||
account.name,
|
group_by="account",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(balance[0][0], 100)
|
self.assertEqual(flt(balance[0].debit) - flt(balance[0].credit), 100)
|
||||||
|
|
||||||
# check if general ledger shows correct balance
|
# check if general ledger shows correct balance
|
||||||
columns, data = execute(
|
columns, data = execute(
|
||||||
|
|||||||
@@ -37,25 +37,22 @@ def validate_disabled_accounts(gl_map):
|
|||||||
|
|
||||||
|
|
||||||
def validate_accounting_period(gl_map):
|
def validate_accounting_period(gl_map):
|
||||||
accounting_periods = frappe.db.sql(
|
ap = frappe.qb.DocType("Accounting Period")
|
||||||
""" SELECT
|
cd = frappe.qb.DocType("Closed Document")
|
||||||
ap.name as name, ap.exempted_role as exempted_role
|
accounting_periods = (
|
||||||
FROM
|
frappe.qb.from_(ap)
|
||||||
`tabAccounting Period` ap, `tabClosed Document` cd
|
.inner_join(cd)
|
||||||
WHERE
|
.on(ap.name == cd.parent)
|
||||||
ap.name = cd.parent
|
.select(ap.name.as_("name"), ap.exempted_role.as_("exempted_role"))
|
||||||
AND ap.company = %(company)s
|
.where(
|
||||||
AND ap.disabled = 0
|
(ap.company == gl_map[0].company)
|
||||||
AND cd.closed = 1
|
& (ap.disabled == 0)
|
||||||
AND cd.document_type = %(voucher_type)s
|
& (cd.closed == 1)
|
||||||
AND %(date)s between ap.start_date and ap.end_date
|
& (cd.document_type == gl_map[0].voucher_type)
|
||||||
""",
|
& (ap.start_date <= gl_map[0].posting_date)
|
||||||
{
|
& (ap.end_date >= gl_map[0].posting_date)
|
||||||
"date": gl_map[0].posting_date,
|
)
|
||||||
"company": gl_map[0].company,
|
.run(as_dict=1)
|
||||||
"voucher_type": gl_map[0].voucher_type,
|
|
||||||
},
|
|
||||||
as_dict=1,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if accounting_periods:
|
if accounting_periods:
|
||||||
@@ -81,13 +78,11 @@ def validate_cwip_accounts(gl_map):
|
|||||||
for ac in frappe.db.get_all("Asset Category", "enable_cwip_accounting")
|
for ac in frappe.db.get_all("Asset Category", "enable_cwip_accounting")
|
||||||
)
|
)
|
||||||
if cwip_enabled:
|
if cwip_enabled:
|
||||||
cwip_accounts = [
|
cwip_accounts = frappe.get_all(
|
||||||
d[0]
|
"Account",
|
||||||
for d in frappe.db.sql(
|
filters={"account_type": "Capital Work in Progress", "is_group": 0},
|
||||||
"""select name from tabAccount
|
pluck="name",
|
||||||
where account_type = 'Capital Work in Progress' and is_group=0"""
|
)
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
for entry in gl_map:
|
for entry in gl_map:
|
||||||
if entry.account in cwip_accounts:
|
if entry.account in cwip_accounts:
|
||||||
|
|||||||
Reference in New Issue
Block a user