← Back to team overview

banking-addons-team team mailing list archive

lp:~c2c/banking-addons/bank-statement-reconcile_add_account-easy-reconcile into lp:banking-addons/bank-statement-reconcile-61

 

Alexandre Fayolle @ camptocamp has proposed merging lp:~c2c/banking-addons/bank-statement-reconcile_add_account-easy-reconcile into lp:banking-addons/bank-statement-reconcile-61.

Requested reviews:
  Sébastien BEAU - http://www.akretion.com (sebastien.beau)
  Banking Addons Team (banking-addons-team)

For more details, see:
https://code.launchpad.net/~c2c/banking-addons/bank-statement-reconcile_add_account-easy-reconcile/+merge/136663

added account_easy_reconcile from account_extra_addons
-- 
https://code.launchpad.net/~c2c/banking-addons/bank-statement-reconcile_add_account-easy-reconcile/+merge/136663
Your team Banking Addons Team is requested to review the proposed merge of lp:~c2c/banking-addons/bank-statement-reconcile_add_account-easy-reconcile into lp:banking-addons/bank-statement-reconcile-61.
=== modified file 'account_advanced_reconcile/__openerp__.py'
--- account_advanced_reconcile/__openerp__.py	2012-09-20 12:18:14 +0000
+++ account_advanced_reconcile/__openerp__.py	2012-11-28 13:35:41 +0000
@@ -25,7 +25,7 @@
  'maintainer': 'Camptocamp',
  'category': 'Finance',
  'complexity': 'normal',
