diff --git a/account_bank_statement_import_paypal/README.rst b/account_bank_statement_import_paypal/README.rst index f14ab9d..0d6f696 100644 --- a/account_bank_statement_import_paypal/README.rst +++ b/account_bank_statement_import_paypal/README.rst @@ -11,7 +11,7 @@ Import Paypal Bank Statements :target: https://odoo-community.org/page/development-status :alt: Beta .. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png - :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :target: https://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fbank--statement--import-lightgray.png?logo=github :target: https://github.com/OCA/bank-statement-import/tree/12.0/account_bank_statement_import_paypal diff --git a/account_bank_statement_import_paypal/__init__.py b/account_bank_statement_import_paypal/__init__.py index aee8895..adc6207 100644 --- a/account_bank_statement_import_paypal/__init__.py +++ b/account_bank_statement_import_paypal/__init__.py @@ -1,2 +1,4 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + from . import models from . import wizards diff --git a/account_bank_statement_import_paypal/__manifest__.py b/account_bank_statement_import_paypal/__manifest__.py index 4322ee1..78e9b00 100644 --- a/account_bank_statement_import_paypal/__manifest__.py +++ b/account_bank_statement_import_paypal/__manifest__.py @@ -1,26 +1,33 @@ # Copyright 2014-2017 Akretion (http://www.akretion.com). -# @author Alexis de Lattre -# @author Sébastien BEAU -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +# Copyright 2019 Tecnativa - Vicent Cubells +# Copyright 2019-2020 Brainbean Apps (https://brainbeanapps.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + { - "name": "Import Paypal Bank Statements", - 'summary': 'Import Paypal CSV files as Bank Statements in Odoo', - "version": "12.0.1.1.0", - "category": "Accounting", - "website": "https://github.com/OCA/bank-statement-import", - "author": " Akretion, Odoo Community Association (OCA)", - "license": "AGPL-3", - "installable": True, - "depends": [ - "account_bank_statement_import", - "sale", + 'name': 'PayPal CSV Format Bank Statements Import', + 'summary': 'Import PayPal CSV files as Bank Statements in Odoo', + 'version': '12.0.2.0.0', + 'category': 'Accounting', + 'website': 'https://github.com/OCA/bank-statement-import', + 'author': + 'Akretion, ' + 'Brainbean Apps, ' + 'Odoo Community Association (OCA)', + 'license': 'AGPL-3', + 'installable': True, + 'depends': [ + 'account_bank_statement_import', + ], + 'external_dependencies': { + 'python': [ + 'csv', + ] + }, + 'data': [ + 'security/ir.model.access.csv', + 'data/maps.xml', + 'views/account_bank_statement_import_paypal_mapping.xml', + 'views/account_bank_statement_import.xml', + 'wizards/account_bank_statement_import_paypal_mapping_wizard.xml', ], - "data": [ - "security/ir.model.access.csv", - "data/paypal_map_data.xml", - "wizards/create_map_lines_from_file_views.xml", - "wizards/account_bank_statement_import_view.xml", - "views/account_journal_views.xml", - "views/paypal_map_views.xml", - ] } diff --git a/account_bank_statement_import_paypal/data/maps.xml b/account_bank_statement_import_paypal/data/maps.xml new file mode 100644 index 0000000..78337e4 --- /dev/null +++ b/account_bank_statement_import_paypal/data/maps.xml @@ -0,0 +1,54 @@ + + + + + + PayPal Statement (EN) + comma + dot + %m/%d/%Y + %H:%M:%S + Date + Time + Time Zone + Name + Currency + Gross + Fee + Balance + Transaction ID + Description + From Email Address + Invoice ID + Bank Name + Bank Account + + + + PayPal Activity (EN) + comma + dot + %d/%m/%Y + %H:%M:%S + Date + Time + TimeZone + Name + Currency + Gross + Fee + Balance + Transaction ID + Type + From Email Address + To Email Address + Invoice ID + Subject + Note + + + diff --git a/account_bank_statement_import_paypal/data/paypal_map_data.xml b/account_bank_statement_import_paypal/data/paypal_map_data.xml deleted file mode 100644 index 0eeef9b..0000000 --- a/account_bank_statement_import_paypal/data/paypal_map_data.xml +++ /dev/null @@ -1,116 +0,0 @@ - - - - - Paypal Monthly Statement - comma - dot - - - - Date - 0 - - date - %m/%d/%Y - - - Time - 1 - - time - - - Time Zone - 2 - - - - Description - 3 - - description - - - Currency - 4 - - currency - - - Gross - 5 - - amount - - - Fee - 6 - - commission - - - Net - 7 - - - - Balance - 8 - - balance - - - Transaction ID - 9 - - transaction_id - - - From Email Address - 10 - - email - - - Name - 11 - - partner_name - - - Bank Name - 12 - - bank_name - - - Bank Account - 13 - - bank_account - - - Shipping and Handling Amount - 14 - - - - Sales Tax - 15 - - - - Invoice ID - 16 - - invoice_number - - - Reference Txn ID - 17 - - origin_transaction_id - - - diff --git a/account_bank_statement_import_paypal/migrations/12.0.2.0.0/post-migration.py b/account_bank_statement_import_paypal/migrations/12.0.2.0.0/post-migration.py new file mode 100644 index 0000000..e215572 --- /dev/null +++ b/account_bank_statement_import_paypal/migrations/12.0.2.0.0/post-migration.py @@ -0,0 +1,103 @@ +# Copyright 2020 Brainbean Apps (https://brainbeanapps.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from openupgradelib import openupgrade + + +@openupgrade.migrate() +def migrate(env, version): + openupgrade.logged_query( + env.cr, + """ +WITH _mappings AS ( + SELECT + m.id, + l.field_to_assign, + l.name, + l.date_format + FROM + account_bank_statement_import_paypal_map AS m + RIGHT OUTER JOIN ( + SELECT + *, + ROW_NUMBER() OVER ( + PARTITION BY map_parent_id, field_to_assign + ORDER BY id ASC + ) AS row_number + FROM account_bank_statement_import_paypal_map_line + WHERE field_to_assign IS NOT NULL + ) AS l ON m.id = l.map_parent_id AND l.row_number = 1 +) + +INSERT INTO account_bank_statement_import_paypal_mapping ( + name, + float_thousands_sep, + float_decimal_sep, + date_format, + time_format, + date_column, + time_column, + tz_column, + name_column, + currency_column, + gross_column, + fee_column, + balance_column, + transaction_id_column, + description_column, + from_email_address_column, + invoice_id_column, + bank_name_column, + bank_account_column +) +SELECT + m.name, + m.float_thousands_sep, + m.float_decimal_sep, + COALESCE(_date.date_format, '%m/%d/%Y') AS date_format, + '%H:%M:%S' AS time_format, + COALESCE(_date.name, 'Date') AS date_column, + COALESCE(_time.name, 'Time') AS time_column, + 'Time Zone' AS tz_column, + COALESCE(_name.name, 'Name') AS name_column, + COALESCE(_currency.name, 'Currency') AS currency_column, + COALESCE(_gross.name, 'Gross') AS gross_column, + COALESCE(_fee.name, 'Fee') AS fee_column, + COALESCE(_balance.name, 'Balance') AS balance_column, + COALESCE(_tid.name, 'Transaction ID') AS transaction_id_column, + COALESCE(_description.name, 'Description') AS description_column, + COALESCE(_from_email.name, 'From Email Address') + AS from_email_address_column, + COALESCE(_invoice.name, 'Invoice ID') AS invoice_id_column, + COALESCE(_bank_name.name, 'Bank Name') AS bank_name_column, + COALESCE(_bank_acc.name, 'Bank Account') AS bank_account_column +FROM + account_bank_statement_import_paypal_map AS m +LEFT JOIN _mappings AS _date + ON m.id = _date.id AND _date.field_to_assign = 'date' +LEFT JOIN _mappings AS _time + ON m.id = _time.id AND _time.field_to_assign = 'time' +LEFT JOIN _mappings AS _name + ON m.id = _name.id AND _name.field_to_assign = 'partner_name' +LEFT JOIN _mappings AS _currency + ON m.id = _currency.id AND _currency.field_to_assign = 'currency' +LEFT JOIN _mappings AS _gross + ON m.id = _gross.id AND _gross.field_to_assign = 'amount' +LEFT JOIN _mappings AS _fee + ON m.id = _fee.id AND _fee.field_to_assign = 'commission' +LEFT JOIN _mappings AS _balance + ON m.id = _balance.id AND _balance.field_to_assign = 'balance' +LEFT JOIN _mappings AS _tid + ON m.id = _tid.id AND _tid.field_to_assign = 'transaction_id' +LEFT JOIN _mappings AS _description + ON m.id = _description.id AND _description.field_to_assign = 'description' +LEFT JOIN _mappings AS _from_email + ON m.id = _from_email.id AND _from_email.field_to_assign = 'email' +LEFT JOIN _mappings AS _invoice + ON m.id = _invoice.id AND _invoice.field_to_assign = 'invoice_number' +LEFT JOIN _mappings AS _bank_name + ON m.id = _bank_name.id AND _bank_name.field_to_assign = 'bank_name' +LEFT JOIN _mappings AS _bank_acc + ON m.id = _bank_acc.id AND _bank_acc.field_to_assign = 'bank_account'; + """ + ) diff --git a/account_bank_statement_import_paypal/models/__init__.py b/account_bank_statement_import_paypal/models/__init__.py index 4257b3b..42211d5 100644 --- a/account_bank_statement_import_paypal/models/__init__.py +++ b/account_bank_statement_import_paypal/models/__init__.py @@ -1,2 +1,6 @@ -from . import account_bank_statement_import_paypal_map +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import account_bank_statement_import_paypal_mapping +from . import account_bank_statement_import_paypal_parser +from . import account_bank_statement_import from . import account_journal diff --git a/account_bank_statement_import_paypal/models/account_bank_statement_import.py b/account_bank_statement_import_paypal/models/account_bank_statement_import.py new file mode 100644 index 0000000..4d3d08c --- /dev/null +++ b/account_bank_statement_import_paypal/models/account_bank_statement_import.py @@ -0,0 +1,35 @@ +# Copyright 2014-2017 Akretion (http://www.akretion.com). +# Copyright 2019 Tecnativa - Vicent Cubells +# Copyright 2019 Brainbean Apps (https://brainbeanapps.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + +import logging +_logger = logging.getLogger(__name__) + + +class AccountBankStatementImport(models.TransientModel): + _inherit = 'account.bank.statement.import' + + paypal_mapping_id = fields.Many2one( + string='PayPal mapping', + comodel_name='account.bank.statement.import.paypal.mapping', + ) + + @api.multi + def _parse_file(self, data_file): + self.ensure_one() + try: + Parser = self.env['account.bank.statement.import.paypal.parser'] + return Parser.parse( + self.paypal_mapping_id, + data_file, + self.filename + ) + except: + if self.env.context.get( + 'account_bank_statement_import_paypal_test'): + raise + _logger.warning('PayPal parser error', exc_info=True) + return super()._parse_file(data_file) diff --git a/account_bank_statement_import_paypal/models/account_bank_statement_import_paypal_map.py b/account_bank_statement_import_paypal/models/account_bank_statement_import_paypal_map.py deleted file mode 100644 index 06c82ef..0000000 --- a/account_bank_statement_import_paypal/models/account_bank_statement_import_paypal_map.py +++ /dev/null @@ -1,110 +0,0 @@ -# Copyright 2019 Tecnativa - Vicent Cubells -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import fields, models, api - - -class AccountBankStatementImportPaypalMap(models.Model): - _name = 'account.bank.statement.import.paypal.map' - _description = 'Account Bank Statement Import Paypal Map' - - name = fields.Char( - required=True, - ) - map_line_ids = fields.One2many( - comodel_name='account.bank.statement.import.paypal.map.line', - inverse_name='map_parent_id', - string="Map lines", - required=True, - copy=True, - ) - float_thousands_sep = fields.Selection( - [('dot', 'dot (.)'), - ('comma', 'comma (,)'), - ('none', 'none'), - ], - string='Thousands separator', - # forward compatibility: this was the value assumed - # before the field was added. - default='dot', - required=True - ) - float_decimal_sep = fields.Selection( - [('dot', 'dot (.)'), - ('comma', 'comma (,)'), - ('none', 'none'), - ], - string='Decimals separator', - # forward compatibility: this was the value assumed - # before the field was added. - default='comma', - required=True - ) - - @api.onchange('float_thousands_sep') - def onchange_thousands_separator(self): - if 'dot' == self.float_thousands_sep == self.float_decimal_sep: - self.float_decimal_sep = 'comma' - elif 'comma' == self.float_thousands_sep == self.float_decimal_sep: - self.float_decimal_sep = 'dot' - - @api.onchange('float_decimal_sep') - def onchange_decimal_separator(self): - if 'dot' == self.float_thousands_sep == self.float_decimal_sep: - self.float_thousands_sep = 'comma' - elif 'comma' == self.float_thousands_sep == self.float_decimal_sep: - self.float_thousands_sep = 'dot' - - def _get_separators(self): - separators = {'dot': '.', - 'comma': ',', - 'none': '', - } - return (separators[self.float_thousands_sep], - separators[self.float_decimal_sep]) - - -class AccountBankStatementImportPaypalMapLine(models.Model): - _name = 'account.bank.statement.import.paypal.map.line' - _description = 'Account Bank Statement Import Paypal Map Line' - _order = "sequence asc, id asc" - - sequence = fields.Integer( - string="Field number", - required=True, - ) - name = fields.Char( - string="Header Name", - required=True, - ) - map_parent_id = fields.Many2one( - comodel_name='account.bank.statement.import.paypal.map', - required=True, - ondelete='cascade', - ) - field_to_assign = fields.Selection( - selection=[ - ('date', 'Date'), - ('time', 'Time'), - ('description', 'Description'), - ('currency', 'Currency'), - ('amount', 'Gross'), - ('commission', 'Fee'), - ('balance', 'Balance'), - ('transaction_id', 'Transaction ID'), - ('email', 'From Email Address'), - ('partner_name', 'Name'), - ('bank_name', 'Bank Name'), - ('bank_account', 'Bank Account'), - ('invoice_number', 'Invoice ID'), - ('origin_transaction_id', 'Origin Transaction ID'), - ], - string="Statement Field to Assign", - ) - date_format = fields.Selection( - selection=[ - ('%d/%m/%Y', 'i.e. 15/12/2019'), - ('%m/%d/%Y', 'i.e. 12/15/2019'), - ], - string="Date Format", - ) diff --git a/account_bank_statement_import_paypal/models/account_bank_statement_import_paypal_mapping.py b/account_bank_statement_import_paypal/models/account_bank_statement_import_paypal_mapping.py new file mode 100644 index 0000000..e87ddcf --- /dev/null +++ b/account_bank_statement_import_paypal/models/account_bank_statement_import_paypal_mapping.py @@ -0,0 +1,130 @@ +# Copyright 2019 Tecnativa - Vicent Cubells +# Copyright 2019 Brainbean Apps (https://brainbeanapps.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class AccountBankStatementImportPayPalMapping(models.Model): + _name = 'account.bank.statement.import.paypal.mapping' + _description = 'Account Bank Statement Import PayPal Mapping' + + name = fields.Char( + required=True, + ) + float_thousands_sep = fields.Selection( + string='Thousands Separator', + selection=[ + ('dot', 'dot (.)'), + ('comma', 'comma (,)'), + ('none', 'none'), + ], + default='dot', + required=True, + ) + float_decimal_sep = fields.Selection( + string='Decimals Separator', + selection=[ + ('dot', 'dot (.)'), + ('comma', 'comma (,)'), + ('none', 'none'), + ], + default='comma', + required=True, + ) + date_format = fields.Char( + string='Date Format', + required=True, + ) + time_format = fields.Char( + string='Time Format', + required=True, + ) + date_column = fields.Char( + string='"Date" column', + required=True, + ) + time_column = fields.Char( + string='"Time" column', + required=True, + ) + tz_column = fields.Char( + string='"Timezone" column', + required=True, + ) + name_column = fields.Char( + string='"Name" column', + required=True, + ) + currency_column = fields.Char( + string='"Currency" column', + required=True, + ) + gross_column = fields.Char( + string='"Gross" column', + required=True, + ) + fee_column = fields.Char( + string='"Fee" column', + required=True, + ) + balance_column = fields.Char( + string='"Balance" column', + required=True, + ) + transaction_id_column = fields.Char( + string='"Transaction ID" column', + required=True, + ) + description_column = fields.Char( + string='"Description" column', + ) + type_column = fields.Char( + string='"Type" column', + ) + from_email_address_column = fields.Char( + string='"From Email Address" column', + ) + to_email_address_column = fields.Char( + string='"To Email Address" column', + ) + invoice_id_column = fields.Char( + string='"Invoice ID" column', + ) + subject_column = fields.Char( + string='"Subject" column', + ) + note_column = fields.Char( + string='"Note" column', + ) + bank_name_column = fields.Char( + string='"Bank Name" column', + ) + bank_account_column = fields.Char( + string='"Bank Account" column', + ) + + @api.onchange('float_thousands_sep') + def onchange_thousands_separator(self): + if 'dot' == self.float_thousands_sep == self.float_decimal_sep: + self.float_decimal_sep = 'comma' + elif 'comma' == self.float_thousands_sep == self.float_decimal_sep: + self.float_decimal_sep = 'dot' + + @api.onchange('float_decimal_sep') + def onchange_decimal_separator(self): + if 'dot' == self.float_thousands_sep == self.float_decimal_sep: + self.float_thousands_sep = 'comma' + elif 'comma' == self.float_thousands_sep == self.float_decimal_sep: + self.float_thousands_sep = 'dot' + + @api.multi + def _get_float_separators(self): + self.ensure_one() + separators = { + 'dot': '.', + 'comma': ',', + 'none': '', + } + return (separators[self.float_thousands_sep], + separators[self.float_decimal_sep]) diff --git a/account_bank_statement_import_paypal/models/account_bank_statement_import_paypal_parser.py b/account_bank_statement_import_paypal/models/account_bank_statement_import_paypal_parser.py new file mode 100644 index 0000000..cdc9bcc --- /dev/null +++ b/account_bank_statement_import_paypal/models/account_bank_statement_import_paypal_parser.py @@ -0,0 +1,265 @@ +# Copyright 2019 Tecnativa - Vicent Cubells +# Copyright 2019 Brainbean Apps (https://brainbeanapps.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, models, _ + +from datetime import datetime +from decimal import Decimal +from io import StringIO +from os import path +import itertools +from pytz import timezone, utc + +import logging +_logger = logging.getLogger(__name__) + +try: + from csv import reader +except (ImportError, IOError) as err: + _logger.error(err) + + +class AccountBankStatementImportPayPalParser(models.TransientModel): + _name = 'account.bank.statement.import.paypal.parser' + _description = 'Account Bank Statement Import PayPal Parser' + + @api.model + def parse_header(self, data_file): + data = StringIO(data_file.decode('utf-8-sig')) + csv_data = reader(data) + return list(next(csv_data)) + + @api.model + def parse(self, mapping, data_file, filename): + journal = self.env['account.journal'].browse( + self.env.context.get('journal_id') + ) + currency_code = ( + journal.currency_id or journal.company_id.currency_id + ).name + account_number = journal.bank_account_id.acc_number + + name = _('%s: %s') % ( + journal.code, + path.basename(filename), + ) + lines = self._parse_lines(mapping, data_file, currency_code) + if not lines: + return currency_code, account_number, [{ + 'name': name, + 'transactions': [], + }] + + lines = list(sorted( + lines, + key=lambda line: line['timestamp'] + )) + first_line = lines[0] + balance_start = first_line['balance_amount'] + balance_start -= first_line['gross_amount'] + balance_start -= first_line['fee_amount'] + last_line = lines[-1] + balance_end = last_line['balance_amount'] + + transactions = list(itertools.chain.from_iterable(map( + lambda line: self._convert_line_to_transactions(line), + lines + ))) + + return currency_code, account_number, [{ + 'name': name, + 'date': first_line['timestamp'].date(), + 'balance_start': float(balance_start), + 'balance_end_real': float(balance_end), + 'transactions': transactions, + }] + + def _parse_lines(self, mapping, data_file, currency_code): + data = StringIO(data_file.decode('utf-8-sig')) + csv_data = reader(data) + + header = list(next(csv_data)) + date_column = header.index(mapping.date_column) + time_column = header.index(mapping.time_column) + tz_column = header.index(mapping.tz_column) + name_column = header.index(mapping.name_column) + currency_column = header.index(mapping.currency_column) + gross_column = header.index(mapping.gross_column) + fee_column = header.index(mapping.fee_column) + balance_column = header.index(mapping.balance_column) + transaction_id_column = header.index(mapping.transaction_id_column) + try: + description_column = header.index(mapping.description_column) + except ValueError: + description_column = None + try: + type_column = header.index(mapping.type_column) + except ValueError: + type_column = None + try: + from_email_address_column = header.index( + mapping.from_email_address_column + ) + except ValueError: + from_email_address_column = None + try: + to_email_address_column = header.index( + mapping.to_email_address_column + ) + except ValueError: + to_email_address_column = None + try: + invoice_id_column = header.index(mapping.invoice_id_column) + except ValueError: + invoice_id_column = None + try: + subject_column = header.index(mapping.subject_column) + except ValueError: + subject_column = None + try: + note_column = header.index(mapping.note_column) + except ValueError: + note_column = None + try: + bank_name_column = header.index(mapping.bank_name_column) + except ValueError: + bank_name_column = None + try: + bank_account_column = header.index(mapping.bank_account_column) + except ValueError: + bank_account_column = None + + lines = [] + for row in csv_data: + row = list(row) + date_value = row[date_column] + time_value = row[time_column] + tz_value = row[tz_column] + name_value = row[name_column] + currency_value = row[currency_column] + gross_value = row[gross_column] + fee_value = row[fee_column] + balance_value = row[balance_column] + transaction_id_value = row[transaction_id_column] + description_value = row[description_column] \ + if description_column is not None else None + type_value = row[type_column] \ + if type_column is not None else None + from_email_address_value = row[from_email_address_column] \ + if from_email_address_column is not None else None + to_email_address_value = row[to_email_address_column] \ + if to_email_address_column is not None else None + invoice_id_value = row[invoice_id_column] \ + if invoice_id_column is not None else None + subject_value = row[subject_column] \ + if subject_column is not None else None + note_value = row[note_column] \ + if note_column is not None else None + bank_name_value = row[bank_name_column] \ + if bank_name_column is not None else None + bank_account_value = row[bank_account_column] \ + if bank_account_column is not None else None + + if currency_value != currency_code: + continue + + date = datetime.strptime(date_value, mapping.date_format).date() + time = datetime.strptime(time_value, mapping.time_format).time() + timestamp = datetime.combine(date, time) + tz_value = self._normalize_tz(tz_value) + tz = timezone(tz_value) + timestamp = timestamp.replace(tzinfo=tz) + timestamp = timestamp.astimezone(utc).replace(tzinfo=None) + gross_amount = self._parse_decimal(gross_value, mapping) + fee_amount = self._parse_decimal(fee_value, mapping) + balance_amount = self._parse_decimal(balance_value, mapping) + bank = '%s - %s' % ( + bank_name_value, + bank_account_value, + ) if bank_name_value and bank_account_value else None + if to_email_address_column is None: + payer_email = from_email_address_value + else: + payer_email = to_email_address_value \ + if gross_amount < 0.0 else from_email_address_value + + lines.append({ + 'transaction_id': transaction_id_value, + 'invoice': invoice_id_value, + 'description': description_value or type_value, + 'details': subject_value or note_value or bank, + 'timestamp': timestamp, + 'gross_amount': gross_amount, + 'fee_amount': fee_amount, + 'balance_amount': balance_amount, + 'payer_name': name_value, + 'payer_email': payer_email, + }) + return lines + + @api.model + def _convert_line_to_transactions(self, line): + transactions = [] + + transaction_id = line['transaction_id'] + invoice = line['invoice'] + description = line['description'] + details = line['details'] + timestamp = line['timestamp'] + gross_amount = line['gross_amount'] + fee_amount = line['fee_amount'] + payer_name = line['payer_name'] + payer_email = line['payer_email'] + if invoice: + invoice = _('Invoice %s') % invoice + note = '%s %s' % ( + description, + transaction_id, + ) + if details: + note += ': %s' % details + if payer_email: + note += ' (%s)' % payer_email + + unique_import_id = '%s-%s' % ( + transaction_id, + int(timestamp.timestamp()), + ) + name = invoice or details or description or '', + transaction = { + 'name': invoice or details or description or '', + 'amount': str(gross_amount), + 'date': timestamp, + 'note': note, + 'unique_import_id': unique_import_id, + } + if payer_name: + line.update({ + 'partner_name': payer_name, + }) + transactions.append(transaction) + + if fee_amount: + transactions.append({ + 'name': _('Fee for %s') % (name or transaction_id), + 'amount': str(fee_amount), + 'date': timestamp, + 'partner_name': 'PayPal', + 'unique_import_id': '%s-FEE' % unique_import_id, + 'note': _('Transaction fee for %s') % note, + }) + return transactions + + @api.model + def _parse_decimal(self, value, mapping): + thousands, decimal = mapping._get_float_separators() + value = value.replace(thousands, '') + value = value.replace(decimal, '.') + return Decimal(value) + + @api.model + def _normalize_tz(self, value): + if value in ['PDT', 'PST']: + return 'PST8PDT' + return value diff --git a/account_bank_statement_import_paypal/models/account_journal.py b/account_bank_statement_import_paypal/models/account_journal.py index 0bc634e..20fbe50 100644 --- a/account_bank_statement_import_paypal/models/account_journal.py +++ b/account_bank_statement_import_paypal/models/account_journal.py @@ -1,18 +1,14 @@ # Copyright 2019 Tecnativa - Vicent Cubells -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +# Copyright 2019 Brainbean Apps (https://brainbeanapps.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import fields, models +from odoo import models class AccountJournal(models.Model): - _inherit = "account.journal" - - paypal_map_id = fields.Many2one( - comodel_name='account.bank.statement.import.paypal.map', - string='Paypal Map', - ) + _inherit = 'account.journal' def _get_bank_statements_available_import_formats(self): res = super()._get_bank_statements_available_import_formats() - res.append('Paypal') + res.append('PayPal Reports') return res diff --git a/account_bank_statement_import_paypal/readme/CONFIGURE.rst b/account_bank_statement_import_paypal/readme/CONFIGURE.rst index 7693cae..f8dc121 100644 --- a/account_bank_statement_import_paypal/readme/CONFIGURE.rst +++ b/account_bank_statement_import_paypal/readme/CONFIGURE.rst @@ -1,12 +1,4 @@ -* Create or go to a bank journal where you want to import Paypal statement. -* Edit that journal and set a Paypal map in **Paypal Map** section in **Advanced - Settings** tab. +To adjust PayPal report columns mapping: -* Now you can import Paypal statements in that journal. - -Note: if existent Paypal Map does not fit to your file to import, you can -create another map in **Invoicing > Configuration > Accounting > Paypal -Mapping**. - -You can import headers from any Paypal file in **Action > Create Paypal Map -Lines** and set every line with which field of statement have to match. +#. Open *Invoicing > Configuration > Accounting > PayPal Report Mappings* +#. Modify mapping(s) according to your CSV reports format. diff --git a/account_bank_statement_import_paypal/readme/CONTRIBUTORS.rst b/account_bank_statement_import_paypal/readme/CONTRIBUTORS.rst index 6e9a18d..c85a59a 100644 --- a/account_bank_statement_import_paypal/readme/CONTRIBUTORS.rst +++ b/account_bank_statement_import_paypal/readme/CONTRIBUTORS.rst @@ -4,3 +4,5 @@ * Vicent Cubells * Victor M.M. Torres + +* Alexey Pelykh diff --git a/account_bank_statement_import_paypal/readme/DESCRIPTION.rst b/account_bank_statement_import_paypal/readme/DESCRIPTION.rst index 492b95d..2c29a68 100644 --- a/account_bank_statement_import_paypal/readme/DESCRIPTION.rst +++ b/account_bank_statement_import_paypal/readme/DESCRIPTION.rst @@ -1,2 +1,2 @@ -This module allows you to import the Paypal CSV files in Odoo as bank +This module allows you to import the PayPal CSV files in Odoo as bank statements. diff --git a/account_bank_statement_import_paypal/readme/HISTORY.rst b/account_bank_statement_import_paypal/readme/HISTORY.rst new file mode 100644 index 0000000..fe7b1ad --- /dev/null +++ b/account_bank_statement_import_paypal/readme/HISTORY.rst @@ -0,0 +1,7 @@ +12.0.2.0.0 +~~~~~~~~~~ + +* [BREAKING] New mapping, please review mappings after upgrade. +* [BREAKING] Different bank accounts have to be used per each currency. +* [ADD] Support for both Statement and Activity reports. +* [ADD] Separate fee and currency exchange parsing. diff --git a/account_bank_statement_import_paypal/readme/USAGE.rst b/account_bank_statement_import_paypal/readme/USAGE.rst index fdcc4da..0e2851c 100644 --- a/account_bank_statement_import_paypal/readme/USAGE.rst +++ b/account_bank_statement_import_paypal/readme/USAGE.rst @@ -1,8 +1,19 @@ -To use this module, you need to: +To import statements from PayPal via Statement report: -#. Go to Paypal and download your Bank Statement +#. Go to `PayPal `__ +#. Open *Reports > Statements* and select *Monthly* or *Custom* +#. Select date range of interest and click *Request* under *CSV* column +#. Wait for statement report to be generated +#. Download it and save to a file +#. Go to Odoo and and import saved statement file, selecting corresponding format -.. image:: account_bank_statement_import_paypal/static/description/paypal_backoffice.png - :alt: . -.. image:: static/description/paypal_backoffice.png - :alt: . +To import statements from PayPal via Activity report: + +#. Go to `PayPal `__ +#. Open *Reports > Activity download* +#. Set *Transaction type* to *Balance affecting* +#. Set *Format* to *CSV* +#. Select date range of interest and click *Create report* +#. Wait for activity report to be generated +#. Download it and save to a file +#. Go to Odoo and and import saved statement file, selecting corresponding format diff --git a/account_bank_statement_import_paypal/security/ir.model.access.csv b/account_bank_statement_import_paypal/security/ir.model.access.csv index db61ffb..bd4912b 100644 --- a/account_bank_statement_import_paypal/security/ir.model.access.csv +++ b/account_bank_statement_import_paypal/security/ir.model.access.csv @@ -1,3 +1,3 @@ "id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" -access_account_bank_statement_import_paypal_map,Paypal map manager,model_account_bank_statement_import_paypal_map,account.group_account_manager,1,1,1,1 -access_account_bank_statement_import_paypal_map_line,Paypal map line manager,model_account_bank_statement_import_paypal_map_line,account.group_account_manager,1,1,1,1 +access_account_bank_statement_import_paypal_mapping_manager,account.bank.statement.import.paypal.mapping:account.group_account_manager,model_account_bank_statement_import_paypal_mapping,account.group_account_manager,1,1,1,1 +access_account_bank_statement_import_paypal_mapping_user,account.bank.statement.import.paypal.mapping:account.group_account_user,model_account_bank_statement_import_paypal_mapping,account.group_account_user,1,0,0,0 diff --git a/account_bank_statement_import_paypal/static/description/index.html b/account_bank_statement_import_paypal/static/description/index.html index 402d195..740eeae 100644 --- a/account_bank_statement_import_paypal/static/description/index.html +++ b/account_bank_statement_import_paypal/static/description/index.html @@ -367,7 +367,7 @@ ul.auto-toc { !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: AGPL-3 OCA/bank-statement-import Translate me on Weblate Try me on Runbot

