diff --git a/account_mass_reconcile/__init__.py b/account_mass_reconcile/__init__.py old mode 100755 new mode 100644 diff --git a/account_mass_reconcile/__manifest__.py b/account_mass_reconcile/__manifest__.py index 6a996f51..16a9ac2d 100644 --- a/account_mass_reconcile/__manifest__.py +++ b/account_mass_reconcile/__manifest__.py @@ -5,9 +5,7 @@ { "name": "Account Mass Reconcile", "version": "12.0.1.0.0", - "depends": [ - "account", - ], + "depends": ["account"], "author": "Akretion,Camptocamp,Odoo Community Association (OCA)", "website": "https://github.com/OCA/account-reconcile", "category": "Finance", @@ -18,5 +16,5 @@ "views/mass_reconcile_history_view.xml", "views/res_config_view.xml", ], - 'license': 'AGPL-3', + "license": "AGPL-3", } diff --git a/account_mass_reconcile/models/__init__.py b/account_mass_reconcile/models/__init__.py old mode 100755 new mode 100644 diff --git a/account_mass_reconcile/models/advanced_reconciliation.py b/account_mass_reconcile/models/advanced_reconciliation.py index acfa6f81..dd18b60b 100644 --- a/account_mass_reconcile/models/advanced_reconciliation.py +++ b/account_mass_reconcile/models/advanced_reconciliation.py @@ -7,8 +7,8 @@ from odoo import models class MassReconcileAdvancedRef(models.TransientModel): - _name = 'mass.reconcile.advanced.ref' - _inherit = 'mass.reconcile.advanced' + _name = "mass.reconcile.advanced.ref" + _inherit = "mass.reconcile.advanced" @staticmethod def _skip_line(move_line): @@ -17,7 +17,7 @@ class MassReconcileAdvancedRef(models.TransientModel): will be skipped for reconciliation. Can be inherited to skip on some conditions. ie: ref or partner_id is empty. """ - return not (move_line.get('ref') and move_line.get('partner_id')) + return not (move_line.get("ref") and move_line.get("partner_id")) @staticmethod def _matchers(move_line): @@ -57,8 +57,10 @@ class MassReconcileAdvancedRef(models.TransientModel): and their values to match in the opposite lines. A matching key can have multiples values. """ - return (('partner_id', move_line['partner_id']), - ('ref', move_line['ref'].lower().strip())) + return ( + ("partner_id", move_line["partner_id"]), + ("ref", move_line["ref"].lower().strip()), + ) @staticmethod def _opposite_matchers(move_line): @@ -99,6 +101,11 @@ class MassReconcileAdvancedRef(models.TransientModel): :param dict move_line: values of the move_line :yield: matchers as tuple ('matcher key', value(s)) """ - yield ('partner_id', move_line['partner_id']) - yield ('ref', ((move_line['ref'] or '').lower().strip(), - move_line['name'].lower().strip())) + yield ("partner_id", move_line["partner_id"]) + yield ( + "ref", + ( + (move_line["ref"] or "").lower().strip(), + move_line["name"].lower().strip(), + ), + ) diff --git a/account_mass_reconcile/models/base_advanced_reconciliation.py b/account_mass_reconcile/models/base_advanced_reconciliation.py index a2c3d50d..7632e7b3 100644 --- a/account_mass_reconcile/models/base_advanced_reconciliation.py +++ b/account_mass_reconcile/models/base_advanced_reconciliation.py @@ -5,15 +5,15 @@ import logging from itertools import product -from odoo import models, api +from odoo import api, models from odoo.tools.translate import _ _logger = logging.getLogger(__name__) class MassReconcileAdvanced(models.AbstractModel): - _name = 'mass.reconcile.advanced' - _inherit = 'mass.reconcile.base' + _name = "mass.reconcile.advanced" + _inherit = "mass.reconcile.base" def _query_debit(self): """Select all move (debit>0) as candidate. """ @@ -22,7 +22,7 @@ class MassReconcileAdvanced(models.AbstractModel): where, params = self._where_query() where += " AND account_move_line.debit > 0 " where2, params2 = self._get_filter() - query = ' '.join((select, sql_from, where, where2)) + query = " ".join((select, sql_from, where, where2)) self.env.cr.execute(query, params + params2) return self.env.cr.dictfetchall() @@ -33,7 +33,7 @@ class MassReconcileAdvanced(models.AbstractModel): where, params = self._where_query() where += " AND account_move_line.credit > 0 " where2, params2 = self._get_filter() - query = ' '.join((select, sql_from, where, where2)) + query = " ".join((select, sql_from, where, where2)) self.env.cr.execute(query, params + params2) return self.env.cr.dictfetchall() @@ -153,15 +153,15 @@ class MassReconcileAdvanced(models.AbstractModel): """ mkey, mvalue = matcher omkey, omvalue = opposite_matcher - assert mkey == omkey, \ - (_("A matcher %s is compared with a matcher %s, the _matchers and " - "_opposite_matchers are probably wrong") % (mkey, omkey)) + assert mkey == omkey, _( + "A matcher %s is compared with a matcher %s, the _matchers and " + "_opposite_matchers are probably wrong" + ) % (mkey, omkey) if not isinstance(mvalue, (list, tuple)): - mvalue = mvalue, + mvalue = (mvalue,) if not isinstance(omvalue, (list, tuple)): - omvalue = omvalue, - return MassReconcileAdvanced._compare_matcher_values(mkey, mvalue, - omvalue) + omvalue = (omvalue,) + return MassReconcileAdvanced._compare_matcher_values(mkey, mvalue, omvalue) def _compare_opposite(self, move_line, opposite_move_line, matchers): """ Iterate over the matchers of the move lines vs opposite move lines @@ -197,8 +197,11 @@ class MassReconcileAdvanced(models.AbstractModel): :return: list of matching lines """ matchers = self._matchers(move_line) - return [op for op in opposite_move_lines if - self._compare_opposite(move_line, op, matchers)] + return [ + op + for op in opposite_move_lines + if self._compare_opposite(move_line, op, matchers) + ] def _action_rec(self): credit_lines = self._query_credit() @@ -221,51 +224,54 @@ class MassReconcileAdvanced(models.AbstractModel): for rec in self: reconcile_groups = [] ctx = self.env.context.copy() - ctx['commit_every'] = ( - rec.account_id.company_id.reconciliation_commit_every - ) + ctx["commit_every"] = rec.account_id.company_id.reconciliation_commit_every _logger.info("%d credit lines to reconcile", len(credit_lines)) for idx, credit_line in enumerate(credit_lines, start=1): if idx % 50 == 0: - _logger.info("... %d/%d credit lines inspected ...", idx, - len(credit_lines)) + _logger.info( + "... %d/%d credit lines inspected ...", idx, len(credit_lines) + ) if self._skip_line(credit_line): continue - opposite_lines = self._search_opposites(credit_line, - debit_lines) + opposite_lines = self._search_opposites(credit_line, debit_lines) if not opposite_lines: continue - opposite_ids = [l['id'] for l in opposite_lines] - line_ids = opposite_ids + [credit_line['id']] + opposite_ids = [l["id"] for l in opposite_lines] + line_ids = opposite_ids + [credit_line["id"]] for group in reconcile_groups: if any([lid in group for lid in opposite_ids]): - _logger.debug("New lines %s matched with an existing " - "group %s", line_ids, group) + _logger.debug( + "New lines %s matched with an existing " "group %s", + line_ids, + group, + ) group.update(line_ids) break else: _logger.debug("New group of lines matched %s", line_ids) reconcile_groups.append(set(line_ids)) - lines_by_id = dict([(l['id'], l) - for l in credit_lines + debit_lines]) - _logger.info("Found %d groups to reconcile", - len(reconcile_groups)) - for group_count, reconcile_group_ids \ - in enumerate(reconcile_groups, start=1): - _logger.debug("Reconciling group %d/%d with ids %s", - group_count, len(reconcile_groups), - reconcile_group_ids) - group_lines = [lines_by_id[lid] - for lid in reconcile_group_ids] - reconciled, full = self._reconcile_lines(group_lines, - allow_partial=True) + lines_by_id = {l["id"]: l for l in credit_lines + debit_lines} + _logger.info("Found %d groups to reconcile", len(reconcile_groups)) + for group_count, reconcile_group_ids in enumerate( + reconcile_groups, start=1 + ): + _logger.debug( + "Reconciling group %d/%d with ids %s", + group_count, + len(reconcile_groups), + reconcile_group_ids, + ) + group_lines = [lines_by_id[lid] for lid in reconcile_group_ids] + reconciled, full = self._reconcile_lines( + group_lines, allow_partial=True + ) if reconciled and full: reconciled_ids += reconcile_group_ids - if (ctx['commit_every'] and - group_count % ctx['commit_every'] == 0): + if ctx["commit_every"] and group_count % ctx["commit_every"] == 0: self.env.cr.commit() - _logger.info("Commit the reconciliations after %d groups", - group_count) + _logger.info( + "Commit the reconciliations after %d groups", group_count + ) _logger.info("Reconciliation is over") return reconciled_ids diff --git a/account_mass_reconcile/models/base_reconciliation.py b/account_mass_reconcile/models/base_reconciliation.py index 2826a8e0..6ae8d692 100644 --- a/account_mass_reconcile/models/base_reconciliation.py +++ b/account_mass_reconcile/models/base_reconciliation.py @@ -11,17 +11,13 @@ from odoo.tools.safe_eval import safe_eval class MassReconcileBase(models.AbstractModel): """Abstract Model for reconciliation methods""" - _name = 'mass.reconcile.base' - _inherit = 'mass.reconcile.options' - account_id = fields.Many2one( - 'account.account', - string='Account', - required=True - ) + _name = "mass.reconcile.base" + _inherit = "mass.reconcile.options" + + account_id = fields.Many2one("account.account", string="Account", required=True) partner_ids = fields.Many2many( - comodel_name='res.partner', - string='Restrict on partners', + comodel_name="res.partner", string="Restrict on partners" ) # other fields are inherited from mass.reconcile.options @@ -47,16 +43,16 @@ class MassReconcileBase(models.AbstractModel): An extra column aliased as ``key`` should be defined in each query.""" aml_cols = ( - 'id', - 'debit', - 'credit', - 'date', - 'ref', - 'name', - 'partner_id', - 'account_id', - 'reconciled', - 'move_id', + "id", + "debit", + "credit", + "date", + "ref", + "name", + "partner_id", + "account_id", + "reconciled", + "move_id", ) return ["account_move_line.{}".format(col) for col in aml_cols] @@ -64,16 +60,18 @@ class MassReconcileBase(models.AbstractModel): return self._base_columns() def _select_query(self, *args, **kwargs): - return "SELECT %s" % ', '.join(self._selection_columns()) + return "SELECT %s" % ", ".join(self._selection_columns()) def _from_query(self, *args, **kwargs): - return ("FROM account_move_line ") + return "FROM account_move_line " @api.multi def _where_query(self, *args, **kwargs): self.ensure_one() - where = ("WHERE account_move_line.account_id = %s " - "AND NOT account_move_line.reconciled") + where = ( + "WHERE account_move_line.account_id = %s " + "AND NOT account_move_line.reconciled" + ) # it would be great to use dict for params # but as we use _where_calc in _get_filter # which returns a list, we have to @@ -87,12 +85,11 @@ class MassReconcileBase(models.AbstractModel): @api.multi def _get_filter(self): self.ensure_one() - ml_obj = self.env['account.move.line'] - where = '' + ml_obj = self.env["account.move.line"] + where = "" params = [] if self._filter: - dummy, where, params = ml_obj._where_calc( - safe_eval(self._filter)).get_sql() + dummy, where, params = ml_obj._where_calc(safe_eval(self._filter)).get_sql() if where: where = " AND %s" % where return where, params @@ -100,37 +97,37 @@ class MassReconcileBase(models.AbstractModel): @api.multi def _below_writeoff_limit(self, lines, writeoff_limit): self.ensure_one() - precision = self.env['decimal.precision'].precision_get('Account') - keys = ('debit', 'credit') + precision = self.env["decimal.precision"].precision_get("Account") + keys = ("debit", "credit") sums = reduce( - lambda line, memo: - dict((key, value + memo[key]) - for key, value - in line.items() - if key in keys), lines) - debit, credit = sums['debit'], sums['credit'] + lambda line, memo: { + key: value + memo[key] for key, value in line.items() if key in keys + }, + lines, + ) + debit, credit = sums["debit"], sums["credit"] writeoff_amount = round(debit - credit, precision) return bool(writeoff_limit >= abs(writeoff_amount)), debit, credit @api.multi - def _get_rec_date(self, lines, based_on='end_period_last_credit'): + def _get_rec_date(self, lines, based_on="end_period_last_credit"): self.ensure_one() def last_date(mlines): - return max(mlines, key=itemgetter('date')) + return max(mlines, key=itemgetter("date")) def credit(mlines): - return [l for l in mlines if l['credit'] > 0] + return [l for l in mlines if l["credit"] > 0] def debit(mlines): - return [l for l in mlines if l['debit'] > 0] + return [l for l in mlines if l["debit"] > 0] - if based_on == 'newest': - return last_date(lines)['date'] - elif based_on == 'newest_credit': - return last_date(credit(lines))['date'] - elif based_on == 'newest_debit': - return last_date(debit(lines))['date'] + if based_on == "newest": + return last_date(lines)["date"] + elif based_on == "newest_credit": + return last_date(credit(lines))["date"] + elif based_on == "newest_debit": + return last_date(debit(lines))["date"] # reconcilation date will be today # when date is None return None @@ -150,22 +147,21 @@ class MassReconcileBase(models.AbstractModel): or partial (False) """ self.ensure_one() - ml_obj = self.env['account.move.line'] + ml_obj = self.env["account.move.line"] below_writeoff, sum_debit, sum_credit = self._below_writeoff_limit( lines, self.write_off ) rec_date = self._get_rec_date(lines, self.date_base_on) - line_rs = ml_obj.browse([l['id'] for l in lines]).with_context( - date_p=rec_date, - comment=_('Automatic Write Off')) + line_rs = ml_obj.browse([l["id"] for l in lines]).with_context( + date_p=rec_date, comment=_("Automatic Write Off") + ) if below_writeoff: if sum_credit > sum_debit: writeoff_account = self.account_profit_id else: writeoff_account = self.account_lost_id line_rs.reconcile( - writeoff_acc_id=writeoff_account, - writeoff_journal_id=self.journal_id + writeoff_acc_id=writeoff_account, writeoff_journal_id=self.journal_id ) return True, True elif allow_partial: @@ -181,8 +177,7 @@ class MassReconcileBase(models.AbstractModel): else: writeoff_account = self.expense_exchange_account_id line_rs.reconcile( - writeoff_acc_id=writeoff_account, - writeoff_journal_id=self.journal_id + writeoff_acc_id=writeoff_account, writeoff_journal_id=self.journal_id ) return True, False return False, False diff --git a/account_mass_reconcile/models/mass_reconcile.py b/account_mass_reconcile/models/mass_reconcile.py index a9d86c6a..828897a9 100644 --- a/account_mass_reconcile/models/mass_reconcile.py +++ b/account_mass_reconcile/models/mass_reconcile.py @@ -2,12 +2,11 @@ # Copyright 2010 Sébastien Beau # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from datetime import datetime import logging +from datetime import datetime -from odoo import models, api, fields, _ +from odoo import _, api, fields, models, sql_db from odoo.exceptions import Warning as UserError -from odoo import sql_db _logger = logging.getLogger(__name__) @@ -20,91 +19,64 @@ class MassReconcileOptions(models.AbstractModel): This allows decoupling of the methods and the wizards and allows to launch the wizards alone """ - _name = 'mass.reconcile.options' - _description = 'Options of a reconciliation profile' + + _name = "mass.reconcile.options" + _description = "Options of a reconciliation profile" @api.model def _get_rec_base_date(self): - return [ - ('newest', 'Most recent move line'), - ('actual', 'Today'), - ] + return [("newest", "Most recent move line"), ("actual", "Today")] - write_off = fields.Float( - 'Write off allowed', - default=0., - ) - account_lost_id = fields.Many2one( - 'account.account', - string="Account Lost", - ) - account_profit_id = fields.Many2one( - 'account.account', - string="Account Profit", - ) - journal_id = fields.Many2one( - 'account.journal', - string="Journal", - ) + write_off = fields.Float("Write off allowed", default=0.0) + account_lost_id = fields.Many2one("account.account", string="Account Lost") + account_profit_id = fields.Many2one("account.account", string="Account Profit") + journal_id = fields.Many2one("account.journal", string="Journal") date_base_on = fields.Selection( - '_get_rec_base_date', + "_get_rec_base_date", required=True, - string='Date of reconciliation', - default='newest', - ) - _filter = fields.Char( - string='Filter', oldname='filter', + string="Date of reconciliation", + default="newest", ) + _filter = fields.Char(string="Filter", oldname="filter") income_exchange_account_id = fields.Many2one( - 'account.account', - string='Gain Exchange Rate Account', + "account.account", string="Gain Exchange Rate Account" ) expense_exchange_account_id = fields.Many2one( - 'account.account', - string='Loss Exchange Rate Account', + "account.account", string="Loss Exchange Rate Account" ) class AccountMassReconcileMethod(models.Model): - _name = 'account.mass.reconcile.method' - _description = 'reconcile method for account_mass_reconcile' - _inherit = 'mass.reconcile.options' - _order = 'sequence' + _name = "account.mass.reconcile.method" + _description = "reconcile method for account_mass_reconcile" + _inherit = "mass.reconcile.options" + _order = "sequence" @staticmethod def _get_reconcilation_methods(): return [ - ('mass.reconcile.simple.name', 'Simple. Amount and Name'), - ('mass.reconcile.simple.partner', 'Simple. Amount and Partner'), - ('mass.reconcile.simple.reference', - 'Simple. Amount and Reference'), - ('mass.reconcile.advanced.ref', - 'Advanced. Partner and Ref.'), + ("mass.reconcile.simple.name", "Simple. Amount and Name"), + ("mass.reconcile.simple.partner", "Simple. Amount and Partner"), + ("mass.reconcile.simple.reference", "Simple. Amount and Reference"), + ("mass.reconcile.advanced.ref", "Advanced. Partner and Ref."), ] def _selection_name(self): return self._get_reconcilation_methods() - name = fields.Selection( - '_selection_name', - string='Type', - required=True, - ) + name = fields.Selection("_selection_name", string="Type", required=True) sequence = fields.Integer( - string='Sequence', + string="Sequence", default=1, required=True, help="The sequence field is used to order the reconcile method", ) task_id = fields.Many2one( - 'account.mass.reconcile', - string='Task', - required=True, - ondelete='cascade', + "account.mass.reconcile", string="Task", required=True, ondelete="cascade" ) company_id = fields.Many2one( - 'res.company', - string='Company', + "res.company", + string="Company", related="task_id.company_id", store=True, readonly=True, @@ -112,90 +84,75 @@ class AccountMassReconcileMethod(models.Model): class AccountMassReconcile(models.Model): - _name = 'account.mass.reconcile' - _inherit = ['mail.thread'] - _description = 'account mass reconcile' + _name = "account.mass.reconcile" + _inherit = ["mail.thread"] + _description = "account mass reconcile" @api.multi - @api.depends('account') + @api.depends("account") def _get_total_unrec(self): - obj_move_line = self.env['account.move.line'] + obj_move_line = self.env["account.move.line"] for rec in self: rec.unreconciled_count = obj_move_line.search_count( - [('account_id', '=', rec.account.id), - ('reconciled', '=', False)]) + [("account_id", "=", rec.account.id), ("reconciled", "=", False)] + ) @api.multi - @api.depends('history_ids') + @api.depends("history_ids") def _last_history(self): # do a search() for retrieving the latest history line, # as a read() will badly split the list of ids with 'date desc' # and return the wrong result. - history_obj = self.env['mass.reconcile.history'] + history_obj = self.env["mass.reconcile.history"] for rec in self: last_history_rs = history_obj.search( - [('mass_reconcile_id', '=', rec.id)], - limit=1, order='date desc' - ) + [("mass_reconcile_id", "=", rec.id)], limit=1, order="date desc" + ) rec.last_history = last_history_rs or False - name = fields.Char( - string='Name', - required=True, - ) - account = fields.Many2one( - 'account.account', - string='Account', - required=True, - ) + name = fields.Char(string="Name", required=True) + account = fields.Many2one("account.account", string="Account", required=True) reconcile_method = fields.One2many( - 'account.mass.reconcile.method', - 'task_id', - string='Method', + "account.mass.reconcile.method", "task_id", string="Method" ) unreconciled_count = fields.Integer( - string='Unreconciled Items', - compute='_get_total_unrec', + string="Unreconciled Items", compute="_get_total_unrec" ) history_ids = fields.One2many( - 'mass.reconcile.history', - 'mass_reconcile_id', - string='History', - readonly=True, + "mass.reconcile.history", "mass_reconcile_id", string="History", readonly=True ) last_history = fields.Many2one( - 'mass.reconcile.history', - string='Last history', readonly=True, - compute='_last_history', - ) - company_id = fields.Many2one( - 'res.company', - string='Company', + "mass.reconcile.history", + string="Last history", + readonly=True, + compute="_last_history", ) + company_id = fields.Many2one("res.company", string="Company") @staticmethod def _prepare_run_transient(rec_method): - return {'account_id': rec_method.task_id.account.id, - 'write_off': rec_method.write_off, - 'account_lost_id': (rec_method.account_lost_id.id), - 'account_profit_id': (rec_method.account_profit_id.id), - 'income_exchange_account_id': - (rec_method.income_exchange_account_id.id), - 'expense_exchange_account_id': - (rec_method.income_exchange_account_id.id), - 'journal_id': (rec_method.journal_id.id), - 'date_base_on': rec_method.date_base_on, - '_filter': rec_method._filter} + return { + "account_id": rec_method.task_id.account.id, + "write_off": rec_method.write_off, + "account_lost_id": (rec_method.account_lost_id.id), + "account_profit_id": (rec_method.account_profit_id.id), + "income_exchange_account_id": (rec_method.income_exchange_account_id.id), + "expense_exchange_account_id": (rec_method.income_exchange_account_id.id), + "journal_id": (rec_method.journal_id.id), + "date_base_on": rec_method.date_base_on, + "_filter": rec_method._filter, + } @api.multi def run_reconcile(self): def find_reconcile_ids(fieldname, move_line_ids): if not move_line_ids: return [] - sql = ("SELECT DISTINCT " + fieldname + - " FROM account_move_line " - " WHERE id in %s " - " AND " + fieldname + " IS NOT NULL") + sql = ( + "SELECT DISTINCT " + fieldname + " FROM account_move_line " + " WHERE id in %s " + " AND " + fieldname + " IS NOT NULL" + ) self.env.cr.execute(sql, (tuple(move_line_ids),)) res = self.env.cr.fetchall() return [row[0] for row in res] @@ -207,10 +164,8 @@ class AccountMassReconcile(models.Model): for rec in self: ctx = self.env.context.copy() - ctx['commit_every'] = ( - rec.account.company_id.reconciliation_commit_every - ) - if ctx['commit_every']: + ctx["commit_every"] = rec.account.company_id.reconciliation_commit_every + if ctx["commit_every"]: new_cr = sql_db.db_connect(self.env.cr.dbname).cursor() else: new_cr = self.env.cr @@ -220,46 +175,38 @@ class AccountMassReconcile(models.Model): for method in rec.reconcile_method: rec_model = self.env[method.name] - auto_rec_id = rec_model.create( - self._prepare_run_transient(method) - ) + auto_rec_id = rec_model.create(self._prepare_run_transient(method)) ml_rec_ids = auto_rec_id.automatic_reconcile() all_ml_rec_ids += ml_rec_ids - reconcile_ids = find_reconcile_ids( - 'full_reconcile_id', - all_ml_rec_ids - ) - self.env['mass.reconcile.history'].create( + reconcile_ids = find_reconcile_ids("full_reconcile_id", all_ml_rec_ids) + self.env["mass.reconcile.history"].create( { - 'mass_reconcile_id': rec.id, - 'date': fields.Datetime.now(), - 'reconcile_ids': [ - (4, rid) for rid in reconcile_ids - ], - }) + "mass_reconcile_id": rec.id, + "date": fields.Datetime.now(), + "reconcile_ids": [(4, rid) for rid in reconcile_ids], + } + ) except Exception as e: # In case of error, we log it in the mail thread, log the # stack trace and create an empty history line; otherwise, # the cron will just loop on this reconcile task. _logger.exception( - "The reconcile task %s had an exception: %s", - rec.name, str(e) + "The reconcile task %s had an exception: %s", rec.name, str(e) ) - message = _("There was an error during reconciliation : %s") \ - % str(e) + message = _("There was an error during reconciliation : %s") % str(e) rec.message_post(body=message) - self.env['mass.reconcile.history'].create( + self.env["mass.reconcile.history"].create( { - 'mass_reconcile_id': rec.id, - 'date': fields.Datetime.now(), - 'reconcile_ids': [], + "mass_reconcile_id": rec.id, + "date": fields.Datetime.now(), + "reconcile_ids": [], } ) finally: - if ctx['commit_every']: + if ctx["commit_every"]: new_cr.commit() new_cr.close() @@ -271,33 +218,32 @@ class AccountMassReconcile(models.Model): task. """ raise UserError( - _('There is no history of reconciled ' - 'items on the task: %s.') % self.name + _("There is no history of reconciled " "items on the task: %s.") % self.name ) @staticmethod def _open_move_line_list(move_line_ids, name): return { - 'name': name, - 'view_mode': 'tree,form', - 'view_id': False, - 'view_type': 'form', - 'res_model': 'account.move.line', - 'type': 'ir.actions.act_window', - 'nodestroy': True, - 'target': 'current', - 'domain': [('id', 'in', move_line_ids)], + "name": name, + "view_mode": "tree,form", + "view_id": False, + "view_type": "form", + "res_model": "account.move.line", + "type": "ir.actions.act_window", + "nodestroy": True, + "target": "current", + "domain": [("id", "in", move_line_ids)], } @api.multi def open_unreconcile(self): """ Open the view of move line with the unreconciled move lines""" self.ensure_one() - obj_move_line = self.env['account.move.line'] + obj_move_line = self.env["account.move.line"] lines = obj_move_line.search( - [('account_id', '=', self.account.id), - ('reconciled', '=', False)]) - name = _('Unreconciled items') + [("account_id", "=", self.account.id), ("reconciled", "=", False)] + ) + name = _("Unreconciled items") return self._open_move_line_list(lines.ids or [], name) def last_history_reconcile(self): @@ -318,6 +264,7 @@ class AccountMassReconcile(models.Model): :returns: True in case of success or raises an exception """ + def _get_date(reconcile): if reconcile.last_history.date: return fields.Datetime.to_datetime(reconcile.last_history.date) diff --git a/account_mass_reconcile/models/mass_reconcile_history.py b/account_mass_reconcile/models/mass_reconcile_history.py index 80a29df5..4b10a6fb 100644 --- a/account_mass_reconcile/models/mass_reconcile_history.py +++ b/account_mass_reconcile/models/mass_reconcile_history.py @@ -1,7 +1,7 @@ # Copyright 2012-2016 Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import models, api, fields, _ +from odoo import _, api, fields, models class MassReconcileHistory(models.Model): @@ -9,47 +9,40 @@ class MassReconcileHistory(models.Model): Each history stores the list of reconciliations done """ - _name = 'mass.reconcile.history' - _description = 'Store an history of the runs per profile' - _rec_name = 'mass_reconcile_id' - _order = 'date DESC' + + _name = "mass.reconcile.history" + _description = "Store an history of the runs per profile" + _rec_name = "mass_reconcile_id" + _order = "date DESC" @api.multi - @api.depends('reconcile_ids') + @api.depends("reconcile_ids") def _get_reconcile_line_ids(self): for rec in self: - rec.reconcile_line_ids = rec.mapped( - 'reconcile_ids.reconciled_line_ids' - ).ids + rec.reconcile_line_ids = rec.mapped("reconcile_ids.reconciled_line_ids").ids mass_reconcile_id = fields.Many2one( - 'account.mass.reconcile', - string='Reconcile Profile', - readonly=True, - ) - date = fields.Datetime( - string='Run date', - readonly=True, - required=True, + "account.mass.reconcile", string="Reconcile Profile", readonly=True ) + date = fields.Datetime(string="Run date", readonly=True, required=True) reconcile_ids = fields.Many2many( - comodel_name='account.full.reconcile', - relation='account_full_reconcile_history_rel', - string='Full Reconciliations', + comodel_name="account.full.reconcile", + relation="account_full_reconcile_history_rel", + string="Full Reconciliations", readonly=True, ) reconcile_line_ids = fields.Many2many( - comodel_name='account.move.line', - relation='account_move_line_history_rel', - string='Reconciled Items', - compute='_get_reconcile_line_ids', + comodel_name="account.move.line", + relation="account_move_line_history_rel", + string="Reconciled Items", + compute="_get_reconcile_line_ids", ) company_id = fields.Many2one( - 'res.company', - string='Company', + "res.company", + string="Company", store=True, readonly=True, - related='mass_reconcile_id.company_id', + related="mass_reconcile_id.company_id", ) @api.multi @@ -60,18 +53,18 @@ class MassReconcileHistory(models.Model): :param history_id: id of the history :return: action to open the move lines """ - move_line_ids = self.mapped('reconcile_ids.reconciled_line_ids').ids - name = _('Reconciliations') + move_line_ids = self.mapped("reconcile_ids.reconciled_line_ids").ids + name = _("Reconciliations") return { - 'name': name, - 'view_mode': 'tree,form', - 'view_id': False, - 'view_type': 'form', - 'res_model': 'account.move.line', - 'type': 'ir.actions.act_window', - 'nodestroy': True, - 'target': 'current', - 'domain': [('id', 'in', move_line_ids)], + "name": name, + "view_mode": "tree,form", + "view_id": False, + "view_type": "form", + "res_model": "account.move.line", + "type": "ir.actions.act_window", + "nodestroy": True, + "target": "current", + "domain": [("id", "in", move_line_ids)], } @api.multi diff --git a/account_mass_reconcile/models/res_config.py b/account_mass_reconcile/models/res_config.py index 5fe00af8..5d24fce8 100644 --- a/account_mass_reconcile/models/res_config.py +++ b/account_mass_reconcile/models/res_config.py @@ -1,11 +1,11 @@ # Copyright 2014-2016 Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import models, fields +from odoo import fields, models class AccountConfigSettings(models.TransientModel): - _inherit = 'res.config.settings' + _inherit = "res.config.settings" reconciliation_commit_every = fields.Integer( related="company_id.reconciliation_commit_every", diff --git a/account_mass_reconcile/models/simple_reconciliation.py b/account_mass_reconcile/models/simple_reconciliation.py index de521e22..0c0f5244 100644 --- a/account_mass_reconcile/models/simple_reconciliation.py +++ b/account_mass_reconcile/models/simple_reconciliation.py @@ -2,12 +2,12 @@ # Copyright 2010 Sébastien Beau # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import models, api +from odoo import api, models class MassReconcileSimple(models.AbstractModel): - _name = 'mass.reconcile.simple' - _inherit = 'mass.reconcile.base' + _name = "mass.reconcile.simple" + _inherit = "mass.reconcile.base" # has to be subclassed # field name used as key for matching the move lines @@ -19,27 +19,26 @@ class MassReconcileSimple(models.AbstractModel): raise ValueError("_key_field has to be defined") count = 0 res = [] - while (count < len(lines)): + while count < len(lines): for i in range(count + 1, len(lines)): if lines[count][self._key_field] != lines[i][self._key_field]: break check = False - if lines[count]['credit'] > 0 and lines[i]['debit'] > 0: + if lines[count]["credit"] > 0 and lines[i]["debit"] > 0: credit_line = lines[count] debit_line = lines[i] check = True - elif lines[i]['credit'] > 0 and lines[count]['debit'] > 0: + elif lines[i]["credit"] > 0 and lines[count]["debit"] > 0: credit_line = lines[i] debit_line = lines[count] check = True if not check: continue reconciled, dummy = self._reconcile_lines( - [credit_line, debit_line], - allow_partial=False - ) + [credit_line, debit_line], allow_partial=False + ) if reconciled: - res += [credit_line['id'], debit_line['id']] + res += [credit_line["id"], debit_line["id"]] del lines[i] break count += 1 @@ -56,11 +55,9 @@ class MassReconcileSimple(models.AbstractModel): where += " AND account_move_line.%s IS NOT NULL " % self._key_field where2, params2 = self._get_filter() - query = ' '.join(( - select, - self._from_query(), - where, where2, - self._simple_order())) + query = " ".join( + (select, self._from_query(), where, where2, self._simple_order()) + ) self.env.cr.execute(query, params + params2) lines = self.env.cr.dictfetchall() @@ -68,27 +65,27 @@ class MassReconcileSimple(models.AbstractModel): class MassReconcileSimpleName(models.TransientModel): - _name = 'mass.reconcile.simple.name' - _inherit = 'mass.reconcile.simple' + _name = "mass.reconcile.simple.name" + _inherit = "mass.reconcile.simple" # has to be subclassed # field name used as key for matching the move lines - _key_field = 'name' + _key_field = "name" class MassReconcileSimplePartner(models.TransientModel): - _name = 'mass.reconcile.simple.partner' - _inherit = 'mass.reconcile.simple' + _name = "mass.reconcile.simple.partner" + _inherit = "mass.reconcile.simple" # has to be subclassed # field name used as key for matching the move lines - _key_field = 'partner_id' + _key_field = "partner_id" class MassReconcileSimpleReference(models.TransientModel): - _name = 'mass.reconcile.simple.reference' - _inherit = 'mass.reconcile.simple' + _name = "mass.reconcile.simple.reference" + _inherit = "mass.reconcile.simple" # has to be subclassed # field name used as key for matching the move lines - _key_field = 'ref' + _key_field = "ref" diff --git a/account_mass_reconcile/security/ir_rule.xml b/account_mass_reconcile/security/ir_rule.xml index cbb2fc94..333d6e95 100644 --- a/account_mass_reconcile/security/ir_rule.xml +++ b/account_mass_reconcile/security/ir_rule.xml @@ -1,25 +1,27 @@ - + - - - Mass reconcile multi-company - - - ['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])] - - - - Mass reconcile history multi-company - - - ['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])] - - - - Mass reconcile method multi-company - - - ['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])] - - + + Mass reconcile multi-company + + + ['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])] + + + Mass reconcile history multi-company + + + ['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])] + + + Mass reconcile method multi-company + + + ['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])] + diff --git a/account_mass_reconcile/tests/test_onchange_company.py b/account_mass_reconcile/tests/test_onchange_company.py index 202e2429..9db6eef4 100644 --- a/account_mass_reconcile/tests/test_onchange_company.py +++ b/account_mass_reconcile/tests/test_onchange_company.py @@ -1,30 +1,31 @@ # © 2014-2016 Camptocamp SA (Damien Crier) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo.tests import common from odoo import tools from odoo.modules import get_module_resource +from odoo.tests import common class TestOnChange(common.SavepointCase): - @classmethod def setUpClass(cls): super(TestOnChange, cls).setUpClass() - tools.convert_file(cls.cr, 'account', - get_module_resource('account', 'test', - 'account_minimal_test.xml'), - {}, 'init', False, 'test') - acc_setting = cls.env['res.config.settings'] + tools.convert_file( + cls.cr, + "account", + get_module_resource("account", "test", "account_minimal_test.xml"), + {}, + "init", + False, + "test", + ) + acc_setting = cls.env["res.config.settings"] cls.acc_setting_obj = acc_setting.create({}) - cls.company_obj = cls.env['res.company'] + cls.company_obj = cls.env["res.company"] # analytic defaults account creation - cls.main_company = cls.env.ref('base.main_company') + cls.main_company = cls.env.ref("base.main_company") cls.sec_company = cls.company_obj.create( - { - 'name': 'Second company', - 'reconciliation_commit_every': 80 - } + {"name": "Second company", "reconciliation_commit_every": 80} ) def test_retrieve_analytic_account(self): @@ -33,12 +34,12 @@ class TestOnChange(common.SavepointCase): self.acc_setting_obj.company_id = self.sec_company - self.assertEqual(sec_company_commit, - self.acc_setting_obj.reconciliation_commit_every, - False) + self.assertEqual( + sec_company_commit, self.acc_setting_obj.reconciliation_commit_every, False + ) self.acc_setting_obj.company_id = self.main_company - self.assertEqual(main_company_commit, - self.acc_setting_obj.reconciliation_commit_every, - False) + self.assertEqual( + main_company_commit, self.acc_setting_obj.reconciliation_commit_every, False + ) diff --git a/account_mass_reconcile/tests/test_reconcile.py b/account_mass_reconcile/tests/test_reconcile.py index 83ac5fc1..06901e8d 100644 --- a/account_mass_reconcile/tests/test_reconcile.py +++ b/account_mass_reconcile/tests/test_reconcile.py @@ -1,42 +1,43 @@ # © 2014-2016 Camptocamp SA (Damien Crier) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo.tests import common -from odoo import fields, exceptions, tools +from odoo import exceptions, fields, tools from odoo.modules import get_module_resource +from odoo.tests import common class TestReconcile(common.SavepointCase): - @classmethod def setUpClass(cls): super(TestReconcile, cls).setUpClass() - tools.convert_file(cls.cr, 'account', - get_module_resource('account', 'test', - 'account_minimal_test.xml'), - {}, 'init', False, 'test') - cls.rec_history_obj = cls.env['mass.reconcile.history'] - cls.mass_rec_obj = cls.env['account.mass.reconcile'] - cls.mass_rec_method_obj = ( - cls.env['account.mass.reconcile.method'] + tools.convert_file( + cls.cr, + "account", + get_module_resource("account", "test", "account_minimal_test.xml"), + {}, + "init", + False, + "test", + ) + cls.rec_history_obj = cls.env["mass.reconcile.history"] + cls.mass_rec_obj = cls.env["account.mass.reconcile"] + cls.mass_rec_method_obj = cls.env["account.mass.reconcile.method"] + cls.mass_rec = cls.mass_rec_obj.create( + {"name": "AER2", "account": cls.env.ref("account.a_salary_expense").id} + ) + cls.mass_rec_method = cls.mass_rec_method_obj.create( + { + "name": "mass.reconcile.simple.name", + "sequence": "10", + "task_id": cls.mass_rec.id, + } + ) + cls.mass_rec_no_history = cls.mass_rec_obj.create( + {"name": "AER3", "account": cls.env.ref("account.a_salary_expense").id} + ) + cls.rec_history = cls.rec_history_obj.create( + {"mass_reconcile_id": cls.mass_rec.id, "date": fields.Datetime.now()} ) - cls.mass_rec = cls.mass_rec_obj.create({ - 'name': 'AER2', - 'account': cls.env.ref('account.a_salary_expense').id, - }) - cls.mass_rec_method = cls.mass_rec_method_obj.create({ - 'name': 'mass.reconcile.simple.name', - 'sequence': '10', - 'task_id': cls.mass_rec.id, - }) - cls.mass_rec_no_history = cls.mass_rec_obj.create({ - 'name': 'AER3', - 'account': cls.env.ref('account.a_salary_expense').id, - }) - cls.rec_history = cls.rec_history_obj.create({ - 'mass_reconcile_id': cls.mass_rec.id, - 'date': fields.Datetime.now(), - }) def test_last_history(self): mass_rec_last_hist = self.mass_rec.last_history @@ -52,9 +53,8 @@ class TestReconcile(common.SavepointCase): def test_open_unreconcile(self): res = self.mass_rec.open_unreconcile() - self.assertEqual([('id', 'in', [])], res.get('domain', [])) + self.assertEqual([("id", "in", [])], res.get("domain", [])) def test_prepare_run_transient(self): res = self.mass_rec._prepare_run_transient(self.mass_rec_method) - self.assertEqual(self.ref('account.a_salary_expense'), - res.get('account_id', 0)) + self.assertEqual(self.ref("account.a_salary_expense"), res.get("account_id", 0)) diff --git a/account_mass_reconcile/tests/test_reconcile_history.py b/account_mass_reconcile/tests/test_reconcile_history.py index b6ec4323..89ea2647 100644 --- a/account_mass_reconcile/tests/test_reconcile_history.py +++ b/account_mass_reconcile/tests/test_reconcile_history.py @@ -1,42 +1,37 @@ # © 2014-2016 Camptocamp SA (Damien Crier) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo.tests import common from odoo import fields, tools from odoo.modules import get_module_resource +from odoo.tests import common class TestReconcileHistory(common.SavepointCase): - @classmethod def setUpClass(cls): super(TestReconcileHistory, cls).setUpClass() - tools.convert_file(cls.cr, 'account', - get_module_resource('account', 'test', - 'account_minimal_test.xml'), - {}, 'init', False, 'test') - cls.rec_history_obj = cls.env['mass.reconcile.history'] - cls.mass_rec_obj = cls.env['account.mass.reconcile'] + tools.convert_file( + cls.cr, + "account", + get_module_resource("account", "test", "account_minimal_test.xml"), + {}, + "init", + False, + "test", + ) + cls.rec_history_obj = cls.env["mass.reconcile.history"] + cls.mass_rec_obj = cls.env["account.mass.reconcile"] cls.mass_rec = cls.mass_rec_obj.create( - { - 'name': 'AER1', - 'account': cls.env.ref('account.a_expense').id, - - } - ) + {"name": "AER1", "account": cls.env.ref("account.a_expense").id} + ) cls.rec_history = cls.rec_history_obj.create( - { - 'mass_reconcile_id': cls.mass_rec.id, - 'date': fields.Datetime.now(), - } - ) + {"mass_reconcile_id": cls.mass_rec.id, "date": fields.Datetime.now()} + ) def test_open_full_empty(self): res = self.rec_history._open_move_lines() - self.assertEqual([('id', 'in', [])], res.get( - 'domain', [])) + self.assertEqual([("id", "in", [])], res.get("domain", [])) def test_open_full_empty_from_method(self): res = self.rec_history.open_reconcile() - self.assertEqual([('id', 'in', [])], res.get( - 'domain', [])) + self.assertEqual([("id", "in", [])], res.get("domain", [])) diff --git a/account_mass_reconcile/tests/test_scenario_reconcile.py b/account_mass_reconcile/tests/test_scenario_reconcile.py index ff74a473..ddbef084 100644 --- a/account_mass_reconcile/tests/test_scenario_reconcile.py +++ b/account_mass_reconcile/tests/test_scenario_reconcile.py @@ -1,40 +1,40 @@ # © 2014-2016 Camptocamp SA (Damien Crier) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo.tests import common from odoo import fields, tools from odoo.modules import get_module_resource +from odoo.tests import common class TestScenarioReconcile(common.SavepointCase): - @classmethod def setUpClass(cls): super(TestScenarioReconcile, cls).setUpClass() - tools.convert_file(cls.cr, 'account', - get_module_resource('account', 'test', - 'account_minimal_test.xml'), - {}, 'init', False, 'test') - cls.rec_history_obj = cls.env['mass.reconcile.history'] - cls.mass_rec_obj = cls.env['account.mass.reconcile'] - cls.invoice_obj = cls.env['account.invoice'] - cls.bk_stmt_obj = cls.env['account.bank.statement'] - cls.bk_stmt_line_obj = cls.env['account.bank.statement.line'] - cls.acc_move_line_obj = cls.env['account.move.line'] - cls.mass_rec_method_obj = ( - cls.env['account.mass.reconcile.method'] + tools.convert_file( + cls.cr, + "account", + get_module_resource("account", "test", "account_minimal_test.xml"), + {}, + "init", + False, + "test", ) + cls.rec_history_obj = cls.env["mass.reconcile.history"] + cls.mass_rec_obj = cls.env["account.mass.reconcile"] + cls.invoice_obj = cls.env["account.invoice"] + cls.bk_stmt_obj = cls.env["account.bank.statement"] + cls.bk_stmt_line_obj = cls.env["account.bank.statement.line"] + cls.acc_move_line_obj = cls.env["account.move.line"] + cls.mass_rec_method_obj = cls.env["account.mass.reconcile.method"] cls.account_fx_income_id = cls.env.ref("account.income_fx_income").id cls.account_fx_expense_id = cls.env.ref("account.income_fx_expense").id - cls.acs_model = cls.env['res.config.settings'] + cls.acs_model = cls.env["res.config.settings"] acs_ids = cls.acs_model.search( - [('company_id', '=', cls.env.ref("base.main_company").id)] - ) + [("company_id", "=", cls.env.ref("base.main_company").id)] + ) - values = { - 'group_multi_currency': True, - } + values = {"group_multi_currency": True} if acs_ids: acs_ids.write(values) @@ -47,49 +47,56 @@ class TestScenarioReconcile(common.SavepointCase): # create invoice invoice = self.invoice_obj.create( { - 'type': 'out_invoice', - 'account_id': self.ref('account.a_recv'), - 'company_id': self.ref('base.main_company'), - 'journal_id': self.ref('account.sales_journal'), - 'partner_id': self.ref('base.res_partner_12'), - 'invoice_line_ids': [ - (0, 0, { - 'name': '[PCSC234] PC Assemble SC234', - 'account_id': self.ref('account.a_sale'), - 'price_unit': 1000.0, - 'quantity': 1.0, - 'product_id': self.ref('product.product_product_3'), - } + "type": "out_invoice", + "account_id": self.ref("account.a_recv"), + "company_id": self.ref("base.main_company"), + "journal_id": self.ref("account.sales_journal"), + "partner_id": self.ref("base.res_partner_12"), + "invoice_line_ids": [ + ( + 0, + 0, + { + "name": "[PCSC234] PC Assemble SC234", + "account_id": self.ref("account.a_sale"), + "price_unit": 1000.0, + "quantity": 1.0, + "product_id": self.ref("product.product_product_3"), + }, ) - ] + ], } ) # validate invoice invoice.action_invoice_open() - self.assertEqual('open', invoice.state) + self.assertEqual("open", invoice.state) # create bank_statement statement = self.bk_stmt_obj.create( { - 'balance_end_real': 0.0, - 'balance_start': 0.0, - 'date': fields.Date.today(), - 'journal_id': self.ref('account.bank_journal'), - 'line_ids': [ - (0, 0, { - 'amount': 1000.0, - 'partner_id': self.ref('base.res_partner_12'), - 'name': invoice.number, - 'ref': invoice.number, - }) - ] + "balance_end_real": 0.0, + "balance_start": 0.0, + "date": fields.Date.today(), + "journal_id": self.ref("account.bank_journal"), + "line_ids": [ + ( + 0, + 0, + { + "amount": 1000.0, + "partner_id": self.ref("base.res_partner_12"), + "name": invoice.number, + "ref": invoice.number, + }, + ) + ], } ) # reconcile line_id = None for l in invoice.move_id.line_ids: - if l.account_id.id == self.ref('account.a_recv'): + if l.account_id.id == self.ref("account.a_recv"): line_id = l break @@ -97,95 +104,97 @@ class TestScenarioReconcile(common.SavepointCase): statement_line.process_reconciliation( [ { - 'move_line': line_id, - 'credit': 1000.0, - 'debit': 0.0, - 'name': invoice.number, + "move_line": line_id, + "credit": 1000.0, + "debit": 0.0, + "name": invoice.number, } ] ) # unreconcile journal item created by previous reconciliation lines_to_unreconcile = self.acc_move_line_obj.search( - [('reconciled', '=', True), - ('statement_id', '=', statement.id)] + [("reconciled", "=", True), ("statement_id", "=", statement.id)] ) lines_to_unreconcile.remove_move_reconcile() # create the mass reconcile record mass_rec = self.mass_rec_obj.create( { - 'name': 'mass_reconcile_1', - 'account': self.ref('account.a_recv'), - 'reconcile_method': [ - (0, 0, { - 'name': 'mass.reconcile.simple.partner', - }) - ] + "name": "mass_reconcile_1", + "account": self.ref("account.a_recv"), + "reconcile_method": [(0, 0, {"name": "mass.reconcile.simple.partner"})], } ) # call the automatic reconcilation method mass_rec.run_reconcile() - self.assertEqual( - 'paid', - invoice.state - ) + self.assertEqual("paid", invoice.state) def test_scenario_reconcile_currency(self): # create currency rate - self.env['res.currency.rate'].create({ - 'name': fields.Date.today().strftime('%Y-%m-%d') + ' 00:00:00', - 'currency_id': self.ref('base.USD'), - 'rate': 1.5, - }) + self.env["res.currency.rate"].create( + { + "name": fields.Date.today().strftime("%Y-%m-%d") + " 00:00:00", + "currency_id": self.ref("base.USD"), + "rate": 1.5, + } + ) # create invoice invoice = self.invoice_obj.create( { - 'type': 'out_invoice', - 'account_id': self.ref('account.a_recv'), - 'company_id': self.ref('base.main_company'), - 'currency_id': self.ref('base.USD'), - 'journal_id': self.ref('account.bank_journal_usd'), - 'partner_id': self.ref('base.res_partner_12'), - 'invoice_line_ids': [ - (0, 0, { - 'name': '[PCSC234] PC Assemble SC234', - 'account_id': self.ref('account.a_sale'), - 'price_unit': 1000.0, - 'quantity': 1.0, - 'product_id': self.ref('product.product_product_3'), - }) - ] + "type": "out_invoice", + "account_id": self.ref("account.a_recv"), + "company_id": self.ref("base.main_company"), + "currency_id": self.ref("base.USD"), + "journal_id": self.ref("account.bank_journal_usd"), + "partner_id": self.ref("base.res_partner_12"), + "invoice_line_ids": [ + ( + 0, + 0, + { + "name": "[PCSC234] PC Assemble SC234", + "account_id": self.ref("account.a_sale"), + "price_unit": 1000.0, + "quantity": 1.0, + "product_id": self.ref("product.product_product_3"), + }, + ) + ], } ) # validate invoice invoice.action_invoice_open() - self.assertEqual('open', invoice.state) + self.assertEqual("open", invoice.state) # create bank_statement statement = self.bk_stmt_obj.create( { - 'balance_end_real': 0.0, - 'balance_start': 0.0, - 'date': fields.Date.today(), - 'journal_id': self.ref('account.bank_journal_usd'), - 'currency_id': self.ref('base.USD'), - 'line_ids': [ - (0, 0, { - 'amount': 1000.0, - 'amount_currency': 1500.0, - 'partner_id': self.ref('base.res_partner_12'), - 'name': invoice.number, - 'ref': invoice.number, - }) - ] + "balance_end_real": 0.0, + "balance_start": 0.0, + "date": fields.Date.today(), + "journal_id": self.ref("account.bank_journal_usd"), + "currency_id": self.ref("base.USD"), + "line_ids": [ + ( + 0, + 0, + { + "amount": 1000.0, + "amount_currency": 1500.0, + "partner_id": self.ref("base.res_partner_12"), + "name": invoice.number, + "ref": invoice.number, + }, + ) + ], } ) # reconcile line_id = None for l in invoice.move_id.line_ids: - if l.account_id.id == self.ref('account.a_recv'): + if l.account_id.id == self.ref("account.a_recv"): line_id = l break @@ -193,35 +202,27 @@ class TestScenarioReconcile(common.SavepointCase): statement_line.process_reconciliation( [ { - 'move_line': line_id, - 'credit': 1000.0, - 'debit': 0.0, - 'name': invoice.number, + "move_line": line_id, + "credit": 1000.0, + "debit": 0.0, + "name": invoice.number, } ] ) # unreconcile journal item created by previous reconciliation lines_to_unreconcile = self.acc_move_line_obj.search( - [('reconciled', '=', True), - ('statement_id', '=', statement.id)] + [("reconciled", "=", True), ("statement_id", "=", statement.id)] ) lines_to_unreconcile.remove_move_reconcile() # create the mass reconcile record mass_rec = self.mass_rec_obj.create( { - 'name': 'mass_reconcile_1', - 'account': self.ref('account.a_recv'), - 'reconcile_method': [ - (0, 0, { - 'name': 'mass.reconcile.simple.partner', - }) - ] + "name": "mass_reconcile_1", + "account": self.ref("account.a_recv"), + "reconcile_method": [(0, 0, {"name": "mass.reconcile.simple.partner"})], } ) # call the automatic reconcilation method mass_rec.run_reconcile() - self.assertEqual( - 'paid', - invoice.state - ) + self.assertEqual("paid", invoice.state) diff --git a/account_mass_reconcile/views/mass_reconcile.xml b/account_mass_reconcile/views/mass_reconcile.xml index a374c883..9247b4a1 100644 --- a/account_mass_reconcile/views/mass_reconcile.xml +++ b/account_mass_reconcile/views/mass_reconcile.xml @@ -1,4 +1,4 @@ - + @@ -8,87 +8,136 @@
-
- - - + + + - -