From 99fbd8ad186c989faa86863d5902a3b4d2015505 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 26 Sep 2023 12:28:38 +0530 Subject: [PATCH] refactor: use DB to store tree and state --- .../bisect_accounting_statements.json | 19 +- .../bisect_accounting_statements.py | 304 +++++++----------- 2 files changed, 115 insertions(+), 208 deletions(-) diff --git a/erpnext/accounts/doctype/bisect_accounting_statements/bisect_accounting_statements.json b/erpnext/accounts/doctype/bisect_accounting_statements/bisect_accounting_statements.json index c5715fe2ea..0de820a80f 100644 --- a/erpnext/accounts/doctype/bisect_accounting_statements/bisect_accounting_statements.json +++ b/erpnext/accounts/doctype/bisect_accounting_statements/bisect_accounting_statements.json @@ -13,8 +13,7 @@ "column_break_iwny", "algorithm", "section_break_zbty", - "current_node", - "tree" + "current_node" ], "fields": [ { @@ -23,12 +22,12 @@ }, { "fieldname": "from_date", - "fieldtype": "Date", + "fieldtype": "Datetime", "label": "From Date" }, { "fieldname": "to_date", - "fieldtype": "Date", + "fieldtype": "Datetime", "label": "To Date" }, { @@ -46,22 +45,18 @@ "fieldname": "section_break_zbty", "fieldtype": "Section Break" }, - { - "fieldname": "tree", - "fieldtype": "JSON", - "label": "Tree" - }, { "fieldname": "current_node", - "fieldtype": "JSON", - "label": "Current Node" + "fieldtype": "Link", + "label": "Current Node", + "options": "Nodes" } ], "hide_toolbar": 1, "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-09-25 21:15:47.905386", + "modified": "2023-09-26 12:09:23.649156", "modified_by": "Administrator", "module": "Accounts", "name": "Bisect Accounting Statements", diff --git a/erpnext/accounts/doctype/bisect_accounting_statements/bisect_accounting_statements.py b/erpnext/accounts/doctype/bisect_accounting_statements/bisect_accounting_statements.py index 53319b3c58..8730772e29 100644 --- a/erpnext/accounts/doctype/bisect_accounting_statements/bisect_accounting_statements.py +++ b/erpnext/accounts/doctype/bisect_accounting_statements/bisect_accounting_statements.py @@ -2,7 +2,6 @@ # For license information, please see license.txt import datetime -import json from collections import deque from math import floor @@ -11,184 +10,10 @@ from dateutil.relativedelta import relativedelta from frappe import _ from frappe.model.document import Document from frappe.utils import getdate -from frappe.utils.data import DATETIME_FORMAT, guess_date_format - - -class Node(object): - def __init__( - self, - parent: int = None, - period: (datetime, datetime) = None, - left: int = None, - right: int = None, - ): - self.parent = parent - self.left_child = left - self.right_child = right - - self.period = period - self.difference = 0.0 - self.profit_and_loss_summary = 0.0 - self.balance_sheet_summary = 0.0 - - def as_dict(self): - return dict( - parent=self.parent, - left_child=self.left_child, - right_child=self.right_child, - period=(self.period[0].strftime(DATETIME_FORMAT), self.period[1].strftime(DATETIME_FORMAT)), - difference=self.difference, - profit_and_loss_summary=self.profit_and_loss_summary, - balance_sheet_summary=self.balance_sheet_summary, - ) - - def __repr__(self): - return f"Node (parent: {self.parent}, left_child: {self.left_child}, right_child: {self.right_child}, period: {self.period})" - - -class BTree(object): - def __init__(self): - self.btree = [] - self.current_node = None - - def as_list(self): - lst = [] - for x in self.btree: - lst.append(x.as_dict()) - return lst - - def bfs(self, from_date: datetime, to_date: datetime): - node = frappe.new_doc("Nodes") - node.period_from_date = from_date - node.period_to_date = to_date - node.root = None - node.insert() - - period_list = deque([node]) - - while period_list: - cur_node = period_list.popleft() - - print(cur_node.as_dict()) - delta = cur_node.period_to_date - cur_node.period_from_date - if delta.days == 0: - continue - else: - cur_floor = floor(delta.days / 2) - left = ( - cur_node.period_from_date, - (cur_node.period_from_date + relativedelta(days=+cur_floor)), - ) - left_node = frappe.get_doc( - { - "doctype": "Nodes", - "period_from_date": cur_node.period_from_date, - "period_to_date": left, - "root": cur_node.name, - } - ).insert() - cur_node.left_child = left_node.name - period_list.append(left_node) - - right = ( - (cur_node.period_from_date + relativedelta(days=+(cur_floor + 1))), - cur_node.period_to_date, - ) - right_node = frappe.get_doc( - { - "doctype": "Nodes", - "period_from_date": right, - "period_to_date": cur_node.period_to_date, - "root": cur_node.name, - } - ).insert() - cur_node.right_child = right_node - period_list.append(right_node) - - def dfs(self, from_date: datetime, to_date: datetime): - root_node = Node(parent=None, period=(getdate(from_date), getdate(to_date))) - root_node.parent = None - - # add root node to tree - self.btree.append(root_node) - cur_node = root_node - period_list = [root_node] - - while period_list: - cur_node = period_list.pop() - cur_node_index = len(self.btree) - 1 - - delta = cur_node.period[1] - cur_node.period[0] - if delta.days == 0: - continue - else: - cur_floor = floor(delta.days / 2) - left = (cur_node.period[0], (cur_node.period[0] + relativedelta(days=+cur_floor))) - left_node = Node(parent=cur_node_index, period=left) - self.btree.append(left_node) - cur_node.left_child = len(self.btree) - 1 - period_list.append(left_node) - - right = ((cur_node.period[0] + relativedelta(days=+(cur_floor + 1))), cur_node.period[1]) - right_node = Node(parent=cur_node_index, period=right) - self.btree.append(right_node) - cur_node.right_child = len(self.btree) - 1 - period_list.append(right_node) - - def load_tree(self, tree: list, current_node: dict): - self.btree = [] - tree = json.loads(tree) - for x in tree: - x = frappe._dict(x) - n = Node(x.parent, None, x.left_child, x.right_child) - date_format = guess_date_format(x.period[0]) - n.period = ( - datetime.datetime.strptime(x.period[0], date_format), - datetime.datetime.strptime(x.period[1], date_format), - ) - n.difference = x.difference - x.profit_and_loss_summary = x.profit_and_loss_summary - x.balance_sheet_summary = x.balance_sheet_summary - self.btree.append(n) - - current_node = frappe._dict(json.loads(current_node)) - n = Node( - current_node.parent, current_node.period, current_node.left_child, current_node.right_child - ) - n.period = current_node.period - n.difference = current_node.difference - n.profit_and_loss_summary = current_node.profit_and_loss_summary - n.balance_sheet_summary = current_node.balance_sheet_summary - self.current_node = n - - def build_tree(self, from_date: datetime, to_date: datetime, alogrithm: str): - frappe.db.delete("Nodes") - if alogrithm == "BFS": - self.bfs(from_date, to_date) - - if alogrithm == "DFS": - self.dfs(from_date, to_date) - - # set root as current node - self.current_node = self.btree[0] - - def bisec_left(self): - pass - - def bisect_right(self): - pass - - def move_up(self): - pass +from frappe.utils.data import guess_date_format class BisectAccountingStatements(Document): - def __init__(self, *args, **kwargs): - super(BisectAccountingStatements, self).__init__(*args, **kwargs) - if self.tree and self.current_node: - self.tree_instance = BTree() - self.tree_instance.load_tree(self.tree, self.current_node) - def validate(self): self.validate_dates() @@ -200,45 +25,132 @@ class BisectAccountingStatements(Document): ) ) + def bfs(self, from_date: datetime, to_date: datetime): + # Make Root node + node = frappe.new_doc("Nodes") + node.root = None + node.period_from_date = from_date + node.period_to_date = to_date + node.insert() + + period_queue = deque([node]) + while period_queue: + cur_node = period_queue.popleft() + delta = cur_node.period_to_date - cur_node.period_from_date + if delta.days == 0: + continue + else: + cur_floor = floor(delta.days / 2) + next_to_date = cur_node.period_from_date + relativedelta(days=+cur_floor) + left_node = frappe.new_doc("Nodes") + left_node.period_from_date = cur_node.period_from_date + left_node.period_to_date = next_to_date + left_node.root = cur_node.name + left_node.insert() + cur_node.left_child = left_node.name + period_queue.append(left_node) + + next_from_date = cur_node.period_from_date + relativedelta(days=+(cur_floor + 1)) + right_node = frappe.new_doc("Nodes") + right_node.period_from_date = next_from_date + right_node.period_to_date = cur_node.period_to_date + right_node.root = cur_node.name + right_node.insert() + cur_node.right_child = right_node.name + period_queue.append(right_node) + + cur_node.save() + + def dfs(self, from_date: datetime, to_date: datetime): + # Make Root node + node = frappe.new_doc("Nodes") + node.root = None + node.period_from_date = from_date + node.period_to_date = to_date + node.insert() + + period_stack = [node] + while period_stack: + cur_node = period_stack.pop() + delta = cur_node.period_to_date - cur_node.period_from_date + if delta.days == 0: + continue + else: + cur_floor = floor(delta.days / 2) + next_to_date = cur_node.period_from_date + relativedelta(days=+cur_floor) + left_node = frappe.new_doc("Nodes") + left_node.period_from_date = cur_node.period_from_date + left_node.period_to_date = next_to_date + left_node.root = cur_node.name + left_node.insert() + cur_node.left_child = left_node.name + period_stack.append(left_node) + + next_from_date = cur_node.period_from_date + relativedelta(days=+(cur_floor + 1)) + right_node = frappe.new_doc("Nodes") + right_node.period_from_date = next_from_date + right_node.period_to_date = cur_node.period_to_date + right_node.root = cur_node.name + right_node.insert() + cur_node.right_child = right_node.name + period_stack.append(right_node) + + cur_node.save() + @frappe.whitelist() def build_tree(self): - self.tree_instance = BTree() - self.tree_instance.build_tree(self.from_date, self.to_date, self.algorithm) - print("printing tree") - for x in self.tree_instance.btree: - print(x) + frappe.db.delete("Nodes") - print("Root", self.tree_instance.current_node) + # Convert str to datetime format + dt_format = guess_date_format(self.from_date) + from_date = datetime.datetime.strptime(self.from_date, dt_format) + to_date = datetime.datetime.strptime(self.to_date, dt_format) - self.tree = json.dumps(self.tree_instance.as_list()) - self.current_node = json.dumps(self.tree_instance.btree[0].as_dict()) + if self.algorithm == "BFS": + self.bfs(from_date, to_date) + + if self.algorithm == "DFS": + self.dfs(from_date, to_date) + + # set root as current node + root = frappe.db.get_all("Nodes", filters={"root": ["is", "not set"]})[0] + frappe.db.set_single_value("Bisect Accounting Statements", "current_node", root.name) @frappe.whitelist() def bisect_left(self): - if self.tree_instance.current_node is not None: - if self.tree_instance.current_node.left_child is not None: - self.current_node = self.tree_instance.btree[self.tree_instance.current_node.left_child] - self.current_node = json.dumps(self.current_node.as_dict()) + if self.current_node is not None: + cur_node = frappe.get_doc("Nodes", self.current_node) + if cur_node.left_child is not None: + lft_node = frappe.get_doc("Nodes", cur_node.left_child) + self.current_node = cur_node.left_child + self.from_date = lft_node.period_from_date + self.to_date = lft_node.period_to_date self.save() else: frappe.msgprint("No more children on Left") @frappe.whitelist() def bisect_right(self): - if self.tree_instance.current_node is not None: - if self.tree_instance.current_node.right_child is not None: - self.current_node = self.tree_instance.btree[self.tree_instance.current_node.right_child] - self.current_node = json.dumps(self.current_node.as_dict()) + if self.current_node is not None: + cur_node = frappe.get_doc("Nodes", self.current_node) + if cur_node.right_child is not None: + rgt_node = frappe.get_doc("Nodes", cur_node.right_child) + self.current_node = cur_node.right_child + self.from_date = rgt_node.period_from_date + self.to_date = rgt_node.period_to_date self.save() else: frappe.msgprint("No more children on Right") @frappe.whitelist() def move_up(self): - if self.tree_instance.current_node is not None: - if self.tree_instance.current_node.parent is not None: - self.current_node = self.tree_instance.btree[self.tree_instance.current_node.parent] - self.current_node = json.dumps(self.current_node.as_dict()) + if self.current_node is not None: + cur_node = frappe.get_doc("Nodes", self.current_node) + if cur_node.root is not None: + root = frappe.get_doc("Nodes", cur_node.root) + self.current_node = cur_node.root + self.from_date = root.period_from_date + self.to_date = root.period_to_date self.save() else: frappe.msgprint("Reached Root")