Changeset - 5085d4d8ef79
[Not reviewed]
0 5 0
Brett Smith - 4 years ago 2020-06-23 18:47:03
brettcsmith@brettcsmith.org
accrual: Outgoing report sets RT CFs for outgoing payment.
5 files changed with 106 insertions and 11 deletions:
0 comments (0 inline, 0 general)
conservancy_beancount/reports/accrual.py
Show inline comments
...
 
@@ -73,2 +73,3 @@ import enum
 
import logging
 
import re
 
import sys
...
 
@@ -473,2 +474,17 @@ class BalanceReport(BaseReport):
 
class OutgoingReport(BaseReport):
 
    PAYMENT_METHODS = {
 
        'ach': 'ACH',
 
        'check': 'Check',
 
        'creditcard': 'Credit Card',
 
        'debitcard': 'Debit Card',
 
        'fxwire': 'International Wire',
 
        'paypal': 'PayPal',
 
        'uswire': 'Domestic Wire',
 
        'vendorportal': 'Vendor Portal',
 
    }
 
    PAYMENT_METHOD_RE = re.compile(
 
        rf'^([A-Z]{{3}})\s+({"|".join(PAYMENT_METHODS)})$',
 
        re.IGNORECASE,
 
    )
 

	
 
    def __init__(self, rt_wrapper: rtutil.RT, out_file: TextIO) -> None:
...
 
@@ -524,5 +540,8 @@ class OutgoingReport(BaseReport):
 
        raw_balance = -posts.balance()
 
        payment_amount = raw_balance.format('¤¤ #,##0.00')
 
        if raw_balance != posts.end_balance:
 
            payment_amount += f' ({balance_s})'
 
            balance_s = f'{raw_balance} ({balance_s})'
 

	
 
        payment_to = ticket.get('CF.{payment-to}') or requestor_name
 
        contract_links = list(posts.all_meta_links('contract'))
...
 
@@ -539,6 +558,5 @@ class OutgoingReport(BaseReport):
 
        yield f"REQUESTOR: {requestor}"
 
        yield f"PAYMENT TO: {payment_to}"
 
        yield f"TOTAL TO PAY: {balance_s}"
 
        yield f"AGREEMENT: {contract_s}"
 
        yield f"PAYMENT TO: {ticket.get('CF.{payment-to}') or requestor_name}"
 
        yield f"PAYMENT METHOD: {ticket.get('CF.{payment-method}', '')}"
 
        yield f"PROJECT: {', '.join(projects)}"
...
 
@@ -552,4 +570,52 @@ class OutgoingReport(BaseReport):
 
                txn = self.rt_wrapper.txn_with_urls(txn, '{}')
 
                # Suppress payment-method metadata from the report.
 
                txn.meta.pop('payment-method', None)
 
                for txn_post in txn.postings:
 
                    if txn_post.meta:
 
                        txn_post.meta.pop('payment-method', None)
 
                yield bc_printer.format_entry(txn)
 

	
 
        cf_targets = {
 
            'payment-amount': payment_amount,
 
            'payment-to': payment_to,
 
        }
 
        payment_methods = posts.meta_values('payment-method')
 
        payment_methods.discard(None)
 
        payment_method_count = len(payment_methods)
 
        if payment_method_count != 1:
 
            self.logger.warning(
 
                "cannot set payment-method for rt:%s: %s metadata values found",
 
                ticket_id, payment_method_count,
 
            )
 
        else:
 
            payment_method = payment_methods.pop()
 
            if isinstance(payment_method, str):
 
                match = self.PAYMENT_METHOD_RE.fullmatch(payment_method)
 
            else:
 
                match = None
 
            if match is None:
 
                self.logger.warning(
 
                    "cannot set payment-method for rt:%s: invalid value %r",
 
                    ticket_id, payment_method,
 
                )
 
            else:
 
                cf_targets['payment-method'] = '{} {}'.format(
 
                    match.group(1).upper(),
 
                    self.PAYMENT_METHODS[match.group(2).lower()],
 
                )
 

	
 
        cf_updates = {
 
            f'CF_{key}': value
 
            for key, value in cf_targets.items()
 
            if ticket.get(f'CF.{{{key}}}') != value
 
        }
 
        if cf_updates:
 
            try:
 
                ok = self.rt_client.edit_ticket(ticket_id, **cf_updates)
 
            except rt.RtError:
 
                self.logger.debug("RT exception on edit_ticket", exc_info=True)
 
                ok = False
 
            if not ok:
 
                self.logger.warning("failed to set custom fields for rt:%s", ticket_id)
 

	
 

	
setup.py
Show inline comments
...
 
