From 0e113173032aba443fc542d50b672fc30aaea750 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 15 Jun 2023 17:40:42 +0530 Subject: [PATCH 01/13] fix: don't add GL Entry for Acc. Depr. while scrapping non-depreciable assets (backport #35714) (#35716) * fix: don't add GL Entry for Acc. Depr. while scrapping non-depreciable assets (#35714) fix: on asset scrap, don't add gl entry for acc. depr. if no acc. depr. (cherry picked from commit bb39a2cac7f714fe30e77ced870e15ec69620801) # Conflicts: # erpnext/assets/doctype/asset/depreciation.py * chore: fix conflict --------- Co-authored-by: Anand Baburajan --- erpnext/assets/doctype/asset/depreciation.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 4f79ae847caf..d086f180d3a8 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -466,15 +466,19 @@ def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None, "cost_center": depreciation_cost_center, "posting_date": date, }, - { - "account": accumulated_depr_account, - "debit_in_account_currency": accumulated_depr_amount, - "debit": accumulated_depr_amount, - "cost_center": depreciation_cost_center, - "posting_date": date, - }, ] + if accumulated_depr_amount: + gl_entries.append( + { + "account": accumulated_depr_account, + "debit_in_account_currency": accumulated_depr_amount, + "debit": accumulated_depr_amount, + "cost_center": depreciation_cost_center, + "posting_date": date, + }, + ) + profit_amount = flt(selling_amount) - flt(value_after_depreciation) if profit_amount: get_profit_gl_entries( From b95d45981295ab3fb9605c0ef83869e5c4c83059 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 16 Jun 2023 11:15:42 +0530 Subject: [PATCH 02/13] fix: Incorrect field while calculating Tax withholding net total --- .../tax_withholding_category/tax_withholding_category.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 6c11e51b2676..32eaec8cf3eb 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -526,7 +526,7 @@ def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net "docstatus": 1, "posting_date": ("between", (ldc.valid_from, ldc.valid_upto)), }, - "sum(tax_withholding_net_total)", + "sum(base_net_total)", ) if is_valid_certificate( From 571c977e8e871ff1b12350fc85fc3a995a34789a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 16 Jun 2023 12:17:31 +0530 Subject: [PATCH 03/13] fix: Incorrect field while calculating Tax withholding net total --- .../tax_withholding_category.py | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 6c11e51b2676..86c6341b68df 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -518,15 +518,19 @@ def get_invoice_total_without_tcs(inv, tax_details): def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net_total): tds_amount = 0 - limit_consumed = frappe.db.get_value( - "Purchase Invoice", - { - "supplier": ("in", parties), - "apply_tds": 1, - "docstatus": 1, - "posting_date": ("between", (ldc.valid_from, ldc.valid_upto)), - }, - "sum(tax_withholding_net_total)", + + limit_consumed = flt( + frappe.db.get_all( + "Purchase Invoice", + filters={ + "supplier": ("in", parties), + "apply_tds": 1, + "docstatus": 1, + "tax_withholding_category": ldc.tax_withholding_category, + "posting_date": ("between", (ldc.valid_from, ldc.valid_upto)), + }, + fields=["sum(base_net_total) as limit_consumed"], + )[0].get("limit_consumed") ) if is_valid_certificate( From 0a8b7148a5bc7854c04e1d57d3e874eecb51da8d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 17 Jun 2023 13:08:06 +0530 Subject: [PATCH 04/13] fix: add validation for QI in PR (backport #35677) (#35758) fix: add validation for QI in PR (cherry picked from commit 2c1ab569a784d482779f29dc2f7085d78e2a5403) Co-authored-by: s-aga-r --- .../purchase_receipt/purchase_receipt.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 0f4606d39472..bccaade5737c 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -113,6 +113,7 @@ def validate(self): self.set_status() self.po_required() + self.validate_items_quality_inspection() self.validate_with_previous_doc() self.validate_uom_is_integer("uom", ["qty", "received_qty"]) self.validate_uom_is_integer("stock_uom", "stock_qty") @@ -183,6 +184,26 @@ def po_required(self): if not d.purchase_order: frappe.throw(_("Purchase Order number required for Item {0}").format(d.item_code)) + def validate_items_quality_inspection(self): + for item in self.get("items"): + if item.quality_inspection: + qi = frappe.db.get_value( + "Quality Inspection", + item.quality_inspection, + ["reference_type", "reference_name", "item_code"], + as_dict=True, + ) + + if qi.reference_type != self.doctype or qi.reference_name != self.name: + msg = f"""Row #{item.idx}: Please select a valid Quality Inspection with Reference Type + {frappe.bold(self.doctype)} and Reference Name {frappe.bold(self.name)}.""" + frappe.throw(_(msg)) + + if qi.item_code != item.item_code: + msg = f"""Row #{item.idx}: Please select a valid Quality Inspection with Item Code + {frappe.bold(item.item_code)}.""" + frappe.throw(_(msg)) + def get_already_received_qty(self, po, po_detail): qty = frappe.db.sql( """select sum(qty) from `tabPurchase Receipt Item` From 46d0b7d3170c798c156cf2d124b77a814108611f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 19 Jun 2023 09:14:01 +0530 Subject: [PATCH 05/13] fix: loan interest accrual date (#35695) fix: loan interest accrual date (#35695) fix: loan interest accrual date --------- Co-authored-by: Abhinav Raut Co-authored-by: Deepesh Garg (cherry picked from commit 2a24423ad2cb6733359fc2b45f39e676e6f9ec24) Co-authored-by: Abhinav Raut --- .../doctype/loan_interest_accrual/loan_interest_accrual.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index 8adbd72b6db4..a6b91806e78c 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -293,8 +293,8 @@ def get_last_accrual_date(loan, posting_date): # interest for last interest accrual date is already booked, so add 1 day last_disbursement_date = get_last_disbursement_date(loan, posting_date) - if last_disbursement_date and getdate(last_disbursement_date) > getdate( - last_interest_accrual_date + if last_disbursement_date and getdate(last_disbursement_date) > add_days( + getdate(last_interest_accrual_date), 1 ): last_interest_accrual_date = last_disbursement_date From 3d0add81fa89a639428d484c2f0d25a409a3cd2a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 19 Jun 2023 12:53:00 +0530 Subject: [PATCH 06/13] fix: Allocated amount validation for other party types (#35741) * fix: Allocated amount validation for other party types (#35741) * fix: Allocated amount validation for other party types * chore: Validation for return allocations * chore: minor typo --------- Co-authored-by: anandbaburajan (cherry picked from commit 9d27a25e5f2335386402f96f83a10729695b20c8) * chore: remove unnecessary donor party type check --------- Co-authored-by: Deepesh Garg Co-authored-by: Anand Baburajan --- .../doctype/payment_entry/payment_entry.py | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 90ae1d792c7f..9db4c45dae08 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -150,9 +150,22 @@ def validate_payment_type_with_outstanding(self): ) def validate_allocated_amount(self): - if self.payment_type == "Internal Transfer" or self.party_type in ("Donor"): + if self.payment_type == "Internal Transfer": return + if self.party_type in ("Customer", "Supplier"): + self.validate_allocated_amount_with_latest_data() + else: + fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.") + for d in self.get("references"): + if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(d.outstanding_amount): + frappe.throw(fail_message.format(d.idx)) + + # Check for negative outstanding invoices as well + if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount): + frappe.throw(fail_message.format(d.idx)) + + def validate_allocated_amount_with_latest_data(self): latest_references = get_outstanding_reference_documents( { "posting_date": self.posting_date, @@ -170,7 +183,7 @@ def validate_allocated_amount(self): d = frappe._dict(d) latest_lookup.update({(d.voucher_type, d.voucher_no): d}) - for d in self.get("references").copy(): + for d in self.get("references"): latest = latest_lookup.get((d.reference_doctype, d.reference_name)) # The reference has already been fully paid @@ -189,18 +202,14 @@ def validate_allocated_amount(self): ).format(d.reference_doctype, d.reference_name) ) - d.outstanding_amount = latest.outstanding_amount - fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.") - if (flt(d.allocated_amount)) > 0: - if flt(d.allocated_amount) > flt(d.outstanding_amount): - frappe.throw(fail_message.format(d.idx)) + if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount): + frappe.throw(fail_message.format(d.idx)) # Check for negative outstanding invoices as well - if flt(d.allocated_amount) < 0: - if flt(d.allocated_amount) < flt(d.outstanding_amount): - frappe.throw(fail_message.format(d.idx)) + if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount): + frappe.throw(fail_message.format(d.idx)) def delink_advance_entry_references(self): for reference in self.references: From f22969d2665bd47e1b9f6428f197025f52334d4c Mon Sep 17 00:00:00 2001 From: Anoop Kurungadam Date: Mon, 8 May 2023 20:41:38 +0530 Subject: [PATCH 07/13] fix: account group totals calculation to consider include_in_gross (cherry picked from commit 8dcb9302b417618505ea24e5566c017eff451c1e) --- .../gross_and_net_profit_report.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py index cd5f36670715..7f2877989bd0 100644 --- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py +++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py @@ -152,10 +152,17 @@ def adjust_account(data, period_list, consolidated=False): totals = {} for node in leaf_nodes: set_total(node, node["total"], data, totals) - for d in data: + + for d in reversed(data): for period in period_list: - key = period if consolidated else period.key + if d.get("is_group"): + # reset totals for group accounts as totals set by get_data doesn't consider include_in_gross check + d[period.key] = sum( + item[period.key] for item in data if item.get("parent_account") == d.get("account") + ) + d["total"] = totals[d["account"]] + return data @@ -170,7 +177,6 @@ def set_total(node, value, complete_list, totals): next(item for item in complete_list if item["account"] == parent), value, complete_list, totals ) - def get_profit( gross_income, gross_expense, period_list, company, profit_type, currency=None, consolidated=False ): From c6885e678944f406e9ee5adcf6e4ac882e9950da Mon Sep 17 00:00:00 2001 From: Anoop Kurungadam Date: Mon, 8 May 2023 20:48:43 +0530 Subject: [PATCH 08/13] refactor: remove unused parameters (cherry picked from commit 50822f207ec5272d4d71a2b6579693da2088105d) --- .../gross_and_net_profit_report.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py index 7f2877989bd0..f2412819c4e8 100644 --- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py +++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py @@ -125,12 +125,12 @@ def get_revenue(data, period_list, include_in_gross=1): data_to_be_removed = True while data_to_be_removed: - revenue, data_to_be_removed = remove_parent_with_no_child(revenue, period_list) + revenue, data_to_be_removed = remove_parent_with_no_child(revenue) revenue = adjust_account(revenue, period_list) return copy.deepcopy(revenue) -def remove_parent_with_no_child(data, period_list): +def remove_parent_with_no_child(data): data_to_be_removed = False for parent in data: if "is_group" in parent and parent.get("is_group") == 1: @@ -147,7 +147,7 @@ def remove_parent_with_no_child(data, period_list): return data, data_to_be_removed -def adjust_account(data, period_list, consolidated=False): +def adjust_account(data, period_list): leaf_nodes = [item for item in data if item["is_group"] == 0] totals = {} for node in leaf_nodes: From a53832e16e45909cdf87d205b31c7bbd2a3400bd Mon Sep 17 00:00:00 2001 From: Anoop Kurungadam Date: Tue, 9 May 2023 09:16:10 +0530 Subject: [PATCH 09/13] refactor: merge separate loops for calculating group / leaf node totals rename function remove return statement as the list is mutated (cherry picked from commit 1a3b9c5bdfb9fac04a3a7d8724e6b3c3b593ec19) --- .../gross_and_net_profit_report.py | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py index f2412819c4e8..a7b7f270cfd9 100644 --- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py +++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py @@ -126,7 +126,9 @@ def get_revenue(data, period_list, include_in_gross=1): data_to_be_removed = True while data_to_be_removed: revenue, data_to_be_removed = remove_parent_with_no_child(revenue) - revenue = adjust_account(revenue, period_list) + + adjust_account_totals(revenue, period_list) + return copy.deepcopy(revenue) @@ -147,23 +149,19 @@ def remove_parent_with_no_child(data): return data, data_to_be_removed -def adjust_account(data, period_list): - leaf_nodes = [item for item in data if item["is_group"] == 0] +def adjust_account_totals(data, period_list): totals = {} - for node in leaf_nodes: - set_total(node, node["total"], data, totals) - for d in reversed(data): - for period in period_list: - if d.get("is_group"): + if d.get("is_group"): + for period in period_list: # reset totals for group accounts as totals set by get_data doesn't consider include_in_gross check d[period.key] = sum( item[period.key] for item in data if item.get("parent_account") == d.get("account") ) + else: + set_total(d, d["total"], data, totals) - d["total"] = totals[d["account"]] - - return data + d["total"] = totals[d["account"]] def set_total(node, value, complete_list, totals): @@ -177,6 +175,7 @@ def set_total(node, value, complete_list, totals): next(item for item in complete_list if item["account"] == parent), value, complete_list, totals ) + def get_profit( gross_income, gross_expense, period_list, company, profit_type, currency=None, consolidated=False ): From e899c30428ce26e2a175708f968cccac49253eef Mon Sep 17 00:00:00 2001 From: Anoop Kurungadam Date: Wed, 10 May 2023 12:34:15 +0530 Subject: [PATCH 10/13] fix: add total col for gross and net profit (cherry picked from commit cb9b4fbb91f4b73916416167932064ef5965eed1) --- .../gross_and_net_profit_report.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py index a7b7f270cfd9..f0ca405401d3 100644 --- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py +++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py @@ -196,6 +196,9 @@ def get_profit( if profit_loss[key]: has_value = True + if not profit_loss.get("total"): + profit_loss["total"] = 0 + profit_loss["total"] += profit_loss[key] if has_value: return profit_loss @@ -234,6 +237,9 @@ def get_net_profit( if profit_loss[key]: has_value = True + if not profit_loss.get("total"): + profit_loss["total"] = 0 + profit_loss["total"] += profit_loss[key] if has_value: return profit_loss From 04990d51db5e68ec5c0405e3c0cfbc639b1c1f80 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 19 Jun 2023 20:42:28 +0530 Subject: [PATCH 11/13] fix: fix get outstanding invoices btn and add get outstanding orders btn (backport #35776) (#35788) * fix: fix get outstanding invoices btn and add get outstanding orders btn (#35776) * fix: fix get outstanding invoices btn and add get outstanding orders btn * chore: remove unnecessary arg (cherry picked from commit c1da3ddbbf5e57a0f96733ef05682056051f9ebd) # Conflicts: # erpnext/accounts/doctype/payment_entry/payment_entry.json # erpnext/accounts/doctype/payment_entry/payment_entry.py * chore: resolving conflicts * chore: resolving conflicts properly --------- Co-authored-by: Anand Baburajan --- .../doctype/payment_entry/payment_entry.js | 32 ++++++- .../doctype/payment_entry/payment_entry.json | 23 +++-- .../doctype/payment_entry/payment_entry.py | 87 +++++++++++-------- 3 files changed, 94 insertions(+), 48 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 2e5674874ccc..562d4893717a 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -623,7 +623,7 @@ frappe.ui.form.on('Payment Entry', { frm.events.set_unallocated_amount(frm); }, - get_outstanding_invoice: function(frm) { + get_outstanding_invoices_or_orders: function(frm, get_outstanding_invoices, get_orders_to_be_billed) { const today = frappe.datetime.get_today(); const fields = [ {fieldtype:"Section Break", label: __("Posting Date")}, @@ -653,12 +653,29 @@ frappe.ui.form.on('Payment Entry', { {fieldtype:"Check", label: __("Allocate Payment Amount"), fieldname:"allocate_payment_amount", default:1}, ]; + let btn_text = ""; + + if (get_outstanding_invoices) { + btn_text = "Get Outstanding Invoices"; + } + else if (get_orders_to_be_billed) { + btn_text = "Get Outstanding Orders"; + } + frappe.prompt(fields, function(filters){ frappe.flags.allocate_payment_amount = true; frm.events.validate_filters_data(frm, filters); frm.doc.cost_center = filters.cost_center; - frm.events.get_outstanding_documents(frm, filters); - }, __("Filters"), __("Get Outstanding Documents")); + frm.events.get_outstanding_documents(frm, filters, get_outstanding_invoices, get_orders_to_be_billed); + }, __("Filters"), __(btn_text)); + }, + + get_outstanding_invoices: function(frm) { + frm.events.get_outstanding_invoices_or_orders(frm, true, false); + }, + + get_outstanding_orders: function(frm) { + frm.events.get_outstanding_invoices_or_orders(frm, false, true); }, validate_filters_data: function(frm, filters) { @@ -684,7 +701,7 @@ frappe.ui.form.on('Payment Entry', { } }, - get_outstanding_documents: function(frm, filters) { + get_outstanding_documents: function(frm, filters, get_outstanding_invoices, get_orders_to_be_billed) { frm.clear_table("references"); if(!frm.doc.party) { @@ -708,6 +725,13 @@ frappe.ui.form.on('Payment Entry', { args[key] = filters[key]; } + if (get_outstanding_invoices) { + args["get_outstanding_invoices"] = true; + } + else if (get_orders_to_be_billed) { + args["get_orders_to_be_billed"] = true; + } + frappe.flags.allocate_payment_amount = filters['allocate_payment_amount']; return frappe.call({ diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 3fc1adff2d37..cb04c1c66b5e 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -48,7 +48,8 @@ "base_received_amount", "base_received_amount_after_tax", "section_break_14", - "get_outstanding_invoice", + "get_outstanding_invoices", + "get_outstanding_orders", "references", "section_break_34", "total_allocated_amount", @@ -353,12 +354,6 @@ "fieldtype": "Section Break", "label": "Reference" }, - { - "depends_on": "eval:doc.docstatus==0", - "fieldname": "get_outstanding_invoice", - "fieldtype": "Button", - "label": "Get Outstanding Invoice" - }, { "fieldname": "references", "fieldtype": "Table", @@ -726,12 +721,24 @@ "fieldname": "section_break_60", "fieldtype": "Section Break", "hide_border": 1 + }, + { + "depends_on": "eval:doc.docstatus==0", + "fieldname": "get_outstanding_invoices", + "fieldtype": "Button", + "label": "Get Outstanding Invoices" + }, + { + "depends_on": "eval:doc.docstatus==0", + "fieldname": "get_outstanding_orders", + "fieldtype": "Button", + "label": "Get Outstanding Orders" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-02-23 20:08:39.559814", + "modified": "2023-06-19 11:38:04.387219", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 9db4c45dae08..6cb514e45d2a 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -174,6 +174,8 @@ def validate_allocated_amount_with_latest_data(self): "payment_type": self.payment_type, "party": self.party, "party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to, + "get_outstanding_invoices": True, + "get_orders_to_be_billed": True, } ) @@ -198,7 +200,7 @@ def validate_allocated_amount_with_latest_data(self): ): frappe.throw( _( - "{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' button to get the latest outstanding amount." + "{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts." ).format(d.reference_doctype, d.reference_name) ) @@ -1365,32 +1367,48 @@ def get_outstanding_reference_documents(args): if args.get("company"): condition += " and company = {0}".format(frappe.db.escape(args.get("company"))) - outstanding_invoices = get_outstanding_invoices( - args.get("party_type"), - args.get("party"), - args.get("party_account"), - args.get("company"), - filters=args, - condition=condition, - ) + outstanding_invoices = [] + negative_outstanding_invoices = [] + + if args.get("get_outstanding_invoices"): + outstanding_invoices = get_outstanding_invoices( + args.get("party_type"), + args.get("party"), + args.get("party_account"), + args.get("company"), + filters=args, + condition=condition, + ) - outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices) + outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices) - for d in outstanding_invoices: - d["exchange_rate"] = 1 - if party_account_currency != company_currency: - if d.voucher_type in ("Sales Invoice", "Purchase Invoice", "Expense Claim"): - d["exchange_rate"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate") - elif d.voucher_type == "Journal Entry": - d["exchange_rate"] = get_exchange_rate( - party_account_currency, company_currency, d.posting_date - ) - if d.voucher_type in ("Purchase Invoice"): - d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no") + for d in outstanding_invoices: + d["exchange_rate"] = 1 + if party_account_currency != company_currency: + if d.voucher_type in ("Sales Invoice", "Purchase Invoice", "Expense Claim"): + d["exchange_rate"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate") + elif d.voucher_type == "Journal Entry": + d["exchange_rate"] = get_exchange_rate( + party_account_currency, company_currency, d.posting_date + ) + if d.voucher_type in ("Purchase Invoice"): + d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no") + + # Get negative outstanding sales /purchase invoices + negative_outstanding_invoices = [] + if args.get("party_type") not in ["Student", "Employee"] and not args.get("voucher_no"): + negative_outstanding_invoices = get_negative_outstanding_invoices( + args.get("party_type"), + args.get("party"), + args.get("party_account"), + party_account_currency, + company_currency, + condition=condition, + ) # Get all SO / PO which are not fully billed or against which full advance not paid orders_to_be_billed = [] - if args.get("party_type") != "Student": + if args.get("get_orders_to_be_billed") and args.get("party_type") != "Student": orders_to_be_billed = get_orders_to_be_billed( args.get("posting_date"), args.get("party_type"), @@ -1401,25 +1419,22 @@ def get_outstanding_reference_documents(args): filters=args, ) - # Get negative outstanding sales /purchase invoices - negative_outstanding_invoices = [] - if args.get("party_type") not in ["Student", "Employee"] and not args.get("voucher_no"): - negative_outstanding_invoices = get_negative_outstanding_invoices( - args.get("party_type"), - args.get("party"), - args.get("party_account"), - party_account_currency, - company_currency, - condition=condition, - ) - data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed if not data: + if args.get("get_outstanding_invoices") and args.get("get_orders_to_be_billed"): + ref_document_type = "invoices or orders" + elif args.get("get_outstanding_invoices"): + ref_document_type = "invoices" + elif args.get("get_orders_to_be_billed"): + ref_document_type = "orders" + frappe.msgprint( _( - "No outstanding invoices found for the {0} {1} which qualify the filters you have specified." - ).format(_(args.get("party_type")).lower(), frappe.bold(args.get("party"))) + "No outstanding {0} found for the {1} {2} which qualify the filters you have specified." + ).format( + ref_document_type, _(args.get("party_type")).lower(), frappe.bold(args.get("party")) + ) ) return data From 7239e839a0c948573895c067b6228cda99ced949 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 20 Jun 2023 12:02:04 +0530 Subject: [PATCH 12/13] perf: index `purpose` in `Stock Entry` (backport #35782) (#35784) * perf: index `purpose` in `Stock Entry` (cherry picked from commit 4f941ac5c07a60aa52d1a1ddfc37e55ec3bea886) # Conflicts: # erpnext/stock/doctype/stock_entry/stock_entry.json * chore: `conflicts` --------- Co-authored-by: s-aga-r --- erpnext/stock/doctype/stock_entry/stock_entry.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index f56e059f81c3..76a8d3da901f 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -122,7 +122,8 @@ "oldfieldname": "purpose", "oldfieldtype": "Select", "options": "Material Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor", - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "company", @@ -619,7 +620,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-05-02 05:21:39.060501", + "modified": "2023-06-19 18:23:40.748114", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry", From aa8446d7949c8d61d3a759bf0da46ef953b54bc2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 20 Jun 2023 13:03:06 +0530 Subject: [PATCH 13/13] fix: date and finance book fixes in fixed asset register (backport #35751) (#35800) * fix: date and finance book fixes in fixed asset register (#35751) * fix: handle finance books properly and show all assets by default in fixed asset register * chore: rename value to depr amount * chore: get asset value for correct fb properly * chore: rename include_default_book_entries to include_default_book_assets (cherry picked from commit 0d125885836f0ae5015195401dd41653314fddfe) # Conflicts: # erpnext/assets/report/fixed_asset_register/fixed_asset_register.py * chore: resolving conflicts and renaming entries to assets * chore: formatting --------- Co-authored-by: Anand Baburajan --- .../fixed_asset_register.js | 85 ++++++----- .../fixed_asset_register.py | 136 +++++++++++------- 2 files changed, 125 insertions(+), 96 deletions(-) diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js index 4f7b83610777..b788a32d6abd 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js @@ -19,13 +19,49 @@ frappe.query_reports["Fixed Asset Register"] = { options: "\nIn Location\nDisposed", default: 'In Location' }, + { + fieldname:"asset_category", + label: __("Asset Category"), + fieldtype: "Link", + options: "Asset Category" + }, + { + fieldname:"cost_center", + label: __("Cost Center"), + fieldtype: "Link", + options: "Cost Center" + }, + { + fieldname:"group_by", + label: __("Group By"), + fieldtype: "Select", + options: ["--Select a group--", "Asset Category", "Location"], + default: "--Select a group--", + reqd: 1 + }, + { + fieldname:"only_existing_assets", + label: __("Only existing assets"), + fieldtype: "Check" + }, + { + fieldname:"finance_book", + label: __("Finance Book"), + fieldtype: "Link", + options: "Finance Book", + }, + { + "fieldname": "include_default_book_assets", + "label": __("Include Default Book Assets"), + "fieldtype": "Check", + "default": 1 + }, { "fieldname":"filter_based_on", "label": __("Period Based On"), "fieldtype": "Select", - "options": ["Fiscal Year", "Date Range"], - "default": "Fiscal Year", - "reqd": 1 + "options": ["--Select a period--", "Fiscal Year", "Date Range"], + "default": "--Select a period--", }, { "fieldname":"from_date", @@ -33,7 +69,6 @@ frappe.query_reports["Fixed Asset Register"] = { "fieldtype": "Date", "default": frappe.datetime.add_months(frappe.datetime.nowdate(), -12), "depends_on": "eval: doc.filter_based_on == 'Date Range'", - "reqd": 1 }, { "fieldname":"to_date", @@ -41,7 +76,6 @@ frappe.query_reports["Fixed Asset Register"] = { "fieldtype": "Date", "default": frappe.datetime.nowdate(), "depends_on": "eval: doc.filter_based_on == 'Date Range'", - "reqd": 1 }, { "fieldname":"from_fiscal_year", @@ -50,7 +84,6 @@ frappe.query_reports["Fixed Asset Register"] = { "options": "Fiscal Year", "default": frappe.defaults.get_user_default("fiscal_year"), "depends_on": "eval: doc.filter_based_on == 'Fiscal Year'", - "reqd": 1 }, { "fieldname":"to_fiscal_year", @@ -59,7 +92,6 @@ frappe.query_reports["Fixed Asset Register"] = { "options": "Fiscal Year", "default": frappe.defaults.get_user_default("fiscal_year"), "depends_on": "eval: doc.filter_based_on == 'Fiscal Year'", - "reqd": 1 }, { "fieldname":"date_based_on", @@ -67,44 +99,7 @@ frappe.query_reports["Fixed Asset Register"] = { "fieldtype": "Select", "options": ["Purchase Date", "Available For Use Date"], "default": "Purchase Date", - "reqd": 1 - }, - { - fieldname:"asset_category", - label: __("Asset Category"), - fieldtype: "Link", - options: "Asset Category" - }, - { - fieldname:"cost_center", - label: __("Cost Center"), - fieldtype: "Link", - options: "Cost Center" - }, - { - fieldname:"group_by", - label: __("Group By"), - fieldtype: "Select", - options: ["--Select a group--", "Asset Category", "Location"], - default: "--Select a group--", - reqd: 1 - }, - { - fieldname:"finance_book", - label: __("Finance Book"), - fieldtype: "Link", - options: "Finance Book", - depends_on: "eval: doc.filter_by_finance_book == 1", - }, - { - fieldname:"filter_by_finance_book", - label: __("Filter by Finance Book"), - fieldtype: "Check" - }, - { - fieldname:"only_existing_assets", - label: __("Only existing assets"), - fieldtype: "Check" + "depends_on": "eval: doc.filter_based_on == 'Date Range' || doc.filter_based_on == 'Fiscal Year'", }, ] }; diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py index c6fbc6c58e87..f810819b4fc7 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -2,9 +2,11 @@ # For license information, please see license.txt +from itertools import chain + import frappe from frappe import _ -from frappe.query_builder.functions import Sum +from frappe.query_builder.functions import IfNull, Sum from frappe.utils import cstr, flt, formatdate, getdate from erpnext.accounts.report.financial_statements import ( @@ -13,7 +15,6 @@ validate_fiscal_year, ) from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation -from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts def execute(filters=None): @@ -64,11 +65,9 @@ def get_conditions(filters): def get_data(filters): - data = [] conditions = get_conditions(filters) - depreciation_amount_map = get_finance_book_value_map(filters) pr_supplier_map = get_purchase_receipt_supplier_map() pi_supplier_map = get_purchase_invoice_supplier_map() @@ -102,20 +101,27 @@ def get_data(filters): ] assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields) - assets_linked_to_fb = None + assets_linked_to_fb = get_assets_linked_to_fb(filters) - if filters.filter_by_finance_book: - assets_linked_to_fb = frappe.db.get_all( - doctype="Asset Finance Book", - filters={"finance_book": filters.finance_book or ("is", "not set")}, - pluck="parent", - ) + company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book") + + if filters.include_default_book_assets and company_fb: + finance_book = company_fb + elif filters.finance_book: + finance_book = filters.finance_book + else: + finance_book = None + + depreciation_amount_map = get_asset_depreciation_amount_map(filters, finance_book) for asset in assets_record: if assets_linked_to_fb and asset.asset_id not in assets_linked_to_fb: continue - asset_value = get_asset_value_after_depreciation(asset.asset_id, filters.finance_book) + asset_value = get_asset_value_after_depreciation( + asset.asset_id, finance_book + ) or get_asset_value_after_depreciation(asset.asset_id) + row = { "asset_id": asset.asset_id, "asset_name": asset.asset_name, @@ -126,7 +132,7 @@ def get_data(filters): or pi_supplier_map.get(asset.purchase_invoice), "gross_purchase_amount": asset.gross_purchase_amount, "opening_accumulated_depreciation": asset.opening_accumulated_depreciation, - "depreciated_amount": get_depreciation_amount_of_asset(asset, depreciation_amount_map, filters), + "depreciated_amount": get_depreciation_amount_of_asset(asset, depreciation_amount_map), "available_for_use_date": asset.available_for_use_date, "location": asset.location, "asset_category": asset.asset_category, @@ -140,14 +146,23 @@ def get_data(filters): def prepare_chart_data(data, filters): labels_values_map = {} - date_field = frappe.scrub(filters.date_based_on) + if filters.filter_based_on not in ("Date Range", "Fiscal Year"): + filters_filter_based_on = "Date Range" + date_field = "purchase_date" + filters_from_date = min(data, key=lambda a: a.get(date_field)).get(date_field) + filters_to_date = max(data, key=lambda a: a.get(date_field)).get(date_field) + else: + filters_filter_based_on = filters.filter_based_on + date_field = frappe.scrub(filters.date_based_on) + filters_from_date = filters.from_date + filters_to_date = filters.to_date period_list = get_period_list( filters.from_fiscal_year, filters.to_fiscal_year, - filters.from_date, - filters.to_date, - filters.filter_based_on, + filters_from_date, + filters_to_date, + filters_filter_based_on, "Monthly", company=filters.company, ignore_fiscal_year=True, @@ -184,57 +199,76 @@ def prepare_chart_data(data, filters): } -def get_depreciation_amount_of_asset(asset, depreciation_amount_map, filters): - if asset.calculate_depreciation: - depr_amount = depreciation_amount_map.get(asset.asset_id) or 0.0 - else: - depr_amount = get_manual_depreciation_amount_of_asset(asset, filters) +def get_assets_linked_to_fb(filters): + afb = frappe.qb.DocType("Asset Finance Book") - return flt(depr_amount, 2) + query = frappe.qb.from_(afb).select( + afb.parent, + ) + if filters.include_default_book_assets: + company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book") -def get_finance_book_value_map(filters): - date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date + if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb): + frappe.throw(_("To use a different finance book, please uncheck 'Include Default Book Assets'")) - return frappe._dict( - frappe.db.sql( - """ Select - parent, SUM(depreciation_amount) - FROM `tabDepreciation Schedule` - WHERE - parentfield='schedules' - AND schedule_date<=%s - AND journal_entry IS NOT NULL - AND ifnull(finance_book, '')=%s - GROUP BY parent""", - (date, cstr(filters.finance_book or "")), + query = query.where( + (afb.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""])) + | (afb.finance_book.isnull()) + ) + else: + query = query.where( + (afb.finance_book.isin([cstr(filters.finance_book), ""])) | (afb.finance_book.isnull()) ) - ) + assets_linked_to_fb = list(chain(*query.run(as_list=1))) -def get_manual_depreciation_amount_of_asset(asset, filters): - date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date + return assets_linked_to_fb + + +def get_depreciation_amount_of_asset(asset, depreciation_amount_map): + return depreciation_amount_map.get(asset.asset_id) or 0.0 - (_, _, depreciation_expense_account) = get_depreciation_accounts(asset) +def get_asset_depreciation_amount_map(filters, finance_book): + date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date + + asset = frappe.qb.DocType("Asset") gle = frappe.qb.DocType("GL Entry") + aca = frappe.qb.DocType("Asset Category Account") + company = frappe.qb.DocType("Company") - result = ( + query = ( frappe.qb.from_(gle) - .select(Sum(gle.debit)) - .where(gle.against_voucher == asset.asset_id) - .where(gle.account == depreciation_expense_account) + .join(asset) + .on(gle.against_voucher == asset.name) + .join(aca) + .on((aca.parent == asset.asset_category) & (aca.company_name == asset.company)) + .join(company) + .on(company.name == asset.company) + .select(asset.name.as_("asset"), Sum(gle.debit).as_("depreciation_amount")) + .where( + gle.account == IfNull(aca.depreciation_expense_account, company.depreciation_expense_account) + ) .where(gle.debit != 0) .where(gle.is_cancelled == 0) - .where(gle.posting_date <= date) - ).run() + .where(asset.docstatus == 1) + .groupby(asset.name) + ) - if result and result[0] and result[0][0]: - depr_amount = result[0][0] + if finance_book: + query = query.where( + (gle.finance_book.isin([cstr(finance_book), ""])) | (gle.finance_book.isnull()) + ) else: - depr_amount = 0 + query = query.where((gle.finance_book.isin([""])) | (gle.finance_book.isnull())) + + if filters.filter_based_on in ("Date Range", "Fiscal Year"): + query = query.where(gle.posting_date <= date) + + asset_depr_amount_map = query.run() - return depr_amount + return dict(asset_depr_amount_map) def get_purchase_receipt_supplier_map():