mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-25 07:54:46 +00:00
refactor: use DB to store tree and state
(cherry picked from commit 99fbd8ad18)
This commit is contained in:
@@ -13,8 +13,7 @@
|
|||||||
"column_break_iwny",
|
"column_break_iwny",
|
||||||
"algorithm",
|
"algorithm",
|
||||||
"section_break_zbty",
|
"section_break_zbty",
|
||||||
"current_node",
|
"current_node"
|
||||||
"tree"
|
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -23,12 +22,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "from_date",
|
"fieldname": "from_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Datetime",
|
||||||
"label": "From Date"
|
"label": "From Date"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "to_date",
|
"fieldname": "to_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Datetime",
|
||||||
"label": "To Date"
|
"label": "To Date"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -46,22 +45,18 @@
|
|||||||
"fieldname": "section_break_zbty",
|
"fieldname": "section_break_zbty",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "tree",
|
|
||||||
"fieldtype": "JSON",
|
|
||||||
"label": "Tree"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "current_node",
|
"fieldname": "current_node",
|
||||||
"fieldtype": "JSON",
|
"fieldtype": "Link",
|
||||||
"label": "Current Node"
|
"label": "Current Node",
|
||||||
|
"options": "Nodes"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"hide_toolbar": 1,
|
"hide_toolbar": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-09-25 21:15:47.905386",
|
"modified": "2023-09-26 12:09:23.649156",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Bisect Accounting Statements",
|
"name": "Bisect Accounting Statements",
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from math import floor
|
from math import floor
|
||||||
|
|
||||||
@@ -11,184 +10,10 @@ from dateutil.relativedelta import relativedelta
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate
|
||||||
from frappe.utils.data import DATETIME_FORMAT, guess_date_format
|
from frappe.utils.data import 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
|
|
||||||
|
|
||||||
|
|
||||||
class BisectAccountingStatements(Document):
|
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):
|
def validate(self):
|
||||||
self.validate_dates()
|
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()
|
@frappe.whitelist()
|
||||||
def build_tree(self):
|
def build_tree(self):
|
||||||
self.tree_instance = BTree()
|
frappe.db.delete("Nodes")
|
||||||
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)
|
|
||||||
|
|
||||||
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())
|
if self.algorithm == "BFS":
|
||||||
self.current_node = json.dumps(self.tree_instance.btree[0].as_dict())
|
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()
|
@frappe.whitelist()
|
||||||
def bisect_left(self):
|
def bisect_left(self):
|
||||||
if self.tree_instance.current_node is not None:
|
if self.current_node is not None:
|
||||||
if self.tree_instance.current_node.left_child is not None:
|
cur_node = frappe.get_doc("Nodes", self.current_node)
|
||||||
self.current_node = self.tree_instance.btree[self.tree_instance.current_node.left_child]
|
if cur_node.left_child is not None:
|
||||||
self.current_node = json.dumps(self.current_node.as_dict())
|
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()
|
self.save()
|
||||||
else:
|
else:
|
||||||
frappe.msgprint("No more children on Left")
|
frappe.msgprint("No more children on Left")
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def bisect_right(self):
|
def bisect_right(self):
|
||||||
if self.tree_instance.current_node is not None:
|
if self.current_node is not None:
|
||||||
if self.tree_instance.current_node.right_child is not None:
|
cur_node = frappe.get_doc("Nodes", self.current_node)
|
||||||
self.current_node = self.tree_instance.btree[self.tree_instance.current_node.right_child]
|
if cur_node.right_child is not None:
|
||||||
self.current_node = json.dumps(self.current_node.as_dict())
|
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()
|
self.save()
|
||||||
else:
|
else:
|
||||||
frappe.msgprint("No more children on Right")
|
frappe.msgprint("No more children on Right")
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def move_up(self):
|
def move_up(self):
|
||||||
if self.tree_instance.current_node is not None:
|
if self.current_node is not None:
|
||||||
if self.tree_instance.current_node.parent is not None:
|
cur_node = frappe.get_doc("Nodes", self.current_node)
|
||||||
self.current_node = self.tree_instance.btree[self.tree_instance.current_node.parent]
|
if cur_node.root is not None:
|
||||||
self.current_node = json.dumps(self.current_node.as_dict())
|
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()
|
self.save()
|
||||||
else:
|
else:
|
||||||
frappe.msgprint("Reached Root")
|
frappe.msgprint("Reached Root")
|
||||||
|
|||||||
Reference in New Issue
Block a user