Changeset - a135e71a4a31
[Not reviewed]
0 3 0
Brett Smith - 6 years ago 2018-09-28 12:31:24
brettcsmith@brettcsmith.org
nbpy2017: Handle complimentary tickets.

The conference is offering these to people who proposed talks this year.
3 files changed with 23 insertions and 20 deletions:
0 comments (0 inline, 0 general)
import2ledger/importers/nbpy2017.py
Show inline comments
...
 
@@ -37,104 +37,107 @@ class Invoice2017:
 
    def __init__(self, source_file):
 
        soup = bs4.BeautifulSoup(source_file, 'html5lib')
 
        for table in soup.find_all('table'):
 
            rows_text = self._table_row_text(table)
 
            first_row_text = next(rows_text, [])
 
            if first_row_text[:1] == ['Number']:
 
                handler = self._read_invoice_header
 
            elif first_row_text == ['Description', 'Quantity', 'Price/Unit', 'Total']:
 
                handler = self._read_invoice_items
 
            elif first_row_text == ['Payment time', 'Reference', 'Amount']:
 
                handler = self._read_invoice_activity
 
            else:
 
                continue
 
            handler(table, first_row_text, rows_text)
 
        self.base_data = {
 
            'amount': self.amount,
 
            'currency': self.CURRENCY,
 
            'invoice_date': self.invoice_date,
 
            'invoice_id': self.invoice_id,
 
            'payee': self.payee,
 
            'shirt_rate': self.shirt_rate,
 
            'shirts_sold': self.shirts_sold,
 
            'ticket_rate': self.ticket_rate,
 
            'tickets_sold': self.tickets_sold,
 
        }
 
        # Raise an AttributeError if we didn't read any invoice activity.
 
        self.actions
 

	
 
    def _strpdate(self, s):
 
        date_s = strparse.rejoin_slice_words(s, slice(2), ',', 2).replace('Sept.', 'Sep.')
 
        return strparse.date(date_s, '%b. %d, %Y')
 

	
 
    def _read_invoice_header(self, table, first_row_text, rows_text):
 
        self.invoice_id = first_row_text[1]
 
        for key, value in rows_text:
 
            if key == 'Issue date':
 
                self.invoice_date = self._strpdate(value)
 
        recipient_h = table.find('th', text='Recipient')
 
        recipient_cell = recipient_h.find_next_sibling('td')
 
        self.payee = next(recipient_cell.stripped_strings)
 

	
 
    def _read_invoice_items(self, table, first_row_text, rows_text):
 
        self.amount = decimal.Decimal(0)
 
        self.tickets_sold = decimal.Decimal(0)
 
        self.ticket_rate = self.STANDARD_TICKET_RATE
 
        self.shirts_sold = decimal.Decimal(0)
 
        self.shirt_rate = self.STANDARD_SHIRT_RATE
 
        for description, qty, unit_price, total in rows_text:
 
            if qty is None:
 
                continue
 
            total = strparse.currency_decimal(total)
 
            self.amount += total
 
            if description.startswith('Ticket - '):
 
                self.tickets_sold += 1
 
                if total > 0:
 
                    self.tickets_sold += 1
 
            elif description.startswith('T-Shirt - '):
 
                self.shirts_sold += 1
 
            elif description.startswith('Early Bird ('):
 
                self.ticket_rate = self.DISCOUNT_TICKET_RATE
 
            if qty:
 
                self.amount += strparse.currency_decimal(total)
 

	
 
    def _read_invoice_activity(self, table, first_row_text, rows_text):
 
        self.actions = [{
 
            'date': self.invoice_date,
 
            'status': STATUS_INVOICED,
 
        }]
 
        for timestamp, description, amount in rows_text:
 
            if description.startswith('Paid '):
 
                last_stripe_id = strparse.rslice_words(description, 1, limit=1)
 
                action = {
 
                    'payment_id': last_stripe_id,
 
                    'status': STATUS_PAID,
 
                }
 
            else:
 
                # Refund handling could go here, if we need it.
 
                continue
 
            action['date'] = self._strpdate(timestamp)
 
            action['stripe_id'] = last_stripe_id
 
            self.actions.append(action)
 

	
 
    def __iter__(self):
 
        return (collections.ChainMap(act, self.base_data) for act in self.actions)
 

	
 

	
 
@functools.lru_cache(5)
 
def _parse_invoice(parser_class, source_file):
 
    try:
 
        return parser_class(source_file)
 
    except AttributeError:
 
        return None
 

	
 