@@ -7,3 +7,3 @@ setup(
 
    description="Plugin, library, and reports for reading Conservancy's books",
 
    version='1.3.1',
 
    version='1.4.0',
 
    author='Software Freedom Conservancy',
tests/books/accruals.beancount
Show inline comments
...
 
@@ -78,2 +78,3 @@
 
  project: "Conservancy"
 
  payment-method: "USD USWire"
 
  Liabilities:Payable:Accounts  -200 USD
...
 
@@ -86,2 +87,3 @@
 
  project: "Conservancy"
 
  payment-method: "USD Check"
 
  Liabilities:Payable:Accounts  -220 USD
...
 
@@ -104,2 +106,3 @@
 
  Liabilities:Payable:Accounts  -200.00 USD
 
  payment-method: "USD ACH"
 

	
...
 
@@ -136,2 +139,3 @@
 
  Liabilities:Payable:Accounts  -220.00 USD
 
  payment-method: "USD ACH"
 

	
...
 
@@ -144,2 +148,3 @@
 
  Liabilities:Payable:Accounts  -60.00 USD
 
  payment-method: "USD ACH"
 

	
...
 
@@ -160,2 +165,3 @@
 
  Liabilities:Payable:Accounts  -1,000 EUR {1.100 USD}
 
  payment-method: "eur fxwire"
 
  Expenses:FilingFees  1,000 EUR {1.100 USD}
tests/test_reports_accrual.py
Show inline comments
...
 
@@ -449,3 +449,4 @@ def test_balance_report(accrual_postings, invoice, expected, caplog):
 
def test_outgoing_report(accrual_postings, caplog):
 
    output = run_outgoing('rt:510', accrual_postings)
 
    rt_client = RTClient()
 
    output = run_outgoing('rt:510', accrual_postings, rt_client)
 
    rt_url = RTClient.DEFAULT_URL[:-9]
...
 
@@ -457,6 +458,5 @@ def test_outgoing_report(accrual_postings, caplog):
 
        r'^REQUESTOR: Mx\. 510 <mx510@example\.org>$',
 
        r'^PAYMENT TO: Hon\. Mx\. 510$',
 
        r'^TOTAL TO PAY: \$280\.00$',
 
        fr'^AGREEMENT: {contract_url}',
 
        r'^PAYMENT TO: Hon\. Mx\. 510$',
 
        r'^PAYMENT METHOD: payment method 510$',
 
        r'^BEANCOUNT ENTRIES:$',
...
 
@@ -471,2 +471,7 @@ def test_outgoing_report(accrual_postings, caplog):
 
    ])
 
    assert rt_client.edits == {'510': {
 
        'CF_payment-amount': 'USD 280.00',
 
        'CF_payment-method': 'USD ACH',
 
    }}
 
    assert 'payment-method:' not in output.getvalue()
 

	
...
 
@@ -480,3 +485,2 @@ def test_outgoing_report_custom_field_fallbacks(accrual_postings, caplog):
 
        r'^PAYMENT TO:\s*$',
 
        r'^PAYMENT METHOD:\s*$',
 
    ])
...
 
@@ -484,3 +488,4 @@ def test_outgoing_report_custom_field_fallbacks(accrual_postings, caplog):
 
def test_outgoing_report_fx_amounts(accrual_postings, caplog):
 
    output = run_outgoing('rt:520 rt:525', accrual_postings)
 
    rt_client = RTClient()
 
    output = run_outgoing('rt:520 rt:525', accrual_postings, rt_client)
 
    assert not caplog.records
...
 
@@ -491,6 +496,14 @@ def test_outgoing_report_fx_amounts(accrual_postings, caplog):
 
    ])
 
    assert rt_client.edits == {'520': {
 
        'CF_payment-amount': 'EUR 1,000.00 ($1,100.00)',
 
        'CF_payment-method': 'EUR International Wire',
 
    }}
 
    assert 'payment-method:' not in output.getvalue()
 

	
 
def test_outgoing_report_multi_invoice(accrual_postings, caplog):
 
    output = run_outgoing('rt:310', accrual_postings)
 
    assert not caplog.records
 
    rt_client = RTClient()
 
    output = run_outgoing('rt:310', accrual_postings, rt_client)
 
    log, = caplog.records
 
    assert log.levelname == 'WARNING'
 
    assert log.message.startswith('cannot set payment-method for rt:310: ')
 
    check_output(output, [
...
 
@@ -500,2 +513,6 @@ def test_outgoing_report_multi_invoice(accrual_postings, caplog):
 
    ])
 
    assert rt_client.edits == {'310': {
 
        'CF_payment-amount': 'USD 420.00',
 
    }}
 
    assert 'payment-method:' not in output.getvalue()
 

	
tests/testutil.py
Show inline comments
...
 
@@ -350,2 +350,3 @@ class RTClient:
 
        self.want_cfs = want_cfs
 
        self.edits = {}
 

	
...
 
@@ -404,3 +405,4 @@ class RTClient:
 
        if self.want_cfs:
 
            retval['CF.{payment-method}'] = f'payment method {ticket_id_s}'
 
            retval['CF.{payment-amount}'] = ''
 
            retval['CF.{payment-method}'] = ''
 
            retval['CF.{payment-to}'] = f'Hon. Mx. {ticket_id_s}'
...
 
@@ -408,2 +410,6 @@ class RTClient:
 

	
 
    def edit_ticket(self, ticket_id, **kwargs):
 
        self.edits.setdefault(str(ticket_id), {}).update(kwargs)
 
        return True
 

	
 
    def get_user(self, user_id):
0 comments (0 inline, 0 general)