From 1ee19e4167f72dd73ed1bc35dd58b60b4c51f012 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Sat, 15 Dec 2018 15:36:09 +0530 Subject: [PATCH 01/14] Analytics report graph fix --- .../purchase_analytics/purchase_analytics.js | 17 ++++++++++++++--- .../report/sales_analytics/sales_analytics.js | 19 +++++++++++++++---- .../report/sales_analytics/sales_analytics.py | 6 +++++- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/erpnext/buying/report/purchase_analytics/purchase_analytics.js b/erpnext/buying/report/purchase_analytics/purchase_analytics.js index b55046e065..e17973c337 100644 --- a/erpnext/buying/report/purchase_analytics/purchase_analytics.js +++ b/erpnext/buying/report/purchase_analytics/purchase_analytics.js @@ -77,9 +77,20 @@ frappe.query_reports["Purchase Analytics"] = { events: { onCheckRow: function(data) { row_name = data[2].content; - row_values = data.slice(5).map(function (column) { - return column.content; - }) + length = data.length; + + var tree_type = frappe.query_report.filters[0].value; + + if(tree_type == "Supplier" || tree_type == "Item") { + row_values = data.slice(4,length-1).map(function (column) { + return column.content; + }) + } + else { + row_values = data.slice(3,length-1).map(function (column) { + return column.content; + }) + } entry = { 'name':row_name, diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.js b/erpnext/selling/report/sales_analytics/sales_analytics.js index 0df425d1cd..fbe045bf35 100644 --- a/erpnext/selling/report/sales_analytics/sales_analytics.js +++ b/erpnext/selling/report/sales_analytics/sales_analytics.js @@ -76,10 +76,21 @@ frappe.query_reports["Sales Analytics"] = { events: { onCheckRow: function(data) { row_name = data[2].content; - length = data.length - row_values = data.slice(4,length-1).map(function (column) { - return column.content; - }) + length = data.length; + + var tree_type = frappe.query_report.filters[0].value; + + if(tree_type == "Customer" || tree_type == "Item") { + row_values = data.slice(4,length-1).map(function (column) { + return column.content; + }) + } + else { + row_values = data.slice(3,length-1).map(function (column) { + return column.content; + }) + } + entry = { 'name':row_name, 'values':row_values diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.py b/erpnext/selling/report/sales_analytics/sales_analytics.py index 9cc6c404a6..c078a08249 100644 --- a/erpnext/selling/report/sales_analytics/sales_analytics.py +++ b/erpnext/selling/report/sales_analytics/sales_analytics.py @@ -276,7 +276,11 @@ class Analytics(object): def get_chart_data(self): length = len(self.columns) - labels = [d.get("label") for d in self.columns[2:length-1]] + + if self.filters.tree_type in ["Customer", "Supplier", "Item"]: + labels = [d.get("label") for d in self.columns[2:length-1]] + else: + labels = [d.get("label") for d in self.columns[1:length-1]] self.chart = { "data": { 'labels': labels, From a8ab9b5c3d9a289cb4d96abdb93ded1e8e11f509 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Mon, 12 Nov 2018 11:17:39 +0530 Subject: [PATCH 02/14] Accounts Receivable report based on payment terms --- .../accounts_receivable.js | 5 + .../accounts_receivable.py | 169 +++++++++++------- 2 files changed, 113 insertions(+), 61 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index b1bdce95c8..bbfee1112f 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -107,6 +107,11 @@ frappe.query_reports["Accounts Receivable"] = { "label": __("Show PDC in Print"), "fieldtype": "Check", }, + { + "fieldname":"based_on_payment_terms", + "label": __("Based On Payment Terms"), + "fieldtype": "Check", + }, { "fieldname":"tax_id", "label": __("Tax Id"), diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 8e05a087af..4455be4ad2 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -152,106 +152,139 @@ class ReceivablePayableReport(object): def get_data(self, party_naming_by, args): from erpnext.accounts.utils import get_currency_precision currency_precision = get_currency_precision() or 2 - dr_or_cr = "debit" if args.get("party_type") == "Customer" else "credit" + self.dr_or_cr = "debit" if args.get("party_type") == "Customer" else "credit" future_vouchers = self.get_entries_after(self.filters.report_date, args.get("party_type")) if not self.filters.get("company"): self.filters["company"] = frappe.db.get_single_value('Global Defaults', 'default_company') - company_currency = frappe.get_cached_value('Company', self.filters.get("company"), "default_currency") + self.company_currency = frappe.get_cached_value('Company', self.filters.get("company"), "default_currency") return_entries = self.get_return_entries(args.get("party_type")) data = [] - pdc_details = get_pdc_details(args.get("party_type"), self.filters.report_date) + self.pdc_details = get_pdc_details(args.get("party_type"), self.filters.report_date) gl_entries_data = self.get_entries_till(self.filters.report_date, args.get("party_type")) if gl_entries_data: voucher_nos = [d.voucher_no for d in gl_entries_data] or [] dn_details = get_dn_details(args.get("party_type"), voucher_nos) - voucher_details = get_voucher_details(args.get("party_type"), voucher_nos, dn_details) + self.voucher_details = get_voucher_details(args.get("party_type"), voucher_nos, dn_details) + + if self.filters.based_on_payment_terms: + self.payment_term_map = self.get_payment_term_detail(voucher_nos) for gle in gl_entries_data: - if self.is_receivable_or_payable(gle, dr_or_cr, future_vouchers): - outstanding_amount, credit_note_amount = self.get_outstanding_amount(gle, - self.filters.report_date, dr_or_cr, return_entries, currency_precision) - if abs(outstanding_amount) > 0.1/10**currency_precision: - row = [gle.posting_date, gle.party] + if self.is_receivable_or_payable(gle, self.dr_or_cr, future_vouchers): + if self.filters.based_on_payment_terms and self.payment_term_map.get(gle.voucher_no): + outstanding_amount, credit_note_amount, payment_amount = self.get_outstanding_amount( + gle,self.filters.report_date, self.dr_or_cr, return_entries, currency_precision) + if abs(outstanding_amount) > 0.1/10**currency_precision: + for d in self.payment_term_map.get(gle.voucher_no): + if payment_amount >= d[1]: + payment_amount -= d[1] + else: + outstanding_amount = d[1] - payment_amount + row = self.prepare_row(party_naming_by, args, gle, outstanding_amount, + credit_note_amount, d[0], payment_amount ) + payment_amount = 0 + data.append(row) + else: + outstanding_amount, credit_note_amount, payment_amount = self.get_outstanding_amount( + gle,self.filters.report_date, self.dr_or_cr, return_entries, currency_precision) + if abs(outstanding_amount) > 0.1/10**currency_precision: + row = self.prepare_row(party_naming_by, args, gle, outstanding_amount, credit_note_amount) + data.append(row) + return data - # customer / supplier name - if party_naming_by == "Naming Series": - row += [self.get_party_name(gle.party_type, gle.party)] + def prepare_row(self, party_naming_by, args, gle, outstanding_amount, credit_note_amount, + due_date=None, paid_amt=None): + row = [gle.posting_date, gle.party] - # get due date - due_date = voucher_details.get(gle.voucher_no, {}).get("due_date", "") - bill_date = voucher_details.get(gle.voucher_no, {}).get("bill_date", "") + # customer / supplier name + if party_naming_by == "Naming Series": + row += [self.get_party_name(gle.party_type, gle.party)] - row += [gle.voucher_type, gle.voucher_no, due_date] + # get due date + if not due_date: + due_date = self.voucher_details.get(gle.voucher_no, {}).get("due_date", "") + bill_date = self.voucher_details.get(gle.voucher_no, {}).get("bill_date", "") - # get supplier bill details - if args.get("party_type") == "Supplier": - row += [ - voucher_details.get(gle.voucher_no, {}).get("bill_no", ""), - voucher_details.get(gle.voucher_no, {}).get("bill_date", "") - ] + row += [gle.voucher_type, gle.voucher_no, due_date] - # invoiced and paid amounts - invoiced_amount = gle.get(dr_or_cr) if (gle.get(dr_or_cr) > 0) else 0 - paid_amt = invoiced_amount - outstanding_amount - credit_note_amount - row += [invoiced_amount, paid_amt, credit_note_amount, outstanding_amount] + # get supplier bill details + if args.get("party_type") == "Supplier": + row += [ + self.voucher_details.get(gle.voucher_no, {}).get("bill_no", ""), + self.voucher_details.get(gle.voucher_no, {}).get("bill_date", "") + ] - # ageing data - if self.filters.ageing_based_on == "Due Date": - entry_date = due_date - elif self.filters.ageing_based_on == "Supplier Invoice Date": - entry_date = bill_date - else: - entry_date = gle.posting_date + # invoiced and paid amounts + invoiced_amount = gle.get(self.dr_or_cr) if (gle.get(self.dr_or_cr) > 0) else 0 - row += get_ageing_data(cint(self.filters.range1), cint(self.filters.range2), - cint(self.filters.range3), self.age_as_on, entry_date, outstanding_amount) + if not self.filters.based_on_payment_terms: + paid_amt = invoiced_amount - outstanding_amount - credit_note_amount + row += [invoiced_amount, paid_amt, credit_note_amount, outstanding_amount] + + # ageing data + if self.filters.ageing_based_on == "Due Date": + entry_date = due_date + elif self.filters.ageing_based_on == "Supplier Invoice Date": + entry_date = bill_date + else: + entry_date = gle.posting_date + + row += get_ageing_data(cint(self.filters.range1), cint(self.filters.range2), + cint(self.filters.range3), self.age_as_on, entry_date, outstanding_amount) - # issue 6371-Ageing buckets should not have amounts if due date is not reached - if self.filters.ageing_based_on == "Due Date" \ - and getdate(due_date) > getdate(self.filters.report_date): - row[-1]=row[-2]=row[-3]=row[-4]=0 + # issue 6371-Ageing buckets should not have amounts if due date is not reached + if self.filters.ageing_based_on == "Due Date" \ + and getdate(due_date) > getdate(self.filters.report_date): + row[-1]=row[-2]=row[-3]=row[-4]=0 - if self.filters.ageing_based_on == "Supplier Invoice Date" \ - and getdate(bill_date) > getdate(self.filters.report_date): + if self.filters.ageing_based_on == "Supplier Invoice Date" \ + and getdate(bill_date) > getdate(self.filters.report_date): - row[-1]=row[-2]=row[-3]=row[-4]=0 + row[-1]=row[-2]=row[-3]=row[-4]=0 - if self.filters.get(scrub(args.get("party_type"))): - row.append(gle.account_currency) - else: - row.append(company_currency) + if self.filters.get(scrub(args.get("party_type"))): + row.append(gle.account_currency) + else: + row.append(self.company_currency) - pdc = pdc_details.get((gle.voucher_no, gle.party), {}) + pdc = self.pdc_details.get((gle.voucher_no, gle.party), {}) - remaining_balance = outstanding_amount - flt(pdc.get("pdc_amount")) - row += [pdc.get("pdc_date"), pdc.get("pdc_ref"), - flt(pdc.get("pdc_amount")), remaining_balance] - - if args.get('party_type') == 'Customer': - # customer LPO - row += [voucher_details.get(gle.voucher_no, {}).get("po_no")] - - # Delivery Note - row += [voucher_details.get(gle.voucher_no, {}).get("delivery_note")] + remaining_balance = outstanding_amount - flt(pdc.get("pdc_amount")) + row += [pdc.get("pdc_date"), pdc.get("pdc_ref"), + flt(pdc.get("pdc_amount")), remaining_balance] +<<<<<<< HEAD # customer territory / supplier group if args.get("party_type") == "Customer": row += [self.get_territory(gle.party), self.get_customer_group(gle.party), voucher_details.get(gle.voucher_no, {}).get("sales_person")] if args.get("party_type") == "Supplier": row += [self.get_supplier_group(gle.party)] +======= + if args.get('party_type') == 'Customer': + # customer LPO + row += [self.voucher_details.get(gle.voucher_no, {}).get("po_no")] +>>>>>>> Accounts Receivable report based on payment terms - row.append(gle.remarks) - data.append(row) + # Delivery Note + row += [self.voucher_details.get(gle.voucher_no, {}).get("delivery_note")] - return data + # customer territory / supplier group + if args.get("party_type") == "Customer": + row += [self.get_territory(gle.party), self.get_customer_group(gle.party)] + if args.get("party_type") == "Supplier": + row += [self.get_supplier_group(gle.party)] + + row.append(gle.remarks) + + return row def get_entries_after(self, report_date, party_type): # returns a distinct list @@ -298,7 +331,7 @@ class ReceivablePayableReport(object): credit_note_amount = flt(credit_note_amount, currency_precision) - return outstanding_amount, credit_note_amount + return outstanding_amount, credit_note_amount, payment_amount def get_party_name(self, party_type, party_name): return self.get_party_map(party_type).get(party_name, {}).get("customer_name" if party_type == "Customer" else "supplier_name") or "" @@ -432,6 +465,20 @@ class ReceivablePayableReport(object): .get(against_voucher_type, {})\ .get(against_voucher, []) + def get_payment_term_detail(self, voucher_nos): + payment_term_map = frappe._dict() + for d in frappe.db.sql(""" select si.name, si.payment_terms_template, ps.due_date, ps.payment_amount + from `tabSales Invoice` si, `tabPayment Schedule` ps + where si.name = ps.parent and + si.docstatus = 1 and si.company = '%s' and + si.name in (%s) order by ps.due_date""" + % (self.filters.company, ','.join(['%s'] *len(voucher_nos))), (tuple(voucher_nos)), as_dict = 1): + if d.payment_terms_template: + payment_term_map.setdefault(d.name,[]) + payment_term_map[d.name].append((d.due_date,d.payment_amount)) + + return payment_term_map + def get_chart_data(self, columns, data): ageing_columns = columns[self.ageing_col_idx_start : self.ageing_col_idx_start+4] From 24f8d3ed0c8bb48839e0581373fe6e3169e489a5 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Mon, 12 Nov 2018 14:45:33 +0530 Subject: [PATCH 03/14] Add columns based on payment terms --- .../accounts_receivable.py | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 4455be4ad2..20b19eb40c 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -57,6 +57,21 @@ class ReceivablePayableReport(object): credit_or_debit_note = "Credit Note" if args.get("party_type") == "Customer" else "Debit Note" + if self.filters.based_on_payment_terms: + columns.append({ + "label": "Payment Term", + "fieldname": "payment_term", + "fieldtype": "Data", + "width": 120 + }) + columns.append({ + "label": "Payment Term Amount", + "fieldname": "payment_term_amount", + "fieldtype": "Currency", + "options": "currency", + "width": 120 + }) + for label in ("Invoiced Amount", "Paid Amount", credit_or_debit_note, "Outstanding Amount"): columns.append({ "label": label, @@ -187,7 +202,7 @@ class ReceivablePayableReport(object): else: outstanding_amount = d[1] - payment_amount row = self.prepare_row(party_naming_by, args, gle, outstanding_amount, - credit_note_amount, d[0], payment_amount ) + credit_note_amount, d[0], payment_amount , d[1], d[2]) payment_amount = 0 data.append(row) else: @@ -199,7 +214,7 @@ class ReceivablePayableReport(object): return data def prepare_row(self, party_naming_by, args, gle, outstanding_amount, credit_note_amount, - due_date=None, paid_amt=None): + due_date=None, paid_amt=None, payment_term_amount=None, payment_term=None): row = [gle.posting_date, gle.party] # customer / supplier name @@ -223,7 +238,10 @@ class ReceivablePayableReport(object): # invoiced and paid amounts invoiced_amount = gle.get(self.dr_or_cr) if (gle.get(self.dr_or_cr) > 0) else 0 - if not self.filters.based_on_payment_terms: + if self.filters.based_on_payment_terms: + row+=[payment_term, payment_term_amount] + + if paid_amt == None: paid_amt = invoiced_amount - outstanding_amount - credit_note_amount row += [invoiced_amount, paid_amt, credit_note_amount, outstanding_amount] @@ -467,15 +485,15 @@ class ReceivablePayableReport(object): def get_payment_term_detail(self, voucher_nos): payment_term_map = frappe._dict() - for d in frappe.db.sql(""" select si.name, si.payment_terms_template, ps.due_date, ps.payment_amount + for d in frappe.db.sql(""" select si.name, si.payment_terms_template, ps.due_date, ps.payment_amount, ps.description from `tabSales Invoice` si, `tabPayment Schedule` ps where si.name = ps.parent and si.docstatus = 1 and si.company = '%s' and si.name in (%s) order by ps.due_date""" % (self.filters.company, ','.join(['%s'] *len(voucher_nos))), (tuple(voucher_nos)), as_dict = 1): if d.payment_terms_template: - payment_term_map.setdefault(d.name,[]) - payment_term_map[d.name].append((d.due_date,d.payment_amount)) + payment_term_map.setdefault(d.name, []) + payment_term_map[d.name].append((d.due_date, d.payment_amount, d.description)) return payment_term_map From b6c083706a3ebfa8e4684cb224226917f571441e Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Tue, 13 Nov 2018 12:11:04 +0530 Subject: [PATCH 04/14] Allocation of credit note amount and pdc in payment terms --- .../accounts_receivable.py | 51 ++++++++++++------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 20b19eb40c..5db186b8f5 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -174,7 +174,7 @@ class ReceivablePayableReport(object): if not self.filters.get("company"): self.filters["company"] = frappe.db.get_single_value('Global Defaults', 'default_company') - self.company_currency = frappe.get_cached_value('Company', self.filters.get("company"), "default_currency") + self.company_currency = frappe.get_cached_value('Company', self.filters.get("company"), "default_currency") return_entries = self.get_return_entries(args.get("party_type")) @@ -192,29 +192,37 @@ class ReceivablePayableReport(object): for gle in gl_entries_data: if self.is_receivable_or_payable(gle, self.dr_or_cr, future_vouchers): - if self.filters.based_on_payment_terms and self.payment_term_map.get(gle.voucher_no): - outstanding_amount, credit_note_amount, payment_amount = self.get_outstanding_amount( - gle,self.filters.report_date, self.dr_or_cr, return_entries, currency_precision) - if abs(outstanding_amount) > 0.1/10**currency_precision: + outstanding_amount, credit_note_amount, payment_amount = self.get_outstanding_amount( + gle,self.filters.report_date, self.dr_or_cr, return_entries, currency_precision) + if abs(outstanding_amount) > 0.1/10**currency_precision: + if self.filters.based_on_payment_terms and self.payment_term_map.get(gle.voucher_no): + pdc_amount = flt(self.pdc_details.get((gle.voucher_no, gle.party), {}).get("pdc_amount")) for d in self.payment_term_map.get(gle.voucher_no): - if payment_amount >= d[1]: - payment_amount -= d[1] + if payment_amount + credit_note_amount >= d[1]: + temp = payment_amount + payment_amount = payment_amount - d[1] + credit_note_amount + credit_note_amount = credit_note_amount - d[1] + temp - payment_amount else: - outstanding_amount = d[1] - payment_amount - row = self.prepare_row(party_naming_by, args, gle, outstanding_amount, - credit_note_amount, d[0], payment_amount , d[1], d[2]) + outstanding_amount = d[1] - payment_amount - credit_note_amount + if pdc_amount > outstanding_amount: + pdc = outstanding_amount + pdc_amount -= outstanding_amount + else: + pdc = pdc_amount + pdc_amount = 0 + + row = self.prepare_row(party_naming_by, args, gle, outstanding_amount, + credit_note_amount, d[0], payment_amount , d[1], d[2], pdc) payment_amount = 0 + credit_note_amount = 0 data.append(row) - else: - outstanding_amount, credit_note_amount, payment_amount = self.get_outstanding_amount( - gle,self.filters.report_date, self.dr_or_cr, return_entries, currency_precision) - if abs(outstanding_amount) > 0.1/10**currency_precision: + else: row = self.prepare_row(party_naming_by, args, gle, outstanding_amount, credit_note_amount) data.append(row) return data def prepare_row(self, party_naming_by, args, gle, outstanding_amount, credit_note_amount, - due_date=None, paid_amt=None, payment_term_amount=None, payment_term=None): + due_date=None, paid_amt=None, payment_term_amount=None, payment_term=None, pdc_amount=None): row = [gle.posting_date, gle.party] # customer / supplier name @@ -274,9 +282,14 @@ class ReceivablePayableReport(object): pdc = self.pdc_details.get((gle.voucher_no, gle.party), {}) - remaining_balance = outstanding_amount - flt(pdc.get("pdc_amount")) - row += [pdc.get("pdc_date"), pdc.get("pdc_ref"), - flt(pdc.get("pdc_amount")), remaining_balance] + if pdc_amount == None: + pdc_amount = flt(pdc.get("pdc_amount")) + + pdc_date = pdc.get("pdc_date") if pdc_amount else '' + pdc_ref = pdc.get("pdc_ref") if pdc_amount else '' + + remaining_balance = outstanding_amount - pdc_amount + row += [pdc_date, pdc_ref, pdc_amount, remaining_balance] <<<<<<< HEAD # customer territory / supplier group @@ -555,7 +568,7 @@ def get_pdc_details(party_type, report_date): on (pref.parent = pent.name) where - pent.docstatus < 2 and pent.posting_date > %s + pent.docstatus < 2 and pent.posting_date > %s and pent.party_type = %s group by pent.party, pref.reference_name""", (report_date, party_type), as_dict=1): pdc_details.setdefault((pdc.invoice_no, pdc.party), pdc) From f98adf2be514a4553cdbb6adabe805e4e57966e4 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Tue, 13 Nov 2018 15:07:05 +0530 Subject: [PATCH 05/14] Changed columns and added currency conditions --- .../accounts_receivable.py | 53 +++++++++++-------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 5db186b8f5..c8f9b6f874 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -65,8 +65,8 @@ class ReceivablePayableReport(object): "width": 120 }) columns.append({ - "label": "Payment Term Amount", - "fieldname": "payment_term_amount", + "label": "Invoice Grand Total", + "fieldname": "invoice_grand_total", "fieldtype": "Currency", "options": "currency", "width": 120 @@ -128,14 +128,14 @@ class ReceivablePayableReport(object): "fieldname": "pdc/lc_amount", "label": _("PDC/LC Amount"), "fieldtype": "Currency", - "options": "Currency", + "options": "currency", "width": 130 }, { "fieldname": "remaining_balance", "label": _("Remaining Balance"), "fieldtype": "Currency", - "options": "Currency", + "options": "currency", "width": 130 }] @@ -166,7 +166,7 @@ class ReceivablePayableReport(object): def get_data(self, party_naming_by, args): from erpnext.accounts.utils import get_currency_precision - currency_precision = get_currency_precision() or 2 + self.currency_precision = get_currency_precision() or 2 self.dr_or_cr = "debit" if args.get("party_type") == "Customer" else "credit" future_vouchers = self.get_entries_after(self.filters.report_date, args.get("party_type")) @@ -193,8 +193,8 @@ class ReceivablePayableReport(object): for gle in gl_entries_data: if self.is_receivable_or_payable(gle, self.dr_or_cr, future_vouchers): outstanding_amount, credit_note_amount, payment_amount = self.get_outstanding_amount( - gle,self.filters.report_date, self.dr_or_cr, return_entries, currency_precision) - if abs(outstanding_amount) > 0.1/10**currency_precision: + gle,self.filters.report_date, self.dr_or_cr, return_entries) + if abs(outstanding_amount) > 0.1/10**self.currency_precision: if self.filters.based_on_payment_terms and self.payment_term_map.get(gle.voucher_no): pdc_amount = flt(self.pdc_details.get((gle.voucher_no, gle.party), {}).get("pdc_amount")) for d in self.payment_term_map.get(gle.voucher_no): @@ -210,7 +210,8 @@ class ReceivablePayableReport(object): else: pdc = pdc_amount pdc_amount = 0 - + if self.filters.get(gle.party_type): + d[1] = d[1] * d[3] row = self.prepare_row(party_naming_by, args, gle, outstanding_amount, credit_note_amount, d[0], payment_amount , d[1], d[2], pdc) payment_amount = 0 @@ -247,7 +248,9 @@ class ReceivablePayableReport(object): invoiced_amount = gle.get(self.dr_or_cr) if (gle.get(self.dr_or_cr) > 0) else 0 if self.filters.based_on_payment_terms: - row+=[payment_term, payment_term_amount] + row+=[payment_term, invoiced_amount] + if payment_term_amount: + invoiced_amount = payment_term_amount if paid_amt == None: paid_amt = invoiced_amount - outstanding_amount - credit_note_amount @@ -344,23 +347,23 @@ class ReceivablePayableReport(object): doctype = "Sales Invoice" if party_type=="Customer" else "Purchase Invoice" return [d.name for d in frappe.get_all(doctype, filters={"is_return": 1, "docstatus": 1})] - def get_outstanding_amount(self, gle, report_date, dr_or_cr, return_entries, currency_precision): + def get_outstanding_amount(self, gle, report_date, dr_or_cr, return_entries): payment_amount, credit_note_amount = 0.0, 0.0 reverse_dr_or_cr = "credit" if dr_or_cr=="debit" else "debit" for e in self.get_gl_entries_for(gle.party, gle.party_type, gle.voucher_type, gle.voucher_no): if getdate(e.posting_date) <= report_date and e.name!=gle.name: - amount = flt(e.get(reverse_dr_or_cr), currency_precision) - flt(e.get(dr_or_cr), currency_precision) + amount = flt(e.get(reverse_dr_or_cr), self.currency_precision) - flt(e.get(dr_or_cr), self.currency_precision) if e.voucher_no not in return_entries: payment_amount += amount else: credit_note_amount += amount - outstanding_amount = (flt((flt(gle.get(dr_or_cr), currency_precision) - - flt(gle.get(reverse_dr_or_cr), currency_precision) - - payment_amount - credit_note_amount), currency_precision)) + outstanding_amount = (flt((flt(gle.get(dr_or_cr), self.currency_precision) + - flt(gle.get(reverse_dr_or_cr), self.currency_precision) + - payment_amount - credit_note_amount), self.currency_precision)) - credit_note_amount = flt(credit_note_amount, currency_precision) + credit_note_amount = flt(credit_note_amount, self.currency_precision) return outstanding_amount, credit_note_amount, payment_amount @@ -498,16 +501,22 @@ class ReceivablePayableReport(object): def get_payment_term_detail(self, voucher_nos): payment_term_map = frappe._dict() - for d in frappe.db.sql(""" select si.name, si.payment_terms_template, ps.due_date, ps.payment_amount, ps.description - from `tabSales Invoice` si, `tabPayment Schedule` ps - where si.name = ps.parent and - si.docstatus = 1 and si.company = '%s' and - si.name in (%s) order by ps.due_date""" + for d in frappe.db.sql(""" select si.name, si.payment_terms_template, + party_account_currency, currency, si.conversion_rate, + ps.due_date, ps.payment_amount, ps.description + from `tabSales Invoice` si, `tabPayment Schedule` ps + where si.name = ps.parent and + si.docstatus = 1 and si.company = '%s' and + si.name in (%s) order by ps.due_date""" % (self.filters.company, ','.join(['%s'] *len(voucher_nos))), (tuple(voucher_nos)), as_dict = 1): if d.payment_terms_template: - payment_term_map.setdefault(d.name, []) - payment_term_map[d.name].append((d.due_date, d.payment_amount, d.description)) + if self.filters.get("customer") and d.currency == d.party_account_currency: + payment_term_amount = d.payment_amount + else: + payment_term_amount = flt(flt(d.payment_amount) * flt(d.conversion_rate), self.currency_precision) + payment_term_map.setdefault(d.name, []) + payment_term_map[d.name].append((d.due_date, payment_term_amount, d.description)) return payment_term_map def get_chart_data(self, columns, data): From e7a91b9526ced2702a1b0d60cc5b81af914f0a56 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Thu, 6 Dec 2018 14:59:54 +0530 Subject: [PATCH 06/14] Credit note fix --- .../accounts_receivable.py | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index c8f9b6f874..b262311d4a 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -198,10 +198,15 @@ class ReceivablePayableReport(object): if self.filters.based_on_payment_terms and self.payment_term_map.get(gle.voucher_no): pdc_amount = flt(self.pdc_details.get((gle.voucher_no, gle.party), {}).get("pdc_amount")) for d in self.payment_term_map.get(gle.voucher_no): - if payment_amount + credit_note_amount >= d[1]: - temp = payment_amount - payment_amount = payment_amount - d[1] + credit_note_amount - credit_note_amount = credit_note_amount - d[1] + temp - payment_amount + term_outstanding_amount = 0 + if payment_amount >= d[1]: + payment_amount = payment_amount - d[1] + if credit_note_amount: + term_outstanding_amount -= credit_note_amount + row = self.prepare_row(party_naming_by, args, gle, term_outstanding_amount, + credit_note_amount, d[0], payment_amount , d[1], d[2], 0) + credit_note_amount = 0 + data.append(row) else: outstanding_amount = d[1] - payment_amount - credit_note_amount if pdc_amount > outstanding_amount: @@ -222,7 +227,7 @@ class ReceivablePayableReport(object): data.append(row) return data - def prepare_row(self, party_naming_by, args, gle, outstanding_amount, credit_note_amount, + def prepare_row(self, party_naming_by, args, gle, outstanding_amount, credit_note_amount, due_date=None, paid_amt=None, payment_term_amount=None, payment_term=None, pdc_amount=None): row = [gle.posting_date, gle.party] @@ -258,8 +263,8 @@ class ReceivablePayableReport(object): # ageing data if self.filters.ageing_based_on == "Due Date": - entry_date = due_date - elif self.filters.ageing_based_on == "Supplier Invoice Date": + entry_date = due_date + elif self.filters.ageing_based_on == "Supplier Invoice Date": entry_date = bill_date else: entry_date = gle.posting_date @@ -450,7 +455,7 @@ class ReceivablePayableReport(object): 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"]) @@ -482,7 +487,7 @@ class ReceivablePayableReport(object): conditions.append("""party in (select name from tabSupplier where supplier_group=%s)""") values.append(self.filters.get("supplier_group")) - + return " and ".join(conditions), values def get_gl_entries_for(self, party, party_type, against_voucher_type, against_voucher): @@ -505,7 +510,7 @@ class ReceivablePayableReport(object): party_account_currency, currency, si.conversion_rate, ps.due_date, ps.payment_amount, ps.description from `tabSales Invoice` si, `tabPayment Schedule` ps - where si.name = ps.parent and + where si.name = ps.parent and si.docstatus = 1 and si.company = '%s' and si.name in (%s) order by ps.due_date""" % (self.filters.company, ','.join(['%s'] *len(voucher_nos))), (tuple(voucher_nos)), as_dict = 1): @@ -577,7 +582,7 @@ def get_pdc_details(party_type, report_date): on (pref.parent = pent.name) where - pent.docstatus < 2 and pent.posting_date > %s + pent.docstatus < 2 and pent.posting_date > %s and pent.party_type = %s group by pent.party, pref.reference_name""", (report_date, party_type), as_dict=1): pdc_details.setdefault((pdc.invoice_no, pdc.party), pdc) From 17544d7ad6885ac27aa2a22dba4d4b0088097d62 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Fri, 7 Dec 2018 08:15:05 +0530 Subject: [PATCH 07/14] Refactored accounts receivable report for payment terms --- .../accounts_receivable.py | 162 ++++++++++-------- 1 file changed, 90 insertions(+), 72 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index b262311d4a..8ff4b2db1b 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe, erpnext from frappe import _, scrub -from frappe.utils import getdate, nowdate, flt, cint +from frappe.utils import getdate, nowdate, flt, cint, formatdate, cstr class ReceivablePayableReport(object): def __init__(self, filters=None): @@ -112,12 +112,12 @@ class ReceivablePayableReport(object): "options": "Currency", "width": 100 }, - { - "fieldname": "pdc/lc_date", - "label": _("PDC/LC Date"), - "fieldtype": "Date", - "width": 110 - }, + # { + # "fieldname": "pdc/lc_date", + # "label": _("PDC/LC Date"), + # "fieldtype": "Date", + # "width": 110 + # }, { "fieldname": "pdc/lc_ref", "label": _("PDC/LC Ref"), @@ -195,40 +195,64 @@ class ReceivablePayableReport(object): outstanding_amount, credit_note_amount, payment_amount = self.get_outstanding_amount( gle,self.filters.report_date, self.dr_or_cr, return_entries) if abs(outstanding_amount) > 0.1/10**self.currency_precision: + pdc_list = self.pdc_details.get((gle.voucher_no, gle.party), []) if self.filters.based_on_payment_terms and self.payment_term_map.get(gle.voucher_no): - pdc_amount = flt(self.pdc_details.get((gle.voucher_no, gle.party), {}).get("pdc_amount")) for d in self.payment_term_map.get(gle.voucher_no): - term_outstanding_amount = 0 - if payment_amount >= d[1]: - payment_amount = payment_amount - d[1] - if credit_note_amount: - term_outstanding_amount -= credit_note_amount - row = self.prepare_row(party_naming_by, args, gle, term_outstanding_amount, - credit_note_amount, d[0], payment_amount , d[1], d[2], 0) - credit_note_amount = 0 - data.append(row) - else: - outstanding_amount = d[1] - payment_amount - credit_note_amount - if pdc_amount > outstanding_amount: - pdc = outstanding_amount - pdc_amount -= outstanding_amount + payment_amount, d.payment_amount = self.allocate_based_on_fifo(payment_amount, d.payment_term_amount) + + term_outstanding_amount = d.payment_term_amount - d.payment_amount + credit_note_amount, d.credit_note_amount = self.allocate_based_on_fifo(credit_note_amount, term_outstanding_amount) + + term_outstanding_amount -= d.credit_note_amount + + row_outstanding = term_outstanding_amount + d.pdc_details = [] + for pdc in pdc_list: + if row_outstanding <= pdc.pdc_amount: + d.pdc_amount += row_outstanding + pdc.pdc_amount -= row_outstanding + if row_outstanding and d.pdc_ref and d.pdc_date: + d.pdc_details.append(cstr(d.pdc_ref) + "/" + formatdate(d.pdc_date)) + row_outstanding = 0 + else: - pdc = pdc_amount - pdc_amount = 0 - if self.filters.get(gle.party_type): - d[1] = d[1] * d[3] - row = self.prepare_row(party_naming_by, args, gle, outstanding_amount, - credit_note_amount, d[0], payment_amount , d[1], d[2], pdc) - payment_amount = 0 - credit_note_amount = 0 + d.pdc_amount = pdc.pdc_amount + if pdc.pdc_amount and d.pdc_ref and d.pdc_date: + d.pdc_details.append(cstr(d.pdc_ref) + "/" + formatdate(d.pdc_date)) + pdc.pdc_amount = 0 + row_outstanding -= d.pdc_amount + + if term_outstanding_amount > 0: + row = self.prepare_row(party_naming_by, args, gle, term_outstanding_amount, + d.credit_note_amount, d.due_date, d.payment_amount , d.payment_term_amount, + d.description, d.pdc_amount, d.pdc_details) data.append(row) else: - row = self.prepare_row(party_naming_by, args, gle, outstanding_amount, credit_note_amount) + pdc_amount = 0 + pdc_details = [] + for d in pdc_list: + pdc_amount += flt(d.pdc_amount) + if pdc_amount and d.pdc_ref and d.pdc_date: + pdc_details.append(cstr(d.pdc_ref) + "/" + formatdate(d.pdc_date)) + + row = self.prepare_row(party_naming_by, args, gle, outstanding_amount, + credit_note_amount, pdc_amount=pdc_amount, pdc_details=pdc_details) data.append(row) return data + def allocate_based_on_fifo(self, total_amount, row_amount): + allocated_amount = 0 + if row_amount <= total_amount: + allocated_amount = row_amount + total_amount -= row_amount + else: + allocated_amount = total_amount + total_amount = 0 + + return total_amount, allocated_amount + def prepare_row(self, party_naming_by, args, gle, outstanding_amount, credit_note_amount, - due_date=None, paid_amt=None, payment_term_amount=None, payment_term=None, pdc_amount=None): + due_date=None, paid_amt=None, payment_term_amount=None, payment_term=None, pdc_amount=None, pdc_details=None): row = [gle.posting_date, gle.party] # customer / supplier name @@ -257,7 +281,7 @@ class ReceivablePayableReport(object): if payment_term_amount: invoiced_amount = payment_term_amount - if paid_amt == None: + if not payment_term_amount: paid_amt = invoiced_amount - outstanding_amount - credit_note_amount row += [invoiced_amount, paid_amt, credit_note_amount, outstanding_amount] @@ -288,16 +312,9 @@ class ReceivablePayableReport(object): else: row.append(self.company_currency) - pdc = self.pdc_details.get((gle.voucher_no, gle.party), {}) - - if pdc_amount == None: - pdc_amount = flt(pdc.get("pdc_amount")) - - pdc_date = pdc.get("pdc_date") if pdc_amount else '' - pdc_ref = pdc.get("pdc_ref") if pdc_amount else '' - - remaining_balance = outstanding_amount - pdc_amount - row += [pdc_date, pdc_ref, pdc_amount, remaining_balance] + remaining_balance = outstanding_amount - flt(pdc_amount) + pdc_details = ", ".join(pdc_details) + row += [pdc_details, pdc_amount, remaining_balance] <<<<<<< HEAD # customer territory / supplier group @@ -506,22 +523,27 @@ class ReceivablePayableReport(object): def get_payment_term_detail(self, voucher_nos): payment_term_map = frappe._dict() - for d in frappe.db.sql(""" select si.name, si.payment_terms_template, + payment_terms_details = frappe.db.sql(""" select si.name, party_account_currency, currency, si.conversion_rate, ps.due_date, ps.payment_amount, ps.description from `tabSales Invoice` si, `tabPayment Schedule` ps where si.name = ps.parent and si.docstatus = 1 and si.company = '%s' and - si.name in (%s) order by ps.due_date""" - % (self.filters.company, ','.join(['%s'] *len(voucher_nos))), (tuple(voucher_nos)), as_dict = 1): - if d.payment_terms_template: - if self.filters.get("customer") and d.currency == d.party_account_currency: - payment_term_amount = d.payment_amount - else: - payment_term_amount = flt(flt(d.payment_amount) * flt(d.conversion_rate), self.currency_precision) + si.name in (%s) order by ps.due_date + """ % (self.filters.company, ','.join(['%s'] *len(voucher_nos))), + (tuple(voucher_nos)), as_dict = 1) - payment_term_map.setdefault(d.name, []) - payment_term_map[d.name].append((d.due_date, payment_term_amount, d.description)) + for d in payment_terms_details: + if self.filters.get("customer") and d.currency == d.party_account_currency: + payment_term_amount = d.payment_amount + else: + payment_term_amount = flt(flt(d.payment_amount) * flt(d.conversion_rate), self.currency_precision) + + payment_term_map.setdefault(d.name, []).append(frappe._dict({ + "due_date": d.due_date, + "payment_term_amount": payment_term_amount, + "description": d.description + })) return payment_term_map def get_chart_data(self, columns, data): @@ -571,12 +593,11 @@ def get_ageing_data(first_range, second_range, third_range, age_as_on, entry_dat def get_pdc_details(party_type, report_date): pdc_details = frappe._dict() - - for pdc in frappe.db.sql(""" + pdc_via_pe = frappe.db.sql(""" select pref.reference_name as invoice_no, pent.party, pent.party_type, - max(pent.posting_date) as pdc_date, sum(ifnull(pref.allocated_amount,0)) as pdc_amount, - GROUP_CONCAT(pent.reference_no SEPARATOR ', ') as pdc_ref + pent.posting_date as pdc_date, ifnull(pref.allocated_amount,0) as pdc_amount, + pent.reference_no as pdc_ref from `tabPayment Entry` as pent inner join `tabPayment Entry Reference` as pref on @@ -584,19 +605,22 @@ def get_pdc_details(party_type, report_date): where pent.docstatus < 2 and pent.posting_date > %s and pent.party_type = %s - group by pent.party, pref.reference_name""", (report_date, party_type), as_dict=1): - pdc_details.setdefault((pdc.invoice_no, pdc.party), pdc) + """, (report_date, party_type), as_dict=1) + + for pdc in pdc_via_pe: + pdc_details.setdefault((pdc.invoice_no, pdc.party), []).append(pdc) + if scrub(party_type): amount_field = ("jea.debit_in_account_currency" if party_type == 'Supplier' else "jea.credit_in_account_currency") else: amount_field = "jea.debit + jea.credit" - for pdc in frappe.db.sql(""" + pdc_via_je = frappe.db.sql(""" select jea.reference_name as invoice_no, jea.party, jea.party_type, - max(je.posting_date) as pdc_date, sum(ifnull({0},0)) as pdc_amount, - GROUP_CONCAT(je.cheque_no SEPARATOR ', ') as pdc_ref + je.posting_date as pdc_date, ifnull({0},0) as pdc_amount, + je.cheque_no as pdc_ref from `tabJournal Entry` as je inner join `tabJournal Entry Account` as jea on @@ -604,16 +628,10 @@ def get_pdc_details(party_type, report_date): where je.docstatus < 2 and je.posting_date > %s and jea.party_type = %s - group by jea.party, jea.reference_name""".format(amount_field), (report_date, party_type), as_dict=1): - if (pdc.invoice_no, pdc.party) in pdc_details: - key = (pdc.invoice_no, pdc.party) - pdc_details[key]["pdc_amount"] += pdc.pdc_amount - if pdc.pdc_ref: - pdc_details[key]["pdc_ref"] += ", " + pdc.pdc_ref - if pdc.pdc_date: - pdc_details[key]["pdc_date"] = max(pdc_details[key]["pdc_date"], pdc.pdc_date) - else: - pdc_details.setdefault((pdc.invoice_no, pdc.party), pdc) + """.format(amount_field), (report_date, party_type), as_dict=1) + + for pdc in pdc_via_je: + pdc_details.setdefault((pdc.invoice_no, pdc.party), []).append(pdc) return pdc_details From a944f88b946b3b9a94dffbbc1cd9c9b18ad51e84 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Fri, 7 Dec 2018 08:17:09 +0530 Subject: [PATCH 08/14] Removed column for pdc date --- .../report/accounts_receivable/accounts_receivable.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 8ff4b2db1b..3d20039da6 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -112,12 +112,6 @@ class ReceivablePayableReport(object): "options": "Currency", "width": 100 }, - # { - # "fieldname": "pdc/lc_date", - # "label": _("PDC/LC Date"), - # "fieldtype": "Date", - # "width": 110 - # }, { "fieldname": "pdc/lc_ref", "label": _("PDC/LC Ref"), From 4ac8fcf4af84e3ac3241110fcfb29c539e1acd24 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Mon, 17 Dec 2018 15:13:33 +0530 Subject: [PATCH 09/14] Added test case for Accounts receivable report based on payment terms --- .../sales_invoice/test_sales_invoice.py | 32 +++---- .../accounts_receivable.py | 16 ++++ .../test_accounts_receivable.py | 84 +++++++++++++++++++ 3 files changed, 116 insertions(+), 16 deletions(-) create mode 100644 erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 94037c7d0a..68cc500004 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -375,7 +375,7 @@ class TestSalesInvoice(unittest.TestCase): si.insert() self.assertEqual(si.net_total, 4600) - + self.assertEqual(si.get("taxes")[0].tax_amount, 874.0) self.assertEqual(si.get("taxes")[0].total, 5474.0) @@ -405,12 +405,12 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(si.total, 975) self.assertEqual(si.net_total, 900) - + self.assertEqual(si.get("taxes")[0].tax_amount, 216.0) self.assertEqual(si.get("taxes")[0].total, 1116.0) self.assertEqual(si.grand_total, 1116.0) - + def test_inclusive_rate_validations(self): si = frappe.copy_doc(test_records[2]) for i, tax in enumerate(si.get("taxes")): @@ -552,7 +552,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(si.grand_total, 1215.90) self.assertEqual(si.rounding_adjustment, 0.01) self.assertEqual(si.base_rounding_adjustment, 0.50) - + def test_outstanding(self): w = self.make() @@ -923,7 +923,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertRaises(SerialNoWarehouseError, si.submit) def test_serial_numbers_against_delivery_note(self): - """ + """ check if the sales invoice item serial numbers and the delivery note items serial numbers are same """ @@ -1238,7 +1238,7 @@ class TestSalesInvoice(unittest.TestCase): def test_item_wise_tax_breakup_india(self): frappe.flags.country = "India" - + si = self.create_si_to_test_tax_breakup() itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si) @@ -1256,12 +1256,12 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(itemised_tax, expected_itemised_tax) self.assertEqual(itemised_taxable_amount, expected_itemised_taxable_amount) - + frappe.flags.country = None def test_item_wise_tax_breakup_outside_india(self): frappe.flags.country = "United States" - + si = self.create_si_to_test_tax_breakup() itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si) @@ -1287,7 +1287,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(itemised_tax, expected_itemised_tax) self.assertEqual(itemised_taxable_amount, expected_itemised_taxable_amount) - + frappe.flags.country = None def create_si_to_test_tax_breakup(self): @@ -1375,7 +1375,7 @@ class TestSalesInvoice(unittest.TestCase): shipping_rule = create_shipping_rule(shipping_rule_type = "Selling", shipping_rule_name = "Shipping Rule - Sales Invoice Test") si = frappe.copy_doc(test_records[2]) - + si.shipping_rule = shipping_rule.name si.insert() @@ -1392,14 +1392,14 @@ class TestSalesInvoice(unittest.TestCase): "cost_center": shipping_rule.cost_center, "tax_amount": shipping_amount, "description": shipping_rule.name - } + } si.append("taxes", shipping_charge) si.save() self.assertEqual(si.net_total, 1250) self.assertEqual(si.total_taxes_and_charges, 577.05) - self.assertEqual(si.grand_total, 1827.05) + self.assertEqual(si.grand_total, 1827.05) def test_create_invoice_without_terms(self): si = create_sales_invoice(do_not_save=1) @@ -1496,7 +1496,7 @@ class TestSalesInvoice(unittest.TestCase): for gle in gl_entries: self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - + accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 accounts_settings.save() @@ -1524,9 +1524,9 @@ def create_sales_invoice(**args): "warehouse": args.warehouse or "_Test Warehouse - _TC", "qty": args.qty or 1, "rate": args.rate or 100, - "income_account": "Sales - _TC", - "expense_account": "Cost of Goods Sold - _TC", - "cost_center": "_Test Cost Center - _TC", + "income_account": args.income_account or "Sales - _TC", + "expense_account": args.expense_account or "Cost of Goods Sold - _TC", + "cost_center": args.cost_center or "_Test Cost Center - _TC", "serial_no": args.serial_no }) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 3d20039da6..8b16ae3399 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -221,6 +221,22 @@ class ReceivablePayableReport(object): d.credit_note_amount, d.due_date, d.payment_amount , d.payment_term_amount, d.description, d.pdc_amount, d.pdc_details) data.append(row) + + if credit_note_amount: + outstanding_amount, credit_note_amount, payment_amount = self.get_outstanding_amount( + gle,self.filters.report_date, self.dr_or_cr, return_entries) + + pdc_amount = 0 + pdc_details = [] + for d in pdc_list: + pdc_amount += flt(d.pdc_amount) + if pdc_amount and d.pdc_ref and d.pdc_date: + pdc_details.append(cstr(d.pdc_ref) + "/" + formatdate(d.pdc_date)) + + row = self.prepare_row(party_naming_by, args, gle, outstanding_amount, + credit_note_amount, pdc_amount=pdc_amount, pdc_details=pdc_details) + data.append(row) + else: pdc_amount = 0 pdc_details = [] diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py new file mode 100644 index 0000000000..34e6c83e01 --- /dev/null +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -0,0 +1,84 @@ +import frappe +import frappe.defaults +import unittest +from frappe.utils import today, getdate, add_days +from erpnext.accounts.report.accounts_receivable.accounts_receivable import execute +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry + +class TestAccountsReceivable(unittest.TestCase): + def test_accounts_receivable(self): + frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company 2'") + frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 2'") + + filters = { + 'company': '_Test Company 2', + 'based_on_payment_terms': 1 + } + + name = make_sales_invoice() + report = execute(filters) + + expected_data = [[100,30], [100,50], [100,20]] + + self.assertEqual(expected_data[0], report[1][0][6:8]) + self.assertEqual(expected_data[1], report[1][1][6:8]) + self.assertEqual(expected_data[2], report[1][2][6:8]) + + make_payment(name) + report = execute(filters) + + expected_data_after_payment = [[100,50], [100,20]] + + self.assertEqual(expected_data_after_payment[0], report[1][0][6:8]) + self.assertEqual(expected_data_after_payment[1], report[1][1][6:8]) + + make_credit_note(name) + report = execute(filters) + + expected_data_after_credit_note = [[100,100,30,100,-30]] + + self.assertEqual(expected_data_after_credit_note[0], report[1][0][6:11]) + + +def make_sales_invoice(): + frappe.set_user("Administrator") + + si = create_sales_invoice(company="_Test Company 2", + customer = '_Test Customer 2', + currency = 'EUR', + warehouse = 'Finished Goods - _TC2', + debit_to = 'Debtors - _TC2', + income_account = 'Sales - _TC2', + expense_account = 'Cost of Goods Sold - _TC2', + cost_center = '_Test Company 2 - _TC2', + do_not_save=1) + + si.append('payment_schedule', dict(due_date=getdate(add_days(today(), 30)), invoice_portion=30.00, payment_amount=30)) + si.append('payment_schedule', dict(due_date=getdate(add_days(today(), 60)), invoice_portion=50.00, payment_amount=50)) + si.append('payment_schedule', dict(due_date=getdate(add_days(today(), 90)), invoice_portion=20.00, payment_amount=20)) + + si.submit() + + return si.name + +def make_payment(docname): + pe = get_payment_entry("Sales Invoice", docname, bank_account="Cash - _TC2", party_amount=30) + pe.paid_from = "Debtors - _TC2" + pe.insert() + pe.submit() + + +def make_credit_note(docname): + create_sales_invoice(company="_Test Company 2", + customer = '_Test Customer 2', + currency = 'EUR', + qty = -1, + warehouse = 'Finished Goods - _TC2', + debit_to = 'Debtors - _TC2', + income_account = 'Sales - _TC2', + expense_account = 'Cost of Goods Sold - _TC2', + cost_center = '_Test Company 2 - _TC2', + is_return = 1, + return_against = docname) + From 5de603c6af8e95789480ede0c869cb5670164504 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Mon, 17 Dec 2018 17:45:39 +0530 Subject: [PATCH 10/14] breaked up code into multiple functions --- .../accounts_receivable.py | 80 +++++++++++-------- 1 file changed, 46 insertions(+), 34 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 8b16ae3399..1fd82d9a56 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -189,32 +189,21 @@ class ReceivablePayableReport(object): outstanding_amount, credit_note_amount, payment_amount = self.get_outstanding_amount( gle,self.filters.report_date, self.dr_or_cr, return_entries) if abs(outstanding_amount) > 0.1/10**self.currency_precision: - pdc_list = self.pdc_details.get((gle.voucher_no, gle.party), []) if self.filters.based_on_payment_terms and self.payment_term_map.get(gle.voucher_no): for d in self.payment_term_map.get(gle.voucher_no): + # Allocate payment amount based on payment terms(FIFO order) payment_amount, d.payment_amount = self.allocate_based_on_fifo(payment_amount, d.payment_term_amount) term_outstanding_amount = d.payment_term_amount - d.payment_amount + + # Allocate credit note based on payment terms(FIFO order) credit_note_amount, d.credit_note_amount = self.allocate_based_on_fifo(credit_note_amount, term_outstanding_amount) term_outstanding_amount -= d.credit_note_amount row_outstanding = term_outstanding_amount - d.pdc_details = [] - for pdc in pdc_list: - if row_outstanding <= pdc.pdc_amount: - d.pdc_amount += row_outstanding - pdc.pdc_amount -= row_outstanding - if row_outstanding and d.pdc_ref and d.pdc_date: - d.pdc_details.append(cstr(d.pdc_ref) + "/" + formatdate(d.pdc_date)) - row_outstanding = 0 - - else: - d.pdc_amount = pdc.pdc_amount - if pdc.pdc_amount and d.pdc_ref and d.pdc_date: - d.pdc_details.append(cstr(d.pdc_ref) + "/" + formatdate(d.pdc_date)) - pdc.pdc_amount = 0 - row_outstanding -= d.pdc_amount + # Allocate PDC based on payment terms(FIFO order) + d.pdc_details, d.pdc_amount = self.allocate_pdc_amount_in_fifo(gle, row_outstanding) if term_outstanding_amount > 0: row = self.prepare_row(party_naming_by, args, gle, term_outstanding_amount, @@ -226,30 +215,53 @@ class ReceivablePayableReport(object): outstanding_amount, credit_note_amount, payment_amount = self.get_outstanding_amount( gle,self.filters.report_date, self.dr_or_cr, return_entries) - pdc_amount = 0 - pdc_details = [] - for d in pdc_list: - pdc_amount += flt(d.pdc_amount) - if pdc_amount and d.pdc_ref and d.pdc_date: - pdc_details.append(cstr(d.pdc_ref) + "/" + formatdate(d.pdc_date)) - - row = self.prepare_row(party_naming_by, args, gle, outstanding_amount, - credit_note_amount, pdc_amount=pdc_amount, pdc_details=pdc_details) + row = self.prepare_row_without_payment_terms(party_naming_by, args, gle, outstanding_amount, + credit_note_amount) data.append(row) else: - pdc_amount = 0 - pdc_details = [] - for d in pdc_list: - pdc_amount += flt(d.pdc_amount) - if pdc_amount and d.pdc_ref and d.pdc_date: - pdc_details.append(cstr(d.pdc_ref) + "/" + formatdate(d.pdc_date)) - - row = self.prepare_row(party_naming_by, args, gle, outstanding_amount, - credit_note_amount, pdc_amount=pdc_amount, pdc_details=pdc_details) + row = self.prepare_row_without_payment_terms(party_naming_by, args, gle, outstanding_amount, + credit_note_amount) data.append(row) return data + def allocate_pdc_amount_in_fifo(self, gle, row_outstanding): + pdc_list = self.pdc_details.get((gle.voucher_no, gle.party), []) + + pdc_details = [] + pdc_amount = 0 + for pdc in pdc_list: + if row_outstanding <= pdc.pdc_amount: + pdc_amount += row_outstanding + pdc.pdc_amount -= row_outstanding + if row_outstanding and pdc.pdc_ref and pdc.pdc_date: + pdc_details.append(cstr(pdc.pdc_ref) + "/" + formatdate(pdc.pdc_date)) + row_outstanding = 0 + + else: + pdc_amount = pdc.pdc_amount + if pdc.pdc_amount and pdc.pdc_ref and pdc.pdc_date: + pdc_details.append(cstr(pdc.pdc_ref) + "/" + formatdate(pdc.pdc_date)) + pdc.pdc_amount = 0 + row_outstanding -= pdc_amount + + return pdc_details, pdc_amount + + def prepare_row_without_payment_terms(self, party_naming_by, args, gle, outstanding_amount, credit_note_amount): + pdc_list = self.pdc_details.get((gle.voucher_no, gle.party), []) + pdc_amount = 0 + pdc_details = [] + for d in pdc_list: + pdc_amount += flt(d.pdc_amount) + if pdc_amount and d.pdc_ref and d.pdc_date: + pdc_details.append(cstr(d.pdc_ref) + "/" + formatdate(d.pdc_date)) + + row = self.prepare_row(party_naming_by, args, gle, outstanding_amount, + credit_note_amount, pdc_amount=pdc_amount, pdc_details=pdc_details) + + return row + + def allocate_based_on_fifo(self, total_amount, row_amount): allocated_amount = 0 if row_amount <= total_amount: From 530453e4fc1e51e98fc751be17f887075a41b27e Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Wed, 19 Dec 2018 18:12:58 +0530 Subject: [PATCH 11/14] Added temp variables for outstanding, credit_note_amt instead of function calling --- .../report/accounts_receivable/accounts_receivable.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 1fd82d9a56..161f845e4f 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -188,6 +188,10 @@ class ReceivablePayableReport(object): if self.is_receivable_or_payable(gle, self.dr_or_cr, future_vouchers): outstanding_amount, credit_note_amount, payment_amount = self.get_outstanding_amount( gle,self.filters.report_date, self.dr_or_cr, return_entries) + + temp_outstanding_amt = outstanding_amount + temp_credit_note_amt = credit_note_amount + if abs(outstanding_amount) > 0.1/10**self.currency_precision: if self.filters.based_on_payment_terms and self.payment_term_map.get(gle.voucher_no): for d in self.payment_term_map.get(gle.voucher_no): @@ -212,11 +216,8 @@ class ReceivablePayableReport(object): data.append(row) if credit_note_amount: - outstanding_amount, credit_note_amount, payment_amount = self.get_outstanding_amount( - gle,self.filters.report_date, self.dr_or_cr, return_entries) - - row = self.prepare_row_without_payment_terms(party_naming_by, args, gle, outstanding_amount, - credit_note_amount) + row = self.prepare_row_without_payment_terms(party_naming_by, args, gle, temp_outstanding_amt, + temp_credit_note_amt) data.append(row) else: From b645c2c75ee60dc853a7f82d484f4b8e844b6413 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Wed, 19 Dec 2018 18:47:36 +0530 Subject: [PATCH 12/14] Rebase using staging-fixes and resolved conflicts --- .../accounts_receivable/accounts_receivable.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 161f845e4f..a084ae986c 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -339,25 +339,17 @@ class ReceivablePayableReport(object): pdc_details = ", ".join(pdc_details) row += [pdc_details, pdc_amount, remaining_balance] -<<<<<<< HEAD - # customer territory / supplier group - if args.get("party_type") == "Customer": - row += [self.get_territory(gle.party), self.get_customer_group(gle.party), - voucher_details.get(gle.voucher_no, {}).get("sales_person")] - if args.get("party_type") == "Supplier": - row += [self.get_supplier_group(gle.party)] -======= if args.get('party_type') == 'Customer': # customer LPO row += [self.voucher_details.get(gle.voucher_no, {}).get("po_no")] ->>>>>>> Accounts Receivable report based on payment terms # Delivery Note row += [self.voucher_details.get(gle.voucher_no, {}).get("delivery_note")] # customer territory / supplier group if args.get("party_type") == "Customer": - row += [self.get_territory(gle.party), self.get_customer_group(gle.party)] + row += [self.get_territory(gle.party), self.get_customer_group(gle.party), + self.voucher_details.get(gle.voucher_no, {}).get("sales_person")] if args.get("party_type") == "Supplier": row += [self.get_supplier_group(gle.party)] From 0866b2b75a882d88743e550203f64380f34bb9b1 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 20 Dec 2018 14:11:20 +0530 Subject: [PATCH 13/14] Update accounts_receivable.py --- .../accounts/report/accounts_receivable/accounts_receivable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index a084ae986c..121d5b0213 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -545,7 +545,7 @@ class ReceivablePayableReport(object): where si.name = ps.parent and si.docstatus = 1 and si.company = '%s' and si.name in (%s) order by ps.due_date - """ % (self.filters.company, ','.join(['%s'] *len(voucher_nos))), + """ % (frappe.db.escape(self.filters.company), ','.join(['%s'] *len(voucher_nos))), (tuple(voucher_nos)), as_dict = 1) for d in payment_terms_details: From 7067727c0c8a776e95fea482666c9debeab10742 Mon Sep 17 00:00:00 2001 From: Frappe Bot Date: Fri, 21 Dec 2018 05:46:22 +0000 Subject: [PATCH 14/14] bumped to version 11.0.3-beta.31 --- erpnext/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index cf8c06d700..53ec3d6e09 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -12,7 +12,7 @@ app_license = "GNU General Public License (v3)" source_link = "https://github.com/frappe/erpnext" develop_version = '12.x.x-develop' -staging_version = '11.0.3-beta.30' +staging_version = '11.0.3-beta.31' error_report_email = "support@erpnext.com"