+from collections import defaultdict
from pathlib import Path
from typing import Dict, List, Any, Optional
from io import StringIO
import enum
-from budget_sync.budget_graph import BudgetGraph, Node, Payment, PayeeState
-from budget_sync.config import Person, Milestone, Config
+from budget_sync.budget_graph import BudgetGraph, Node, Payment, PayeeState, PaymentSummary
+from budget_sync.config import Person, Milestone
+from budget_sync.money import Money
+from budget_sync.ordered_set import OrderedSet
from budget_sync.util import BugStatus
self.last_headers, headers)
assert headers == self.last_headers
- def write_node(self,
- headers: List[str],
- node: Node,
- payment: Optional[Payment]):
+ def write_node_header(self,
+ headers: List[str],
+ node: Optional[Node]):
self.write_headers(headers)
+ if node is None:
+ print("* None", file=self.buffer)
+ return
summary = markdown_escape(node.bug.summary)
print(f"* [Bug #{node.bug.id}]({node.bug_url}):\n {summary}",
file=self.buffer)
+
+ def write_node(self,
+ headers: List[str],
+ node: Node,
+ payment: Optional[Payment]):
+ self.write_node_header(headers, node)
if payment is not None:
if node.fixed_budget_excluding_subtasks \
!= node.budget_excluding_subtasks:
def _markdown_for_person(person: Person,
payments_dict: Dict[Milestone, List[Payment]],
- assigned_nodes: List[Node]) -> str:
+ assigned_nodes: List[Node],
+ nodes_subset: Optional[OrderedSet[Node]] = None,
+ ) -> str:
+ def node_included(node: Node) -> bool:
+ return nodes_subset is None or node in nodes_subset
writer = MarkdownWriter()
print(f"<!-- autogenerated by budget-sync -->", file=writer.buffer)
writer.write_headers([f"\n# {person.full_name} ({person.identifier})\n"])
def write_display_status_chunk(display_status: DisplayStatus):
display_status_header = f"\n## {display_status.value}\n"
for node in displayed_nodes_dict[display_status]:
+ if not node_included(node):
+ continue
if display_status == DisplayStatus.Completed:
payment_found = False
for payment in node.payments.values():
for payee_state in PayeeState:
if payee_state == PayeeState.NotYetSubmitted:
- display_status_header = f"## Payment not yet submitted"
+ display_status_header = "\n## Payment not yet submitted\n"
+ subtotals_header = ("\n#### MoU Milestone subtotals for not "
+ "yet submitted payments\n")
elif payee_state == PayeeState.Submitted:
- display_status_header = f"## Submitted to NLNet but not yet paid"
+ display_status_header = ("\n## Submitted to NLNet but "
+ "not yet paid\n")
+ subtotals_header = ("\n#### MoU Milestone subtotals for "
+ "submitted but not yet paid payments\n")
else:
assert payee_state == PayeeState.Paid
- display_status_header = f"## Paid by NLNet"
- display_status_header = "\n%s\n" % display_status_header
+ display_status_header = "\n## Paid by NLNet\n"
+ subtotals_header = ("\n#### MoU Milestone subtotals for paid "
+ "payments\n")
for milestone, payments_list in payments_dict.items():
milestone_header = f"\n### {milestone.identifier}\n"
+ mou_subtotals: Dict[Optional[Node], Money] = defaultdict(Money)
+ headers = [status_tracking_header,
+ display_status_header,
+ milestone_header]
for payment in payments_list:
- if payment.state == payee_state:
- writer.write_node(headers=[status_tracking_header,
- display_status_header,
- milestone_header],
+ node = payment.node
+ if payment.state == payee_state and node_included(node):
+ mou_subtotals[node.closest_bug_in_mou] += payment.amount
+ writer.write_node(headers=headers,
node=payment.node, payment=payment)
+ headers.append(subtotals_header)
+ for node, subtotal in mou_subtotals.items():
+ writer.write_node_header(headers, node)
+ if node is None:
+ budget = ""
+ elif node.fixed_budget_including_subtasks \
+ != node.budget_including_subtasks:
+ budget = (" out of total including subtasks of "
+ f"€{node.fixed_budget_including_subtasks}"
+ " (budget is fixed from amount appearing in "
+ "bug report, which is "
+ f"€{node.budget_including_subtasks})")
+ else:
+ budget = (" out of total including subtasks of "
+ f"€{node.fixed_budget_including_subtasks}")
+ print(f" * subtotal €{subtotal}{budget}",
+ file=writer.buffer)
# write_display_status_chunk(DisplayStatus.NotYetStarted)
def write_budget_markdown(budget_graph: BudgetGraph,
- output_dir: Path):
+ output_dir: Path,
+ nodes_subset: Optional[OrderedSet[Node]] = None):
output_dir.mkdir(parents=True, exist_ok=True)
for person, payments_dict in budget_graph.payments.items():
markdown = _markdown_for_person(person,
payments_dict,
- budget_graph.assigned_nodes[person])
+ budget_graph.assigned_nodes[person],
+ nodes_subset)
output_file = output_dir.joinpath(person.output_markdown_file)
output_file.write_text(markdown, encoding="utf-8")
+
+
+def markdown_for_person(budget_graph: BudgetGraph, person: Person,
+ nodes_subset: Optional[OrderedSet[Node]] = None,
+ ) -> str:
+ return _markdown_for_person(person, budget_graph.payments[person],
+ budget_graph.assigned_nodes[person],
+ nodes_subset)