[IMP] account_mass_reconcile: black, isort

This commit is contained in:
Adrià Gil Sorribes
2019-12-24 13:11:07 +01:00
committed by Miquel Raïch
parent 8df9d45621
commit efca7067ef
18 changed files with 691 additions and 675 deletions

0
account_mass_reconcile/__init__.py Executable file → Normal file
View File

View File

@@ -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",
}

0
account_mass_reconcile/models/__init__.py Executable file → Normal file
View File

View File

@@ -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(),
),
)

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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",

View File

@@ -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"

View File

@@ -1,25 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="mass_reconcile_rule" model="ir.rule">
<field name="name">Mass reconcile multi-company</field>
<field name="model_id" ref="model_account_mass_reconcile"/>
<field name="global" eval="True"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field>
</record>
<record id="mass_reconcile_history_rule" model="ir.rule">
<field name="name">Mass reconcile history multi-company</field>
<field name="model_id" ref="model_mass_reconcile_history"/>
<field name="global" eval="True"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field>
</record>
<record id="mass_reconcile_method_rule" model="ir.rule">
<field name="name">Mass reconcile method multi-company</field>
<field name="model_id" ref="model_account_mass_reconcile_method"/>
<field name="global" eval="True"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field>
</record>
<record id="mass_reconcile_rule" model="ir.rule">
<field name="name">Mass reconcile multi-company</field>
<field name="model_id" ref="model_account_mass_reconcile" />
<field name="global" eval="True" />
<field
name="domain_force"
>['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field>
</record>
<record id="mass_reconcile_history_rule" model="ir.rule">
<field name="name">Mass reconcile history multi-company</field>
<field name="model_id" ref="model_mass_reconcile_history" />
<field name="global" eval="True" />
<field
name="domain_force"
>['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field>
</record>
<record id="mass_reconcile_method_rule" model="ir.rule">
<field name="name">Mass reconcile method multi-company</field>
<field name="model_id" ref="model_account_mass_reconcile_method" />
<field name="global" eval="True" />
<field
name="domain_force"
>['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field>
</record>
</odoo>

View File

@@ -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
)

View File

@@ -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))

View File

@@ -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", []))

View File

