diff --git a/account_bank_statement_import_online_paypal/__manifest__.py b/account_bank_statement_import_online_paypal/__manifest__.py index 7cbda544..5e6d41fc 100644 --- a/account_bank_statement_import_online_paypal/__manifest__.py +++ b/account_bank_statement_import_online_paypal/__manifest__.py @@ -3,21 +3,15 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). { - 'name': 'Online Bank Statements: PayPal.com', - 'version': '12.0.1.1.0', - 'author': - 'CorporateHub, ' - 'Odoo Community Association (OCA)', - 'maintainers': ['alexey-pelykh'], - 'website': 'https://github.com/OCA/bank-statement-import/', - 'license': 'AGPL-3', - 'category': 'Accounting', - 'summary': 'Online bank statements for PayPal.com', - 'depends': [ - 'account_bank_statement_import_online', - ], - 'data': [ - 'views/online_bank_statement_provider.xml', - ], - 'installable': True, + "name": "Online Bank Statements: PayPal.com", + "version": "12.0.1.1.0", + "author": "CorporateHub, " "Odoo Community Association (OCA)", + "maintainers": ["alexey-pelykh"], + "website": "https://github.com/OCA/bank-statement-import/", + "license": "AGPL-3", + "category": "Accounting", + "summary": "Online bank statements for PayPal.com", + "depends": ["account_bank_statement_import_online",], + "data": ["views/online_bank_statement_provider.xml",], + "installable": True, } diff --git a/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py b/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py index 0cbd4b0f..6d25016a 100644 --- a/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py +++ b/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py @@ -1,194 +1,189 @@ # Copyright 2019-2020 Brainbean Apps (https://brainbeanapps.com) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). -from base64 import b64encode -from datetime import datetime -from dateutil.relativedelta import relativedelta -import dateutil.parser -from decimal import Decimal import itertools import json -import pytz +import urllib.request +from base64 import b64encode +from datetime import datetime +from decimal import Decimal from urllib.error import HTTPError from urllib.parse import urlencode -import urllib.request -from odoo import models, api, _ +import dateutil.parser +import pytz +from dateutil.relativedelta import relativedelta + +from odoo import _, api, models from odoo.exceptions import UserError - -PAYPAL_API_BASE = 'https://api.paypal.com' -TRANSACTIONS_SCOPE = 'https://uri.paypal.com/services/reporting/search/read' +PAYPAL_API_BASE = "https://api.paypal.com" +TRANSACTIONS_SCOPE = "https://uri.paypal.com/services/reporting/search/read" EVENT_DESCRIPTIONS = { - 'T0000': _('General PayPal-to-PayPal payment'), - 'T0001': _('MassPay payment'), - 'T0002': _('Subscription payment'), - 'T0003': _('Pre-approved payment (BillUser API)'), - 'T0004': _('eBay auction payment'), - 'T0005': _('Direct payment API'), - 'T0006': _('PayPal Checkout APIs'), - 'T0007': _('Website payments standard payment'), - 'T0008': _('Postage payment to carrier'), - 'T0009': _('Gift certificate payment, purchase of gift certificate'), - 'T0010': _('Third-party auction payment'), - 'T0011': _('Mobile payment, made through a mobile phone'), - 'T0012': _('Virtual terminal payment'), - 'T0013': _('Donation payment'), - 'T0014': _('Rebate payments'), - 'T0015': _('Third-party payout'), - 'T0016': _('Third-party recoupment'), - 'T0017': _('Store-to-store transfers'), - 'T0018': _('PayPal Here payment'), - 'T0019': _('Generic instrument-funded payment'), - 'T0100': _('General non-payment fee'), - 'T0101': _('Website payments. Pro account monthly fee'), - 'T0102': _('Foreign bank withdrawal fee'), - 'T0103': _('WorldLink check withdrawal fee'), - 'T0104': _('Mass payment batch fee'), - 'T0105': _('Check withdrawal'), - 'T0106': _('Chargeback processing fee'), - 'T0107': _('Payment fee'), - 'T0108': _('ATM withdrawal'), - 'T0109': _('Auto-sweep from account'), - 'T0110': _('International credit card withdrawal'), - 'T0111': _('Warranty fee for warranty purchase'), - 'T0112': _('Gift certificate expiration fee'), - 'T0113': _('Partner fee'), - 'T0200': _('General currency conversion'), - 'T0201': _('User-initiated currency conversion'), - 'T0202': _('Currency conversion required to cover negative balance'), - 'T0300': _('General funding of PayPal account'), - 'T0301': _('PayPal balance manager funding of PayPal account'), - 'T0302': _('ACH funding for funds recovery from account balance'), - 'T0303': _('Electronic funds transfer (EFT)'), - 'T0400': _('General withdrawal from PayPal account'), - 'T0401': _('AutoSweep'), - 'T0500': _('General PayPal debit card transaction'), - 'T0501': _('Virtual PayPal debit card transaction'), - 'T0502': _('PayPal debit card withdrawal to ATM'), - 'T0503': _('Hidden virtual PayPal debit card transaction'), - 'T0504': _('PayPal debit card cash advance'), - 'T0505': _('PayPal debit authorization'), - 'T0600': _('General credit card withdrawal'), - 'T0700': _('General credit card deposit'), - 'T0701': _('Credit card deposit for negative PayPal account balance'), - 'T0800': _('General bonus'), - 'T0801': _('Debit card cash back bonus'), - 'T0802': _('Merchant referral account bonus'), - 'T0803': _('Balance manager account bonus'), - 'T0804': _('PayPal buyer warranty bonus'), - 'T0805': _( - 'PayPal protection bonus, payout for PayPal buyer protection, payout ' - 'for full protection with PayPal buyer credit.' + "T0000": _("General PayPal-to-PayPal payment"), + "T0001": _("MassPay payment"), + "T0002": _("Subscription payment"), + "T0003": _("Pre-approved payment (BillUser API)"), + "T0004": _("eBay auction payment"), + "T0005": _("Direct payment API"), + "T0006": _("PayPal Checkout APIs"), + "T0007": _("Website payments standard payment"), + "T0008": _("Postage payment to carrier"), + "T0009": _("Gift certificate payment, purchase of gift certificate"), + "T0010": _("Third-party auction payment"), + "T0011": _("Mobile payment, made through a mobile phone"), + "T0012": _("Virtual terminal payment"), + "T0013": _("Donation payment"), + "T0014": _("Rebate payments"), + "T0015": _("Third-party payout"), + "T0016": _("Third-party recoupment"), + "T0017": _("Store-to-store transfers"), + "T0018": _("PayPal Here payment"), + "T0019": _("Generic instrument-funded payment"), + "T0100": _("General non-payment fee"), + "T0101": _("Website payments. Pro account monthly fee"), + "T0102": _("Foreign bank withdrawal fee"), + "T0103": _("WorldLink check withdrawal fee"), + "T0104": _("Mass payment batch fee"), + "T0105": _("Check withdrawal"), + "T0106": _("Chargeback processing fee"), + "T0107": _("Payment fee"), + "T0108": _("ATM withdrawal"), + "T0109": _("Auto-sweep from account"), + "T0110": _("International credit card withdrawal"), + "T0111": _("Warranty fee for warranty purchase"), + "T0112": _("Gift certificate expiration fee"), + "T0113": _("Partner fee"), + "T0200": _("General currency conversion"), + "T0201": _("User-initiated currency conversion"), + "T0202": _("Currency conversion required to cover negative balance"), + "T0300": _("General funding of PayPal account"), + "T0301": _("PayPal balance manager funding of PayPal account"), + "T0302": _("ACH funding for funds recovery from account balance"), + "T0303": _("Electronic funds transfer (EFT)"), + "T0400": _("General withdrawal from PayPal account"), + "T0401": _("AutoSweep"), + "T0500": _("General PayPal debit card transaction"), + "T0501": _("Virtual PayPal debit card transaction"), + "T0502": _("PayPal debit card withdrawal to ATM"), + "T0503": _("Hidden virtual PayPal debit card transaction"), + "T0504": _("PayPal debit card cash advance"), + "T0505": _("PayPal debit authorization"), + "T0600": _("General credit card withdrawal"), + "T0700": _("General credit card deposit"), + "T0701": _("Credit card deposit for negative PayPal account balance"), + "T0800": _("General bonus"), + "T0801": _("Debit card cash back bonus"), + "T0802": _("Merchant referral account bonus"), + "T0803": _("Balance manager account bonus"), + "T0804": _("PayPal buyer warranty bonus"), + "T0805": _( + "PayPal protection bonus, payout for PayPal buyer protection, payout " + "for full protection with PayPal buyer credit." ), - 'T0806': _('Bonus for first ACH use'), - 'T0807': _('Credit card security charge refund'), - 'T0808': _('Credit card cash back bonus'), - 'T0900': _('General incentive or certificate redemption'), - 'T0901': _('Gift certificate redemption'), - 'T0902': _('Points incentive redemption'), - 'T0903': _('Coupon redemption'), - 'T0904': _('eBay loyalty incentive'), - 'T0905': _('Offers used as funding source'), - 'T1000': _('Bill pay transaction'), - 'T1100': _('General reversal'), - 'T1101': _('Reversal of ACH withdrawal transaction'), - 'T1102': _('Reversal of debit card transaction'), - 'T1103': _('Reversal of points usage'), - 'T1104': _('Reversal of ACH deposit'), - 'T1105': _('Reversal of general account hold'), - 'T1106': _('Payment reversal, initiated by PayPal'), - 'T1107': _('Payment refund, initiated by merchant'), - 'T1108': _('Fee reversal'), - 'T1109': _('Fee refund'), - 'T1110': _('Hold for dispute investigation'), - 'T1111': _('Cancellation of hold for dispute resolution'), - 'T1112': _('MAM reversal'), - 'T1113': _('Non-reference credit payment'), - 'T1114': _('MassPay reversal transaction'), - 'T1115': _('MassPay refund transaction'), - 'T1116': _('Instant payment review (IPR) reversal'), - 'T1117': _('Rebate or cash back reversal'), - 'T1118': _('Generic instrument/Open Wallet reversals (seller side)'), - 'T1119': _('Generic instrument/Open Wallet reversals (buyer side)'), - 'T1200': _('General account adjustment'), - 'T1201': _('Chargeback'), - 'T1202': _('Chargeback reversal'), - 'T1203': _('Charge-off adjustment'), - 'T1204': _('Incentive adjustment'), - 'T1205': _('Reimbursement of chargeback'), - 'T1207': _('Chargeback re-presentment rejection'), - 'T1208': _('Chargeback cancellation'), - 'T1300': _('General authorization'), - 'T1301': _('Reauthorization'), - 'T1302': _('Void of authorization'), - 'T1400': _('General dividend'), - 'T1500': _('General temporary hold'), - 'T1501': _('Account hold for open authorization'), - 'T1502': _('Account hold for ACH deposit'), - 'T1503': _('Temporary hold on available balance'), - 'T1600': _('PayPal buyer credit payment funding'), - 'T1601': _('BML credit, transfer from BML'), - 'T1602': _('Buyer credit payment'), - 'T1603': _('Buyer credit payment withdrawal, transfer to BML'), - 'T1700': _('General withdrawal to non-bank institution'), - 'T1701': _('WorldLink withdrawal'), - 'T1800': _('General buyer credit payment'), - 'T1801': _('BML withdrawal, transfer to BML'), - 'T1900': _('General adjustment without business-related event'), - 'T2000': _('General intra-account transfer'), - 'T2001': _('Settlement consolidation'), - 'T2002': _('Transfer of funds from payable'), - 'T2003': _('Transfer to external GL entity'), - 'T2101': _('General hold'), - 'T2102': _('General hold release'), - 'T2103': _('Reserve hold'), - 'T2104': _('Reserve release'), - 'T2105': _('Payment review hold'), - 'T2106': _('Payment review release'), - 'T2107': _('Payment hold'), - 'T2108': _('Payment hold release'), - 'T2109': _('Gift certificate purchase'), - 'T2110': _('Gift certificate redemption'), - 'T2111': _('Funds not yet available'), - 'T2112': _('Funds available'), - 'T2113': _('Blocked payments'), - 'T2201': _('Transfer to and from a credit-card-funded restricted balance'), - 'T3000': _('Generic instrument/Open Wallet transaction'), - 'T5000': _('Deferred disbursement, funds collected for disbursement'), - 'T5001': _('Delayed disbursement, funds disbursed'), - 'T9700': _('Account receivable for shipping'), - 'T9701': _('Funds payable: PayPal-provided funds that must be paid back'), - 'T9702': _( - 'Funds receivable: PayPal-provided funds that are being paid back' - ), - 'T9800': _('Display only transaction'), - 'T9900': _('Other'), + "T0806": _("Bonus for first ACH use"), + "T0807": _("Credit card security charge refund"), + "T0808": _("Credit card cash back bonus"), + "T0900": _("General incentive or certificate redemption"), + "T0901": _("Gift certificate redemption"), + "T0902": _("Points incentive redemption"), + "T0903": _("Coupon redemption"), + "T0904": _("eBay loyalty incentive"), + "T0905": _("Offers used as funding source"), + "T1000": _("Bill pay transaction"), + "T1100": _("General reversal"), + "T1101": _("Reversal of ACH withdrawal transaction"), + "T1102": _("Reversal of debit card transaction"), + "T1103": _("Reversal of points usage"), + "T1104": _("Reversal of ACH deposit"), + "T1105": _("Reversal of general account hold"), + "T1106": _("Payment reversal, initiated by PayPal"), + "T1107": _("Payment refund, initiated by merchant"), + "T1108": _("Fee reversal"), + "T1109": _("Fee refund"), + "T1110": _("Hold for dispute investigation"), + "T1111": _("Cancellation of hold for dispute resolution"), + "T1112": _("MAM reversal"), + "T1113": _("Non-reference credit payment"), + "T1114": _("MassPay reversal transaction"), + "T1115": _("MassPay refund transaction"), + "T1116": _("Instant payment review (IPR) reversal"), + "T1117": _("Rebate or cash back reversal"), + "T1118": _("Generic instrument/Open Wallet reversals (seller side)"), + "T1119": _("Generic instrument/Open Wallet reversals (buyer side)"), + "T1200": _("General account adjustment"), + "T1201": _("Chargeback"), + "T1202": _("Chargeback reversal"), + "T1203": _("Charge-off adjustment"), + "T1204": _("Incentive adjustment"), + "T1205": _("Reimbursement of chargeback"), + "T1207": _("Chargeback re-presentment rejection"), + "T1208": _("Chargeback cancellation"), + "T1300": _("General authorization"), + "T1301": _("Reauthorization"), + "T1302": _("Void of authorization"), + "T1400": _("General dividend"), + "T1500": _("General temporary hold"), + "T1501": _("Account hold for open authorization"), + "T1502": _("Account hold for ACH deposit"), + "T1503": _("Temporary hold on available balance"), + "T1600": _("PayPal buyer credit payment funding"), + "T1601": _("BML credit, transfer from BML"), + "T1602": _("Buyer credit payment"), + "T1603": _("Buyer credit payment withdrawal, transfer to BML"), + "T1700": _("General withdrawal to non-bank institution"), + "T1701": _("WorldLink withdrawal"), + "T1800": _("General buyer credit payment"), + "T1801": _("BML withdrawal, transfer to BML"), + "T1900": _("General adjustment without business-related event"), + "T2000": _("General intra-account transfer"), + "T2001": _("Settlement consolidation"), + "T2002": _("Transfer of funds from payable"), + "T2003": _("Transfer to external GL entity"), + "T2101": _("General hold"), + "T2102": _("General hold release"), + "T2103": _("Reserve hold"), + "T2104": _("Reserve release"), + "T2105": _("Payment review hold"), + "T2106": _("Payment review release"), + "T2107": _("Payment hold"), + "T2108": _("Payment hold release"), + "T2109": _("Gift certificate purchase"), + "T2110": _("Gift certificate redemption"), + "T2111": _("Funds not yet available"), + "T2112": _("Funds available"), + "T2113": _("Blocked payments"), + "T2201": _("Transfer to and from a credit-card-funded restricted balance"), + "T3000": _("Generic instrument/Open Wallet transaction"), + "T5000": _("Deferred disbursement, funds collected for disbursement"), + "T5001": _("Delayed disbursement, funds disbursed"), + "T9700": _("Account receivable for shipping"), + "T9701": _("Funds payable: PayPal-provided funds that must be paid back"), + "T9702": _("Funds receivable: PayPal-provided funds that are being paid back"), + "T9800": _("Display only transaction"), + "T9900": _("Other"), } -NO_DATA_FOR_DATE_AVAIL_MSG = 'Data for the given start date is not available.' +NO_DATA_FOR_DATE_AVAIL_MSG = "Data for the given start date is not available." class OnlineBankStatementProviderPayPal(models.Model): - _inherit = 'online.bank.statement.provider' + _inherit = "online.bank.statement.provider" @api.model def _get_available_services(self): return super()._get_available_services() + [ - ('paypal', 'PayPal.com'), + ("paypal", "PayPal.com"), ] @api.multi def _obtain_statement_data(self, date_since, date_until): self.ensure_one() - if self.service != 'paypal': + if self.service != "paypal": return super()._obtain_statement_data( - date_since, - date_until, + date_since, date_until, ) # pragma: no cover - currency = ( - self.currency_id or self.company_id.currency_id - ).name + currency = (self.currency_id or self.company_id.currency_id).name if date_since.tzinfo: date_since = date_since.astimezone(pytz.utc).replace(tzinfo=None) @@ -196,215 +191,178 @@ class OnlineBankStatementProviderPayPal(models.Model): date_until = date_until.astimezone(pytz.utc).replace(tzinfo=None) if date_since < datetime.utcnow() - relativedelta(years=3): - raise UserError(_( - 'PayPal allows retrieving transactions only up to 3 years in ' - 'the past. Please import older transactions manually. See ' - 'https://www.paypal.com/us/smarthelp/article/why-can\'t-i' - '-access-transaction-history-greater-than-3-years-ts2241' - )) + raise UserError( + _( + "PayPal allows retrieving transactions only up to 3 years in " + "the past. Please import older transactions manually. See " + "https://www.paypal.com/us/smarthelp/article/why-can't-i" + "-access-transaction-history-greater-than-3-years-ts2241" + ) + ) token = self._paypal_get_token() transactions = self._paypal_get_transactions( - token, - currency, - date_since, - date_until + token, currency, date_since, date_until ) if not transactions: - balance = self._paypal_get_balance( - token, - currency, - date_since - ) - return [], { - 'balance_start': balance, - 'balance_end_real': balance, - } + balance = self._paypal_get_balance(token, currency, date_since) + return [], {"balance_start": balance, "balance_end_real": balance,} # Normalize transactions, sort by date, and get lines - transactions = list(sorted( - transactions, - key=lambda transaction: self._paypal_get_transaction_date( - transaction + transactions = list( + sorted( + transactions, + key=lambda transaction: self._paypal_get_transaction_date(transaction), ) - )) - lines = list(itertools.chain.from_iterable(map( - lambda x: self._paypal_transaction_to_lines(x), - transactions - ))) + ) + lines = list( + itertools.chain.from_iterable( + map(lambda x: self._paypal_transaction_to_lines(x), transactions) + ) + ) first_transaction = transactions[0] - first_transaction_id = \ - first_transaction['transaction_info']['transaction_id'] - first_transaction_date = self._paypal_get_transaction_date( - first_transaction - ) + first_transaction_id = first_transaction["transaction_info"]["transaction_id"] + first_transaction_date = self._paypal_get_transaction_date(first_transaction) first_transaction = self._paypal_get_transaction( - token, - first_transaction_id, - first_transaction_date + token, first_transaction_id, first_transaction_date ) if not first_transaction: - raise UserError(_('Failed to resolve transaction %s (%s)') % ( - first_transaction_id, - first_transaction_date - )) - balance_start = self._paypal_get_transaction_ending_balance( - first_transaction - ) - balance_start -= self._paypal_get_transaction_total_amount( - first_transaction - ) - balance_start -= self._paypal_get_transaction_fee_amount( - first_transaction - ) + raise UserError( + _("Failed to resolve transaction %s (%s)") + % (first_transaction_id, first_transaction_date) + ) + balance_start = self._paypal_get_transaction_ending_balance(first_transaction) + balance_start -= self._paypal_get_transaction_total_amount(first_transaction) + balance_start -= self._paypal_get_transaction_fee_amount(first_transaction) last_transaction = transactions[-1] - last_transaction_id = \ - last_transaction['transaction_info']['transaction_id'] - last_transaction_date = self._paypal_get_transaction_date( - last_transaction - ) + last_transaction_id = last_transaction["transaction_info"]["transaction_id"] + last_transaction_date = self._paypal_get_transaction_date(last_transaction) last_transaction = self._paypal_get_transaction( - token, - last_transaction_id, - last_transaction_date + token, last_transaction_id, last_transaction_date ) if not last_transaction: - raise UserError(_('Failed to resolve transaction %s (%s)') % ( - last_transaction_id, - last_transaction_date - )) - balance_end = self._paypal_get_transaction_ending_balance( - last_transaction - ) + raise UserError( + _("Failed to resolve transaction %s (%s)") + % (last_transaction_id, last_transaction_date) + ) + balance_end = self._paypal_get_transaction_ending_balance(last_transaction) - return lines, { - 'balance_start': balance_start, - 'balance_end_real': balance_end, - } + return lines, {"balance_start": balance_start, "balance_end_real": balance_end,} @api.model def _paypal_preparse_transaction(self, transaction): - date = dateutil.parser.parse( - self._paypal_get_transaction_date(transaction) - ).astimezone(pytz.utc).replace(tzinfo=None) - transaction['transaction_info']['transaction_updated_date'] = date + date = ( + dateutil.parser.parse(self._paypal_get_transaction_date(transaction)) + .astimezone(pytz.utc) + .replace(tzinfo=None) + ) + transaction["transaction_info"]["transaction_updated_date"] = date return transaction @api.model def _paypal_transaction_to_lines(self, data): - transaction = data['transaction_info'] - payer = data['payer_info'] - transaction_id = transaction['transaction_id'] - event_code = transaction['transaction_event_code'] + transaction = data["transaction_info"] + payer = data["payer_info"] + transaction_id = transaction["transaction_id"] + event_code = transaction["transaction_event_code"] date = self._paypal_get_transaction_date(data) total_amount = self._paypal_get_transaction_total_amount(data) fee_amount = self._paypal_get_transaction_fee_amount(data) - transaction_subject = transaction.get('transaction_subject') - transaction_note = transaction.get('transaction_note') - invoice = transaction.get('invoice_id') - payer_name = payer.get('payer_name', {}) - payer_email = payer_name.get('email_address') + transaction_subject = transaction.get("transaction_subject") + transaction_note = transaction.get("transaction_note") + invoice = transaction.get("invoice_id") + payer_name = payer.get("payer_name", {}) + payer_email = payer_name.get("email_address") if invoice: - invoice = _('Invoice %s') % invoice + invoice = _("Invoice %s") % invoice note = transaction_id if transaction_subject or transaction_note: - note = '%s: %s' % ( - note, - transaction_subject or transaction_note - ) + note = "{}: {}".format(note, transaction_subject or transaction_note) if payer_email: - note += ' (%s)' % payer_email - unique_import_id = '%s-%s' % ( - transaction_id, - int(date.timestamp()), + note += " (%s)" % payer_email + unique_import_id = "{}-{}".format(transaction_id, int(date.timestamp())) + name = ( + invoice + or transaction_subject + or transaction_note + or EVENT_DESCRIPTIONS.get(event_code) + or "" ) - name = invoice \ - or transaction_subject \ - or transaction_note \ - or EVENT_DESCRIPTIONS.get(event_code) \ - or '' line = { - 'name': name, - 'amount': str(total_amount), - 'date': date, - 'note': note, - 'unique_import_id': unique_import_id, + "name": name, + "amount": str(total_amount), + "date": date, + "note": note, + "unique_import_id": unique_import_id, } - payer_full_name = payer_name.get('full_name') or \ - payer_name.get('alternate_full_name') + payer_full_name = payer_name.get("full_name") or payer_name.get( + "alternate_full_name" + ) if payer_full_name: - line.update({ - 'partner_name': payer_full_name, - }) + line.update( + {"partner_name": payer_full_name,} + ) lines = [line] if fee_amount: - lines += [{ - 'name': _('Fee for %s') % (name or transaction_id), - 'amount': str(fee_amount), - 'date': date, - 'partner_name': 'PayPal', - 'unique_import_id': '%s-FEE' % unique_import_id, - 'note': _('Transaction fee for %s') % note, - }] + lines += [ + { + "name": _("Fee for %s") % (name or transaction_id), + "amount": str(fee_amount), + "date": date, + "partner_name": "PayPal", + "unique_import_id": "%s-FEE" % unique_import_id, + "note": _("Transaction fee for %s") % note, + } + ] return lines @api.multi def _paypal_get_token(self): self.ensure_one() data = self._paypal_retrieve( - (self.api_base or PAYPAL_API_BASE) + '/v1/oauth2/token', + (self.api_base or PAYPAL_API_BASE) + "/v1/oauth2/token", (self.username, self.password), - data=urlencode({ - 'grant_type': 'client_credentials', - }).encode('utf-8') + data=urlencode({"grant_type": "client_credentials",}).encode("utf-8"), ) - if 'scope' not in data or TRANSACTIONS_SCOPE not in data['scope']: - raise UserError(_( - 'PayPal App features are configured incorrectly!' - )) - if 'token_type' not in data or data['token_type'] != 'Bearer': - raise UserError(_('Invalid token type!')) - if 'access_token' not in data: - raise UserError(_( - 'Failed to acquire token using Client ID and Secret!' - )) - return data['access_token'] + if "scope" not in data or TRANSACTIONS_SCOPE not in data["scope"]: + raise UserError(_("PayPal App features are configured incorrectly!")) + if "token_type" not in data or data["token_type"] != "Bearer": + raise UserError(_("Invalid token type!")) + if "access_token" not in data: + raise UserError(_("Failed to acquire token using Client ID and Secret!")) + return data["access_token"] @api.multi def _paypal_get_balance(self, token, currency, as_of_timestamp): self.ensure_one() - url = (self.api_base or PAYPAL_API_BASE) \ - + '/v1/reporting/balances?currency_code=%s&as_of_time=%s' % ( - currency, - as_of_timestamp.isoformat() + 'Z', - ) + url = ( + self.api_base or PAYPAL_API_BASE + ) + "/v1/reporting/balances?currency_code={}&as_of_time={}".format( + currency, + as_of_timestamp.isoformat() + "Z", + ) data = self._paypal_retrieve(url, token) - available_balance = data['balances'][0].get('available_balance') + available_balance = data["balances"][0].get("available_balance") if not available_balance: return Decimal() - return Decimal(available_balance['value']) + return Decimal(available_balance["value"]) @api.multi def _paypal_get_transaction(self, token, transaction_id, timestamp): self.ensure_one() - transaction_date = timestamp.isoformat() + 'Z' - url = (self.api_base or PAYPAL_API_BASE) \ - + '/v1/reporting/transactions' \ - + ( - '?start_date=%s' - '&end_date=%s' - '&fields=all' - ) % ( - transaction_date, - transaction_date, + transaction_date = timestamp.isoformat() + "Z" + url = ( + (self.api_base or PAYPAL_API_BASE) + + "/v1/reporting/transactions" + + ("?start_date=%s" "&end_date=%s" "&fields=all") + % (transaction_date, transaction_date,) ) data = self._paypal_retrieve(url, token) - transactions = data['transaction_details'] + transactions = data["transaction_details"] for transaction in transactions: - if transaction['transaction_info']['transaction_id'] != \ - transaction_id: + if transaction["transaction_info"]["transaction_id"] != transaction_id: continue return transaction return None @@ -422,48 +380,49 @@ class OnlineBankStatementProviderPayPal(models.Model): page = 1 total_pages = None while total_pages is None or page <= total_pages: - url = (self.api_base or PAYPAL_API_BASE) \ - + '/v1/reporting/transactions' \ + url = ( + (self.api_base or PAYPAL_API_BASE) + + "/v1/reporting/transactions" + ( - '?transaction_currency=%s' - '&start_date=%s' - '&end_date=%s' - '&fields=all' - '&balance_affecting_records_only=Y' - '&page_size=500' - '&page=%d' - % ( - currency, - interval_start.isoformat() + 'Z', - interval_end.isoformat() + 'Z', - page, - )) + "?transaction_currency=%s" + "&start_date=%s" + "&end_date=%s" + "&fields=all" + "&balance_affecting_records_only=Y" + "&page_size=500" + "&page=%d" + % ( + currency, + interval_start.isoformat() + "Z", + interval_end.isoformat() + "Z", + page, + ) + ) + ) # NOTE: Workaround for INVALID_REQUEST (see ROADMAP.rst) invalid_data_workaround = self.env.context.get( - 'test_account_bank_statement_import_online_paypal_monday', - interval_start.weekday() == 0 and ( - datetime.utcnow() - interval_start - ).total_seconds() < 28800 + "test_account_bank_statement_import_online_paypal_monday", + interval_start.weekday() == 0 + and (datetime.utcnow() - interval_start).total_seconds() < 28800, ) data = self.with_context( invalid_data_workaround=invalid_data_workaround, )._paypal_retrieve(url, token) interval_transactions = map( - lambda transaction: self._paypal_preparse_transaction( - transaction - ), - data['transaction_details'] + lambda transaction: self._paypal_preparse_transaction(transaction), + data["transaction_details"], ) - transactions += list(filter( - lambda transaction: - interval_start <= self._paypal_get_transaction_date( - transaction - ) < interval_end, - interval_transactions - )) - total_pages = data['total_pages'] + transactions += list( + filter( + lambda transaction: interval_start + <= self._paypal_get_transaction_date(transaction) + < interval_end, + interval_transactions, + ) + ) + total_pages = data["total_pages"] page += 1 interval_start += interval_step return transactions @@ -471,45 +430,46 @@ class OnlineBankStatementProviderPayPal(models.Model): @api.model def _paypal_get_transaction_date(self, transaction): # NOTE: CSV reports from PayPal use this date, search as well - return transaction['transaction_info']['transaction_updated_date'] + return transaction["transaction_info"]["transaction_updated_date"] @api.model def _paypal_get_transaction_total_amount(self, transaction): - transaction_amount = \ - transaction['transaction_info'].get('transaction_amount') + transaction_amount = transaction["transaction_info"].get("transaction_amount") if not transaction_amount: return Decimal() - return Decimal(transaction_amount['value']) + return Decimal(transaction_amount["value"]) @api.model def _paypal_get_transaction_fee_amount(self, transaction): - fee_amount = transaction['transaction_info'].get('fee_amount') + fee_amount = transaction["transaction_info"].get("fee_amount") if not fee_amount: return Decimal() - return Decimal(fee_amount['value']) + return Decimal(fee_amount["value"]) @api.model def _paypal_get_transaction_ending_balance(self, transaction): # NOTE: 'available_balance' instead of 'ending_balance' as per CSV file - transaction_amount = \ - transaction['transaction_info'].get('available_balance') + transaction_amount = transaction["transaction_info"].get("available_balance") if not transaction_amount: return Decimal() - return Decimal(transaction_amount['value']) + return Decimal(transaction_amount["value"]) @api.model def _paypal_decode_error(self, content): - if 'name' in content: - return UserError('%s: %s' % ( - content['name'], - content.get('message', _('Unknown error')), - )) + if "name" in content: + return UserError( + "%s: %s" + % (content["name"], content.get("message", _("Unknown error")),) + ) - if 'error' in content: - return UserError('%s: %s' % ( - content['error'], - content.get('error_description', _('Unknown error')), - )) + if "error" in content: + return UserError( + "%s: %s" + % ( + content["error"], + content.get("error_description", _("Unknown error")), + ) + ) return None @@ -517,19 +477,21 @@ class OnlineBankStatementProviderPayPal(models.Model): def _paypal_retrieve(self, url, auth, data=None): try: with self._paypal_urlopen(url, auth, data) as response: - content = response.read().decode('utf-8') + content = response.read().decode("utf-8") except HTTPError as e: - content = json.loads(e.read().decode('utf-8')) + content = json.loads(e.read().decode("utf-8")) # NOTE: Workaround for INVALID_REQUEST (see ROADMAP.rst) - if self.env.context.get('invalid_data_workaround') \ - and content.get('name') == 'INVALID_REQUEST' \ - and content.get('message') == NO_DATA_FOR_DATE_AVAIL_MSG: + if ( + self.env.context.get("invalid_data_workaround") + and content.get("name") == "INVALID_REQUEST" + and content.get("message") == NO_DATA_FOR_DATE_AVAIL_MSG + ): return { - 'transaction_details': [], - 'page': 1, - 'total_items': 0, - 'total_pages': 0, + "transaction_details": [], + "page": 1, + "total_items": 0, + "total_pages": 0, } raise self._paypal_decode_error(content) or e @@ -538,21 +500,18 @@ class OnlineBankStatementProviderPayPal(models.Model): @api.model def _paypal_urlopen(self, url, auth, data=None): if not auth: - raise UserError(_('No authentication specified!')) + raise UserError(_("No authentication specified!")) request = urllib.request.Request(url, data=data) if isinstance(auth, tuple): request.add_header( - 'Authorization', - 'Basic %s' % str( - b64encode(('%s:%s' % (auth[0], auth[1])).encode('utf-8')), - 'utf-8' - ) + "Authorization", + "Basic %s" + % str( + b64encode(("{}:{}".format(auth[0], auth[1])).encode("utf-8")), "utf-8" + ), ) elif isinstance(auth, str): - request.add_header( - 'Authorization', - 'Bearer %s' % auth - ) + request.add_header("Authorization", "Bearer %s" % auth) else: - raise UserError(_('Unknown authentication specified!')) + raise UserError(_("Unknown authentication specified!")) return urllib.request.urlopen(request) diff --git a/account_bank_statement_import_online_paypal/tests/test_account_bank_statement_import_online_paypal.py b/account_bank_statement_import_online_paypal/tests/test_account_bank_statement_import_online_paypal.py index 93c8e1c1..8f5b692d 100644 --- a/account_bank_statement_import_online_paypal/tests/test_account_bank_statement_import_online_paypal.py +++ b/account_bank_statement_import_online_paypal/tests/test_account_bank_statement_import_online_paypal.py @@ -1,22 +1,23 @@ # Copyright 2019 Brainbean Apps (https://brainbeanapps.com) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from datetime import datetime -from dateutil.relativedelta import relativedelta -from decimal import Decimal import json +from datetime import datetime +from decimal import Decimal from unittest import mock from urllib.error import HTTPError +from dateutil.relativedelta import relativedelta + from odoo import fields from odoo.exceptions import UserError from odoo.tests import common -_module_ns = 'odoo.addons.account_bank_statement_import_online_paypal' +_module_ns = "odoo.addons.account_bank_statement_import_online_paypal" _provider_class = ( _module_ns - + '.models.online_bank_statement_provider_paypal' - + '.OnlineBankStatementProviderPayPal' + + ".models.online_bank_statement_provider_paypal" + + ".OnlineBankStatementProviderPayPal" ) @@ -25,7 +26,7 @@ class FakeHTTPError(HTTPError): self.content = content def read(self): - return self.content.encode('utf-8') + return self.content.encode("utf-8") class UrlopenRetValMock: @@ -42,165 +43,178 @@ class UrlopenRetValMock: def read(self): if self.throw: raise FakeHTTPError(self.content) - return self.content.encode('utf-8') + return self.content.encode("utf-8") -class TestAccountBankAccountStatementImportOnlinePayPal( - common.TransactionCase -): - +class TestAccountBankAccountStatementImportOnlinePayPal(common.TransactionCase): def setUp(self): super().setUp() self.now = fields.Datetime.now() - self.currency_eur = self.env.ref('base.EUR') - self.currency_usd = self.env.ref('base.USD') - self.AccountJournal = self.env['account.journal'] - self.OnlineBankStatementProvider = self.env[ - 'online.bank.statement.provider' - ] - self.AccountBankStatement = self.env['account.bank.statement'] - self.AccountBankStatementLine = self.env['account.bank.statement.line'] + self.currency_eur = self.env.ref("base.EUR") + self.currency_usd = self.env.ref("base.USD") + self.AccountJournal = self.env["account.journal"] + self.OnlineBankStatementProvider = self.env["online.bank.statement.provider"] + self.AccountBankStatement = self.env["account.bank.statement"] + self.AccountBankStatementLine = self.env["account.bank.statement.line"] Provider = self.OnlineBankStatementProvider self.paypal_parse_transaction = lambda payload: ( Provider._paypal_transaction_to_lines( Provider._paypal_preparse_transaction( - json.loads( - payload, - parse_float=Decimal, - ) + json.loads(payload, parse_float=Decimal,) ) ) ) self.mock_token = lambda: mock.patch( - _provider_class + '._paypal_get_token', - return_value='--TOKEN--', + _provider_class + "._paypal_get_token", return_value="--TOKEN--", ) def test_good_token(self): - journal = self.AccountJournal.create({ - 'name': 'Bank', - 'type': 'bank', - 'code': 'BANK', - 'currency_id': self.currency_eur.id, - 'bank_statements_source': 'online', - 'online_bank_statement_provider': 'paypal', - }) + journal = self.AccountJournal.create( + { + "name": "Bank", + "type": "bank", + "code": "BANK", + "currency_id": self.currency_eur.id, + "bank_statements_source": "online", + "online_bank_statement_provider": "paypal", + } + ) provider = journal.online_bank_statement_provider_id - mocked_response = json.loads("""{ + mocked_response = json.loads( + """{ "scope": "https://uri.paypal.com/services/reporting/search/read", "access_token": "---TOKEN---", "token_type": "Bearer", "app_id": "APP-1234567890", "expires_in": 32400, "nonce": "---NONCE---" -}""", parse_float=Decimal) +}""", + parse_float=Decimal, + ) token = None with mock.patch( - _provider_class + '._paypal_retrieve', - return_value=mocked_response, + _provider_class + "._paypal_retrieve", return_value=mocked_response, ): token = provider._paypal_get_token() - self.assertEqual(token, '---TOKEN---') + self.assertEqual(token, "---TOKEN---") def test_bad_token_scope(self): - journal = self.AccountJournal.create({ - 'name': 'Bank', - 'type': 'bank', - 'code': 'BANK', - 'currency_id': self.currency_eur.id, - 'bank_statements_source': 'online', - 'online_bank_statement_provider': 'paypal', - }) + journal = self.AccountJournal.create( + { + "name": "Bank", + "type": "bank", + "code": "BANK", + "currency_id": self.currency_eur.id, + "bank_statements_source": "online", + "online_bank_statement_provider": "paypal", + } + ) provider = journal.online_bank_statement_provider_id - mocked_response = json.loads("""{ + mocked_response = json.loads( + """{ "scope": "openid https://uri.paypal.com/services/applications/webhooks", "access_token": "---TOKEN---", "token_type": "Bearer", "app_id": "APP-1234567890", "expires_in": 32400, "nonce": "---NONCE---" -}""", parse_float=Decimal) +}""", + parse_float=Decimal, + ) with mock.patch( - _provider_class + '._paypal_retrieve', - return_value=mocked_response, + _provider_class + "._paypal_retrieve", return_value=mocked_response, ): with self.assertRaises(Exception): provider._paypal_get_token() def test_bad_token_type(self): - journal = self.AccountJournal.create({ - 'name': 'Bank', - 'type': 'bank', - 'code': 'BANK', - 'currency_id': self.currency_eur.id, - 'bank_statements_source': 'online', - 'online_bank_statement_provider': 'paypal', - }) + journal = self.AccountJournal.create( + { + "name": "Bank", + "type": "bank", + "code": "BANK", + "currency_id": self.currency_eur.id, + "bank_statements_source": "online", + "online_bank_statement_provider": "paypal", + } + ) provider = journal.online_bank_statement_provider_id - mocked_response = json.loads("""{ + mocked_response = json.loads( + """{ "scope": "https://uri.paypal.com/services/reporting/search/read", "access_token": "---TOKEN---", "token_type": "NotBearer", "app_id": "APP-1234567890", "expires_in": 32400, "nonce": "---NONCE---" -}""", parse_float=Decimal) +}""", + parse_float=Decimal, + ) with mock.patch( - _provider_class + '._paypal_retrieve', - return_value=mocked_response, + _provider_class + "._paypal_retrieve", return_value=mocked_response, ): with self.assertRaises(Exception): provider._paypal_get_token() def test_no_token(self): - journal = self.AccountJournal.create({ - 'name': 'Bank', - 'type': 'bank', - 'code': 'BANK', - 'currency_id': self.currency_eur.id, - 'bank_statements_source': 'online', - 'online_bank_statement_provider': 'paypal', - }) + journal = self.AccountJournal.create( + { + "name": "Bank", + "type": "bank", + "code": "BANK", + "currency_id": self.currency_eur.id, + "bank_statements_source": "online", + "online_bank_statement_provider": "paypal", + } + ) provider = journal.online_bank_statement_provider_id - mocked_response = json.loads("""{ + mocked_response = json.loads( + """{ "scope": "https://uri.paypal.com/services/reporting/search/read", "token_type": "Bearer", "app_id": "APP-1234567890", "expires_in": 32400, "nonce": "---NONCE---" - }""", parse_float=Decimal) + }""", + parse_float=Decimal, + ) with mock.patch( - _provider_class + '._paypal_retrieve', - return_value=mocked_response, + _provider_class + "._paypal_retrieve", return_value=mocked_response, ): with self.assertRaises(Exception): provider._paypal_get_token() def test_no_data_on_monday(self): - journal = self.AccountJournal.create({ - 'name': 'Bank', - 'type': 'bank', - 'code': 'BANK', - 'currency_id': self.currency_eur.id, - 'bank_statements_source': 'online', - 'online_bank_statement_provider': 'paypal', - }) + journal = self.AccountJournal.create( + { + "name": "Bank", + "type": "bank", + "code": "BANK", + "currency_id": self.currency_eur.id, + "bank_statements_source": "online", + "online_bank_statement_provider": "paypal", + } + ) provider = journal.online_bank_statement_provider_id - mocked_response_1 = UrlopenRetValMock("""{ + mocked_response_1 = UrlopenRetValMock( + """{ "debug_id": "eec890ebd5798", "details": "xxxxxx", "links": "xxxxxx", "message": "Data for the given start date is not available.", "name": "INVALID_REQUEST" -}""", throw=True) - mocked_response_2 = UrlopenRetValMock("""{ +}""", + throw=True, + ) + mocked_response_2 = UrlopenRetValMock( + """{ "balances": [ { "currency": "EUR", @@ -222,79 +236,85 @@ class TestAccountBankAccountStatementImportOnlinePayPal( "account_id": "1234567890", "as_of_time": "2019-08-01T00:00:00+0000", "last_refresh_time": "2019-08-01T00:00:00+0000" -}""") +}""" + ) with mock.patch( - _provider_class + '._paypal_urlopen', + _provider_class + "._paypal_urlopen", side_effect=[mocked_response_1, mocked_response_2], ), self.mock_token(): data = provider.with_context( test_account_bank_statement_import_online_paypal_monday=True, - )._obtain_statement_data( - self.now - relativedelta(hours=1), - self.now, - ) + )._obtain_statement_data(self.now - relativedelta(hours=1), self.now,) - self.assertEqual(data, ([], { - 'balance_start': 0.75, - 'balance_end_real': 0.75, - })) + self.assertEqual(data, ([], {"balance_start": 0.75, "balance_end_real": 0.75,})) def test_error_handling_1(self): - journal = self.AccountJournal.create({ - 'name': 'Bank', - 'type': 'bank', - 'code': 'BANK', - 'currency_id': self.currency_eur.id, - 'bank_statements_source': 'online', - 'online_bank_statement_provider': 'paypal', - }) + journal = self.AccountJournal.create( + { + "name": "Bank", + "type": "bank", + "code": "BANK", + "currency_id": self.currency_eur.id, + "bank_statements_source": "online", + "online_bank_statement_provider": "paypal", + } + ) provider = journal.online_bank_statement_provider_id - mocked_response = UrlopenRetValMock("""{ + mocked_response = UrlopenRetValMock( + """{ "message": "MESSAGE", "name": "ERROR" -}""", throw=True) +}""", + throw=True, + ) with mock.patch( - _provider_class + '._paypal_urlopen', - return_value=mocked_response, + _provider_class + "._paypal_urlopen", return_value=mocked_response, ): with self.assertRaises(UserError): - provider._paypal_retrieve('https://url', '') + provider._paypal_retrieve("https://url", "") def test_error_handling_2(self): - journal = self.AccountJournal.create({ - 'name': 'Bank', - 'type': 'bank', - 'code': 'BANK', - 'currency_id': self.currency_eur.id, - 'bank_statements_source': 'online', - 'online_bank_statement_provider': 'paypal', - }) + journal = self.AccountJournal.create( + { + "name": "Bank", + "type": "bank", + "code": "BANK", + "currency_id": self.currency_eur.id, + "bank_statements_source": "online", + "online_bank_statement_provider": "paypal", + } + ) provider = journal.online_bank_statement_provider_id - mocked_response = UrlopenRetValMock("""{ + mocked_response = UrlopenRetValMock( + """{ "error_description": "ERROR DESCRIPTION", "error": "ERROR" -}""", throw=True) +}""", + throw=True, + ) with mock.patch( - _provider_class + '._paypal_urlopen', - return_value=mocked_response, + _provider_class + "._paypal_urlopen", return_value=mocked_response, ): with self.assertRaises(UserError): - provider._paypal_retrieve('https://url', '') + provider._paypal_retrieve("https://url", "") def test_empty_pull(self): - journal = self.AccountJournal.create({ - 'name': 'Bank', - 'type': 'bank', - 'code': 'BANK', - 'currency_id': self.currency_eur.id, - 'bank_statements_source': 'online', - 'online_bank_statement_provider': 'paypal', - }) + journal = self.AccountJournal.create( + { + "name": "Bank", + "type": "bank", + "code": "BANK", + "currency_id": self.currency_eur.id, + "bank_statements_source": "online", + "online_bank_statement_provider": "paypal", + } + ) provider = journal.online_bank_statement_provider_id - mocked_response_1 = json.loads("""{ + mocked_response_1 = json.loads( + """{ "transaction_details": [], "account_number": "1234567890", "start_date": "2019-08-01T00:00:00+0000", @@ -303,8 +323,11 @@ class TestAccountBankAccountStatementImportOnlinePayPal( "page": 1, "total_items": 0, "total_pages": 0 -}""", parse_float=Decimal) - mocked_response_2 = json.loads("""{ +}""", + parse_float=Decimal, + ) + mocked_response_2 = json.loads( + """{ "balances": [ { "currency": "EUR", @@ -326,33 +349,34 @@ class TestAccountBankAccountStatementImportOnlinePayPal( "account_id": "1234567890", "as_of_time": "2019-08-01T00:00:00+0000", "last_refresh_time": "2019-08-01T00:00:00+0000" -}""", parse_float=Decimal) +}""", + parse_float=Decimal, + ) with mock.patch( - _provider_class + '._paypal_retrieve', + _provider_class + "._paypal_retrieve", side_effect=[mocked_response_1, mocked_response_2], ), self.mock_token(): data = provider._obtain_statement_data( - self.now - relativedelta(hours=1), - self.now, + self.now - relativedelta(hours=1), self.now, ) - self.assertEqual(data, ([], { - 'balance_start': 0.75, - 'balance_end_real': 0.75, - })) + self.assertEqual(data, ([], {"balance_start": 0.75, "balance_end_real": 0.75,})) def test_ancient_pull(self): - journal = self.AccountJournal.create({ - 'name': 'Bank', - 'type': 'bank', - 'code': 'BANK', - 'currency_id': self.currency_eur.id, - 'bank_statements_source': 'online', - 'online_bank_statement_provider': 'paypal', - }) + journal = self.AccountJournal.create( + { + "name": "Bank", + "type": "bank", + "code": "BANK", + "currency_id": self.currency_eur.id, + "bank_statements_source": "online", + "online_bank_statement_provider": "paypal", + } + ) provider = journal.online_bank_statement_provider_id - mocked_response = json.loads("""{ + mocked_response = json.loads( + """{ "transaction_details": [], "account_number": "1234567890", "start_date": "2019-08-01T00:00:00+0000", @@ -361,29 +385,32 @@ class TestAccountBankAccountStatementImportOnlinePayPal( "page": 1, "total_items": 0, "total_pages": 0 -}""", parse_float=Decimal) +}""", + parse_float=Decimal, + ) with mock.patch( - _provider_class + '._paypal_retrieve', - return_value=mocked_response, + _provider_class + "._paypal_retrieve", return_value=mocked_response, ), self.mock_token(): with self.assertRaises(Exception): provider._obtain_statement_data( - self.now - relativedelta(years=5), - self.now, + self.now - relativedelta(years=5), self.now, ) def test_pull(self): - journal = self.AccountJournal.create({ - 'name': 'Bank', - 'type': 'bank', - 'code': 'BANK', - 'currency_id': self.currency_eur.id, - 'bank_statements_source': 'online', - 'online_bank_statement_provider': 'paypal', - }) + journal = self.AccountJournal.create( + { + "name": "Bank", + "type": "bank", + "code": "BANK", + "currency_id": self.currency_eur.id, + "bank_statements_source": "online", + "online_bank_statement_provider": "paypal", + } + ) provider = journal.online_bank_statement_provider_id - mocked_response = json.loads("""{ + mocked_response = json.loads( + """{ "transaction_details": [{ "transaction_info": { "paypal_account_id": "1234567890", @@ -476,40 +503,44 @@ class TestAccountBankAccountStatementImportOnlinePayPal( "page": 1, "total_items": 1, "total_pages": 1 -}""", parse_float=Decimal) +}""", + parse_float=Decimal, + ) with mock.patch( - _provider_class + '._paypal_retrieve', - return_value=mocked_response, + _provider_class + "._paypal_retrieve", return_value=mocked_response, ), self.mock_token(): data = provider._obtain_statement_data( - datetime(2019, 8, 1), - datetime(2019, 8, 2), + datetime(2019, 8, 1), datetime(2019, 8, 2), ) self.assertEqual(len(data[0]), 2) - self.assertEqual(data[0][0], { - 'date': datetime(2019, 8, 1), - 'amount': '1000.00', - 'name': 'Invoice 1', - 'note': '1234567890: Payment for Invoice(s) 1', - 'partner_name': 'Acme, Inc.', - 'unique_import_id': '1234567890-1564617600', - }) - self.assertEqual(data[0][1], { - 'date': datetime(2019, 8, 1), - 'amount': '-100.00', - 'name': 'Fee for Invoice 1', - 'note': 'Transaction fee for 1234567890: Payment for Invoice(s) 1', - 'partner_name': 'PayPal', - 'unique_import_id': '1234567890-1564617600-FEE', - }) - self.assertEqual(data[1], { - 'balance_start': 0.0, - 'balance_end_real': 900.0, - }) + self.assertEqual( + data[0][0], + { + "date": datetime(2019, 8, 1), + "amount": "1000.00", + "name": "Invoice 1", + "note": "1234567890: Payment for Invoice(s) 1", + "partner_name": "Acme, Inc.", + "unique_import_id": "1234567890-1564617600", + }, + ) + self.assertEqual( + data[0][1], + { + "date": datetime(2019, 8, 1), + "amount": "-100.00", + "name": "Fee for Invoice 1", + "note": "Transaction fee for 1234567890: Payment for Invoice(s) 1", + "partner_name": "PayPal", + "unique_import_id": "1234567890-1564617600-FEE", + }, + ) + self.assertEqual(data[1], {"balance_start": 0.0, "balance_end_real": 900.0,}) def test_transaction_parse_1(self): - lines = self.paypal_parse_transaction("""{ + lines = self.paypal_parse_transaction( + """{ "transaction_info": { "paypal_account_id": "1234567890", "transaction_id": "1234567890", @@ -551,19 +582,24 @@ class TestAccountBankAccountStatementImportOnlinePayPal( "store_info": {}, "auction_info": {}, "incentive_info": {} -}""") +}""" + ) self.assertEqual(len(lines), 1) - self.assertEqual(lines[0], { - 'date': datetime(2019, 8, 1), - 'amount': '1000.00', - 'name': 'Invoice 1', - 'note': '1234567890: Payment for Invoice(s) 1', - 'partner_name': 'Acme, Inc.', - 'unique_import_id': '1234567890-1564617600', - }) + self.assertEqual( + lines[0], + { + "date": datetime(2019, 8, 1), + "amount": "1000.00", + "name": "Invoice 1", + "note": "1234567890: Payment for Invoice(s) 1", + "partner_name": "Acme, Inc.", + "unique_import_id": "1234567890-1564617600", + }, + ) def test_transaction_parse_2(self): - lines = self.paypal_parse_transaction("""{ + lines = self.paypal_parse_transaction( + """{ "transaction_info": { "paypal_account_id": "1234567890", "transaction_id": "1234567890", @@ -605,19 +641,24 @@ class TestAccountBankAccountStatementImportOnlinePayPal( "store_info": {}, "auction_info": {}, "incentive_info": {} -}""") +}""" + ) self.assertEqual(len(lines), 1) - self.assertEqual(lines[0], { - 'date': datetime(2019, 8, 1), - 'amount': '1000.00', - 'name': 'Invoice 1', - 'note': '1234567890: Payment for Invoice(s) 1', - 'partner_name': 'Acme, Inc.', - 'unique_import_id': '1234567890-1564617600', - }) + self.assertEqual( + lines[0], + { + "date": datetime(2019, 8, 1), + "amount": "1000.00", + "name": "Invoice 1", + "note": "1234567890: Payment for Invoice(s) 1", + "partner_name": "Acme, Inc.", + "unique_import_id": "1234567890-1564617600", + }, + ) def test_transaction_parse_3(self): - lines = self.paypal_parse_transaction("""{ + lines = self.paypal_parse_transaction( + """{ "transaction_info": { "paypal_account_id": "1234567890", "transaction_id": "1234567890", @@ -659,27 +700,35 @@ class TestAccountBankAccountStatementImportOnlinePayPal( "store_info": {}, "auction_info": {}, "incentive_info": {} -}""") +}""" + ) self.assertEqual(len(lines), 2) - self.assertEqual(lines[0], { - 'date': datetime(2019, 8, 1), - 'amount': '1000.00', - 'name': 'Invoice 1', - 'note': '1234567890: Payment for Invoice(s) 1', - 'partner_name': 'Acme, Inc.', - 'unique_import_id': '1234567890-1564617600', - }) - self.assertEqual(lines[1], { - 'date': datetime(2019, 8, 1), - 'amount': '-100.00', - 'name': 'Fee for Invoice 1', - 'note': 'Transaction fee for 1234567890: Payment for Invoice(s) 1', - 'partner_name': 'PayPal', - 'unique_import_id': '1234567890-1564617600-FEE', - }) + self.assertEqual( + lines[0], + { + "date": datetime(2019, 8, 1), + "amount": "1000.00", + "name": "Invoice 1", + "note": "1234567890: Payment for Invoice(s) 1", + "partner_name": "Acme, Inc.", + "unique_import_id": "1234567890-1564617600", + }, + ) + self.assertEqual( + lines[1], + { + "date": datetime(2019, 8, 1), + "amount": "-100.00", + "name": "Fee for Invoice 1", + "note": "Transaction fee for 1234567890: Payment for Invoice(s) 1", + "partner_name": "PayPal", + "unique_import_id": "1234567890-1564617600-FEE", + }, + ) def test_transaction_parse_4(self): - lines = self.paypal_parse_transaction("""{ + lines = self.paypal_parse_transaction( + """{ "transaction_info": { "paypal_account_id": "1234567890", "transaction_id": "1234567890", @@ -717,13 +766,17 @@ class TestAccountBankAccountStatementImportOnlinePayPal( "store_info": {}, "auction_info": {}, "incentive_info": {} -}""") +}""" + ) self.assertEqual(len(lines), 1) - self.assertEqual(lines[0], { - 'date': datetime(2019, 8, 1), - 'amount': '1000.00', - 'name': 'Invoice 1', - 'note': '1234567890: Payment for Invoice(s) 1', - 'partner_name': 'Acme, Inc.', - 'unique_import_id': '1234567890-1564617600', - }) + self.assertEqual( + lines[0], + { + "date": datetime(2019, 8, 1), + "amount": "1000.00", + "name": "Invoice 1", + "note": "1234567890: Payment for Invoice(s) 1", + "partner_name": "Acme, Inc.", + "unique_import_id": "1234567890-1564617600", + }, + ) diff --git a/account_bank_statement_import_online_paypal/views/online_bank_statement_provider.xml b/account_bank_statement_import_online_paypal/views/online_bank_statement_provider.xml index 62d0ba99..7d416cc0 100644 --- a/account_bank_statement_import_online_paypal/views/online_bank_statement_provider.xml +++ b/account_bank_statement_import_online_paypal/views/online_bank_statement_provider.xml @@ -1,14 +1,16 @@ - + - online.bank.statement.provider.form online.bank.statement.provider - + @@ -35,5 +37,4 @@ -