balance_sheet: Add functional expenses report.
This commit is contained in:
		
							parent
							
								
									29d4325c7a
								
							
						
					
					
						commit
						eaaf8fe98c
					
				
					 2 changed files with 69 additions and 3 deletions
				
			
		| 
						 | 
					@ -218,6 +218,7 @@ class Report(core.BaseODS[Sequence[None], None]):
 | 
				
			||||||
    def write_all(self) -> None:
 | 
					    def write_all(self) -> None:
 | 
				
			||||||
        self.write_financial_position()
 | 
					        self.write_financial_position()
 | 
				
			||||||
        self.write_activities()
 | 
					        self.write_activities()
 | 
				
			||||||
 | 
					        self.write_functional_expenses()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def walk_classifications(self, cseq: Iterable[data.Account]) \
 | 
					    def walk_classifications(self, cseq: Iterable[data.Account]) \
 | 
				
			||||||
        -> Iterator[Tuple[str, Optional[data.Account]]]:
 | 
					        -> Iterator[Tuple[str, Optional[data.Account]]]:
 | 
				
			||||||
| 
						 | 
					@ -225,9 +226,9 @@ class Report(core.BaseODS[Sequence[None], None]):
 | 
				
			||||||
        for classification in cseq:
 | 
					        for classification in cseq:
 | 
				
			||||||
            parts = classification.split(':')
 | 
					            parts = classification.split(':')
 | 
				
			||||||
            tail = parts.pop()
 | 
					            tail = parts.pop()
 | 
				
			||||||
            tabs = '\t' * len(parts)
 | 
					            tabs = ' ' * 4 * len(parts)
 | 
				
			||||||
            if parts != last_prefix:
 | 
					            if parts != last_prefix:
 | 
				
			||||||
                yield f'{tabs[1:]}{parts[-1]}', None
 | 
					                yield f'{tabs[4:]}{parts[-1]}', None
 | 
				
			||||||
                last_prefix = parts
 | 
					                last_prefix = parts
 | 
				
			||||||
            yield f'{tabs}{tail}', classification
 | 
					            yield f'{tabs}{tail}', classification
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -509,6 +510,71 @@ class Report(core.BaseODS[Sequence[None], None]):
 | 
				
			||||||
              for beg_bal, tot_bal in zip(beginnings, totals)),
 | 
					              for beg_bal, tot_bal in zip(beginnings, totals)),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def write_functional_expenses(self) -> None:
 | 
				
			||||||
 | 
					        self.use_sheet("Functional Expenses")
 | 
				
			||||||
 | 
					        bal_kwargs: Sequence[Dict[str, Any]] = [
 | 
				
			||||||
 | 
					            {'period': Period.PERIOD, 'post_type': 'program'},
 | 
				
			||||||
 | 
					            {'period': Period.PERIOD, 'post_type': 'management'},
 | 
				
			||||||
 | 
					            {'period': Period.PERIOD, 'post_type': 'fundraising'},
 | 
				
			||||||
 | 
					            {'period': Period.PERIOD},
 | 
				
			||||||
 | 
					            {'period': Period.PRIOR},
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        col_count = len(bal_kwargs) + 1
 | 
				
			||||||
 | 
					        for index in range(col_count):
 | 
				
			||||||
 | 
					            col_style = self.column_style(1.5 if index else 3)
 | 
				
			||||||
 | 
					            self.sheet.addElement(odf.table.TableColumn(stylename=col_style))
 | 
				
			||||||
 | 
					        self.add_row(
 | 
				
			||||||
 | 
					            self.multiline_cell([
 | 
				
			||||||
 | 
					                "DRAFT Statement of Functional Expenses",
 | 
				
			||||||
 | 
					                self.period_name,
 | 
				
			||||||
 | 
					            ], numbercolumnsspanned=col_count, stylename=self.style_header)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self.add_row()
 | 
				
			||||||
 | 
					        self.add_row(
 | 
				
			||||||
 | 
					            odf.table.TableCell(),
 | 
				
			||||||
 | 
					            self.multiline_cell(["Program", "Services"],
 | 
				
			||||||
 | 
					                                stylename=self.style_huline),
 | 
				
			||||||
 | 
					            self.multiline_cell(["Management and", "Administrative"],
 | 
				
			||||||
 | 
					                                stylename=self.style_huline),
 | 
				
			||||||
 | 
					            self.multiline_cell(["Fundraising"],
 | 
				
			||||||
 | 
					                                stylename=self.style_huline),
 | 
				
			||||||
 | 
					            self.multiline_cell(["Total Year Ended", self.period_name],
 | 
				
			||||||
 | 
					                                stylename=self.style_huline),
 | 
				
			||||||
 | 
					            self.multiline_cell(["Total Year Ended", self.opening_name],
 | 
				
			||||||
 | 
					                                stylename=self.style_huline),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self.add_row()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        totals = [core.MutableBalance() for _ in bal_kwargs]
 | 
				
			||||||
 | 
					        for text, classification in self.walk_classifications_by_account('Expenses'):
 | 
				
			||||||
 | 
					            text_cell = self.string_cell(text)
 | 
				
			||||||
 | 
					            if classification is None:
 | 
				
			||||||
 | 
					                if not text[0].isspace():
 | 
				
			||||||
 | 
					                    self.add_row()
 | 
				
			||||||
 | 
					                self.add_row(text_cell)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                balances = [
 | 
				
			||||||
 | 
					                    self.balances.total(classification=classification, **kwargs)
 | 
				
			||||||
 | 
					                    for kwargs in bal_kwargs
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
 | 
					                self.add_row(
 | 
				
			||||||
 | 
					                    text_cell,
 | 
				
			||||||
 | 
					                    *(self.balance_cell(bal) for bal in balances),
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                break_bal = sum(balances[:3], core.MutableBalance())
 | 
				
			||||||
 | 
					                if not (break_bal - balances[3]).clean_copy(1).is_zero():
 | 
				
			||||||
 | 
					                    logger.warning(
 | 
				
			||||||
 | 
					                        "Functional expenses breakdown does not match total on row %s",
 | 
				
			||||||
 | 
					                        len(self.sheet.childNodes) - col_count,
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                for total, bal in zip(totals, balances):
 | 
				
			||||||
 | 
					                    total += bal
 | 
				
			||||||
 | 
					        self.add_row()
 | 
				
			||||||
 | 
					        self.add_row(
 | 
				
			||||||
 | 
					            self.string_cell("Total Expenses"),
 | 
				
			||||||
 | 
					            *(self.balance_cell(tot_bal, stylename=self.style_bottomline)
 | 
				
			||||||
 | 
					              for tot_bal in totals),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def parse_arguments(arglist: Optional[Sequence[str]]=None) -> argparse.Namespace:
 | 
					def parse_arguments(arglist: Optional[Sequence[str]]=None) -> argparse.Namespace:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								setup.py
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.py
									
										
									
									
									
								
							| 
						 | 
					@ -5,7 +5,7 @@ from setuptools import setup
 | 
				
			||||||
setup(
 | 
					setup(
 | 
				
			||||||
    name='conservancy_beancount',
 | 
					    name='conservancy_beancount',
 | 
				
			||||||
    description="Plugin, library, and reports for reading Conservancy's books",
 | 
					    description="Plugin, library, and reports for reading Conservancy's books",
 | 
				
			||||||
    version='1.7.3',
 | 
					    version='1.7.4',
 | 
				
			||||||
    author='Software Freedom Conservancy',
 | 
					    author='Software Freedom Conservancy',
 | 
				
			||||||
    author_email='info@sfconservancy.org',
 | 
					    author_email='info@sfconservancy.org',
 | 
				
			||||||
    license='GNU AGPLv3+',
 | 
					    license='GNU AGPLv3+',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue