mirror of
https://github.com/OCA/account-reconcile.git
synced 2025-01-20 12:27:39 +02:00
[IMP] account_easy_reconcile: extracted classes in modules
(lp:account-extra-addons rev 26.1.10)
This commit is contained in:
@@ -1,21 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#########################################################################
|
||||
# #
|
||||
# Copyright (C) 2010 Sébastien Beau #
|
||||
# #
|
||||
#This program is free software: you can redistribute it and/or modify #
|
||||
#it under the terms of the GNU General Public License as published by #
|
||||
#the Free Software Foundation, either version 3 of the License, or #
|
||||
#(at your option) any later version. #
|
||||
# #
|
||||
#This program is distributed in the hope that it will be useful, #
|
||||
#but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
#GNU General Public License for more details. #
|
||||
# #
|
||||
#You should have received a copy of the GNU General Public License #
|
||||
#along with this program. If not, see <http://www.gnu.org/licenses/>. #
|
||||
#########################################################################
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright 2012 Camptocamp SA (Guewen Baconnier)
|
||||
# Copyright (C) 2010 Sébastien Beau
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import easy_reconcile
|
||||
|
||||
import base_reconciliation
|
||||
import simple_reconciliation
|
||||
|
||||
@@ -1,35 +1,56 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#########################################################################
|
||||
# #
|
||||
# Copyright (C) 2010 Sébastien Beau #
|
||||
# #
|
||||
#This program is free software: you can redistribute it and/or modify #
|
||||
#it under the terms of the GNU General Public License as published by #
|
||||
#the Free Software Foundation, either version 3 of the License, or #
|
||||
#(at your option) any later version. #
|
||||
# #
|
||||
#This program is distributed in the hope that it will be useful, #
|
||||
#but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
#GNU General Public License for more details. #
|
||||
# #
|
||||
#You should have received a copy of the GNU General Public License #
|
||||
#along with this program. If not, see <http://www.gnu.org/licenses/>. #
|
||||
#########################################################################
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright 2012 Camptocamp SA (Guewen Baconnier)
|
||||
# Copyright (C) 2010 Sébastien Beau
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
{
|
||||
"name" : "Easy Reconcile",
|
||||
"version" : "1.0",
|
||||
"depends" : ["account", "base_scheduler_creator"
|
||||
],
|
||||
"author" : "Sébastien Beau",
|
||||
"description": """A new view to reconcile easily your account
|
||||
"author" : "Akretion,Camptocamp",
|
||||
"description": """
|
||||
This is a shared work between Akretion and Camptocamp in order to provide:
|
||||
- reconciliation facilities for big volume of transactions
|
||||
- setup different profiles of reconciliation by account
|
||||
- each profile can use many methods of reconciliation
|
||||
- this module is also a base to create others reconciliation methods
|
||||
which can plug in the profiles
|
||||
- a profile a reconciliation can be run manually or by a cron
|
||||
- monitoring of reconcilation runs with a few logs
|
||||
|
||||
2 simple reconciliation methods are integrated in this module, the simple
|
||||
reconciliations works on 2 lines (1 debit / 1 credit) and do not allows
|
||||
partial reconcilation, they also match on 1 key, partner or entry name.
|
||||
|
||||
You may be interested to install also the account_advanced_reconciliation
|
||||
module available at: https://code.launchpad.net/c2c-financial-addons
|
||||
This latter add more complex reconciliations, allows multiple lines and partial.
|
||||
|
||||
""",
|
||||
"website" : "http://www.akretion.com/",
|
||||
"category" : "Customer Modules",
|
||||
"category" : "Finance",
|
||||
"init_xml" : [],
|
||||
"demo_xml" : [],
|
||||
"update_xml" : ["easy_reconcile.xml"],
|
||||
"active": False,
|
||||
'license': 'AGPL-3',
|
||||
"auto_install": False,
|
||||
"installable": True,
|
||||
|
||||
}
|
||||
|
||||
207
account_easy_reconcile/base_reconciliation.py
Normal file
207
account_easy_reconcile/base_reconciliation.py
Normal file
@@ -0,0 +1,207 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright 2012 Camptocamp SA (Guewen Baconnier)
|
||||
# Copyright (C) 2010 Sébastien Beau
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv.orm import AbstractModel
|
||||
from openerp.osv import fields
|
||||
from operator import itemgetter, attrgetter
|
||||
|
||||
|
||||
class easy_reconcile_base(AbstractModel):
|
||||
"""Abstract Model for reconciliation methods"""
|
||||
|
||||
_name = 'easy.reconcile.base'
|
||||
|
||||
_inherit = 'easy.reconcile.options'
|
||||
_auto = True # restore property set to False by AbstractModel
|
||||
|
||||
_columns = {
|
||||
'account_id': fields.many2one('account.account', 'Account', required=True),
|
||||
'partner_ids': fields.many2many('res.partner',
|
||||
string="Restrict on partners"),
|
||||
# other columns are inherited from easy.reconcile.options
|
||||
}
|
||||
|
||||
def automatic_reconcile(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
:return: list of reconciled ids, list of partially reconciled entries
|
||||
"""
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
assert len(ids) == 1, "Has to be called on one id"
|
||||
rec = self.browse(cr, uid, ids[0], context=context)
|
||||
return self._action_rec(cr, uid, rec, context=context)
|
||||
|
||||
def _action_rec(self, cr, uid, rec, context=None):
|
||||
"""Must be inherited to implement the reconciliation
|
||||
:return: list of reconciled ids
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _base_columns(self, rec):
|
||||
"""Mandatory columns for move lines queries
|
||||
An extra column aliased as `key` should be defined
|
||||
in each query."""
|
||||
aml_cols = (
|
||||
'id',
|
||||
'debit',
|
||||
'credit',
|
||||
'date',
|
||||
'period_id',
|
||||
'ref',
|
||||
'name',
|
||||
'partner_id',
|
||||
'account_id',
|
||||
'move_id')
|
||||
return ["account_move_line.%s" % col for col in aml_cols]
|
||||
|
||||
def _select(self, rec, *args, **kwargs):
|
||||
return "SELECT %s" % ', '.join(self._base_columns(rec))
|
||||
|
||||
def _from(self, rec, *args, **kwargs):
|
||||
return "FROM account_move_line"
|
||||
|
||||
def _where(self, rec, *args, **kwargs):
|
||||
where = ("WHERE account_move_line.account_id = %s "
|
||||
"AND account_move_line.reconcile_id IS NULL ")
|
||||
# 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
|
||||
# accomodate with that
|
||||
params = [rec.account_id.id]
|
||||
|
||||
if rec.partner_ids:
|
||||
where += " AND account_move_line.partner_id IN %s"
|
||||
params.append(tuple([l.id for l in rec.partner_ids]))
|
||||
return where, params
|
||||
|
||||
def _get_filter(self, cr, uid, rec, context):
|
||||
ml_obj = self.pool.get('account.move.line')
|
||||
where = ''
|
||||
params = []
|
||||
if rec.filter:
|
||||
dummy, where, params = ml_obj._where_calc(
|
||||
cr, uid, rec.filter, context=context).get_sql()
|
||||
if where:
|
||||
where = " AND %s" % where
|
||||
return where, params
|
||||
|
||||
def _below_writeoff_limit(self, cr, uid, rec, lines,
|
||||
writeoff_limit, context=None):
|
||||
precision = self.pool.get('decimal.precision').precision_get(
|
||||
cr, uid, 'Account')
|
||||
keys = ('debit', 'credit')
|
||||
sums = reduce(
|
||||
lambda line, memo:
|
||||
dict((key, value + memo[key])
|
||||
for key, value
|
||||
in line.iteritems()
|
||||
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
|
||||
|
||||
def _get_rec_date(self, cr, uid, rec, lines, based_on='end_period_last_credit', context=None):
|
||||
period_obj = self.pool.get('account.period')
|
||||
|
||||
def last_period(mlines):
|
||||
period_ids = [ml['period_id'] for ml in mlines]
|
||||
periods = period_obj.browse(
|
||||
cr, uid, period_ids, context=context)
|
||||
return max(periods, key=attrgetter('date_stop'))
|
||||
|
||||
def last_date(mlines):
|
||||
return max(mlines, key=itemgetter('date'))
|
||||
|
||||
def credit(mlines):
|
||||
return [l for l in mlines if l['credit'] > 0]
|
||||
|
||||
def debit(mlines):
|
||||
return [l for l in mlines if l['debit'] > 0]
|
||||
|
||||
if based_on == 'end_period_last_credit':
|
||||
return last_period(credit(lines)).date_stop
|
||||
if based_on == 'end_period':
|
||||
return last_period(lines).date_stop
|
||||
elif 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
|
||||
|
||||
def _reconcile_lines(self, cr, uid, rec, lines, allow_partial=False, context=None):
|
||||
""" Try to reconcile given lines
|
||||
|
||||
:param list lines: list of dict of move lines, they must at least
|
||||
contain values for : id, debit, credit
|
||||
:param boolean allow_partial: if True, partial reconciliation will be
|
||||
created, otherwise only Full reconciliation will be created
|
||||
:return: tuple of boolean values, first item is wether the the entries
|
||||
have been reconciled or not, the second is wether the reconciliation
|
||||
is full (True) or partial (False)
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
ml_obj = self.pool.get('account.move.line')
|
||||
writeoff = rec.write_off
|
||||
|
||||
keys = ('debit', 'credit')
|
||||
|
||||
line_ids = [l['id'] for l in lines]
|
||||
below_writeoff, sum_debit, sum_credit = self._below_writeoff_limit(
|
||||
cr, uid, rec, lines, writeoff, context=context)
|
||||
date = self._get_rec_date(
|
||||
cr, uid, rec, lines, rec.date_base_on, context=context)
|
||||
|
||||
rec_ctx = dict(context, date_p=date)
|
||||
if below_writeoff:
|
||||
if sum_credit < sum_debit:
|
||||
writeoff_account_id = rec.account_profit_id.id
|
||||
else:
|
||||
writeoff_account_id = rec.account_lost_id.id
|
||||
|
||||
period_id = self.pool.get('account.period').find(
|
||||
cr, uid, dt=date, context=context)[0]
|
||||
|
||||
ml_obj.reconcile(
|
||||
cr, uid,
|
||||
line_ids,
|
||||
type='auto',
|
||||
writeoff_acc_id=writeoff_account_id,
|
||||
writeoff_period_id=period_id,
|
||||
writeoff_journal_id=rec.journal_id.id,
|
||||
context=rec_ctx)
|
||||
return True, True
|
||||
elif allow_partial:
|
||||
ml_obj.reconcile_partial(
|
||||
cr, uid,
|
||||
line_ids,
|
||||
type='manual',
|
||||
context=rec_ctx)
|
||||
return True, False
|
||||
|
||||
return False, False
|
||||
|
||||
@@ -1,58 +1,80 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#########################################################################
|
||||
# #
|
||||
# Copyright (C) 2010 Sébastien Beau #
|
||||
# Copyright (C) 2012 Camptocamp SA (authored by Guewen Baconnier) #
|
||||
# #
|
||||
#This program is free software: you can redistribute it and/or modify #
|
||||
#it under the terms of the GNU General Public License as published by #
|
||||
#the Free Software Foundation, either version 3 of the License, or #
|
||||
#(at your option) any later version. #
|
||||
# #
|
||||
#This program is distributed in the hope that it will be useful, #
|
||||
#but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
#GNU General Public License for more details. #
|
||||
# #
|
||||
#You should have received a copy of the GNU General Public License #
|
||||
#along with this program. If not, see <http://www.gnu.org/licenses/>. #
|
||||
#########################################################################
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright 2012 Camptocamp SA (Guewen Baconnier)
|
||||
# Copyright (C) 2010 Sébastien Beau
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import time
|
||||
import string
|
||||
from operator import itemgetter, attrgetter
|
||||
from openerp.osv.orm import Model, TransientModel, AbstractModel
|
||||
from openerp.osv.orm import Model, AbstractModel
|
||||
from openerp.osv import fields
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
|
||||
|
||||
class easy_reconcile_options(AbstractModel):
|
||||
"""Options of a reconciliation profile, columns
|
||||
shared by the configuration of methods and by the
|
||||
reconciliation wizards. This allows decoupling
|
||||
of the methods with the wizards and allows to
|
||||
launch the wizards alone
|
||||
"""
|
||||
|
||||
_name = 'easy.reconcile.options'
|
||||
|
||||
def _get_rec_base_date(self, cr, uid, context=None):
|
||||
return [('end_period_last_credit', 'End of period of most recent credit'),
|
||||
('newest', 'Most recent move line'),
|
||||
('actual', 'Today'),
|
||||
('end_period', 'End of period of most recent move line'),
|
||||
('newest_credit', 'Date of most recent credit'),
|
||||
('newest_debit', 'Date of most recent debit')]
|
||||
|
||||
_columns = {
|
||||
'write_off': fields.float('Write off allowed'),
|
||||
'account_lost_id': fields.many2one('account.account', 'Account Lost'),
|
||||
'account_profit_id': fields.many2one('account.account', 'Account Profit'),
|
||||
'journal_id': fields.many2one('account.journal', 'Journal'),
|
||||
'date_base_on': fields.selection(_get_rec_base_date,
|
||||
required=True,
|
||||
string='Date of reconcilation'),
|
||||
'filter': fields.char('Filter', size=128),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'write_off': 0.,
|
||||
'date_base_on': 'end_period_last_credit',
|
||||
}
|
||||
|
||||
|
||||
class account_easy_reconcile_method(Model):
|
||||
|
||||
_name = 'account.easy.reconcile.method'
|
||||
_description = 'reconcile method for account_easy_reconcile'
|
||||
|
||||
def onchange_name(self, cr, uid, id, name, write_off, context=None):
|
||||
if name in ['easy.reconcile.simple.name',
|
||||
'easy.reconcile.simple.partner']:
|
||||
if write_off>0:
|
||||
return {'value': {'require_write_off': True, 'require_account_id': True, 'require_journal_id': True}}
|
||||
return {'value': {'require_write_off': True}}
|
||||
return {}
|
||||
_inherit = 'easy.reconcile.options'
|
||||
_auto = True # restore property set to False by AbstractModel
|
||||
|
||||
def onchange_write_off(self, cr, uid, id, name, write_off, context=None):
|
||||
if name in ['easy.reconcile.simple.name',
|
||||
'easy.reconcile.simple.partner']:
|
||||
if write_off>0:
|
||||
return {'value': {'require_account_id': True, 'require_journal_id': True}}
|
||||
else:
|
||||
return {'value': {'require_account_id': False, 'require_journal_id': False}}
|
||||
return {}
|
||||
_order = 'sequence'
|
||||
|
||||
def _get_all_rec_method(self, cr, uid, context=None):
|
||||
return [
|
||||
('easy.reconcile.simple.name', 'Simple method based on amount and name'),
|
||||
('easy.reconcile.simple.partner', 'Simple method based on amount and partner'),
|
||||
('easy.reconcile.simple.name', 'Simple. Amount and Name'),
|
||||
('easy.reconcile.simple.partner', 'Simple. Amount and Partner'),
|
||||
]
|
||||
|
||||
def _get_rec_method(self, cr, uid, context=None):
|
||||
@@ -60,32 +82,16 @@ class account_easy_reconcile_method(Model):
|
||||
|
||||
_columns = {
|
||||
'name': fields.selection(_get_rec_method, 'Type', size=128, required=True),
|
||||
'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the reconcile method"),
|
||||
'write_off': fields.float('Write off Value'),
|
||||
'account_lost_id': fields.many2one('account.account', 'Account Lost'),
|
||||
'account_profit_id': fields.many2one('account.account', 'Account Profit'),
|
||||
'journal_id': fields.many2one('account.journal', 'Journal'),
|
||||
'require_write_off': fields.boolean('Require Write-off'),
|
||||
'require_account_id': fields.boolean('Require Account'),
|
||||
'require_journal_id': fields.boolean('Require Journal'),
|
||||
'date_base_on': fields.selection(
|
||||
[('newest', 'Most recent move line'),
|
||||
('actual', 'Today'),
|
||||
('end_period_last_credit', 'End of period of most recent credit'),
|
||||
('end_period', 'End of period of most recent move line'),
|
||||
('newest_credit', 'Date of most recent credit'),
|
||||
('newest_debit', 'Date of most recent debit')],
|
||||
string='Date of reconcilation'),
|
||||
'filter': fields.char('Filter', size=128),
|
||||
'task_id': fields.many2one('account.easy.reconcile', 'Task', required=True, ondelete='cascade'),
|
||||
'sequence': fields.integer('Sequence', required=True,
|
||||
help="The sequence field is used to order the reconcile method"),
|
||||
'task_id': fields.many2one('account.easy.reconcile', 'Task',
|
||||
required=True, ondelete='cascade'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'write_off': lambda *a: 0,
|
||||
'sequence': 1,
|
||||
}
|
||||
|
||||
_order = 'sequence'
|
||||
|
||||
def init(self, cr):
|
||||
""" Migration stuff, name is not anymore methods names
|
||||
but models name"""
|
||||
@@ -100,16 +106,34 @@ class account_easy_reconcile_method(Model):
|
||||
WHERE name = 'action_rec_auto_name'
|
||||
""")
|
||||
|
||||
|
||||
class account_easy_reconcile(Model):
|
||||
|
||||
_name = 'account.easy.reconcile'
|
||||
_description = 'account easy reconcile'
|
||||
|
||||
def _get_unrec_number(self, cr, uid, ids, name, arg, context=None):
|
||||
def _get_total_unrec(self, cr, uid, ids, name, arg, context=None):
|
||||
obj_move_line = self.pool.get('account.move.line')
|
||||
res={}
|
||||
for task in self.read(cr, uid, ids, ['account'], context=context):
|
||||
res[task['id']] = len(obj_move_line.search(cr, uid, [('account_id', '=', task['account'][0]), ('reconcile_id', '=', False)], context=context))
|
||||
res = {}
|
||||
for task in self.browse(cr, uid, ids, context=context):
|
||||
res[task.id] = len(obj_move_line.search(
|
||||
cr, uid,
|
||||
[('account_id', '=', task.account.id),
|
||||
('reconcile_id', '=', False),
|
||||
('reconcile_partial_id', '=', False)],
|
||||
context=context))
|
||||
return res
|
||||
|
||||
def _get_partial_rec(self, cr, uid, ids, name, arg, context=None):
|
||||
obj_move_line = self.pool.get('account.move.line')
|
||||
res = {}
|
||||
for task in self.browse(cr, uid, ids, context=context):
|
||||
res[task.id] = len(obj_move_line.search(
|
||||
cr, uid,
|
||||
[('account_id', '=', task.account.id),
|
||||
('reconcile_id', '=', False),
|
||||
('reconcile_partial_id', '!=', False)],
|
||||
context=context))
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
@@ -118,285 +142,65 @@ class account_easy_reconcile(Model):
|
||||
'reconcile_method': fields.one2many('account.easy.reconcile.method', 'task_id', 'Method'),
|
||||
'scheduler': fields.many2one('ir.cron', 'scheduler', readonly=True),
|
||||
'rec_log': fields.text('log', readonly=True),
|
||||
'unreconcile_entry_number': fields.function(_get_unrec_number, method=True, type='integer', string='Unreconcile Entries'),
|
||||
'unreconciled_count': fields.function(_get_total_unrec,
|
||||
type='integer', string='Fully Unreconciled Entries'),
|
||||
'reconciled_partial_count': fields.function(_get_partial_rec,
|
||||
type='integer', string='Partially Reconciled Entries'),
|
||||
}
|
||||
|
||||
def copy_data(self, cr, uid, id, default=None, context=None):
|
||||
if default is None:
|
||||
default = {}
|
||||
default = dict(default, rec_log=False, scheduler=False)
|
||||
return super(account_easy_reconcile, self).copy_data(
|
||||
cr, uid, id, default=default, context=context)
|
||||
|
||||
def _prepare_run_transient(self, cr, uid, rec_method, context=None):
|
||||
return {'account_id': rec_method.task_id.account.id,
|
||||
'write_off': rec_method.write_off,
|
||||
'account_lost_id': rec_method.account_lost_id and \
|
||||
rec_method.account_lost_id.id,
|
||||
'account_profit_id': rec_method.account_profit_id and \
|
||||
rec_method.account_profit_id.id,
|
||||
'journal_id': rec_method.journal_id and rec_method.journal_id.id,
|
||||
'date_base_on': rec_method.date_base_on,
|
||||
'filter': rec_method.filter}
|
||||
|
||||
def run_reconcile(self, cr, uid, ids, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
for rec_id in ids:
|
||||
rec = self.browse(cr, uid, rec_id, context=context)
|
||||
total_rec = 0
|
||||
details = ''
|
||||
total_partial_rec = 0
|
||||
details = []
|
||||
count = 0
|
||||
|
||||
for method in rec.reconcile_method:
|
||||
count += 1
|
||||
ctx = dict(
|
||||
context,
|
||||
date_base_on=method.date_base_on,
|
||||
filter=eval(method.filter or '[]'),
|
||||
write_off=(method.write_off > 0 and method.write_off) or 0,
|
||||
account_lost_id=method.account_lost_id.id,
|
||||
account_profit_id=method.account_profit_id.id,
|
||||
journal_id=method.journal_id.id)
|
||||
|
||||
rec_model = self.pool.get(method.name)
|
||||
auto_rec_id = rec_model.create(
|
||||
cr, uid, {'easy_reconcile_id': rec_id}, context=ctx)
|
||||
res = rec_model.automatic_reconcile(cr, uid, auto_rec_id, context=ctx)
|
||||
cr, uid,
|
||||
self._prepare_run_transient(cr, uid, method, context=context),
|
||||
context=context)
|
||||
|
||||
rec_ids, partial_ids = rec_model.automatic_reconcile(
|
||||
cr, uid, auto_rec_id, context=context)
|
||||
|
||||
details.append(_('method %d : full: %d lines, partial: %d lines') % \
|
||||
(count, len(rec_ids), len(partial_ids)))
|
||||
|
||||
total_rec += len(rec_ids)
|
||||
total_partial_rec += len(partial_ids)
|
||||
|
||||
details += _(' method %d : %d lines |') % (count, res)
|
||||
log = self.read(cr, uid, rec_id, ['rec_log'], context=context)['rec_log']
|
||||
log_lines = log and log.splitlines() or []
|
||||
log_lines[0:0] = [_('%s : %d lines have been reconciled (%s)') %
|
||||
(time.strftime(DEFAULT_SERVER_DATETIME_FORMAT), total_rec, details[0:-2])]
|
||||
log_lines[0:0] = [_("%s : %d lines have been fully reconciled" \
|
||||
" and %d lines have been partially reconciled (%s)") % \
|
||||
(time.strftime(DEFAULT_SERVER_DATETIME_FORMAT), total_rec,
|
||||
total_partial_rec, ' | '.join(details))]
|
||||
log = "\n".join(log_lines)
|
||||
self.write(cr, uid, rec_id, {'rec_log': log}, context=context)
|
||||
return True
|
||||
|
||||
|
||||
class easy_reconcile_base(AbstractModel):
|
||||
"""Abstract Model for reconciliation methods"""
|
||||
|
||||
_name = 'easy.reconcile.base'
|
||||
|
||||
_columns = {
|
||||
'easy_reconcile_id': fields.many2one('account.easy.reconcile', string='Easy Reconcile')
|
||||
}
|
||||
|
||||
def automatic_reconcile(self, cr, uid, ids, context=None):
|
||||
"""Must be inherited to implement the reconciliation"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _base_columns(self, rec):
|
||||
"""Mandatory columns for move lines queries
|
||||
An extra column aliased as `key` should be defined
|
||||
in each query."""
|
||||
aml_cols = (
|
||||
'id',
|
||||
'debit',
|
||||
'credit',
|
||||
'date',
|
||||
'period_id',
|
||||
'ref',
|
||||
'name',
|
||||
'partner_id',
|
||||
'account_id',
|
||||
'move_id')
|
||||
return ["account_move_line.%s" % col for col in aml_cols]
|
||||
|
||||
def _select(self, rec, *args, **kwargs):
|
||||
return "SELECT %s" % ', '.join(self._base_columns(rec))
|
||||
|
||||
def _from(self, rec, *args, **kwargs):
|
||||
return "FROM account_move_line"
|
||||
|
||||
def _where(self, rec, *args, **kwargs):
|
||||
where = ("WHERE account_move_line.account_id = %s "
|
||||
"AND account_move_line.reconcile_id IS NULL ")
|
||||
# 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
|
||||
# accomodate with that
|
||||
params = [rec.easy_reconcile_id.account.id]
|
||||
return where, params
|
||||
|
||||
def _get_filter(self, cr, uid, rec, context):
|
||||
ml_obj = self.pool.get('account.move.line')
|
||||
where = ''
|
||||
params = []
|
||||
if context.get('filter'):
|
||||
dummy, where, params = ml_obj._where_calc(
|
||||
cr, uid, context['filter'], context=context).get_sql()
|
||||
if where:
|
||||
where = " AND %s" % where
|
||||
return where, params
|
||||
|
||||
def _below_writeoff_limit(self, cr, uid, lines,
|
||||
writeoff_limit, context=None):
|
||||
precision = self.pool.get('decimal.precision').precision_get(
|
||||
cr, uid, 'Account')
|
||||
keys = ('debit', 'credit')
|
||||
sums = reduce(
|
||||
lambda line, memo:
|
||||
dict((key, value + memo[key])
|
||||
for key, value
|
||||
in line.iteritems()
|
||||
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
|
||||
|
||||
def _get_rec_date(self, cr, uid, lines, based_on='end_period_last_credit', context=None):
|
||||
period_obj = self.pool.get('account.period')
|
||||
|
||||
def last_period(mlines):
|
||||
period_ids = [ml['period_id'] for ml in mlines]
|
||||
periods = period_obj.browse(
|
||||
cr, uid, period_ids, context=context)
|
||||
return max(periods, key=attrgetter('date_stop'))
|
||||
|
||||
def last_date(mlines):
|
||||
return max(mlines, key=itemgetter('date'))
|
||||
|
||||
def credit(mlines):
|
||||
return [l for l in mlines if l['credit'] > 0]
|
||||
|
||||
def debit(mlines):
|
||||
return [l for l in mlines if l['debit'] > 0]
|
||||
|
||||
if based_on == 'end_period_last_credit':
|
||||
return last_period(credit(lines)).date_stop
|
||||
if based_on == 'end_period':
|
||||
return last_period(lines).date_stop
|
||||
elif 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
|
||||
|
||||
def _reconcile_lines(self, cr, uid, lines, allow_partial=False, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
ml_obj = self.pool.get('account.move.line')
|
||||
writeoff = context.get('write_off', 0.)
|
||||
|
||||
keys = ('debit', 'credit')
|
||||
|
||||
line_ids = [l['id'] for l in lines]
|
||||
below_writeoff, sum_debit, sum_credit = self._below_writeoff_limit(
|
||||
cr, uid, lines, writeoff, context=context)
|
||||
date = self._get_rec_date(
|
||||
cr, uid, lines, context.get('date_base_on'), context=context)
|
||||
|
||||
rec_ctx = dict(context, date_p=date)
|
||||
if below_writeoff:
|
||||
if sum_credit < sum_debit:
|
||||
writeoff_account_id = context.get('account_profit_id', False)
|
||||
else:
|
||||
writeoff_account_id = context.get('account_lost_id', False)
|
||||
|
||||
period_id = self.pool.get('account.period').find(
|
||||
cr, uid, dt=date, context=context)[0]
|
||||
|
||||
ml_obj.reconcile(
|
||||
cr, uid,
|
||||
line_ids,
|
||||
type='auto',
|
||||
writeoff_acc_id=writeoff_account_id,
|
||||
writeoff_period_id=period_id,
|
||||
writeoff_journal_id=context.get('journal_id'),
|
||||
context=rec_ctx)
|
||||
return True
|
||||
elif allow_partial:
|
||||
ml_obj.reconcile_partial(
|
||||
cr, uid,
|
||||
line_ids,
|
||||
type='manual',
|
||||
context=rec_ctx)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class easy_reconcile_simple(AbstractModel):
|
||||
|
||||
_name = 'easy.reconcile.simple'
|
||||
_inherit = 'easy.reconcile.base'
|
||||
|
||||
# has to be subclassed
|
||||
# field name used as key for matching the move lines
|
||||
_key_field = None
|
||||
|
||||
def rec_auto_lines_simple(self, cr, uid, lines, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
if self._key_field is None:
|
||||
raise ValueError("_key_field has to be defined")
|
||||
|
||||
count = 0
|
||||
res = 0
|
||||
while (count < len(lines)):
|
||||
for i in range(count+1, len(lines)):
|
||||
writeoff_account_id = False
|
||||
if lines[count][self._key_field] != lines[i][self._key_field]:
|
||||
break
|
||||
|
||||
check = False
|
||||
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:
|
||||
credit_line = lines[i]
|
||||
debit_line = lines[count]
|
||||
check = True
|
||||
if not check:
|
||||
continue
|
||||
|
||||
if self._reconcile_lines(cr, uid, [credit_line, debit_line],
|
||||
allow_partial=False, context=context):
|
||||
res += 2
|
||||
del lines[i]
|
||||
break
|
||||
count += 1
|
||||
return res
|
||||
|
||||
def _simple_order(self, rec, *args, **kwargs):
|
||||
return "ORDER BY account_move_line.%s" % self._key_field
|
||||
|
||||
def _action_rec_simple(self, cr, uid, rec, context=None):
|
||||
"""Match only 2 move lines, do not allow partial reconcile"""
|
||||
select = self._select(rec)
|
||||
select += ", account_move_line.%s " % self._key_field
|
||||
where, params = self._where(rec)
|
||||
where += " AND account_move_line.%s IS NOT NULL " % self._key_field
|
||||
|
||||
where2, params2 = self._get_filter(cr, uid, rec, context=context)
|
||||
query = ' '.join((
|
||||
select,
|
||||
self._from(rec),
|
||||
where, where2,
|
||||
self._simple_order(rec)))
|
||||
|
||||
cr.execute(query, params + params2)
|
||||
lines = cr.dictfetchall()
|
||||
return self.rec_auto_lines_simple(cr, uid, lines, context)
|
||||
|
||||
def automatic_reconcile(self, cr, uid, ids, context=None):
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
assert len(ids) == 1, "Has to be called on one id"
|
||||
rec = self.browse(cr, uid, ids[0], context=context)
|
||||
return self._action_rec_simple(cr, uid, rec, context=context)
|
||||
|
||||
|
||||
class easy_reconcile_simple_name(TransientModel):
|
||||
|
||||
_name = 'easy.reconcile.simple.name'
|
||||
_inherit = 'easy.reconcile.simple'
|
||||
_auto = True # False when inherited from AbstractModel
|
||||
|
||||
# has to be subclassed
|
||||
# field name used as key for matching the move lines
|
||||
_key_field = 'name'
|
||||
|
||||
|
||||
class easy_reconcile_simple_partner(TransientModel):
|
||||
|
||||
_name = 'easy.reconcile.simple.partner'
|
||||
_inherit = 'easy.reconcile.simple'
|
||||
_auto = True # False when inherited from AbstractModel
|
||||
|
||||
# has to be subclassed
|
||||
# field name used as key for matching the move lines
|
||||
_key_field = 'partner'
|
||||
|
||||
|
||||
@@ -1,121 +1,124 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<data>
|
||||
|
||||
<!-- account.easy.reconcile view -->
|
||||
|
||||
<record id="account_easy_reconcile_form" model="ir.ui.view">
|
||||
<field name="name">account.easy.reconcile.form</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="model">account.easy.reconcile</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="easy reconcile">
|
||||
<separator colspan="4" string="Task Information" />
|
||||
<field name="name" select="1"/>
|
||||
<field name="account"/>
|
||||
<field name="unreconcile_entry_number"/>
|
||||
<field name="scheduler"/>
|
||||
<separator colspan="4" string="Reconcile Method" />
|
||||
<!-- account.easy.reconcile view -->
|
||||
<record id="account_easy_reconcile_form" model="ir.ui.view">
|
||||
<field name="name">account.easy.reconcile.form</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="model">account.easy.reconcile</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Automatic Easy Reconcile">
|
||||
<separator colspan="4" string="Task Information" />
|
||||
<field name="name" select="1"/>
|
||||
<field name="account"/>
|
||||
<field name="unreconciled_count"/>
|
||||
<field name="reconciled_partial_count"/>
|
||||
<field name="scheduler"/>
|
||||
<separator colspan="4" string="Reconcile Method" />
|
||||
<notebook colspan="4">
|
||||
<page name="methods" string="Configuration">
|
||||
<field name="reconcile_method" colspan = "4" nolabel="1"/>
|
||||
<button icon="gtk-ok" name="run_reconcile" colspan = "4" string="Start Auto Reconcilation" type="object"/>
|
||||
<separator colspan="4" string="Log" />
|
||||
<field name="rec_log" colspan = "4" nolabel="1"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="account_easy_reconcile_tree" model="ir.ui.view">
|
||||
<field name="name">account.easy.reconcile.tree</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="model">account.easy.reconcile</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="easy reconcile">
|
||||
<field name="name"/>
|
||||
<field name="account"/>
|
||||
<field name="reconcile_method"/>
|
||||
<field name="scheduler"/>
|
||||
<field name="unreconcile_entry_number"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_account_easy_reconcile" model="ir.actions.act_window">
|
||||
<field name="name">account easy reconcile</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">account.easy.reconcile</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{'wizard_object' : 'account.easy.reconcile', 'function' : 'action_rec_auto', 'object_link' : 'account.easy.reconcile' }</field>
|
||||
</record>
|
||||
</page>
|
||||
<page name="information" string="Information">
|
||||
<separator colspan="4" string="Simple. Amount and Name"/>
|
||||
<label string="Match one debit line vs one credit line. Do not allow partial reconcilation.
|
||||
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 Name"/>
|
||||
<label string="Match one debit line vs one credit line. Do not allow partial reconcilation.
|
||||
The lines should have the same amount (with the write-off) and the same partner to be reconciled." colspan="4"/>
|
||||
|
||||
</page>
|
||||
</notebook>
|
||||
<button icon="gtk-ok" name="run_reconcile" colspan = "4" string="Start Auto Reconcilation" type="object"/>
|
||||
<separator colspan="4" string="Log" />
|
||||
<field name="rec_log" colspan = "4" nolabel="1"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="account_easy_reconcile_tree" model="ir.ui.view">
|
||||
<field name="name">account.easy.reconcile.tree</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="model">account.easy.reconcile</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Automatic Easy Reconcile">
|
||||
<field name="name"/>
|
||||
<field name="account"/>
|
||||
<field name="scheduler"/>
|
||||
<field name="unreconciled_count"/>
|
||||
<field name="reconciled_partial_count"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_account_easy_reconcile" model="ir.actions.act_window">
|
||||
<field name="name">Easy Automatic Reconcile</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">account.easy.reconcile</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{'wizard_object' : 'account.easy.reconcile', 'function' : 'action_rec_auto', 'object_link' : 'account.easy.reconcile' }</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- account.easy.reconcile.method view -->
|
||||
|
||||
<record id="account_easy_reconcile_method_form" model="ir.ui.view">
|
||||
<field name="name">account.easy.reconcile.method.form</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="model">account.easy.reconcile.method</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="easy reconcile method">
|
||||
<field name="sequence"/>
|
||||
<field name="name" on_change="onchange_name(name, write_off)"/>
|
||||
<field name="write_off" attrs="{'required':[('require_write_off','=',True)]}"/>
|
||||
<field name="account_lost_id" attrs="{'required':[('require_account_id','=',True)]}"/>
|
||||
<field name="account_profit_id" attrs="{'required':[('require_account_id','=',True)]}"/>
|
||||
<field name="journal_id" attrs="{'required':[('require_journal_id','=',True)]}"/>
|
||||
<field name="date_base_on"/>
|
||||
<field name="filter"/>
|
||||
<field name="require_write_off" invisible="1"/>
|
||||
<field name="require_account_id" invisible="1"/>
|
||||
<field name="require_journal_id" invisible="1"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="account_easy_reconcile_method_tree" model="ir.ui.view">
|
||||
<field name="name">account.easy.reconcile.method.tree</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="model">account.easy.reconcile.method</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree editable="top" string="easy reconcile method">
|
||||
<field name="sequence"/>
|
||||
<field name="name" on_change="onchange_name(name, write_off)"/>
|
||||
<field name="write_off" attrs="{'required':[('require_write_off','=',True)]}" on_change="onchange_write_off(name, write_off)"/>
|
||||
<field name="account_lost_id" attrs="{'required':[('require_account_id','=',True)]}"/>
|
||||
<field name="account_profit_id" attrs="{'required':[('require_account_id','=',True)]}"/>
|
||||
<field name="journal_id" attrs="{'required':[('require_journal_id','=',True)]}"/>
|
||||
<field name="date_base_on"/>
|
||||
<field name="filter"/>
|
||||
<field name="require_write_off" invisible="1"/>
|
||||
<field name="require_account_id" invisible="1"/>
|
||||
<field name="require_journal_id" invisible="1"/>
|
||||
<record id="account_easy_reconcile_method_form" model="ir.ui.view">
|
||||
<field name="name">account.easy.reconcile.method.form</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="model">account.easy.reconcile.method</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Automatic Easy Reconcile Method">
|
||||
<field name="sequence"/>
|
||||
<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="journal_id" attrs="{'required':[('write_off','>',0)]}"/>
|
||||
<field name="date_base_on"/>
|
||||
<field name="filter" groups="base.group_extended"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="account_easy_reconcile_method_tree" model="ir.ui.view">
|
||||
<field name="name">account.easy.reconcile.method.tree</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="model">account.easy.reconcile.method</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree editable="top" string="Automatic Easy Reconcile Method">
|
||||
<field name="sequence"/>
|
||||
<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="journal_id" attrs="{'required':[('write_off','>',0)]}"/>
|
||||
<field name="date_base_on"/>
|
||||
<field name="filter"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- menu item -->
|
||||
|
||||
<menuitem action="action_account_easy_reconcile" id="menu_easy_reconcile" parent="account.periodical_processing_reconciliation"/>
|
||||
<menuitem action="action_account_easy_reconcile" id="menu_easy_reconcile" parent="account.periodical_processing_reconciliation"/>
|
||||
|
||||
|
||||
<!-- button on the left -->
|
||||
|
||||
<record id="ir_action_create_scheduler_in_easy_reconcile" model="ir.values">
|
||||
<field name="key2">client_action_multi</field>
|
||||
<field name="model">account.easy.reconcile</field>
|
||||
<field name="name">Create a Scheduler</field>
|
||||
<field eval="'ir.actions.act_window,%d'%ref('base_scheduler_creator.action_scheduler_creator_wizard')" name="value"/>
|
||||
<field eval="True" name="object"/>
|
||||
</record>
|
||||
<record id="ir_action_create_scheduler_in_easy_reconcile" model="ir.values">
|
||||
<field name="key2">client_action_multi</field>
|
||||
<field name="model">account.easy.reconcile</field>
|
||||
<field name="name">Create a Scheduler</field>
|
||||
<field eval="'ir.actions.act_window,%d'%ref('base_scheduler_creator.action_scheduler_creator_wizard')" name="value"/>
|
||||
<field eval="True" name="object"/>
|
||||
</record>
|
||||
|
||||
|
||||
|
||||
|
||||
</data>
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
113
account_easy_reconcile/simple_reconciliation.py
Normal file
113
account_easy_reconcile/simple_reconciliation.py
Normal file
@@ -0,0 +1,113 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright 2012 Camptocamp SA (Guewen Baconnier)
|
||||
# Copyright (C) 2010 Sébastien Beau
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv.orm import AbstractModel, TransientModel
|
||||
|
||||
|
||||
class easy_reconcile_simple(AbstractModel):
|
||||
|
||||
_name = 'easy.reconcile.simple'
|
||||
_inherit = 'easy.reconcile.base'
|
||||
|
||||
# has to be subclassed
|
||||
# field name used as key for matching the move lines
|
||||
_key_field = None
|
||||
|
||||
def rec_auto_lines_simple(self, cr, uid, rec, lines, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
if self._key_field is None:
|
||||
raise ValueError("_key_field has to be defined")
|
||||
|
||||
count = 0
|
||||
res = []
|
||||
while (count < len(lines)):
|
||||
for i in range(count+1, len(lines)):
|
||||
writeoff_account_id = False
|
||||
if lines[count][self._key_field] != lines[i][self._key_field]:
|
||||
break
|
||||
|
||||
check = False
|
||||
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:
|
||||
credit_line = lines[i]
|
||||
debit_line = lines[count]
|
||||
check = True
|
||||
if not check:
|
||||
continue
|
||||
|
||||
reconciled, dummy = self._reconcile_lines(
|
||||
cr, uid, rec, [credit_line, debit_line],
|
||||
allow_partial=False, context=context)
|
||||
if reconciled:
|
||||
res += [credit_line['id'], debit_line['id']]
|
||||
del lines[i]
|
||||
break
|
||||
count += 1
|
||||
return res, [] # empty list for partial, only full rec in "simple" rec
|
||||
|
||||
def _simple_order(self, rec, *args, **kwargs):
|
||||
return "ORDER BY account_move_line.%s" % self._key_field
|
||||
|
||||
def _action_rec(self, cr, uid, rec, context=None):
|
||||
"""Match only 2 move lines, do not allow partial reconcile"""
|
||||
select = self._select(rec)
|
||||
select += ", account_move_line.%s " % self._key_field
|
||||
where, params = self._where(rec)
|
||||
where += " AND account_move_line.%s IS NOT NULL " % self._key_field
|
||||
|
||||
where2, params2 = self._get_filter(cr, uid, rec, context=context)
|
||||
query = ' '.join((
|
||||
select,
|
||||
self._from(rec),
|
||||
where, where2,
|
||||
self._simple_order(rec)))
|
||||
|
||||
cr.execute(query, params + params2)
|
||||
lines = cr.dictfetchall()
|
||||
return self.rec_auto_lines_simple(cr, uid, rec, lines, context)
|
||||
|
||||
|
||||
class easy_reconcile_simple_name(TransientModel):
|
||||
|
||||
_name = 'easy.reconcile.simple.name'
|
||||
_inherit = 'easy.reconcile.simple'
|
||||
_auto = True # False when inherited from AbstractModel
|
||||
|
||||
# has to be subclassed
|
||||
# field name used as key for matching the move lines
|
||||
_key_field = 'name'
|
||||
|
||||
|
||||
class easy_reconcile_simple_partner(TransientModel):
|
||||
|
||||
_name = 'easy.reconcile.simple.partner'
|
||||
_inherit = 'easy.reconcile.simple'
|
||||
_auto = True # False when inherited from AbstractModel
|
||||
|
||||
# has to be subclassed
|
||||
# field name used as key for matching the move lines
|
||||
_key_field = 'partner_id'
|
||||
|
||||
Reference in New Issue
Block a user