From 1cde804c773de41520a6148e7d99ab0c23c39ae1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 30 Nov 2023 13:11:11 +0530 Subject: [PATCH 01/22] refactor: dimensions section in allocation table in reconciliation --- .../payment_reconciliation_allocation.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json index 491c67818d..cbfd9b2d8b 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json +++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json @@ -24,6 +24,7 @@ "difference_account", "exchange_rate", "currency", + "accounting_dimensions_section", "cost_center" ], "fields": [ @@ -157,12 +158,17 @@ "fieldname": "gain_loss_posting_date", "fieldtype": "Date", "label": "Difference Posting Date" + }, + { + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" } ], "is_virtual": 1, "istable": 1, "links": [], - "modified": "2023-11-17 17:33:38.612615", + "modified": "2023-12-14 12:32:45.554730", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation Allocation", From cfb3d872673844f04f5c9dd3f7d7f56288e5dd22 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 14 Dec 2023 12:16:50 +0530 Subject: [PATCH 02/22] refactor: update dimension doctypes in hooks --- erpnext/hooks.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 6efb893e63..cd588c87a6 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -538,6 +538,8 @@ accounting_dimension_doctypes = [ "Account Closing Balance", "Supplier Quotation", "Supplier Quotation Item", + "Payment Reconciliation", + "Payment Reconciliation Allocation", ] get_matching_queries = ( From 20e0acc20a218029d7101a1ba6ff3c1ae03fac02 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 14 Dec 2023 12:39:13 +0530 Subject: [PATCH 03/22] refactor: dimensions filter section in payment reconciliation --- .../payment_reconciliation.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json index ccb9e648cb..bab79d311d 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json @@ -25,6 +25,7 @@ "invoice_limit", "payment_limit", "bank_cash_account", + "accounting_dimensions_section", "cost_center", "sec_break1", "invoice_name", @@ -208,6 +209,14 @@ "fieldname": "payment_name", "fieldtype": "Data", "label": "Filter on Payment" + }, + { + "collapsible": 1, + "collapsible_depends_on": "eval: doc.invoices.length == 0", + "depends_on": "eval:doc.receivable_payable_account", + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions Filter" } ], "hide_toolbar": 1, @@ -215,7 +224,7 @@ "is_virtual": 1, "issingle": 1, "links": [], - "modified": "2023-11-17 17:33:55.701726", + "modified": "2023-12-14 12:38:44.910625", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation", From 20576e0f47ba3c4937121bfab1e0d8d395a590ce Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 14 Dec 2023 13:33:24 +0530 Subject: [PATCH 04/22] refactor: column break in dimension section --- .../payment_reconciliation/payment_reconciliation.json | 7 ++++++- .../payment_reconciliation_allocation.json | 9 +++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json index bab79d311d..666926f00e 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json @@ -27,6 +27,7 @@ "bank_cash_account", "accounting_dimensions_section", "cost_center", + "dimension_col_break", "sec_break1", "invoice_name", "invoices", @@ -217,6 +218,10 @@ "fieldname": "accounting_dimensions_section", "fieldtype": "Section Break", "label": "Accounting Dimensions Filter" + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break" } ], "hide_toolbar": 1, @@ -224,7 +229,7 @@ "is_virtual": 1, "issingle": 1, "links": [], - "modified": "2023-12-14 12:38:44.910625", + "modified": "2023-12-14 13:38:16.264013", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation", diff --git a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json index cbfd9b2d8b..3f85b21350 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json +++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json @@ -25,7 +25,8 @@ "exchange_rate", "currency", "accounting_dimensions_section", - "cost_center" + "cost_center", + "dimension_col_break" ], "fields": [ { @@ -163,12 +164,16 @@ "fieldname": "accounting_dimensions_section", "fieldtype": "Section Break", "label": "Accounting Dimensions" + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break" } ], "is_virtual": 1, "istable": 1, "links": [], - "modified": "2023-12-14 12:32:45.554730", + "modified": "2023-12-14 13:38:26.104150", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation Allocation", From c1fe4bcc64775507a3bd8e02b61274d8dc2d6447 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 15 Dec 2023 16:02:12 +0530 Subject: [PATCH 05/22] refactor: handle dimension filters --- .../payment_reconciliation/payment_reconciliation.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index ed0921ba5b..092cf45100 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -10,6 +10,7 @@ from frappe.query_builder.custom import ConstantColumn from frappe.utils import flt, fmt_money, get_link_to_form, getdate, nowdate, today import erpnext +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions from erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation import ( is_any_doc_running, ) @@ -648,6 +649,14 @@ class PaymentReconciliation(Document): if not invoices_to_reconcile: frappe.throw(_("No records found in Allocation table")) + def build_dimensions_filter_conditions(self): + ple = qb.DocType("Payment Ledger Entry") + dimensions_and_defaults = get_dimensions() + for x in dimensions_and_defaults[0]: + dimension = x.fieldname + if self.get(dimension): + self.accounting_dimension_filter_conditions.append(ple[dimension] == self.get(dimension)) + def build_qb_filter_conditions(self, get_invoices=False, get_return_invoices=False): self.common_filter_conditions.clear() self.accounting_dimension_filter_conditions.clear() @@ -671,6 +680,8 @@ class PaymentReconciliation(Document): if self.to_payment_date: self.ple_posting_date_filter.append(ple.posting_date.lte(self.to_payment_date)) + self.build_dimensions_filter_conditions() + def get_conditions(self, get_payments=False): condition = " and company = '{0}' ".format(self.company) From ff60ec85b85d5548886e247b72cf1262587feba3 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 15 Dec 2023 17:25:02 +0530 Subject: [PATCH 06/22] refactor: pass dimension filters to query --- .../payment_reconciliation.py | 9 +++ erpnext/controllers/accounts_controller.py | 69 ++++++++----------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 092cf45100..cb7d5ea951 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -173,6 +173,15 @@ class PaymentReconciliation(Document): if self.payment_name: condition.update({"name": self.payment_name}) + # pass dynamic dimension filter values to query builder + dimensions = {} + dimensions_and_defaults = get_dimensions() + for x in dimensions_and_defaults[0]: + dimension = x.fieldname + if self.get(dimension): + dimensions.update({dimension: self.get(dimension)}) + condition.update({"accounting_dimensions": dimensions}) + payment_entries = get_advance_payment_entries_for_regional( self.party_type, self.party, diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index aef3d31b10..31ff79906f 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -7,6 +7,7 @@ import json import frappe from frappe import _, bold, qb, throw from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied +from frappe.query_builder import Criterion from frappe.query_builder.custom import ConstantColumn from frappe.query_builder.functions import Abs, Sum from frappe.utils import ( @@ -2695,47 +2696,37 @@ def get_common_query( q = q.select((payment_entry.target_exchange_rate).as_("exchange_rate")) if condition: - if condition.get("name", None): - q = q.where(payment_entry.name.like(f"%{condition.get('name')}%")) + # conditions should be built as an array and passed as Criterion + common_filter_conditions = [] + + common_filter_conditions.append(payment_entry.company == condition["company"]) + if condition.get("name", None): + common_filter_conditions.append(payment_entry.name.like(f"%{condition.get('name')}%")) + + if condition.get("from_payment_date"): + common_filter_conditions.append(payment_entry.posting_date.gte(condition["from_payment_date"])) + + if condition.get("to_payment_date"): + common_filter_conditions.append(payment_entry.posting_date.lte(condition["to_payment_date"])) - q = q.where(payment_entry.company == condition["company"]) - q = ( - q.where(payment_entry.posting_date >= condition["from_payment_date"]) - if condition.get("from_payment_date") - else q - ) - q = ( - q.where(payment_entry.posting_date <= condition["to_payment_date"]) - if condition.get("to_payment_date") - else q - ) if condition.get("get_payments") == True: - q = ( - q.where(payment_entry.cost_center == condition["cost_center"]) - if condition.get("cost_center") - else q - ) - q = ( - q.where(payment_entry.unallocated_amount >= condition["minimum_payment_amount"]) - if condition.get("minimum_payment_amount") - else q - ) - q = ( - q.where(payment_entry.unallocated_amount <= condition["maximum_payment_amount"]) - if condition.get("maximum_payment_amount") - else q - ) - else: - q = ( - q.where(payment_entry.total_debit >= condition["minimum_payment_amount"]) - if condition.get("minimum_payment_amount") - else q - ) - q = ( - q.where(payment_entry.total_debit <= condition["maximum_payment_amount"]) - if condition.get("maximum_payment_amount") - else q - ) + if condition.get("cost_center"): + common_filter_conditions.append(payment_entry.cost_center == condition["cost_center"]) + + if condition.get("accounting_dimensions"): + for field, val in condition.get("accounting_dimensions").items(): + common_filter_conditions.append(payment_entry[field] == val) + + if condition.get("minimum_payment_amount"): + common_filter_conditions.append( + payment_entry.unallocated_amount.gte(condition["minimum_payment_amount"]) + ) + + if condition.get("maximum_payment_amount"): + common_filter_conditions.append( + payment_entry.unallocated_amount.lte(condition["maximum_payment_amount"]) + ) + q = q.where(Criterion.all(common_filter_conditions)) q = q.orderby(payment_entry.posting_date) q = q.limit(limit) if limit else q From ad8475cb8b24d40b04f86903feee08ecac6aa1f1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 19 Dec 2023 17:07:54 +0530 Subject: [PATCH 07/22] refactor: set query filters for dimensions --- .../payment_reconciliation.js | 21 +++++++++++++++++++ .../payment_reconciliation.py | 17 +++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index fc90c3dec0..99593defae 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -95,6 +95,8 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo this.frm.change_custom_button_type(__('Allocate'), null, 'default'); } + this.frm.trigger("set_query_for_dimension_filters"); + // check for any running reconciliation jobs if (this.frm.doc.receivable_payable_account) { this.frm.call({ @@ -125,6 +127,25 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo } } + set_query_for_dimension_filters() { + frappe.call({ + method: "erpnext.accounts.doctype.payment_reconciliation.payment_reconciliation.get_queries_for_dimension_filters", + args: { + company: this.frm.doc.company, + }, + callback: (r) => { + if (!r.exc && r.message) { + r.message.forEach(x => { + this.frm.set_query(x.fieldname, () => { + return { + 'filters': x.filters + }; + }); + }); + } + } + }); + } company() { this.frm.set_value('party', ''); diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index cb7d5ea951..83bccf4271 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -813,3 +813,20 @@ def reconcile_dr_cr_note(dr_cr_notes, company): @erpnext.allow_regional def adjust_allocations_for_taxes(doc): pass + + +@frappe.whitelist() +def get_queries_for_dimension_filters(company: str = None): + dimensions_with_filters = [] + for d in get_dimensions()[0]: + filters = {} + meta = frappe.get_meta(d.document_type) + if meta.has_field("company") and company: + filters.update({"company": company}) + + if meta.is_tree: + filters.update({"is_group": 0}) + + dimensions_with_filters.append({"fieldname": d.fieldname, "filters": filters}) + + return dimensions_with_filters From 5dc22e1811bb1841bb8c790cc3a1e1315cef6074 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 20 Dec 2023 17:19:27 +0530 Subject: [PATCH 08/22] refactor: pass dimension details to query --- .../payment_reconciliation.py | 17 ++++++++++++----- erpnext/accounts/utils.py | 2 ++ erpnext/controllers/accounts_controller.py | 2 ++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 83bccf4271..f382434eb7 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -71,6 +71,7 @@ class PaymentReconciliation(Document): self.common_filter_conditions = [] self.accounting_dimension_filter_conditions = [] self.ple_posting_date_filter = [] + self.dimensions = get_dimensions()[0] def load_from_db(self): # 'modified' attribute is required for `run_doc_method` to work properly. @@ -175,8 +176,7 @@ class PaymentReconciliation(Document): # pass dynamic dimension filter values to query builder dimensions = {} - dimensions_and_defaults = get_dimensions() - for x in dimensions_and_defaults[0]: + for x in self.dimensions: dimension = x.fieldname if self.get(dimension): dimensions.update({dimension: self.get(dimension)}) @@ -528,7 +528,7 @@ class PaymentReconciliation(Document): self.get_unreconciled_entries() def get_payment_details(self, row, dr_or_cr): - return frappe._dict( + payment_details = frappe._dict( { "voucher_type": row.get("reference_type"), "voucher_no": row.get("reference_name"), @@ -551,6 +551,14 @@ class PaymentReconciliation(Document): } ) + dimensions_dict = {} + for x in self.dimensions: + if row.get(x.fieldname): + dimensions_dict.update({x.fieldname: row.get(x.fieldname)}) + + payment_details.update({"dimensions": dimensions_dict}) + return payment_details + def check_mandatory_to_fetch(self): for fieldname in ["company", "party_type", "party", "receivable_payable_account"]: if not self.get(fieldname): @@ -660,8 +668,7 @@ class PaymentReconciliation(Document): def build_dimensions_filter_conditions(self): ple = qb.DocType("Payment Ledger Entry") - dimensions_and_defaults = get_dimensions() - for x in dimensions_and_defaults[0]: + for x in self.dimensions: dimension = x.fieldname if self.get(dimension): self.accounting_dimension_filter_conditions.append(ple[dimension] == self.get(dimension)) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index f933209364..5fffa270f5 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -668,6 +668,7 @@ def update_reference_in_payment_entry( else payment_entry.get_exchange_rate(), "exchange_gain_loss": d.difference_amount, "account": d.account, + "dimensions": d.dimensions, } if d.voucher_detail_no: @@ -2043,6 +2044,7 @@ def create_gain_loss_journal( ref2_dn, ref2_detail_no, cost_center, + dimensions, ) -> str: journal_entry = frappe.new_doc("Journal Entry") journal_entry.voucher_type = "Exchange Gain Or Loss" diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 31ff79906f..f8d53d8082 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1275,6 +1275,7 @@ class AccountsController(TransactionBase): self.name, arg.get("referenced_row"), arg.get("cost_center"), + {}, ) frappe.msgprint( _("Exchange Gain/Loss amount has been booked through {0}").format( @@ -1355,6 +1356,7 @@ class AccountsController(TransactionBase): self.name, d.idx, self.cost_center, + {}, ) frappe.msgprint( _("Exchange Gain/Loss amount has been booked through {0}").format( From 9c5a79209eb014c90aac46a5dd5ed0d9b7cb8f87 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 29 Dec 2023 17:42:41 +0530 Subject: [PATCH 09/22] refactor: replace sql with query builder for Jourals query --- .../payment_reconciliation.py | 133 ++++++++---------- 1 file changed, 61 insertions(+), 72 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index f382434eb7..26bf1c0605 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -195,66 +195,67 @@ class PaymentReconciliation(Document): return payment_entries def get_jv_entries(self): - condition = self.get_conditions() + je = qb.DocType("Journal Entry") + jea = qb.DocType("Journal Entry Account") + conditions = self.get_journal_filter_conditions() + + # Dimension filters + for x in self.dimensions: + dimension = x.fieldname + if self.get(dimension): + conditions.append(jea[dimension] == self.get(dimension)) if self.payment_name: - condition += f" and t1.name like '%%{self.payment_name}%%'" + conditions.append(je.name.like(f"%%{self.payent_name}%%")) if self.get("cost_center"): - condition += f" and t2.cost_center = '{self.cost_center}' " + conditions.append(jea.cost_center == self.cost_center) dr_or_cr = ( "credit_in_account_currency" if erpnext.get_party_account_type(self.party_type) == "Receivable" else "debit_in_account_currency" ) + conditions.append(jea[dr_or_cr].gt(0)) - bank_account_condition = ( - "t2.against_account like %(bank_cash_account)s" if self.bank_cash_account else "1=1" + if self.bank_cash_account: + conditions.append(jea.against_account.like(f"%%{self.bank_cash_account}%%")) + + journal_query = ( + qb.from_(je) + .inner_join(jea) + .on(jea.parent == je.name) + .select( + ConstantColumn("Journal Entry").as_("reference_type"), + je.name.as_("reference_name"), + je.posting_date, + je.remark.as_("remarks"), + jea.name.as_("reference_row"), + jea[dr_or_cr].as_("amount"), + jea.is_advance, + jea.exchange_rate, + jea.account_currency.as_("currency"), + jea.cost_center.as_("cost_center"), + ) + .where( + (je.docstatus == 1) + & (jea.party_type == self.party_type) + & (jea.party == self.party) + & (jea.account == self.receivable_payable_account) + & ( + (jea.reference_type == "") + | (jea.reference_type.isnull()) + | (jea.reference_type.isin(("Sales Order", "Purchase Order"))) + ) + ) + .where(Criterion.all(conditions)) + .orderby(je.posting_date) ) - limit = f"limit {self.payment_limit}" if self.payment_limit else " " + if self.payment_limit: + journal_query = journal_query.limit(self.payment_limit) - # nosemgrep - journal_entries = frappe.db.sql( - """ - select - "Journal Entry" as reference_type, t1.name as reference_name, - t1.posting_date, t1.remark as remarks, t2.name as reference_row, - {dr_or_cr} as amount, t2.is_advance, t2.exchange_rate, - t2.account_currency as currency, t2.cost_center as cost_center - from - `tabJournal Entry` t1, `tabJournal Entry Account` t2 - where - t1.name = t2.parent and t1.docstatus = 1 and t2.docstatus = 1 - and t2.party_type = %(party_type)s and t2.party = %(party)s - and t2.account = %(account)s and {dr_or_cr} > 0 {condition} - and (t2.reference_type is null or t2.reference_type = '' or - (t2.reference_type in ('Sales Order', 'Purchase Order') - and t2.reference_name is not null and t2.reference_name != '')) - and (CASE - WHEN t1.voucher_type in ('Debit Note', 'Credit Note') - THEN 1=1 - ELSE {bank_account_condition} - END) - order by t1.posting_date - {limit} - """.format( - **{ - "dr_or_cr": dr_or_cr, - "bank_account_condition": bank_account_condition, - "condition": condition, - "limit": limit, - } - ), - { - "party_type": self.party_type, - "party": self.party, - "account": self.receivable_payable_account, - "bank_cash_account": "%%%s%%" % self.bank_cash_account, - }, - as_dict=1, - ) + journal_entries = journal_query.run(as_dict=True) return list(journal_entries) @@ -698,37 +699,25 @@ class PaymentReconciliation(Document): self.build_dimensions_filter_conditions() - def get_conditions(self, get_payments=False): - condition = " and company = '{0}' ".format(self.company) + def get_journal_filter_conditions(self): + conditions = [] + je = qb.DocType("Journal Entry") + jea = qb.DocType("Journal Entry Account") + conditions.append(je.company == self.company) - if self.get("cost_center") and get_payments: - condition = " and cost_center = '{0}' ".format(self.cost_center) + if self.from_payment_date: + conditions.append(je.posting_date.gte(self.from_payment_date)) - condition += ( - " and posting_date >= {0}".format(frappe.db.escape(self.from_payment_date)) - if self.from_payment_date - else "" - ) - condition += ( - " and posting_date <= {0}".format(frappe.db.escape(self.to_payment_date)) - if self.to_payment_date - else "" - ) + if self.to_payment_date: + conditions.append(je.posting_date.lte(self.to_payment_date)) if self.minimum_payment_amount: - condition += ( - " and unallocated_amount >= {0}".format(flt(self.minimum_payment_amount)) - if get_payments - else " and total_debit >= {0}".format(flt(self.minimum_payment_amount)) - ) - if self.maximum_payment_amount: - condition += ( - " and unallocated_amount <= {0}".format(flt(self.maximum_payment_amount)) - if get_payments - else " and total_debit <= {0}".format(flt(self.maximum_payment_amount)) - ) + conditions.append(je.total_debit.gte(self.minimum_payment_amount)) - return condition + if self.maximum_payment_amount: + conditions.append(je.total_debit.lte(self.maximumb_payment_amount)) + + return conditions def reconcile_dr_cr_note(dr_cr_notes, company): From 2154502955166243e354897d7dcb22d1987c4693 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 2 Jan 2024 11:33:02 +0530 Subject: [PATCH 10/22] refactor: partial change on outstanding invoice popup --- .../doctype/payment_entry/payment_entry.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 9402e3da09..a98934a664 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -638,6 +638,20 @@ frappe.ui.form.on('Payment Entry', { frm.events.set_unallocated_amount(frm); }, + get_dimensions: function(frm) { + let result = []; + frappe.call({ + method: "erpnext.accounts.doctype.accounting_dimension.accounting_dimension.get_dimensions", + async: false, + callback: function(r) { + if(!r.exc) { + result = r.message[0].map(elem => elem.document_type); + } + } + }); + return result; + }, + get_outstanding_invoices_or_orders: function(frm, get_outstanding_invoices, get_orders_to_be_billed) { const today = frappe.datetime.get_today(); const fields = [ From 0ec17590ae062fbda0c14a2806ec1ac07c638593 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 15 Jan 2024 14:56:51 +0530 Subject: [PATCH 11/22] fix: typo's and parameter changes --- .../doctype/payment_reconciliation/payment_reconciliation.py | 5 +++-- erpnext/accounts/utils.py | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 26bf1c0605..81601a2196 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -206,7 +206,7 @@ class PaymentReconciliation(Document): conditions.append(jea[dimension] == self.get(dimension)) if self.payment_name: - conditions.append(je.name.like(f"%%{self.payent_name}%%")) + conditions.append(je.name.like(f"%%{self.payment_name}%%")) if self.get("cost_center"): conditions.append(jea.cost_center == self.cost_center) @@ -715,7 +715,7 @@ class PaymentReconciliation(Document): conditions.append(je.total_debit.gte(self.minimum_payment_amount)) if self.maximum_payment_amount: - conditions.append(je.total_debit.lte(self.maximumb_payment_amount)) + conditions.append(je.total_debit.lte(self.maximum_payment_amount)) return conditions @@ -803,6 +803,7 @@ def reconcile_dr_cr_note(dr_cr_notes, company): inv.against_voucher, None, inv.cost_center, + frappe._dict(), ) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 5fffa270f5..a80cf6fb1f 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -2046,6 +2046,8 @@ def create_gain_loss_journal( cost_center, dimensions, ) -> str: + # TODO: pass dimensions to Journal + journal_entry = frappe.new_doc("Journal Entry") journal_entry.voucher_type = "Exchange Gain Or Loss" journal_entry.company = company From ab939cc6e8ab3669f1e9b0f007e9459be180ac32 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 15 Jan 2024 16:13:26 +0530 Subject: [PATCH 12/22] refactor: Credit Note and its Exc gain/loss JE inherits dimensions --- .../payment_reconciliation.py | 31 ++++++++++++++----- erpnext/accounts/utils.py | 6 ++-- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 81601a2196..be7201b32f 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -458,8 +458,15 @@ class PaymentReconciliation(Document): row = self.append("allocation", {}) row.update(entry) + def update_dimension_values_in_allocated_entries(self, res): + for x in self.dimensions: + dimension = x.fieldname + if self.get(dimension): + res[dimension] = self.get(dimension) + return res + def get_allocated_entry(self, pay, inv, allocated_amount): - return frappe._dict( + res = frappe._dict( { "reference_type": pay.get("reference_type"), "reference_name": pay.get("reference_name"), @@ -475,6 +482,9 @@ class PaymentReconciliation(Document): } ) + res = self.update_dimension_values_in_allocated_entries(res) + return res + def reconcile_allocations(self, skip_ref_details_update_for_pe=False): adjust_allocations_for_taxes(self) dr_or_cr = ( @@ -500,7 +510,7 @@ class PaymentReconciliation(Document): reconcile_against_document(entry_list, skip_ref_details_update_for_pe) if dr_or_cr_notes: - reconcile_dr_cr_note(dr_or_cr_notes, self.company) + reconcile_dr_cr_note(dr_or_cr_notes, self.company, self.dimensions) @frappe.whitelist() def reconcile(self): @@ -552,12 +562,10 @@ class PaymentReconciliation(Document): } ) - dimensions_dict = {} for x in self.dimensions: if row.get(x.fieldname): - dimensions_dict.update({x.fieldname: row.get(x.fieldname)}) + payment_details[x.fieldname] = row.get(x.fieldname) - payment_details.update({"dimensions": dimensions_dict}) return payment_details def check_mandatory_to_fetch(self): @@ -720,7 +728,7 @@ class PaymentReconciliation(Document): return conditions -def reconcile_dr_cr_note(dr_cr_notes, company): +def reconcile_dr_cr_note(dr_cr_notes, company, active_dimensions=None): for inv in dr_cr_notes: voucher_type = "Credit Note" if inv.voucher_type == "Sales Invoice" else "Debit Note" @@ -770,6 +778,15 @@ def reconcile_dr_cr_note(dr_cr_notes, company): } ) + # Credit Note(JE) will inherit the same dimension values as payment + dimensions_dict = frappe._dict() + if active_dimensions: + for dim in active_dimensions: + dimensions_dict[dim.fieldname] = inv.get(dim.fieldname) + + jv.accounts[0].update(dimensions_dict) + jv.accounts[1].update(dimensions_dict) + jv.flags.ignore_mandatory = True jv.flags.ignore_exchange_rate = True jv.remark = None @@ -803,7 +820,7 @@ def reconcile_dr_cr_note(dr_cr_notes, company): inv.against_voucher, None, inv.cost_center, - frappe._dict(), + dimensions_dict, ) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index a80cf6fb1f..9f7e89a189 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -2046,8 +2046,6 @@ def create_gain_loss_journal( cost_center, dimensions, ) -> str: - # TODO: pass dimensions to Journal - journal_entry = frappe.new_doc("Journal Entry") journal_entry.voucher_type = "Exchange Gain Or Loss" journal_entry.company = company @@ -2080,7 +2078,7 @@ def create_gain_loss_journal( dr_or_cr + "_in_account_currency": 0, } ) - + journal_account.update(dimensions) journal_entry.append("accounts", journal_account) journal_account = frappe._dict( @@ -2096,7 +2094,7 @@ def create_gain_loss_journal( reverse_dr_or_cr: abs(exc_gain_loss), } ) - + journal_account.update(dimensions) journal_entry.append("accounts", journal_account) journal_entry.save() From 188ff8cde794bb1ef1043f0e47469d65944aac1e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 17 Jan 2024 12:37:43 +0530 Subject: [PATCH 13/22] refactor: apply dimension filters on cr/dr notes --- .../doctype/payment_reconciliation/payment_reconciliation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index be7201b32f..e5a34e21ab 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -309,6 +309,7 @@ class PaymentReconciliation(Document): min_outstanding=-(self.minimum_payment_amount) if self.minimum_payment_amount else None, max_outstanding=-(self.maximum_payment_amount) if self.maximum_payment_amount else None, get_payments=True, + accounting_dimensions=self.accounting_dimension_filter_conditions, ) for inv in return_outstanding: From e3c44231abbbe389a1f815ab77f2d6ff0c614e1b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 17 Jan 2024 12:50:05 +0530 Subject: [PATCH 14/22] chore: test dimension filter output --- .../tests/test_accounts_controller.py | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index 97d3c5c32d..3a3e6def48 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -56,6 +56,7 @@ class TestAccountsController(FrappeTestCase): 20 series - Sales Invoice against Journals 30 series - Sales Invoice against Credit Notes 40 series - Company default Cost center is unset + 50 series - Dimension inheritence """ def setUp(self): @@ -1255,3 +1256,88 @@ class TestAccountsController(FrappeTestCase): ) frappe.db.set_value("Company", self.company, "cost_center", cc) + + def setup_dimensions(self): + # create dimension + from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import ( + create_dimension, + ) + + create_dimension() + # make it non-mandatory + loc = frappe.get_doc("Accounting Dimension", "Location") + for x in loc.dimension_defaults: + x.mandatory_for_bs = False + x.mandatory_for_pl = False + loc.save() + + def test_50_dimensions_filter(self): + """ + Gain/Loss JE should inherit its dimension from payment + """ + self.setup_dimensions() + rate_in_account_currency = 1 + + # Invoices + si1 = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True) + si1.department = "Management" + si1.save().submit() + + si2 = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True) + si2.department = "Operations" + si2.save().submit() + + # Payments + cr_note1 = self.create_sales_invoice(qty=-1, conversion_rate=75, rate=1, do_not_save=True) + cr_note1.department = "Management" + cr_note1.is_return = 1 + cr_note1.save().submit() + + cr_note2 = self.create_sales_invoice(qty=-1, conversion_rate=75, rate=1, do_not_save=True) + cr_note2.department = "Legal" + cr_note2.is_return = 1 + cr_note2.save().submit() + + pe1 = get_payment_entry(si1.doctype, si1.name) + pe1.references = [] + pe1.department = "Research & Development" + pe1.save().submit() + + pe2 = get_payment_entry(si1.doctype, si1.name) + pe2.references = [] + pe2.department = "Management" + pe2.save().submit() + + je1 = self.create_journal_entry( + acc1=self.debit_usd, + acc1_exc_rate=75, + acc2=self.cash, + acc1_amount=-1, + acc2_amount=-75, + acc2_exc_rate=1, + ) + je1.accounts[0].party_type = "Customer" + je1.accounts[0].party = self.customer + je1.accounts[0].department = "Management" + je1.save().submit() + + # assert dimension filter's result + pr = self.create_payment_reconciliation() + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 2) + self.assertEqual(len(pr.payments), 5) + + pr.department = "Legal" + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 0) + self.assertEqual(len(pr.payments), 1) + + pr.department = "Management" + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 3) + + pr.department = "Research & Development" + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 0) + self.assertEqual(len(pr.payments), 1) From ba5a7c8cd8ee6fc09b0d81ffbe8b364e584f1f1b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 18 Jan 2024 13:20:06 +0530 Subject: [PATCH 15/22] test: dimension inheritance for cr note reconciliation --- .../tests/test_accounts_controller.py | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index 3a3e6def48..a448ad4a57 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -1273,7 +1273,7 @@ class TestAccountsController(FrappeTestCase): def test_50_dimensions_filter(self): """ - Gain/Loss JE should inherit its dimension from payment + Test workings of dimension filters """ self.setup_dimensions() rate_in_account_currency = 1 @@ -1341,3 +1341,45 @@ class TestAccountsController(FrappeTestCase): pr.get_unreconciled_entries() self.assertEqual(len(pr.invoices), 0) self.assertEqual(len(pr.payments), 1) + + def test_51_cr_note_should_inherit_dimension_from_payment(self): + self.setup_dimensions() + rate_in_account_currency = 1 + + # Invoice + si = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True) + si.department = "Management" + si.save().submit() + + # Payment + cr_note = self.create_sales_invoice(qty=-1, conversion_rate=75, rate=1, do_not_save=True) + cr_note.department = "Management" + cr_note.is_return = 1 + cr_note.save().submit() + + pr = self.create_payment_reconciliation() + pr.department = "Management" + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 1) + invoices = [x.as_dict() for x in pr.invoices] + payments = [x.as_dict() for x in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + self.assertEqual(len(pr.invoices), 0) + self.assertEqual(len(pr.payments), 0) + + # There should be 2 journals, JE(Cr Note) and JE(Exchange Gain/Loss) + exc_je_for_si = self.get_journals_for(si.doctype, si.name) + exc_je_for_cr_note = self.get_journals_for(cr_note.doctype, cr_note.name) + self.assertNotEqual(exc_je_for_si, []) + self.assertEqual(len(exc_je_for_si), 2) + self.assertEqual(len(exc_je_for_cr_note), 2) + self.assertEqual(exc_je_for_si, exc_je_for_cr_note) + + for x in exc_je_for_si + exc_je_for_cr_note: + with self.subTest(x=x): + self.assertEqual( + [cr_note.department, cr_note.department], + frappe.db.get_all("Journal Entry Account", filters={"parent": x.parent}, pluck="department"), + ) From c44eb432a59fb3ffb3748e47356068499f1129b1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 18 Jan 2024 14:35:06 +0530 Subject: [PATCH 16/22] refactor: pass dimension values to Gain/Loss journal --- .../payment_reconciliation.py | 2 +- erpnext/accounts/utils.py | 29 +++++++++++++++---- erpnext/controllers/accounts_controller.py | 8 +++-- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index e5a34e21ab..b2716c9da4 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -508,7 +508,7 @@ class PaymentReconciliation(Document): reconciled_entry.append(payment_details) if entry_list: - reconcile_against_document(entry_list, skip_ref_details_update_for_pe) + reconcile_against_document(entry_list, skip_ref_details_update_for_pe, self.dimensions) if dr_or_cr_notes: reconcile_dr_cr_note(dr_or_cr_notes, self.company, self.dimensions) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 9f7e89a189..d688544122 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -453,7 +453,19 @@ def add_cc(args=None): return cc.name -def reconcile_against_document(args, skip_ref_details_update_for_pe=False): # nosemgrep +def _build_dimensions_dict_for_exc_gain_loss( + entry: dict | object = None, active_dimensions: list = None +): + dimensions_dict = frappe._dict() + if entry and active_dimensions: + for dim in active_dimensions: + dimensions_dict[dim.fieldname] = entry.get(dim.fieldname) + return dimensions_dict + + +def reconcile_against_document( + args, skip_ref_details_update_for_pe=False, active_dimensions=None +): # nosemgrep """ Cancel PE or JV, Update against document, split if required and resubmit """ @@ -482,6 +494,8 @@ def reconcile_against_document(args, skip_ref_details_update_for_pe=False): # n check_if_advance_entry_modified(entry) validate_allocated_amount(entry) + dimensions_dict = _build_dimensions_dict_for_exc_gain_loss(entry, active_dimensions) + # update ref in advance entry if voucher_type == "Journal Entry": referenced_row = update_reference_in_journal_entry(entry, doc, do_not_save=False) @@ -489,10 +503,14 @@ def reconcile_against_document(args, skip_ref_details_update_for_pe=False): # n # amount and account in args # referenced_row is used to deduplicate gain/loss journal entry.update({"referenced_row": referenced_row}) - doc.make_exchange_gain_loss_journal([entry]) + doc.make_exchange_gain_loss_journal([entry], dimensions_dict) else: referenced_row = update_reference_in_payment_entry( - entry, doc, do_not_save=True, skip_ref_details_update_for_pe=skip_ref_details_update_for_pe + entry, + doc, + do_not_save=True, + skip_ref_details_update_for_pe=skip_ref_details_update_for_pe, + dimensions_dict=dimensions_dict, ) doc.save(ignore_permissions=True) @@ -655,7 +673,7 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False): def update_reference_in_payment_entry( - d, payment_entry, do_not_save=False, skip_ref_details_update_for_pe=False + d, payment_entry, do_not_save=False, skip_ref_details_update_for_pe=False, dimensions_dict=None ): reference_details = { "reference_doctype": d.against_voucher_type, @@ -701,8 +719,9 @@ def update_reference_in_payment_entry( if not skip_ref_details_update_for_pe: payment_entry.set_missing_ref_details() payment_entry.set_amounts() + payment_entry.make_exchange_gain_loss_journal( - frappe._dict({"difference_posting_date": d.difference_posting_date}) + frappe._dict({"difference_posting_date": d.difference_posting_date}), dimensions_dict ) if not do_not_save: diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index f8d53d8082..1696df1cf0 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1220,7 +1220,9 @@ class AccountsController(TransactionBase): return True return False - def make_exchange_gain_loss_journal(self, args: dict = None) -> None: + def make_exchange_gain_loss_journal( + self, args: dict = None, dimensions_dict: dict = None + ) -> None: """ Make Exchange Gain/Loss journal for Invoices and Payments """ @@ -1275,7 +1277,7 @@ class AccountsController(TransactionBase): self.name, arg.get("referenced_row"), arg.get("cost_center"), - {}, + dimensions_dict, ) frappe.msgprint( _("Exchange Gain/Loss amount has been booked through {0}").format( @@ -1356,7 +1358,7 @@ class AccountsController(TransactionBase): self.name, d.idx, self.cost_center, - {}, + dimensions_dict, ) frappe.msgprint( _("Exchange Gain/Loss amount has been booked through {0}").format( From 6148fb024b7157d637aa2308e7c856969858468d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 18 Jan 2024 17:08:30 +0530 Subject: [PATCH 17/22] test: dimension inheritance in PE reconciliation --- .../tests/test_accounts_controller.py | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index a448ad4a57..331599f787 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -1342,7 +1342,7 @@ class TestAccountsController(FrappeTestCase): self.assertEqual(len(pr.invoices), 0) self.assertEqual(len(pr.payments), 1) - def test_51_cr_note_should_inherit_dimension_from_payment(self): + def test_51_cr_note_should_inherit_dimension(self): self.setup_dimensions() rate_in_account_currency = 1 @@ -1383,3 +1383,39 @@ class TestAccountsController(FrappeTestCase): [cr_note.department, cr_note.department], frappe.db.get_all("Journal Entry Account", filters={"parent": x.parent}, pluck="department"), ) + + def test_52_dimension_inhertiance_exc_gain_loss(self): + # Sales Invoice in Foreign Currency + self.setup_dimensions() + rate = 80 + rate_in_account_currency = 1 + dimension = "Research & Development" + + si = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_save=True) + si.department = dimension + si.save().submit() + + pe = self.create_payment_entry(amount=1, source_exc_rate=82).save() + pe.department = dimension + pe = pe.save().submit() + + pr = self.create_payment_reconciliation() + pr.department = dimension + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 1) + invoices = [x.as_dict() for x in pr.invoices] + payments = [x.as_dict() for x in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + self.assertEqual(len(pr.invoices), 0) + self.assertEqual(len(pr.payments), 0) + journals = self.get_journals_for(si.doctype, si.name) + self.assertEqual( + [dimension, dimension], + frappe.db.get_all( + "Journal Entry Account", + filters={"parent": ("in", [x.parent for x in journals])}, + pluck="department", + ), + ) From cbd443a78afbc7c58055881e534a8aa56ca4bea6 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 19 Jan 2024 16:44:20 +0530 Subject: [PATCH 18/22] refactor: pass dimensions on advance allocation --- erpnext/controllers/accounts_controller.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 1696df1cf0..d9aa7e8b5a 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -28,6 +28,7 @@ from frappe.utils import ( import erpnext from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, + get_dimensions, ) from erpnext.accounts.doctype.pricing_rule.utils import ( apply_pricing_rule_for_free_items, @@ -1423,7 +1424,13 @@ class AccountsController(TransactionBase): if lst: from erpnext.accounts.utils import reconcile_against_document - reconcile_against_document(lst) + # pass dimension values to utility method + active_dimensions = get_dimensions()[0] + for x in lst: + for dim in active_dimensions: + if self.get(dim.fieldname): + x.update({dim.fieldname: self.get(dim.fieldname)}) + reconcile_against_document(lst, active_dimensions=active_dimensions) def on_cancel(self): from erpnext.accounts.doctype.bank_transaction.bank_transaction import ( From fcf4687c523202436234814af3da4c4d84f5eba9 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 19 Jan 2024 16:50:54 +0530 Subject: [PATCH 19/22] test: dimension inheritance on adv allocation --- .../tests/test_accounts_controller.py | 58 +++++++++++++++++-- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index 331599f787..fad216d5a4 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -1389,18 +1389,18 @@ class TestAccountsController(FrappeTestCase): self.setup_dimensions() rate = 80 rate_in_account_currency = 1 - dimension = "Research & Development" + dpt = "Research & Development" si = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_save=True) - si.department = dimension + si.department = dpt si.save().submit() pe = self.create_payment_entry(amount=1, source_exc_rate=82).save() - pe.department = dimension + pe.department = dpt pe = pe.save().submit() pr = self.create_payment_reconciliation() - pr.department = dimension + pr.department = dpt pr.get_unreconciled_entries() self.assertEqual(len(pr.invoices), 1) self.assertEqual(len(pr.payments), 1) @@ -1410,9 +1410,57 @@ class TestAccountsController(FrappeTestCase): pr.reconcile() self.assertEqual(len(pr.invoices), 0) self.assertEqual(len(pr.payments), 0) + + # Exc Gain/Loss journals should inherit dimension from parent journals = self.get_journals_for(si.doctype, si.name) self.assertEqual( - [dimension, dimension], + [dpt, dpt], + frappe.db.get_all( + "Journal Entry Account", + filters={"parent": ("in", [x.parent for x in journals])}, + pluck="department", + ), + ) + + def test_53_dimension_inheritance_on_advance(self): + self.setup_dimensions() + dpt = "Research & Development" + + adv = self.create_payment_entry(amount=1, source_exc_rate=85) + adv.department = dpt + adv.save().submit() + adv.reload() + + # Sales Invoices in different exchange rates + si = self.create_sales_invoice(qty=1, conversion_rate=82, rate=1, do_not_submit=True) + si.department = dpt + advances = si.get_advance_entries() + self.assertEqual(len(advances), 1) + self.assertEqual(advances[0].reference_name, adv.name) + si.append( + "advances", + { + "doctype": "Sales Invoice Advance", + "reference_type": advances[0].reference_type, + "reference_name": advances[0].reference_name, + "reference_row": advances[0].reference_row, + "advance_amount": 1, + "allocated_amount": 1, + "ref_exchange_rate": advances[0].exchange_rate, + "remarks": advances[0].remarks, + }, + ) + si = si.save().submit() + + # Outstanding in both currencies should be '0' + adv.reload() + self.assertEqual(si.outstanding_amount, 0) + self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0) + + # Exc Gain/Loss journals should inherit dimension from parent + journals = self.get_journals_for(si.doctype, si.name) + self.assertEqual( + [dpt, dpt], frappe.db.get_all( "Journal Entry Account", filters={"parent": ("in", [x.parent for x in journals])}, From f8bbb0619cbbbaace8f54a9f8758c3962ebe4725 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 19 Jan 2024 18:00:31 +0530 Subject: [PATCH 20/22] refactor: dynamic dimension filters in pop up --- .../doctype/payment_entry/payment_entry.js | 45 +++++++++---------- .../doctype/payment_entry/payment_entry.py | 8 ++++ .../public/js/utils/dimension_tree_filter.js | 4 ++ 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index a98934a664..f5a07bf2ff 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -638,23 +638,9 @@ frappe.ui.form.on('Payment Entry', { frm.events.set_unallocated_amount(frm); }, - get_dimensions: function(frm) { - let result = []; - frappe.call({ - method: "erpnext.accounts.doctype.accounting_dimension.accounting_dimension.get_dimensions", - async: false, - callback: function(r) { - if(!r.exc) { - result = r.message[0].map(elem => elem.document_type); - } - } - }); - return result; - }, - get_outstanding_invoices_or_orders: function(frm, get_outstanding_invoices, get_orders_to_be_billed) { const today = frappe.datetime.get_today(); - const fields = [ + let fields = [ {fieldtype:"Section Break", label: __("Posting Date")}, {fieldtype:"Date", label: __("From Date"), fieldname:"from_posting_date", default:frappe.datetime.add_days(today, -30)}, @@ -669,18 +655,29 @@ frappe.ui.form.on('Payment Entry', { fieldname:"outstanding_amt_greater_than", default: 0}, {fieldtype:"Column Break"}, {fieldtype:"Float", label: __("Less Than Amount"), fieldname:"outstanding_amt_less_than"}, - {fieldtype:"Section Break"}, - {fieldtype:"Link", label:__("Cost Center"), fieldname:"cost_center", options:"Cost Center", - "get_query": function() { - return { - "filters": {"company": frm.doc.company} - } + ]; + + if (frm.dimension_filters) { + let column_break_insertion_point = Math.ceil((frm.dimension_filters.length)/2); + + fields.push({fieldtype:"Section Break"}); + frm.dimension_filters.map((elem, idx)=>{ + fields.push({ + fieldtype: "Link", + label: elem.document_type == "Cost Center" ? "Cost Center" : elem.label, + options: elem.document_type, + fieldname: elem.fieldname || elem.document_type + }); + if(idx+1 == column_break_insertion_point) { + fields.push({fieldtype:"Column Break"}); } - }, - {fieldtype:"Column Break"}, + }); + } + + fields = fields.concat([ {fieldtype:"Section Break"}, {fieldtype:"Check", label: __("Allocate Payment Amount"), fieldname:"allocate_payment_amount", default:1}, - ]; + ]); let btn_text = ""; diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 37bd8e6fcd..45e0f0b5a0 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -13,6 +13,7 @@ from pypika import Case from pypika.functions import Coalesce, Sum import erpnext +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions from erpnext.accounts.doctype.bank_account.bank_account import ( get_bank_account_details, get_party_bank_account, @@ -1684,6 +1685,13 @@ def get_outstanding_reference_documents(args, validate=False): condition += " and cost_center='%s'" % args.get("cost_center") accounting_dimensions_filter.append(ple.cost_center == args.get("cost_center")) + # dynamic dimension filters + active_dimensions = get_dimensions()[0] + for dim in active_dimensions: + if args.get(dim.fieldname): + condition += " and {0}='{1}'".format(dim.fieldname, args.get(dim.fieldname)) + accounting_dimensions_filter.append(ple[dim.fieldname] == args.get(dim.fieldname)) + date_fields_dict = { "posting_date": ["from_posting_date", "to_posting_date"], "due_date": ["from_due_date", "to_due_date"], diff --git a/erpnext/public/js/utils/dimension_tree_filter.js b/erpnext/public/js/utils/dimension_tree_filter.js index 3f70c09f66..27d00bacb8 100644 --- a/erpnext/public/js/utils/dimension_tree_filter.js +++ b/erpnext/public/js/utils/dimension_tree_filter.js @@ -25,6 +25,10 @@ erpnext.accounts.dimensions = { }, setup_filters(frm, doctype) { + if (doctype == 'Payment Entry' && this.accounting_dimensions) { + frm.dimension_filters = this.accounting_dimensions + } + if (this.accounting_dimensions) { this.accounting_dimensions.forEach((dimension) => { frappe.model.with_doctype(dimension['document_type'], () => { From ec0f17ca8bd810e41ae73f5a45f304ba38c63d0a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 22 Jan 2024 11:59:20 +0530 Subject: [PATCH 21/22] refactor: update dimensions, only if provided --- erpnext/accounts/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index d688544122..5525af4545 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -2097,7 +2097,8 @@ def create_gain_loss_journal( dr_or_cr + "_in_account_currency": 0, } ) - journal_account.update(dimensions) + if dimensions: + journal_account.update(dimensions) journal_entry.append("accounts", journal_account) journal_account = frappe._dict( @@ -2113,7 +2114,8 @@ def create_gain_loss_journal( reverse_dr_or_cr: abs(exc_gain_loss), } ) - journal_account.update(dimensions) + if dimensions: + journal_account.update(dimensions) journal_entry.append("accounts", journal_account) journal_entry.save() From 7c2cb70387d7dbb7f976d28919ce21f25a0b6acd Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 22 Jan 2024 12:06:07 +0530 Subject: [PATCH 22/22] refactor: handle dynamic dimension in order query --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 45e0f0b5a0..76f9c4641d 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1925,6 +1925,12 @@ def get_orders_to_be_billed( if doc and hasattr(doc, "cost_center") and doc.cost_center: condition = " and cost_center='%s'" % cost_center + # dynamic dimension filters + active_dimensions = get_dimensions()[0] + for dim in active_dimensions: + if filters.get(dim.fieldname): + condition += " and {0}='{1}'".format(dim.fieldname, filters.get(dim.fieldname)) + if party_account_currency == company_currency: grand_total_field = "base_grand_total" rounded_total_field = "base_rounded_total"