mirror of
https://github.com/OCA/bank-statement-import.git
synced 2025-01-20 12:37:43 +02:00
[ADD] Adyen statement import
This commit is contained in:
committed by
Ronald Portier (Therp BV)
parent
f625bd6293
commit
ce6a6d6852
69
account_bank_statement_import_adyen/README.rst
Normal file
69
account_bank_statement_import_adyen/README.rst
Normal file
@@ -0,0 +1,69 @@
|
||||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
|
||||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
||||
:alt: License: AGPL-3
|
||||
|
||||
======================
|
||||
Adyen statement import
|
||||
======================
|
||||
|
||||
This module processes Adyen transaction statements in xlsx format. You can
|
||||
import the statements in a dedicated journal. Reconcile your sale invoices
|
||||
with the credit transations. Reconcile the aggregated counterpart
|
||||
transaction with the transaction in your real bank journal and register the
|
||||
aggregated fee line containing commision and markup on the applicable
|
||||
cost account.
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Configure a pseudo bank journal by creating a new journal with a dedicated
|
||||
Adyen clearing account as the default ledger account. Set your merchant
|
||||
account string in the Advanced settings on the journal form.
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
After installing this module, you can import your Adyen transaction statements
|
||||
through Menu Finance -> Bank -> Import. Don't enter a journal in the import
|
||||
wizard.
|
||||
|
||||
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
|
||||
:alt: Try me on Runbot
|
||||
:target: https://runbot.odoo-community.org/runbot/174/8.0
|
||||
|
||||
|
||||
Bug Tracker
|
||||
===========
|
||||
|
||||
Bugs are tracked on `GitHub Issues
|
||||
<https://github.com/OCA/bank-statement-import/issues>`_. In case of trouble, please
|
||||
check there if your issue has already been reported. If you spotted it first,
|
||||
help us smash it by providing detailed and welcomed feedback.
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
||||
Images
|
||||
------
|
||||
|
||||
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
* Stefan Rijnhart <stefan@opener.am>
|
||||
|
||||
Maintainer
|
||||
----------
|
||||
|
||||
.. image:: https://odoo-community.org/logo.png
|
||||
:alt: Odoo Community Association
|
||||
:target: https://odoo-community.org
|
||||
|
||||
This module is maintained by the OCA.
|
||||
|
||||
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.
|
||||
|
||||
To contribute to this module, please visit https://odoo-community.org.
|
||||
1
account_bank_statement_import_adyen/__init__.py
Normal file
1
account_bank_statement_import_adyen/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
18
account_bank_statement_import_adyen/__openerp__.py
Normal file
18
account_bank_statement_import_adyen/__openerp__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# coding: utf-8
|
||||
# © 2017 Opener BV (<https://opener.amsterdam>)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
{
|
||||
'name': 'Adyen statement import',
|
||||
'version': '8.0.1.0.0',
|
||||
'author': 'Opener BV, Odoo Community Association (OCA)',
|
||||
'category': 'Banking addons',
|
||||
'website': 'https://github.com/oca/bank-statement-import',
|
||||
'depends': [
|
||||
'account_bank_statement_import',
|
||||
'account_bank_statement_clearing_account',
|
||||
],
|
||||
'data': [
|
||||
'views/account_journal.xml',
|
||||
],
|
||||
'installable': True,
|
||||
}
|
||||
2
account_bank_statement_import_adyen/models/__init__.py
Normal file
2
account_bank_statement_import_adyen/models/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import account_bank_statement_import
|
||||
from . import account_journal
|
||||
@@ -0,0 +1,139 @@
|
||||
# coding: utf-8
|
||||
# © 2017 Opener BV (<https://opener.amsterdam>)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
from io import BytesIO
|
||||
from openpyxl import load_workbook
|
||||
from zipfile import BadZipfile
|
||||
|
||||
from openerp import models, api
|
||||
from openerp.exceptions import Warning as UserError
|
||||
from openerp.tools.misc import DEFAULT_SERVER_DATE_FORMAT as DATEFMT
|
||||
from openerp.tools.translate import _
|
||||
from openerp.addons.account_bank_statement_import.parserlib import (
|
||||
BankStatement)
|
||||
|
||||
|
||||
class Import(models.TransientModel):
|
||||
_inherit = 'account.bank.statement.import'
|
||||
|
||||
@api.model
|
||||
def _parse_file(self, data_file):
|
||||
"""Parse an Adyen xlsx file and map merchant account strings
|
||||
to journals. """
|
||||
try:
|
||||
statements = self.import_adyen_xlsx(data_file)
|
||||
except ValueError:
|
||||
return super(Import, self)._parse_file(data_file)
|
||||
|
||||
for statement in statements:
|
||||
merchant_id = statement['account_number']
|
||||
journal = self.env['account.journal'].search([
|
||||
('adyen_merchant_account', '=', merchant_id)], limit=1)
|
||||
if journal:
|
||||
statement['adyen_journal_id'] = journal.id
|
||||
else:
|
||||
raise UserError(
|
||||
_('Please create a journal with merchant account "%s"') %
|
||||
merchant_id)
|
||||
statement['account_number'] = False
|
||||
return statements
|
||||
|
||||
@api.model
|
||||
def _import_statement(self, stmt_vals):
|
||||
""" Propagate found journal to context, fromwhere it is picked up
|
||||
in _get_journal """
|
||||
journal_id = stmt_vals.pop('adyen_journal_id', None)
|
||||
if journal_id:
|
||||
self = self.with_context(journal_id=journal_id)
|
||||
return super(Import, self)._import_statement(stmt_vals)
|
||||
|
||||
@api.model
|
||||
def balance(self, row):
|
||||
return -(row[15] or 0) + sum(
|
||||
row[i] if row[i] else 0.0
|
||||
for i in (16, 17, 18, 19, 20))
|
||||
|
||||
@api.model
|
||||
def import_adyen_transaction(self, statement, row):
|
||||
transaction = statement.create_transaction()
|
||||
transaction.value_date = row[6].strftime(DATEFMT)
|
||||
transaction.transferred_amount = self.balance(row)
|
||||
transaction.note = (
|
||||
'%s %s %s %s' % (row[2], row[3], row[4], row[21]))
|
||||
transaction.message = "%s" % (row[3] or row[4] or row[9])
|
||||
return transaction
|
||||
|
||||
@api.model
|
||||
def import_adyen_xlsx(self, data_file):
|
||||
statements = []
|
||||
statement = None
|
||||
headers = False
|
||||
fees = 0.0
|
||||
balance = 0.0
|
||||
payout = 0.0
|
||||
|
||||
with BytesIO() as buf:
|
||||
buf.write(data_file)
|
||||
try:
|
||||
sheet = load_workbook(buf)._sheets[0]
|
||||
except BadZipfile as e:
|
||||
raise ValueError(e)
|
||||
for row in sheet.rows:
|
||||
row = [cell.value for cell in row]
|
||||
if len(row) != 31:
|
||||
raise ValueError(
|
||||
'Not an Adyen statement. Unexpected row length %s '
|
||||
'instead of 31' % len(row))
|
||||
if not row[1]:
|
||||
continue
|
||||
if not headers:
|
||||
if row[1] != 'Company Account':
|
||||
raise ValueError(
|
||||
'Not an Adyen statement. Unexpected header "%s" '
|
||||
'instead of "Company Account"', row[1])
|
||||
headers = True
|
||||
continue
|
||||
if not statement:
|
||||
statement = BankStatement()
|
||||
statements.append(statement)
|
||||
statement.statement_id = '%s %s/%s' % (
|
||||
row[2], row[6].strftime('%Y'), int(row[23]))
|
||||
statement.local_currency = row[14]
|
||||
statement.local_account = row[2]
|
||||
date = row[6].strftime(DATEFMT)
|
||||
if not statement.date or statement.date > date:
|
||||
statement.date = date
|
||||
|
||||
row[8] = row[8].strip()
|
||||
if row[8] == 'MerchantPayout':
|
||||
payout -= self.balance(row)
|
||||
else:
|
||||
balance += self.balance(row)
|
||||
self.import_adyen_transaction(statement, row)
|
||||
fees += sum(
|
||||
row[i] if row[i] else 0.0
|
||||
for i in (17, 18, 19, 20))
|
||||
|
||||
if not headers:
|
||||
raise ValueError(
|
||||
'Not an Adyen statement. Did not encounter header row.')
|
||||
|
||||
if fees:
|
||||
transaction = statement.create_transaction()
|
||||
transaction.value_date = max(
|
||||
t.value_date for t in statement['transactions'])
|
||||
transaction.transferred_amount = -fees
|
||||
balance -= fees
|
||||
transaction.message = 'Commision, markup etc. batch %s' % (
|
||||
int(row[23]))
|
||||
|
||||
if statement['transactions'] and not payout:
|
||||
raise UserError(
|
||||
_('No payout detected in Adyen statement.'))
|
||||
if self.env.user.company_id.currency_id.compare_amounts(
|
||||
balance, payout) != 0:
|
||||
raise UserError(
|
||||
_('Parse error. Balance %s not equal to merchant '
|
||||
'payout %s') % (balance, payout))
|
||||
|
||||
return statements
|
||||
@@ -0,0 +1,10 @@
|
||||
# coding: utf-8
|
||||
from openerp import fields, models
|
||||
|
||||
|
||||
class Journal(models.Model):
|
||||
_inherit = 'account.journal'
|
||||
|
||||
adyen_merchant_account = fields.Char(
|
||||
help=('Fill in the exact merchant account string to select this '
|
||||
'journal when importing Adyen statements'))
|
||||
BIN
account_bank_statement_import_adyen/test_files/adyen_test.xlsx
Normal file
BIN
account_bank_statement_import_adyen/test_files/adyen_test.xlsx
Normal file
Binary file not shown.
Binary file not shown.
1
account_bank_statement_import_adyen/tests/__init__.py
Normal file
1
account_bank_statement_import_adyen/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import test_import_adyen
|
||||
@@ -0,0 +1,55 @@
|
||||
# coding: utf-8
|
||||
from openerp.addons.account_bank_statement_import.tests import (
|
||||
TestStatementFile)
|
||||
|
||||
|
||||
class TestImportAdyen(TestStatementFile):
|
||||
def setUp(self):
|
||||
super(TestImportAdyen, self).setUp()
|
||||
self.journal = self.env['account.journal'].search(
|
||||
[('type', '=', 'bank')], limit=1)
|
||||
self.journal.default_debit_account_id.reconcile = True
|
||||
self.journal.write({
|
||||
'adyen_merchant_account': 'YOURCOMPANY_ACCOUNT',
|
||||
'update_posted': True,
|
||||
})
|
||||
|
||||
def test_import_adyen(self):
|
||||
self._test_statement_import(
|
||||
'account_bank_statement_import_adyen', 'adyen_test.xlsx',
|
||||
'YOURCOMPANY_ACCOUNT 2016/48')
|
||||
statement = self.env['account.bank.statement'].search(
|
||||
[], order='create_date desc', limit=1)
|
||||
self.assertEqual(len(statement.line_ids), 22)
|
||||
self.assertTrue(
|
||||
self.env.user.company_id.currency_id.is_zero(
|
||||
sum(line.amount for line in statement.line_ids)))
|
||||
|
||||
account = self.env['account.account'].search([(
|
||||
'type', '=', 'receivable')], limit=1)
|
||||
for line in statement.line_ids:
|
||||
line.process_reconciliation([{
|
||||
'debit': -line.amount if line.amount < 0 else 0,
|
||||
'credit': line.amount if line.amount > 0 else 0,
|
||||
'account_id': account.id}])
|
||||
|
||||
statement.button_confirm_bank()
|
||||
self.assertEqual(statement.state, 'confirm')
|
||||
lines = self.env['account.move.line'].search([
|
||||
('account_id', '=', self.journal.default_debit_account_id.id),
|
||||
('statement_id', '=', statement.id)])
|
||||
reconcile = lines.mapped('reconcile_id')
|
||||
self.assertEqual(len(reconcile), 1)
|
||||
self.assertFalse(lines.mapped('reconcile_partial_id'))
|
||||
self.assertEqual(lines, reconcile.line_id)
|
||||
|
||||
statement.button_draft()
|
||||
self.assertEqual(statement.state, 'draft')
|
||||
self.assertFalse(lines.mapped('reconcile_partial_id'))
|
||||
self.assertFalse(lines.mapped('reconcile_id'))
|
||||
|
||||
def test_import_adyen_credit_fees(self):
|
||||
self._test_statement_import(
|
||||
'account_bank_statement_import_adyen',
|
||||
'adyen_test_credit_fees.xlsx',
|
||||
'YOURCOMPANY_ACCOUNT 2016/8')
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record id="view_account_journal_form" model="ir.ui.view">
|
||||
<field name="name">Add Adyen merchant account</field>
|
||||
<field name="model">account.journal</field>
|
||||
<field name="inherit_id" ref="account.view_account_journal_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="sequence_id" position="after">
|
||||
<field name="adyen_merchant_account"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
Reference in New Issue
Block a user