@@ -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)

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- account.mass.reconcile view -->
<record id="account_mass_reconcile_form" model="ir.ui.view">
@@ -8,87 +8,136 @@
<field name="arch" type="xml">
<form string="Automatic Mass Reconcile" version="7.0">
<header>
<button name="run_reconcile" class="oe_highlight"
string="Start Auto Reconciliation" type="object"/>
<button icon="fa-share" name="last_history_reconcile"
<button
name="run_reconcile"
class="oe_highlight"
string="Start Auto Reconciliation"
type="object"
/>
<button
icon="fa-share"
name="last_history_reconcile"
string="Display items reconciled on the last run"
type="object"/>
type="object"
/>
</header>
<sheet>
<separator colspan="4" string="Profile Information" />
<group>
<group>
<field name="name" select="1"/>
<field name="account"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="name" select="1" />
<field name="account" />
<field
name="company_id"
groups="base.group_multi_company"
/>
</group>
<group>
<group>
<field name="unreconciled_count"/>
<button icon="fa-share" name="open_unreconcile"
string="Go to unreconciled items" type="object"/>
<field name="unreconciled_count" />
<button
icon="fa-share"
name="open_unreconcile"
string="Go to unreconciled items"
type="object"
/>
</group>
</group>
</group>
<notebook colspan="4">
<page name="methods" string="Configuration">
<field name="reconcile_method" colspan = "4" nolabel="1"/>
<field name="reconcile_method" colspan="4" nolabel="1" />
</page>
<page name="history" string="History">
<field name="history_ids" nolabel="1">
<tree string="Automatic Mass Reconcile History">
<field name="date"/>
<button icon="fa-share" name="open_reconcile"
string="Go to reconciled items" type="object"/>
<field name="date" />
<button
icon="fa-share"
name="open_reconcile"
string="Go to reconciled items"
type="object"
/>
</tree>
</field>
</page>
<page name="information" string="Information">
<separator colspan="4" string="Simple. Amount and Name"/>
<label for="reconcile_method" string="Match one debit line vs one credit line. Do not allow partial reconciliation.
The lines should have the same amount (with the write-off) and the same name to be reconciled." colspan="4"/>
<separator colspan="4" string="Simple. Amount and Partner"/>
<label for="reconcile_method" string="Match one debit line vs one credit line. Do not allow partial reconciliation.
The lines should have the same amount (with the write-off) and the same partner to be reconciled." colspan="4"/>
<separator colspan="4" string="Simple. Amount and Reference"/>
<label for="reconcile_method" string="Match one debit line vs one credit line. Do not allow partial reconciliation.
The lines should have the same amount (with the write-off) and the same reference to be reconciled." colspan="4"/>
<group colspan="2" col="2">
<separator colspan="4" string="Advanced. Partner and Ref"/>
<label for="reconcile_method" string="Match multiple debit vs multiple credit entries. Allow partial reconciliation.
The lines should have the same partner, and the credit entry ref. is matched with the debit entry ref. or name." colspan="4"/>
</group>
<separator colspan="4" string="Simple. Amount and Name" />
<label
for="reconcile_method"
string="Match one debit line vs one credit line. Do not allow partial reconciliation.
The lines should have the same amount (with the write-off) and the same name to be reconciled."
colspan="4"
/>
<separator
colspan="4"
string="Simple. Amount and Partner"
/>
<label
for="reconcile_method"
string="Match one debit line vs one credit line. Do not allow partial reconciliation.
The lines should have the same amount (with the write-off) and the same partner to be reconciled."
colspan="4"
/>
<separator
colspan="4"
string="Simple. Amount and Reference"
/>
<label
for="reconcile_method"
string="Match one debit line vs one credit line. Do not allow partial reconciliation.
The lines should have the same amount (with the write-off) and the same reference to be reconciled."
colspan="4"
/>
<group colspan="2" col="2">
<separator
colspan="4"
string="Advanced. Partner and Ref"
/>
<label
for="reconcile_method"
string="Match multiple debit vs multiple credit entries. Allow partial reconciliation.
The lines should have the same partner, and the credit entry ref. is matched with the debit entry ref. or name."
colspan="4"
/>
</group>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers" />
<field name="message_ids" widget="mail_thread" />
</div>
</form>
</field>
</record>
<record id="account_mass_reconcile_tree" model="ir.ui.view">
<field name="name">account.mass.reconcile.tree</field>
<field name="priority">20</field>
<field name="model">account.mass.reconcile</field>
<field name="arch" type="xml">
<tree string="Automatic Mass Reconcile">
<field name="name"/>
<field name="account"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="unreconciled_count"/>
<button icon="fa-cogs" name="run_reconcile" colspan="4"
string="Start Auto Reconcilation" type="object"/>
<button icon="fa-share" name="last_history_reconcile" colspan="2"
string="Display items reconciled on the last run" type="object"/>
<field name="name" />
<field name="account" />
<field name="company_id" groups="base.group_multi_company" />
<field name="unreconciled_count" />
<button
icon="fa-cogs"
name="run_reconcile"
colspan="4"
string="Start Auto Reconcilation"
type="object"
/>
<button
icon="fa-share"
name="last_history_reconcile"
colspan="2"
string="Display items reconciled on the last run"
type="object"
/>
</tree>
</field>
</record>
<record id="action_account_mass_reconcile" model="ir.actions.act_window">
<field name="name">Mass Automatic Reconcile</field>
<field name="type">ir.actions.act_window</field>
@@ -96,9 +145,10 @@ The lines should have the same partner, and the credit entry ref. is matched wit
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
<p class="oe_view_nocontent_create">
Click to add a reconciliation profile.
</p><p>
</p>
<p>
A reconciliation profile specifies, for one account, how
the entries should be reconciled.
You can select one or many reconciliation methods which will
@@ -106,50 +156,58 @@ The lines should have the same partner, and the credit entry ref. is matched wit
</p>
</field>
</record>
<!-- account.mass.reconcile.method view -->
<record id="account_mass_reconcile_method_tree" model="ir.ui.view">
<field name="name">account.mass.reconcile.method.tree</field>
<field name="priority">20</field>
<field name="model">account.mass.reconcile.method</field>
<field name="arch" type="xml">
<tree editable="top" string="Automatic Mass Reconcile Method">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="write_off"/>
<field name="account_lost_id" attrs="{'required':[('write_off','>',0)]}"/>
<field name="account_profit_id" attrs="{'required':[('write_off','>',0)]}"/>
<field name="income_exchange_account_id" groups="base.group_multi_currency"/>
<field name="expense_exchange_account_id" groups="base.group_multi_currency"/>
<field name="journal_id" attrs="{'required':[('write_off','>',0)]}"/>
<field name="date_base_on"/>
<field name="sequence" widget="handle" />
<field name="name" />
<field name="write_off" />
<field
name="account_lost_id"
attrs="{'required':[('write_off','>',0)]}"
/>
<field
name="account_profit_id"
attrs="{'required':[('write_off','>',0)]}"
/>
<field
name="income_exchange_account_id"
groups="base.group_multi_currency"
/>
<field
name="expense_exchange_account_id"
groups="base.group_multi_currency"
/>
<field name="journal_id" attrs="{'required':[('write_off','>',0)]}" />
<field name="date_base_on" />
</tree>
</field>
</record>
<!-- menu item -->
<menuitem action="action_account_mass_reconcile"
id="menu_mass_reconcile" sequence="30"
parent="account.menu_finance_entries_actions"/>
<data noupdate="1">
<record forcecreate="True" id="ir_cron_run_reconciliations" model="ir.cron">
<field name="name">Do Automatic Reconciliations</field>
<field name="model_id" ref="account_mass_reconcile.model_account_mass_reconcile"/>
<field name="active" eval="False"/>
<field name="user_id" ref="base.user_root"/>
<field name="interval_number">3</field>
<field name="interval_type">hours</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
<field name="code">model.run_scheduler()</field>
</record>
</data>
<menuitem
action="action_account_mass_reconcile"
id="menu_mass_reconcile"
sequence="30"
parent="account.menu_finance_entries_actions"
/>
<data noupdate="1">
<record forcecreate="True" id="ir_cron_run_reconciliations" model="ir.cron">
<field name="name">Do Automatic Reconciliations</field>
<field
name="model_id"
ref="account_mass_reconcile.model_account_mass_reconcile"
/>
<field name="active" eval="False" />
<field name="user_id" ref="base.user_root" />
<field name="interval_number">3</field>
<field name="interval_type">hours</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False" />
<field name="code">model.run_scheduler()</field>
</record>
</data>
</odoo>

