refactor: age range in one field (#42736)
* fix: age range in one field * fix: patch for custom reports * refactor: stock ageing and account payable report * fix: fixing the test cases * fix: common patch for reports with ageing * refactor: rename variable and minor refactor * fix: fixing the test case
This commit is contained in:
@@ -61,32 +61,10 @@ frappe.query_reports["Accounts Payable"] = {
|
|||||||
default: "Due Date",
|
default: "Due Date",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "range1",
|
fieldname: "range",
|
||||||
label: __("Ageing Range 1"),
|
label: __("Ageing Range"),
|
||||||
fieldtype: "Int",
|
fieldtype: "Data",
|
||||||
default: "30",
|
default: "30, 60, 90, 120",
|
||||||
reqd: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: "range2",
|
|
||||||
label: __("Ageing Range 2"),
|
|
||||||
fieldtype: "Int",
|
|
||||||
default: "60",
|
|
||||||
reqd: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: "range3",
|
|
||||||
label: __("Ageing Range 3"),
|
|
||||||
fieldtype: "Int",
|
|
||||||
default: "90",
|
|
||||||
reqd: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: "range4",
|
|
||||||
label: __("Ageing Range 4"),
|
|
||||||
fieldtype: "Int",
|
|
||||||
default: "120",
|
|
||||||
reqd: 1,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "payment_terms_template",
|
fieldname: "payment_terms_template",
|
||||||
|
|||||||
@@ -30,10 +30,7 @@ class TestAccountsPayable(AccountsTestMixin, FrappeTestCase):
|
|||||||
"party_type": "Supplier",
|
"party_type": "Supplier",
|
||||||
"party": [self.supplier],
|
"party": [self.supplier],
|
||||||
"report_date": today(),
|
"report_date": today(),
|
||||||
"range1": 30,
|
"range": "30, 60, 90, 120",
|
||||||
"range2": 60,
|
|
||||||
"range3": 90,
|
|
||||||
"range4": 120,
|
|
||||||
"in_party_currency": 1,
|
"in_party_currency": 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,32 +24,10 @@ frappe.query_reports["Accounts Payable Summary"] = {
|
|||||||
default: "Due Date",
|
default: "Due Date",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "range1",
|
fieldname: "range",
|
||||||
label: __("Ageing Range 1"),
|
label: __("Ageing Range"),
|
||||||
fieldtype: "Int",
|
fieldtype: "Data",
|
||||||
default: "30",
|
default: "30, 60, 90, 120",
|
||||||
reqd: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: "range2",
|
|
||||||
label: __("Ageing Range 2"),
|
|
||||||
fieldtype: "Int",
|
|
||||||
default: "60",
|
|
||||||
reqd: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: "range3",
|
|
||||||
label: __("Ageing Range 3"),
|
|
||||||
fieldtype: "Int",
|
|
||||||
default: "90",
|
|
||||||
reqd: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: "range4",
|
|
||||||
label: __("Ageing Range 4"),
|
|
||||||
fieldtype: "Int",
|
|
||||||
default: "120",
|
|
||||||
reqd: 1,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "finance_book",
|
fieldname: "finance_book",
|
||||||
|
|||||||
@@ -89,32 +89,10 @@ frappe.query_reports["Accounts Receivable"] = {
|
|||||||
default: "Due Date",
|
default: "Due Date",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "range1",
|
fieldname: "range",
|
||||||
label: __("Ageing Range 1"),
|
label: __("Ageing Range"),
|
||||||
fieldtype: "Int",
|
fieldtype: "Data",
|
||||||
default: "30",
|
default: "30, 60, 90, 120",
|
||||||
reqd: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: "range2",
|
|
||||||
label: __("Ageing Range 2"),
|
|
||||||
fieldtype: "Int",
|
|
||||||
default: "60",
|
|
||||||
reqd: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: "range3",
|
|
||||||
label: __("Ageing Range 3"),
|
|
||||||
fieldtype: "Int",
|
|
||||||
default: "90",
|
|
||||||
reqd: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: "range4",
|
|
||||||
label: __("Ageing Range 4"),
|
|
||||||
fieldtype: "Int",
|
|
||||||
default: "120",
|
|
||||||
reqd: 1,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "customer_group",
|
fieldname: "customer_group",
|
||||||
|
|||||||
@@ -50,6 +50,11 @@ class ReceivablePayableReport:
|
|||||||
getdate(nowdate()) if self.filters.report_date > getdate(nowdate()) else self.filters.report_date
|
getdate(nowdate()) if self.filters.report_date > getdate(nowdate()) else self.filters.report_date
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not self.filters.range:
|
||||||
|
self.filters.range = "30, 60, 90, 120"
|
||||||
|
self.ranges = [num.strip() for num in self.filters.range.split(",") if num.strip().isdigit()]
|
||||||
|
self.range_numbers = [num for num in range(1, len(self.ranges) + 2)]
|
||||||
|
|
||||||
def run(self, args):
|
def run(self, args):
|
||||||
self.filters.update(args)
|
self.filters.update(args)
|
||||||
self.set_defaults()
|
self.set_defaults()
|
||||||
@@ -733,37 +738,22 @@ class ReceivablePayableReport:
|
|||||||
|
|
||||||
# ageing buckets should not have amounts if due date is not reached
|
# ageing buckets should not have amounts if due date is not reached
|
||||||
if getdate(entry_date) > getdate(self.filters.report_date):
|
if getdate(entry_date) > getdate(self.filters.report_date):
|
||||||
row.range1 = row.range2 = row.range3 = row.range4 = row.range5 = 0.0
|
[setattr(row, f"range{i}", 0.0) for i in self.range_numbers]
|
||||||
|
|
||||||
row.total_due = row.range1 + row.range2 + row.range3 + row.range4 + row.range5
|
row.total_due = sum(row[f"range{i}"] for i in self.range_numbers)
|
||||||
|
|
||||||
def get_ageing_data(self, entry_date, row):
|
def get_ageing_data(self, entry_date, row):
|
||||||
# [0-30, 30-60, 60-90, 90-120, 120-above]
|
# [0-30, 30-60, 60-90, 90-120, 120-above]
|
||||||
row.range1 = row.range2 = row.range3 = row.range4 = row.range5 = 0.0
|
[setattr(row, f"range{i}", 0.0) for i in self.range_numbers]
|
||||||
|
|
||||||
if not (self.age_as_on and entry_date):
|
if not (self.age_as_on and entry_date):
|
||||||
return
|
return
|
||||||
|
|
||||||
row.age = (getdate(self.age_as_on) - getdate(entry_date)).days or 0
|
row.age = (getdate(self.age_as_on) - getdate(entry_date)).days or 0
|
||||||
index = None
|
|
||||||
|
|
||||||
if not (self.filters.range1 and self.filters.range2 and self.filters.range3 and self.filters.range4):
|
index = next(
|
||||||
self.filters.range1, self.filters.range2, self.filters.range3, self.filters.range4 = (
|
(i for i, days in enumerate(self.ranges) if cint(row.age) <= cint(days)), len(self.ranges)
|
||||||
30,
|
)
|
||||||
60,
|
|
||||||
90,
|
|
||||||
120,
|
|
||||||
)
|
|
||||||
|
|
||||||
for i, days in enumerate(
|
|
||||||
[self.filters.range1, self.filters.range2, self.filters.range3, self.filters.range4]
|
|
||||||
):
|
|
||||||
if cint(row.age) <= cint(days):
|
|
||||||
index = i
|
|
||||||
break
|
|
||||||
|
|
||||||
if index is None:
|
|
||||||
index = 4
|
|
||||||
row["range" + str(index + 1)] = row.outstanding
|
row["range" + str(index + 1)] = row.outstanding
|
||||||
|
|
||||||
def get_ple_entries(self):
|
def get_ple_entries(self):
|
||||||
@@ -1075,6 +1065,7 @@ class ReceivablePayableReport:
|
|||||||
self.add_column(_("Debit Note"), fieldname="credit_note")
|
self.add_column(_("Debit Note"), fieldname="credit_note")
|
||||||
self.add_column(_("Outstanding Amount"), fieldname="outstanding")
|
self.add_column(_("Outstanding Amount"), fieldname="outstanding")
|
||||||
|
|
||||||
|
self.add_column(label=_("Age (Days)"), fieldname="age", fieldtype="Int", width=80)
|
||||||
self.setup_ageing_columns()
|
self.setup_ageing_columns()
|
||||||
|
|
||||||
self.add_column(
|
self.add_column(
|
||||||
@@ -1133,34 +1124,26 @@ class ReceivablePayableReport:
|
|||||||
def setup_ageing_columns(self):
|
def setup_ageing_columns(self):
|
||||||
# for charts
|
# for charts
|
||||||
self.ageing_column_labels = []
|
self.ageing_column_labels = []
|
||||||
self.add_column(label=_("Age (Days)"), fieldname="age", fieldtype="Int", width=80)
|
ranges = [*self.ranges, "Above"]
|
||||||
|
|
||||||
|
prev_range_value = 0
|
||||||
|
for idx, curr_range_value in enumerate(ranges):
|
||||||
|
label = f"{prev_range_value}-{curr_range_value}"
|
||||||
|
self.add_column(label=label, fieldname="range" + str(idx + 1))
|
||||||
|
|
||||||
for i, label in enumerate(
|
|
||||||
[
|
|
||||||
"0-{range1}".format(range1=self.filters["range1"]),
|
|
||||||
"{range1}-{range2}".format(
|
|
||||||
range1=cint(self.filters["range1"]) + 1, range2=self.filters["range2"]
|
|
||||||
),
|
|
||||||
"{range2}-{range3}".format(
|
|
||||||
range2=cint(self.filters["range2"]) + 1, range3=self.filters["range3"]
|
|
||||||
),
|
|
||||||
"{range3}-{range4}".format(
|
|
||||||
range3=cint(self.filters["range3"]) + 1, range4=self.filters["range4"]
|
|
||||||
),
|
|
||||||
_("{range4}-Above").format(range4=cint(self.filters["range4"]) + 1),
|
|
||||||
]
|
|
||||||
):
|
|
||||||
self.add_column(label=label, fieldname="range" + str(i + 1))
|
|
||||||
self.ageing_column_labels.append(label)
|
self.ageing_column_labels.append(label)
|
||||||
|
|
||||||
|
if curr_range_value.isdigit():
|
||||||
|
prev_range_value = cint(curr_range_value) + 1
|
||||||
|
|
||||||
def get_chart_data(self):
|
def get_chart_data(self):
|
||||||
|
precision = cint(frappe.db.get_default("float_precision")) or 2
|
||||||
rows = []
|
rows = []
|
||||||
for row in self.data:
|
for row in self.data:
|
||||||
row = frappe._dict(row)
|
row = frappe._dict(row)
|
||||||
if not cint(row.bold):
|
if not cint(row.bold):
|
||||||
values = [row.range1, row.range2, row.range3, row.range4, row.range5]
|
values = [flt(row.get(f"range{i}", None), precision) for i in self.range_numbers]
|
||||||
precision = cint(frappe.db.get_default("float_precision")) or 2
|
rows.append({"values": values})
|
||||||
rows.append({"values": [flt(val, precision) for val in values]})
|
|
||||||
|
|
||||||
self.chart = {
|
self.chart = {
|
||||||
"data": {"labels": self.ageing_column_labels, "datasets": rows},
|
"data": {"labels": self.ageing_column_labels, "datasets": rows},
|
||||||
|
|||||||
@@ -83,10 +83,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
"party": [self.customer],
|
"party": [self.customer],
|
||||||
"report_date": add_days(today(), 2),
|
"report_date": add_days(today(), 2),
|
||||||
"based_on_payment_terms": 0,
|
"based_on_payment_terms": 0,
|
||||||
"range1": 30,
|
"range": "30, 60, 90, 120",
|
||||||
"range2": 60,
|
|
||||||
"range3": 90,
|
|
||||||
"range4": 120,
|
|
||||||
"show_remarks": False,
|
"show_remarks": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,10 +113,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
"company": self.company,
|
"company": self.company,
|
||||||
"based_on_payment_terms": 1,
|
"based_on_payment_terms": 1,
|
||||||
"report_date": today(),
|
"report_date": today(),
|
||||||
"range1": 30,
|
"range": "30, 60, 90, 120",
|
||||||
"range2": 60,
|
|
||||||
"range3": 90,
|
|
||||||
"range4": 120,
|
|
||||||
"show_remarks": True,
|
"show_remarks": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,10 +166,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
filters = {
|
filters = {
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"report_date": today(),
|
"report_date": today(),
|
||||||
"range1": 30,
|
"range": "30, 60, 90, 120",
|
||||||
"range2": 60,
|
|
||||||
"range3": 90,
|
|
||||||
"range4": 120,
|
|
||||||
"show_remarks": True,
|
"show_remarks": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,10 +257,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
"company": self.company,
|
"company": self.company,
|
||||||
"based_on_payment_terms": 0,
|
"based_on_payment_terms": 0,
|
||||||
"report_date": today(),
|
"report_date": today(),
|
||||||
"range1": 30,
|
"range": "30, 60, 90, 120",
|
||||||
"range2": 60,
|
|
||||||
"range3": 90,
|
|
||||||
"range4": 120,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
report = execute(filters)
|
report = execute(filters)
|
||||||
@@ -328,10 +316,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
filters = {
|
filters = {
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"report_date": today(),
|
"report_date": today(),
|
||||||
"range1": 30,
|
"range": "30, 60, 90, 120",
|
||||||
"range2": 60,
|
|
||||||
"range3": 90,
|
|
||||||
"range4": 120,
|
|
||||||
}
|
}
|
||||||
report = execute(filters)
|
report = execute(filters)
|
||||||
|
|
||||||
@@ -397,10 +382,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
filters = {
|
filters = {
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"report_date": today(),
|
"report_date": today(),
|
||||||
"range1": 30,
|
"range": "30, 60, 90, 120",
|
||||||
"range2": 60,
|
|
||||||
"range3": 90,
|
|
||||||
"range4": 120,
|
|
||||||
}
|
}
|
||||||
report = execute(filters)
|
report = execute(filters)
|
||||||
self.assertEqual(report[1], [])
|
self.assertEqual(report[1], [])
|
||||||
@@ -416,10 +398,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
filters = {
|
filters = {
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"report_date": today(),
|
"report_date": today(),
|
||||||
"range1": 30,
|
"range": "30, 60, 90, 120",
|
||||||
"range2": 60,
|
|
||||||
"range3": 90,
|
|
||||||
"range4": 120,
|
|
||||||
"group_by_party": True,
|
"group_by_party": True,
|
||||||
}
|
}
|
||||||
report = execute(filters)[1]
|
report = execute(filters)[1]
|
||||||
@@ -493,10 +472,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
filters = {
|
filters = {
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"report_date": today(),
|
"report_date": today(),
|
||||||
"range1": 30,
|
"range": "30, 60, 90, 120",
|
||||||
"range2": 60,
|
|
||||||
"range3": 90,
|
|
||||||
"range4": 120,
|
|
||||||
"show_future_payments": True,
|
"show_future_payments": True,
|
||||||
}
|
}
|
||||||
report = execute(filters)[1]
|
report = execute(filters)[1]
|
||||||
@@ -555,10 +531,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
filters = {
|
filters = {
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"report_date": today(),
|
"report_date": today(),
|
||||||
"range1": 30,
|
"range": "30, 60, 90, 120",
|
||||||
"range2": 60,
|
|
||||||
"range3": 90,
|
|
||||||
"range4": 120,
|
|
||||||
"sales_person": sales_person.name,
|
"sales_person": sales_person.name,
|
||||||
"show_sales_person": True,
|
"show_sales_person": True,
|
||||||
}
|
}
|
||||||
@@ -575,10 +548,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
filters = {
|
filters = {
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"report_date": today(),
|
"report_date": today(),
|
||||||
"range1": 30,
|
"range": "30, 60, 90, 120",
|
||||||
"range2": 60,
|
|
||||||
"range3": 90,
|
|
||||||
"range4": 120,
|
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
}
|
}
|
||||||
report = execute(filters)[1]
|
report = execute(filters)[1]
|
||||||
@@ -593,10 +563,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
filters = {
|
filters = {
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"report_date": today(),
|
"report_date": today(),
|
||||||
"range1": 30,
|
"range": "30, 60, 90, 120",
|
||||||
"range2": 60,
|
|
||||||
"range3": 90,
|
|
||||||
"range4": 120,
|
|
||||||
"customer_group": cus_group,
|
"customer_group": cus_group,
|
||||||
}
|
}
|
||||||
report = execute(filters)[1]
|
report = execute(filters)[1]
|
||||||
@@ -618,10 +585,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
filters = {
|
filters = {
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"report_date": today(),
|
"report_date": today(),
|
||||||
"range1": 30,
|
"range": "30, 60, 90, 120",
|
||||||
"range2": 60,
|
|
||||||
"range3": 90,
|
|
||||||
"range4": 120,
|
|
||||||
"customer_group": cus_groups_list, # Use the list of customer groups
|
"customer_group": cus_groups_list, # Use the list of customer groups
|
||||||
}
|
}
|
||||||
report = execute(filters)[1]
|
report = execute(filters)[1]
|
||||||
@@ -660,10 +624,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
filters = {
|
filters = {
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"report_date": today(),
|
"report_date": today(),
|
||||||
"range1": 30,
|
"range": "30, 60, 90, 120",
|
||||||
"range2": 60,
|
|
||||||
"range3": 90,
|
|
||||||
"range4": 120,
|
|
||||||
"party_account": self.debit_to,
|
"party_account": self.debit_to,
|
||||||
}
|
}
|
||||||
report = execute(filters)[1]
|
report = execute(filters)[1]
|
||||||
@@ -711,10 +672,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
"party_type": "Customer",
|
"party_type": "Customer",
|
||||||
"party": [self.customer],
|
"party": [self.customer],
|
||||||
"report_date": today(),
|
"report_date": today(),
|
||||||
"range1": 30,
|
"range": "30, 60, 90, 120",
|
||||||
"range2": 60,
|
|
||||||
"range3": 90,
|
|
||||||
"range4": 120,
|
|
||||||
"in_party_currency": 1,
|
"in_party_currency": 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -754,10 +712,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
"party_type": "Customer",
|
"party_type": "Customer",
|
||||||
"party": [self.customer1, self.customer3],
|
"party": [self.customer1, self.customer3],
|
||||||
"report_date": today(),
|
"report_date": today(),
|
||||||
"range1": 30,
|
"range": "30, 60, 90, 120",
|
||||||
"range2": 60,
|
|
||||||
"range3": 90,
|
|
||||||
"range4": 120,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
si1 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
|
si1 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
|
||||||
@@ -837,10 +792,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
filters = {
|
filters = {
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"report_date": today(),
|
"report_date": today(),
|
||||||
"range1": 30,
|
"range": "30, 60, 90, 120",
|
||||||
"range2": 60,
|
|
||||||
"range3": 90,
|
|
||||||
"range4": 120,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
report_ouput = execute(filters)[1]
|
report_ouput = execute(filters)[1]
|
||||||
@@ -903,10 +855,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
{
|
{
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"report_date": today(),
|
"report_date": today(),
|
||||||
"range1": 30,
|
"range": "30, 60, 90, 120",
|
||||||
"range2": 60,
|
|
||||||
"range3": 90,
|
|
||||||
"range4": 120,
|
|
||||||
"show_future_payments": True,
|
"show_future_payments": True,
|
||||||
"in_party_currency": False,
|
"in_party_currency": False,
|
||||||
}
|
}
|
||||||
@@ -965,10 +914,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
filters = {
|
filters = {
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"report_date": today(),
|
"report_date": today(),
|
||||||
"range1": 30,
|
"range": "30, 60, 90, 120",
|
||||||
"range2": 60,
|
|
||||||
"range3": 90,
|
|
||||||
"range4": 120,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# check invoice grand total and invoiced column's value for 3 payment terms
|
# check invoice grand total and invoiced column's value for 3 payment terms
|
||||||
@@ -991,10 +937,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
filters = {
|
filters = {
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"report_date": today(),
|
"report_date": today(),
|
||||||
"range1": 30,
|
"range": "30, 60, 90, 120",
|
||||||
"range2": 60,
|
|
||||||
"range3": 90,
|
|
||||||
"range4": 120,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# check invoice grand total and invoiced column's value for 3 payment terms
|
# check invoice grand total and invoiced column's value for 3 payment terms
|
||||||
|
|||||||
@@ -24,32 +24,10 @@ frappe.query_reports["Accounts Receivable Summary"] = {
|
|||||||
default: "Due Date",
|
default: "Due Date",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "range1",
|
fieldname: "range",
|
||||||
label: __("Ageing Range 1"),
|
label: __("Ageing Range"),
|
||||||
fieldtype: "Int",
|
fieldtype: "Data",
|
||||||
default: "30",
|
default: "30, 60, 90, 120",
|
||||||
reqd: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: "range2",
|
|
||||||
label: __("Ageing Range 2"),
|
|
||||||
fieldtype: "Int",
|
|
||||||
default: "60",
|
|
||||||
reqd: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: "range3",
|
|
||||||
label: __("Ageing Range 3"),
|
|
||||||
fieldtype: "Int",
|
|
||||||
default: "90",
|
|
||||||
reqd: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: "range4",
|
|
||||||
label: __("Ageing Range 4"),
|
|
||||||
fieldtype: "Int",
|
|
||||||
default: "120",
|
|
||||||
reqd: 1,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "finance_book",
|
fieldname: "finance_book",
|
||||||
|
|||||||
@@ -104,25 +104,23 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
|||||||
self.set_party_details(d)
|
self.set_party_details(d)
|
||||||
|
|
||||||
def init_party_total(self, row):
|
def init_party_total(self, row):
|
||||||
|
default_dict = {
|
||||||
|
"invoiced": 0.0,
|
||||||
|
"paid": 0.0,
|
||||||
|
"credit_note": 0.0,
|
||||||
|
"outstanding": 0.0,
|
||||||
|
"total_due": 0.0,
|
||||||
|
"future_amount": 0.0,
|
||||||
|
"sales_person": [],
|
||||||
|
"party_type": row.party_type,
|
||||||
|
}
|
||||||
|
for i in self.range_numbers:
|
||||||
|
range_key = f"range{i}"
|
||||||
|
default_dict[range_key] = 0.0
|
||||||
|
|
||||||
self.party_total.setdefault(
|
self.party_total.setdefault(
|
||||||
row.party,
|
row.party,
|
||||||
frappe._dict(
|
frappe._dict(default_dict),
|
||||||
{
|
|
||||||
"invoiced": 0.0,
|
|
||||||
"paid": 0.0,
|
|
||||||
"credit_note": 0.0,
|
|
||||||
"outstanding": 0.0,
|
|
||||||
"range1": 0.0,
|
|
||||||
"range2": 0.0,
|
|
||||||
"range3": 0.0,
|
|
||||||
"range4": 0.0,
|
|
||||||
"range5": 0.0,
|
|
||||||
"total_due": 0.0,
|
|
||||||
"future_amount": 0.0,
|
|
||||||
"sales_person": [],
|
|
||||||
"party_type": row.party_type,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_party_details(self, row):
|
def set_party_details(self, row):
|
||||||
@@ -173,6 +171,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
|||||||
self.add_column(_("Difference"), fieldname="diff")
|
self.add_column(_("Difference"), fieldname="diff")
|
||||||
|
|
||||||
self.setup_ageing_columns()
|
self.setup_ageing_columns()
|
||||||
|
self.add_column(label="Total Amount Due", fieldname="total_due")
|
||||||
|
|
||||||
if self.filters.show_future_payments:
|
if self.filters.show_future_payments:
|
||||||
self.add_column(label=_("Future Payment Amount"), fieldname="future_amount")
|
self.add_column(label=_("Future Payment Amount"), fieldname="future_amount")
|
||||||
@@ -206,27 +205,6 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
|||||||
label=_("Currency"), fieldname="currency", fieldtype="Link", options="Currency", width=80
|
label=_("Currency"), fieldname="currency", fieldtype="Link", options="Currency", width=80
|
||||||
)
|
)
|
||||||
|
|
||||||
def setup_ageing_columns(self):
|
|
||||||
for i, label in enumerate(
|
|
||||||
[
|
|
||||||
"0-{range1}".format(range1=self.filters["range1"]),
|
|
||||||
"{range1}-{range2}".format(
|
|
||||||
range1=cint(self.filters["range1"]) + 1, range2=self.filters["range2"]
|
|
||||||
),
|
|
||||||
"{range2}-{range3}".format(
|
|
||||||
range2=cint(self.filters["range2"]) + 1, range3=self.filters["range3"]
|
|
||||||
),
|
|
||||||
"{range3}-{range4}".format(
|
|
||||||
range3=cint(self.filters["range3"]) + 1, range4=self.filters["range4"]
|
|
||||||
),
|
|
||||||
"{range4}-{above}".format(range4=cint(self.filters["range4"]) + 1, above=_("Above")),
|
|
||||||
]
|
|
||||||
):
|
|
||||||
self.add_column(label=label, fieldname="range" + str(i + 1))
|
|
||||||
|
|
||||||
# Add column for total due amount
|
|
||||||
self.add_column(label="Total Amount Due", fieldname="total_due")
|
|
||||||
|
|
||||||
|
|
||||||
def get_gl_balance(report_date, company):
|
def get_gl_balance(report_date, company):
|
||||||
return frappe._dict(
|
return frappe._dict(
|
||||||
|
|||||||
@@ -27,10 +27,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
"company": self.company,
|
"company": self.company,
|
||||||
"customer": self.customer,
|
"customer": self.customer,
|
||||||
"posting_date": today(),
|
"posting_date": today(),
|
||||||
"range1": 30,
|
"range": "30, 60, 90, 120",
|
||||||
"range2": 60,
|
|
||||||
"range3": 90,
|
|
||||||
"range4": 120,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
si = create_sales_invoice(
|
si = create_sales_invoice(
|
||||||
@@ -121,10 +118,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
"company": self.company,
|
"company": self.company,
|
||||||
"customer": self.customer,
|
"customer": self.customer,
|
||||||
"posting_date": today(),
|
"posting_date": today(),
|
||||||
"range1": 30,
|
"range": "30, 60, 90, 120",
|
||||||
"range2": 60,
|
|
||||||
"range3": 90,
|
|
||||||
"range4": 120,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
si = create_sales_invoice(
|
si = create_sales_invoice(
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ DEFAULT_FILTERS = {
|
|||||||
REPORT_FILTER_TEST_CASES: list[tuple[ReportName, ReportFilters]] = [
|
REPORT_FILTER_TEST_CASES: list[tuple[ReportName, ReportFilters]] = [
|
||||||
("General Ledger", {"group_by": "Group by Voucher (Consolidated)"}),
|
("General Ledger", {"group_by": "Group by Voucher (Consolidated)"}),
|
||||||
("General Ledger", {"group_by": "Group by Voucher (Consolidated)", "include_dimensions": 1}),
|
("General Ledger", {"group_by": "Group by Voucher (Consolidated)", "include_dimensions": 1}),
|
||||||
("Accounts Payable", {"range1": 30, "range2": 60, "range3": 90, "range4": 120}),
|
("Accounts Payable", {"range": "30, 60, 90, 120"}),
|
||||||
("Accounts Receivable", {"range1": 30, "range2": 60, "range3": 90, "range4": 120}),
|
("Accounts Receivable", {"range": "30, 60, 90, 120"}),
|
||||||
("Consolidated Financial Statement", {"report": "Balance Sheet"}),
|
("Consolidated Financial Statement", {"report": "Balance Sheet"}),
|
||||||
("Consolidated Financial Statement", {"report": "Profit and Loss Statement"}),
|
("Consolidated Financial Statement", {"report": "Profit and Loss Statement"}),
|
||||||
("Consolidated Financial Statement", {"report": "Cash Flow"}),
|
("Consolidated Financial Statement", {"report": "Cash Flow"}),
|
||||||
|
|||||||
@@ -375,6 +375,7 @@ erpnext.patches.v15_0.update_warehouse_field_in_asset_repair_consumed_item_docty
|
|||||||
erpnext.patches.v15_0.update_asset_repair_field_in_stock_entry
|
erpnext.patches.v15_0.update_asset_repair_field_in_stock_entry
|
||||||
erpnext.patches.v15_0.update_total_number_of_booked_depreciations
|
erpnext.patches.v15_0.update_total_number_of_booked_depreciations
|
||||||
erpnext.patches.v15_0.do_not_use_batchwise_valuation
|
erpnext.patches.v15_0.do_not_use_batchwise_valuation
|
||||||
|
erpnext.patches.v14_0.update_reports_with_range
|
||||||
erpnext.patches.v15_0.drop_index_posting_datetime_from_sle
|
erpnext.patches.v15_0.drop_index_posting_datetime_from_sle
|
||||||
erpnext.patches.v15_0.add_disassembly_order_stock_entry_type #1
|
erpnext.patches.v15_0.add_disassembly_order_stock_entry_type #1
|
||||||
erpnext.patches.v15_0.set_standard_stock_entry_type
|
erpnext.patches.v15_0.set_standard_stock_entry_type
|
||||||
|
|||||||
36
erpnext/patches/v14_0/update_reports_with_range.py
Normal file
36
erpnext/patches/v14_0/update_reports_with_range.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
REFERENCE_REPORTS = [
|
||||||
|
"Accounts Receivable",
|
||||||
|
"Accounts Receivable Summary",
|
||||||
|
"Accounts Payable",
|
||||||
|
"Accounts Payable Summary",
|
||||||
|
"Stock Ageing",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
for report in REFERENCE_REPORTS:
|
||||||
|
update_reference_reports(report)
|
||||||
|
|
||||||
|
|
||||||
|
def update_reference_reports(reference_report):
|
||||||
|
reports = frappe.get_all(
|
||||||
|
"Report", filters={"reference_report": reference_report}, fields={"json", "name"}
|
||||||
|
)
|
||||||
|
|
||||||
|
for report in reports:
|
||||||
|
update_report_json(report)
|
||||||
|
update_reference_reports(report.name)
|
||||||
|
|
||||||
|
|
||||||
|
def update_report_json(report):
|
||||||
|
report_json = json.loads(report.json)
|
||||||
|
report_filter = report_json.get("filters")
|
||||||
|
|
||||||
|
keys_to_pop = [key for key in report_filter if key.startswith("range")]
|
||||||
|
report_filter["range"] = ", ".join(str(report_filter.pop(key)) for key in keys_to_pop)
|
||||||
|
|
||||||
|
frappe.db.set_value("Report", report.name, "json", json.dumps(report_json))
|
||||||
@@ -54,25 +54,10 @@ frappe.query_reports["Stock Ageing"] = {
|
|||||||
options: "Brand",
|
options: "Brand",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "range1",
|
fieldname: "range",
|
||||||
label: __("Ageing Range 1"),
|
label: __("Ageing Range"),
|
||||||
fieldtype: "Int",
|
fieldtype: "Data",
|
||||||
default: "30",
|
default: "30, 60, 90",
|
||||||
reqd: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: "range2",
|
|
||||||
label: __("Ageing Range 2"),
|
|
||||||
fieldtype: "Int",
|
|
||||||
default: "60",
|
|
||||||
reqd: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: "range3",
|
|
||||||
label: __("Ageing Range 3"),
|
|
||||||
fieldtype: "Int",
|
|
||||||
default: "90",
|
|
||||||
reqd: 1,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "show_warehouse_wise_stock",
|
fieldname: "show_warehouse_wise_stock",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ Filters = frappe._dict
|
|||||||
|
|
||||||
def execute(filters: Filters = None) -> tuple:
|
def execute(filters: Filters = None) -> tuple:
|
||||||
to_date = filters["to_date"]
|
to_date = filters["to_date"]
|
||||||
|
filters.ranges = [num.strip() for num in filters.range.split(",") if num.strip().isdigit()]
|
||||||
columns = get_columns(filters)
|
columns = get_columns(filters)
|
||||||
|
|
||||||
item_details = FIFOSlots(filters).generate()
|
item_details = FIFOSlots(filters).generate()
|
||||||
@@ -48,7 +49,7 @@ def format_report_data(filters: Filters, item_details: dict, to_date: str) -> li
|
|||||||
average_age = get_average_age(fifo_queue, to_date)
|
average_age = get_average_age(fifo_queue, to_date)
|
||||||
earliest_age = date_diff(to_date, fifo_queue[0][1])
|
earliest_age = date_diff(to_date, fifo_queue[0][1])
|
||||||
latest_age = date_diff(to_date, fifo_queue[-1][1])
|
latest_age = date_diff(to_date, fifo_queue[-1][1])
|
||||||
range1, range2, range3, above_range3 = get_range_age(filters, fifo_queue, to_date, item_dict)
|
range_values = get_range_age(filters, fifo_queue, to_date, item_dict)
|
||||||
|
|
||||||
row = [details.name, details.item_name, details.description, details.item_group, details.brand]
|
row = [details.name, details.item_name, details.description, details.item_group, details.brand]
|
||||||
|
|
||||||
@@ -59,10 +60,7 @@ def format_report_data(filters: Filters, item_details: dict, to_date: str) -> li
|
|||||||
[
|
[
|
||||||
flt(item_dict.get("total_qty"), precision),
|
flt(item_dict.get("total_qty"), precision),
|
||||||
average_age,
|
average_age,
|
||||||
range1,
|
*range_values,
|
||||||
range2,
|
|
||||||
range3,
|
|
||||||
above_range3,
|
|
||||||
earliest_age,
|
earliest_age,
|
||||||
latest_age,
|
latest_age,
|
||||||
details.stock_uom,
|
details.stock_uom,
|
||||||
@@ -89,25 +87,22 @@ def get_average_age(fifo_queue: list, to_date: str) -> float:
|
|||||||
return flt(age_qty / total_qty, 2) if total_qty else 0.0
|
return flt(age_qty / total_qty, 2) if total_qty else 0.0
|
||||||
|
|
||||||
|
|
||||||
def get_range_age(filters: Filters, fifo_queue: list, to_date: str, item_dict: dict) -> tuple:
|
def get_range_age(filters: Filters, fifo_queue: list, to_date: str, item_dict: dict) -> list:
|
||||||
precision = cint(frappe.db.get_single_value("System Settings", "float_precision", cache=True))
|
precision = cint(frappe.db.get_single_value("System Settings", "float_precision", cache=True))
|
||||||
|
range_values = [0.0] * (len(filters.ranges) + 1)
|
||||||
range1 = range2 = range3 = above_range3 = 0.0
|
|
||||||
|
|
||||||
for item in fifo_queue:
|
for item in fifo_queue:
|
||||||
age = flt(date_diff(to_date, item[1]))
|
age = flt(date_diff(to_date, item[1]))
|
||||||
qty = flt(item[0]) if not item_dict["has_serial_no"] else 1.0
|
qty = flt(item[0]) if not item_dict["has_serial_no"] else 1.0
|
||||||
|
|
||||||
if age <= flt(filters.range1):
|
for i, age_limit in enumerate(filters.ranges):
|
||||||
range1 = flt(range1 + qty, precision)
|
if age <= flt(age_limit):
|
||||||
elif age <= flt(filters.range2):
|
range_values[i] = flt(range_values[i] + qty, precision)
|
||||||
range2 = flt(range2 + qty, precision)
|
break
|
||||||
elif age <= flt(filters.range3):
|
|
||||||
range3 = flt(range3 + qty, precision)
|
|
||||||
else:
|
else:
|
||||||
above_range3 = flt(above_range3 + qty, precision)
|
range_values[-1] = flt(range_values[-1] + qty, precision)
|
||||||
|
|
||||||
return range1, range2, range3, above_range3
|
return range_values
|
||||||
|
|
||||||
|
|
||||||
def get_columns(filters: Filters) -> list[dict]:
|
def get_columns(filters: Filters) -> list[dict]:
|
||||||
@@ -193,12 +188,14 @@ def get_chart_data(data: list, filters: Filters) -> dict:
|
|||||||
|
|
||||||
|
|
||||||
def setup_ageing_columns(filters: Filters, range_columns: list):
|
def setup_ageing_columns(filters: Filters, range_columns: list):
|
||||||
ranges = [
|
prev_range_value = 0
|
||||||
f"0 - {filters['range1']}",
|
ranges = []
|
||||||
f"{cint(filters['range1']) + 1} - {cint(filters['range2'])}",
|
for range in filters.ranges:
|
||||||
f"{cint(filters['range2']) + 1} - {cint(filters['range3'])}",
|
ranges.append(f"{prev_range_value} - {range}")
|
||||||
_("{0} - Above").format(cint(filters["range3"]) + 1),
|
prev_range_value = cint(range) + 1
|
||||||
]
|
|
||||||
|
ranges.append(f"{prev_range_value} - Above")
|
||||||
|
|
||||||
for i, label in enumerate(ranges):
|
for i, label in enumerate(ranges):
|
||||||
fieldname = "range" + str(i + 1)
|
fieldname = "range" + str(i + 1)
|
||||||
add_column(range_columns, label=_("Age ({0})").format(label), fieldname=fieldname)
|
add_column(range_columns, label=_("Age ({0})").format(label), fieldname=fieldname)
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ from erpnext.stock.report.stock_ageing.stock_ageing import FIFOSlots, format_rep
|
|||||||
|
|
||||||
class TestStockAgeing(FrappeTestCase):
|
class TestStockAgeing(FrappeTestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
self.filters = frappe._dict(
|
self.filters = frappe._dict(company="_Test Company", to_date="2021-12-10", ranges=["30", "60", "90"])
|
||||||
company="_Test Company", to_date="2021-12-10", range1=30, range2=60, range3=90
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_normal_inward_outward_queue(self):
|
def test_normal_inward_outward_queue(self):
|
||||||
"Reference: Case 1 in stock_ageing_fifo_logic.md (same wh)"
|
"Reference: Case 1 in stock_ageing_fifo_logic.md (same wh)"
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ REPORT_FILTER_TEST_CASES: list[tuple[ReportName, ReportFilters]] = [
|
|||||||
("Item Prices", {"items": "Enabled Items only"}),
|
("Item Prices", {"items": "Enabled Items only"}),
|
||||||
("Delayed Item Report", {"based_on": "Sales Invoice"}),
|
("Delayed Item Report", {"based_on": "Sales Invoice"}),
|
||||||
("Delayed Item Report", {"based_on": "Delivery Note"}),
|
("Delayed Item Report", {"based_on": "Delivery Note"}),
|
||||||
("Stock Ageing", {"range1": 30, "range2": 60, "range3": 90, "_optional": True}),
|
("Stock Ageing", {"range": "30, 60, 90", "_optional": True}),
|
||||||
("Stock Ledger Invariant Check", {"warehouse": "_Test Warehouse - _TC", "item": "_Test Item"}),
|
("Stock Ledger Invariant Check", {"warehouse": "_Test Warehouse - _TC", "item": "_Test Item"}),
|
||||||
("FIFO Queue vs Qty After Transaction Comparison", {"warehouse": "_Test Warehouse - _TC"}),
|
("FIFO Queue vs Qty After Transaction Comparison", {"warehouse": "_Test Warehouse - _TC"}),
|
||||||
("FIFO Queue vs Qty After Transaction Comparison", {"item_group": "All Item Groups"}),
|
("FIFO Queue vs Qty After Transaction Comparison", {"item_group": "All Item Groups"}),
|
||||||
|
|||||||
Reference in New Issue
Block a user