From 0ce9e0cc1f40c458a60f33d42f84674a9b7aec82 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 18 Feb 2020 16:07:34 +0530 Subject: [PATCH] feat: Group by AR/AP report (#20573) * feat: Group by AR/AP report * fix: Do not consider total row in charts * fix: Subtotal row for last party --- .../accounts_payable/accounts_payable.js | 15 ++++ .../accounts_receivable.js | 16 +++- .../accounts_receivable.py | 75 ++++++++++++++++--- 3 files changed, 96 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index b1f427ca7f..df700ec9d3 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -100,6 +100,11 @@ frappe.query_reports["Accounts Payable"] = { "fieldtype": "Link", "options": "Supplier Group" }, + { + "fieldname": "group_by_party", + "label": __("Group By Supplier"), + "fieldtype": "Check" + }, { "fieldname":"based_on_payment_terms", "label": __("Based On Payment Terms"), @@ -112,6 +117,16 @@ frappe.query_reports["Accounts Payable"] = { "hidden": 1 } ], + + "formatter": function(value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + if (data && data.bold) { + value = value.bold(); + + } + return value; + }, + onload: function(report) { report.page.add_inner_button(__("Accounts Payable Summary"), function() { var filters = report.get_values(); diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index 9b4dda2f69..5d0154f597 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -87,7 +87,7 @@ frappe.query_reports["Accounts Receivable"] = { frappe.query_report.set_filter_value('payment_terms', value["payment_terms"]); }); - frappe.db.get_value('Customer Credit Limit', {'parent': customer, 'company': company}, + frappe.db.get_value('Customer Credit Limit', {'parent': customer, 'company': company}, ["credit_limit"], function(value) { if (value) { frappe.query_report.set_filter_value('credit_limit', value["credit_limit"]); @@ -131,6 +131,11 @@ frappe.query_reports["Accounts Receivable"] = { "fieldtype": "Link", "options": "Sales Person" }, + { + "fieldname": "group_by_party", + "label": __("Group By Customer"), + "fieldtype": "Check" + }, { "fieldname":"based_on_payment_terms", "label": __("Based On Payment Terms"), @@ -177,6 +182,15 @@ frappe.query_reports["Accounts Receivable"] = { } ], + "formatter": function(value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + if (data && data.bold) { + value = value.bold(); + + } + return value; + }, + onload: function(report) { report.page.add_inner_button(__("Accounts Receivable Summary"), function() { var filters = report.get_values(); diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index f82146a1df..0438f6d09e 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -46,7 +46,7 @@ class ReceivablePayableReport(object): self.get_columns() self.get_data() self.get_chart_data() - return self.columns, self.data, None, self.chart + return self.columns, self.data, None, self.chart, None, self.skip_total_row def set_defaults(self): if not self.filters.get("company"): @@ -57,6 +57,12 @@ class ReceivablePayableReport(object): self.party_type = self.filters.party_type self.party_details = {} self.invoices = set() + self.skip_total_row = 0 + + if self.filters.get('group_by_party'): + self.previous_party='' + self.total_row_map = {} + self.skip_total_row = 1 def get_data(self): self.get_gl_entries() @@ -102,6 +108,12 @@ class ReceivablePayableReport(object): ) self.get_invoices(gle) + if self.filters.get('group_by_party'): + self.init_subtotal_row(gle.party) + + if self.filters.get('group_by_party'): + self.init_subtotal_row('Total') + def get_invoices(self, gle): if gle.voucher_type in ('Sales Invoice', 'Purchase Invoice'): if self.filters.get("sales_person"): @@ -111,6 +123,20 @@ class ReceivablePayableReport(object): else: self.invoices.add(gle.voucher_no) + def init_subtotal_row(self, party): + if not self.total_row_map.get(party): + self.total_row_map.setdefault(party, { + 'party': party, + 'bold': 1 + }) + + for field in self.get_currency_fields(): + self.total_row_map[party][field] = 0.0 + + def get_currency_fields(self): + return ['invoiced', 'paid', 'credit_note', 'outstanding', 'range1', + 'range2', 'range3', 'range4', 'range5'] + def update_voucher_balance(self, gle): # get the row where this balance needs to be updated # if its a payment, it will return the linked invoice or will be considered as advance @@ -135,6 +161,18 @@ class ReceivablePayableReport(object): # advance / unlinked payment or other adjustment row.paid -= gle_balance + def update_sub_total_row(self, row, party): + total_row = self.total_row_map.get(party) + + for field in self.get_currency_fields(): + total_row[field] += row.get(field, 0.0) + + def append_subtotal_row(self, party): + sub_total_row = self.total_row_map.get(party) + self.data.append(sub_total_row) + self.data.append({}) + self.update_sub_total_row(sub_total_row, 'Total') + def get_voucher_balance(self, gle): if self.filters.get("sales_person"): against_voucher = gle.against_voucher or gle.voucher_no @@ -192,11 +230,22 @@ class ReceivablePayableReport(object): else: self.append_row(row) + if self.filters.get('group_by_party'): + self.append_subtotal_row(self.previous_party) + self.data.append(self.total_row_map.get('Total')) + def append_row(self, row): self.allocate_future_payments(row) self.set_invoice_details(row) self.set_party_details(row) self.set_ageing(row) + + if self.filters.get('group_by_party'): + self.update_sub_total_row(row, row.party) + if self.previous_party and (self.previous_party != row.party): + self.append_subtotal_row(self.previous_party) + self.previous_party = row.party + self.data.append(row) def set_invoice_details(self, row): @@ -503,6 +552,7 @@ class ReceivablePayableReport(object): # get all the GL entries filtered by the given filters conditions, values = self.prepare_conditions() + order_by = self.get_order_by_condition() if self.filters.get(scrub(self.party_type)): select_fields = "debit_in_account_currency as debit, credit_in_account_currency as credit" @@ -520,9 +570,8 @@ class ReceivablePayableReport(object): and party_type=%s and (party is not null and party != '') and posting_date <= %s - {1} - order by posting_date, party""" - .format(select_fields, conditions), values, as_dict=True) + {1} {2}""" + .format(select_fields, conditions, order_by), values, as_dict=True) def get_sales_invoices_or_customers_based_on_sales_person(self): if self.filters.get("sales_person"): @@ -557,6 +606,12 @@ class ReceivablePayableReport(object): return " and ".join(conditions), values + def get_order_by_condition(self): + if self.filters.get('group_by_party'): + return "order by party, posting_date" + else: + return "order by posting_date, party" + def add_common_filters(self, conditions, values, party_type_field): if self.filters.company: conditions.append("company=%s") @@ -736,11 +791,13 @@ class ReceivablePayableReport(object): def get_chart_data(self): rows = [] for row in self.data: - values = [row.range1, row.range2, row.range3, row.range4, row.range5] - precision = cint(frappe.db.get_default("float_precision")) or 2 - rows.append({ - 'values': [flt(val, precision) for val in values] - }) + row = frappe._dict(row) + if not cint(row.bold): + values = [row.range1, row.range2, row.range3, row.range4, row.range5] + precision = cint(frappe.db.get_default("float_precision")) or 2 + rows.append({ + 'values': [flt(val, precision) for val in values] + }) self.chart = { "data": {