View File

@@ -1,73 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_mass_reconcile_history_search" model="ir.ui.view">
<field name="name">mass.reconcile.history.search</field>
<field name="model">mass.reconcile.history</field>
<field name="arch" type="xml">
<search string="Automatic Mass Reconcile History">
<filter icon="terp-go-today" name="Today"
<filter
icon="terp-go-today"
name="Today"
domain="[('date','&lt;', time.strftime('%%Y-%%m-%%d 23:59:59')), ('date','&gt;=', time.strftime('%%Y-%%m-%%d 00:00:00'))]"
help="Todays' Reconcilations" />
<filter icon="terp-go-week" name="7 Days"
help="Todays' Reconcilations"
/>
<filter
icon="terp-go-week"
name="7 Days"
help="Reconciliations of last 7 days"
domain="[('date','&lt;', time.strftime('%%Y-%%m-%%d 23:59:59')),('date','&gt;=',(datetime.date.today()-datetime.timedelta(days=7)).strftime('%%Y-%%m-%%d 00:00:00'))]"
/>
<separator orientation="vertical"/>
<field name="mass_reconcile_id"/>
<field name="date"/>
<newline/>
<separator orientation="vertical" />
<field name="mass_reconcile_id" />
<field name="date" />
<newline />
<group expand="0" string="Group By...">
<filter name="Reconciliation Profile"
<filter
name="Reconciliation Profile"
icon="terp-stock_effects-object-colorize"
domain="[]" context="{'group_by': 'mass_reconcile_id'}"/>
<filter name="Date" icon="terp-go-month" domain="[]"
context="{'group_by': 'date'}"/>
domain="[]"
context="{'group_by': 'mass_reconcile_id'}"
/>
<filter
name="Date"
icon="terp-go-month"
domain="[]"
context="{'group_by': 'date'}"
/>
</group>
</search>
</field>
</record>
<record id="mass_reconcile_history_form" model="ir.ui.view">
<field name="name">mass.reconcile.history.form</field>
<field name="model">mass.reconcile.history</field>
<field name="arch" type="xml">
<form string="Automatic Mass Reconcile History" version="7.0">
<header>
<button name="open_reconcile"
<button
name="open_reconcile"
string="Go to reconciled items"
icon="fa-share" type="object"/>
icon="fa-share"
type="object"
/>
</header>
<sheet>
<group>
<field name="mass_reconcile_id"/>
<field name="date"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="mass_reconcile_id" />
<field name="date" />
<field name="company_id" groups="base.group_multi_company" />
</group>
<group col="2">
<separator colspan="2" string="Reconciliations"/>
<field name="reconcile_ids" nolabel="1"/>
<separator colspan="2" string="Reconciliations" />
<field name="reconcile_ids" nolabel="1" />
</group>
</sheet>
</form>
</field>
</record>
<record id="mass_reconcile_history_tree" model="ir.ui.view">
<field name="name">mass.reconcile.history.tree</field>
<field name="model">mass.reconcile.history</field>
<field name="arch" type="xml">
<tree string="Automatic Mass Reconcile History">
<field name="mass_reconcile_id"/>
<field name="date"/>
<button icon="fa-share" name="open_reconcile"
string="Go to reconciled items" type="object"/>
<field name="mass_reconcile_id" />
<field name="date" />
<button
icon="fa-share"
name="open_reconcile"
string="Go to reconciled items"
type="object"
/>
</tree>
</field>
</record>
<record id="action_mass_reconcile_history" model="ir.actions.act_window">
<field name="name">Mass Automatic Reconcile History</field>
<field name="type">ir.actions.act_window</field>
@@ -75,13 +88,12 @@
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<act_window
context="{'search_default_mass_reconcile_id': [active_id], 'default_mass_reconcile_id': active_id}"
id="act_mass_reconcile_to_history"
name="History Details"
groups=""
res_model="mass.reconcile.history"
src_model="account.mass.reconcile"/>
context="{'search_default_mass_reconcile_id': [active_id], 'default_mass_reconcile_id': active_id}"
id="act_mass_reconcile_to_history"
name="History Details"
groups=""
res_model="mass.reconcile.history"
binding_model="account.mass.reconcile"
/>
</odoo>

View File

@@ -1,30 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_account_config" model="ir.ui.view">
<field name="name">account settings</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="account.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@id='invoicing_settings']" position="after">
<h2>Reconciliation</h2>
<div class="row mt16 o_settings_container" id="reconciliation_settings">
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane"/>
<div class="o_setting_right_pane">
<label for="reconciliation_commit_every" string="Commit frequency"/>
<div class="text-muted">
<field name="name">account settings</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="account.res_config_settings_view_form" />
<field name="arch" type="xml">
<xpath expr="//div[@id='invoicing_settings']" position="after">
<h2>Reconciliation</h2>
<div class="row mt16 o_settings_container" id="reconciliation_settings">
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane" />
<div class="o_setting_right_pane">
<label
for="reconciliation_commit_every"
string="Commit frequency"
/>
<div class="text-muted">
How often to commit when performing automatic reconciliation.
Leave zero to commit only at the end of the process.
</div>
<div class="content-group">
<field name="reconciliation_commit_every" class="oe_inline"/>
<div class="content-group">
<field
name="reconciliation_commit_every"
class="oe_inline"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</xpath>
</field>
</xpath>
</field>
</record>
</odoo>