diff --git a/account_move_base_import/__init__.py b/account_move_base_import/__init__.py index 3f3a2791..9de55afe 100644 --- a/account_move_base_import/__init__.py +++ b/account_move_base_import/__init__.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from . import parser from . import wizard from . import models diff --git a/account_move_base_import/__manifest__.py b/account_move_base_import/__manifest__.py index a65c214c..893f4fb7 100644 --- a/account_move_base_import/__manifest__.py +++ b/account_move_base_import/__manifest__.py @@ -1,12 +1,11 @@ -# -*- coding: utf-8 -*- -# © 2011-2016 Akretion -# © 2011-2016 Camptocamp SA -# © 2013 Savoir-faire Linux -# © 2014 ACSONE SA/NV +# Copyright 2011-2016 Akretion +# Copyright 2011-2019 Camptocamp SA +# Copyright 2013 Savoir-faire Linux +# Copyright 2014 ACSONE SA/NV # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) { 'name': "Journal Entry base import", - 'version': '10.0.1.1.0', + 'version': '11.0.1.0.0', 'author': "Akretion,Camptocamp,Odoo Community Association (OCA)", 'category': 'Finance', 'depends': ['account'], @@ -19,13 +18,6 @@ "views/journal_view.xml", "views/partner_view.xml", ], - 'test': [ - 'test/partner.yml', - 'test/invoice.yml', - 'test/supplier_invoice.yml', - 'test/refund.yml', - 'test/completion_test.yml' - ], 'installable': True, 'license': 'AGPL-3', } diff --git a/account_move_base_import/data/statement.csv b/account_move_base_import/data/statement.csv deleted file mode 100644 index 21dead31..00000000 --- a/account_move_base_import/data/statement.csv +++ /dev/null @@ -1,4 +0,0 @@ -"date";"amount";"commission_amount";"label" -2011-03-07 13:45:14;118.4;-11.84;"label a" -2011-03-02 13:45:14;189;-15.12;"label b" -2011-03-02 17:45:14;189;-15.12;"label c" diff --git a/account_move_base_import/demo/statement.csv b/account_move_base_import/demo/statement.csv new file mode 100644 index 00000000..333ee62d --- /dev/null +++ b/account_move_base_import/demo/statement.csv @@ -0,0 +1,4 @@ +date;amount;commission_amount;label +2011-03-07 13:45:14;118.4;-11.84;label a +2011-03-02 13:45:14;189;-15.12;label b +2011-03-02 17:45:14;189;-15.12;label c diff --git a/account_move_base_import/data/statement.xls b/account_move_base_import/demo/statement.xls similarity index 100% rename from account_move_base_import/data/statement.xls rename to account_move_base_import/demo/statement.xls diff --git a/account_move_base_import/models/__init__.py b/account_move_base_import/models/__init__.py index bfd56a48..2835820b 100644 --- a/account_move_base_import/models/__init__.py +++ b/account_move_base_import/models/__init__.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from . import account_journal from . import account_move from . import partner diff --git a/account_move_base_import/models/account_journal.py b/account_move_base_import/models/account_journal.py index 5fe8b2aa..0040dcb3 100644 --- a/account_move_base_import/models/account_journal.py +++ b/account_move_base_import/models/account_journal.py @@ -1,8 +1,7 @@ -# -*- coding: utf-8 -*- -# © 2011-2016 Akretion -# © 2011-2019 Camptocamp SA -# © 2013 Savoir-faire Linux -# © 2014 ACSONE SA/NV +# Copyright 2011-2016 Akretion +# Copyright 2011-2019 Camptocamp SA +# Copyright 2013 Savoir-faire Linux +# Copyright 2014 ACSONE SA/NV # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) import sys import traceback @@ -330,7 +329,9 @@ class AccountJournal(models.Model): if not result_row_list: raise UserError(_("Nothing to import: " "The file is empty")) - parsed_cols = parser.get_move_line_vals(result_row_list[0]).keys() + parsed_cols = list( + parser.get_move_line_vals(result_row_list[0]).keys() + ) for col in parsed_cols: if col not in move_line_obj._fields: raise UserError( @@ -345,9 +346,10 @@ class AccountJournal(models.Model): parser_vals = parser.get_move_line_vals(line) values = self.prepare_move_line_vals(parser_vals, move) move_store.append(values) + # TODO Check if this is still needed # Hack to bypass ORM poor perfomance. Sob... move_line_obj._insert_lines(move_store) - self.env.invalidate_all() + self.invalidate_cache() self._write_extra_move_lines(parser, move) if self.create_counterpart: self._create_counterpart(parser, move) diff --git a/account_move_base_import/models/account_move.py b/account_move_base_import/models/account_move.py index ef83d892..c1238b98 100644 --- a/account_move_base_import/models/account_move.py +++ b/account_move_base_import/models/account_move.py @@ -1,8 +1,7 @@ -# -*- coding: utf-8 -*- -# © 2011-2016 Akretion -# © 2011-2016 Camptocamp SA -# © 2013 Savoir-faire Linux -# © 2014 ACSONE SA/NV +# Copyright 2011-2016 Akretion +# Copyright 2011-2019 Camptocamp SA +# Copyright 2013 Savoir-faire Linux +# Copyright 2014 ACSONE SA/NV # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) import traceback import sys @@ -10,8 +9,9 @@ import logging import psycopg2 -from odoo import _, api, fields, models +from odoo import _, api, fields, models, registry from odoo.exceptions import ValidationError +from odoo.tools import float_repr _logger = logging.getLogger(__name__) @@ -62,7 +62,8 @@ class AccountMoveCompletionRule(models.Model): 'From line name (based on partner field)'), ('get_from_name_and_partner_name', 'From line name (based on partner name)') - ], string='Method') + ], string='Method' + ) def _find_invoice(self, line, inv_type): """Find invoice related to statement line""" @@ -262,9 +263,9 @@ class AccountMoveLine(models.Model): """Return writeable by SQL columns""" model_cols = self._fields avail = [ - k for k, col in model_cols.iteritems() if not hasattr(col, '_fnct') + k for k, col in model_cols.items() if not hasattr(col, '_fnct') ] - keys = [k for k in move_store[0].keys() if k in avail] + keys = [k for k in list(move_store[0].keys()) if k in avail] keys.sort() return keys @@ -273,9 +274,34 @@ class AccountMoveLine(models.Model): Return a copy of statement """ move_copy = move - for k, col in move_copy.iteritems(): + for k, col in move_copy.items(): if k in cols: - move_copy[k] = self._fields[k].convert_to_column(col, None) + # In v11, the record is needed to call convert_to_column on a + # Monetary field because it must find the currency to apply the + # proper rounding. So we mimic the call here as best as we can. + if isinstance(self._fields[k], fields.Monetary): + currency_field = move_copy.get( + self._fields[k].currency_field + ) + if currency_field: + currency_id = ( + move_copy.get(currency_field) + or move.get('company_currency_id') + ) + if currency_id: + currency = self.env['res.currency'].browse( + currency_id + ) + move_copy[k] = float_repr( + currency.round(col), currency.decimal_places + ) + continue + # If no currency was found, set what convert_to_column + # would do and used to do in v10 + move_copy[k] = float(col or 0.0) + continue + else: + move_copy[k] = self._fields[k].convert_to_column(col, None) return move_copy def _prepare_manyinsert(self, move_store, cols): @@ -333,19 +359,16 @@ class AccountMove(models.Model): related='journal_id.used_for_completion', readonly=True) completion_logs = fields.Text(string='Completion Log', readonly=True) - # partner_id is a native field of the account module - # (related='line_ids.partner_id', store=True, readonly=True) - partner_id = fields.Many2one(related=False, compute='_compute_partner_id') import_partner_id = fields.Many2one('res.partner', string="Partner from import") - @api.one @api.depends('line_ids.partner_id', 'import_partner_id') def _compute_partner_id(self): - if self.import_partner_id: - self.partner_id = self.import_partner_id - elif self.line_ids: - self.partner_id = self.line_ids[0].partner_id + for move in self: + if move.import_partner_id: + move.partner_id = move.import_partner_id + else: + super(AccountMove, move)._compute_partner_id() def write_completion_log(self, error_msg, number_imported): """Write the log in the completion_logs field of the bank statement to @@ -392,9 +415,9 @@ class AccountMove(models.Model): res = line._get_line_values_from_rules(rules) if res: compl_lines += 1 - except ErrorTooManyPartner, exc: + except ErrorTooManyPartner as exc: msg_lines.append(repr(exc)) - except Exception, exc: + except Exception as exc: msg_lines.append(repr(exc)) error_type, error_value, trbk = sys.exc_info() st = "Error: %s\nDescription: %s\nTraceback:" % ( @@ -411,10 +434,6 @@ class AccountMove(models.Model): error_type.__name__, error_value) st += ''.join(traceback.format_tb(trbk, 30)) _logger.error(st) - # we can commit as it is not needed to be atomic - # commiting here adds a nice perfo boost - if not compl_lines % 500: - self.env.cr.commit() - msg = u'\n'.join(msg_lines) + msg = '\n'.join(msg_lines) self.write_completion_log(msg, compl_lines) return True diff --git a/account_move_base_import/models/partner.py b/account_move_base_import/models/partner.py index 7391479b..416a2c0b 100644 --- a/account_move_base_import/models/partner.py +++ b/account_move_base_import/models/partner.py @@ -1,8 +1,7 @@ -# -*- coding: utf-8 -*- -# © 2011-2016 Akretion -# © 2011-2016 Camptocamp SA -# © 2013 Savoir-faire Linux -# © 2014 ACSONE SA/NV +# Copyright 2011-2016 Akretion +# Copyright 2011-2019 Camptocamp SA +# Copyright 2013 Savoir-faire Linux +# Copyright 2014 ACSONE SA/NV # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) from odoo import fields, models diff --git a/account_move_base_import/parser/__init__.py b/account_move_base_import/parser/__init__.py index 1233bf9d..527dfdb2 100644 --- a/account_move_base_import/parser/__init__.py +++ b/account_move_base_import/parser/__init__.py @@ -1,8 +1,7 @@ -# -*- coding: utf-8 -*- -# © 2011 Akretion -# © 2011-2016 Camptocamp SA -# © 2013 Savoir-faire Linux -# © 2014 ACSONE SA/NV +# Copyright 2011 Akretion +# Copyright 2011-2019 Camptocamp SA +# Copyright 2013 Savoir-faire Linux +# Copyright 2014 ACSONE SA/NV # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) from .parser import new_move_parser from .parser import AccountMoveImportParser diff --git a/account_move_base_import/parser/file_parser.py b/account_move_base_import/parser/file_parser.py index 6b3f48fb..5700ede7 100644 --- a/account_move_base_import/parser/file_parser.py +++ b/account_move_base_import/parser/file_parser.py @@ -1,18 +1,17 @@ -# -*- coding: utf-8 -*- -# © 2011 Akretion -# © 2011-2016 Camptocamp SA -# © 2013 Savoir-faire Linux -# © 2014 ACSONE SA/NV +# Copyright 2011 Akretion +# Copyright 2011-2019 Camptocamp SA +# Copyright 2013 Savoir-faire Linux +# Copyright 2014 ACSONE SA/NV # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) -from openerp.tools.translate import _ -from openerp.exceptions import UserError -import tempfile import datetime +import tempfile +import xlrd + +from odoo import _ +from odoo.exceptions import UserError + + from .parser import AccountMoveImportParser, UnicodeDictReader -try: - import xlrd -except: - raise Exception(_('Please install python lib xlrd')) def float_or_zero(val): @@ -37,14 +36,14 @@ class FileParser(AccountMoveImportParser): :param list: header : specify header fields if the csv file has no header """ - super(FileParser, self).__init__(journal, **kwargs) + super().__init__(journal, **kwargs) if ftype in ('csv', 'xls', 'xlsx'): self.ftype = ftype[0:3] else: raise UserError( _('Invalid file type %s. Please use csv, xls or xlsx') % ftype) self.conversion_dict = extra_fields - self.keys_to_validate = self.conversion_dict.keys() + self.keys_to_validate = list(self.conversion_dict.keys()) self.fieldnames = header self._datemode = 0 # used only for xls documents, # 0 means Windows mode (1900 based dates). @@ -79,7 +78,7 @@ class FileParser(AccountMoveImportParser): separately (in the field: fieldnames). """ if self.fieldnames is None: - parsed_cols = self.result_row_list[0].keys() + parsed_cols = list(self.result_row_list[0].keys()) for col in self.keys_to_validate: if col not in parsed_cols: raise UserError(_('Column %s not present in file') % col) @@ -113,7 +112,7 @@ class FileParser(AccountMoveImportParser): header = sheet.row_values(0) res = [] for rownum in range(1, sheet.nrows): - res.append(dict(zip(header, sheet.row_values(rownum)))) + res.append(dict(list(zip(header, sheet.row_values(rownum))))) return res def _from_csv(self, result_set, conversion_rules): diff --git a/account_move_base_import/parser/generic_file_parser.py b/account_move_base_import/parser/generic_file_parser.py index d6f466b1..34cc1687 100644 --- a/account_move_base_import/parser/generic_file_parser.py +++ b/account_move_base_import/parser/generic_file_parser.py @@ -1,15 +1,11 @@ -# -*- coding: utf-8 -*- -# © 2011 Akretion -# © 2011-2016 Camptocamp SA -# © 2013 Savoir-faire Linux -# © 2014 ACSONE SA/NV +# Copyright 2011 Akretion +# Copyright 2011-2019 Camptocamp SA +# Copyright 2013 Savoir-faire Linux +# Copyright 2014 ACSONE SA/NV # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) import datetime -from .file_parser import FileParser -from openerp.addons.account_move_base_import.parser.file_parser import ( - float_or_zero -) -from openerp.tools import ustr +from .file_parser import FileParser, float_or_zero +from odoo.tools import ustr class GenericFileParser(FileParser): @@ -27,7 +23,7 @@ class GenericFileParser(FileParser): } # set self.env for later ORM searches self.env = journal.env - super(GenericFileParser, self).__init__( + super().__init__( journal, ftype=ftype, extra_fields=conversion_dict, **kwargs) diff --git a/account_move_base_import/parser/parser.py b/account_move_base_import/parser/parser.py index e20d5613..2cc7584d 100644 --- a/account_move_base_import/parser/parser.py +++ b/account_move_base_import/parser/parser.py @@ -1,8 +1,7 @@ -# -*- coding: utf-8 -*- -# © 2011 Akretion -# © 2011-2016 Camptocamp SA -# © 2013 Savoir-faire Linux -# © 2014 ACSONE SA/NV +# Copyright 2011 Akretion +# Copyright 2011-2019 Camptocamp SA +# Copyright 2013 Savoir-faire Linux +# Copyright 2014 ACSONE SA/NV # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) import base64 import csv @@ -21,9 +20,9 @@ def UnicodeDictReader(utf8_data, **kwargs): dialect = kwargs.pop('dialect') csv_reader = csv.DictReader(utf8_data, dialect=dialect, **kwargs) for row in csv_reader: - yield dict([(unicode(key or '', 'utf-8'), - unicode(value or '', 'utf-8')) - for key, value in row.iteritems()]) + yield dict([(str(key or ''), + str(value or '')) + for key, value in row.items()]) class AccountMoveImportParser(object): diff --git a/account_move_base_import/readme/CONTRIBUTORS.rst b/account_move_base_import/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..ef9233ac --- /dev/null +++ b/account_move_base_import/readme/CONTRIBUTORS.rst @@ -0,0 +1,7 @@ +* Joël Grand-Guillaume +* Nicolas Bessi +* Laurent Mignon +* Sébastien Beau +* Matthieu Dietrich +* Alexandre Fayolle +* Akim Juillerat diff --git a/account_move_base_import/readme/DESCRIPTION.rst b/account_move_base_import/readme/DESCRIPTION.rst new file mode 100644 index 00000000..7e23ab29 --- /dev/null +++ b/account_move_base_import/readme/DESCRIPTION.rst @@ -0,0 +1,42 @@ +This module is a grouping of 7.0/8.0 modules, used to import accounting files +and completing them automatically: + +* account_statement_base_completion +* account_statement_base_import +* account_statement_commission +* account_statement_ext + +The main change is that, in order to import financial data, this information +is now imported directly as a Journal Entry. + +Most of the information present in the "statement profile" is now located in +the account journal (with 2 boolean parameters which allows to use +this journal for importation and/or auto-completion). + +Financial data can be imported using a standard .csv or .xls file (you'll find +it in the 'data' folder). It respects the journal to pass the entries. + +This module can handle a commission taken by the payment office and has the +following format: +* __date__: date of the payment +* __amount__: amount paid in the currency of the journal used in the +importation +* __label__: the comunication given by the payment office, used as +communication in the generated entries. + +Another column which can be used is __commission_amount__, representing +the amount for the commission taken by line. + +Afterwards, the goal is to populate the journal items with information that +the bank or office gave you. For this, completion rules can be specified by +journal. + +Some basic rules are provided in this module: + +1) Match from statement line label (based on partner field 'Bank Statement +Label') +2) Match from statement line label (based on partner name) +3) Match from statement line label (based on Invoice reference) + +Feel free to extend either the importation method, the completion method, or +both. diff --git a/account_move_base_import/readme/ROADMAP.rst b/account_move_base_import/readme/ROADMAP.rst new file mode 100644 index 00000000..39566aa6 --- /dev/null +++ b/account_move_base_import/readme/ROADMAP.rst @@ -0,0 +1 @@ +* As for now, the module does not handle multicurrency imports. diff --git a/account_move_base_import/static/description/icon.png b/account_move_base_import/static/description/icon.png deleted file mode 100644 index 3a0328b5..00000000 Binary files a/account_move_base_import/static/description/icon.png and /dev/null differ diff --git a/account_move_base_import/test/completion_test.yml b/account_move_base_import/test/completion_test.yml deleted file mode 100644 index 12e13a07..00000000 --- a/account_move_base_import/test/completion_test.yml +++ /dev/null @@ -1,108 +0,0 @@ -- - In order to test the banking framework, I first need to create a journal -- - !record {model: account.journal, id: account.bank_journal}: - used_for_completion: True - rule_ids: - - bank_statement_completion_rule_4 - - bank_statement_completion_rule_2 - - bank_statement_completion_rule_3 - - bank_statement_completion_rule_5 -- - Now I create a statement. I create statment lines separately because I need - to find each one by XML id -- - !record {model: account.move, id: move_test1}: - name: Move 2 - journal_id: account.bank_journal - company_id: base.main_company -- - I create a move line for a CI -- - !record {model: account.move.line, id: move_line_ci}: - name: \ - account_id: account.a_sale - move_id: move_test1 - date_maturity: '2013-12-20' - credit: 0.0 -- - I create a move line for a SI -- - !record {model: account.move.line, id: move_line_si}: - name: \ - account_id: account.a_expense - move_id: move_test1 - date_maturity: '2013-12-19' - debit: 0.0 -- - I create a move line for a CR -- - !record {model: account.move.line, id: move_line_cr}: - name: \ - account_id: account.a_expense - move_id: move_test1 - date_maturity: '2013-12-19' - debit: 0.0 -- - I create a move line for the Partner Name -- - !record {model: account.move.line, id: move_line_partner_name}: - name: Test autocompletion based on Partner Name Camptocamp - account_id: account.a_sale - move_id: move_test1 - date_maturity: '2013-12-17' - credit: 0.0 -- - I create a move line for the Partner Label -- - !record {model: account.move.line, id: move_line_partner_label}: - name: XXX66Z - account_id: account.a_sale - move_id: move_test1 - date_maturity: '2013-12-24' - debit: 0.0 -- - and add the correct name -- - !python {model: account.move.line}: | - import datetime as dt - model.browse(ref('move_line_ci')).with_context(check_move_validity=False).write({'name': dt.date.today().strftime('TBNK/%Y/0001'),'credit': 210.0}) - model.browse(ref('move_line_si')).with_context(check_move_validity=False).write({'name': 'T2S12345','debit': 65.0}) - model.browse(ref('move_line_cr')).with_context(check_move_validity=False).write({'name': dt.date.today().strftime('RTEXJ/%Y/0001'),'debit': 210.0}) - model.browse(ref('move_line_partner_name')).with_context(check_move_validity=False).write({'credit': 600.0}) - model.browse(ref('move_line_partner_label')).with_context(check_move_validity=False).write({'debit': 932.4}) -- - I run the auto complete -- - !python {model: account.move, ref: move_test1}: | - result = model.browse(ref('move_test1')).button_auto_completion() -- - Now I can check that all is nice and shiny, line 1. I expect the Customer - Invoice Number to be recognised. - I Use _ref, because ref conflicts with the field ref of the statement line -- - !assert {model: account.move.line, id: move_line_ci, string: Check completion by CI number}: - - partner_id.id == _ref("base.res_partner_12") -- - Line 2. I expect the Supplier invoice number to be recognised. The supplier - invoice was created by the account module demo data, and we confirmed it - here. -- - !assert {model: account.move.line, id: move_line_si, string: Check completion by SI number}: - - partner_id.id == _ref("base.res_partner_12") -- - Line 3. I expect the Customer refund number to be recognised. It should be - the commercial partner, and not the regular partner. -- - !assert {model: account.move.line, id: move_line_cr, string: Check completion by CR number and commercial partner}: - - partner_id.id == _ref("base.res_partner_12") -- - Line 4. I check that the partner name has been recognised. -- - !assert {model: account.move.line, id: move_line_partner_name, string: Check completion by partner name}: - - partner_id.name == 'Camptocamp' -- - Line 5. I check that the partner special label has been recognised. -- - !assert {model: account.move.line, id: move_line_partner_label, string: Check completion by partner label}: - - partner_id.id == _ref("base.res_partner_4") diff --git a/account_move_base_import/test/invoice.yml b/account_move_base_import/test/invoice.yml deleted file mode 100644 index 14cb6351..00000000 --- a/account_move_base_import/test/invoice.yml +++ /dev/null @@ -1,43 +0,0 @@ -- - I import account minimal data -- - !python {model: account.invoice}: | - openerp.tools.convert_file(cr, - 'account', - openerp.modules.get_module_resource( - 'account', - 'test', - 'account_minimal_test.xml'), - {}, 'init', False, 'test') -- - I create a customer Invoice to be found by the completion. -- - !record {model: account.invoice, id: invoice_for_completion_1}: - company_id: base.main_company - currency_id: base.EUR - invoice_line_ids: - - name: '[PCSC234] PC Assemble SC234' - price_unit: 210.0 - quantity: 1.0 - product_id: product.product_product_3 - uom_id: product.product_uom_unit - journal_id: account.bank_journal - partner_id: base.res_partner_12 - reference_type: none -- - I confirm the Invoice -- - !python {model: account.invoice, id: invoice_for_completion_1}: - self.action_invoice_open() -- - I check that the invoice state is "Open" -- - !assert {model: account.invoice, id: invoice_for_completion_1}: - - state == 'open' -- - I check that it is given the number "TBNK/%Y/0001" -- - !python {model: account.invoice}: | - import datetime as dt - invoice = model.browse(ref('invoice_for_completion_1')) - assert invoice.number == dt.date.today().strftime('TBNK/%Y/0001') diff --git a/account_move_base_import/test/partner.yml b/account_move_base_import/test/partner.yml deleted file mode 100644 index 5e207100..00000000 --- a/account_move_base_import/test/partner.yml +++ /dev/null @@ -1,5 +0,0 @@ -- - I fill in the field Bank Statement Label in a Partner -- - !record {model: res.partner, id: base.res_partner_4}: - bank_statement_label: XXX66Z diff --git a/account_move_base_import/test/refund.yml b/account_move_base_import/test/refund.yml deleted file mode 100644 index ec97bb04..00000000 --- a/account_move_base_import/test/refund.yml +++ /dev/null @@ -1,43 +0,0 @@ -- - I create a "child" partner, to use in the invoice - (and have a different commercial_partner_id than itself) -- - !record {model: res.partner, id: res_partner_12_child}: - name: Child Partner - supplier: False - customer: True - is_company: False - parent_id: base.res_partner_12 -- - I create a customer refund to be found by the completion. -- - !record {model: account.invoice, id: refund_for_completion_1}: - company_id: base.main_company - currency_id: base.EUR - invoice_line_ids: - - name: '[PCSC234] PC Assemble SC234' - price_unit: 210.0 - quantity: 1.0 - product_id: product.product_product_3 - uom_id: product.product_uom_unit - journal_id: account.expenses_journal - partner_id: res_partner_12_child - type: 'out_refund' - reference_type: none -- - I confirm the refund -- - !python {model: account.invoice, ref: invoice_for_completion_1}: | - model.browse(ref('refund_for_completion_1')).action_invoice_open() -- - I check that the refund state is "Open" -- - !assert {model: account.invoice, id: refund_for_completion_1}: - - state == 'open' -- - I check that it is given the number "RTEXJ/%Y/0001" -- - !python {model: account.invoice}: | - import datetime as dt - invoice = model.browse(ref('refund_for_completion_1')) - assert invoice.number == dt.date.today().strftime('RTEXJ/%Y/0001') diff --git a/account_move_base_import/test/supplier_invoice.yml b/account_move_base_import/test/supplier_invoice.yml deleted file mode 100644 index e3e90050..00000000 --- a/account_move_base_import/test/supplier_invoice.yml +++ /dev/null @@ -1,41 +0,0 @@ -- - I import account minimal data -- - !python {model: account.invoice}: | - openerp.tools.convert_file(cr, - 'account', - openerp.modules.get_module_resource( - 'account', - 'demo', - 'account_invoice_demo.yml'), - {}, 'init', False, 'test') -- - I check that my invoice is a supplier invoice -- - !assert {model: account.invoice, id: account.demo_invoice_0, string: Check invoice type}: - - type == 'in_invoice' -- - I add a reference to an existing supplier invoce -- - !python {model: account.invoice, id: account.demo_invoice_0}: | - self.write({'reference': 'T2S12345'}) -- - I check a second time that my invoice is still a supplier invoice -- - !assert {model: account.invoice, id: account.demo_invoice_0, string: Check invoice type 2}: - - type == 'in_invoice' -- - Now I confirm it -- - !python {model: account.invoice, ref: account.demo_invoice_0}: | - self.action_invoice_open() -- - I check that the supplier number is there -- - !assert {model: account.invoice, id: account.demo_invoice_0, string: Check supplier number}: - - reference == 'T2S12345' -- - I check a third time that my invoice is still a supplier invoice -- - !assert {model: account.invoice, id: account.demo_invoice_0, string: Check invoice type 3}: - - type == 'in_invoice' diff --git a/account_move_base_import/tests/__init__.py b/account_move_base_import/tests/__init__.py index 2a150908..0e6efa7d 100644 --- a/account_move_base_import/tests/__init__.py +++ b/account_move_base_import/tests/__init__.py @@ -1,8 +1,8 @@ -# -*- coding: utf-8 -*- -# © 2011 Akretion -# © 2011-2016 Camptocamp SA -# © 2013 Savoir-faire Linux -# © 2014 ACSONE SA/NV +# Copyright 2011 Akretion +# Copyright 2011-2019 Camptocamp SA +# Copyright 2013 Savoir-faire Linux +# Copyright 2014 ACSONE SA/NV # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) from . import test_base_completion from . import test_base_import +from . import test_invoice diff --git a/account_move_base_import/tests/test_base_completion.py b/account_move_base_import/tests/test_base_completion.py index 15c48934..f619e958 100644 --- a/account_move_base_import/tests/test_base_completion.py +++ b/account_move_base_import/tests/test_base_completion.py @@ -1,11 +1,10 @@ -# -*- coding: utf-8 -*- -# © 2011-2016 Akretion -# © 2011-2016 Camptocamp SA -# © 2013 Savoir-faire Linux -# © 2014 ACSONE SA/NV +# Copyright 2011-2016 Akretion +# Copyright 2011-2019 Camptocamp SA +# Copyright 2013 Savoir-faire Linux +# Copyright 2014 ACSONE SA/NV # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) from odoo import fields, tools -from odoo.modules import get_module_resource +from odoo.modules import get_resource_path from odoo.tests import common from collections import namedtuple @@ -36,10 +35,10 @@ NAMES_COMPLETION_CASES = [ class BaseCompletion(common.TransactionCase): def setUp(self): - super(BaseCompletion, self).setUp() + super().setUp() tools.convert_file(self.cr, 'account', - get_module_resource('account', 'test', - 'account_minimal_test.xml'), + get_resource_path('account', 'test', + 'account_minimal_test.xml'), {}, 'init', False, 'test') self.account_move_obj = self.env["account.move"] self.account_move_line_obj = \ @@ -82,13 +81,13 @@ class BaseCompletion(common.TransactionCase): "Partner_id must be blank before completion") self.move.button_auto_completion() if case.should_match: - self.assertEquals( + self.assertEqual( self.partner, self.move_line.partner_id, "Missing expected partner id after completion " "(partner_name: %s, line_name: %s)" % (case.partner_name, case.line_label)) else: - self.assertNotEquals( + self.assertNotEqual( self.partner, self.move_line.partner_id, "Partner id should be empty after completion " "(partner_name: %s, line_name: %s)" diff --git a/account_move_base_import/tests/test_base_import.py b/account_move_base_import/tests/test_base_import.py index 0c036091..bb041b18 100644 --- a/account_move_base_import/tests/test_base_import.py +++ b/account_move_base_import/tests/test_base_import.py @@ -1,26 +1,24 @@ -# -*- coding: utf-8 -*- -# © 2011-2016 Akretion -# © 2011-2016 Camptocamp SA -# © 2013 Savoir-faire Linux -# © 2014 ACSONE SA/NV +# Copyright 2011-2016 Akretion +# Copyright 2011-2019 Camptocamp SA +# Copyright 2013 Savoir-faire Linux +# Copyright 2014 ACSONE SA/NV # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) import base64 -import inspect import os from operator import attrgetter from odoo.tests import common from odoo import tools -from odoo.modules import get_module_resource +from odoo.modules import get_resource_path class TestCodaImport(common.TransactionCase): def setUp(self): - super(TestCodaImport, self).setUp() + super().setUp() self.company_a = self.browse_ref('base.main_company') tools.convert_file(self.cr, 'account', - get_module_resource('account', 'test', - 'account_minimal_test.xml'), + get_resource_path('account', 'test', + 'account_minimal_test.xml'), {}, 'init', False, 'test') self.account_move_obj = self.env["account.move"] self.account_move_line_obj = self.env["account.move.line"] @@ -37,15 +35,11 @@ class TestCodaImport(common.TransactionCase): 'create_counterpart': True, }) - def _filename_to_abs_filename(self, file_name): - dir_name = os.path.dirname(inspect.getfile(self.__class__)) - return os.path.join(dir_name, file_name) - def _import_file(self, file_name): """ import a file using the wizard return the create account.bank.statement object """ - with open(file_name) as f: + with open(file_name, 'rb') as f: content = f.read() self.wizard = self.import_wizard_obj.create({ "journal_id": self.journal.id, @@ -58,16 +52,18 @@ class TestCodaImport(common.TransactionCase): def test_simple_xls(self): """Test import from xls """ - file_name = self._filename_to_abs_filename( - os.path.join("..", "data", "statement.xls")) + file_name = get_resource_path( + 'account_move_base_import', 'demo', 'statement.xls' + ) move = self._import_file(file_name) self._validate_imported_move(move) def test_simple_csv(self): """Test import from csv """ - file_name = self._filename_to_abs_filename( - os.path.join("..", "data", "statement.csv")) + file_name = get_resource_path( + 'account_move_base_import', 'demo', 'statement.csv' + ) move = self._import_file(file_name) self._validate_imported_move(move) diff --git a/account_move_base_import/tests/test_invoice.py b/account_move_base_import/tests/test_invoice.py new file mode 100644 index 00000000..c2910e13 --- /dev/null +++ b/account_move_base_import/tests/test_invoice.py @@ -0,0 +1,256 @@ +# Copyright 2019 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +import datetime as dt + +from odoo.modules import get_resource_path +from odoo.tests import SingleTransactionCase +from odoo.tools import convert_file + + +class TestInvoice(SingleTransactionCase): + + def setUp(self): + super().setUp() + self.account_move_obj = self.env["account.move"] + self.account_move_line_obj = \ + self.env["account.move.line"] + self.company_a = self.env.ref('base.main_company') + self.partner = self.env.ref("base.res_partner_12") + + def test_01_partner(self): + # I fill in the field Bank Statement Label in a Partner + self.partner_4 = self.env.ref('base.res_partner_4') + self.partner_4.bank_statement_label = 'XXX66Z' + self.assertEqual(self.partner_4.bank_statement_label, 'XXX66Z') + + def test_02_invoice(self): + convert_file( + self.cr, 'account', get_resource_path( + 'account', 'test', 'account_minimal_test.xml' + ), {}, 'init', False, 'test' + ) + self.journal = self.env.ref("account.bank_journal") + self.account_id = self.env.ref("account.a_recv") + # I create a customer Invoice to be found by the completion. + product_3 = self.env.ref('product.product_product_3') + self.invoice_for_completion_1 = self.env['account.invoice'].create({ + 'currency_id': self.env.ref('base.EUR').id, + 'invoice_line_ids': [(0, 0, { + 'name': '[PCSC234] PC Assemble SC234', + 'product_id': product_3.id, + 'price_unit': 210.0, + 'quantity': 1.0, + 'uom_id': self.env.ref('product.product_uom_unit').id, + 'account_id': self.env.ref('account.a_sale').id, + })], + 'journal_id': self.journal.id, + 'partner_id': self.partner.id, + 'reference_type': 'none', + 'account_id': self.env.ref('account.a_recv').id, + }) + # I confirm the Invoice + self.invoice_for_completion_1.action_invoice_open() + # I check that the invoice state is "Open" + self.assertEqual(self.invoice_for_completion_1.state, 'open') + # I check that it is given the number "TBNK/%Y/0001" + self.assertEqual( + self.invoice_for_completion_1.number, + dt.date.today().strftime('TBNK/%Y/0001') + ) + + def test_03_supplier_invoice(self): + # I import account minimal data + convert_file( + self.cr, 'account', get_resource_path( + 'account', 'demo', 'account_invoice_demo.yml' + ), {}, 'init', False, 'test' + ) + + # I check that my invoice is a supplier invoice + demo_invoice_0 = self.env.ref('account.demo_invoice_0') + self.assertEqual( + demo_invoice_0.type, 'in_invoice', msg="Check invoice type" + ) + # I add a reference to an existing supplier invoice + demo_invoice_0.write({'reference': 'T2S12345'}) + # I check a second time that my invoice is still a supplier invoice + self.assertEqual( + demo_invoice_0.type, 'in_invoice', msg="Check invoice type 2" + ) + # Now I confirm it + demo_invoice_0.action_invoice_open() + # I check that the supplier number is there + self.assertEqual( + demo_invoice_0.reference, 'T2S12345', msg="Check supplier number" + ) + # I check a third time that my invoice is still a supplier invoice + self.assertEqual( + demo_invoice_0.type, 'in_invoice', msg="Check invoice type 3" + ) + + def test_04_refund(self): + # I create a "child" partner, to use in the invoice + # (and have a different commercial_partner_id than itself) + res_partner_12_child = self.env['res.partner'].create({ + 'name': 'Child Partner', + 'supplier': False, + 'customer': True, + 'is_company': False, + 'parent_id': self.partner.id, + }) + # I create a customer refund to be found by the completion. + product_3 = self.env.ref('product.product_product_3') + self.refund_for_completion_1 = self.env['account.invoice'].create({ + 'currency_id': self.env.ref('base.EUR').id, + 'invoice_line_ids': [(0, 0, { + 'name': '[PCSC234] PC Assemble SC234', + 'product_id': product_3.id, + 'price_unit': 210.0, + 'quantity': 1.0, + 'uom_id': self.env.ref('product.product_uom_unit').id, + 'account_id': self.env.ref('account.a_sale').id, + })], + 'journal_id': self.env.ref('account.expenses_journal').id, + 'partner_id': res_partner_12_child.id, + 'type': 'out_refund', + 'reference_type': 'none', + 'account_id': self.env.ref('account.a_recv').id, + }) + # I confirm the refund + self.refund_for_completion_1.action_invoice_open() + + # I check that the refund state is "Open" + self.assertEqual(self.refund_for_completion_1.state, 'open') + # I check that it is given the number "RTEXJ/%Y/0001" + self.assertEqual( + self.refund_for_completion_1.number, + dt.date.today().strftime('RTEXJ/%Y/0001') + ) + + def test_05_completion(self): + + # In order to test the banking framework, I first need to create a + # journal + self.journal = self.env.ref("account.bank_journal") + completion_rule_4 = self.env.ref( + 'account_move_base_import.bank_statement_completion_rule_4' + ) + completion_rule_2 = self.env.ref( + 'account_move_base_import.bank_statement_completion_rule_2' + ) + completion_rule_3 = self.env.ref( + 'account_move_base_import.bank_statement_completion_rule_3' + ) + completion_rule_5 = self.env.ref( + 'account_move_base_import.bank_statement_completion_rule_5' + ) + completion_rules = ( + completion_rule_2 | completion_rule_3 | completion_rule_4 + | completion_rule_5 + ) + self.journal.write({ + 'used_for_completion': True, + 'rule_ids': [ + (4, comp_rule.id, False) for comp_rule in completion_rules + ] + }) + # Now I create a statement. I create statment lines separately because + # I need to find each one by XML id + move_test1 = self.env['account.move'].create({ + 'name': 'Move 2', + 'journal_id': self.journal.id, + }) + # I create a move line for a CI + move_line_ci = self.env['account.move.line'].create({ + 'name': '\\', + 'account_id': self.env.ref('account.a_sale').id, + 'move_id': move_test1.id, + 'date_maturity': '2013-12-20', + 'credit': 0.0, + }) + # I create a move line for a SI + move_line_si = self.env['account.move.line'].create({ + 'name': '\\', + 'account_id': self.env.ref('account.a_expense').id, + 'move_id': move_test1.id, + 'date_maturity': '2013-12-19', + 'debit': 0.0, + }) + # I create a move line for a CR + move_line_cr = self.env['account.move.line'].create({ + 'name': '\\', + 'account_id': self.env.ref('account.a_expense').id, + 'move_id': move_test1.id, + 'date_maturity': '2013-12-19', + 'debit': 0.0, + }) + # I create a move line for the Partner Name + move_line_partner_name = self.env['account.move.line'].create({ + 'name': 'Test autocompletion based on Partner Name Camptocamp', + 'account_id': self.env.ref('account.a_sale').id, + 'move_id': move_test1.id, + 'date_maturity': '2013-12-17', + 'credit': 0.0, + }) + # I create a move line for the Partner Label + move_line_partner_label = self.env['account.move.line'].create({ + 'name': 'XXX66Z', + 'account_id': self.env.ref('account.a_sale').id, + 'move_id': move_test1.id, + 'date_maturity': '2013-12-24', + 'debit': 0.0, + }) + # and add the correct name + move_line_ci.with_context(check_move_validity=False).write({ + 'name': dt.date.today().strftime('TBNK/%Y/0001'), + 'credit': 210.0, + }) + move_line_si.with_context(check_move_validity=False).write({ + 'name': 'T2S12345', + 'debit': 65.0, + }) + move_line_cr.with_context(check_move_validity=False).write({ + 'name': dt.date.today().strftime('RTEXJ/%Y/0001'), + 'debit': 210.0, + }) + move_line_partner_name.with_context(check_move_validity=False).write({ + 'credit': 600.0, + }) + move_line_partner_label.with_context(check_move_validity=False).write({ + 'debit': 932.4, + }) + # I run the auto complete + move_test1.button_auto_completion() + # Now I can check that all is nice and shiny, line 1. I expect the + # Customer Invoice Number to be recognised. + # I Use _ref, because ref conflicts with the field ref of the + # statement line + self.assertEqual( + move_line_ci.partner_id, self.partner, + msg="Check completion by CI number" + ) + # Line 2. I expect the Supplier invoice number to be recognised. The + # supplier invoice was created by the account module demo data, and we + # confirmed it here. + self.assertEqual( + move_line_si.partner_id, self.partner, + msg="Check completion by SI number" + ) + # Line 3. I expect the Customer refund number to be recognised. It + # should be the commercial partner, and not the regular partner. + self.assertEqual( + move_line_cr.partner_id, self.partner, + msg="Check completion by CR number and commercial partner" + ) + # Line 4. I check that the partner name has been recognised. + self.assertEqual( + move_line_partner_name.partner_id.name, 'Camptocamp', + msg="Check completion by partner name" + ) + # Line 5. I check that the partner special label has been recognised. + self.partner_4 = self.env.ref('base.res_partner_4') + self.assertEqual( + move_line_partner_label.partner_id, + self.partner_4, + msg="Check completion by partner label" + ) diff --git a/account_move_base_import/views/account_move_view.xml b/account_move_base_import/views/account_move_view.xml index 68c81535..05108438 100644 --- a/account_move_base_import/views/account_move_view.xml +++ b/account_move_base_import/views/account_move_view.xml @@ -8,7 +8,7 @@ -