class InvoiceImporter:
 
    INVOICE_CLASS = Invoice2017
 
    LEDGER_TEMPLATE_KEY_FMT = 'nbpy2017 {0} ledger entry'
 

	
 
    @classmethod
 
    def _parse_invoice(cls, source_file):
 
        return _parse_invoice(cls.INVOICE_CLASS, source_file)
 

	
 
    @classmethod
 
    def can_import(cls, source_file):
 
        return cls._parse_invoice(source_file) is not None
 

	
 
    def __init__(self, source_file):
 
        self.invoice = self._parse_invoice(source_file)
 

	
 
    def __iter__(self):
 
        for entry in self.invoice:
tests/data/imports.yml
Show inline comments
...
 
@@ -193,111 +193,111 @@
 
      amount: !!python/object/apply:decimal.Decimal ["80.00"]
 
      tickets_sold: !!python/object/apply:decimal.Decimal ["1"]
 
      ticket_rate: !!python/object/apply:decimal.Decimal ["21.25"]
 
      shirts_sold: !!python/object/apply:decimal.Decimal ["1"]
 
      shirt_rate: !!python/object/apply:decimal.Decimal ["25.50"]
 
      currency: USD
 
      status: Payment
 
      invoice_id: "83"
 
      invoice_date: !!python/object/apply:datetime.date [2017, 10, 19]
 
      payment_id: ch_ahr0ue8lai1ohqu4Gei4Biem
 
      stripe_id: ch_ahr0ue8lai1ohqu4Gei4Biem
 

	
 
- source: nbpy2017b.html
 
  importer: nbpy2017.InvoiceImporter
 
  expect:
 
    - payee: Python Person B
 
      ledger template: nbpy2017 invoice ledger entry
 
      date: !!python/object/apply:datetime.date [2017, 12, 3]
 
      amount: !!python/object/apply:decimal.Decimal ["50.00"]
 
      tickets_sold: !!python/object/apply:decimal.Decimal ["1"]
 
      ticket_rate: !!python/object/apply:decimal.Decimal ["42.50"]
 
      shirts_sold: !!python/object/apply:decimal.Decimal ["0"]
 
      shirt_rate: !!python/object/apply:decimal.Decimal ["25.50"]
 
      status: Invoice
 
      currency: USD
 
      invoice_date: !!python/object/apply:datetime.date [2017, 12, 3]
 
      invoice_id: "304"
 
    - payee: Python Person B
 
      ledger template: nbpy2017 payment ledger entry
 
      date: !!python/object/apply:datetime.date [2017, 12, 3]
 
      amount: !!python/object/apply:decimal.Decimal ["50.00"]
 
      tickets_sold: !!python/object/apply:decimal.Decimal ["1"]
 
      ticket_rate: !!python/object/apply:decimal.Decimal ["42.50"]
 
      shirts_sold: !!python/object/apply:decimal.Decimal ["0"]
 
      shirt_rate: !!python/object/apply:decimal.Decimal ["25.50"]
 
      status: Payment
 
      currency: USD
 
      invoice_date: !!python/object/apply:datetime.date [2017, 12, 3]
 
      payment_id: ch_eishei9aiY8aiqu4lieYiu9i
 
      stripe_id: ch_eishei9aiY8aiqu4lieYiu9i
 
      invoice_id: "304"
 

	
 
- source: nbpy2017c.html
 
  importer: nbpy2017.InvoiceImporter
 
  expect:
 
    - payee: Python Person C
 
      ledger template: nbpy2017 invoice ledger entry
 
      date: !!python/object/apply:datetime.date [2017, 9, 5]
 
      amount: !!python/object/apply:decimal.Decimal ["55.00"]
 
      tickets_sold: !!python/object/apply:decimal.Decimal ["1"]
 
      ticket_rate: !!python/object/apply:decimal.Decimal ["21.25"]
 
      amount: !!python/object/apply:decimal.Decimal ["30.00"]
 
      tickets_sold: !!python/object/apply:decimal.Decimal ["0"]
 
      ticket_rate: !!python/object/apply:decimal.Decimal ["42.50"]
 
      shirts_sold: !!python/object/apply:decimal.Decimal ["1"]
 
      shirt_rate: !!python/object/apply:decimal.Decimal ["25.50"]
 
      status: Invoice
 
      currency: USD
 
      invoice_date: !!python/object/apply:datetime.date [2017, 9, 5]
 
      invoice_id: "11"
 
    - payee: Python Person C
 
      ledger template: nbpy2017 payment ledger entry
 
      date: !!python/object/apply:datetime.date [2017, 9, 5]
 
      amount: !!python/object/apply:decimal.Decimal ["55.00"]
 
      tickets_sold: !!python/object/apply:decimal.Decimal ["1"]
 
      ticket_rate: !!python/object/apply:decimal.Decimal ["21.25"]
 
      amount: !!python/object/apply:decimal.Decimal ["30.00"]
 
      tickets_sold: !!python/object/apply:decimal.Decimal ["0"]
 
      ticket_rate: !!python/object/apply:decimal.Decimal ["42.50"]
 
      shirts_sold: !!python/object/apply:decimal.Decimal ["1"]
 
      shirt_rate: !!python/object/apply:decimal.Decimal ["25.50"]
 
      status: Payment
 
      currency: USD
 
      invoice_date: !!python/object/apply:datetime.date [2017, 9, 5]
 
      payment_id: ch_daer0ahwoh9oDeiqu2eimoD7
 
      stripe_id: ch_daer0ahwoh9oDeiqu2eimoD7
 
      invoice_id: "11"
 

	
 
