From d392660d455f0be92cc423eca611e40f96100c07 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 23 Oct 2024 14:27:10 +0530 Subject: [PATCH] chore: release version 15.39.1 (#43800) * fix: map doc from purchase order (cherry picked from commit 60ceb91ace8fbb8ddd127a00e7eadd431d4e250c) * test: auto create purchase receipt (cherry picked from commit 59887bbc13c223739750eb27599be5e251bca91e) * fix: better implementation, handle missing purchase order (cherry picked from commit 66211dafd69ecbdc56a52d8391b6fdcf02c3154a) * perf: performance optimizations for accounting reports by refactoring account closing balance and period closing voucher (#43798) * fix: Gl Entry form cleanup * fix: Added indexes in gl entry table * perf: Refactored period closing voucher to handle large volume of gle * fix: fixes as per new period start and end date fields in PCV * perf: performance optimization for accounting reports * perf: performance optimizations for account closing balance patch * fix: test cases * fix: lenter issues - direct use of sql query * fix: test cases * fix: test cases * fix: test cases * fix: wrong fieldname * fix: test cases --------- Co-authored-by: Ninad1306 Co-authored-by: Smit Vora --- .../account_closing_balance.py | 4 +- .../accounting_period/accounting_period.py | 2 + .../accounts/doctype/gl_entry/gl_entry.json | 106 ++- erpnext/accounts/doctype/gl_entry/gl_entry.py | 3 +- .../test_payment_reconciliation.py | 6 +- .../period_closing_voucher.js | 18 + .../period_closing_voucher.json | 36 +- .../period_closing_voucher.py | 611 ++++++++++-------- .../test_period_closing_voucher.py | 8 +- .../repost_accounting_ledger.py | 4 +- .../test_repost_accounting_ledger.py | 6 +- erpnext/accounts/general_ledger.py | 34 +- .../accounts_receivable.py | 29 +- .../accounts/report/financial_statements.py | 143 ++-- .../report/trial_balance/trial_balance.py | 20 +- erpnext/patches.txt | 1 + .../fix_additional_cost_in_mfg_stock_entry.py | 2 +- .../v14_0/set_period_start_end_date_in_pcv.py | 17 + .../patches/v14_0/single_to_multi_dunning.py | 2 +- .../patches/v14_0/update_closing_balances.py | 95 +-- .../doctype/delivery_note/delivery_note.py | 25 +- .../repost_item_valuation.py | 2 +- .../stock/doctype/stock_entry/stock_entry.py | 8 +- erpnext/stock/report/test_reports.py | 3 +- .../subcontracting_receipt.py | 167 ++--- .../test_subcontracting_receipt.py | 43 +- erpnext/tests/test_perf.py | 2 +- 27 files changed, 843 insertions(+), 554 deletions(-) create mode 100644 erpnext/patches/v14_0/set_period_start_end_date_in_pcv.py diff --git a/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py b/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py index 82821e140eaf..6d5e023f039e 100644 --- a/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py +++ b/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py @@ -113,9 +113,9 @@ def get_previous_closing_entries(company, closing_date, accounting_dimensions): entries = [] last_period_closing_voucher = frappe.db.get_all( "Period Closing Voucher", - filters={"docstatus": 1, "company": company, "posting_date": ("<", closing_date)}, + filters={"docstatus": 1, "company": company, "period_end_date": ("<", closing_date)}, fields=["name"], - order_by="posting_date desc", + order_by="period_end_date desc", limit=1, ) diff --git a/erpnext/accounts/doctype/accounting_period/accounting_period.py b/erpnext/accounts/doctype/accounting_period/accounting_period.py index 172ef93f14d3..300d216618e6 100644 --- a/erpnext/accounts/doctype/accounting_period/accounting_period.py +++ b/erpnext/accounts/doctype/accounting_period/accounting_period.py @@ -101,6 +101,8 @@ def validate_accounting_period_on_doc_save(doc, method=None): date = doc.available_for_use_date elif doc.doctype == "Asset Repair": date = doc.completion_date + elif doc.doctype == "Period Closing Voucher": + date = doc.period_end_date else: date = doc.posting_date diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json index 2d106ad8cee8..c285a33f73e6 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.json +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json @@ -6,38 +6,50 @@ "document_type": "Document", "engine": "InnoDB", "field_order": [ + "dates_section", "posting_date", "transaction_date", + "column_break_avko", + "fiscal_year", + "due_date", + "account_details_section", "account", - "party_type", - "party", - "cost_center", - "debit", - "credit", "account_currency", - "debit_in_account_currency", - "credit_in_account_currency", + "column_break_ifvf", "against", - "against_voucher_type", - "against_voucher", + "party_type", + "party", + "transaction_details_section", "voucher_type", - "voucher_subtype", "voucher_no", + "voucher_subtype", + "transaction_currency", + "column_break_dpsx", + "against_voucher_type", + "against_voucher", "voucher_detail_no", + "transaction_exchange_rate", + "amounts_section", + "debit_in_account_currency", + "debit", + "debit_in_transaction_currency", + "column_break_bm1w", + "credit_in_account_currency", + "credit", + "credit_in_transaction_currency", + "dimensions_section", + "cost_center", + "column_break_lmnm", "project", - "remarks", + "more_info_section", + "finance_book", + "company", "is_opening", "is_advance", - "fiscal_year", - "company", - "finance_book", + "column_break_8abq", "to_rename", - "due_date", "is_cancelled", - "transaction_currency", - "debit_in_transaction_currency", - "credit_in_transaction_currency", - "transaction_exchange_rate" + "remarks" ], "fields": [ { @@ -285,13 +297,67 @@ "fieldname": "voucher_subtype", "fieldtype": "Small Text", "label": "Voucher Subtype" + }, + { + "fieldname": "dates_section", + "fieldtype": "Section Break", + "label": "Dates" + }, + { + "fieldname": "column_break_avko", + "fieldtype": "Column Break" + }, + { + "fieldname": "account_details_section", + "fieldtype": "Section Break", + "label": "Account Details" + }, + { + "fieldname": "column_break_ifvf", + "fieldtype": "Column Break" + }, + { + "fieldname": "transaction_details_section", + "fieldtype": "Section Break", + "label": "Transaction Details" + }, + { + "fieldname": "amounts_section", + "fieldtype": "Section Break", + "label": "Amounts" + }, + { + "fieldname": "column_break_dpsx", + "fieldtype": "Column Break" + }, + { + "fieldname": "more_info_section", + "fieldtype": "Section Break", + "label": "More Info" + }, + { + "fieldname": "column_break_bm1w", + "fieldtype": "Column Break" + }, + { + "fieldname": "dimensions_section", + "fieldtype": "Section Break", + "label": "Dimensions" + }, + { + "fieldname": "column_break_lmnm", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_8abq", + "fieldtype": "Column Break" } ], "icon": "fa fa-list", "idx": 1, "in_create": 1, "links": [], - "modified": "2024-07-02 14:31:51.496466", + "modified": "2024-08-22 13:03:39.997475", "modified_by": "Administrator", "module": "Accounts", "name": "GL Entry", diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index d74224c4aa2c..a7e7edb098dd 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -430,8 +430,9 @@ def update_against_account(voucher_type, voucher_no): def on_doctype_update(): - frappe.db.add_index("GL Entry", ["against_voucher_type", "against_voucher"]) frappe.db.add_index("GL Entry", ["voucher_type", "voucher_no"]) + frappe.db.add_index("GL Entry", ["posting_date", "company"]) + frappe.db.add_index("GL Entry", ["party_type", "party"]) def rename_gle_sle_docs(): diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index 883c638398c5..1b19949bb7eb 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -1986,13 +1986,15 @@ def make_period_closing_voucher(company, cost_center, posting_date=None, submit= parent_account=parent_account, doctype="Account", ) + fy = get_fiscal_year(posting_date, company=company) pcv = frappe.get_doc( { "doctype": "Period Closing Voucher", "transaction_date": posting_date or today(), - "posting_date": posting_date or today(), + "period_start_date": fy[1], + "period_end_date": fy[2], "company": company, - "fiscal_year": get_fiscal_year(posting_date or today(), company=company)[0], + "fiscal_year": fy[0], "cost_center": cost_center, "closing_account_head": surplus_account, "remarks": "test", diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js index 82d8cb37fe7f..095310c7e706 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js @@ -19,6 +19,24 @@ frappe.ui.form.on("Period Closing Voucher", { }); }, + fiscal_year: function (frm) { + if (frm.doc.fiscal_year) { + frappe.call({ + method: "erpnext.accounts.doctype.period_closing_voucher.period_closing_voucher.get_period_start_end_date", + args: { + fiscal_year: frm.doc.fiscal_year, + company: frm.doc.company, + }, + callback: function (r) { + if (r.message) { + frm.set_value("period_start_date", r.message[0]); + frm.set_value("period_end_date", r.message[1]); + } + }, + }); + } + }, + refresh: function (frm) { if (frm.doc.docstatus > 0) { frm.add_custom_button( diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json index 624b5f82f64c..f41cff0e0d8f 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json @@ -6,39 +6,32 @@ "engine": "InnoDB", "field_order": [ "transaction_date", - "posting_date", + "company", "fiscal_year", - "year_start_date", + "period_start_date", + "period_end_date", "amended_from", - "company", "column_break1", "closing_account_head", - "remarks", "gle_processing_status", + "remarks", "error_message" ], "fields": [ { + "default": "Today", "fieldname": "transaction_date", "fieldtype": "Date", "label": "Transaction Date", "oldfieldname": "transaction_date", "oldfieldtype": "Date" }, - { - "fieldname": "posting_date", - "fieldtype": "Date", - "label": "Posting Date", - "oldfieldname": "posting_date", - "oldfieldtype": "Date", - "reqd": 1 - }, { "fieldname": "fiscal_year", "fieldtype": "Link", "in_list_view": 1, "in_standard_filter": 1, - "label": "Closing Fiscal Year", + "label": "Fiscal Year", "oldfieldname": "fiscal_year", "oldfieldtype": "Select", "options": "Fiscal Year", @@ -103,16 +96,25 @@ "read_only": 1 }, { - "fieldname": "year_start_date", + "fieldname": "period_end_date", + "fieldtype": "Date", + "label": "Period End Date", + "reqd": 1 + }, + { + "fieldname": "period_start_date", "fieldtype": "Date", - "label": "Year Start Date" + "label": "Period Start Date", + "oldfieldname": "posting_date", + "oldfieldtype": "Date", + "reqd": 1 } ], "icon": "fa fa-file-text", "idx": 1, "is_submittable": 1, "links": [], - "modified": "2023-09-11 20:19:11.810533", + "modified": "2024-09-15 17:22:45.291628", "modified_by": "Administrator", "module": "Accounts", "name": "Period Closing Voucher", @@ -148,7 +150,7 @@ "write": 1 } ], - "search_fields": "posting_date, fiscal_year", + "search_fields": "fiscal_year, period_start_date, period_end_date", "sort_field": "modified", "sort_order": "DESC", "states": [], diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index dd8a5ff16c3d..d6bd217650b2 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -2,15 +2,20 @@ # License: GNU General Public License v3. See license.txt +import copy + import frappe from frappe import _ from frappe.query_builder.functions import Sum -from frappe.utils import add_days, flt +from frappe.utils import add_days, flt, formatdate, getdate +from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import ( + make_closing_entries, +) from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, ) -from erpnext.accounts.utils import get_account_currency, get_fiscal_year, validate_fiscal_year +from erpnext.accounts.utils import get_account_currency, get_fiscal_year from erpnext.controllers.accounts_controller import AccountsController @@ -29,352 +34,424 @@ class PeriodClosingVoucher(AccountsController): error_message: DF.Text | None fiscal_year: DF.Link gle_processing_status: DF.Literal["In Progress", "Completed", "Failed"] - posting_date: DF.Date + period_end_date: DF.Date + period_start_date: DF.Date remarks: DF.SmallText transaction_date: DF.Date | None - year_start_date: DF.Date | None # end: auto-generated types def validate(self): - self.validate_account_head() - self.validate_posting_date() - - def on_submit(self): - self.db_set("gle_processing_status", "In Progress") - get_opening_entries = False - - if not frappe.db.exists( - "Period Closing Voucher", {"company": self.company, "docstatus": 1, "name": ("!=", self.name)} - ): - get_opening_entries = True - - self.make_gl_entries(get_opening_entries=get_opening_entries) + self.validate_start_and_end_date() + self.check_if_previous_year_closed() + self.block_if_future_closing_voucher_exists() + self.check_closing_account_type() + self.check_closing_account_currency() - def on_cancel(self): - self.validate_future_closing_vouchers() - self.db_set("gle_processing_status", "In Progress") - self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry") - gle_count = frappe.db.count( - "GL Entry", - {"voucher_type": "Period Closing Voucher", "voucher_no": self.name, "is_cancelled": 0}, + def validate_start_and_end_date(self): + self.fy_start_date, self.fy_end_date = frappe.db.get_value( + "Fiscal Year", self.fiscal_year, ["year_start_date", "year_end_date"] ) - if gle_count > 5000: - frappe.enqueue( - process_cancellation, - voucher_type="Period Closing Voucher", - voucher_no=self.name, - queue="long", - enqueue_after_commit=True, - ) - frappe.msgprint( - _("The GL Entries will be cancelled in the background, it can take a few minutes."), - alert=True, - ) - else: - process_cancellation(voucher_type="Period Closing Voucher", voucher_no=self.name) - - def validate_future_closing_vouchers(self): - if frappe.db.exists( - "Period Closing Voucher", - {"posting_date": (">", self.posting_date), "docstatus": 1, "company": self.company}, - ): - frappe.throw( - _( - "You can not cancel this Period Closing Voucher, please cancel the future Period Closing Vouchers first" - ) - ) - - def validate_account_head(self): - closing_account_type = frappe.get_cached_value("Account", self.closing_account_head, "root_type") - - if closing_account_type not in ["Liability", "Equity"]: - frappe.throw( - _("Closing Account {0} must be of type Liability / Equity").format(self.closing_account_head) - ) - account_currency = get_account_currency(self.closing_account_head) - company_currency = frappe.get_cached_value("Company", self.company, "default_currency") - if account_currency != company_currency: - frappe.throw(_("Currency of the Closing Account must be {0}").format(company_currency)) - - def validate_posting_date(self): - validate_fiscal_year( - self.posting_date, self.fiscal_year, self.company, label=_("Posting Date"), doc=self + prev_closed_period_end_date = get_previous_closed_period_in_current_year( + self.fiscal_year, self.company + ) + valid_start_date = ( + add_days(prev_closed_period_end_date, 1) if prev_closed_period_end_date else self.fy_start_date ) - self.year_start_date = get_fiscal_year(self.posting_date, self.fiscal_year, company=self.company)[1] - - self.check_if_previous_year_closed() + if getdate(self.period_start_date) != getdate(valid_start_date): + frappe.throw(_("Period Start Date must be {0}").format(formatdate(valid_start_date))) - pcv = frappe.qb.DocType("Period Closing Voucher") - existing_entry = ( - frappe.qb.from_(pcv) - .select(pcv.name) - .where( - (pcv.posting_date >= self.posting_date) - & (pcv.fiscal_year == self.fiscal_year) - & (pcv.docstatus == 1) - & (pcv.company == self.company) - ) - .run() - ) + if getdate(self.period_start_date) > getdate(self.period_end_date): + frappe.throw(_("Period Start Date cannot be greater than Period End Date")) - if existing_entry and existing_entry[0][0]: - frappe.throw( - _("Another Period Closing Entry {0} has been made after {1}").format( - existing_entry[0][0], self.posting_date - ) - ) + if getdate(self.period_end_date) > getdate(self.fy_end_date): + frappe.throw(_("Period End Date cannot be greater than Fiscal Year End Date")) def check_if_previous_year_closed(self): - last_year_closing = add_days(self.year_start_date, -1) + last_year_closing = add_days(self.fy_start_date, -1) previous_fiscal_year = get_fiscal_year(last_year_closing, company=self.company, boolean=True) if not previous_fiscal_year: return previous_fiscal_year_start_date = previous_fiscal_year[0][1] - if not frappe.db.exists( + gle_exists_in_previous_year = frappe.db.exists( "GL Entry", { "posting_date": ("between", [previous_fiscal_year_start_date, last_year_closing]), "company": self.company, "is_cancelled": 0, }, - ): + ) + if not gle_exists_in_previous_year: return - if not frappe.db.exists( + previous_fiscal_year_closed = frappe.db.exists( "Period Closing Voucher", { - "posting_date": ("between", [previous_fiscal_year_start_date, last_year_closing]), + "period_end_date": ("between", [previous_fiscal_year_start_date, last_year_closing]), "docstatus": 1, "company": self.company, }, - ): + ) + if not previous_fiscal_year_closed: frappe.throw(_("Previous Year is not closed, please close it first")) - def make_gl_entries(self, get_opening_entries=False): - gl_entries = self.get_gl_entries() - closing_entries = self.get_grouped_gl_entries(get_opening_entries=get_opening_entries) - if len(gl_entries + closing_entries) > 3000: + def block_if_future_closing_voucher_exists(self): + future_closing_voucher = self.get_future_closing_voucher() + if future_closing_voucher and future_closing_voucher[0][0]: + action = "cancel" if self.docstatus == 2 else "create" + frappe.throw( + _( + "You cannot {0} this document because another Period Closing Entry {1} exists after {2}" + ).format(action, future_closing_voucher[0][0], self.period_end_date) + ) + + def get_future_closing_voucher(self): + return frappe.db.get_value( + "Period Closing Voucher", + {"period_end_date": (">", self.period_end_date), "docstatus": 1, "company": self.company}, + "name", + ) + + def check_closing_account_type(self): + closing_account_type = frappe.get_cached_value("Account", self.closing_account_head, "root_type") + + if closing_account_type not in ["Liability", "Equity"]: + frappe.throw( + _("Closing Account {0} must be of type Liability / Equity").format(self.closing_account_head) + ) + + def check_closing_account_currency(self): + account_currency = get_account_currency(self.closing_account_head) + company_currency = frappe.get_cached_value("Company", self.company, "default_currency") + if account_currency != company_currency: + frappe.throw(_("Currency of the Closing Account must be {0}").format(company_currency)) + + def on_submit(self): + self.db_set("gle_processing_status", "In Progress") + self.make_gl_entries() + + def on_cancel(self): + self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry") + self.block_if_future_closing_voucher_exists() + self.db_set("gle_processing_status", "In Progress") + self.cancel_gl_entries() + + def make_gl_entries(self): + if self.get_gle_count_in_selected_period() > 5000: frappe.enqueue( process_gl_and_closing_entries, - gl_entries=gl_entries, - closing_entries=closing_entries, - voucher_name=self.name, - company=self.company, - closing_date=self.posting_date, - timeout=3000, + doc=self, + timeout=1800, ) - frappe.msgprint( - _("The GL Entries will be processed in the background, it can take a few minutes."), + _( + "The GL Entries and closing balances will be processed in the background, it can take a few minutes." + ), alert=True, ) else: - process_gl_and_closing_entries( - gl_entries, closing_entries, self.name, self.company, self.posting_date - ) - - def get_grouped_gl_entries(self, get_opening_entries=False): - closing_entries = [] - for acc in self.get_balances_based_on_dimensions( - group_by_account=True, for_aggregation=True, get_opening_entries=get_opening_entries - ): - closing_entries.append(self.get_closing_entries(acc)) + process_gl_and_closing_entries(self) - return closing_entries - - def get_gl_entries(self): - gl_entries = [] + def get_gle_count_in_selected_period(self): + return frappe.db.count( + "GL Entry", + { + "posting_date": ["between", [self.period_start_date, self.period_end_date]], + "company": self.company, + "is_cancelled": 0, + }, + ) - # pl account - for acc in self.get_balances_based_on_dimensions( - group_by_account=True, report_type="Profit and Loss" - ): - if flt(acc.bal_in_company_currency): - gl_entries.append(self.get_gle_for_pl_account(acc)) + def get_pcv_gl_entries(self): + self.pl_accounts_reverse_gle = [] + self.closing_account_gle = [] - # closing liability account - for acc in self.get_balances_based_on_dimensions( - group_by_account=False, report_type="Profit and Loss" - ): - if flt(acc.bal_in_company_currency): - gl_entries.append(self.get_gle_for_closing_account(acc)) + pl_account_balances = self.get_account_balances_based_on_dimensions(report_type="Profit and Loss") + for dimensions, account_balances in pl_account_balances.items(): + for acc, balances in account_balances.items(): + balance_in_company_currency = flt(balances.debit_in_account_currency) - flt( + balances.credit_in_account_currency + ) + if balance_in_company_currency and acc != "balances": + self.pl_accounts_reverse_gle.append( + self.get_gle_for_pl_account(acc, balances, dimensions) + ) + + # closing liability account + self.closing_account_gle.append( + self.get_gle_for_closing_account(account_balances["balances"], dimensions) + ) - return gl_entries + return self.pl_accounts_reverse_gle + self.closing_account_gle - def get_gle_for_pl_account(self, acc): - gl_entry = self.get_gl_dict( + def get_gle_for_pl_account(self, acc, balances, dimensions): + balance_in_account_currency = flt(balances.debit_in_account_currency) - flt( + balances.credit_in_account_currency + ) + balance_in_company_currency = flt(balances.debit) - flt(balances.credit) + gl_entry = frappe._dict( { "company": self.company, - "closing_date": self.posting_date, - "account": acc.account, - "cost_center": acc.cost_center, - "finance_book": acc.finance_book, - "account_currency": acc.account_currency, - "debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) - if flt(acc.bal_in_account_currency) < 0 - else 0, - "debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0, - "credit_in_account_currency": abs(flt(acc.bal_in_account_currency)) - if flt(acc.bal_in_account_currency) > 0 + "posting_date": self.period_end_date, + "account": acc, + "account_currency": balances.account_currency, + "debit_in_account_currency": abs(balance_in_account_currency) + if balance_in_account_currency < 0 else 0, - "credit": abs(flt(acc.bal_in_company_currency)) - if flt(acc.bal_in_company_currency) > 0 + "debit": abs(balance_in_company_currency) if balance_in_company_currency < 0 else 0, + "credit_in_account_currency": abs(balance_in_account_currency) + if balance_in_account_currency > 0 else 0, + "credit": abs(balance_in_company_currency) if balance_in_company_currency > 0 else 0, "is_period_closing_voucher_entry": 1, - }, - item=acc, + "voucher_type": "Period Closing Voucher", + "voucher_no": self.name, + "fiscal_year": self.fiscal_year, + "remarks": self.remarks, + "is_opening": "No", + } ) - self.update_default_dimensions(gl_entry, acc) + self.update_default_dimensions(gl_entry, dimensions) return gl_entry - def get_gle_for_closing_account(self, acc): - gl_entry = self.get_gl_dict( + def get_gle_for_closing_account(self, dimension_balance, dimensions): + balance_in_account_currency = flt(dimension_balance.balance_in_account_currency) + balance_in_company_currency = flt(dimension_balance.balance_in_company_currency) + gl_entry = frappe._dict( { "company": self.company, - "closing_date": self.posting_date, + "posting_date": self.period_end_date, "account": self.closing_account_head, - "cost_center": acc.cost_center, - "finance_book": acc.finance_book, - "account_currency": acc.account_currency, - "debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) - if flt(acc.bal_in_account_currency) > 0 - else 0, - "debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0, - "credit_in_account_currency": abs(flt(acc.bal_in_account_currency)) - if flt(acc.bal_in_account_currency) < 0 + "account_currency": frappe.db.get_value( + "Account", self.closing_account_head, "account_currency" + ), + "debit_in_account_currency": balance_in_account_currency + if balance_in_account_currency > 0 else 0, - "credit": abs(flt(acc.bal_in_company_currency)) - if flt(acc.bal_in_company_currency) < 0 + "debit": balance_in_company_currency if balance_in_company_currency > 0 else 0, + "credit_in_account_currency": abs(balance_in_account_currency) + if balance_in_account_currency < 0 else 0, + "credit": abs(balance_in_company_currency) if balance_in_company_currency < 0 else 0, "is_period_closing_voucher_entry": 1, - }, - item=acc, + "voucher_type": "Period Closing Voucher", + "voucher_no": self.name, + "fiscal_year": self.fiscal_year, + "remarks": self.remarks, + "is_opening": "No", + } ) - self.update_default_dimensions(gl_entry, acc) + self.update_default_dimensions(gl_entry, dimensions) return gl_entry - def get_closing_entries(self, acc): - closing_entry = self.get_gl_dict( - { - "company": self.company, - "closing_date": self.posting_date, - "period_closing_voucher": self.name, - "account": acc.account, - "cost_center": acc.cost_center, - "finance_book": acc.finance_book, - "account_currency": acc.account_currency, - "debit_in_account_currency": flt(acc.debit_in_account_currency), - "debit": flt(acc.debit), - "credit_in_account_currency": flt(acc.credit_in_account_currency), - "credit": flt(acc.credit), - }, - item=acc, - ) + def update_default_dimensions(self, gl_entry, dimensions): + for i, dimension in enumerate(self.accounting_dimension_fields): + gl_entry[dimension] = dimensions[i] - for dimension in self.accounting_dimensions: - closing_entry.update({dimension: acc.get(dimension)}) + def get_account_balances_based_on_dimensions(self, report_type): + """Get balance for dimension-wise pl accounts""" + self.get_accounting_dimension_fields() + acc_bal_dict = frappe._dict() + gl_entries = [] - return closing_entry + with frappe.db.unbuffered_cursor(): + gl_entries = self.get_gl_entries_for_current_period(report_type, as_iterator=True) + for gle in gl_entries: + acc_bal_dict = self.set_account_balance_dict(gle, acc_bal_dict) - def update_default_dimensions(self, gl_entry, acc): - if not self.accounting_dimensions: - self.accounting_dimensions = get_accounting_dimensions() + if report_type == "Balance Sheet" and self.is_first_period_closing_voucher(): + opening_entries = self.get_gl_entries_for_current_period(report_type, only_opening_entries=True) + for gle in opening_entries: + acc_bal_dict = self.set_account_balance_dict(gle, acc_bal_dict) - for dimension in self.accounting_dimensions: - gl_entry.update({dimension: acc.get(dimension)}) + return acc_bal_dict - def get_balances_based_on_dimensions( - self, group_by_account=False, report_type=None, for_aggregation=False, get_opening_entries=False - ): - """Get balance for dimension-wise pl accounts""" + def get_accounting_dimension_fields(self): + default_dimensions = ["cost_center", "finance_book", "project"] + self.accounting_dimension_fields = default_dimensions + get_accounting_dimensions() - qb_dimension_fields = ["cost_center", "finance_book", "project"] + def get_gl_entries_for_current_period(self, report_type, only_opening_entries=False, as_iterator=False): + date_condition = "" + if only_opening_entries: + 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 + return frappe.db.sql( + """ + SELECT + name, + posting_date, + account, + account_currency, + debit_in_account_currency, + credit_in_account_currency, + debit, + credit, + {} + FROM `tabGL Entry` + WHERE + {} + AND company = %s + AND voucher_type != 'Period Closing Voucher' + AND EXISTS(SELECT name FROM `tabAccount` WHERE name = account AND report_type = %s) + AND is_cancelled = 0 + """.format( + ", ".join(self.accounting_dimension_fields), + date_condition, + ), + (self.company, report_type), + as_dict=1, + as_iterator=as_iterator, + ) - self.accounting_dimensions = get_accounting_dimensions() - for dimension in self.accounting_dimensions: - qb_dimension_fields.append(dimension) + def set_account_balance_dict(self, gle, acc_bal_dict): + key = self.get_key(gle) + + acc_bal_dict.setdefault(key, frappe._dict()).setdefault( + gle.account, + frappe._dict( + { + "debit_in_account_currency": 0, + "credit_in_account_currency": 0, + "debit": 0, + "credit": 0, + "account_currency": gle.account_currency, + } + ), + ) - if group_by_account: - qb_dimension_fields.append("account") + acc_bal_dict[key][gle.account].debit_in_account_currency += flt(gle.debit_in_account_currency) + acc_bal_dict[key][gle.account].credit_in_account_currency += flt(gle.credit_in_account_currency) + acc_bal_dict[key][gle.account].debit += flt(gle.debit) + acc_bal_dict[key][gle.account].credit += flt(gle.credit) + + # dimension-wise total balances + acc_bal_dict[key].setdefault( + "balances", + frappe._dict( + { + "balance_in_account_currency": 0, + "balance_in_company_currency": 0, + } + ), + ) - account_filters = { - "company": self.company, - "is_group": 0, - } + balance_in_account_currency = flt(gle.debit_in_account_currency) - flt(gle.credit_in_account_currency) + balance_in_company_currency = flt(gle.debit) - flt(gle.credit) - if report_type: - account_filters.update({"report_type": report_type}) + acc_bal_dict[key]["balances"].balance_in_account_currency += balance_in_account_currency + acc_bal_dict[key]["balances"].balance_in_company_currency += balance_in_company_currency - accounts = frappe.get_all("Account", filters=account_filters, pluck="name") + return acc_bal_dict - gl_entry = frappe.qb.DocType("GL Entry") - query = frappe.qb.from_(gl_entry).select(gl_entry.account, gl_entry.account_currency) + def get_key(self, gle): + return tuple([gle.get(dimension) for dimension in self.accounting_dimension_fields]) - if not for_aggregation: - query = query.select( - (Sum(gl_entry.debit_in_account_currency) - Sum(gl_entry.credit_in_account_currency)).as_( - "bal_in_account_currency" - ), - (Sum(gl_entry.debit) - Sum(gl_entry.credit)).as_("bal_in_company_currency"), - ) - else: - query = query.select( - (Sum(gl_entry.debit_in_account_currency)).as_("debit_in_account_currency"), - (Sum(gl_entry.credit_in_account_currency)).as_("credit_in_account_currency"), - (Sum(gl_entry.debit)).as_("debit"), - (Sum(gl_entry.credit)).as_("credit"), - ) + def get_account_closing_balances(self): + pl_closing_entries = self.get_closing_entries_for_pl_accounts() + bs_closing_entries = self.get_closing_entries_for_balance_sheet_accounts() + closing_entries = pl_closing_entries + bs_closing_entries + return closing_entries - for dimension in qb_dimension_fields: - query = query.select(gl_entry[dimension]) + def get_closing_entries_for_pl_accounts(self): + closing_entries = copy.deepcopy(self.pl_accounts_reverse_gle) + for d in self.pl_accounts_reverse_gle: + # reverse debit and credit + gle_copy = copy.deepcopy(d) + gle_copy.debit = d.credit + gle_copy.credit = d.debit + gle_copy.debit_in_account_currency = d.credit_in_account_currency + gle_copy.credit_in_account_currency = d.debit_in_account_currency + gle_copy.is_period_closing_voucher_entry = 0 + gle_copy.period_closing_voucher = self.name + closing_entries.append(gle_copy) - query = query.where( - (gl_entry.company == self.company) - & (gl_entry.is_cancelled == 0) - & (gl_entry.account.isin(accounts)) + return closing_entries + + def get_closing_entries_for_balance_sheet_accounts(self): + closing_entries = [] + balance_sheet_account_balances = self.get_account_balances_based_on_dimensions( + report_type="Balance Sheet" ) - if get_opening_entries: - query = query.where( - ( # noqa: UP034 - (gl_entry.posting_date.between(self.get("year_start_date"), self.posting_date)) - | (gl_entry.is_opening == "Yes") + for dimensions, account_balances in balance_sheet_account_balances.items(): + for acc, balances in account_balances.items(): + balance_in_company_currency = flt(balances.debit_in_account_currency) - flt( + balances.credit_in_account_currency ) - ) - else: - query = query.where( - gl_entry.posting_date.between(self.get("year_start_date"), self.posting_date) - & gl_entry.is_opening - == "No" - ) + if acc != "balances" and balance_in_company_currency: + closing_entries.append(self.get_closing_entry(acc, balances, dimensions)) + + return closing_entries + + def get_closing_entry(self, account, balances, dimensions): + closing_entry = frappe._dict( + { + "company": self.company, + "closing_date": self.period_end_date, + "period_closing_voucher": self.name, + "account": account, + "account_currency": balances.account_currency, + "debit_in_account_currency": flt(balances.debit_in_account_currency), + "debit": flt(balances.debit), + "credit_in_account_currency": flt(balances.credit_in_account_currency), + "credit": flt(balances.credit), + "is_period_closing_voucher_entry": 0, + } + ) + self.update_default_dimensions(closing_entry, dimensions) + return closing_entry - if for_aggregation: - query = query.where(gl_entry.voucher_type != "Period Closing Voucher") + def is_first_period_closing_voucher(self): + return not frappe.db.exists( + "Period Closing Voucher", + {"company": self.company, "docstatus": 1, "name": ("!=", self.name)}, + ) - for dimension in qb_dimension_fields: - query = query.groupby(gl_entry[dimension]) + def cancel_gl_entries(self): + if self.get_gle_count_against_current_pcv() > 5000: + frappe.enqueue( + process_cancellation, + voucher_type="Period Closing Voucher", + voucher_no=self.name, + queue="long", + enqueue_after_commit=True, + ) + frappe.msgprint( + _("The GL Entries will be cancelled in the background, it can take a few minutes."), + alert=True, + ) + else: + process_cancellation(voucher_type="Period Closing Voucher", voucher_no=self.name) - return query.run(as_dict=1) + def get_gle_count_against_current_pcv(self): + return frappe.db.count( + "GL Entry", + {"voucher_type": "Period Closing Voucher", "voucher_no": self.name, "is_cancelled": 0}, + ) -def process_gl_and_closing_entries(gl_entries, closing_entries, voucher_name, company, closing_date): - from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import ( - make_closing_entries, - ) +def process_gl_and_closing_entries(doc): from erpnext.accounts.general_ledger import make_gl_entries try: + gl_entries = doc.get_pcv_gl_entries() if gl_entries: make_gl_entries(gl_entries, merge_entries=False) - make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date) - frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Completed") + + closing_entries = doc.get_account_closing_balances() + if closing_entries: + make_closing_entries(closing_entries, doc.name, doc.company, doc.period_end_date) + + frappe.db.set_value(doc.doctype, doc.name, "gle_processing_status", "Completed") except Exception as e: frappe.db.rollback() frappe.log_error(e) - frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Failed") + frappe.db.set_value(doc.doctype, doc.name, "gle_processing_status", "Failed") def process_cancellation(voucher_type, voucher_no): @@ -395,3 +472,29 @@ def delete_closing_entries(voucher_no): frappe.qb.from_(closing_balance).delete().where( closing_balance.period_closing_voucher == voucher_no ).run() + + +@frappe.whitelist() +def get_period_start_end_date(fiscal_year, company): + fy_start_date, fy_end_date = frappe.db.get_value( + "Fiscal Year", fiscal_year, ["year_start_date", "year_end_date"] + ) + prev_closed_period_end_date = get_previous_closed_period_in_current_year(fiscal_year, company) + period_start_date = ( + add_days(prev_closed_period_end_date, 1) if prev_closed_period_end_date else fy_start_date + ) + return period_start_date, fy_end_date + + +def get_previous_closed_period_in_current_year(fiscal_year, company): + prev_closed_period_end_date = frappe.db.get_value( + "Period Closing Voucher", + filters={ + "company": company, + "fiscal_year": fiscal_year, + "docstatus": 1, + }, + fieldname=["period_end_date"], + order_by="period_end_date desc", + ) + return prev_closed_period_end_date diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index 1bd565e1b361..e9d65f7f8560 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -317,16 +317,18 @@ def test_closing_balance_with_dimensions_and_test_reposting_entry(self): repost_doc.posting_date = today() repost_doc.save() - def make_period_closing_voucher(self, posting_date=None, submit=True): + def make_period_closing_voucher(self, posting_date, submit=True): surplus_account = create_account() cost_center = create_cost_center("Test Cost Center 1") + fy = get_fiscal_year(posting_date, company="Test PCV Company") pcv = frappe.get_doc( { "doctype": "Period Closing Voucher", "transaction_date": posting_date or today(), - "posting_date": posting_date or today(), + "period_start_date": fy[1], + "period_end_date": fy[2], "company": "Test PCV Company", - "fiscal_year": get_fiscal_year(today(), company="Test PCV Company")[0], + "fiscal_year": fy[0], "cost_center": cost_center, "closing_account_head": surplus_account, "remarks": "test", diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py index 8c8ba633df02..f37e542dd894 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -46,8 +46,8 @@ def validate_for_closed_fiscal_year(self): frappe.db.get_all( "Period Closing Voucher", filters={"company": self.company}, - order_by="posting_date desc", - pluck="posting_date", + order_by="period_end_date desc", + pluck="period_end_date", limit=1, ) or None diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py index f631ef437d61..9f906bb7647d 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py @@ -129,13 +129,15 @@ def test_04_pcv_validation(self): cost_center=self.cost_center, rate=100, ) + fy = get_fiscal_year(today(), company=self.company) pcv = frappe.get_doc( { "doctype": "Period Closing Voucher", "transaction_date": today(), - "posting_date": today(), + "period_start_date": fy[1], + "period_end_date": today(), "company": self.company, - "fiscal_year": get_fiscal_year(today(), company=self.company)[0], + "fiscal_year": fy[0], "cost_center": self.cost_center, "closing_account_head": self.retained_earnings, "remarks": "test", diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index ad8cc97e101d..856e2b96af09 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -37,13 +37,14 @@ def make_gl_entries( validate_disabled_accounts(gl_map) gl_map = process_gl_map(gl_map, merge_entries) if gl_map and len(gl_map) > 1: - create_payment_ledger_entry( - gl_map, - cancel=0, - adv_adj=adv_adj, - update_outstanding=update_outstanding, - from_repost=from_repost, - ) + if gl_map[0].voucher_type != "Period Closing Voucher": + create_payment_ledger_entry( + gl_map, + cancel=0, + adv_adj=adv_adj, + update_outstanding=update_outstanding, + from_repost=from_repost, + ) save_entries(gl_map, adv_adj, update_outstanding, from_repost) # Post GL Map proccess there may no be any GL Entries elif gl_map: @@ -116,17 +117,16 @@ def get_accounting_dimensions_for_offsetting_entry(gl_map, company): def validate_disabled_accounts(gl_map): accounts = [d.account for d in gl_map if d.account] - Account = frappe.qb.DocType("Account") - - disabled_accounts = ( - frappe.qb.from_(Account) - .where(Account.name.isin(accounts) & Account.disabled == 1) - .select(Account.name, Account.disabled) - ).run(as_dict=True) + disabled_accounts = frappe.get_all( + "Account", + filters={"disabled": 1, "is_group": 0, "company": gl_map[0].company}, + fields=["name"], + ) - if disabled_accounts: + used_disabled_accounts = set(accounts).intersection(set([d.name for d in disabled_accounts])) + if used_disabled_accounts: account_list = "
" - account_list += ", ".join([frappe.bold(d.name) for d in disabled_accounts]) + account_list += ", ".join([frappe.bold(d) for d in used_disabled_accounts]) frappe.throw( _("Cannot create accounting entries against disabled accounts: {0}").format(account_list), title=_("Disabled Account Selected"), @@ -708,7 +708,7 @@ def validate_against_pcv(is_opening, posting_date, company): ) last_pcv_date = frappe.db.get_value( - "Period Closing Voucher", {"docstatus": 1, "company": company}, "max(posting_date)" + "Period Closing Voucher", {"docstatus": 1, "company": company}, "max(period_end_date)" ) if last_pcv_date and getdate(posting_date) <= getdate(last_pcv_date): diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 43100c812852..8d4a8579ae3d 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -385,6 +385,7 @@ def build_delivery_note_map(self): self.delivery_notes = frappe._dict() # delivery note link inside sales invoice + # nosemgrep si_against_dn = frappe.db.sql( """ select parent, delivery_note @@ -400,6 +401,7 @@ def build_delivery_note_map(self): if d.delivery_note: self.delivery_notes.setdefault(d.parent, set()).add(d.delivery_note) + # nosemgrep dn_against_si = frappe.db.sql( """ select distinct parent, against_sales_invoice @@ -417,13 +419,16 @@ def build_delivery_note_map(self): def get_invoice_details(self): self.invoice_details = frappe._dict() if self.account_type == "Receivable": + # nosemgrep si_list = frappe.db.sql( """ select name, due_date, po_no from `tabSales Invoice` where posting_date <= %s + and company = %s + and docstatus = 1 """, - self.filters.report_date, + (self.filters.report_date, self.filters.company), as_dict=1, ) for d in si_list: @@ -431,6 +436,7 @@ def get_invoice_details(self): # Get Sales Team if self.filters.show_sales_person: + # nosemgrep sales_team = frappe.db.sql( """ select parent, sales_person @@ -445,25 +451,33 @@ def get_invoice_details(self): ) if self.account_type == "Payable": + # nosemgrep for pi in frappe.db.sql( """ select name, due_date, bill_no, bill_date from `tabPurchase Invoice` - where posting_date <= %s + where + posting_date <= %s + and company = %s + and docstatus = 1 """, - self.filters.report_date, + (self.filters.report_date, self.filters.company), as_dict=1, ): self.invoice_details.setdefault(pi.name, pi) # Invoices booked via Journal Entries + # nosemgrep journal_entries = frappe.db.sql( """ select name, due_date, bill_no, bill_date from `tabJournal Entry` - where posting_date <= %s + where + posting_date <= %s + and company = %s + and docstatus = 1 """, - self.filters.report_date, + (self.filters.report_date, self.filters.company), as_dict=1, ) @@ -472,6 +486,8 @@ def get_invoice_details(self): self.invoice_details.setdefault(je.name, je) def set_party_details(self, row): + if not row.party: + return # customer / supplier name party_details = self.get_party_details(row.party) or {} row.update(party_details) @@ -496,6 +512,7 @@ def allocate_outstanding_based_on_payment_terms(self, row): def get_payment_terms(self, row): # build payment_terms for row + # nosemgrep payment_terms_details = frappe.db.sql( f""" select @@ -708,6 +725,7 @@ def allocate_future_payments(self, row): def get_return_entries(self): doctype = "Sales Invoice" if self.account_type == "Receivable" else "Purchase Invoice" filters = { + "posting_date": ("<=", self.filters.report_date), "is_return": 1, "docstatus": 1, "company": self.filters.company, @@ -815,6 +833,7 @@ def get_sales_invoices_or_customers_based_on_sales_person(self): if self.filters.get("sales_person"): lft, rgt = frappe.db.get_value("Sales Person", self.filters.get("sales_person"), ["lft", "rgt"]) + # nosemgrep records = frappe.db.sql( """ select distinct parent, parenttype diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 6d7635979bb7..c233f3c7e2b6 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -9,6 +9,7 @@ import frappe from frappe import _ from frappe.utils import add_days, add_months, cint, cstr, flt, formatdate, get_first_day, getdate +from pypika.terms import ExistsCriterion from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, @@ -181,12 +182,12 @@ def get_data( company, period_list[0]["year_start_date"] if only_current_fiscal_year else None, period_list[-1]["to_date"], - root.lft, - root.rgt, filters, gl_entries_by_account, - ignore_closing_entries=ignore_closing_entries, + root.lft, + root.rgt, root_type=root_type, + ignore_closing_entries=ignore_closing_entries, ) calculate_values( @@ -419,93 +420,78 @@ def set_gl_entries_by_account( company, from_date, to_date, - root_lft, - root_rgt, filters, gl_entries_by_account, + root_lft=None, + root_rgt=None, + root_type=None, ignore_closing_entries=False, ignore_opening_entries=False, - root_type=None, ): """Returns a dict like { "account": [gl entries], ... }""" gl_entries = [] - account_filters = { - "company": company, - "is_group": 0, - "lft": (">=", root_lft), - "rgt": ("<=", root_rgt), - } - - if root_type: - account_filters.update( - { - "root_type": root_type, - } - ) - - accounts_list = frappe.db.get_all( - "Account", - filters=account_filters, - pluck="name", + # For balance sheet + ignore_closing_balances = frappe.db.get_single_value( + "Accounts Settings", "ignore_account_closing_balance" ) - - if accounts_list: - # For balance sheet - ignore_closing_balances = frappe.db.get_single_value( - "Accounts Settings", "ignore_account_closing_balance" + if not from_date and not ignore_closing_balances: + last_period_closing_voucher = frappe.db.get_all( + "Period Closing Voucher", + filters={ + "docstatus": 1, + "company": filters.company, + "period_end_date": ("<", filters["period_start_date"]), + }, + fields=["period_end_date", "name"], + order_by="period_end_date desc", + limit=1, ) - if not from_date and not ignore_closing_balances: - last_period_closing_voucher = frappe.db.get_all( - "Period Closing Voucher", - filters={ - "docstatus": 1, - "company": filters.company, - "posting_date": ("<", filters["period_start_date"]), - }, - fields=["posting_date", "name"], - order_by="posting_date desc", - limit=1, + if last_period_closing_voucher: + gl_entries += get_accounting_entries( + "Account Closing Balance", + from_date, + to_date, + filters, + root_lft, + root_rgt, + root_type, + ignore_closing_entries, + last_period_closing_voucher[0].name, ) - if last_period_closing_voucher: - gl_entries += get_accounting_entries( - "Account Closing Balance", - from_date, - to_date, - accounts_list, - filters, - ignore_closing_entries, - last_period_closing_voucher[0].name, - ) - from_date = add_days(last_period_closing_voucher[0].posting_date, 1) - ignore_opening_entries = True - - gl_entries += get_accounting_entries( - "GL Entry", - from_date, - to_date, - accounts_list, - filters, - ignore_closing_entries, - ignore_opening_entries=ignore_opening_entries, - ) + from_date = add_days(last_period_closing_voucher[0].period_end_date, 1) + ignore_opening_entries = True + + gl_entries += get_accounting_entries( + "GL Entry", + from_date, + to_date, + filters, + root_lft, + root_rgt, + root_type, + ignore_closing_entries, + ignore_opening_entries=ignore_opening_entries, + ) - if filters and filters.get("presentation_currency"): - convert_to_presentation_currency(gl_entries, get_currency(filters)) + if filters and filters.get("presentation_currency"): + convert_to_presentation_currency(gl_entries, get_currency(filters)) - for entry in gl_entries: - gl_entries_by_account.setdefault(entry.account, []).append(entry) + for entry in gl_entries: + gl_entries_by_account.setdefault(entry.account, []).append(entry) - return gl_entries_by_account + return gl_entries_by_account def get_accounting_entries( doctype, from_date, to_date, - accounts, filters, - ignore_closing_entries, + root_lft=None, + root_rgt=None, + root_type=None, + ignore_closing_entries=None, period_closing_voucher=None, ignore_opening_entries=False, ): @@ -535,13 +521,30 @@ def get_accounting_entries( query = query.where(gl_entry.period_closing_voucher == period_closing_voucher) query = apply_additional_conditions(doctype, query, from_date, ignore_closing_entries, filters) - query = query.where(gl_entry.account.isin(accounts)) + + if (root_lft and root_rgt) or root_type: + account_filter_query = get_account_filter_query(root_lft, root_rgt, root_type, gl_entry) + query = query.where(ExistsCriterion(account_filter_query)) entries = query.run(as_dict=True) return entries +def get_account_filter_query(root_lft, root_rgt, root_type, gl_entry): + acc = frappe.qb.DocType("Account") + exists_query = ( + frappe.qb.from_(acc).select(acc.name).where(acc.name == gl_entry.account).where(acc.is_group == 0) + ) + if root_lft and root_rgt: + exists_query = exists_query.where(acc.lft >= root_lft).where(acc.rgt <= root_rgt) + + if root_type: + exists_query = exists_query.where(acc.root_type == root_type) + + return exists_query + + def apply_additional_conditions(doctype, query, from_date, ignore_closing_entries, filters): gl_entry = frappe.qb.DocType(doctype) accounting_dimensions = get_accounting_dimensions(as_list=False) diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index f216ecea15a1..8ca850f301e4 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -94,12 +94,6 @@ def get_data(filters): accounts, accounts_by_name, parent_children_map = filter_accounts(accounts) - min_lft, max_rgt = frappe.db.sql( - """select min(lft), max(rgt) from `tabAccount` - where company=%s""", - (filters.company,), - )[0] - gl_entries_by_account = {} opening_balances = get_opening_balances(filters) @@ -112,10 +106,10 @@ def get_data(filters): filters.company, filters.from_date, filters.to_date, - min_lft, - max_rgt, filters, gl_entries_by_account, + root_lft=None, + root_rgt=None, ignore_closing_entries=not flt(filters.with_period_closing_entry_for_current_period), ignore_opening_entries=True, ) @@ -150,9 +144,9 @@ def get_rootwise_opening_balances(filters, report_type): if not ignore_closing_balances: last_period_closing_voucher = frappe.db.get_all( "Period Closing Voucher", - filters={"docstatus": 1, "company": filters.company, "posting_date": ("<", filters.from_date)}, - fields=["posting_date", "name"], - order_by="posting_date desc", + filters={"docstatus": 1, "company": filters.company, "period_end_date": ("<", filters.from_date)}, + fields=["period_end_date", "name"], + order_by="period_end_date desc", limit=1, ) @@ -168,8 +162,8 @@ def get_rootwise_opening_balances(filters, report_type): ) # Report getting generate from the mid of a fiscal year - if getdate(last_period_closing_voucher[0].posting_date) < getdate(add_days(filters.from_date, -1)): - start_date = add_days(last_period_closing_voucher[0].posting_date, 1) + if getdate(last_period_closing_voucher[0].period_end_date) < getdate(add_days(filters.from_date, -1)): + start_date = add_days(last_period_closing_voucher[0].period_end_date, 1) gle += get_opening_balance( "GL Entry", filters, report_type, accounting_dimensions, start_date=start_date ) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index e59938909c7f..515a299504af 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -314,6 +314,7 @@ erpnext.patches.v13_0.update_docs_link erpnext.patches.v15_0.update_asset_value_for_manual_depr_entries erpnext.patches.v15_0.update_gpa_and_ndb_for_assdeprsch erpnext.patches.v14_0.create_accounting_dimensions_for_closing_balance +erpnext.patches.v14_0.set_period_start_end_date_in_pcv erpnext.patches.v14_0.update_closing_balances #14-07-2023 execute:frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0) erpnext.patches.v14_0.update_reference_type_in_journal_entry_accounts diff --git a/erpnext/patches/v13_0/fix_additional_cost_in_mfg_stock_entry.py b/erpnext/patches/v13_0/fix_additional_cost_in_mfg_stock_entry.py index e305b375c7fb..5609b6bb895e 100644 --- a/erpnext/patches/v13_0/fix_additional_cost_in_mfg_stock_entry.py +++ b/erpnext/patches/v13_0/fix_additional_cost_in_mfg_stock_entry.py @@ -15,7 +15,7 @@ def execute(): def find_broken_stock_entries() -> list[StockEntryCode]: period_closing_date = frappe.db.get_value( - "Period Closing Voucher", {"docstatus": 1}, "posting_date", order_by="posting_date desc" + "Period Closing Voucher", {"docstatus": 1}, "period_end_date", order_by="period_end_date desc" ) stock_entries_to_patch = frappe.db.sql( diff --git a/erpnext/patches/v14_0/set_period_start_end_date_in_pcv.py b/erpnext/patches/v14_0/set_period_start_end_date_in_pcv.py new file mode 100644 index 000000000000..8020a286f69d --- /dev/null +++ b/erpnext/patches/v14_0/set_period_start_end_date_in_pcv.py @@ -0,0 +1,17 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors +# License: MIT. See LICENSE + + +import frappe + + +def execute(): + # nosemgrep + frappe.db.sql( + """ + UPDATE `tabPeriod Closing Voucher` + SET + period_start_date = (select year_start_date from `tabFiscal Year` where name = fiscal_year), + period_end_date = posting_date + """ + ) diff --git a/erpnext/patches/v14_0/single_to_multi_dunning.py b/erpnext/patches/v14_0/single_to_multi_dunning.py index 3b01871d4376..98be0204518c 100644 --- a/erpnext/patches/v14_0/single_to_multi_dunning.py +++ b/erpnext/patches/v14_0/single_to_multi_dunning.py @@ -66,7 +66,7 @@ def get_accounts_closing_date(): ) # always returns datetime.date period_closing_date = frappe.db.get_value( - "Period Closing Voucher", {"docstatus": 1}, "posting_date", order_by="posting_date desc" + "Period Closing Voucher", {"docstatus": 1}, "period_end_date", order_by="period_end_date desc" ) # Set most recent frozen/closing date as filter diff --git a/erpnext/patches/v14_0/update_closing_balances.py b/erpnext/patches/v14_0/update_closing_balances.py index cfc29c87fa11..a2670717ee91 100644 --- a/erpnext/patches/v14_0/update_closing_balances.py +++ b/erpnext/patches/v14_0/update_closing_balances.py @@ -7,67 +7,68 @@ from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import ( make_closing_entries, ) +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( + get_accounting_dimensions, +) from erpnext.accounts.utils import get_fiscal_year def execute(): frappe.db.truncate("Account Closing Balance") + gle_fields = get_gle_fields() + for company in frappe.get_all("Company", pluck="name"): i = 0 company_wise_order = {} - for pcv in frappe.db.get_all( - "Period Closing Voucher", - fields=["company", "posting_date", "name"], - filters={"docstatus": 1, "company": company}, - order_by="posting_date", - ): + for pcv in get_period_closing_vouchers(company): company_wise_order.setdefault(pcv.company, []) - if pcv.posting_date not in company_wise_order[pcv.company]: - pcv_doc = frappe.get_doc("Period Closing Voucher", pcv.name) - pcv_doc.year_start_date = get_fiscal_year( - pcv.posting_date, pcv.fiscal_year, company=pcv.company - )[1] + if pcv.period_end_date not in company_wise_order[pcv.company]: + pcv.pl_accounts_reverse_gle = get_pcv_gl_entries(pcv, gle_fields) + closing_entries = pcv.get_account_closing_balances() + if closing_entries: + make_closing_entries(closing_entries, pcv.name, pcv.company, pcv.period_end_date) - # get gl entries against pcv - gl_entries = frappe.db.get_all( - "GL Entry", filters={"voucher_no": pcv.name, "is_cancelled": 0}, fields=["*"] - ) - for entry in gl_entries: - entry["is_period_closing_voucher_entry"] = 1 - entry["closing_date"] = pcv_doc.posting_date - entry["period_closing_voucher"] = pcv_doc.name + company_wise_order[pcv.company].append(pcv.period_end_date) + i += 1 - closing_entries = [] - if pcv.posting_date not in company_wise_order[pcv.company]: - # get all gl entries for the year - closing_entries = frappe.db.get_all( - "GL Entry", - filters={ - "is_cancelled": 0, - "voucher_no": ["!=", pcv.name], - "posting_date": ["between", [pcv_doc.year_start_date, pcv.posting_date]], - "is_opening": "No", - "company": company, - }, - fields=["*"], - ) +def get_gle_fields(): + default_diemnsion_fields = ["cost_center", "finance_book", "project"] + accounting_dimension_fields = get_accounting_dimensions() + gle_fields = [ + "name", + "posting_date", + "account", + "account_currency", + "debit", + "credit", + "debit_in_account_currency", + "credit_in_account_currency", + *default_diemnsion_fields, + *accounting_dimension_fields, + ] - if i == 0: - # add opening entries only for the first pcv - closing_entries += frappe.db.get_all( - "GL Entry", - filters={"is_cancelled": 0, "is_opening": "Yes", "company": company}, - fields=["*"], - ) + return gle_fields - for entry in closing_entries: - entry["closing_date"] = pcv_doc.posting_date - entry["period_closing_voucher"] = pcv_doc.name - entries = gl_entries + closing_entries +def get_period_closing_vouchers(company): + return frappe.db.get_all( + "Period Closing Voucher", + fields=["name", "closing_account_head", "period_start_date", "period_end_date", "company"], + filters={"docstatus": 1, "company": company}, + order_by="period_end_date", + ) - make_closing_entries(entries, pcv.name, pcv.company, pcv.posting_date) - company_wise_order[pcv.company].append(pcv.posting_date) - i += 1 + +def get_pcv_gl_entries(pcv, gle_fields): + gl_entries = frappe.db.get_all( + "GL Entry", + filters={"voucher_no": pcv.name, "account": ["!=", pcv.closing_account_head], "is_cancelled": 0}, + fields=gle_fields, + ) + for entry in gl_entries: + entry["is_period_closing_voucher_entry"] = 1 + entry["closing_date"] = pcv.period_end_date + entry["period_closing_voucher"] = pcv.name + return gl_entries diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index d203b979c614..cb006bb3e99d 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -1188,18 +1188,19 @@ def postprocess(source, target): # As we are using session user details in the pickup_contact then pickup_contact_person will be session user target.pickup_contact_person = frappe.session.user - contact = frappe.db.get_value( - "Contact", source.contact_person, ["email_id", "phone", "mobile_no"], as_dict=1 - ) - delivery_contact_display = f"{source.contact_display}" - if contact: - if contact.email_id: - delivery_contact_display += "
" + contact.email_id - if contact.phone: - delivery_contact_display += "
" + contact.phone - if contact.mobile_no and not contact.phone: - delivery_contact_display += "
" + contact.mobile_no - target.delivery_contact = delivery_contact_display + if source.contact_person: + contact = frappe.db.get_value( + "Contact", source.contact_person, ["email_id", "phone", "mobile_no"], as_dict=1 + ) + delivery_contact_display = f"{source.contact_display}" + if contact: + if contact.email_id: + delivery_contact_display += "
" + contact.email_id + if contact.phone: + delivery_contact_display += "
" + contact.phone + if contact.mobile_no and not contact.phone: + delivery_contact_display += "
" + contact.mobile_no + target.delivery_contact = delivery_contact_display if source.shipping_address_name: target.delivery_address_name = source.shipping_address_name diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index c20eadeb78d9..0c81a296d5ee 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -125,7 +125,7 @@ def get_max_period_closing_date(company): query = ( frappe.qb.from_(table) - .select(Max(table.posting_date)) + .select(Max(table.period_end_date)) .where((table.company == company) & (table.docstatus == 1)) ).run() diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index ae6ca4e25d05..5993580032f5 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1309,10 +1309,10 @@ def validate_finished_goods(self): 3. Check FG Item and Qty against WO if present (mfg) """ production_item, wo_qty, finished_items = None, 0, [] - - wo_details = frappe.db.get_value("Work Order", self.work_order, ["production_item", "qty"]) - if wo_details: - production_item, wo_qty = wo_details + if self.work_order: + wo_details = frappe.db.get_value("Work Order", self.work_order, ["production_item", "qty"]) + if wo_details: + production_item, wo_qty = wo_details for d in self.get("items"): if d.is_finished_item: diff --git a/erpnext/stock/report/test_reports.py b/erpnext/stock/report/test_reports.py index 85337a3bf548..ad2b46b393fa 100644 --- a/erpnext/stock/report/test_reports.py +++ b/erpnext/stock/report/test_reports.py @@ -1,6 +1,7 @@ import unittest import frappe +from frappe.utils.make_random import get_random from erpnext.tests.utils import ReportFilters, ReportName, execute_script_report @@ -11,7 +12,7 @@ } -batch = frappe.db.get_value("Batch", fieldname=["name"], as_dict=True, order_by="creation desc") +batch = get_random("Batch") REPORT_FILTER_TEST_CASES: list[tuple[ReportName, ReportFilters]] = [ ("Stock Ledger", {"_optional": True}), diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index db912514988b..50ce270c1662 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -687,86 +687,103 @@ def make_purchase_receipt(source_name, target_doc=None, save=False, submit=False else: source_doc = source_name - if not source_doc.is_return: - if not target_doc: - target_doc = frappe.new_doc("Purchase Receipt") - target_doc.is_subcontracted = 1 - target_doc.is_old_subcontracting_flow = 0 - - target_doc = get_mapped_doc( - "Subcontracting Receipt", - source_doc.name, + if source_doc.is_return: + return + + po_sr_item_dict = {} + po_name = None + for item in source_doc.items: + if not item.purchase_order: + continue + + if not po_name: + po_name = item.purchase_order + + po_sr_item_dict[item.purchase_order_item] = { + "qty": flt(item.qty), + "rejected_qty": flt(item.rejected_qty), + "warehouse": item.warehouse, + "rejected_warehouse": item.rejected_warehouse, + "subcontracting_receipt_item": item.name, + } + + if not po_name: + frappe.throw( + _("Purchase Order Item reference is missing in Subcontracting Receipt {0}").format( + source_doc.name + ) + ) + + def update_item(obj, target, source_parent): + sr_item_details = po_sr_item_dict.get(obj.name) + ratio = flt(obj.qty) / flt(obj.fg_item_qty) + + target.update( { - "Subcontracting Receipt": { - "doctype": "Purchase Receipt", - "field_map": { - "posting_date": "posting_date", - "posting_time": "posting_time", - "name": "subcontracting_receipt", - "supplier_warehouse": "supplier_warehouse", - }, - "field_no_map": ["total_qty", "total"], - }, - }, - target_doc, - ignore_child_tables=True, + "qty": ratio * sr_item_details["qty"], + "rejected_qty": ratio * sr_item_details["rejected_qty"], + "warehouse": sr_item_details["warehouse"], + "rejected_warehouse": sr_item_details["rejected_warehouse"], + "subcontracting_receipt_item": sr_item_details["subcontracting_receipt_item"], + } ) - target_doc.currency = frappe.get_cached_value("Company", target_doc.company, "default_currency") - - po_items_details = {} - for item in source_doc.items: - if item.purchase_order and item.purchase_order_item: - if item.purchase_order not in po_items_details: - po_doc = frappe.get_doc("Purchase Order", item.purchase_order) - po_items_details[item.purchase_order] = { - po_item.name: po_item for po_item in po_doc.items - } - - if po_item := po_items_details[item.purchase_order].get(item.purchase_order_item): - conversion_factor = flt(po_item.qty) / flt(po_item.fg_item_qty) - item_row = { - "item_code": po_item.item_code, - "item_name": po_item.item_name, - "conversion_factor": conversion_factor, - "qty": flt(item.qty) * conversion_factor, - "rejected_qty": flt(item.rejected_qty) * conversion_factor, - "uom": po_item.uom, - "rate": po_item.rate, - "warehouse": item.warehouse, - "rejected_warehouse": item.rejected_warehouse, - "purchase_order": item.purchase_order, - "purchase_order_item": item.purchase_order_item, - "subcontracting_receipt_item": item.name, - "project": po_item.project, - } - target_doc.append("items", item_row) - - if not target_doc.items: - frappe.throw( - _("Purchase Order Item reference is missing in Subcontracting Receipt {0}").format( - source_doc.name - ) - ) + def post_process(source, target): + target.set_missing_values() + target.update( + { + "posting_date": source_doc.posting_date, + "posting_time": source_doc.posting_time, + "subcontracting_receipt": source_doc.name, + "supplier_warehouse": source_doc.supplier_warehouse, + "is_subcontracted": 1, + "is_old_subcontracting_flow": 0, + "currency": frappe.get_cached_value("Company", target.company, "default_currency"), + } + ) - target_doc.set_missing_values() + target_doc = get_mapped_doc( + "Purchase Order", + po_name, + { + "Purchase Order": { + "doctype": "Purchase Receipt", + "field_map": {"supplier_warehouse": "supplier_warehouse"}, + "validation": { + "docstatus": ["=", 1], + }, + }, + "Purchase Order Item": { + "doctype": "Purchase Receipt Item", + "field_map": { + "name": "purchase_order_item", + "parent": "purchase_order", + "bom": "bom", + }, + "postprocess": update_item, + "condition": lambda doc: doc.name in po_sr_item_dict, + }, + "Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges", "reset_value": True}, + }, + postprocess=post_process, + ) - if (save or submit) and frappe.has_permission(target_doc.doctype, "create"): - target_doc.save() + if (save or submit) and frappe.has_permission(target_doc.doctype, "create"): + target_doc.save() - if submit and frappe.has_permission(target_doc.doctype, "submit", target_doc): - try: - target_doc.submit() - except Exception as e: - target_doc.add_comment("Comment", _("Submit Action Failed") + "

" + str(e)) + if submit and frappe.has_permission(target_doc.doctype, "submit", target_doc): + try: + target_doc.submit() + except Exception as e: + target_doc.add_comment("Comment", _("Submit Action Failed") + "

" + str(e)) - if notify: - frappe.msgprint( - _("Purchase Receipt {0} created.").format( - get_link_to_form(target_doc.doctype, target_doc.name) - ), - indicator="green", - alert=True, - ) + if notify: + frappe.msgprint( + _("Purchase Receipt {0} created.").format( + get_link_to_form(target_doc.doctype, target_doc.name) + ), + indicator="green", + alert=True, + ) - return target_doc + return target_doc diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index 27ad7dbebdfa..e0fa7923ef99 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -1075,18 +1075,42 @@ def test_subcontracting_receipt_cancel_with_batch(self): @change_settings("Buying Settings", {"auto_create_purchase_receipt": 1}) def test_auto_create_purchase_receipt(self): + from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order + fg_item = "Subcontracted Item SA1" service_items = [ { "warehouse": "_Test Warehouse - _TC", "item_code": "Subcontracted Service Item 1", - "qty": 5, + "qty": 10, "rate": 100, "fg_item": fg_item, "fg_item_qty": 5, }, ] - sco = get_subcontracting_order(service_items=service_items) + + po = create_purchase_order( + rm_items=service_items, + is_subcontracted=1, + supplier_warehouse="_Test Warehouse 1 - _TC", + do_not_submit=True, + ) + po.append( + "taxes", + { + "account_head": "_Test Account Excise Duty - _TC", + "charge_type": "On Net Total", + "cost_center": "_Test Cost Center - _TC", + "description": "Excise Duty", + "doctype": "Purchase Taxes and Charges", + "rate": 10, + }, + ) + po.save() + po.submit() + + sco = get_subcontracting_order(po_name=po.name) + rm_items = get_rm_items(sco.supplied_items) itemwise_details = make_stock_in_entry(rm_items=rm_items) make_stock_transfer_entry( @@ -1094,11 +1118,24 @@ def test_auto_create_purchase_receipt(self): rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details), ) + scr = make_subcontracting_receipt(sco.name) + scr.items[0].qty = 3 scr.save() scr.submit() - self.assertTrue(frappe.db.get_value("Purchase Receipt", {"subcontracting_receipt": scr.name})) + pr_details = frappe.get_all( + "Purchase Receipt", + filters={"subcontracting_receipt": scr.name}, + fields=["name", "total_taxes_and_charges"], + ) + + self.assertTrue(pr_details) + + pr_qty = frappe.db.get_value("Purchase Receipt Item", {"parent": pr_details[0]["name"]}, "qty") + self.assertEqual(pr_qty, 6) + + self.assertEqual(pr_details[0]["total_taxes_and_charges"], 60) def test_use_serial_batch_fields_for_subcontracting_receipt(self): fg_item = make_item( diff --git a/erpnext/tests/test_perf.py b/erpnext/tests/test_perf.py index fc17b1dcbda3..db54ca973958 100644 --- a/erpnext/tests/test_perf.py +++ b/erpnext/tests/test_perf.py @@ -3,7 +3,7 @@ INDEXED_FIELDS = { "Bin": ["item_code"], - "GL Entry": ["voucher_type", "against_voucher_type"], + "GL Entry": ["voucher_no", "posting_date", "company", "party"], "Purchase Order Item": ["item_code"], "Stock Ledger Entry": ["warehouse"], }