Compare commits

...

10 commits

Author SHA1 Message Date
579f6dde4b
Add importer for Printful Orders.csv file 2023-10-13 19:56:37 +11:00
5f1315b4e4
Update Stripe importer to rename tax field
This reflects the current CSV output from the "Export" button.
2023-05-17 22:39:44 +10:00
Rosanne DiMesio
950c7fd644 Include sales with status Partial Refund in Eventbrite imports 2023-05-03 14:10:23 -05:00
Rosanne DiMesio
8b0fd52602 update GitHub Sponsors importer 2021-10-25 11:29:41 -05:00
Rosanne DiMesio
ac2359101b Update brightfunds importer and bump version number 2021-07-14 13:19:50 -05:00
Brett Smith
c714b2f41f patreon: Income2017Importer grabs email address. 2021-03-18 11:47:56 -04:00
Brett Smith
1e24bfe304 github: Turn PRIVATE handle into Anonymous. 2020-12-18 10:35:50 -05:00
Brett Smith
742fccca0c patreon: Exclude tax from full amount.
This makes the balancing logic in entry generation work the way we want.
2020-12-10 09:43:47 -05:00
Brett Smith
c1d1e17afb tests: CSV that should've been included in last commit. 2020-12-09 16:35:28 -05:00
Brett Smith
15ef02c153 patreon: New importer for revamped earnings report. 2020-12-08 11:31:40 -05:00
10 changed files with 173 additions and 17 deletions

View file

@ -5,6 +5,7 @@ import functools
import io import io
import logging import logging
import operator import operator
import pprint
import re import re
import tokenize import tokenize
@ -192,8 +193,18 @@ class AccountSplitter:
"template {!r}".format(self.template_name) "template {!r}".format(self.template_name)
) from error ) from error
if sum(amt for _, amt in amounts) != 0: if sum(amt for _, amt in amounts) != 0:
try:
self._balance_amounts(amounts, template_vars['amount']) self._balance_amounts(amounts, template_vars['amount'])
self._balance_amounts(amounts, -template_vars['amount']) self._balance_amounts(amounts, -template_vars['amount'])
except errors.UserInputConfigurationError as error:
printer = pprint.PrettyPrinter()
logger.debug(
"%s\n%s\n%s",
error.args[0],
printer.pformat(amounts),
printer.pformat(dict(template_vars)),
)
raise
return amounts return amounts
def _iter_splits(self, template_vars): def _iter_splits(self, template_vars):

View file

@ -53,7 +53,12 @@ class _BrightFundsMixin:
class DonorReportImporter(_BrightFundsMixin, _csv.CSVImporterBase): class DonorReportImporter(_BrightFundsMixin, _csv.CSVImporterBase):
pass BOOK_KWARGS = {'encoding_override': 'utf-8'}
DATE_FMT = '%m/%d/%Y'
DATE_FIELD = 'Created'
DONOR_FIELD = 'Donor Name'
EMAIL_FIELD = 'Donor Email'
TYPE_FIELD = 'Type'
class DonorReportXLSImporter(_BrightFundsMixin, _xls.XLSImporterBase): class DonorReportXLSImporter(_BrightFundsMixin, _xls.XLSImporterBase):

View file