- source: AmazonAffiliateEarnings.csv
 
  importer: amazon.EarningsImporter
 
  expect:
 
    - payee: Amazon
 
      date: !!python/object/apply:datetime.date [2016, 12, 20]
 
      amount: !!python/object/apply:decimal.Decimal ["4.24"]
 
      currency: USD
 
    - payee: Amazon
 
      date: !!python/object/apply:datetime.date [2017, 1, 7]
 
      amount: !!python/object/apply:decimal.Decimal ["-.08"]
 
      currency: USD
 

	
 
- source: Benevity.csv
 
  importer: benevity.DonationsImporter
 
  expect:
 
    - date: !!python/object/apply:datetime.date [2017, 10, 28]
 
      currency: USD
 
      disbursement_id: ABCDE12345
 
      amount: !!python/object/apply:decimal.Decimal [20]
 
      donation_amount: !!python/object/apply:decimal.Decimal [20]
 
      match_amount: !!python/object/apply:decimal.Decimal [0]
 
      payee: Dakota Smith
 
      corporation: Company A
 
      project: ""
 
      comment: ""
 
      frequency: One-time
 
      transaction_id: 67890QWERT
 
    - date: !!python/object/apply:datetime.date [2017, 10, 30]
 
      currency: USD
 
      disbursement_id: ABCDE12345
 
      amount: !!python/object/apply:decimal.Decimal [25]
 
      donation_amount: !!python/object/apply:decimal.Decimal [25]
 
      match_amount: !!python/object/apply:decimal.Decimal [0]
 
      payee: Dakota Smith
 
      corporation: Company A
 
      project: ""
 
      comment: ""
 
      frequency: One-time
 
      transaction_id: 67890WERTY
tests/data/nbpy2017c.html
Show inline comments
...
 
@@ -127,157 +127,157 @@
 
        <li role="presentation" >
 
            <a role="menuitem" href="/account/logout/" >Log Out</a>
 
        </li>
 
</ul>
 
        </li>
 
</ul>
 
          </div>
 
      </div>
 
    </div>
 
  </header>
 
  <div class="homepage-block-bg website-background"></div>
 
  <div id="background-filter">
 
    <section id="content_body">
 
        <div class="container">
 
            <div class="row">
 
                <div class="col-md-9">
 
      <h1></h1>
 
      <p class="lead">
 
      <div>
 
<div class="panel panel-primary">
 
  <div class="panel-heading">
 
      <h2>
 
    Registration Receipt
 
</h2>
 
      <div>
 
  North Bay Python. December 2 &amp; 3 2017. Petaluma, California.
 