- 'depends': ['account_easy_reconcile', # this comes from  lp:account-extra-addons
+ 'depends': ['account_easy_reconcile',
              ],
  'description': """
 Advanced reconciliation methods for the module account_easy_reconcile.

=== added directory 'account_easy_reconcile'
=== added file 'account_easy_reconcile/__init__.py'
--- account_easy_reconcile/__init__.py	1970-01-01 00:00:00 +0000
+++ account_easy_reconcile/__init__.py	2012-11-28 13:35:41 +0000
@@ -0,0 +1,24 @@
+# -*- 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/>.
+#
+##############################################################################
+
+import easy_reconcile
+import base_reconciliation
+import simple_reconciliation

=== added file 'account_easy_reconcile/__openerp__.py'
--- account_easy_reconcile/__openerp__.py	1970-01-01 00:00:00 +0000
+++ account_easy_reconcile/__openerp__.py	2012-11-28 13:35:41 +0000
@@ -0,0 +1,56 @@
+# -*- 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/>.
+#
+##############################################################################
+
+{
+    "name" : "Easy Reconcile",
+    "version" : "1.0",
+    "depends" : ["account", "base_scheduler_creator"
+                ],
+    "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" : "Finance",
+    "init_xml" : [],
+    "demo_xml" : [],
+    "update_xml" : ["easy_reconcile.xml"],
+    'license': 'AGPL-3',
+    "auto_install": False,
+    "installable": True,
+
+}

=== added file 'account_easy_reconcile/base_reconciliation.py'
--- account_easy_reconcile/base_reconciliation.py	1970-01-01 00:00:00 +0000
+++ account_easy_reconcile/base_reconciliation.py	2012-11-28 13:35:41 +0000
@@ -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, eval(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
+

=== added file 'account_easy_reconcile/easy_reconcile.py'
--- account_easy_reconcile/easy_reconcile.py	1970-01-01 00:00:00 +0000
+++ account_easy_reconcile/easy_reconcile.py	2012-11-28 13:35:41 +0000
@@ -0,0 +1,206 @@
+# -*- 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/>.
+#
+##############################################################################
+
+import time
+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'
+
+    _inherit = 'easy.reconcile.options'
+    _auto = True  # restore property set to False by AbstractModel
+
+    _order = 'sequence'
+
+    def _get_all_rec_method(self, cr, uid, context=None):
+        return [
+            ('easy.reconcile.simple.name', 'Simple. Amount and Name'),
+            ('easy.reconcile.simple.partner', 'Simple. Amount and Partner'),
+            ('easy.reconcile.simple.reference', 'Simple. Amount and Reference'),
+            ]
+
+    def _get_rec_method(self, cr, uid, context=None):
+        return self._get_all_rec_method(cr, uid, context=None)
+
+    _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"),
+            'task_id': fields.many2one('account.easy.reconcile', 'Task',
+                required=True, ondelete='cascade'),
+    }
+
+    _defaults = {
+        'sequence': 1,
+    }
+
+    def init(self, cr):
+        """ Migration stuff, name is not anymore methods names
+        but models name"""
+        cr.execute("""
+        UPDATE account_easy_reconcile_method
+        SET name = 'easy.reconcile.simple.partner'
+        WHERE name = 'action_rec_auto_partner'
+        """)
+        cr.execute("""
+        UPDATE account_easy_reconcile_method
+        SET name = 'easy.reconcile.simple.name'
+        WHERE name = 'action_rec_auto_name'
+        """)
+
+
+class account_easy_reconcile(Model):
+
+    _name = 'account.easy.reconcile'
+    _description = 'account easy reconcile'
+
+    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.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 = {
+        'name': fields.char('Name', size=64, required=True),
+        'account': fields.many2one('account.account', 'Account', required=True),
+        '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),
+        '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
+            total_partial_rec = 0
+            details = []
+            count = 0
+            for method in rec.reconcile_method:
+                count += 1
+
+                rec_model = self.pool.get(method.name)
+                auto_rec_id = rec_model.create(
+                    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)
+
+            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 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
+

=== added file 'account_easy_reconcile/easy_reconcile.xml'
--- account_easy_reconcile/easy_reconcile.xml	1970-01-01 00:00:00 +0000
+++ account_easy_reconcile/easy_reconcile.xml	2012-11-28 13:35:41 +0000
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?> 
+<openerp>
+<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="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"/>
+                    </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="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>
+
+    <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"/>
+
+
+<!-- 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>
+
+</data>
+</openerp>

=== added directory 'account_easy_reconcile/i18n'
=== added file 'account_easy_reconcile/i18n/fr.po'
--- account_easy_reconcile/i18n/fr.po	1970-01-01 00:00:00 +0000
+++ account_easy_reconcile/i18n/fr.po	2012-11-28 13:35:41 +0000
@@ -0,0 +1,118 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+#	* account_easy_reconcile
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 6.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-11-07 12:59+0000\n"
+"PO-Revision-Date: 2012-11-07 12:59+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: account_easy_reconcile
+#: view:account.easy.reconcile:0
+msgid "Information"
+msgstr "Information"
+
+#. module: account_easy_reconcile
+#: view:account.easy.reconcile.method:0
+msgid "Automatic Easy Reconcile Method"
+msgstr "Méthode de léttrage automatisé"
+
+#. module: account_easy_reconcile
+#: view:account.easy.reconcile:0
+msgid "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."
+msgstr "Lettre un débit avec un crédit ayant le même montant et le même partenaire. Le lettrage ne peut être partiel (écriture d'ajustement en cas d'écart)."
+
+#. module: account_easy_reconcile
+#: view:account.easy.reconcile:0
+msgid "Log"
+msgstr "Historique"
+
+#. module: account_easy_reconcile
+#: view:account.easy.reconcile:0
+msgid "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."
+msgstr "Lettre un débit avec un crédit ayant le même montant et la même description. Le lettrage ne peut être partiel (écriture d'ajustement en cas d'écart)."
+
+#. module: account_easy_reconcile
+#: view:account.easy.reconcile:0
+msgid "Automatic Easy Reconcile"
+msgstr "Léttrage automatisé"
+
+#. module: account_easy_reconcile
+#: model:ir.model,name:account_easy_reconcile.model_account_easy_reconcile_method
+msgid "reconcile method for account_easy_reconcile"
+msgstr "Méthode de léttrage"
+
+#. module: account_easy_reconcile
+#: view:account.easy.reconcile:0
+msgid "Start Auto Reconcilation"
+msgstr "Lancer le léttrage automatisé"
+
+#. module: account_easy_reconcile
+#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_simple_name
+msgid "easy.reconcile.simple.name"
+msgstr "Léttrage automatisé.simple.Description"
+
+#. module: account_easy_reconcile
+#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_options
+msgid "easy.reconcile.options"
+msgstr "Léttrage automatisé.options"
+
+#. module: account_easy_reconcile
+#: view:account.easy.reconcile:0
+msgid "Simple. Amount and Name"
+msgstr "Simple. Montant et description"
+
+#. module: account_easy_reconcile
+#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_simple
+msgid "easy.reconcile.simple"
+msgstr "Léttrage automatisé.simple"
+
+#. module: account_easy_reconcile
+#: model:ir.actions.act_window,name:account_easy_reconcile.action_account_easy_reconcile
+#: model:ir.ui.menu,name:account_easy_reconcile.menu_easy_reconcile
+msgid "Easy Automatic Reconcile"
+msgstr "Léttrage automatisé"
+
+#. module: account_easy_reconcile
+#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_simple_reference
+msgid "easy.reconcile.simple.reference"
+msgstr "Léttrage automatisé.simple.réference"
+
+#. module: account_easy_reconcile
+#: view:account.easy.reconcile:0
+msgid "Reconcile Method"
+msgstr "Méthode de léttrage"
+
+#. module: account_easy_reconcile
+#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_base
+msgid "easy.reconcile.base"
+msgstr "Léttrage automatisé.base"
+
+#. module: account_easy_reconcile
+#: view:account.easy.reconcile:0
+msgid "Configuration"
+msgstr "Configuration"
+
+#. module: account_easy_reconcile
+#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_simple_partner
+msgid "easy.reconcile.simple.partner"
+msgstr "Léttrage automatisé.simple.partenaire"
+
+#. module: account_easy_reconcile
+#: view:account.easy.reconcile:0
+msgid "Task Information"
+msgstr "Information sur la tâche"
+
+#. module: account_easy_reconcile
+#: model:ir.model,name:account_easy_reconcile.model_account_easy_reconcile
+msgid "account easy reconcile"
+msgstr "Léttrage automatisé"
+

=== added file 'account_easy_reconcile/simple_reconciliation.py'
--- account_easy_reconcile/simple_reconciliation.py	1970-01-01 00:00:00 +0000
+++ account_easy_reconcile/simple_reconciliation.py	2012-11-28 13:35:41 +0000
@@ -0,0 +1,122 @@
+# -*- 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'
+
+class easy_reconcile_simple_reference(TransientModel):
+
+    _name = 'easy.reconcile.simple.reference'
+    _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 = 'ref'


Follow ups