diff --git a/conservancy_beancount/reports/accrual.py b/conservancy_beancount/reports/accrual.py index e81c7f2f2636a638f402029618657ac0d395ae32..b00ee283c45e6ea6c1fdf6d10511ceba125b1342 100644 --- a/conservancy_beancount/reports/accrual.py +++ b/conservancy_beancount/reports/accrual.py @@ -507,11 +507,12 @@ class OutgoingReport(BaseReport): self.rt_wrapper = rtutil.RT(rt_client) def _primary_rt_id(self, posts: AccrualPostings) -> rtutil.TicketAttachmentIds: - rt_ids = list(posts.first_meta_links('rt-id')) - rt_ids_count = len(rt_ids) - if rt_ids_count != 1: - raise ValueError(f"{rt_ids_count} rt-id links found") - parsed = rtutil.RT.parse(rt_ids.pop()) + rt_id = posts.rt_id + if rt_id is None: + raise ValueError("no rt-id links found") + elif isinstance(rt_id, Sentinel): + raise ValueError("multiple rt-id links found") + parsed = rtutil.RT.parse(rt_id) if parsed is None: raise ValueError("rt-id is not a valid RT reference") else: @@ -598,20 +599,10 @@ class ReportType(enum.Enum): except KeyError: raise ValueError(f"unknown report type {name!r}") from None - @classmethod - def default_for(cls, groups: PostGroups) -> 'ReportType': - if len(groups) == 1 and all( - group.accrual_type is AccrualAccount.PAYABLE - and not group.is_paid() - for group in groups.values() - ): - return cls.OUTGOING - else: - return cls.BALANCE - class ReturnFlag(enum.IntFlag): LOAD_ERRORS = 1 + # 2 was used in the past, it can probably be reclaimed. REPORT_ERRORS = 4 NOTHING_TO_REPORT = 8 @@ -687,6 +678,7 @@ def main(arglist: Optional[Sequence[str]]=None, config = configmod.Config() config.load_file() + returncode = 0 books_loader = config.books_loader() if books_loader is None: entries, load_errors, _ = books.Loader.load_none(config.config_file_path()) @@ -695,29 +687,37 @@ def main(arglist: Optional[Sequence[str]]=None, else: entries, load_errors, _ = books_loader.load_all(args.since) filters.remove_opening_balance_txn(entries) - - returncode = 0 - postings = filter_search(data.Posting.from_entries(entries), args.search_terms) - groups: PostGroups = dict(AccrualPostings.group_by_first_meta_link(postings, 'invoice')) for error in load_errors: bc_printer.print_error(error, file=stderr) returncode |= ReturnFlag.LOAD_ERRORS - if not groups: + + postings = list(filter_search( + data.Posting.from_entries(entries), args.search_terms, + )) + if not postings: logger.warning("no matching entries found to report") returncode |= ReturnFlag.NOTHING_TO_REPORT - - groups = { - key: posts - for source_posts in groups.values() - for key, posts in source_posts.make_consistent() - } + groups: PostGroups + if args.report_type is None or args.report_type is ReportType.OUTGOING: + groups = dict(AccrualPostings.group_by_first_meta_link(postings, 'rt-id')) + if (args.report_type is None + and len(groups) == 1 + and all(g.accrual_type is AccrualAccount.PAYABLE and not g.is_paid() + for g in groups.values()) + ): + args.report_type = ReportType.OUTGOING + if args.report_type is not ReportType.OUTGOING: + groups = { + key: group + for _, source in AccrualPostings.group_by_first_meta_link(postings, 'invoice') + for key, group in source.make_consistent() + } if args.report_type is not ReportType.AGING: groups = { key: posts for key, posts in groups.items() if not posts.is_paid() } or groups + del postings - if args.report_type is None: - args.report_type = ReportType.default_for(groups) report: Optional[BaseReport] = None output_path: Optional[Path] = None if args.report_type is ReportType.AGING: @@ -740,7 +740,7 @@ def main(arglist: Optional[Sequence[str]]=None, report = OutgoingReport(rt_client, out_file) else: out_file = cliutil.text_output(args.output_file, stdout) - report = args.report_type.value(out_file) + report = BalanceReport(out_file) if report is None: returncode |= ReturnFlag.REPORT_ERRORS