From 41236ed0e5a73ed3c767cd9812ddaf7cc6b51e90 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Wed, 12 Dec 2018 05:12:20 +0500 Subject: [PATCH 1/6] feat: Customer Ledger Summary report --- .../customer_ledger_summary/__init__.py | 0 .../customer_ledger_summary.js | 97 +++++++ .../customer_ledger_summary.json | 26 ++ .../customer_ledger_summary.py | 264 ++++++++++++++++++ 4 files changed, 387 insertions(+) create mode 100644 erpnext/accounts/report/customer_ledger_summary/__init__.py create mode 100644 erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js create mode 100644 erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.json create mode 100644 erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py diff --git a/erpnext/accounts/report/customer_ledger_summary/__init__.py b/erpnext/accounts/report/customer_ledger_summary/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js new file mode 100644 index 0000000000..105977b6e4 --- /dev/null +++ b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js @@ -0,0 +1,97 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Customer Ledger Summary"] = { + "filters": [ + { + "fieldname":"company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company") + }, + { + "fieldname":"from_date", + "label": __("From Date"), + "fieldtype": "Date", + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), + "reqd": 1, + "width": "60px" + }, + { + "fieldname":"to_date", + "label": __("To Date"), + "fieldtype": "Date", + "default": frappe.datetime.get_today(), + "reqd": 1, + "width": "60px" + }, + { + "fieldname":"finance_book", + "label": __("Finance Book"), + "fieldtype": "Link", + "options": "Finance Book" + }, + { + "fieldname":"party", + "label": __("Customer"), + "fieldtype": "Link", + "options": "Customer", + on_change: () => { + var customer = frappe.query_report.get_filter_value('customer'); + if (customer) { + frappe.db.get_value('Customer', customer, ["tax_id", "customer_name", "credit_limit", "payment_terms"], function(value) { + frappe.query_report.set_filter_value('tax_id', value["tax_id"]); + frappe.query_report.set_filter_value('customer_name', value["customer_name"]); + }); + } else { + frappe.query_report.set_filter_value('tax_id', ""); + frappe.query_report.set_filter_value('customer_name', ""); + } + } + }, + { + "fieldname":"customer_group", + "label": __("Customer Group"), + "fieldtype": "Link", + "options": "Customer Group" + }, + { + "fieldname":"payment_terms_template", + "label": __("Payment Terms Template"), + "fieldtype": "Link", + "options": "Payment Terms Template" + }, + { + "fieldname":"territory", + "label": __("Territory"), + "fieldtype": "Link", + "options": "Territory" + }, + { + "fieldname":"sales_partner", + "label": __("Sales Partner"), + "fieldtype": "Link", + "options": "Sales Partner" + }, + { + "fieldname":"sales_person", + "label": __("Sales Person"), + "fieldtype": "Link", + "options": "Sales Person" + }, + { + "fieldname":"tax_id", + "label": __("Tax Id"), + "fieldtype": "Data", + "hidden": 1 + }, + { + "fieldname":"customer_name", + "label": __("Customer Name"), + "fieldtype": "Data", + "hidden": 1 + } + ] +}; diff --git a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.json b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.json new file mode 100644 index 0000000000..91e4e197d3 --- /dev/null +++ b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.json @@ -0,0 +1,26 @@ +{ + "add_total_row": 1, + "creation": "2018-12-11 00:58:19.078506", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "modified": "2018-12-11 00:59:21.708343", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Customer Ledger Summary", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Sales Invoice", + "report_name": "Customer Ledger Summary", + "report_type": "Script Report", + "roles": [ + { + "role": "Accounts Manager" + }, + { + "role": "Accounts User" + } + ] +} \ No newline at end of file diff --git a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py new file mode 100644 index 0000000000..0b590db49d --- /dev/null +++ b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py @@ -0,0 +1,264 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +import erpnext +from frappe import _, scrub +from frappe.utils import getdate, nowdate, flt, cint + +class PartyLedgerSummaryReport(object): + def __init__(self, filters=None): + self.filters = frappe._dict(filters or {}) + self.filters.from_date = getdate(self.filters.from_date or nowdate()) + self.filters.to_date = getdate(self.filters.to_date or nowdate()) + + def run(self, args): + if self.filters.from_date > self.filters.to_date: + frappe.throw(_("From Date must be before To Date")) + + self.filters.party_type = args.get("party_type") + self.party_naming_by = frappe.db.get_value(args.get("naming_by")[0], None, args.get("naming_by")[1]) + + columns = self.get_columns() + data = self.get_data() + return columns, data + + def get_columns(self): + columns = [{ + "label": _(self.filters.party_type), + "fieldtype": "Link", + "fieldname": "party", + "options": self.filters.party_type, + "width": 200 + }] + + if self.party_naming_by == "Naming Series": + columns.append({ + "label": _(self.filters.party_type + "Name"), + "fieldtype": "Data", + "fieldname": "party_name", + "width": 110 + }) + + credit_or_debit_note = "Credit Note" if self.filters.party_type == "Customer" else "Debit Note" + columns += [ + { + "label": _("Opening Balance"), + "fieldname": "opening_balance", + "fieldtype": "Currency", + "width": 120 + }, + { + "label": _("Invoiced Amount"), + "fieldname": "invoiced_amount", + "fieldtype": "Currency", + "width": 120 + }, + { + "label": _("Paid Amount"), + "fieldname": "paid_amount", + "fieldtype": "Currency", + "width": 120 + }, + { + "label": _(credit_or_debit_note), + "fieldname": "return_amount", + "fieldtype": "Currency", + "width": 120 + }, + { + "label": _("Write Off Amount"), + "fieldname": "write_off_amount", + "fieldtype": "Currency", + "width": 120 + }, + { + "label": _("Closing Balance"), + "fieldname": "closing_balance", + "fieldtype": "Currency", + "width": 120 + } + ] + + return columns + + def get_data(self): + if not self.filters.get("company"): + self.filters["company"] = frappe.db.get_single_value('Global Defaults', 'default_company') + + credit_or_debit_note = "Credit Note" if self.filters.party_type == "Customer" else "Debit Note" + invoice_dr_or_cr = "debit" if self.filters.party_type == "Customer" else "credit" + reverse_dr_or_cr = "credit" if self.filters.party_type == "Customer" else "debit" + + self.get_gl_entries() + self.get_return_invoices() + self.get_party_write_off_amounts() + + self.party_data = frappe._dict({}) + for gle in self.gl_entries: + self.party_data.setdefault(gle.party, frappe._dict({ + "party": gle.party, + "party_name": "", # TODO add party_name + "opening_balance": 0, + "invoiced_amount": 0, + "paid_amount": -self.party_write_off_amounts.get(gle.party, 0), + "return_amount": 0, + "write_off_amount": self.party_write_off_amounts.get(gle.party, 0), + "closing_balance": 0 + })) + + amount = gle.get(invoice_dr_or_cr) - gle.get(reverse_dr_or_cr) + self.party_data[gle.party].closing_balance += amount + + if gle.posting_date < self.filters.from_date: + self.party_data[gle.party].opening_balance += amount + else: + if amount > 0: + self.party_data[gle.party].invoiced_amount += amount + elif gle.voucher_no in self.return_invoices: + self.party_data[gle.party].return_amount -= amount + else: + self.party_data[gle.party].paid_amount -= amount + + return [d for d in self.party_data.values() if d.opening_balance or d.invoiced_amount or d.paid_amount + or d.return_amount or d.write_off_amount or d.closing_amount] + + def get_gl_entries(self): + conditions = self.prepare_conditions() + + self.gl_entries = frappe.db.sql(""" + select + posting_date, party, voucher_type, voucher_no, against_voucher_type, against_voucher, debit, credit + from + `tabGL Entry` + where + docstatus < 2 and party_type=%(party_type)s and ifnull(party, '') != '' and posting_date <= %(to_date)s + {0} + order by posting_date""".format(conditions), self.filters, as_dict=True) + + def prepare_conditions(self): + conditions = [""] + + if self.filters.company: + conditions.append("company=%(company)s") + + self.filters.company_finance_book = erpnext.get_default_finance_book(self.filters.company) + + if not self.filters.finance_book or (self.filters.finance_book == self.filters.company_finance_book): + conditions.append("ifnull(finance_book,'') in (%(company_finance_book)s, '')") + elif self.filters.finance_book: + conditions.append("ifnull(finance_book,'') = %(finance_book)s") + + if self.filters.get("party"): + conditions.append("party=%(party)s") + + if self.filters.party_type == "Customer": + if self.filters.get("customer_group"): + lft, rgt = frappe.db.get_value("Customer Group", + self.filters.get("customer_group"), ["lft", "rgt"]) + + conditions.append("""party in (select name from tabCustomer + where exists(select name from `tabCustomer Group` where lft >= {0} and rgt <= {1} + and name=tabCustomer.customer_group))""".format(lft, rgt)) + + if self.filters.get("territory"): + lft, rgt = frappe.db.get_value("Territory", + self.filters.get("territory"), ["lft", "rgt"]) + + conditions.append("""party in (select name from tabCustomer + where exists(select name from `tabTerritory` where lft >= {0} and rgt <= {1} + and name=tabCustomer.territory))""".format(lft, rgt)) + + if self.filters.get("payment_terms_template"): + conditions.append("party in (select name from tabCustomer where payment_terms=%(payment_terms_template)s)") + + if self.filters.get("sales_partner"): + conditions.append("party in (select name from tabCustomer where default_sales_partner=%(sales_partner)s)") + + if self.filters.get("sales_person"): + lft, rgt = frappe.db.get_value("Sales Person", + self.filters.get("sales_person"), ["lft", "rgt"]) + + conditions.append("""party in (select parent from `tabSales Team` + where parenttype = 'Customer' and exists(select name from `tabSales Person` + where lft >= {0} and rgt <= {1} and name=`tabSales Team`.sales_person))""".format(lft, rgt)) + + if self.filters.party_type == "Supplier": + if self.filters.get("supplier_group"): + conditions.append("""party in (select name from tabSupplier + where supplier_group=%(supplier_group)s)""") + + return " and ".join(conditions) + + def get_return_invoices(self): + doctype = "Sales Invoice" if self.filters.party_type == "Customer" else "Purchase Invoice" + self.return_invoices = [d.name for d in frappe.get_all(doctype, filters={"is_return": 1, "docstatus": 1, + "posting_date": ["between", [self.filters.from_date, self.filters.to_date]]})] + + def get_party_write_off_amounts(self): + conditions = self.prepare_conditions() + income_or_expense = "Expense Account" if self.filters.party_type == "Customer" else "Income Account" + invoice_dr_or_cr = "debit" if self.filters.party_type == "Customer" else "credit" + reverse_dr_or_cr = "credit" if self.filters.party_type == "Customer" else "debit" + round_off_account = frappe.get_cached_value('Company', self.filters.company, "round_off_account") + + gl_entries = frappe.db.sql(""" + select + posting_date, account, party, voucher_type, voucher_no, debit, credit + from + `tabGL Entry` + where + docstatus < 2 + and (voucher_type, voucher_no) in ( + select voucher_type, voucher_no from `tabGL Entry` gle, `tabAccount` acc + where acc.name = gle.account and acc.account_type = '{income_or_expense}' + and gle.posting_date between %(from_date)s and %(to_date)s and gle.docstatus < 2 + ) and (voucher_type, voucher_no) in ( + select voucher_type, voucher_no from `tabGL Entry` gle + where gle.party_type=%(party_type)s and ifnull(party, '') != '' + and gle.posting_date between %(from_date)s and %(to_date)s and gle.docstatus < 2 {conditions} + ) + order by posting_date + """.format(conditions=conditions, income_or_expense=income_or_expense), self.filters, as_dict=True) + + self.party_write_off_amounts = {} + write_off_voucher_entries = {} + for gle in gl_entries: + write_off_voucher_entries.setdefault((gle.voucher_type, gle.voucher_no), []) + write_off_voucher_entries[(gle.voucher_type, gle.voucher_no)].append(gle) + + for voucher, voucher_gl_entries in write_off_voucher_entries.iteritems(): + parties = {} + write_offs = {} + has_irrelevant_entry = False + + for gle in voucher_gl_entries: + if gle.account == round_off_account: + continue + elif gle.party: + parties.setdefault(gle.party, 0) + parties[gle.party] += gle.get(reverse_dr_or_cr) - gle.get(invoice_dr_or_cr) + elif frappe.get_cached_value("Account", gle.account, "account_type") == income_or_expense: + write_offs.setdefault(gle.account, 0) + write_offs[gle.account] += gle.get(invoice_dr_or_cr) - gle.get(reverse_dr_or_cr) + else: + has_irrelevant_entry = True + + if parties and write_offs: + if len(parties) == 1: + for account, amount in write_offs.iteritems(): + party = parties.keys()[0] + self.party_write_off_amounts.setdefault(party, 0) + self.party_write_off_amounts[party] += amount + elif len(write_offs) == 1 and not has_irrelevant_entry: + for party, amount in parties.iteritems(): + self.party_write_off_amounts.setdefault(party, 0) + self.party_write_off_amounts[party] += amount + +def execute(filters=None): + args = { + "party_type": "Customer", + "naming_by": ["Selling Settings", "cust_master_name"], + } + return PartyLedgerSummaryReport(filters).run(args) \ No newline at end of file From b07d108beeb361309df1cb11d3be831e5f24841c Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Wed, 12 Dec 2018 05:45:49 +0500 Subject: [PATCH 2/6] feat: Supplier Ledger Summary --- .../customer_ledger_summary.js | 6 +- .../customer_ledger_summary.py | 18 +++- .../supplier_ledger_summary/__init__.py | 0 .../supplier_ledger_summary.js | 97 +++++++++++++++++++ .../supplier_ledger_summary.json | 27 ++++++ .../supplier_ledger_summary.py | 13 +++ 6 files changed, 156 insertions(+), 5 deletions(-) create mode 100644 erpnext/accounts/report/supplier_ledger_summary/__init__.py create mode 100644 erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js create mode 100644 erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.json create mode 100644 erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.py diff --git a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js index 105977b6e4..a123631663 100644 --- a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js +++ b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js @@ -39,9 +39,9 @@ frappe.query_reports["Customer Ledger Summary"] = { "fieldtype": "Link", "options": "Customer", on_change: () => { - var customer = frappe.query_report.get_filter_value('customer'); - if (customer) { - frappe.db.get_value('Customer', customer, ["tax_id", "customer_name", "credit_limit", "payment_terms"], function(value) { + var party = frappe.query_report.get_filter_value('party'); + if (party) { + frappe.db.get_value('Customer', party, ["tax_id", "customer_name"], function(value) { frappe.query_report.set_filter_value('tax_id', value["tax_id"]); frappe.query_report.set_filter_value('customer_name', value["customer_name"]); }); diff --git a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py index 0b590db49d..63a3498f00 100644 --- a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py +++ b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py @@ -47,37 +47,50 @@ class PartyLedgerSummaryReport(object): "label": _("Opening Balance"), "fieldname": "opening_balance", "fieldtype": "Currency", + "options": "currency", "width": 120 }, { "label": _("Invoiced Amount"), "fieldname": "invoiced_amount", "fieldtype": "Currency", + "options": "currency", "width": 120 }, { "label": _("Paid Amount"), "fieldname": "paid_amount", "fieldtype": "Currency", + "options": "currency", "width": 120 }, { "label": _(credit_or_debit_note), "fieldname": "return_amount", "fieldtype": "Currency", + "options": "currency", "width": 120 }, { "label": _("Write Off Amount"), "fieldname": "write_off_amount", "fieldtype": "Currency", + "options": "currency", "width": 120 }, { "label": _("Closing Balance"), "fieldname": "closing_balance", "fieldtype": "Currency", + "options": "currency", "width": 120 + }, + { + "label": _("Currency"), + "fieldname": "currency", + "fieldtype": "Link", + "options": "Currency", + "width": 50 } ] @@ -87,7 +100,7 @@ class PartyLedgerSummaryReport(object): if not self.filters.get("company"): self.filters["company"] = frappe.db.get_single_value('Global Defaults', 'default_company') - credit_or_debit_note = "Credit Note" if self.filters.party_type == "Customer" else "Debit Note" + company_currency = frappe.get_cached_value('Company', self.filters.get("company"), "default_currency") invoice_dr_or_cr = "debit" if self.filters.party_type == "Customer" else "credit" reverse_dr_or_cr = "credit" if self.filters.party_type == "Customer" else "debit" @@ -105,7 +118,8 @@ class PartyLedgerSummaryReport(object): "paid_amount": -self.party_write_off_amounts.get(gle.party, 0), "return_amount": 0, "write_off_amount": self.party_write_off_amounts.get(gle.party, 0), - "closing_balance": 0 + "closing_balance": 0, + "currency": company_currency })) amount = gle.get(invoice_dr_or_cr) - gle.get(reverse_dr_or_cr) diff --git a/erpnext/accounts/report/supplier_ledger_summary/__init__.py b/erpnext/accounts/report/supplier_ledger_summary/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js new file mode 100644 index 0000000000..6fd16f2090 --- /dev/null +++ b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js @@ -0,0 +1,97 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Supplier Ledger Summary"] = { + "filters": [ + { + "fieldname":"company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company") + }, + { + "fieldname":"from_date", + "label": __("From Date"), + "fieldtype": "Date", + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), + "reqd": 1, + "width": "60px" + }, + { + "fieldname":"to_date", + "label": __("To Date"), + "fieldtype": "Date", + "default": frappe.datetime.get_today(), + "reqd": 1, + "width": "60px" + }, + { + "fieldname":"finance_book", + "label": __("Finance Book"), + "fieldtype": "Link", + "options": "Finance Book" + }, + { + "fieldname":"party", + "label": __("Customer"), + "fieldtype": "Link", + "options": "Customer", + on_change: () => { + var party = frappe.query_report.get_filter_value('party'); + if (party) { + frappe.db.get_value('Supplier', party, ["tax_id", "supplier_name"], function(value) { + frappe.query_report.set_filter_value('tax_id', value["tax_id"]); + frappe.query_report.set_filter_value('supplier_name', value["supplier_name"]); + }); + } else { + frappe.query_report.set_filter_value('tax_id', ""); + frappe.query_report.set_filter_value('supplier_name', ""); + } + } + }, + { + "fieldname":"supplier_group", + "label": __("Supplier Group"), + "fieldtype": "Link", + "options": "Supplier Group" + }, + { + "fieldname":"payment_terms_template", + "label": __("Payment Terms Template"), + "fieldtype": "Link", + "options": "Payment Terms Template" + }, + { + "fieldname":"territory", + "label": __("Territory"), + "fieldtype": "Link", + "options": "Territory" + }, + { + "fieldname":"sales_partner", + "label": __("Sales Partner"), + "fieldtype": "Link", + "options": "Sales Partner" + }, + { + "fieldname":"sales_person", + "label": __("Sales Person"), + "fieldtype": "Link", + "options": "Sales Person" + }, + { + "fieldname":"tax_id", + "label": __("Tax Id"), + "fieldtype": "Data", + "hidden": 1 + }, + { + "fieldname":"supplier_name", + "label": __("Supplier Name"), + "fieldtype": "Data", + "hidden": 1 + } + ] +}; diff --git a/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.json b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.json new file mode 100644 index 0000000000..eb3b4123e2 --- /dev/null +++ b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.json @@ -0,0 +1,27 @@ +{ + "add_total_row": 1, + "creation": "2018-12-12 05:10:02.987274", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "letter_head": "Capital Traders", + "modified": "2018-12-12 05:10:02.987274", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Supplier Ledger Summary", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Purchase Invoice", + "report_name": "Supplier Ledger Summary", + "report_type": "Script Report", + "roles": [ + { + "role": "Accounts Manager" + }, + { + "role": "Accounts User" + } + ] +} \ No newline at end of file diff --git a/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.py b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.py new file mode 100644 index 0000000000..d2c23ee4e7 --- /dev/null +++ b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.py @@ -0,0 +1,13 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from erpnext.accounts.report.customer_ledger_summary.customer_ledger_summary import PartyLedgerSummaryReport + +def execute(filters=None): + args = { + "party_type": "Supplier", + "naming_by": ["Buying Settings", "supp_master_name"], + } + return PartyLedgerSummaryReport(filters).run(args) \ No newline at end of file From b53231595d5649ed9fb3d45853ab46ae73240e52 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Wed, 12 Dec 2018 15:46:50 +0500 Subject: [PATCH 3/6] Using same filter for Sales Person from Accounts Receivable Added Customer/Supplier Ledger Summary in Accounts Module page --- .../customer_ledger_summary.py | 11 +++-- erpnext/config/accounts.py | 48 ++++++++++++------- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py index 63a3498f00..2b228fd6b6 100644 --- a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py +++ b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py @@ -194,9 +194,14 @@ class PartyLedgerSummaryReport(object): lft, rgt = frappe.db.get_value("Sales Person", self.filters.get("sales_person"), ["lft", "rgt"]) - conditions.append("""party in (select parent from `tabSales Team` - where parenttype = 'Customer' and exists(select name from `tabSales Person` - where lft >= {0} and rgt <= {1} and name=`tabSales Team`.sales_person))""".format(lft, rgt)) + conditions.append("""exists(select name from `tabSales Team` steam where + steam.sales_person in (select name from `tabSales Person` where lft >= {0} and rgt <= {1}) + and ((steam.parent = voucher_no and steam.parenttype = voucher_type) + or (steam.parent = against_voucher and steam.parenttype = against_voucher_type) + or (steam.parent = party and steam.parenttype = 'Customer')))""".format(lft, rgt)) + #conditions.append("""party in (select parent from `tabSales Team` + # where parenttype = 'Customer' and exists(select name from `tabSales Person` + # where lft >= {0} and rgt <= {1} and name=`tabSales Team`.sales_person))""".format(lft, rgt)) if self.filters.party_type == "Supplier": if self.filters.get("supplier_group"): diff --git a/erpnext/config/accounts.py b/erpnext/config/accounts.py index 15996c3a44..bfa7162400 100644 --- a/erpnext/config/accounts.py +++ b/erpnext/config/accounts.py @@ -351,6 +351,36 @@ def get_data(): "is_query_report": True, "doctype": "Sales Invoice" }, + { + "type": "report", + "name": "Item-wise Sales Register", + "is_query_report": True, + "doctype": "Sales Invoice" + }, + { + "type": "report", + "name": "Item-wise Purchase Register", + "is_query_report": True, + "doctype": "Purchase Invoice" + }, + { + "type": "report", + "name": "Profitability Analysis", + "doctype": "GL Entry", + "is_query_report": True, + }, + { + "type": "report", + "name": "Customer Ledger Summary", + "doctype": "Sales Invoice", + "is_query_report": True, + }, + { + "type": "report", + "name": "Supplier Ledger Summary", + "doctype": "Sales Invoice", + "is_query_report": True, + } ] }, { @@ -363,12 +393,6 @@ def get_data(): "doctype": "GL Entry", "is_query_report": True, }, - { - "type": "report", - "name": "Profitability Analysis", - "doctype": "GL Entry", - "is_query_report": True, - }, { "type": "report", "name": "Payment Period Based On Invoice Date", @@ -381,18 +405,6 @@ def get_data(): "is_query_report": True, "doctype": "Sales Invoice" }, - { - "type": "report", - "name": "Item-wise Sales Register", - "is_query_report": True, - "doctype": "Sales Invoice" - }, - { - "type": "report", - "name": "Item-wise Purchase Register", - "is_query_report": True, - "doctype": "Purchase Invoice" - }, { "type": "report", "name": "Accounts Receivable Summary", From f86123ad5a5a0da9be7e74f7b6e1360f3f9733f9 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Tue, 15 Jan 2019 15:18:43 +0500 Subject: [PATCH 4/6] feat(Party Ledger Summary): Include columns for discount and other adjustments --- .../customer_ledger_summary.py | 91 +++++++++++++------ erpnext/setup/doctype/company/company.js | 2 + erpnext/setup/doctype/company/company.json | 68 +++++++++++++- 3 files changed, 131 insertions(+), 30 deletions(-) diff --git a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py index 2b228fd6b6..c8044c532a 100644 --- a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py +++ b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py @@ -6,6 +6,7 @@ import frappe import erpnext from frappe import _, scrub from frappe.utils import getdate, nowdate, flt, cint +from six import iteritems class PartyLedgerSummaryReport(object): def __init__(self, filters=None): @@ -20,6 +21,11 @@ class PartyLedgerSummaryReport(object): self.filters.party_type = args.get("party_type") self.party_naming_by = frappe.db.get_value(args.get("naming_by")[0], None, args.get("naming_by")[1]) + discount_account_field = "discount_allowed_account" if self.filters.party_type == "Customer" \ + else "discount_received_account" + self.round_off_account, self.write_off_account, self.discount_account = frappe.get_cached_value('Company', + self.filters.company, ["round_off_account", "write_off_account", discount_account_field]) + columns = self.get_columns() data = self.get_data() return columns, data @@ -42,6 +48,8 @@ class PartyLedgerSummaryReport(object): }) credit_or_debit_note = "Credit Note" if self.filters.party_type == "Customer" else "Debit Note" + discount_allowed_or_received = "Discount Allowed" if self.filters.party_type == "Customer" else "Discount Received" + columns += [ { "label": _("Opening Balance"), @@ -71,6 +79,13 @@ class PartyLedgerSummaryReport(object): "options": "currency", "width": 120 }, + { + "label": _(discount_allowed_or_received), + "fieldname": "discount_amount", + "fieldtype": "Currency", + "options": "currency", + "width": 120 + }, { "label": _("Write Off Amount"), "fieldname": "write_off_amount", @@ -78,6 +93,13 @@ class PartyLedgerSummaryReport(object): "options": "currency", "width": 120 }, + { + "label": _("Other Adjustments"), + "fieldname": "adjustment_amount", + "fieldtype": "Currency", + "options": "currency", + "width": 120 + }, { "label": _("Closing Balance"), "fieldname": "closing_balance", @@ -106,18 +128,17 @@ class PartyLedgerSummaryReport(object): self.get_gl_entries() self.get_return_invoices() - self.get_party_write_off_amounts() + self.get_party_adjustment_amounts() self.party_data = frappe._dict({}) for gle in self.gl_entries: self.party_data.setdefault(gle.party, frappe._dict({ "party": gle.party, - "party_name": "", # TODO add party_name + "party_name": "", # TODO add party_name "opening_balance": 0, "invoiced_amount": 0, - "paid_amount": -self.party_write_off_amounts.get(gle.party, 0), + "paid_amount": 0, "return_amount": 0, - "write_off_amount": self.party_write_off_amounts.get(gle.party, 0), "closing_balance": 0, "currency": company_currency })) @@ -135,8 +156,18 @@ class PartyLedgerSummaryReport(object): else: self.party_data[gle.party].paid_amount -= amount - return [d for d in self.party_data.values() if d.opening_balance or d.invoiced_amount or d.paid_amount - or d.return_amount or d.write_off_amount or d.closing_amount] + out = [] + for party, row in iteritems(self.party_data): + if row.opening_balance or row.invoiced_amount or row.paid_amount or row.return_amount or row.closing_amount: + total_party_adjustment = sum([amount for account, amount in iteritems(self.party_adjustment_details.get(party, {}))]) + row.paid_amount -= total_party_adjustment + row.discount_amount = self.party_adjustment_details.get(party, {}).get(self.discount_account, 0) + row.write_off_amount = self.party_adjustment_details.get(party, {}).get(self.write_off_account, 0) + row.adjustment_amount = total_party_adjustment - row.discount_amount - row.write_off_amount + + out.append(row) + + return out def get_gl_entries(self): conditions = self.prepare_conditions() @@ -215,12 +246,11 @@ class PartyLedgerSummaryReport(object): self.return_invoices = [d.name for d in frappe.get_all(doctype, filters={"is_return": 1, "docstatus": 1, "posting_date": ["between", [self.filters.from_date, self.filters.to_date]]})] - def get_party_write_off_amounts(self): + def get_party_adjustment_amounts(self): conditions = self.prepare_conditions() - income_or_expense = "Expense Account" if self.filters.party_type == "Customer" else "Income Account" + income_or_expense = "Expense" if self.filters.party_type == "Customer" else "Income" invoice_dr_or_cr = "debit" if self.filters.party_type == "Customer" else "credit" reverse_dr_or_cr = "credit" if self.filters.party_type == "Customer" else "debit" - round_off_account = frappe.get_cached_value('Company', self.filters.company, "round_off_account") gl_entries = frappe.db.sql(""" select @@ -231,7 +261,7 @@ class PartyLedgerSummaryReport(object): docstatus < 2 and (voucher_type, voucher_no) in ( select voucher_type, voucher_no from `tabGL Entry` gle, `tabAccount` acc - where acc.name = gle.account and acc.account_type = '{income_or_expense}' + where acc.name = gle.account and acc.root_type = '{income_or_expense}' and gle.posting_date between %(from_date)s and %(to_date)s and gle.docstatus < 2 ) and (voucher_type, voucher_no) in ( select voucher_type, voucher_no from `tabGL Entry` gle @@ -241,39 +271,42 @@ class PartyLedgerSummaryReport(object): order by posting_date """.format(conditions=conditions, income_or_expense=income_or_expense), self.filters, as_dict=True) - self.party_write_off_amounts = {} - write_off_voucher_entries = {} + self.party_adjustment_details = {} + adjustment_voucher_entries = {} for gle in gl_entries: - write_off_voucher_entries.setdefault((gle.voucher_type, gle.voucher_no), []) - write_off_voucher_entries[(gle.voucher_type, gle.voucher_no)].append(gle) + adjustment_voucher_entries.setdefault((gle.voucher_type, gle.voucher_no), []) + adjustment_voucher_entries[(gle.voucher_type, gle.voucher_no)].append(gle) - for voucher, voucher_gl_entries in write_off_voucher_entries.iteritems(): + for voucher, voucher_gl_entries in iteritems(adjustment_voucher_entries): parties = {} - write_offs = {} + accounts = {} has_irrelevant_entry = False for gle in voucher_gl_entries: - if gle.account == round_off_account: + if gle.account == self.round_off_account: continue elif gle.party: parties.setdefault(gle.party, 0) parties[gle.party] += gle.get(reverse_dr_or_cr) - gle.get(invoice_dr_or_cr) - elif frappe.get_cached_value("Account", gle.account, "account_type") == income_or_expense: - write_offs.setdefault(gle.account, 0) - write_offs[gle.account] += gle.get(invoice_dr_or_cr) - gle.get(reverse_dr_or_cr) + elif frappe.get_cached_value("Account", gle.account, "root_type") == income_or_expense: + accounts.setdefault(gle.account, 0) + accounts[gle.account] += gle.get(invoice_dr_or_cr) - gle.get(reverse_dr_or_cr) else: has_irrelevant_entry = True - if parties and write_offs: + if parties and accounts: if len(parties) == 1: - for account, amount in write_offs.iteritems(): - party = parties.keys()[0] - self.party_write_off_amounts.setdefault(party, 0) - self.party_write_off_amounts[party] += amount - elif len(write_offs) == 1 and not has_irrelevant_entry: - for party, amount in parties.iteritems(): - self.party_write_off_amounts.setdefault(party, 0) - self.party_write_off_amounts[party] += amount + party = parties.keys()[0] + for account, amount in iteritems(accounts): + self.party_adjustment_details.setdefault(party, {}) + self.party_adjustment_details[party].setdefault(account, 0) + self.party_adjustment_details[party][account] += amount + elif len(accounts) == 1 and not has_irrelevant_entry: + account = accounts.keys()[0] + for party, amount in iteritems(parties): + self.party_adjustment_details.setdefault(party, {}) + self.party_adjustment_details[party].setdefault(account, 0) + self.party_adjustment_details[party][account] += amount def execute(filters=None): args = { diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 16676ac78a..70e047a4e8 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -206,6 +206,8 @@ erpnext.company.setup_queries = function(frm) { ["default_payroll_payable_account", {"root_type": "Liability"}], ["round_off_account", {"root_type": "Expense"}], ["write_off_account", {"root_type": "Expense"}], + ["discount_allowed_account", {"root_type": "Expense"}], + ["discount_received_account", {"root_type": "Income"}], ["exchange_gain_loss_account", {"root_type": "Expense"}], ["unrealized_exchange_gain_loss_account", {"root_type": "Expense"}], ["accumulated_depreciation_account", diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 01f8956a82..77c371e0cd 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -1250,6 +1250,72 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "discount_allowed_account", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Discount Allowed Account", + "length": 0, + "no_copy": 0, + "options": "Account", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "discount_received_account", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Discount Received Account", + "length": 0, + "no_copy": 0, + "options": "Account", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -2903,7 +2969,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2018-10-24 12:57:46.776452", + "modified": "2019-01-15 13:29:54.510379", "modified_by": "Administrator", "module": "Setup", "name": "Company", From 55566b7f8e838fbeb349d754e6235737581af032 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Sat, 19 Jan 2019 15:12:08 +0500 Subject: [PATCH 5/6] fix(Party Ledger Summary): Codacy fix --- .../customer_ledger_summary.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py index c8044c532a..47116cf336 100644 --- a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py +++ b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py @@ -4,9 +4,9 @@ from __future__ import unicode_literals import frappe import erpnext -from frappe import _, scrub -from frappe.utils import getdate, nowdate, flt, cint -from six import iteritems +from frappe import _ +from frappe.utils import getdate, nowdate +from six import iteritems, itervalues class PartyLedgerSummaryReport(object): def __init__(self, filters=None): @@ -159,7 +159,7 @@ class PartyLedgerSummaryReport(object): out = [] for party, row in iteritems(self.party_data): if row.opening_balance or row.invoiced_amount or row.paid_amount or row.return_amount or row.closing_amount: - total_party_adjustment = sum([amount for account, amount in iteritems(self.party_adjustment_details.get(party, {}))]) + total_party_adjustment = sum([amount for amount in itervalues(self.party_adjustment_details.get(party, {}))]) row.paid_amount -= total_party_adjustment row.discount_amount = self.party_adjustment_details.get(party, {}).get(self.discount_account, 0) row.write_off_amount = self.party_adjustment_details.get(party, {}).get(self.write_off_account, 0) @@ -230,9 +230,6 @@ class PartyLedgerSummaryReport(object): and ((steam.parent = voucher_no and steam.parenttype = voucher_type) or (steam.parent = against_voucher and steam.parenttype = against_voucher_type) or (steam.parent = party and steam.parenttype = 'Customer')))""".format(lft, rgt)) - #conditions.append("""party in (select parent from `tabSales Team` - # where parenttype = 'Customer' and exists(select name from `tabSales Person` - # where lft >= {0} and rgt <= {1} and name=`tabSales Team`.sales_person))""".format(lft, rgt)) if self.filters.party_type == "Supplier": if self.filters.get("supplier_group"): @@ -277,7 +274,7 @@ class PartyLedgerSummaryReport(object): adjustment_voucher_entries.setdefault((gle.voucher_type, gle.voucher_no), []) adjustment_voucher_entries[(gle.voucher_type, gle.voucher_no)].append(gle) - for voucher, voucher_gl_entries in iteritems(adjustment_voucher_entries): + for voucher_gl_entries in itervalues(adjustment_voucher_entries): parties = {} accounts = {} has_irrelevant_entry = False @@ -313,4 +310,4 @@ def execute(filters=None): "party_type": "Customer", "naming_by": ["Selling Settings", "cust_master_name"], } - return PartyLedgerSummaryReport(filters).run(args) \ No newline at end of file + return PartyLedgerSummaryReport(filters).run(args) From 31d58eac01d2ef5f96185be15295e0f08172ccb7 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Sat, 19 Jan 2019 15:28:40 +0500 Subject: [PATCH 6/6] fix(Party Ledger Summary): Added Supplier/Customer Name column --- .../customer_ledger_summary.py | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py index 47116cf336..e33bd61411 100644 --- a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py +++ b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py @@ -134,7 +134,7 @@ class PartyLedgerSummaryReport(object): for gle in self.gl_entries: self.party_data.setdefault(gle.party, frappe._dict({ "party": gle.party, - "party_name": "", # TODO add party_name + "party_name": gle.party_name, "opening_balance": 0, "invoiced_amount": 0, "paid_amount": 0, @@ -171,16 +171,25 @@ class PartyLedgerSummaryReport(object): def get_gl_entries(self): conditions = self.prepare_conditions() + join = join_field = "" + if self.filters.party_type == "Customer": + join_field = ", p.customer_name as party_name" + join = "left join `tabCustomer` p on gle.party = p.name" + elif self.filters.party_type == "Supplier": + join_field = ", p.supplier_name as party_name" + join = "left join `tabSupplier` p on gle.party = p.name" self.gl_entries = frappe.db.sql(""" select - posting_date, party, voucher_type, voucher_no, against_voucher_type, against_voucher, debit, credit - from - `tabGL Entry` + gle.posting_date, gle.party, gle.voucher_type, gle.voucher_no, gle.against_voucher_type, + gle.against_voucher, gle.debit, gle.credit {join_field} + from `tabGL Entry` gle + {join} where - docstatus < 2 and party_type=%(party_type)s and ifnull(party, '') != '' and posting_date <= %(to_date)s - {0} - order by posting_date""".format(conditions), self.filters, as_dict=True) + gle.docstatus < 2 and gle.party_type=%(party_type)s and ifnull(gle.party, '') != '' + and gle.posting_date <= %(to_date)s {conditions} + order by gle.posting_date + """.format(join=join, join_field=join_field, conditions=conditions), self.filters, as_dict=True) def prepare_conditions(self): conditions = [""] @@ -265,7 +274,6 @@ class PartyLedgerSummaryReport(object): where gle.party_type=%(party_type)s and ifnull(party, '') != '' and gle.posting_date between %(from_date)s and %(to_date)s and gle.docstatus < 2 {conditions} ) - order by posting_date """.format(conditions=conditions, income_or_expense=income_or_expense), self.filters, as_dict=True) self.party_adjustment_details = {}