</div>
 
  </div>
 
    <table class="table">
 
      <tr><th>Number</th><td> 11</td></tr>
 
      <tr><th>Status</th><td> Refunded</td></tr>
 
      <tr><th>Issue date</th><td> Sept. 5, 2017</td></tr>
 
        <tr><th>Due</th><td> Sept. 6, 2017, 9:09 p.m.</td></tr>
 
      <tr><th>Recipient</th><td> Python Person C<br />Anycountry</td></tr>
 
    </table>
 
  <div class="panel-body">
 
      <div class="alert alert-info">
 
    This is a refunded registration summary for North Bay Python 2017. It is provided for informational purposes only.
 
      </div>
 
    <div class="panel panel-default">
 
      <table class="table table-striped">
 
        <tr>
 
          <th>Description</th>
 
          <th class="text-right">Quantity</th>
 
          <th class="text-right">Price/Unit</th>
 
          <th class="text-right">Total</th>
 
        </tr>
 
          <tr>
 
            <td>Ticket - Unaffiliated Individual</td>
 
            <td>Ticket - Talk Proposer</td>
 
            <td class="text-right">1</td>
 
            <td class="text-right">$50.00</td>
 
            <td class="text-right">$50.00</td>
 
            <td class="text-right">$0.00</td>
 
            <td class="text-right">$0.00</td>
 
          </tr>
 
          <tr>
 
            <td>T-Shirt - Men&#39;s/Straight Cut Size M</td>
 
            <td class="text-right">1</td>
 
            <td class="text-right">$30.00</td>
 
            <td class="text-right">$30.00</td>
 
          </tr>
 
          <tr>
 
            <td>Early Bird (Ticket - Unaffiliated Individual)</td>
 
            <td>Tutorials - Notify me when tutorials are open</td>
 
            <td class="text-right">1</td>
 
            <td class="text-right">$-25.00</td>
 
            <td class="text-right">$-25.00</td>
 
            <td class="text-right">$-0.00</td>
 
            <td class="text-right">$-0.00</td>
 
          </tr>
 
        <tr>
 
          <th colspan="3">TOTAL</th>
 
          <td class="text-right">$55.00</td>
 
          <td class="text-right">$30.00</td>
 
        </tr>
 
    <tr>
 
      <td colspan="3">Includes donation eligible for tax deduction in the USA:</td>
 
      <td class="text-right">$8.25</td>
 
      <td class="text-right">$4.25</td>
 
    </tr>
 
      </table>
 
    </div>
 
    <div class="panel panel-info">
 
      <div class="panel-heading">
 
        <h3 class="panel-title">Balance</h3>
 
      </div>
 
      <table class="table table-striped">
 
        <tr>
 
          <td colspan="3">Total payments:</td>
 
          <td class="text-right">$0</td>
 
        </tr>
 
        <tr>
 
          <td colspan="3">Balance due:</td>
 
          <td class="text-right">$55.00</td>
 
          <td class="text-right">$30.00</td>
 
        </tr>
 
      </table>
 
    </div>
 
      <div class="panel panel-info">
 
        <div class="panel-heading">
 
          <h4 class="panel-title">Payments received</h4>
 
        </div>
 
  <table class="table table-striped">
 
    <tr>
 
      <th>Payment time</th>
 
      <th>Reference</th>
 
      <th>Amount</th>
 
    </tr>
 
      <tr>
 
        <td>Sept. 5, 2017, 9:14 p.m.</td>
 
        <td>Paid with Stripe reference: ch_daer0ahwoh9oDeiqu2eimoD7</td>
 
        <td>55.00</td>
 
        <td>30.00</td>
 
      </tr>
 
      <tr>
 
        <td>Sept. 8, 2017, 3:20 p.m.</td>
 
        <td>Generated credit note 31</td>
 
        <td>-55.00</td>
 
        <td>-30.00</td>
 
      </tr>
 
  </table>
 
      </div>
 
    <div class="panel panel-default">
 
        <div class="panel-heading">
 
          <h3 class="panel-title">
 
              Contact Information
 
          </h3>
 
        </div>
 
        <div class="panel-body">
 
          <p>
 
  <p>Direct inquiries to <a href="mailto:hello@northbaypython.org">hello@northbaypython.org</a></p>
 
  <p>North Bay Python is run by North Bay and Bay Area locals, as a member project of <a href="https://sfconservancy.org">Software Freedom Conservancy</a>, a 501(c)(3) not-for-profit public charity registered in New York. Software Freedom Conservancy's federal tax-exempt EIN is 41-2203632.</p>
 
  <strong>Mailing Address</strong>
 
  <address>
 
    Software Freedom Conservancy, Inc.<br>
 
    137 MONTAGUE ST STE 380<br>
 
    Brooklyn, NY 11201-3548<br>
 
  </address>
 
          </p>
 
        </div>
 
    </div>
 
  </div>
 
</div>
 
      </div>
 
                </div>
 
                <div class="col-md-3">
 
<h3>Sponsors</h3>
 
<div class="sponsor-list">
 
          <h4>Platinum</h4>
 
              <div>
 
                      <a href="http://revsys.com">
 
                          <img src="/site_media/media/sponsor_files/new-horizontal-large.png.600x360_q85.png" alt="REVSYS">
 
                      </a>
 
              </div>
 
              <div>
 
                      <a href="https://www.cloverhealth.com/en/">
 
                          <img src="/site_media/media/sponsor_files/Clover.png.600x360_q85.png" alt="Clover Health">
 
                      </a>
 
              </div>
 
          <h4>Gold</h4>
 
              <div>
 
                      <a href="https://python.org">
 
                          <img src="/site_media/media/sponsor_files/PSF_logo.png.600x360_q85.png" alt="Python Software Foundation">
 
                      </a>
 
              </div>
 
              <div>
 
                      <a href="https://ibm.com">
0 comments (0 inline, 0 general)