From 941c7522a72566de373c675e07cff9f2f00bbd19 Mon Sep 17 00:00:00 2001
From: "@" <@>
Date: Mon, 18 Jun 2012 13:25:49 +0200
Subject: [PATCH] [REF] account_advanced_reconcile: arrange files
(lp:c2c-financial-addons/6.1 rev 24.2.7)
---
account_advanced_reconcile/__init__.py | 3 +-
account_advanced_reconcile/__openerp__.py | 36 +-
.../advanced_reconciliation.py | 120 +++++++
.../base_advanced_reconciliation.py | 226 +-----------
account_advanced_reconcile/easy_reconcile.py | 37 ++
.../easy_reconcile_view.xml | 20 ++
account_advanced_reconcile/wizard/__init__.py | 20 --
.../wizard/statement_auto_reconcile.py | 338 ------------------
.../wizard/statement_auto_reconcile_view.xml | 72 ----
9 files changed, 210 insertions(+), 662 deletions(-)
create mode 100644 account_advanced_reconcile/advanced_reconciliation.py
create mode 100644 account_advanced_reconcile/easy_reconcile.py
create mode 100644 account_advanced_reconcile/easy_reconcile_view.xml
delete mode 100644 account_advanced_reconcile/wizard/__init__.py
delete mode 100644 account_advanced_reconcile/wizard/statement_auto_reconcile.py
delete mode 100644 account_advanced_reconcile/wizard/statement_auto_reconcile_view.xml
diff --git a/account_advanced_reconcile/__init__.py b/account_advanced_reconcile/__init__.py
index 263cb053..1c643cae 100644
--- a/account_advanced_reconcile/__init__.py
+++ b/account_advanced_reconcile/__init__.py
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
-# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Guewen Baconnier
@@ -20,4 +19,6 @@
#
##############################################################################
+import easy_reconcile
import base_advanced_reconciliation
+import advanced_reconciliation
diff --git a/account_advanced_reconcile/__openerp__.py b/account_advanced_reconcile/__openerp__.py
index 4b1f8e81..5ca17767 100644
--- a/account_advanced_reconcile/__openerp__.py
+++ b/account_advanced_reconcile/__openerp__.py
@@ -1,7 +1,8 @@
-# -*- coding: utf-8 -*- ##############################################################################
+# -*- coding: utf-8 -*-
+##############################################################################
#
-# Author: Nicolas Bessi, Joel Grand-Guillaume
-# Copyright 2011-2012 Camptocamp SA
+# Author: Guewen Baconnier
+# Copyright 2012 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@@ -28,6 +29,21 @@
'description': """
Advanced reconciliation methods for the module account_easy_reconcile.
+account_easy_reconcile, which is a dependency, is available in the branch:
+lp:~openerp-community-committers/+junk/account-extra-addons
+This branch is temporary and will soon be merged with the Akretion master
+branch, but the master branch does not already exist. Sorry for the
+inconvenience.
+
+In addition to the features implemented in account_easy_reconcile, which are:
+ - 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
+
It implements a basis to created advanced reconciliation methods in a few lines
of code.
@@ -37,6 +53,11 @@ Typically, such a method can be:
or name
- Reconcile entries if the partner is equal and the ref match with a pattern
+And they allows:
+ - Reconciliations with multiple credit / multiple debit lines
+ - Partial reconciliations
+ - Write-off amount as well
+
A method is already implemented in this module, it matches on entries:
* Partner
* Ref on credit move lines should be case insensitive equals to the ref or
@@ -45,9 +66,6 @@ A method is already implemented in this module, it matches on entries:
The base class to find the reconciliations is built to be as efficient as
possible.
-Reconciliations with multiple credit / debit lines is possible.
-Partial reconciliation are generated.
-You can choose a write-off amount as well.
So basically, if you have an invoice with 3 payments (one per month), the first
month, it will partial reconcile the debit move line with the first payment, the second
@@ -55,16 +73,18 @@ month, it will partial reconcile the debit move line with 2 first payments,
the third month, it will make the full reconciliation.
This module is perfectly adapted for E-Commerce business where a big volume of
-move lines and so, reconciliations, is involved and payments often come from
+move lines and so, reconciliations, are involved and payments often come from
many offices.
+
""",
'website': 'http://www.camptocamp.com',
'init_xml': [],
- 'update_xml': [],
+ 'update_xml': ['easy_reconcile_view.xml'],
'demo_xml': [],
'test': [],
'images': [],
'installable': True,
'auto_install': False,
'license': 'AGPL-3',
+ 'application': True,
}
diff --git a/account_advanced_reconcile/advanced_reconciliation.py b/account_advanced_reconcile/advanced_reconciliation.py
new file mode 100644
index 00000000..dfdb8883
--- /dev/null
+++ b/account_advanced_reconcile/advanced_reconciliation.py
@@ -0,0 +1,120 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Guewen Baconnier
+# Copyright 2012 Camptocamp SA
+#
+# 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 .
+#
+##############################################################################
+
+from openerp.osv.orm import TransientModel
+
+
+class easy_reconcile_advanced_ref(TransientModel):
+
+ _name = 'easy.reconcile.advanced.ref'
+ _inherit = 'easy.reconcile.advanced'
+ _auto = True # False when inherited from AbstractModel
+
+ def _skip_line(self, cr, uid, rec, move_line, context=None):
+ """
+ When True is returned on some conditions, the credit move line
+ 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'))
+
+ def _matchers(self, cr, uid, rec, move_line, context=None):
+ """
+ Return the values used as matchers to found the opposite lines
+
+ All the matcher keys in the dict must have their equivalent in
+ the `_opposite_matchers`.
+
+ The values of each matcher key will be searched in the
+ one returned by the `_opposite_matchers`
+
+ Must be inherited to implement the matchers for one method
+
+ As instance, it can returns:
+ return ('ref', move_line['rec'])
+
+ or
+ return (('partner_id', move_line['partner_id']),
+ ('ref', "prefix_%s" % move_line['rec']))
+
+ All the matchers have to be found in the opposite lines
+ to consider them as "opposite"
+
+ The matchers will be evaluated in the same order than declared
+ vs the the opposite matchers, so you can gain performance by
+ declaring first the partners with the less computation.
+
+ All matchers should match with their opposite to be considered
+ as "matching".
+ So with the previous example, partner_id and ref have to be
+ equals on the opposite line matchers.
+
+ :return: tuple of tuples (key, value) where the keys are
+ the matchers keys
+ (must be the same than `_opposite_matchers` returns,
+ 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()))
+
+ def _opposite_matchers(self, cr, uid, rec, move_line, context=None):
+ """
+ Return the values of the opposite line used as matchers
+ so the line is matched
+
+ Must be inherited to implement the matchers for one method
+ It can be inherited to apply some formatting of fields
+ (strip(), lower() and so on)
+
+ This method is the counterpart of the `_matchers()` method.
+
+ Each matcher have to yield its value respecting the orders
+ of the `_matchers()`.
+
+ When a matcher does not correspond, the next matchers won't
+ be evaluated so the ones which need the less computation
+ have to be executed first.
+
+ If the `_matchers()` returns:
+ (('partner_id', move_line['partner_id']),
+ ('ref', move_line['ref']))
+
+ Here, you should yield :
+ yield ('partner_id', move_line['partner_id'])
+ yield ('ref', move_line['ref'])
+
+ Note that a matcher can contain multiple values, as instance,
+ if for a move line, you want to search from its `ref` in the
+ `ref` or `name` fields of the opposite move lines, you have to
+ yield ('partner_id', move_line['partner_id'])
+ yield ('ref', (move_line['ref'], move_line['name'])
+
+ An OR is used between the values for the same key.
+ An AND is used between the differents keys.
+
+ :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'].lower().strip(),
+ move_line['name'].lower().strip()))
+
diff --git a/account_advanced_reconcile/base_advanced_reconciliation.py b/account_advanced_reconcile/base_advanced_reconciliation.py
index e117525d..df26708c 100644
--- a/account_advanced_reconcile/base_advanced_reconciliation.py
+++ b/account_advanced_reconcile/base_advanced_reconciliation.py
@@ -25,22 +25,6 @@ from openerp.osv.orm import Model, AbstractModel, TransientModel
from openerp.osv import fields
-class account_easy_reconcile_method(Model):
-
- _inherit = 'account.easy.reconcile.method'
-
- def _get_all_rec_method(self, cr, uid, context=None):
- methods = super(account_easy_reconcile_method, self).\
- _get_all_rec_method(cr, uid, context=context)
- methods += [
- ('easy.reconcile.advanced.ref',
- 'Advanced method, payment ref matches with ref or name'),
- ('easy.reconcile.advanced.tid',
- 'Advanced method, payment Transaction ID matches with ref or name')
- ]
- return methods
-
-
class easy_reconcile_advanced(AbstractModel):
_name = 'easy.reconcile.advanced'
@@ -279,216 +263,12 @@ class easy_reconcile_advanced(AbstractModel):
lines_by_id = dict([(l['id'], l) for l in credit_lines + debit_lines])
for reconcile_group_ids in reconcile_groups:
group_lines = [lines_by_id[lid] for lid in reconcile_group_ids]
- reconciled, partial = self._reconcile_lines(
+ reconciled, full = self._reconcile_lines(
cr, uid, rec, group_lines, allow_partial=True, context=context)
- if reconciled and partial:
+ if reconciled and full:
reconciled_ids += reconcile_group_ids
- elif partial:
+ elif reconciled:
partial_reconciled_ids += reconcile_group_ids
return reconciled_ids, partial_reconciled_ids
-
-class easy_reconcile_advanced_ref(TransientModel):
-
- _name = 'easy.reconcile.advanced.ref'
- _inherit = 'easy.reconcile.advanced'
- _auto = True # False when inherited from AbstractModel
-
- def _skip_line(self, cr, uid, rec, move_line, context=None):
- """
- When True is returned on some conditions, the credit move line
- 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'))
-
- def _matchers(self, cr, uid, rec, move_line, context=None):
- """
- Return the values used as matchers to found the opposite lines
-
- All the matcher keys in the dict must have their equivalent in
- the `_opposite_matchers`.
-
- The values of each matcher key will be searched in the
- one returned by the `_opposite_matchers`
-
- Must be inherited to implement the matchers for one method
-
- As instance, it can returns:
- return ('ref', move_line['rec'])
-
- or
- return (('partner_id', move_line['partner_id']),
- ('ref', "prefix_%s" % move_line['rec']))
-
- All the matchers have to be found in the opposite lines
- to consider them as "opposite"
-
- The matchers will be evaluated in the same order than declared
- vs the the opposite matchers, so you can gain performance by
- declaring first the partners with the less computation.
-
- All matchers should match with their opposite to be considered
- as "matching".
- So with the previous example, partner_id and ref have to be
- equals on the opposite line matchers.
-
- :return: tuple of tuples (key, value) where the keys are
- the matchers keys
- (must be the same than `_opposite_matchers` returns,
- 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()))
-
- def _opposite_matchers(self, cr, uid, rec, move_line, context=None):
- """
- Return the values of the opposite line used as matchers
- so the line is matched
-
- Must be inherited to implement the matchers for one method
- It can be inherited to apply some formatting of fields
- (strip(), lower() and so on)
-
- This method is the counterpart of the `_matchers()` method.
-
- Each matcher have to yield its value respecting the orders
- of the `_matchers()`.
-
- When a matcher does not correspond, the next matchers won't
- be evaluated so the ones which need the less computation
- have to be executed first.
-
- If the `_matchers()` returns:
- (('partner_id', move_line['partner_id']),
- ('ref', move_line['ref']))
-
- Here, you should yield :
- yield ('partner_id', move_line['partner_id'])
- yield ('ref', move_line['ref'])
-
- Note that a matcher can contain multiple values, as instance,
- if for a move line, you want to search from its `ref` in the
- `ref` or `name` fields of the opposite move lines, you have to
- yield ('partner_id', move_line['partner_id'])
- yield ('ref', (move_line['ref'], move_line['name'])
-
- An OR is used between the values for the same key.
- An AND is used between the differents keys.
-
- :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'].lower().strip(),
- move_line['name'].lower().strip()))
-
-
-class easy_reconcile_advanced_tid(TransientModel):
-
- # tid means for transaction_id
- _name = 'easy.reconcile.advanced.tid'
- _inherit = 'easy.reconcile.advanced'
- _auto = True # False when inherited from AbstractModel
-
- def _skip_line(self, cr, uid, rec, move_line, context=None):
- """
- When True is returned on some conditions, the credit move line
- 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'))
-
- def _matchers(self, cr, uid, rec, move_line, context=None):
- """
- Return the values used as matchers to found the opposite lines
-
- All the matcher keys in the dict must have their equivalent in
- the `_opposite_matchers`.
-
- The values of each matcher key will be searched in the
- one returned by the `_opposite_matchers`
-
- Must be inherited to implement the matchers for one method
-
- As instance, it can returns:
- return ('ref', move_line['rec'])
-
- or
- return (('partner_id', move_line['partner_id']),
- ('ref', "prefix_%s" % move_line['rec']))
-
- All the matchers have to be found in the opposite lines
- to consider them as "opposite"
-
- The matchers will be evaluated in the same order than declared
- vs the the opposite matchers, so you can gain performance by
- declaring first the partners with the less computation.
-
- All matchers should match with their opposite to be considered
- as "matching".
- So with the previous example, partner_id and ref have to be
- equals on the opposite line matchers.
-
- :return: tuple of tuples (key, value) where the keys are
- the matchers keys
- (must be the same than `_opposite_matchers` returns,
- 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()))
-
- def _opposite_matchers(self, cr, uid, rec, move_line, context=None):
- """
- Return the values of the opposite line used as matchers
- so the line is matched
-
- Must be inherited to implement the matchers for one method
- It can be inherited to apply some formatting of fields
- (strip(), lower() and so on)
-
- This method is the counterpart of the `_matchers()` method.
-
- Each matcher have to yield its value respecting the orders
- of the `_matchers()`.
-
- When a matcher does not correspond, the next matchers won't
- be evaluated so the ones which need the less computation
- have to be executed first.
-
- If the `_matchers()` returns:
- (('partner_id', move_line['partner_id']),
- ('ref', move_line['ref']))
-
- Here, you should yield :
- yield ('partner_id', move_line['partner_id'])
- yield ('ref', move_line['ref'])
-
- Note that a matcher can contain multiple values, as instance,
- if for a move line, you want to search from its `ref` in the
- `ref` or `name` fields of the opposite move lines, you have to
- yield ('partner_id', move_line['partner_id'])
- yield ('ref', (move_line['ref'], move_line['name'])
-
- An OR is used between the values for the same key.
- An AND is used between the differents keys.
-
- :param dict move_line: values of the move_line
- :yield: matchers as tuple ('matcher key', value(s))
- """
- yield ('partner_id', move_line['partner_id'])
-
- prefixes = ('tid_', 'tid_mag_')
- refs = []
- if move_line.get('ref'):
- lref = move_line['ref'].lower().strip()
- refs.append(lref)
- refs += ["%s%s" % (s, lref) for s in prefixes]
-
- if move_line.get('name'):
- refs.append(move_line['name'].lower().strip())
- yield ('ref', refs)
-
diff --git a/account_advanced_reconcile/easy_reconcile.py b/account_advanced_reconcile/easy_reconcile.py
new file mode 100644
index 00000000..747a2e3c
--- /dev/null
+++ b/account_advanced_reconcile/easy_reconcile.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Guewen Baconnier
+# Copyright 2012 Camptocamp SA
+#
+# 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 .
+#
+##############################################################################
+
+from openerp.osv.orm import Model
+
+
+class account_easy_reconcile_method(Model):
+
+ _inherit = 'account.easy.reconcile.method'
+
+ def _get_all_rec_method(self, cr, uid, context=None):
+ methods = super(account_easy_reconcile_method, self).\
+ _get_all_rec_method(cr, uid, context=context)
+ methods += [
+ ('easy.reconcile.advanced.ref',
+ 'Advanced. Partner and Ref.'),
+ ]
+ return methods
+
diff --git a/account_advanced_reconcile/easy_reconcile_view.xml b/account_advanced_reconcile/easy_reconcile_view.xml
new file mode 100644
index 00000000..961add68
--- /dev/null
+++ b/account_advanced_reconcile/easy_reconcile_view.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ account.easy.reconcile.form
+ account.easy.reconcile
+ form
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/account_advanced_reconcile/wizard/__init__.py b/account_advanced_reconcile/wizard/__init__.py
deleted file mode 100644
index f72fd976..00000000
--- a/account_advanced_reconcile/wizard/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# Author Nicolas Bessi. Copyright Camptocamp SA
-#
-# 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 .
-#
-##############################################################################
-import statement_auto_reconcile
diff --git a/account_advanced_reconcile/wizard/statement_auto_reconcile.py b/account_advanced_reconcile/wizard/statement_auto_reconcile.py
deleted file mode 100644
index 732d5b55..00000000
--- a/account_advanced_reconcile/wizard/statement_auto_reconcile.py
+++ /dev/null
@@ -1,338 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# Author: Nicolas Bessi, Guewen Baconnier
-# Copyright 2011-2012 Camptocamp SA
-#
-# 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 .
-#
-##############################################################################
-
-import netsvc
-
-from osv import osv, fields
-from tools.translate import _
-from operator import itemgetter, attrgetter
-from itertools import groupby
-import logging
-logger = logging.getLogger('account.statement.reconcile')
-
-class AccountsStatementAutoReconcile(osv.osv_memory):
- _name = 'account.statement.import.automatic.reconcile'
- _description = 'Automatic Reconcile'
-
- _columns = {
- 'account_ids': fields.many2many('account.account',
- 'statement_reconcile_account_rel',
- 'reconcile_id',
- 'account_id',
- 'Accounts to Reconcile',
- domain=[('reconcile', '=', True)]),
- 'partner_ids': fields.many2many('res.partner',
- 'statement_reconcile_res_partner_rel',
- 'reconcile_id',
- 'res_partner_id',
- 'Partners to Reconcile'),
- 'invoice_ids': fields.many2many('account.invoice',
- 'statement_account_invoice_rel',
- 'reconcile_id',
- 'invoice_id',
- 'Invoices to Reconcile',
- domain = [('type','=','out_invoice')]),
- 'writeoff_acc_id': fields.many2one('account.account', 'Account'),
- 'writeoff_amount_limit': fields.float('Max amount allowed for write off'),
- 'journal_id': fields.many2one('account.journal', 'Journal'),
- 'reconciled': fields.integer('Reconciled transactions', readonly=True),
- 'allow_write_off': fields.boolean('Allow write off'),
- }
-
- def _get_reconciled(self, cr, uid, context=None):
- if context is None:
- context = {}
- return context.get('reconciled', 0)
-
- _defaults = {
- 'reconciled': _get_reconciled,
- }
-
- def return_stats(self, cr, uid, reconciled, context=None):
- obj_model = self.pool.get('ir.model.data')
- context = context or {}
- context.update({'reconciled': reconciled})
- model_data_ids = obj_model.search(
- cr, uid,
- [('model','=','ir.ui.view'),
- ('name','=','stat_account_automatic_reconcile_view1')]
- )
- resource_id = obj_model.read(
- cr, uid, model_data_ids, fields=['res_id'])[0]['res_id']
- return {
- 'view_type': 'form',
- 'view_mode': 'form',
- 'res_model': 'account.statement.import.automatic.reconcile',
- 'views': [(resource_id,'form')],
- 'type': 'ir.actions.act_window',
- 'target': 'new',
- 'context': context,
- }
-
- def _below_write_off_limit(self, cr, uid, lines,
- writeoff_limit, context=None):
-
- keys = ('debit', 'credit')
- sums = reduce(lambda x, y:
- dict((k, v + y[k]) for k, v in x.iteritems() if k in keys),
- lines)
- debit, credit = sums['debit'], sums['credit']
- writeoff_amount = debit - credit
- return bool(writeoff_limit >= abs(writeoff_amount))
-
- def _query_moves(self, cr, uid, form, context=None):
- """Select all move (debit>0) as candidate. Optionnal choice on invoice
- will filter with an inner join on the related moves.
- """
- sql_params=[]
- select_sql = ("SELECT "
- "l.account_id, "
- "l.ref as transaction_id, "
- "l.name as origin, "
- "l.id as invoice_id, "
- "l.move_id as move_id, "
- "l.id as move_line_id, "
- "l.debit, l.credit, "
- "l.partner_id "
- "FROM account_move_line l "
- "INNER JOIN account_move m "
- "ON m.id = l.move_id ")
- where_sql = (
- "WHERE "
- # "AND l.move_id NOT IN %(invoice_move_ids)s "
- "l.reconcile_id IS NULL "
- # "AND NOT EXISTS (select id FROM account_invoice i WHERE i.move_id = m.id) "
- "AND l.debit > 0 ")
- if form.account_ids:
- account_ids = [str(x.id) for x in form.account_ids]
- sql_params = {'account_ids': tuple(account_ids)}
- where_sql += "AND l.account_id in %(account_ids)s "
- if form.invoice_ids:
- invoice_ids = [str(x.id) for x in form.invoice_ids]
- where_sql += "AND i.id IN %(invoice_ids)s "
- select_sql += "INNER JOIN account_invoice i ON m.id = i.move_id "
- sql_params['invoice_ids'] = tuple(invoice_ids)
- if form.partner_ids:
- partner_ids = [str(x.id) for x in form.partner_ids]
- where_sql += "AND l.partner_id IN %(partner_ids)s "
- sql_params['partner_ids'] = tuple(partner_ids)
- sql = select_sql + where_sql
- cr.execute(sql, sql_params)
- return cr.dictfetchall()
-
- def _query_payments(self, cr, uid, account_id, invoice_move_ids, context=None):
- sql_params = {'account_id': account_id,
- 'invoice_move_ids': tuple(invoice_move_ids)}
- sql = ("SELECT l.id, l.move_id, "
- "l.ref, l.name, "
- "l.debit, l.credit, "
- "l.period_id as period_id, "
- "l.partner_id "
- "FROM account_move_line l "
- "INNER JOIN account_move m "
- "ON m.id = l.move_id "
- "WHERE l.account_id = %(account_id)s "
- "AND l.move_id NOT IN %(invoice_move_ids)s "
- "AND l.reconcile_id IS NULL "
- "AND NOT EXISTS (select id FROM account_invoice i WHERE i.move_id = m.id) "
- "AND l.credit > 0")
- cr.execute(sql, sql_params)
- return cr.dictfetchall()
-
- @staticmethod
- def _groupby_keys(keys, lines):
- res = {}
- key = keys.pop(0)
- sorted_lines = sorted(lines, key=itemgetter(key))
-
- for reference, iter_lines in groupby(sorted_lines, itemgetter(key)):
- group_lines = list(iter_lines)
-
- if keys:
- group_lines = (AccountsStatementAutoReconcile.
- _groupby_keys(keys[:], group_lines))
- else:
- # as we sort on all the keys, the last list
- # is perforce alone in the list
- group_lines = group_lines[0]
- res[reference] = group_lines
-
- return res
-
- def _search_payment_ref(self, cr, uid, all_payments,
- reference_key, reference, context=None):
- def compare_key(payment, key, reference_patterns):
- if not payment.get(key):
- return False
- if payment.get(key).lower() in reference_patterns:
- return True
-
- res = []
- if not reference:
- return res
-
- lref = reference.lower()
- reference_patterns = (lref, 'tid_' + lref, 'tid_mag_' + lref)
- res_append = res.append
- for payment in all_payments:
- if (compare_key(payment, 'ref', reference_patterns) or
- compare_key(payment, 'name', reference_patterns)):
- res_append(payment)
- # remove payment from all_payments?
-
-# if res:
-# print '----------------------------------'
-# print 'ref: ' + reference
-# for l in res:
-# print (l.get('ref','') or '') + ' ' + (l.get('name','') or '')
- return res
-
- def _search_payments(self, cr, uid, all_payments,
- references, context=None):
- payments = []
- for field_reference in references:
- ref_key, reference = field_reference
- payments = self._search_payment_ref(
- cr, uid, all_payments, ref_key, reference, context=context)
- # if match is found for one reference (transaction_id or origin)
- # we have found our payments, don't need to search for the order
- # reference
- if payments:
- break
- return payments
-
- def reconcile(self, cr, uid, form_id, context=None):
- context = context or {}
- move_line_obj = self.pool.get('account.move.line')
- period_obj = self.pool.get('account.period')
-
- if isinstance(form_id, list):
- form_id = form_id[0]
-
- form = self.browse(cr, uid, form_id)
-
- allow_write_off = form.allow_write_off
-
- if not form.account_ids :
- raise osv.except_osv(_('UserError'),
- _('You must select accounts to reconcile'))
-
- # returns a list with a dict per line :
- # [{'account_id': 5,'reference': 'A', 'move_id': 1, 'move_line_id': 1},
- # {'account_id': 5,'reference': 'A', 'move_id': 1, 'move_line_id': 2},
- # {'account_id': 6,'reference': 'B', 'move_id': 3, 'move_line_id': 3}],
- moves = self._query_moves(cr, uid, form, context=context)
- if not moves:
- return False
- # returns a tree :
- # { 5: {1: {1: {'reference': 'A', 'move_id': 1, 'move_line_id': 1}},
- # {2: {'reference': 'A', 'move_id': 1, 'move_line_id': 2}}}},
- # 6: {3: {3: {'reference': 'B', 'move_id': 3, 'move_line_id': 3}}}}}
- moves_tree = self._groupby_keys(['account_id',
- 'move_id',
- 'move_line_id'],
- moves)
-
- reconciled = 0
- details = ""
- for account_id, account_tree in moves_tree.iteritems():
- # [0] because one move id per invoice
- account_move_ids = [move_tree.keys() for
- move_tree in account_tree.values()]
-
- account_payments = self._query_payments(cr, uid,
- account_id,
- account_move_ids[0],
- context=context)
-
- for move_id, move_tree in account_tree.iteritems():
-
- # in any case one invoice = one move
- # move_id, move_tree = invoice_tree.items()[0]
-
- move_line_ids = []
- move_lines = []
- move_lines_ids_append = move_line_ids.append
- move_lines_append = move_lines.append
- for move_line_id, vals in move_tree.iteritems():
- move_lines_ids_append(move_line_id)
- move_lines_append(vals)
-
- # take the first one because the reference
- # is the same everywhere for an invoice
- transaction_id = move_lines[0]['transaction_id']
- origin = move_lines[0]['origin']
- partner_id = move_lines[0]['partner_id']
-
- references = (('transaction_id', transaction_id),
- ('origin', origin))
-
- partner_payments = [p for p in account_payments if \
- p['partner_id'] == partner_id]
- payments = self._search_payments(
- cr, uid, partner_payments, references, context=context)
-
- if not payments:
- continue
-
- payment_ids = [p['id'] for p in payments]
- # take the period of the payment last move line
- # it will be used as the reconciliation date
- # and for the write off date
- period_ids = [ml['period_id'] for ml in payments]
- periods = period_obj.browse(
- cr, uid, period_ids, context=context)
- last_period = max(periods, key=attrgetter('date_stop'))
-
- reconcile_ids = move_line_ids + payment_ids
- do_write_off = (allow_write_off and
- self._below_write_off_limit(
- cr, uid, move_lines + payments,
- form.writeoff_amount_limit,
- context=context))
- # date of reconciliation
- rec_ctx = dict(context, date_p=last_period.date_stop)
- try:
- if do_write_off:
- r_id = move_line_obj.reconcile(cr,
- uid,
- reconcile_ids,
- 'auto',
- form.writeoff_acc_id.id,
- # period of the write-off
- last_period.id,
- form.journal_id.id,
- context=rec_ctx)
- logger.info("Auto statement reconcile: Reconciled with write-off move id %s" % (move_id,))
- else:
- r_id = move_line_obj.reconcile_partial(cr,
- uid,
- reconcile_ids,
- 'manual',
- context=rec_ctx)
- logger.info("Auto statement reconcile: Partial Reconciled move id %s" % (move_id,))
- except Exception, exc:
- logger.error("Auto statement reconcile: Can't reconcile move id %s because: %s" % (move_id, exc,))
- reconciled += 1
- cr.commit()
- return self.return_stats(cr, uid, reconciled, context)
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/account_advanced_reconcile/wizard/statement_auto_reconcile_view.xml b/account_advanced_reconcile/wizard/statement_auto_reconcile_view.xml
deleted file mode 100644
index 12c9855e..00000000
--- a/account_advanced_reconcile/wizard/statement_auto_reconcile_view.xml
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
-
-
-
- Account Automatic Reconcile
- account.statement.import.automatic.reconcile
- form
-
-
-
-
-
-
- Account Automatic Reconcile
- account.statement.import.automatic.reconcile
- ir.actions.act_window
- form
- tree,form
-
- new
-
-
-
-
-
- Automatic reconcile unreconcile
- account.statement.import.automatic.reconcile
- form
-
-
-
-
-
-
-