diff --git a/account_bank_statement_import_paypal/README.rst b/account_bank_statement_import_paypal/README.rst new file mode 100644 index 0000000..bdbff9f --- /dev/null +++ b/account_bank_statement_import_paypal/README.rst @@ -0,0 +1,105 @@ +============================= +Import Paypal Bank Statements +============================= + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :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 + :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/11.0/account_bank_statement_import_paypal + :alt: OCA/bank-statement-import +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/bank-statement-import-11-0/bank-statement-import-11-0-account_bank_statement_import_paypal + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/174/11.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows you to import the Paypal CSV files in Odoo as bank statements. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +In the menu Accounting > Configuration > Accounting > Bank Accounts, +make sure that you have your Paypal bank account with the following parameters: +* Bank Account Type: Normal Bank Account +* Account Number: the email address associated with your Paypal account +* Account Journal: the journal associated to your Paypal account + +TIPS +---- +For now only French and English report are supported. +For adding new support you just need to add your header in +model/account_bank_statement_import_paypal.py in the variables HEADERS. +Please help us and do a PR for adding new header ! Thanks + +Usage +===== + +To use this module, you need to: + +#. Go to Paypal and download your Bank Statement + +.. image:: account_bank_statement_import_paypal/static/description/paypal_backoffice.png + :alt: . +.. image:: static/description/paypal_backoffice.png + :alt: . + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Akretion + +Contributors +~~~~~~~~~~~~ + +* Alexis de Lattre +* Sebastien BEAU +* Tecnativa (https://www.tecnativa.com) + + * Vicent Cubells + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/bank-statement-import `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/account_bank_statement_import_paypal/__init__.py b/account_bank_statement_import_paypal/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/account_bank_statement_import_paypal/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/account_bank_statement_import_paypal/__manifest__.py b/account_bank_statement_import_paypal/__manifest__.py new file mode 100644 index 0000000..ada7c9a --- /dev/null +++ b/account_bank_statement_import_paypal/__manifest__.py @@ -0,0 +1,18 @@ +# 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). +{ + "name": "Import Paypal Bank Statements", + 'summary': 'Import Paypal CSV files as Bank Statements in Odoo', + "version": "11.0.1.0.0", + "category": "Accounting", + "website": "https://github.com/OCA/bank-statement-import", + "author": " Akretion, Odoo Community Association (OCA)", + "license": "AGPL-3", + "application": False, + "installable": True, + "depends": [ + "account_bank_statement_import", + ], +} diff --git a/account_bank_statement_import_paypal/models/__init__.py b/account_bank_statement_import_paypal/models/__init__.py new file mode 100644 index 0000000..4e11f72 --- /dev/null +++ b/account_bank_statement_import_paypal/models/__init__.py @@ -0,0 +1 @@ +from . import account_bank_statement_import_paypal diff --git a/account_bank_statement_import_paypal/models/account_bank_statement_import_paypal.py b/account_bank_statement_import_paypal/models/account_bank_statement_import_paypal.py new file mode 100644 index 0000000..e15d15f --- /dev/null +++ b/account_bank_statement_import_paypal/models/account_bank_statement_import_paypal.py @@ -0,0 +1,281 @@ +# 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' + + @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 _get_paypal_date_format(self): + """ This method is designed to be inherited """ + return '%d/%m/%Y' + + @api.model + def _paypal_convert_amount(self, amount_str): + """ This method is designed to be inherited """ + valstr = re.sub(r'[^\d,.-]', '', amount_str) + valstrdot = valstr.replace('.', '') + valstrdot = valstrdot.replace(',', '.') + return float(valstrdot) + + @api.model + def _check_paypal(self, data_file): + data_file = self._get_paypal_str_data(data_file) + for header in HEADERS: + if data_file.strip().startswith(header): + return True + return False + + def _convert_paypal_line_to_dict(self, idx, line): + date_dt = datetime.strptime(line[0], self._get_paypal_date_format()) + rline = { + 'date': fields.Date.to_string(date_dt), + 'time': line[1], + 'description': line[3], + 'currency': line[4], + 'amount': line[5], + 'commission': line[6], + 'balance': line[8], + 'transaction_id': line[9], + 'email': line[10], + 'partner_name': line[11], + # This two field are useful for bank transfer + 'bank_name': line[12], + 'bank_account': line[13], + 'invoice_number': line[16], + 'origin_transaction_id': line[17], + 'idx': idx, + } + 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 _post_process_statement_line(self, raw_lines): + journal_id = self.env.context.get('journal_id') + if not journal_id: + raise UserError(_('You must run this wizard from the journal')) + journal = self.env['account.journal'].browse(journal_id) + 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, + 'account_id': partner.property_account_receivable.id, + } + return None + + @api.model + def _complete_statement(self, stmts_vals, journal_id, account_number): + """ Match the partner from paypal information """ + stmts_vals = super(AccountBankStatementImport, self).\ + _complete_statement(stmts_vals, journal_id, account_number) + for line in stmts_vals['transactions']: + vals = self._complete_paypal_statement_line(line) + if vals: + line.update(vals) + return stmts_vals diff --git a/account_bank_statement_import_paypal/readme/CONFIGURE.rst b/account_bank_statement_import_paypal/readme/CONFIGURE.rst new file mode 100644 index 0000000..33f0236 --- /dev/null +++ b/account_bank_statement_import_paypal/readme/CONFIGURE.rst @@ -0,0 +1,12 @@ +In the menu Accounting > Configuration > Accounting > Bank Accounts, +make sure that you have your Paypal bank account with the following parameters: +* Bank Account Type: Normal Bank Account +* Account Number: the email address associated with your Paypal account +* Account Journal: the journal associated to your Paypal account + +TIPS +---- +For now only French and English report are supported. +For adding new support you just need to add your header in +model/account_bank_statement_import_paypal.py in the variables HEADERS. +Please help us and do a PR for adding new header ! Thanks diff --git a/account_bank_statement_import_paypal/readme/CONTRIBUTORS.rst b/account_bank_statement_import_paypal/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000..480d383 --- /dev/null +++ b/account_bank_statement_import_paypal/readme/CONTRIBUTORS.rst @@ -0,0 +1,5 @@ +* Alexis de Lattre +* Sebastien BEAU +* Tecnativa (https://www.tecnativa.com) + + * Vicent Cubells diff --git a/account_bank_statement_import_paypal/readme/DESCRIPTION.rst b/account_bank_statement_import_paypal/readme/DESCRIPTION.rst new file mode 100644 index 0000000..4da61e3 --- /dev/null +++ b/account_bank_statement_import_paypal/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module allows you to import the Paypal CSV files in Odoo as bank statements. diff --git a/account_bank_statement_import_paypal/readme/USAGE.rst b/account_bank_statement_import_paypal/readme/USAGE.rst new file mode 100644 index 0000000..fdcc4da --- /dev/null +++ b/account_bank_statement_import_paypal/readme/USAGE.rst @@ -0,0 +1,8 @@ +To use this module, you need to: + +#. Go to Paypal and download your Bank Statement + +.. image:: account_bank_statement_import_paypal/static/description/paypal_backoffice.png + :alt: . +.. image:: static/description/paypal_backoffice.png + :alt: . diff --git a/account_bank_statement_import_paypal/static/description/icon.png b/account_bank_statement_import_paypal/static/description/icon.png new file mode 100644 index 0000000..5a237c2 Binary files /dev/null and b/account_bank_statement_import_paypal/static/description/icon.png differ diff --git a/account_bank_statement_import_paypal/static/description/paypal_backoffice.png b/account_bank_statement_import_paypal/static/description/paypal_backoffice.png new file mode 100644 index 0000000..a902aee Binary files /dev/null and b/account_bank_statement_import_paypal/static/description/paypal_backoffice.png differ