+

Beta License: AGPL-3 OCA/bank-statement-import Translate me on Weblate Try me on Runbot

This module allows you to import the Paypal CSV files in Odoo as bank statements.

Table of contents

diff --git a/account_bank_statement_import_paypal/static/description/paypal_backoffice.png b/account_bank_statement_import_paypal/static/description/paypal_backoffice.png deleted file mode 100644 index a902aee..0000000 Binary files a/account_bank_statement_import_paypal/static/description/paypal_backoffice.png and /dev/null differ diff --git a/account_bank_statement_import_paypal/tests/__init__.py b/account_bank_statement_import_paypal/tests/__init__.py index 8160886..e6b11d1 100644 --- a/account_bank_statement_import_paypal/tests/__init__.py +++ b/account_bank_statement_import_paypal/tests/__init__.py @@ -1 +1,3 @@ -from . import test_paypal_statement_import +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import test_account_bank_statement_import_paypal diff --git a/account_bank_statement_import_paypal/tests/fixtures/activity_en.csv b/account_bank_statement_import_paypal/tests/fixtures/activity_en.csv new file mode 100644 index 0000000..258dedd --- /dev/null +++ b/account_bank_statement_import_paypal/tests/fixtures/activity_en.csv @@ -0,0 +1,77 @@ +"Date","Time","TimeZone","Name","Type","Status","Currency","Gross","Fee","Net","From Email Address","To Email Address","Transaction ID","Address Status","Sales Tax","Reference Txn ID","Invoice Number","Receipt ID","Balance","Contact Phone Number","Subject","Note" +"29/12/2015","07:45:51","PST","PARTNER1","General Payment","Completed","USD","84.00","-4.25","79.75","paypal@partner1.com","paypal@my-company.com","TID1","Non-Confirmed","","","","","79.75","","","" +"12/02/2016","13:30:31","PST","","User Initiated Currency Conversion","Completed","USD","-50.00","0.00","-50.00","paypal@my-company.com","","TID2","","","TID3","","","29.75","","","" +"12/02/2016","13:30:31","PST","","User Initiated Currency Conversion","Completed","EUR","43.34","0.00","43.34","","paypal@my-company.com","TID3","","","TID2","","","43.34","","","" +"09/06/2016","07:19:50","PDT","PARTNER2","Website Payment","Completed","USD","1,653.75","-73.07","1,580.68","paypal@partner2.com","paypal@my-company.com","TID4","Confirmed","","RTID1","31","RID1","1,610.43","","Payment to My Company for invoice 31","" +"22/06/2016","02:43:36","PDT","","General Currency Conversion","Completed","USD","-78.02","0.00","-78.02","paypal@my-company.com","","TID5","","0.00","TID7","PARTNER3-INV1","","1,532.41","","Shopping Cart","" +"22/06/2016","02:43:36","PDT","","General Currency Conversion","Completed","AUD","100.00","0.00","100.00","","paypal@my-company.com","TID6","","0.00","TID7","PARTNER3-INV1","","100.00","","Shopping Cart","" +"22/06/2016","02:43:36","PDT","PARTNER3","Website Payment","Completed","AUD","-100.00","0.00","-100.00","paypal@my-company.com","paypal@partner3.com","TID7","Non-Confirmed","0.00","","PARTNER3-INV1","","0.00","","Shopping Cart","" +"01/07/2016","12:23:59","PDT","PARTNER2","Website Payment","Completed","USD","2,602.50","-114.81","2,487.69","paypal@partner2.com","paypal@my-company.com","TID8","Confirmed","","RTID2","33","RID2","4,020.10","","Payment to My Company for invoice 33","" +"06/07/2016","08:17:57","PDT","PARTNER2","Website Payment","Completed","USD","4,466.50","-196.83","4,269.67","paypal@partner2.com","paypal@my-company.com","TID9","Confirmed","","RTID3","34","RID3","8,289.77","","Payment to My Company for invoice 34","" +"22/07/2016","12:15:13","PDT","PARTNER2","Website Payment","Completed","USD","6,245.50","-275.10","5,970.40","paypal@partner2.com","paypal@my-company.com","TID10","Confirmed","","RTID4","35","RID4","14,260.17","","Payment to My Company for invoice 35","" +"25/07/2016","01:47:56","PDT","","General Withdrawal","Pending","EUR","-12,431.37","0.00","-12,431.37","paypal@my-company.com","","TID11","","","","","","-12,388.03","","","" +"25/07/2016","01:47:56","PDT","","General Currency Conversion","Completed","USD","-14,000.00","0.00","-14,000.00","paypal@my-company.com","","TID12","","","TID11","","","260.17","","","" +"25/07/2016","01:47:56","PDT","","General Currency Conversion","Completed","EUR","12,431.37","0.00","12,431.37","","paypal@my-company.com","TID13","","","TID11","","","43.34","","","" +"26/07/2016","04:31:12","PDT","PARTNER4","Website Payment","Completed","USD","-195.00","0.00","-195.00","paypal@my-company.com","paypal@partner4.com","TID14","Non-Confirmed","0.00","","PARTNER4-INV1","","65.17","","Shopping Cart","" +"01/08/2016","15:11:56","PDT","PARTNER5","Website Payment","Completed","USD","2,616.25","-115.42","2,500.83","paypal@partner5.com","paypal@my-company.com","TID15","Confirmed","","RTID5","36","","2,566.00","","Payment to My Company for invoice 36","" +"15/08/2016","14:34:48","PDT","PARTNER2","Website Payment","Completed","USD","2,249.00","-99.26","2,149.74","paypal@partner2.com","paypal@my-company.com","TID16","Confirmed","","RTID6","37","RID5","4,715.74","","Payment to My Company for invoice 37","" +"24/08/2016","21:17:40","PDT","PARTNER5","Website Payment","Completed","USD","2,683.75","-118.39","2,565.36","paypal@partner5.com","paypal@my-company.com","TID17","Confirmed","","RTID7","38","","7,281.10","","Payment to My Company for invoice 38","" +"28/08/2016","23:18:09","PDT","","General Withdrawal","Pending","EUR","-6,248.60","0.00","-6,248.60","paypal@my-company.com","","TID18","","","","","","-6,205.26","","","" +"28/08/2016","23:18:09","PDT","","General Currency Conversion","Completed","USD","-7,200.00","0.00","-7,200.00","paypal@my-company.com","","TID19","","","TID18","","","81.10","","","" +"28/08/2016","23:18:09","PDT","","General Currency Conversion","Completed","EUR","6,248.60","0.00","6,248.60","","paypal@my-company.com","TID20","","","TID18","","","43.34","","","" +"29/08/2016","09:37:37","PDT","PARTNER2","Website Payment","Completed","USD","1,428.75","-63.17","1,365.58","paypal@partner2.com","paypal@my-company.com","TID21","Confirmed","","RTID8","40","RID6","1,446.68","","Payment to My Company for invoice 40","" +"29/08/2016","11:44:29","PDT","","General Withdrawal","Pending","EUR","-1,219.77","0.00","-1,219.77","paypal@my-company.com","","TID22","","","","","","-1,176.43","","","" +"29/08/2016","11:44:29","PDT","","General Currency Conversion","Completed","USD","-1,400.00","0.00","-1,400.00","paypal@my-company.com","","TID23","","","TID22","","","46.68","","","" +"29/08/2016","11:44:29","PDT","","General Currency Conversion","Completed","EUR","1,219.77","0.00","1,219.77","","paypal@my-company.com","TID24","","","TID22","","","43.34","","","" +"06/09/2016","20:33:20","PDT","PARTNER5","Website Payment","Completed","USD","4,439.05","-195.62","4,243.43","paypal@partner5.com","paypal@my-company.com","TID25","Confirmed","","RTID9","41","","4,290.11","","Payment to My Company for invoice 41","" +"06/09/2016","22:02:21","PDT","","General Withdrawal","Pending","EUR","-3,640.49","0.00","-3,640.49","paypal@my-company.com","","TID26","","","","","","-3,597.15","","","" +"06/09/2016","22:02:21","PDT","","General Currency Conversion","Completed","USD","-4,200.00","0.00","-4,200.00","paypal@my-company.com","","TID27","","","TID26","","","90.11","","","" +"06/09/2016","22:02:21","PDT","","General Currency Conversion","Completed","EUR","3,640.49","0.00","3,640.49","","paypal@my-company.com","TID28","","","TID26","","","43.34","","","" +"13/09/2016","20:01:45","PDT","PARTNER5","Website Payment","Completed","USD","2,967.50","-130.87","2,836.63","paypal@partner5.com","paypal@my-company.com","TID29","Confirmed","","RTID10","43","","2,926.74","","Payment to My Company for invoice 43","" +"14/09/2016","06:58:24","PDT","","General Withdrawal","Pending","EUR","-2,516.43","0.00","-2,516.43","paypal@my-company.com","","TID30","","","","","","-2,473.09","","","" +"14/09/2016","06:58:24","PDT","","General Currency Conversion","Completed","USD","-2,900.00","0.00","-2,900.00","paypal@my-company.com","","TID31","","","TID30","","","26.74","","","" +"14/09/2016","06:58:24","PDT","","General Currency Conversion","Completed","EUR","2,516.43","0.00","2,516.43","","paypal@my-company.com","TID32","","","TID30","","","43.34","","","" +"14/09/2016","16:53:38","PDT","PARTNER6","Website Payment","Completed","USD","603.75","-26.87","576.88","paypal@partner6.com","paypal@my-company.com","TID33","Confirmed","","RTID11","45","","603.62","","Payment to My Company for invoice 45","" +"15/09/2016","13:11:39","PDT","PARTNER2","Website Payment","Completed","USD","779.00","-34.58","744.42","paypal@partner2.com","paypal@my-company.com","TID34","Confirmed","","RTID12","44","RID7","1,348.04","","Payment to My Company for invoice 44","" +"24/09/2016","08:40:33","PDT","PARTNER6","Website Payment","Completed","USD","983.50","-43.57","939.93","paypal@partner6.com","paypal@my-company.com","TID35","Confirmed","","RTID13","50","","2,287.97","","Payment to My Company for invoice 50","" +"25/09/2016","13:34:51","PDT","PARTNER7","Website Payment","Completed","USD","3,920.00","-172.78","3,747.22","paypal@partner7.com","paypal@my-company.com","TID36","Non-Confirmed","","RTID14","47","","6,035.19","","Payment to My Company for invoice 47","" +"25/09/2016","13:34:51","PDT","","Payment Hold","Completed","USD","-3,747.22","0.00","-3,747.22","paypal@my-company.com","","TID37","","","TID36","47","","2,287.97","","","" +"26/09/2016","00:57:57","PDT","","General Withdrawal","Pending","EUR","-1,952.53","0.00","-1,952.53","paypal@my-company.com","","TID38","","","","","","-1,909.19","","","" +"26/09/2016","00:57:57","PDT","","General Currency Conversion","Completed","USD","-2,250.00","0.00","-2,250.00","paypal@my-company.com","","TID39","","","TID38","","","37.97","","","" +"26/09/2016","00:57:57","PDT","","General Currency Conversion","Completed","EUR","1,952.53","0.00","1,952.53","","paypal@my-company.com","TID40","","","TID38","","","43.34","","","" +"26/09/2016","21:36:04","PDT","PARTNER5","Website Payment","Completed","USD","3,623.75","-159.75","3,464.00","paypal@partner5.com","paypal@my-company.com","TID41","Confirmed","","RTID15","48","","3,501.97","","Payment to My Company for invoice 48","" +"26/09/2016","22:11:37","PDT","","General Withdrawal","Pending","EUR","-3,026.35","0.00","-3,026.35","paypal@my-company.com","","TID42","","","","","","-2,983.01","","","" +"26/09/2016","22:11:37","PDT","","General Currency Conversion","Completed","USD","-3,500.00","0.00","-3,500.00","paypal@my-company.com","","TID43","","","TID42","","","1.97","","","" +"26/09/2016","22:11:37","PDT","","General Currency Conversion","Completed","EUR","3,026.35","0.00","3,026.35","","paypal@my-company.com","TID44","","","TID42","","","43.34","","","" +"27/09/2016","15:58:17","PDT","PARTNER2","Website Payment","Completed","USD","1,985.25","-87.65","1,897.60","paypal@partner2.com","paypal@my-company.com","TID45","Confirmed","","RTID16","49","RID8","1,899.57","","Payment to My Company for invoice 49","" +"28/09/2016","13:38:43","PDT","","Payment Release","Completed","USD","3,747.22","0.00","3,747.22","","paypal@my-company.com","TID46","","","TID36","47","","5,646.79","","","" +"28/09/2016","15:39:16","PDT","","General Withdrawal","Pending","EUR","-4,904.75","0.00","-4,904.75","paypal@my-company.com","","TID47","","","","","","-4,861.41","","","" +"28/09/2016","15:39:16","PDT","","General Currency Conversion","Completed","USD","-5,640.00","0.00","-5,640.00","paypal@my-company.com","","TID48","","","TID47","","","6.79","","","" +"28/09/2016","15:39:16","PDT","","General Currency Conversion","Completed","EUR","4,904.75","0.00","4,904.75","","paypal@my-company.com","TID49","","","TID47","","","43.34","","","" +"11/10/2016","10:21:06","PDT","PARTNER2","Website Payment","Completed","USD","1,406.75","-52.35","1,354.40","paypal@partner2.com","paypal@my-company.com","TID50","Confirmed","","RTID17","53","RID9","1,361.19","","Payment to My Company for invoice 53","" +"17/10/2016","08:39:03","PDT","PARTNER8","Website Payment","Completed","USD","1,505.00","-55.99","1,449.01","paypal@partner8.com","paypal@my-company.com","TID51","Non-Confirmed","","RTID18","54","RID10","2,810.20","","Payment to My Company for invoice 54","" +"18/10/2016","21:40:10","PDT","PARTNER5","Website Payment","Completed","USD","5,973.40","-221.32","5,752.08","paypal@partner5.com","paypal@my-company.com","TID52","Confirmed","","RTID19","52","","8,562.28","","Payment to My Company for invoice 52","" +"24/10/2016","05:52:21","PDT","PARTNER6","Website Payment","Completed","USD","2,129.75","-79.10","2,050.65","paypal@partner6.com","paypal@my-company.com","TID53","Confirmed","","RTID20","55","","10,612.93","","Payment to My Company for invoice 55","" +"24/10/2016","06:32:13","PDT","","General Withdrawal","Pending","EUR","-9,484.13","0.00","-9,484.13","paypal@my-company.com","","TID54","","","","","","-9,440.79","","","" +"24/10/2016","06:32:13","PDT","","General Currency Conversion","Completed","USD","-10,600.00","0.00","-10,600.00","paypal@my-company.com","","TID55","","","TID54","","","12.93","","","" +"24/10/2016","06:32:13","PDT","","General Currency Conversion","Completed","EUR","9,484.13","0.00","9,484.13","","paypal@my-company.com","TID56","","","TID54","","","43.34","","","" +"25/10/2016","06:51:49","PDT","PARTNER7","Website Payment","Completed","USD","4,725.00","-175.13","4,549.87","paypal@partner7.com","paypal@my-company.com","TID57","Non-Confirmed","","RTID21","59","","4,562.80","","Payment to My Company for invoice 59","" +"27/10/2016","03:00:00","PDT","","General Withdrawal","Pending","EUR","-4,061.92","0.00","-4,061.92","paypal@my-company.com","","TID58","","","","","","-4,018.58","","","" +"27/10/2016","03:00:00","PDT","","General Currency Conversion","Completed","USD","-4,550.00","0.00","-4,550.00","paypal@my-company.com","","TID59","","","TID58","","","12.80","","","" +"27/10/2016","03:00:00","PDT","","General Currency Conversion","Completed","EUR","4,061.92","0.00","4,061.92","","paypal@my-company.com","TID60","","","TID58","","","43.34","","","" +"28/10/2016","09:28:14","PDT","PARTNER2","Website Payment","Completed","USD","3,140.75","-116.51","3,024.24","paypal@partner2.com","paypal@my-company.com","TID61","Confirmed","","RTID22","58","RID11","3,037.04","","Payment to My Company for invoice 58","" +"29/10/2016","04:07:58","PDT","","General Withdrawal","Pending","EUR","-2,668.56","0.00","-2,668.56","paypal@my-company.com","","TID62","","","","","","-2,625.22","","","" +"29/10/2016","04:07:58","PDT","","General Currency Conversion","Completed","USD","-3,000.00","0.00","-3,000.00","paypal@my-company.com","","TID63","","","TID62","","","37.04","","","" +"29/10/2016","04:07:58","PDT","","General Currency Conversion","Completed","EUR","2,668.56","0.00","2,668.56","","paypal@my-company.com","TID64","","","TID62","","","43.34","","","" +"31/10/2016","10:57:29","PDT","PARTNER9","Website Payment","Completed","USD","2,616.25","-97.10","2,519.15","paypal@partner9.com","paypal@my-company.com","TID65","Confirmed","","RTID23","62","","2,556.19","","Payment to My Company for invoice 62","" +"31/10/2016","11:05:15","PDT","","General Withdrawal","Pending","EUR","-2,267.68","0.00","-2,267.68","paypal@my-company.com","","TID66","","","","","","-2,224.34","","","" +"31/10/2016","11:05:15","PDT","","General Currency Conversion","Completed","USD","-2,550.00","0.00","-2,550.00","paypal@my-company.com","","TID67","","","TID66","","","6.19","","","" +"31/10/2016","11:05:15","PDT","","General Currency Conversion","Completed","EUR","2,267.68","0.00","2,267.68","","paypal@my-company.com","TID68","","","TID66","","","43.34","","","" +"01/11/2016","09:41:29","PDT","PARTNER6","Website Payment","Completed","USD","1,246.00","-46.40","1,199.60","paypal@partner6.com","paypal@my-company.com","TID69","Confirmed","","RTID24","61","","1,205.79","","Payment to My Company for invoice 61","" +"01/11/2016","09:50:01","PDT","","General Withdrawal","Pending","EUR","-1,063.52","0.00","-1,063.52","paypal@my-company.com","","TID70","","","","","","-1,020.18","","","" +"01/11/2016","09:50:01","PDT","","General Currency Conversion","Completed","USD","-1,200.00","0.00","-1,200.00","paypal@my-company.com","","TID71","","","TID70","","","5.79","","","" +"01/11/2016","09:50:01","PDT","","General Currency Conversion","Completed","EUR","1,063.52","0.00","1,063.52","","paypal@my-company.com","TID72","","","TID70","","","43.34","","","" +"01/11/2016","15:40:16","PDT","PARTNER5","Website Payment","Completed","USD","4,586.10","-169.99","4,416.11","paypal@partner5.com","paypal@my-company.com","TID73","Confirmed","","RTID25","57","","4,421.90","","Payment to My Company for invoice 57","" +"01/11/2016","18:39:27","PDT","","General Withdrawal","Pending","EUR","-3,897.58","0.00","-3,897.58","paypal@my-company.com","","TID74","","","","","","-3,854.24","","","" +"01/11/2016","18:39:27","PDT","","General Currency Conversion","Completed","USD","-4,420.00","0.00","-4,420.00","paypal@my-company.com","","TID75","","","TID74","","","1.90","","","" +"01/11/2016","18:39:27","PDT","","General Currency Conversion","Completed","EUR","3,897.58","0.00","3,897.58","","paypal@my-company.com","TID76","","","TID74","","","43.34","","","" diff --git a/account_bank_statement_import_paypal/tests/fixtures/empty_activity.csv b/account_bank_statement_import_paypal/tests/fixtures/empty_activity.csv new file mode 100644 index 0000000..1729ed7 --- /dev/null +++ b/account_bank_statement_import_paypal/tests/fixtures/empty_activity.csv @@ -0,0 +1 @@ +"Date","Time","TimeZone","Name","Type","Status","Currency","Gross","Fee","Net","From Email Address","To Email Address","Transaction ID","Address Status","Sales Tax","Reference Txn ID","Invoice Number","Receipt ID","Balance","Contact Phone Number","Subject","Note" diff --git a/account_bank_statement_import_paypal/tests/fixtures/statement_en.csv b/account_bank_statement_import_paypal/tests/fixtures/statement_en.csv new file mode 100644 index 0000000..b7407db --- /dev/null +++ b/account_bank_statement_import_paypal/tests/fixtures/statement_en.csv @@ -0,0 +1,20 @@ +"Date","Time","Time Zone","Description","Currency","Gross","Fee","Net","Balance","Transaction ID","From Email Address","Name","Bank Name","Bank Account","Shipping and Handling Amount","Sales Tax","Invoice ID","Reference Txn ID" +"9/4/2018","21:15:11","America/Los_Angeles","General Withdrawal - Bank Account","EUR","-1,058.09","0.00","-1,058.09","-1,057.34","TID1","","","Bank A","6491","0.00","0.00","","" +"9/4/2018","21:15:11","America/Los_Angeles","General Currency Conversion","EUR","1,058.09","0.00","1,058.09","0.75","TID2","","","","","0.00","0.00","","TID1" +"9/11/2018","00:01:50","America/Los_Angeles","General Withdrawal - Bank Account","EUR","-3,105.58","0.00","-3,105.58","-3,104.83","TID3","","","Bank B","9169","0.00","0.00","","" +"9/11/2018","00:01:50","America/Los_Angeles","General Currency Conversion","EUR","3,105.58","0.00","3,105.58","0.75","TID4","","","","","0.00","0.00","","TID3" +"9/25/2018","04:22:39","America/Los_Angeles","General Withdrawal - Bank Account","EUR","-3,534.51","0.00","-3,534.51","-3,533.76","TID5","","","Bank B","9169","0.00","0.00","","" +"9/25/2018","04:22:39","America/Los_Angeles","General Currency Conversion","EUR","3,534.51","0.00","3,534.51","0.75","TID6","","","","","0.00","0.00","","TID5" +"9/27/2018","21:33:27","America/Los_Angeles","General Withdrawal - Bank Account","EUR","-9,648.13","0.00","-9,648.13","-9,647.38","TID7","","","Bank B","9169","0.00","0.00","","" +"9/27/2018","21:33:27","America/Los_Angeles","General Currency Conversion","EUR","9,648.13","0.00","9,648.13","0.75","TID8","","","","","0.00","0.00","","TID7" +"9/4/2018","20:44:10","America/Los_Angeles","Express Checkout Payment","USD","1,309.80","-48.76","1,261.04","1,261.93","TID9","paypal@partner1.com","Partner 1","","","0.00","0.00","361","" +"9/4/2018","21:15:11","America/Los_Angeles","General Currency Conversion","USD","-1,261.00","0.00","-1,261.00","0.93","TID10","","","","","0.00","0.00","","TID1" +"9/10/2018","17:48:19","America/Los_Angeles","Express Checkout Payment","USD","3,840.60","-142.40","3,698.20","3,699.13","TID11","paypa@partner2.com","Partner 2","","","0.00","0.00","362","" +"9/11/2018","00:01:50","America/Los_Angeles","General Currency Conversion","USD","-3,699.00","0.00","-3,699.00","0.13","TID12","","","","","0.00","0.00","","TID3" +"9/24/2018","16:41:01","America/Los_Angeles","Express Checkout Payment","USD","4,447.40","-164.85","4,282.55","4,282.68","TID13","paypa@partner2.com","Partner 2","","","0.00","0.00","363","" +"9/25/2018","04:22:39","America/Los_Angeles","General Currency Conversion","USD","-4,282.00","0.00","-4,282.00","0.68","TID14","","","","","0.00","0.00","","TID5" +"9/27/2018","18:15:34","America/Los_Angeles","Express Checkout Payment","USD","5,600.00","-207.50","5,392.50","5,393.18","TID15","paypa@partner3.com","Partner 3","","","0.00","0.00","366","" +"9/27/2018","18:16:12","CET","Express Checkout Payment","USD","920.70","-34.37","886.33","6,279.51","TID16","paypa@partner3.com","Partner 3","","","0.00","0.00","367","" +"9/27/2018","18:17:59","America/Los_Angeles","Express Checkout Payment","USD","5,600.00","-207.50","5,392.50","11,672.01","TID17","paypa@partner3.com","Partner 3","","","0.00","0.00","371","" +"9/27/2018","21:33:27","America/Los_Angeles","General Currency Conversion","USD","-11,672.00","0.00","-11,672.00","0.01","TID18","","","","","0.00","0.00","","TID7" +"9/30/2018","21:22:33","America/Los_Angeles","Express Checkout Payment","USD","292.30","-11.12","281.18","281.19","TID19","paypal@partner1.com","Partner 1","","","0.00","0.00","380","" diff --git a/account_bank_statement_import_paypal/tests/paypal_en.csv b/account_bank_statement_import_paypal/tests/paypal_en.csv deleted file mode 100644 index 234a327..0000000 --- a/account_bank_statement_import_paypal/tests/paypal_en.csv +++ /dev/null @@ -1,3 +0,0 @@ -"Date","Time","Time Zone","Description","Currency","Gross","Fee ","Net","Balance","Transaction ID","From Email Address","Name","Bank Name","Bank Account","Shipping and Handling Amount","Sales Tax","Invoice ID","Reference Txn ID" -"12/15/2018","20:07:53","CET","Your best supplier","USD","-33.50","-2.3","-31.2","-31.2","53820712527632627","","John Doe","Bank of America","123456789","0","0","INV25","23" -"12/15/2018","22:07:53","CET","Your payment","USD","1,525.00","0","1,525.00","1,493.80","34731322767782103","","Azure Interior","","","0","0","INV/2019/0003","24" diff --git a/account_bank_statement_import_paypal/tests/test_account_bank_statement_import_paypal.py b/account_bank_statement_import_paypal/tests/test_account_bank_statement_import_paypal.py new file mode 100644 index 0000000..8ad24de --- /dev/null +++ b/account_bank_statement_import_paypal/tests/test_account_bank_statement_import_paypal.py @@ -0,0 +1,132 @@ +# Copyright 2019 Tecnativa - Vicent Cubells +# Copyright 2019 Brainbean Apps (https://brainbeanapps.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields +from odoo.exceptions import UserError +from odoo.tests import common + +from base64 import b64encode +from os import path + + +class TestAccountBankStatementImportPayPal(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.paypal_statement_map_en = self.env.ref( + 'account_bank_statement_import_paypal.paypal_statement_map_en' + ) + self.paypal_activity_map_en = self.env.ref( + 'account_bank_statement_import_paypal.paypal_activity_map_en' + ) + self.AccountJournal = self.env['account.journal'] + self.AccountBankStatement = self.env['account.bank.statement'] + self.AccountBankStatementImport = self.env[ + 'account.bank.statement.import' + ] + self.AccountBankStatementImportPayPalMapping = self.env[ + 'account.bank.statement.import.paypal.mapping' + ] + self.AccountBankStatementImportPayPalMappingWizard = self.env[ + 'account.bank.statement.import.paypal.mapping.wizard' + ] + + def _data_file(self, filename): + with open(path.join(path.dirname(__file__), filename)) as file: + return b64encode(file.read().encode('utf-8')) + + def test_import_statement_en(self): + journal = self.AccountJournal.create({ + 'name': 'PayPal', + 'type': 'bank', + 'code': 'PP', + 'currency_id': self.currency_usd.id, + }) + wizard = self.AccountBankStatementImport.with_context({ + 'journal_id': journal.id, + }).create({ + 'filename': 'fixtures/statement_en.csv', + 'data_file': self._data_file('fixtures/statement_en.csv'), + 'paypal_mapping_id': self.paypal_statement_map_en.id, + }) + wizard.with_context({ + 'journal_id': journal.id, + 'account_bank_statement_import_paypal_test': True, + }).import_file() + statement = self.AccountBankStatement.search([ + ('journal_id', '=', journal.id), + ]) + self.assertEqual(len(statement), 1) + self.assertEqual(len(statement.line_ids), 18) + + def test_import_activity_en(self): + journal = self.AccountJournal.create({ + 'name': 'PayPal', + 'type': 'bank', + 'code': 'PP', + 'currency_id': self.currency_usd.id, + }) + wizard = self.AccountBankStatementImport.with_context({ + 'journal_id': journal.id, + }).create({ + 'filename': 'fixtures/activity_en.csv', + 'data_file': self._data_file('fixtures/activity_en.csv'), + 'paypal_mapping_id': self.paypal_activity_map_en.id, + }) + wizard.with_context({ + 'journal_id': journal.id, + 'account_bank_statement_import_paypal_test': True, + }).import_file() + statement = self.AccountBankStatement.search([ + ('journal_id', '=', journal.id), + ]) + self.assertEqual(len(statement), 1) + self.assertEqual(len(statement.line_ids), 71) + + def test_import_empty_activity(self): + journal = self.AccountJournal.create({ + 'name': 'PayPal', + 'type': 'bank', + 'code': 'PP', + 'currency_id': self.currency_usd.id, + }) + wizard = self.AccountBankStatementImport.with_context({ + 'journal_id': journal.id, + }).create({ + 'filename': 'fixtures/empty_activity.csv', + 'data_file': self._data_file('fixtures/empty_activity.csv'), + 'paypal_mapping_id': self.paypal_activity_map_en.id, + }) + with self.assertRaises(UserError): + wizard.with_context({ + 'journal_id': journal.id, + 'account_bank_statement_import_paypal_test': True, + }).import_file() + statement = self.AccountBankStatement.search([ + ('journal_id', '=', journal.id), + ]) + self.assertEqual(len(statement), 0) + + def test_import_activity_mapping_en(self): + wizard = self.AccountBankStatementImportPayPalMappingWizard.create({ + 'filename': 'fixtures/activity_en.csv', + 'data_file': self._data_file('fixtures/activity_en.csv'), + }) + mapping = self.AccountBankStatementImportPayPalMapping.browse( + wizard.import_mapping()['res_id'] + ) + self.assertTrue(mapping) + + def test_import_statement_mapping_en(self): + wizard = self.AccountBankStatementImportPayPalMappingWizard.create({ + 'filename': 'fixtures/statement_en.csv', + 'data_file': self._data_file('fixtures/statement_en.csv'), + }) + mapping = self.AccountBankStatementImportPayPalMapping.browse( + wizard.import_mapping()['res_id'] + ) + self.assertTrue(mapping) diff --git a/account_bank_statement_import_paypal/tests/test_paypal_statement_import.py b/account_bank_statement_import_paypal/tests/test_paypal_statement_import.py deleted file mode 100644 index 632aec9..0000000 --- a/account_bank_statement_import_paypal/tests/test_paypal_statement_import.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright 2019 Tecnativa - Vicent Cubells -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -import os -import base64 -from odoo.tests import common - - -class TestPaypalFile(common.SavepointCase): - @classmethod - def setUpClass(cls): - super(TestPaypalFile, cls).setUpClass() - - cls.map = cls.env['account.bank.statement.import.paypal.map'].create({ - 'name': 'Paypal Map Test', - }) - usd = cls.env.ref('base.USD') - cls.journal = cls.env['account.journal'].create({ - 'name': 'Paypal Bank', - 'type': 'bank', - 'code': 'PYPAL', - 'currency_id': ( - usd.id if cls.env.user.company_id.currency_id != usd else False - ), - }) - - def _do_import(self, file_name): - file_name = os.path.join(os.path.dirname(__file__), file_name) - return open(file_name).read() - - def test_import_header(self): - file = self._do_import('paypal_en.csv') - file = base64.b64encode(file.encode("utf-8")) - wizard = self.env['wizard.paypal.map.create'].with_context({ - 'journal_id': self.journal.id, - 'active_ids': [self.map.id], - }).create({'data_file': file}) - wizard.create_map_lines() - self.assertEqual(len(self.map.map_line_ids.ids), 18) - - def test_import_paypal_file(self): - # Current statements before to run the wizard - old_statements = self.env['account.bank.statement'].search([]) - # This journal is for Paypal statements - paypal_map = self.env.ref( - 'account_bank_statement_import_paypal.paypal_map' - ) - self.journal.paypal_map_id = paypal_map.id - file = self._do_import('paypal_en.csv') - file = base64.b64encode(file.encode("utf-8")) - wizard = self.env['account.bank.statement.import'].with_context({ - 'journal_id': self.journal.id, - }).create({'data_file': file}) - wizard.import_file() - staments_now = self.env['account.bank.statement'].search([]) - statement = staments_now - old_statements - self.assertEqual(len(statement.line_ids), 3) - self.assertEqual(len(statement.mapped('line_ids').filtered( - lambda x: x.partner_id)), 1) - self.assertAlmostEqual( - sum(statement.mapped('line_ids.amount')), 1489.2 - ) diff --git a/account_bank_statement_import_paypal/wizards/account_bank_statement_import_view.xml b/account_bank_statement_import_paypal/views/account_bank_statement_import.xml similarity index 51% rename from account_bank_statement_import_paypal/wizards/account_bank_statement_import_view.xml rename to account_bank_statement_import_paypal/views/account_bank_statement_import.xml index ecb7364..6d5d7bf 100644 --- a/account_bank_statement_import_paypal/wizards/account_bank_statement_import_view.xml +++ b/account_bank_statement_import_paypal/views/account_bank_statement_import.xml @@ -1,12 +1,21 @@ + + account.bank.statement.import account.bank.statement.import -
  • Paypal with Template:
  • +
  • + PayPal Report mapping: +
  • diff --git a/account_bank_statement_import_paypal/views/account_bank_statement_import_paypal_mapping.xml b/account_bank_statement_import_paypal/views/account_bank_statement_import_paypal_mapping.xml new file mode 100644 index 0000000..05ed5fe --- /dev/null +++ b/account_bank_statement_import_paypal/views/account_bank_statement_import_paypal_mapping.xml @@ -0,0 +1,78 @@ + + + + + + account.bank.statement.import.paypal.mapping.form + account.bank.statement.import.paypal.mapping + + + + + + + + + account.bank.statement.import.paypal.mapping.tree + account.bank.statement.import.paypal.mapping + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + + + + + PayPal Report Mappings + account.bank.statement.import.paypal.mapping + form + tree,form + + + + +
    diff --git a/account_bank_statement_import_paypal/views/account_journal_views.xml b/account_bank_statement_import_paypal/views/account_journal_views.xml deleted file mode 100644 index 3e565b7..0000000 --- a/account_bank_statement_import_paypal/views/account_journal_views.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - account.journal - - - - - - - - - - - diff --git a/account_bank_statement_import_paypal/views/paypal_map_views.xml b/account_bank_statement_import_paypal/views/paypal_map_views.xml deleted file mode 100644 index 1fee4a6..0000000 --- a/account_bank_statement_import_paypal/views/paypal_map_views.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - - account.bank.statement.import.paypal.map - - - - - - - - - account.bank.statement.import.paypal.map - -
    - - - - - - - - - -
    - - - account.bank.statement.import.paypal.map.line - - - - - - - - - - - - account.bank.statement.import.paypal.map.line - -
    - - - - - - - -
    -
    -
    - - - Paypal Mapping - account.bank.statement.import.paypal.map - form - tree,form - - - - -
    diff --git a/account_bank_statement_import_paypal/wizards/__init__.py b/account_bank_statement_import_paypal/wizards/__init__.py index 006d24c..5aeccea 100644 --- a/account_bank_statement_import_paypal/wizards/__init__.py +++ b/account_bank_statement_import_paypal/wizards/__init__.py @@ -1,2 +1,3 @@ -from . import create_map_lines_from_file -from . import account_bank_statement_import_paypal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import account_bank_statement_import_paypal_mapping_wizard diff --git a/account_bank_statement_import_paypal/wizards/account_bank_statement_import_paypal.py b/account_bank_statement_import_paypal/wizards/account_bank_statement_import_paypal.py deleted file mode 100644 index 5eb194b..0000000 --- a/account_bank_statement_import_paypal/wizards/account_bank_statement_import_paypal.py +++ /dev/null @@ -1,300 +0,0 @@ -# Copyright 2014-2017 Akretion (http://www.akretion.com). -# @author Alexis de Lattre -# @author Sébastien BEAU -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -import logging -from datetime import datetime -from odoo import _, api, fields, models -from odoo.exceptions import UserError -import re -from io import StringIO -_logger = logging.getLogger(__name__) - -try: - import csv -except (ImportError, IOError) as err: - _logger.debug(err) - -# Paypal header depend of the country the order are the same but the -# value are translated. You can add you header here - -HEADERS = [ - # French - '"Date","Heure","Fuseau horaire","Description","Devise","Avant commission"' - ',"Commission","Net","Solde","Numéro de transaction","Adresse email de ' - 'l\'expéditeur","Nom","Nom de la banque","Compte bancaire","Montant des ' - 'frais de livraison et de traitement","TVA","Identifiant de facture",' - '"Numéro de la transaction de référence"', - # English - '"Date","Time","Time Zone","Description","Currency","Gross ","Fee ","Net",' - '"Balance","Transaction ID","From Email Address","Name","Bank Name",' - '"Bank Account","Shipping and Handling Amount","Sales Tax","Invoice ID",' - '"Reference Txn ID"', - ] - - -class AccountBankStatementImport(models.TransientModel): - _inherit = 'account.bank.statement.import' - - paypal_map_id = fields.Many2one( - comodel_name='account.bank.statement.import.paypal.map', - string='Paypal map', - readonly=True, - ) - - @api.model - def _get_paypal_encoding(self): - return 'utf-8-sig' - - @api.model - def _get_paypal_str_data(self, data_file): - if not isinstance(data_file, str): - data_file = data_file.decode(self._get_paypal_encoding()) - return data_file.strip() - - @api.model - def _paypal_convert_amount(self, amount_str): - if self.paypal_map_id: - thousands, decimal = self.paypal_map_id._get_separators() - else: - thousands, decimal = ',', '.' - valstr = re.sub(r'[^\d%s%s.-]' % (thousands, decimal), '', amount_str) - valstrdot = valstr.replace(thousands, '') - valstrdot = valstrdot.replace(decimal, '.') - return float(valstrdot) - - @api.model - def _check_paypal(self, data_file): - if not self.paypal_map_id: - return False - try: - data_file = self._get_paypal_str_data(data_file) - except UnicodeDecodeError as e: - _logger.debug(e) - return False - headers = self.mapped('paypal_map_id.map_line_ids.name') - file_headers = data_file.split('\n', 1)[0] - if any(item not in file_headers for item in headers): - raise UserError( - _("Headers of file to import and Paypal map lines does not " - "match.")) - return True - - def _convert_paypal_line_to_dict(self, idx, line): - rline = dict() - for item in range(len(line)): - paypal_map = self.mapped('paypal_map_id.map_line_ids')[item] - value = line[item] - if not paypal_map.field_to_assign: - continue - if paypal_map.date_format: - try: - value = fields.Date.to_string( - datetime.strptime(value, paypal_map.date_format)) - except Exception: - raise UserError( - _("Date format of map file and Paypal date does " - "not match.")) - rline[paypal_map.field_to_assign] = value - - for field in ['commission', 'amount', 'balance']: - _logger.debug('Trying to convert %s to float' % rline[field]) - try: - rline[field] = self._paypal_convert_amount(rline[field]) - except Exception: - raise UserError( - _("Value '%s' for the field '%s' on line %d, " - "cannot be converted to float") - % (rline[field], field, idx)) - return rline - - def _parse_paypal_file(self, data_file): - data_file = self._get_paypal_str_data(data_file) - f = StringIO(data_file) - f.seek(0) - raw_lines = [] - reader = csv.reader(f) - next(reader) # Drop header - for idx, line in enumerate(reader): - _logger.debug("Line %d: %s" % (idx, line)) - raw_lines.append(self._convert_paypal_line_to_dict(idx, line)) - return raw_lines - - def _prepare_paypal_currency_vals(self, cline): - currencies = self.env['res.currency'].search( - [('name', '=', cline['currency'])]) - if not currencies: - raise UserError( - _('currency %s on line %d cannot be found in odoo') - % (cline['currency'], cline['idx'])) - return { - 'amount_currency': cline['amount'], - 'currency_id': currencies.id, - 'currency': cline['currency'], - 'partner_name': cline['partner_name'], - 'description': cline['description'], - 'email': cline['email'], - 'transaction_id': cline['transaction_id'], - } - - def _get_journal(self): - journal_id = self.env.context.get('journal_id') - if not journal_id: - raise UserError(_('You must run this wizard from the journal')) - return self.env['account.journal'].browse(journal_id) - - def _post_process_statement_line(self, raw_lines): - journal = self._get_journal() - currency = journal.currency_id or journal.company_id.currency_id - currency_change_lines = {} - real_transactions = [] - for line in raw_lines: - if line['currency'] != currency.name: - currency_change_lines[line['transaction_id']] = line - else: - real_transactions.append(line) - - for line in real_transactions: - # Check if the current transaction is linked with a - # transaction of currency change if yes merge the transaction - # as for odoo it's only one line - cline = currency_change_lines.get(line['origin_transaction_id']) - if cline: - # we update the current line with currency information - vals = self._prepare_paypal_currency_vals(cline) - line.update(vals) - return real_transactions - - def _prepare_paypal_statement_line(self, fline): - if fline['bank_name']: - name = '|'.join([ - fline['description'], - fline['bank_name'], - fline['bank_account'] - ]) - else: - name = '|'.join([ - fline['description'], - fline['partner_name'], - fline['email'], - fline['invoice_number'], - ]) - return { - 'date': fline['date'], - 'name': name, - 'ref': fline['transaction_id'], - 'unique_import_id': - fline['transaction_id'] + fline['date'] + fline['time'], - 'amount': fline['amount'], - 'bank_account_id': False, - 'currency_id': fline.get('currency_id'), - 'amount_currency': fline.get('amount_currency'), - } - - def _prepare_paypal_statement(self, lines): - return { - 'name': - _('PayPal Import %s > %s') - % (lines[0]['date'], lines[-1]['date']), - 'date': lines[-1]['date'], - 'balance_start': - lines[0]['balance'] - - lines[0]['amount'] - - lines[0]['commission'], - 'balance_end_real': lines[-1]['balance'], - } - - @api.model - def _parse_file(self, data_file): - """ Import a file in Paypal CSV format """ - paypal = self._check_paypal(data_file) - if not paypal: - return super(AccountBankStatementImport, self)._parse_file( - data_file) - - raw_lines = self._parse_paypal_file(data_file) - final_lines = self._post_process_statement_line(raw_lines) - - vals_bank_statement = self._prepare_paypal_statement(final_lines) - - transactions = [] - commission_total = 0 - for fline in final_lines: - commission_total += fline['commission'] - vals_line = self._prepare_paypal_statement_line(fline) - _logger.debug("vals_line = %s" % vals_line) - transactions.append(vals_line) - - if commission_total: - commission_line = { - 'date': vals_bank_statement['date'], - 'name': _('Paypal commissions'), - 'ref': _('PAYPAL-COSTS'), - 'amount': commission_total, - 'unique_import_id': False, - } - transactions.append(commission_line) - - vals_bank_statement['transactions'] = transactions - return None, None, [vals_bank_statement] - - @api.model - def _get_paypal_partner(self, description, partner_name, - partner_email, invoice_number): - if invoice_number: - # In most case e-commerce case invoice_number - # will contain the sale order number - sale = self.env['sale.order'].search([ - ('name', '=', invoice_number)]) - if sale and len(sale) == 1: - return sale.partner_id.commercial_partner_id - - invoice = self.env['account.invoice'].search([ - ('number', '=', invoice_number)]) - if invoice and len(invoice) == 1: - return invoice.partner_id.commercial_partner_id - - if partner_email: - partner = self.env['res.partner'].search([ - ('email', '=', partner_email), - ('parent_id', '=', False)]) - if partner and len(partner) == 1: - return partner.commercial_partner_id - - if partner_name: - partner = self.env['res.partner'].search([ - ('name', '=ilike', partner_name)]) - if partner and len(partner) == 1: - return partner.commercial_partner_id - return None - - @api.model - def _complete_paypal_statement_line(self, line): - _logger.debug('Process line %s', line['name']) - info = line['name'].split('|') - if len(info) == 4: - partner = self._get_paypal_partner(*info) - if partner: - return { - 'partner_id': partner.id, - } - return None - - @api.model - def _complete_stmts_vals(self, stmts_vals, journal_id, account_number): - """ Match the partner from paypal information """ - stmts_vals = super(AccountBankStatementImport, self). \ - _complete_stmts_vals(stmts_vals, journal_id, account_number) - for line in stmts_vals[0]['transactions']: - vals = self._complete_paypal_statement_line(line) - if vals: - line.update(vals) - return stmts_vals - - @api.model - def default_get(self, fields): - res = super(AccountBankStatementImport, self).default_get(fields) - journal = self._get_journal() - res['paypal_map_id'] = journal.paypal_map_id.id - return res diff --git a/account_bank_statement_import_paypal/wizards/account_bank_statement_import_paypal_mapping_wizard.py b/account_bank_statement_import_paypal/wizards/account_bank_statement_import_paypal_mapping_wizard.py new file mode 100644 index 0000000..e7eb45c --- /dev/null +++ b/account_bank_statement_import_paypal/wizards/account_bank_statement_import_paypal_mapping_wizard.py @@ -0,0 +1,84 @@ +# Copyright 2019 Tecnativa - Vicent Cubells +# Copyright 2019 Brainbean Apps (https://brainbeanapps.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models, _ +from odoo.exceptions import UserError + +from base64 import b64decode +from os import path + + +class AccountBankStatementImportPayPalMappingWizard(models.TransientModel): + _name = 'account.bank.statement.import.paypal.mapping.wizard' + _description = 'Account Bank Statement Import PayPal Mapping Wizard' + + data_file = fields.Binary( + string='Bank Statement File', + required=True, + ) + filename = fields.Char() + + @api.multi + def import_mapping(self): + self.ensure_one() + mapping_values = { + 'name': _('Mapping from %s') % path.basename(self.filename), + 'float_thousands_sep': 'comma', + 'float_decimal_sep': 'dot', + 'date_format': '%d/%m/%Y', + 'time_format': '%H:%M:%S', + } + header = self.env['account.bank.statement.import.paypal.parser'] \ + .parse_header(b64decode(self.data_file)) + if len(header) == 22: + mapping_values.update({ + 'date_column': header[0], + 'time_column': header[1], + 'tz_column': header[2], + 'name_column': header[3], + 'currency_column': header[6], + 'gross_column': header[7], + 'fee_column': header[8], + 'balance_column': header[18], + 'transaction_id_column': header[12], + 'type_column': header[4], + 'from_email_address_column': header[10], + 'to_email_address_column': header[11], + 'invoice_id_column': header[16], + 'subject_column': header[20], + 'note_column': header[21], + }) + elif len(header) == 18: + mapping_values.update({ + 'date_column': header[0], + 'time_column': header[1], + 'tz_column': header[2], + 'name_column': header[11], + 'currency_column': header[4], + 'gross_column': header[5], + 'fee_column': header[6], + 'balance_column': header[8], + 'transaction_id_column': header[9], + 'description_column': header[3], + 'from_email_address_column': header[10], + 'invoice_id_column': header[16], + 'bank_name_column': header[12], + 'bank_account_column': header[13], + }) + else: + raise UserError(_( + 'File structure does not look like a PayPal report, please ' + 'check the file or create the mapping manually.' + )) + mapping = self.env['account.bank.statement.import.paypal.mapping']\ + .create(mapping_values) + return { + 'type': 'ir.actions.act_window', + 'name': _('Imported Mapping'), + 'res_model': 'account.bank.statement.import.paypal.mapping', + 'res_id': mapping.id, + 'view_mode': 'form', + 'view_id': False, + 'target': 'new', + } diff --git a/account_bank_statement_import_paypal/wizards/account_bank_statement_import_paypal_mapping_wizard.xml b/account_bank_statement_import_paypal/wizards/account_bank_statement_import_paypal_mapping_wizard.xml new file mode 100644 index 0000000..148e5b4 --- /dev/null +++ b/account_bank_statement_import_paypal/wizards/account_bank_statement_import_paypal_mapping_wizard.xml @@ -0,0 +1,34 @@ + + + + + + account.bank.statement.import.paypal.mapping.wizard.form + account.bank.statement.import.paypal.mapping.wizard + +
    +

    Select a PayPal report file to import mapping.

    + + +
    +
    + +
    +
    + + + Import Mapping + account.bank.statement.import.paypal.mapping.wizard + account.bank.statement.import.paypal.mapping + form + form + new + + +
    \ No newline at end of file diff --git a/account_bank_statement_import_paypal/wizards/create_map_lines_from_file.py b/account_bank_statement_import_paypal/wizards/create_map_lines_from_file.py deleted file mode 100644 index a9ca019..0000000 --- a/account_bank_statement_import_paypal/wizards/create_map_lines_from_file.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2019 Tecnativa - Vicent Cubells -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -import csv -import base64 -from odoo import api, fields, models -from io import StringIO - - -class WizardPaypalMapCreate(models.TransientModel): - _name = 'wizard.paypal.map.create' - _description = 'Wizard Paypal Map Create' - - data_file = fields.Binary( - string='Bank Statement File', - required=True, - ) - filename = fields.Char() - - @api.multi - def create_map_lines(self): - statement_obj = self.env['account.bank.statement.import.paypal.map'] - data_file = base64.b64decode(self.data_file) - if not isinstance(data_file, str): - data_file = data_file.decode('utf-8-sig').strip() - file = StringIO(data_file) - file.seek(0) - reader = csv.reader(file) - headers = [] - for row in reader: - headers = row - break - lines = [] - for idx, title in enumerate(headers): - lines.append((0, 0, {'sequence': idx, 'name': title})) - if lines: - for statement in statement_obj.browse( - self.env.context.get('active_ids')): - statement.map_line_ids = lines diff --git a/account_bank_statement_import_paypal/wizards/create_map_lines_from_file_views.xml b/account_bank_statement_import_paypal/wizards/create_map_lines_from_file_views.xml deleted file mode 100644 index c61136d..0000000 --- a/account_bank_statement_import_paypal/wizards/create_map_lines_from_file_views.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - Create Paypal Map Lines - wizard.paypal.map.create - -
    -

    Select a Paypal bank statement file to create all the map lines from headers file.

    -

    Download a bank statement from your bank and import it here.

    -

    All the Paypal map lines will be created automatically.

    - - -
    -
    - -
    -
    - - -