@ -29,7 +29,8 @@ class SalesImporter(_csv.CSVImporterBase):
ENTRY_SEED = {'currency': 'USD'} ENTRY_SEED = {'currency': 'USD'}
def _read_row(self, row): def _read_row(self, row):
if row['Order Type'] != 'Eventbrite Completed': # if row['Order Type'] != 'Eventbrite Completed':
if row['Order Type'] == 'Free Order':
return None return None
retval = { retval = {
self.DECIMAL_FIELDS[key]: strparse.currency_decimal(row[key]) self.DECIMAL_FIELDS[key]: strparse.currency_decimal(row[key])

View file

@ -6,24 +6,27 @@ from .. import strparse
class SponsorsImporter(_csv.CSVImporterBase): class SponsorsImporter(_csv.CSVImporterBase):
DATE_FMT = '%Y-%m-%d %H:%M:%S %z' DATE_FMT = '%Y-%m-%d %H:%M:%S %z'
NEEDED_FIELDS = frozenset([ NEEDED_FIELDS = frozenset([
'processed amount', 'transferred amount',
'status', 'transaction timestamp',
'transaction date',
]) ])
COPIED_FIELDS = { COPIED_FIELDS = {
'sponsor handle': 'handle', 'sponsor handle': 'handle',
# 'sponsor profile name': 'name', # 'sponsor profile name': 'name',
'sponsor email': 'email', 'sponsor email': 'email',
'transaction id': 'transaction_id', 'stripe transfer id': 'transaction_id',
} }
ENTRY_SEED = {'currency': 'USD'} ENTRY_SEED = {'currency': 'USD'}
PAYEE_MAP = {
'PRIVATE': 'Anonymous',
}
def _read_row(self, row): def _read_row(self, row):
amount = strparse.currency_decimal(row['processed amount']) amount = strparse.currency_decimal(row['transferred amount'])
if (not amount) or row['status'] != 'settled': if (not amount):
return None return None
payee = row.get('sponsor profile name') or row['sponsor handle']
return { return {
'amount': amount, 'amount': amount,
'date': strparse.date(row['transaction date'], self.DATE_FMT), 'date': strparse.date(row['transaction timestamp'], self.DATE_FMT),
'payee': row.get('sponsor profile name') or row['sponsor handle'], 'payee': self.PAYEE_MAP.get(payee, payee),
} }

View file

@ -4,6 +4,38 @@ import re
from . import _csv from . import _csv
from .. import strparse from .. import strparse
class EarningsImporter(_csv.CSVImporterBase):
AMOUNT_FIELDS = {
'Creator Share': 'net_amount',
'Creator Platform Fee': 'platform_fee',
'Creator Payment Processing Fee': 'processing_fee',
'Creator Currency Conversion Fee': 'currency_fee',
'Creator Equivalent of Patron Tax': 'tax_amount',
}
DATE_KEY = 'Date'
TYPE_KEY = 'Event Type'
NEEDED_FIELDS = frozenset([*AMOUNT_FIELDS, DATE_KEY, TYPE_KEY])
COPIED_FIELDS = {
'Creator Currency': 'currency',
'Event ID': 'event_id',
'Patron Email': 'email',
'Patron Name': 'payee',
'Patron User ID': 'user_id',
}
DATE_FMT = '%Y-%m-%d %H:%M:%S'
def _read_row(self, row):
if row[self.TYPE_KEY] != 'Payment':
return None
retval = {
ret_key: strparse.currency_decimal(row[src_key] or 0)
for src_key, ret_key in self.AMOUNT_FIELDS.items()
}
retval['amount'] = sum(retval.values()) - retval['tax_amount']
retval['date'] = strparse.date(row[self.DATE_KEY], self.DATE_FMT)
return retval
class IncomeImporter(_csv.CSVImporterBase): class IncomeImporter(_csv.CSVImporterBase):
AMOUNT_KEY = 'Pledge Amount' AMOUNT_KEY = 'Pledge Amount'
DATE_KEY = 'Last Charge Date' DATE_KEY = 'Last Charge Date'
@ -118,6 +150,7 @@ class Income2017Importer(_csv.CSVImporterBase):
'Status', 'Status',
]) ])
COPIED_FIELDS = { COPIED_FIELDS = {
'Email': 'email',
'Pledge': 'amount', 'Pledge': 'amount',
} }
ENTRY_SEED = { ENTRY_SEED = {

View file

@ -0,0 +1,58 @@
import re
from . import _csv
from .. import strparse
class SalesImporter(_csv.CSVImporterBase):
DECIMAL_FIELDS = {
'Products': 'products',
'Shipping': 'shipping_fees',
'Tax': 'tax',
'VAT': 'vat',
'Total': 'amount',
'Discount': 'discount',
}
NEEDED_FIELDS = {'Printful ID', *DECIMAL_FIELDS}
COPIED_FIELDS = {}
CURRENCY_MAP = {
'$': 'USD',
'': 'EUR',
'£': 'GBP',
'AU$': 'AUD',
'C$': 'CAD',
}
currency_regexp = re.compile(r'-?(?P<currency>\D*)\d.*')
def __init__(self, input_file):
# Orders.csv file is in UTF-8-SIG encoding and begins with a byte order
# mark character \ufeff. This happens *after* NEEDED_FIELDS is
# checked. This encoding check is done conditionally to handle the
# situation where someone has modified the CSV and saved as UTF-8.
first_char = input_file.read(1)
input_file.seek(0)
if first_char == '\ufeff':
input_file.reconfigure(encoding='UTF-8-SIG')
super().__init__(input_file)
def _read_row(self, row):
if row['Date'].startswith('Total paid'):
return None
# Parse the currency values. A hyphen represents zero.
record = {
self.DECIMAL_FIELDS[key]: strparse.currency_decimal(row[key] if row[key] != '-' else '0')
for key in self.DECIMAL_FIELDS
}
# Most importers don't deal with multiple currencies. There are
# libraries that partly help with currency codes and symbols, but the
# codes/symbols Printful use have some quirks, so it's easier to do this
# ourselves.
currency_code = re.match(self.currency_regexp, row['Total']).group('currency')
record['currency'] = self.CURRENCY_MAP.get(currency_code, currency_code).replace('$', '')
record['date'] = strparse.date(row['Date'], '%B %d, %Y')
record['order_id'] = row['Order'].lstrip('Order ')
record['payee'] = 'Printful' # Not used but required to be set
return record

View file

@ -1,15 +1,14 @@
import decimal
from . import _csv from . import _csv
from .. import strparse from .. import strparse
class PaymentImporter(_csv.CSVImporterBase): class PaymentImporter(_csv.CSVImporterBase):
NEEDED_FIELDS = frozenset([ NEEDED_FIELDS = frozenset([
'Converted Currency', 'Converted Currency',
'Created (UTC)', 'Created (UTC)',
'Fee', 'Fee',
'Status', 'Status',
'Tax', 'Taxes On Fee',
]) ])
COPIED_FIELDS = { COPIED_FIELDS = {
'Card Name': 'payee', 'Card Name': 'payee',
@ -31,7 +30,7 @@ class PaymentImporter(_csv.CSVImporterBase):
'currency': row['Converted Currency'].upper(), 'currency': row['Converted Currency'].upper(),
'date': strparse.date(date_s, self.DATE_FMT), 'date': strparse.date(date_s, self.DATE_FMT),
'fee': strparse.currency_decimal(row['Fee']), 'fee': strparse.currency_decimal(row['Fee']),
'tax': strparse.currency_decimal(row['Tax']), 'tax': strparse.currency_decimal(row['Taxes On Fee']),
} }

View file

@ -30,7 +30,7 @@ REQUIREMENTS['tests_require'] = [
setup( setup(
name='import2ledger', name='import2ledger',
description="Import different sources of financial data to Ledger", description="Import different sources of financial data to Ledger",
version='1.3.1', version='1.4.5',
author='Brett Smith', author='Brett Smith',
author_email='brettcsmith@brettcsmith.org', author_email='brettcsmith@brettcsmith.org',
license='GNU AGPLv3+', license='GNU AGPLv3+',

View file

@ -0,0 +1,4 @@
Date,Event ID,Event Type,Creator Currency,Creator Share,Creator Platform Fee,Creator Payment Processing Fee,Creator Currency Conversion Fee,Creator Equivalent of Patron Tax,Currency Conversion Rate,Patron Currency,Patron Charge Amount,Patron Tax Amount,Patron User ID,Patron Name,Patron Email
2020-12-03 04:05:06,30123456,Payment,USD,4.31,0.25,0.44,,1.00,,USD,6.00,1.00,32101234,Frances Jones,fjones@example.com
2020-12-02 03:04:05,34567890,Payment,USD,5.14,0.31,0.42,0.19,1.27,1212240,EUR,6.05,1.05,2345678,Barbara Johnson,bjohnson@example.com
2020-12-01 02:03:04,456789090,Payment,USD,12.64,0.70,0.66,,,,USD,14.00,,7654321,Alex Jones,ajones@example.com
1 Date Event ID Event Type Creator Currency Creator Share Creator Platform Fee Creator Payment Processing Fee Creator Currency Conversion Fee Creator Equivalent of Patron Tax Currency Conversion Rate Patron Currency Patron Charge Amount Patron Tax Amount Patron User ID Patron Name Patron Email
2 2020-12-03 04:05:06 30123456 Payment USD 4.31 0.25 0.44 1.00 USD 6.00 1.00 32101234 Frances Jones fjones@example.com
3 2020-12-02 03:04:05 34567890 Payment USD 5.14 0.31 0.42 0.19 1.27 1212240 EUR 6.05 1.05 2345678 Barbara Johnson bjohnson@example.com
4 2020-12-01 02:03:04 456789090 Payment USD 12.64 0.70 0.66 USD 14.00 7654321 Alex Jones ajones@example.com

View file

@ -5,10 +5,12 @@
date: !!python/object/apply:datetime.date [2017, 9, 1] date: !!python/object/apply:datetime.date [2017, 9, 1]
amount: !!python/object/apply:decimal.Decimal ["1500.00"] amount: !!python/object/apply:decimal.Decimal ["1500.00"]
currency: USD currency: USD
email: alex@example.org
- payee: Dakota Doe - payee: Dakota Doe
date: !!python/object/apply:datetime.date [2017, 9, 1] date: !!python/object/apply:datetime.date [2017, 9, 1]
amount: !!python/object/apply:decimal.Decimal ["12.00"] amount: !!python/object/apply:decimal.Decimal ["12.00"]
currency: USD currency: USD
email: ddoe@example.org
- source: PatreonPatronReport_2020-08-01.csv - source: PatreonPatronReport_2020-08-01.csv
importer: patreon.Income2020AugustImporter importer: patreon.Income2020AugustImporter
@ -76,6 +78,46 @@
amount: !!python/object/apply:decimal.Decimal ["117.03"] amount: !!python/object/apply:decimal.Decimal ["117.03"]
currency: USD currency: USD
- source: PatreonEarnings2020.csv
importer: patreon.EarningsImporter
expect:
- payee: Frances Jones
email: fjones@example.com
user_id: "32101234"
event_id: "30123456"
date: !!python/object/apply:datetime.date [2020, 12, 3]
currency: USD
amount: !!python/object/apply:decimal.Decimal ["5"]
tax_amount: !!python/object/apply:decimal.Decimal ["1"]
net_amount: !!python/object/apply:decimal.Decimal ["4.31"]
platform_fee: !!python/object/apply:decimal.Decimal ["0.25"]
processing_fee: !!python/object/apply:decimal.Decimal ["0.44"]
currency_fee: !!python/object/apply:decimal.Decimal ["0"]
- payee: Barbara Johnson
email: bjohnson@example.com
user_id: "2345678"
event_id: "34567890"
date: !!python/object/apply:datetime.date [2020, 12, 2]
currency: USD
amount: !!python/object/apply:decimal.Decimal ["6.06"]
tax_amount: !!python/object/apply:decimal.Decimal ["1.27"]
net_amount: !!python/object/apply:decimal.Decimal ["5.14"]
platform_fee: !!python/object/apply:decimal.Decimal ["0.31"]
processing_fee: !!python/object/apply:decimal.Decimal ["0.42"]
currency_fee: !!python/object/apply:decimal.Decimal ["0.19"]
- payee: Alex Jones
email: ajones@example.com
user_id: "7654321"
event_id: "456789090"
date: !!python/object/apply:datetime.date [2020, 12, 1]
currency: USD
amount: !!python/object/apply:decimal.Decimal ["14"]
tax_amount: !!python/object/apply:decimal.Decimal ["0"]
net_amount: !!python/object/apply:decimal.Decimal ["12.64"]
platform_fee: !!python/object/apply:decimal.Decimal [".7"]
processing_fee: !!python/object/apply:decimal.Decimal [".66"]
currency_fee: !!python/object/apply:decimal.Decimal ["0"]
- source: PatreonEarnings.csv - source: PatreonEarnings.csv
importer: patreon.CardFeesImporter importer: patreon.CardFeesImporter
expect: expect: