Merge lp:~camptocamp/banking-addons/bank-statement-ext_report_lang into lp:banking-addons/6.1

Proposed by Vincent Renaville@camptocamp
Status: Superseded
Proposed branch: lp:~camptocamp/banking-addons/bank-statement-ext_report_lang
Merge into: lp:banking-addons/6.1
Diff against target: 8071 lines (+7614/-0)
85 files modified
account_advanced_reconcile/__init__.py (+24/-0)
account_advanced_reconcile/__openerp__.py (+83/-0)
account_advanced_reconcile/advanced_reconciliation.py (+118/-0)
account_advanced_reconcile/base_advanced_reconciliation.py (+272/-0)
account_advanced_reconcile/easy_reconcile.py (+36/-0)
account_advanced_reconcile/easy_reconcile_view.xml (+19/-0)
account_advanced_reconcile/i18n/fr.po (+91/-0)
account_easy_reconcile/__init__.py (+25/-0)
account_easy_reconcile/__openerp__.py (+66/-0)
account_easy_reconcile/base_reconciliation.py (+208/-0)
account_easy_reconcile/easy_reconcile.py (+285/-0)
account_easy_reconcile/easy_reconcile.xml (+156/-0)
account_easy_reconcile/easy_reconcile_history.py (+153/-0)
account_easy_reconcile/easy_reconcile_history_view.xml (+98/-0)
account_easy_reconcile/i18n/fr.po (+425/-0)
account_easy_reconcile/security/ir.model.access.csv (+15/-0)
account_easy_reconcile/security/ir_rule.xml (+25/-0)
account_easy_reconcile/simple_reconciliation.py (+120/-0)
account_statement_base_completion/__init__.py (+23/-0)
account_statement_base_completion/__openerp__.py (+74/-0)
account_statement_base_completion/data.xml (+37/-0)
account_statement_base_completion/i18n/fr.po (+180/-0)
account_statement_base_completion/partner.py (+38/-0)
account_statement_base_completion/partner_view.xml (+22/-0)
account_statement_base_completion/security/ir.model.access.csv (+3/-0)
account_statement_base_completion/statement.py (+527/-0)
account_statement_base_completion/statement_view.xml (+104/-0)
account_statement_base_import/__init__.py (+23/-0)
account_statement_base_import/__openerp__.py (+70/-0)
account_statement_base_import/data/statement.csv (+4/-0)
account_statement_base_import/i18n/fr.po (+253/-0)
account_statement_base_import/parser/__init__.py (+25/-0)
account_statement_base_import/parser/file_parser.py (+218/-0)
account_statement_base_import/parser/generic_file_parser.py (+102/-0)
account_statement_base_import/parser/parser.py (+219/-0)
account_statement_base_import/statement.py (+303/-0)
account_statement_base_import/statement_view.xml (+46/-0)
account_statement_base_import/wizard/__init__.py (+20/-0)
account_statement_base_import/wizard/import_statement.py (+122/-0)
account_statement_base_import/wizard/import_statement_view.xml (+44/-0)
account_statement_completion_voucher/__init__.py (+20/-0)
account_statement_completion_voucher/__openerp__.py (+46/-0)
account_statement_completion_voucher/statement_view.xml (+21/-0)
account_statement_ext/__init__.py (+25/-0)
account_statement_ext/__openerp__.py (+88/-0)
account_statement_ext/account.py (+38/-0)
account_statement_ext/i18n/fr.po (+301/-0)
account_statement_ext/report.xml (+25/-0)
account_statement_ext/report/__init__.py (+26/-0)
account_statement_ext/report/bank_statement_report.mako (+69/-0)
account_statement_ext/report/bank_statement_report.py (+71/-0)
account_statement_ext/report/bank_statement_webkit_header.xml (+180/-0)
account_statement_ext/security/ir.model.access.csv (+3/-0)
account_statement_ext/security/ir_rule.xml (+10/-0)
account_statement_ext/statement.py (+678/-0)
account_statement_ext/statement_view.xml (+162/-0)
account_statement_ext/voucher.py (+49/-0)
account_statement_ext_voucher/__init__.py (+20/-0)
account_statement_ext_voucher/__openerp__.py (+52/-0)
account_statement_ext_voucher/statement_voucher.py (+51/-0)
account_statement_transactionid_completion/__init__.py (+22/-0)
account_statement_transactionid_completion/__openerp__.py (+56/-0)
account_statement_transactionid_completion/data.xml (+12/-0)
account_statement_transactionid_completion/statement.py (+94/-0)
account_statement_transactionid_completion/statement_view.xml (+22/-0)
account_statement_transactionid_import/__init__.py (+22/-0)
account_statement_transactionid_import/__openerp__.py (+60/-0)
account_statement_transactionid_import/data/statement.csv (+4/-0)
account_statement_transactionid_import/parser/__init__.py (+22/-0)
account_statement_transactionid_import/parser/transactionid_file_parser.py (+86/-0)
account_statement_transactionid_import/statement.py (+47/-0)
base_transaction_id/__init__.py (+24/-0)
base_transaction_id/__openerp__.py (+57/-0)
base_transaction_id/invoice.py (+36/-0)
base_transaction_id/invoice_view.xml (+30/-0)
base_transaction_id/sale.py (+43/-0)
base_transaction_id/sale_view.xml (+21/-0)
base_transaction_id/stock.py (+42/-0)
invoicing_voucher_killer/__init__.py (+20/-0)
invoicing_voucher_killer/__openerp__.py (+39/-0)
invoicing_voucher_killer/invoice_data.xml (+7/-0)
invoicing_voucher_killer/invoice_view.xml (+48/-0)
statement_voucher_killer/__init__.py (+21/-0)
statement_voucher_killer/__openerp__.py (+40/-0)
statement_voucher_killer/voucher.py (+128/-0)
To merge this branch: bzr merge lp:~camptocamp/banking-addons/bank-statement-ext_report_lang
Reviewer Review Type Date Requested Status
Banking Addons Core Editors Pending
Review via email: mp+175309@code.launchpad.net

This proposal supersedes a proposal from 2013-07-17.

This proposal has been superseded by a proposal from 2013-07-17.

Description of the change

This fix take the language on the partner linked to the current user, for the bank statement report

To post a comment you must log in.

Unmerged revisions

94. By Vincent Renaville@camptocamp

[FIX] fix report lang

93. By Vincent Renaville@camptocamp

[MRG] prevent account_advanced_reconcile to crash if ref in the move is not set

92. By Nicolas Bessi - Camptocamp

[MRG] [FIX] Restore default period in bank statement by taking context in account

91. By Nicolas Bessi - Camptocamp

[MRG] [IMP] Refine partner label lookup error message
[FIX] Restore error message in log
[FIX] Fix partner label lookup regexp

90. By Nicolas Bessi - Camptocamp

[MRG] Major refactoring of statement import and completion:

Fix the way to look default account on partner:
- If master account is provided in profile it will be forced
 - If the customer checkbox is checked on the found partner, type and account will be customer and receivable
 - If the supplier checkbox is checked on the found partner, type and account will be supplier and payable
 - If both checkbox are checked or none of them, it'll be based on the amount :
    If amount is positif the type and account will be customer and receivable,
    If amount is negativ, the type and account will be supplier and payable

Completion:
-Fix and refactor the the invoices lookup for completion
-Various fixes in completion rules
- Non matches lines are not mark as completed.

Optimisation:

Refactoring of statement import:
We by pass ORM to increase performances.
TODO support of sparse field

Refactoring of completion.
We have done some structural changes in order to avoid a lot of un needed call to ORM.
Bypass orm when writing to database.

These merge is required in order to fix transaction id completion rules and import +
Handle the new semantic change in OpenERP partner model

89. By Alexandre Fayolle - camptocamp

[FIX] permissions on account.statement.profil: an accountant needs write access on the model to be able to import a bank statement

  otherwise, the message.post call fails

88. By Nicolas Bessi - Camptocamp

[MRG] Fixes performance trouble when using bank_statement_label based completion rules by using memoizer pattern.

Add lines in context to be able to acces them in completion rules. It is not
mandatory as we can do line.satement_id.line_ids but it is more efficient.

Some minor cleanup

87. By Nicolas Bessi - Camptocamp

[MRG] Improve statement import wizard by using mass writing of statement line in order to avoid store field loop computation.

86. By Nicolas Bessi - Camptocamp

[MRG] Improve statement import global usability by retuning usable error message while parsing files.
Allows empty value for float in parsed CSV.

Added note about the additional dependency on python-xlrd in the description.

85. By Nicolas Bessi - Camptocamp

[MRG] Add a completion rule to allows supplier invoice completion baser on invoice number

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'account_advanced_reconcile'
2=== added file 'account_advanced_reconcile/__init__.py'
3--- account_advanced_reconcile/__init__.py 1970-01-01 00:00:00 +0000
4+++ account_advanced_reconcile/__init__.py 2013-07-17 14:55:41 +0000
5@@ -0,0 +1,24 @@
6+# -*- coding: utf-8 -*-
7+##############################################################################
8+#
9+# Author: Guewen Baconnier
10+# Copyright 2012 Camptocamp SA
11+#
12+# This program is free software: you can redistribute it and/or modify
13+# it under the terms of the GNU Affero General Public License as
14+# published by the Free Software Foundation, either version 3 of the
15+# License, or (at your option) any later version.
16+#
17+# This program is distributed in the hope that it will be useful,
18+# but WITHOUT ANY WARRANTY; without even the implied warranty of
19+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20+# GNU Affero General Public License for more details.
21+#
22+# You should have received a copy of the GNU Affero General Public License
23+# along with this program. If not, see <http://www.gnu.org/licenses/>.
24+#
25+##############################################################################
26+
27+import easy_reconcile
28+import base_advanced_reconciliation
29+import advanced_reconciliation
30
31=== added file 'account_advanced_reconcile/__openerp__.py'
32--- account_advanced_reconcile/__openerp__.py 1970-01-01 00:00:00 +0000
33+++ account_advanced_reconcile/__openerp__.py 2013-07-17 14:55:41 +0000
34@@ -0,0 +1,83 @@
35+# -*- coding: utf-8 -*-
36+##############################################################################
37+#
38+# Author: Guewen Baconnier
39+# Copyright 2012 Camptocamp SA
40+#
41+# This program is free software: you can redistribute it and/or modify
42+# it under the terms of the GNU Affero General Public License as
43+# published by the Free Software Foundation, either version 3 of the
44+# License, or (at your option) any later version.
45+#
46+# This program is distributed in the hope that it will be useful,
47+# but WITHOUT ANY WARRANTY; without even the implied warranty of
48+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
49+# GNU Affero General Public License for more details.
50+#
51+# You should have received a copy of the GNU Affero General Public License
52+# along with this program. If not, see <http://www.gnu.org/licenses/>.
53+#
54+##############################################################################
55+
56+{'name': "Advanced Reconcile",
57+ 'version': '1.0',
58+ 'author': 'Camptocamp',
59+ 'maintainer': 'Camptocamp',
60+ 'category': 'Finance',
61+ 'complexity': 'normal',
62+ 'depends': ['account_easy_reconcile',
63+ ],
64+ 'description': """
65+Advanced reconciliation methods for the module account_easy_reconcile.
66+
67+In addition to the features implemented in account_easy_reconcile, which are:
68+ - reconciliation facilities for big volume of transactions
69+ - setup different profiles of reconciliation by account
70+ - each profile can use many methods of reconciliation
71+ - this module is also a base to create others reconciliation methods
72+ which can plug in the profiles
73+ - a profile a reconciliation can be run manually or by a cron
74+ - monitoring of reconcilation runs with an history
75+
76+It implements a basis to created advanced reconciliation methods in a few lines
77+of code.
78+
79+Typically, such a method can be:
80+ - Reconcile Journal items if the partner and the ref are equal
81+ - Reconcile Journal items if the partner is equal and the ref
82+ is the same than ref or name
83+ - Reconcile Journal items if the partner is equal and the ref
84+ match with a pattern
85+
86+And they allows:
87+ - Reconciliations with multiple credit / multiple debit lines
88+ - Partial reconciliations
89+ - Write-off amount as well
90+
91+A method is already implemented in this module, it matches on items:
92+ - Partner
93+ - Ref on credit move lines should be case insensitive equals to the ref or
94+ the name of the debit move line
95+
96+The base class to find the reconciliations is built to be as efficient as
97+possible.
98+
99+So basically, if you have an invoice with 3 payments (one per month), the first
100+month, it will partial reconcile the debit move line with the first payment, the second
101+month, it will partial reconcile the debit move line with 2 first payments,
102+the third month, it will make the full reconciliation.
103+
104+This module is perfectly adapted for E-Commerce business where a big volume of
105+move lines and so, reconciliations, are involved and payments often come from
106+many offices.
107+
108+ """,
109+ 'website': 'http://www.camptocamp.com',
110+ 'data': ['easy_reconcile_view.xml'],
111+ 'test': [],
112+ 'images': [],
113+ 'installable': True,
114+ 'auto_install': False,
115+ 'license': 'AGPL-3',
116+ 'application': True,
117+}
118
119=== added file 'account_advanced_reconcile/advanced_reconciliation.py'
120--- account_advanced_reconcile/advanced_reconciliation.py 1970-01-01 00:00:00 +0000
121+++ account_advanced_reconcile/advanced_reconciliation.py 2013-07-17 14:55:41 +0000
122@@ -0,0 +1,118 @@
123+# -*- coding: utf-8 -*-
124+##############################################################################
125+#
126+# Author: Guewen Baconnier
127+# Copyright 2012 Camptocamp SA
128+#
129+# This program is free software: you can redistribute it and/or modify
130+# it under the terms of the GNU Affero General Public License as
131+# published by the Free Software Foundation, either version 3 of the
132+# License, or (at your option) any later version.
133+#
134+# This program is distributed in the hope that it will be useful,
135+# but WITHOUT ANY WARRANTY; without even the implied warranty of
136+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
137+# GNU Affero General Public License for more details.
138+#
139+# You should have received a copy of the GNU Affero General Public License
140+# along with this program. If not, see <http://www.gnu.org/licenses/>.
141+#
142+##############################################################################
143+
144+from openerp.osv import orm
145+
146+
147+class easy_reconcile_advanced_ref(orm.TransientModel):
148+
149+ _name = 'easy.reconcile.advanced.ref'
150+ _inherit = 'easy.reconcile.advanced'
151+
152+ def _skip_line(self, cr, uid, rec, move_line, context=None):
153+ """
154+ When True is returned on some conditions, the credit move line
155+ will be skipped for reconciliation. Can be inherited to
156+ skip on some conditions. ie: ref or partner_id is empty.
157+ """
158+ return not (move_line.get('ref') and move_line.get('partner_id'))
159+
160+ def _matchers(self, cr, uid, rec, move_line, context=None):
161+ """
162+ Return the values used as matchers to find the opposite lines
163+
164+ All the matcher keys in the dict must have their equivalent in
165+ the `_opposite_matchers`.
166+
167+ The values of each matcher key will be searched in the
168+ one returned by the `_opposite_matchers`
169+
170+ Must be inherited to implement the matchers for one method
171+
172+ For instance, it can return:
173+ return ('ref', move_line['rec'])
174+
175+ or
176+ return (('partner_id', move_line['partner_id']),
177+ ('ref', "prefix_%s" % move_line['rec']))
178+
179+ All the matchers have to be found in the opposite lines
180+ to consider them as "opposite"
181+
182+ The matchers will be evaluated in the same order as declared
183+ vs the the opposite matchers, so you can gain performance by
184+ declaring first the partners with the less computation.
185+
186+ All matchers should match with their opposite to be considered
187+ as "matching".
188+ So with the previous example, partner_id and ref have to be
189+ equals on the opposite line matchers.
190+
191+ :return: tuple of tuples (key, value) where the keys are
192+ the matchers keys
193+ (must be the same than `_opposite_matchers` returns,
194+ and their values to match in the opposite lines.
195+ A matching key can have multiples values.
196+ """
197+ return (('partner_id', move_line['partner_id']),
198+ ('ref', move_line['ref'].lower().strip()))
199+
200+ def _opposite_matchers(self, cr, uid, rec, move_line, context=None):
201+ """
202+ Return the values of the opposite line used as matchers
203+ so the line is matched
204+
205+ Must be inherited to implement the matchers for one method
206+ It can be inherited to apply some formatting of fields
207+ (strip(), lower() and so on)
208+
209+ This method is the counterpart of the `_matchers()` method.
210+
211+ Each matcher has to yield its value respecting the order
212+ of the `_matchers()`.
213+
214+ When a matcher does not correspond, the next matchers won't
215+ be evaluated so the ones which need the less computation
216+ have to be executed first.
217+
218+ If the `_matchers()` returns:
219+ (('partner_id', move_line['partner_id']),
220+ ('ref', move_line['ref']))
221+
222+ Here, you should yield :
223+ yield ('partner_id', move_line['partner_id'])
224+ yield ('ref', move_line['ref'])
225+
226+ Note that a matcher can contain multiple values, as instance,
227+ if for a move line, you want to search from its `ref` in the
228+ `ref` or `name` fields of the opposite move lines, you have to
229+ yield ('partner_id', move_line['partner_id'])
230+ yield ('ref', (move_line['ref'], move_line['name'])
231+
232+ An OR is used between the values for the same key.
233+ An AND is used between the differents keys.
234+
235+ :param dict move_line: values of the move_line
236+ :yield: matchers as tuple ('matcher key', value(s))
237+ """
238+ yield ('partner_id', move_line['partner_id'])
239+ yield ('ref', ((move_line['ref'] or '').lower().strip(),
240+ move_line['name'].lower().strip()))
241
242=== added file 'account_advanced_reconcile/base_advanced_reconciliation.py'
243--- account_advanced_reconcile/base_advanced_reconciliation.py 1970-01-01 00:00:00 +0000
244+++ account_advanced_reconcile/base_advanced_reconciliation.py 2013-07-17 14:55:41 +0000
245@@ -0,0 +1,272 @@
246+# -*- coding: utf-8 -*-
247+##############################################################################
248+#
249+# Author: Guewen Baconnier
250+# Copyright 2012 Camptocamp SA
251+#
252+# This program is free software: you can redistribute it and/or modify
253+# it under the terms of the GNU Affero General Public License as
254+# published by the Free Software Foundation, either version 3 of the
255+# License, or (at your option) any later version.
256+#
257+# This program is distributed in the hope that it will be useful,
258+# but WITHOUT ANY WARRANTY; without even the implied warranty of
259+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
260+# GNU Affero General Public License for more details.
261+#
262+# You should have received a copy of the GNU Affero General Public License
263+# along with this program. If not, see <http://www.gnu.org/licenses/>.
264+#
265+##############################################################################
266+
267+from itertools import product
268+from openerp.osv import orm
269+
270+
271+class easy_reconcile_advanced(orm.AbstractModel):
272+
273+ _name = 'easy.reconcile.advanced'
274+ _inherit = 'easy.reconcile.base'
275+
276+ def _query_debit(self, cr, uid, rec, context=None):
277+ """Select all move (debit>0) as candidate. """
278+ select = self._select(rec)
279+ sql_from = self._from(rec)
280+ where, params = self._where(rec)
281+ where += " AND account_move_line.debit > 0 "
282+
283+ where2, params2 = self._get_filter(cr, uid, rec, context=context)
284+
285+ query = ' '.join((select, sql_from, where, where2))
286+
287+ cr.execute(query, params + params2)
288+ return cr.dictfetchall()
289+
290+ def _query_credit(self, cr, uid, rec, context=None):
291+ """Select all move (credit>0) as candidate. """
292+ select = self._select(rec)
293+ sql_from = self._from(rec)
294+ where, params = self._where(rec)
295+ where += " AND account_move_line.credit > 0 "
296+
297+ where2, params2 = self._get_filter(cr, uid, rec, context=context)
298+
299+ query = ' '.join((select, sql_from, where, where2))
300+
301+ cr.execute(query, params + params2)
302+ return cr.dictfetchall()
303+
304+ def _matchers(self, cr, uid, rec, move_line, context=None):
305+ """
306+ Return the values used as matchers to find the opposite lines
307+
308+ All the matcher keys in the dict must have their equivalent in
309+ the `_opposite_matchers`.
310+
311+ The values of each matcher key will be searched in the
312+ one returned by the `_opposite_matchers`
313+
314+ Must be inherited to implement the matchers for one method
315+
316+ As instance, it can return:
317+ return ('ref', move_line['rec'])
318+
319+ or
320+ return (('partner_id', move_line['partner_id']),
321+ ('ref', "prefix_%s" % move_line['rec']))
322+
323+ All the matchers have to be found in the opposite lines
324+ to consider them as "opposite"
325+
326+ The matchers will be evaluated in the same order as declared
327+ vs the the opposite matchers, so you can gain performance by
328+ declaring first the partners with the less computation.
329+
330+ All matchers should match with their opposite to be considered
331+ as "matching".
332+ So with the previous example, partner_id and ref have to be
333+ equals on the opposite line matchers.
334+
335+ :return: tuple of tuples (key, value) where the keys are
336+ the matchers keys
337+ (must be the same than `_opposite_matchers` returns,
338+ and their values to match in the opposite lines.
339+ A matching key can have multiples values.
340+ """
341+ raise NotImplementedError
342+
343+ def _opposite_matchers(self, cr, uid, rec, move_line, context=None):
344+ """
345+ Return the values of the opposite line used as matchers
346+ so the line is matched
347+
348+ Must be inherited to implement the matchers for one method
349+ It can be inherited to apply some formatting of fields
350+ (strip(), lower() and so on)
351+
352+ This method is the counterpart of the `_matchers()` method.
353+
354+ Each matcher has to yield its value respecting the order
355+ of the `_matchers()`.
356+
357+ When a matcher does not correspond, the next matchers won't
358+ be evaluated so the ones which need the less computation
359+ have to be executed first.
360+
361+ If the `_matchers()` returns:
362+ (('partner_id', move_line['partner_id']),
363+ ('ref', move_line['ref']))
364+
365+ Here, you should yield :
366+ yield ('partner_id', move_line['partner_id'])
367+ yield ('ref', move_line['ref'])
368+
369+ Note that a matcher can contain multiple values, as instance,
370+ if for a move line, you want to search from its `ref` in the
371+ `ref` or `name` fields of the opposite move lines, you have to
372+ yield ('partner_id', move_line['partner_id'])
373+ yield ('ref', (move_line['ref'], move_line['name'])
374+
375+ An OR is used between the values for the same key.
376+ An AND is used between the differents keys.
377+
378+ :param dict move_line: values of the move_line
379+ :yield: matchers as tuple ('matcher key', value(s))
380+ """
381+ raise NotImplementedError
382+
383+ @staticmethod
384+ def _compare_values(key, value, opposite_value):
385+ """Can be inherited to modify the equality condition
386+ specifically according to the matcher key (maybe using
387+ a like operator instead of equality on 'ref' as instance)
388+ """
389+ # consider that empty vals are not valid matchers
390+ # it can still be inherited for some special cases
391+ # where it would be allowed
392+ if not (value and opposite_value):
393+ return False
394+
395+ if value == opposite_value:
396+ return True
397+ return False
398+
399+ @staticmethod
400+ def _compare_matcher_values(key, values, opposite_values):
401+ """ Compare every values from a matcher vs an opposite matcher
402+ and return True if it matches
403+ """
404+ for value, ovalue in product(values, opposite_values):
405+ # we do not need to compare all values, if one matches
406+ # we are done
407+ if easy_reconcile_advanced._compare_values(key, value, ovalue):
408+ return True
409+ return False
410+
411+ @staticmethod
412+ def _compare_matchers(matcher, opposite_matcher):
413+ """
414+ Prepare and check the matchers to compare
415+ """
416+ mkey, mvalue = matcher
417+ omkey, omvalue = opposite_matcher
418+ assert mkey == omkey, ("A matcher %s is compared with a matcher %s, "
419+ " the _matchers and _opposite_matchers are probably wrong" %
420+ (mkey, omkey))
421+ if not isinstance(mvalue, (list, tuple)):
422+ mvalue = mvalue,
423+ if not isinstance(omvalue, (list, tuple)):
424+ omvalue = omvalue,
425+ return easy_reconcile_advanced._compare_matcher_values(mkey, mvalue, omvalue)
426+
427+ def _compare_opposite(self, cr, uid, rec, move_line, opposite_move_line,
428+ matchers, context=None):
429+ """ Iterate over the matchers of the move lines vs opposite move lines
430+ and if they all match, return True.
431+
432+ If all the matchers match for a move line and an opposite move line,
433+ they are candidate for a reconciliation.
434+ """
435+ opp_matchers = self._opposite_matchers(cr, uid, rec, opposite_move_line,
436+ context=context)
437+ for matcher in matchers:
438+ try:
439+ opp_matcher = opp_matchers.next()
440+ except StopIteration:
441+ # if you fall here, you probably missed to put a `yield`
442+ # in `_opposite_matchers()`
443+ raise ValueError("Missing _opposite_matcher: %s" % matcher[0])
444+
445+ if not self._compare_matchers(matcher, opp_matcher):
446+ # if any of the matcher fails, the opposite line
447+ # is not a valid counterpart
448+ # directly returns so the next yield of _opposite_matchers
449+ # are not evaluated
450+ return False
451+
452+ return True
453+
454+ def _search_opposites(self, cr, uid, rec, move_line, opposite_move_lines, context=None):
455+ """
456+ Search the opposite move lines for a move line
457+
458+ :param dict move_line: the move line for which we search opposites
459+ :param list opposite_move_lines: list of dict of move lines values, the move
460+ lines we want to search for
461+ :return: list of matching lines
462+ """
463+ matchers = self._matchers(cr, uid, rec, move_line, context=context)
464+ return [op for op in opposite_move_lines if
465+ self._compare_opposite(
466+ cr, uid, rec, move_line, op, matchers, context=context)]
467+
468+ def _action_rec(self, cr, uid, rec, context=None):
469+ credit_lines = self._query_credit(cr, uid, rec, context=context)
470+ debit_lines = self._query_debit(cr, uid, rec, context=context)
471+ return self._rec_auto_lines_advanced(
472+ cr, uid, rec, credit_lines, debit_lines, context=context)
473+
474+ def _skip_line(self, cr, uid, rec, move_line, context=None):
475+ """
476+ When True is returned on some conditions, the credit move line
477+ will be skipped for reconciliation. Can be inherited to
478+ skip on some conditions. ie: ref or partner_id is empty.
479+ """
480+ return False
481+
482+ def _rec_auto_lines_advanced(self, cr, uid, rec, credit_lines, debit_lines, context=None):
483+ """ Advanced reconciliation main loop """
484+ reconciled_ids = []
485+ partial_reconciled_ids = []
486+ reconcile_groups = []
487+
488+ for credit_line in credit_lines:
489+ if self._skip_line(cr, uid, rec, credit_line, context=context):
490+ continue
491+
492+ opposite_lines = self._search_opposites(
493+ cr, uid, rec, credit_line, debit_lines, context=context)
494+
495+ if not opposite_lines:
496+ continue
497+
498+ opposite_ids = [l['id'] for l in opposite_lines]
499+ line_ids = opposite_ids + [credit_line['id']]
500+ for group in reconcile_groups:
501+ if any([lid in group for lid in opposite_ids]):
502+ group.update(line_ids)
503+ break
504+ else:
505+ reconcile_groups.append(set(line_ids))
506+
507+ lines_by_id = dict([(l['id'], l) for l in credit_lines + debit_lines])
508+ for reconcile_group_ids in reconcile_groups:
509+ group_lines = [lines_by_id[lid] for lid in reconcile_group_ids]
510+ reconciled, full = self._reconcile_lines(
511+ cr, uid, rec, group_lines, allow_partial=True, context=context)
512+ if reconciled and full:
513+ reconciled_ids += reconcile_group_ids
514+ elif reconciled:
515+ partial_reconciled_ids += reconcile_group_ids
516+
517+ return reconciled_ids, partial_reconciled_ids
518
519=== added file 'account_advanced_reconcile/easy_reconcile.py'
520--- account_advanced_reconcile/easy_reconcile.py 1970-01-01 00:00:00 +0000
521+++ account_advanced_reconcile/easy_reconcile.py 2013-07-17 14:55:41 +0000
522@@ -0,0 +1,36 @@
523+# -*- coding: utf-8 -*-
524+##############################################################################
525+#
526+# Author: Guewen Baconnier
527+# Copyright 2012 Camptocamp SA
528+#
529+# This program is free software: you can redistribute it and/or modify
530+# it under the terms of the GNU Affero General Public License as
531+# published by the Free Software Foundation, either version 3 of the
532+# License, or (at your option) any later version.
533+#
534+# This program is distributed in the hope that it will be useful,
535+# but WITHOUT ANY WARRANTY; without even the implied warranty of
536+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
537+# GNU Affero General Public License for more details.
538+#
539+# You should have received a copy of the GNU Affero General Public License
540+# along with this program. If not, see <http://www.gnu.org/licenses/>.
541+#
542+##############################################################################
543+
544+from openerp.osv import orm
545+
546+
547+class account_easy_reconcile_method(orm.Model):
548+
549+ _inherit = 'account.easy.reconcile.method'
550+
551+ def _get_all_rec_method(self, cr, uid, context=None):
552+ methods = super(account_easy_reconcile_method, self).\
553+ _get_all_rec_method(cr, uid, context=context)
554+ methods += [
555+ ('easy.reconcile.advanced.ref',
556+ 'Advanced. Partner and Ref.'),
557+ ]
558+ return methods
559
560=== added file 'account_advanced_reconcile/easy_reconcile_view.xml'
561--- account_advanced_reconcile/easy_reconcile_view.xml 1970-01-01 00:00:00 +0000
562+++ account_advanced_reconcile/easy_reconcile_view.xml 2013-07-17 14:55:41 +0000
563@@ -0,0 +1,19 @@
564+<?xml version="1.0" encoding="utf-8"?>
565+<openerp>
566+ <data noupdate="0">
567+ <record id="view_easy_reconcile_form" model="ir.ui.view">
568+ <field name="name">account.easy.reconcile.form</field>
569+ <field name="model">account.easy.reconcile</field>
570+ <field name="inherit_id" ref="account_easy_reconcile.account_easy_reconcile_form"/>
571+ <field name="arch" type="xml">
572+ <page name="information" position="inside">
573+ <group colspan="2" col="2">
574+ <separator colspan="4" string="Advanced. Partner and Ref"/>
575+ <label string="Match multiple debit vs multiple credit entries. Allow partial reconciliation.
576+The lines should have the partner, the credit entry ref. is matched vs the debit entry ref. or name." colspan="4"/>
577+ </group>
578+ </page>
579+ </field>
580+ </record>
581+ </data>
582+</openerp>
583
584=== added directory 'account_advanced_reconcile/i18n'
585=== added file 'account_advanced_reconcile/i18n/fr.po'
586--- account_advanced_reconcile/i18n/fr.po 1970-01-01 00:00:00 +0000
587+++ account_advanced_reconcile/i18n/fr.po 2013-07-17 14:55:41 +0000
588@@ -0,0 +1,91 @@
589+# Translation of OpenERP Server.
590+# This file contains the translation of the following modules:
591+# * account_advanced_reconcile
592+#
593+msgid ""
594+msgstr ""
595+"Project-Id-Version: OpenERP Server 6.1\n"
596+"Report-Msgid-Bugs-To: \n"
597+"POT-Creation-Date: 2013-01-04 08:25+0000\n"
598+"PO-Revision-Date: 2013-01-04 09:27+0100\n"
599+"Last-Translator: Guewen Baconnier <guewen.baconnier@camptocamp.com>\n"
600+"Language-Team: \n"
601+"Language: \n"
602+"MIME-Version: 1.0\n"
603+"Content-Type: text/plain; charset=UTF-8\n"
604+"Content-Transfer-Encoding: 8bit\n"
605+"Plural-Forms: \n"
606+
607+#. module: account_advanced_reconcile
608+#: field:easy.reconcile.advanced,partner_ids:0
609+#: field:easy.reconcile.advanced.ref,partner_ids:0
610+msgid "Restrict on partners"
611+msgstr "Restriction sur les partenaires"
612+
613+#. module: account_advanced_reconcile
614+#: field:easy.reconcile.advanced,account_id:0
615+#: field:easy.reconcile.advanced.ref,account_id:0
616+msgid "Account"
617+msgstr "Compte"
618+
619+#. module: account_advanced_reconcile
620+#: model:ir.model,name:account_advanced_reconcile.model_account_easy_reconcile_method
621+msgid "reconcile method for account_easy_reconcile"
622+msgstr "Méthode de lettrage pour le module account_easy_reconcile"
623+
624+#. module: account_advanced_reconcile
625+#: field:easy.reconcile.advanced,journal_id:0
626+#: field:easy.reconcile.advanced.ref,journal_id:0
627+msgid "Journal"
628+msgstr "Journal"
629+
630+#. module: account_advanced_reconcile
631+#: field:easy.reconcile.advanced,account_profit_id:0
632+#: field:easy.reconcile.advanced.ref,account_profit_id:0
633+msgid "Account Profit"
634+msgstr "Compte de produit"
635+
636+#. module: account_advanced_reconcile
637+#: view:account.easy.reconcile:0
638+msgid "Match multiple debit vs multiple credit entries. Allow partial reconciliation. The lines should have the partner, the credit entry ref. is matched vs the debit entry ref. or name."
639+msgstr "Le Lettrage peut s'effectuer sur plusieurs écritures de débit et crédit. Le Lettrage partiel est autorisé. Les écritures doivent avoir le même partenaire et la référence sur les écritures de crédit doit se retrouver dans la référence ou la description sur les écritures de débit."
640+
641+#. module: account_advanced_reconcile
642+#: field:easy.reconcile.advanced,filter:0
643+#: field:easy.reconcile.advanced.ref,filter:0
644+msgid "Filter"
645+msgstr "Filtre"
646+
647+#. module: account_advanced_reconcile
648+#: view:account.easy.reconcile:0
649+msgid "Advanced. Partner and Ref"
650+msgstr "Avancé. Partenaire et Réf."
651+
652+#. module: account_advanced_reconcile
653+#: field:easy.reconcile.advanced,date_base_on:0
654+#: field:easy.reconcile.advanced.ref,date_base_on:0
655+msgid "Date of reconciliation"
656+msgstr "Date de lettrage"
657+
658+#. module: account_advanced_reconcile
659+#: model:ir.model,name:account_advanced_reconcile.model_easy_reconcile_advanced
660+msgid "easy.reconcile.advanced"
661+msgstr "easy.reconcile.advanced"
662+
663+#. module: account_advanced_reconcile
664+#: field:easy.reconcile.advanced,account_lost_id:0
665+#: field:easy.reconcile.advanced.ref,account_lost_id:0
666+msgid "Account Lost"
667+msgstr "Compte de charge"
668+
669+#. module: account_advanced_reconcile
670+#: model:ir.model,name:account_advanced_reconcile.model_easy_reconcile_advanced_ref
671+msgid "easy.reconcile.advanced.ref"
672+msgstr "easy.reconcile.advanced.ref"
673+
674+#. module: account_advanced_reconcile
675+#: field:easy.reconcile.advanced,write_off:0
676+#: field:easy.reconcile.advanced.ref,write_off:0
677+msgid "Write off allowed"
678+msgstr "Écart autorisé"
679+
680
681=== added directory 'account_easy_reconcile'
682=== added file 'account_easy_reconcile/__init__.py'
683--- account_easy_reconcile/__init__.py 1970-01-01 00:00:00 +0000
684+++ account_easy_reconcile/__init__.py 2013-07-17 14:55:41 +0000
685@@ -0,0 +1,25 @@
686+# -*- coding: utf-8 -*-
687+##############################################################################
688+#
689+# Copyright 2012 Camptocamp SA (Guewen Baconnier)
690+# Copyright (C) 2010 Sébastien Beau
691+#
692+# This program is free software: you can redistribute it and/or modify
693+# it under the terms of the GNU Affero General Public License as
694+# published by the Free Software Foundation, either version 3 of the
695+# License, or (at your option) any later version.
696+#
697+# This program is distributed in the hope that it will be useful,
698+# but WITHOUT ANY WARRANTY; without even the implied warranty of
699+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
700+# GNU Affero General Public License for more details.
701+#
702+# You should have received a copy of the GNU Affero General Public License
703+# along with this program. If not, see <http://www.gnu.org/licenses/>.
704+#
705+##############################################################################
706+
707+import easy_reconcile
708+import base_reconciliation
709+import simple_reconciliation
710+import easy_reconcile_history
711
712=== added file 'account_easy_reconcile/__openerp__.py'
713--- account_easy_reconcile/__openerp__.py 1970-01-01 00:00:00 +0000
714+++ account_easy_reconcile/__openerp__.py 2013-07-17 14:55:41 +0000
715@@ -0,0 +1,66 @@
716+# -*- coding: utf-8 -*-
717+##############################################################################
718+#
719+# Copyright 2012 Camptocamp SA (Guewen Baconnier)
720+# Copyright (C) 2010 Sébastien Beau
721+#
722+# This program is free software: you can redistribute it and/or modify
723+# it under the terms of the GNU Affero General Public License as
724+# published by the Free Software Foundation, either version 3 of the
725+# License, or (at your option) any later version.
726+#
727+# This program is distributed in the hope that it will be useful,
728+# but WITHOUT ANY WARRANTY; without even the implied warranty of
729+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
730+# GNU Affero General Public License for more details.
731+#
732+# You should have received a copy of the GNU Affero General Public License
733+# along with this program. If not, see <http://www.gnu.org/licenses/>.
734+#
735+##############################################################################
736+
737+{
738+ "name": "Easy Reconcile",
739+ "version": "1.3.0",
740+ "depends": ["account"],
741+ "author": "Akretion,Camptocamp",
742+ "description": """
743+Easy Reconcile
744+==============
745+
746+This is a shared work between Akretion and Camptocamp
747+in order to provide:
748+ - reconciliation facilities for big volume of transactions
749+ - setup different profiles of reconciliation by account
750+ - each profile can use many methods of reconciliation
751+ - this module is also a base to create others
752+ reconciliation methods which can plug in the profiles
753+ - a profile a reconciliation can be run manually
754+ or by a cron
755+ - monitoring of reconciliation runs with an history
756+ which keep track of the reconciled Journal items
757+
758+2 simple reconciliation methods are integrated
759+in this module, the simple reconciliations works
760+on 2 lines (1 debit / 1 credit) and do not allow
761+partial reconcilation, they also match on 1 key,
762+partner or Journal item name.
763+
764+You may be interested to install also the
765+``account_advanced_reconciliation`` module.
766+This latter add more complex reconciliations,
767+allows multiple lines and partial.
768+
769+""",
770+ "website": "http://www.akretion.com/",
771+ "category": "Finance",
772+ "demo_xml": [],
773+ "data": ["easy_reconcile.xml",
774+ "easy_reconcile_history_view.xml",
775+ "security/ir_rule.xml",
776+ "security/ir.model.access.csv"],
777+ 'license': 'AGPL-3',
778+ "auto_install": False,
779+ "installable": True,
780+
781+}
782
783=== added file 'account_easy_reconcile/base_reconciliation.py'
784--- account_easy_reconcile/base_reconciliation.py 1970-01-01 00:00:00 +0000
785+++ account_easy_reconcile/base_reconciliation.py 2013-07-17 14:55:41 +0000
786@@ -0,0 +1,208 @@
787+# -*- coding: utf-8 -*-
788+##############################################################################
789+#
790+# Copyright 2012-2013 Camptocamp SA (Guewen Baconnier)
791+# Copyright (C) 2010 Sébastien Beau
792+#
793+# This program is free software: you can redistribute it and/or modify
794+# it under the terms of the GNU Affero General Public License as
795+# published by the Free Software Foundation, either version 3 of the
796+# License, or (at your option) any later version.
797+#
798+# This program is distributed in the hope that it will be useful,
799+# but WITHOUT ANY WARRANTY; without even the implied warranty of
800+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
801+# GNU Affero General Public License for more details.
802+#
803+# You should have received a copy of the GNU Affero General Public License
804+# along with this program. If not, see <http://www.gnu.org/licenses/>.
805+#
806+##############################################################################
807+
808+from openerp.osv import fields, orm
809+from operator import itemgetter, attrgetter
810+
811+
812+class easy_reconcile_base(orm.AbstractModel):
813+ """Abstract Model for reconciliation methods"""
814+
815+ _name = 'easy.reconcile.base'
816+
817+ _inherit = 'easy.reconcile.options'
818+
819+ _columns = {
820+ 'account_id': fields.many2one(
821+ 'account.account', 'Account', required=True),
822+ 'partner_ids': fields.many2many(
823+ 'res.partner', string="Restrict on partners"),
824+ # other columns are inherited from easy.reconcile.options
825+ }
826+
827+ def automatic_reconcile(self, cr, uid, ids, context=None):
828+ """ Reconciliation method called from the view.
829+
830+ :return: list of reconciled ids, list of partially reconciled items
831+ """
832+ if isinstance(ids, (int, long)):
833+ ids = [ids]
834+ assert len(ids) == 1, "Has to be called on one id"
835+ rec = self.browse(cr, uid, ids[0], context=context)
836+ return self._action_rec(cr, uid, rec, context=context)
837+
838+ def _action_rec(self, cr, uid, rec, context=None):
839+ """ Must be inherited to implement the reconciliation
840+
841+ :return: list of reconciled ids
842+ """
843+ raise NotImplementedError
844+
845+ def _base_columns(self, rec):
846+ """ Mandatory columns for move lines queries
847+ An extra column aliased as ``key`` should be defined
848+ in each query."""
849+ aml_cols = (
850+ 'id',
851+ 'debit',
852+ 'credit',
853+ 'date',
854+ 'period_id',
855+ 'ref',
856+ 'name',
857+ 'partner_id',
858+ 'account_id',
859+ 'move_id')
860+ return ["account_move_line.%s" % col for col in aml_cols]
861+
862+ def _select(self, rec, *args, **kwargs):
863+ return "SELECT %s" % ', '.join(self._base_columns(rec))
864+
865+ def _from(self, rec, *args, **kwargs):
866+ return "FROM account_move_line"
867+
868+ def _where(self, rec, *args, **kwargs):
869+ where = ("WHERE account_move_line.account_id = %s "
870+ "AND account_move_line.reconcile_id IS NULL ")
871+ # it would be great to use dict for params
872+ # but as we use _where_calc in _get_filter
873+ # which returns a list, we have to
874+ # accomodate with that
875+ params = [rec.account_id.id]
876+
877+ if rec.partner_ids:
878+ where += " AND account_move_line.partner_id IN %s"
879+ params.append(tuple([l.id for l in rec.partner_ids]))
880+ return where, params
881+
882+ def _get_filter(self, cr, uid, rec, context):
883+ ml_obj = self.pool.get('account.move.line')
884+ where = ''
885+ params = []
886+ if rec.filter:
887+ dummy, where, params = ml_obj._where_calc(
888+ cr, uid, eval(rec.filter), context=context).get_sql()
889+ if where:
890+ where = " AND %s" % where
891+ return where, params
892+
893+ def _below_writeoff_limit(self, cr, uid, rec, lines,
894+ writeoff_limit, context=None):
895+ precision = self.pool.get('decimal.precision').precision_get(
896+ cr, uid, 'Account')
897+ keys = ('debit', 'credit')
898+ sums = reduce(
899+ lambda line, memo:
900+ dict((key, value + memo[key])
901+ for key, value
902+ in line.iteritems()
903+ if key in keys), lines)
904+
905+ debit, credit = sums['debit'], sums['credit']
906+ writeoff_amount = round(debit - credit, precision)
907+ return bool(writeoff_limit >= abs(writeoff_amount)), debit, credit
908+
909+ def _get_rec_date(self, cr, uid, rec, lines,
910+ based_on='end_period_last_credit', context=None):
911+ period_obj = self.pool.get('account.period')
912+
913+ def last_period(mlines):
914+ period_ids = [ml['period_id'] for ml in mlines]
915+ periods = period_obj.browse(
916+ cr, uid, period_ids, context=context)
917+ return max(periods, key=attrgetter('date_stop'))
918+
919+ def last_date(mlines):
920+ return max(mlines, key=itemgetter('date'))
921+
922+ def credit(mlines):
923+ return [l for l in mlines if l['credit'] > 0]
924+
925+ def debit(mlines):
926+ return [l for l in mlines if l['debit'] > 0]
927+
928+ if based_on == 'end_period_last_credit':
929+ return last_period(credit(lines)).date_stop
930+ if based_on == 'end_period':
931+ return last_period(lines).date_stop
932+ elif based_on == 'newest':
933+ return last_date(lines)['date']
934+ elif based_on == 'newest_credit':
935+ return last_date(credit(lines))['date']
936+ elif based_on == 'newest_debit':
937+ return last_date(debit(lines))['date']
938+ # reconcilation date will be today
939+ # when date is None
940+ return None
941+
942+ def _reconcile_lines(self, cr, uid, rec, lines, allow_partial=False, context=None):
943+ """ Try to reconcile given lines
944+
945+ :param list lines: list of dict of move lines, they must at least
946+ contain values for : id, debit, credit
947+ :param boolean allow_partial: if True, partial reconciliation will be
948+ created, otherwise only Full
949+ reconciliation will be created
950+ :return: tuple of boolean values, first item is wether the items
951+ have been reconciled or not,
952+ the second is wether the reconciliation is full (True)
953+ or partial (False)
954+ """
955+ if context is None:
956+ context = {}
957+
958+ ml_obj = self.pool.get('account.move.line')
959+ writeoff = rec.write_off
960+
961+ line_ids = [l['id'] for l in lines]
962+ below_writeoff, sum_debit, sum_credit = self._below_writeoff_limit(
963+ cr, uid, rec, lines, writeoff, context=context)
964+ date = self._get_rec_date(
965+ cr, uid, rec, lines, rec.date_base_on, context=context)
966+
967+ rec_ctx = dict(context, date_p=date)
968+ if below_writeoff:
969+ if sum_credit < sum_debit:
970+ writeoff_account_id = rec.account_profit_id.id
971+ else:
972+ writeoff_account_id = rec.account_lost_id.id
973+
974+ period_id = self.pool.get('account.period').find(
975+ cr, uid, dt=date, context=context)[0]
976+
977+ ml_obj.reconcile(
978+ cr, uid,
979+ line_ids,
980+ type='auto',
981+ writeoff_acc_id=writeoff_account_id,
982+ writeoff_period_id=period_id,
983+ writeoff_journal_id=rec.journal_id.id,
984+ context=rec_ctx)
985+ return True, True
986+ elif allow_partial:
987+ ml_obj.reconcile_partial(
988+ cr, uid,
989+ line_ids,
990+ type='manual',
991+ context=rec_ctx)
992+ return True, False
993+
994+ return False, False
995
996=== added file 'account_easy_reconcile/easy_reconcile.py'
997--- account_easy_reconcile/easy_reconcile.py 1970-01-01 00:00:00 +0000
998+++ account_easy_reconcile/easy_reconcile.py 2013-07-17 14:55:41 +0000
999@@ -0,0 +1,285 @@
1000+# -*- coding: utf-8 -*-
1001+##############################################################################
1002+#
1003+# Copyright 2012-2013 Camptocamp SA (Guewen Baconnier)
1004+# Copyright (C) 2010 Sébastien Beau
1005+#
1006+# This program is free software: you can redistribute it and/or modify
1007+# it under the terms of the GNU Affero General Public License as
1008+# published by the Free Software Foundation, either version 3 of the
1009+# License, or (at your option) any later version.
1010+#
1011+# This program is distributed in the hope that it will be useful,
1012+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1013+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1014+# GNU Affero General Public License for more details.
1015+#
1016+# You should have received a copy of the GNU Affero General Public License
1017+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1018+#
1019+##############################################################################
1020+
1021+from openerp.osv import fields, osv, orm
1022+from openerp.tools.translate import _
1023+from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
1024+
1025+
1026+class easy_reconcile_options(orm.AbstractModel):
1027+ """Options of a reconciliation profile
1028+
1029+ Columns shared by the configuration of methods
1030+ and by the reconciliation wizards.
1031+ This allows decoupling of the methods and the
1032+ wizards and allows to launch the wizards alone
1033+ """
1034+
1035+ _name = 'easy.reconcile.options'
1036+
1037+ def _get_rec_base_date(self, cr, uid, context=None):
1038+ return [('end_period_last_credit', 'End of period of most recent credit'),
1039+ ('newest', 'Most recent move line'),
1040+ ('actual', 'Today'),
1041+ ('end_period', 'End of period of most recent move line'),
1042+ ('newest_credit', 'Date of most recent credit'),
1043+ ('newest_debit', 'Date of most recent debit')]
1044+
1045+ _columns = {
1046+ 'write_off': fields.float('Write off allowed'),
1047+ 'account_lost_id': fields.many2one(
1048+ 'account.account', 'Account Lost'),
1049+ 'account_profit_id': fields.many2one(
1050+ 'account.account', 'Account Profit'),
1051+ 'journal_id': fields.many2one(
1052+ 'account.journal', 'Journal'),
1053+ 'date_base_on': fields.selection(
1054+ _get_rec_base_date,
1055+ required=True,
1056+ string='Date of reconciliation'),
1057+ 'filter': fields.char('Filter', size=128),
1058+ }
1059+
1060+ _defaults = {
1061+ 'write_off': 0.,
1062+ 'date_base_on': 'end_period_last_credit',
1063+ }
1064+
1065+
1066+class account_easy_reconcile_method(orm.Model):
1067+
1068+ _name = 'account.easy.reconcile.method'
1069+ _description = 'reconcile method for account_easy_reconcile'
1070+
1071+ _inherit = 'easy.reconcile.options'
1072+
1073+ _order = 'sequence'
1074+
1075+ def _get_all_rec_method(self, cr, uid, context=None):
1076+ return [
1077+ ('easy.reconcile.simple.name', 'Simple. Amount and Name'),
1078+ ('easy.reconcile.simple.partner', 'Simple. Amount and Partner'),
1079+ ('easy.reconcile.simple.reference', 'Simple. Amount and Reference'),
1080+ ]
1081+
1082+ def _get_rec_method(self, cr, uid, context=None):
1083+ return self._get_all_rec_method(cr, uid, context=None)
1084+
1085+ _columns = {
1086+ 'name': fields.selection(
1087+ _get_rec_method, 'Type', required=True),
1088+ 'sequence': fields.integer(
1089+ 'Sequence',
1090+ required=True,
1091+ help="The sequence field is used to order "
1092+ "the reconcile method"),
1093+ 'task_id': fields.many2one(
1094+ 'account.easy.reconcile',
1095+ string='Task',
1096+ required=True,
1097+ ondelete='cascade'),
1098+ 'company_id': fields.related('task_id','company_id',
1099+ relation='res.company',
1100+ type='many2one',
1101+ string='Company',
1102+ store=True,
1103+ readonly=True),
1104+ }
1105+
1106+ _defaults = {
1107+ 'sequence': 1,
1108+ }
1109+
1110+ def init(self, cr):
1111+ """ Migration stuff
1112+
1113+ Name is not anymore methods names but the name
1114+ of the model which does the reconciliation
1115+ """
1116+ cr.execute("""
1117+ UPDATE account_easy_reconcile_method
1118+ SET name = 'easy.reconcile.simple.partner'
1119+ WHERE name = 'action_rec_auto_partner'
1120+ """)
1121+ cr.execute("""
1122+ UPDATE account_easy_reconcile_method
1123+ SET name = 'easy.reconcile.simple.name'
1124+ WHERE name = 'action_rec_auto_name'
1125+ """)
1126+
1127+
1128+class account_easy_reconcile(orm.Model):
1129+
1130+ _name = 'account.easy.reconcile'
1131+ _description = 'account easy reconcile'
1132+
1133+ def _get_total_unrec(self, cr, uid, ids, name, arg, context=None):
1134+ obj_move_line = self.pool.get('account.move.line')
1135+ res = {}
1136+ for task in self.browse(cr, uid, ids, context=context):
1137+ res[task.id] = len(obj_move_line.search(
1138+ cr, uid,
1139+ [('account_id', '=', task.account.id),
1140+ ('reconcile_id', '=', False),
1141+ ('reconcile_partial_id', '=', False)],
1142+ context=context))
1143+ return res
1144+
1145+ def _get_partial_rec(self, cr, uid, ids, name, arg, context=None):
1146+ obj_move_line = self.pool.get('account.move.line')
1147+ res = {}
1148+ for task in self.browse(cr, uid, ids, context=context):
1149+ res[task.id] = len(obj_move_line.search(
1150+ cr, uid,
1151+ [('account_id', '=', task.account.id),
1152+ ('reconcile_id', '=', False),
1153+ ('reconcile_partial_id', '!=', False)],
1154+ context=context))
1155+ return res
1156+
1157+ def _last_history(self, cr, uid, ids, name, args, context=None):
1158+ result = {}
1159+ for history in self.browse(cr, uid, ids, context=context):
1160+ result[history.id] = False
1161+ if history.history_ids:
1162+ # history is sorted by date desc
1163+ result[history.id] = history.history_ids[0].id
1164+ return result
1165+
1166+ _columns = {
1167+ 'name': fields.char('Name', required=True),
1168+ 'account': fields.many2one(
1169+ 'account.account', 'Account', required=True),
1170+ 'reconcile_method': fields.one2many(
1171+ 'account.easy.reconcile.method', 'task_id', 'Method'),
1172+ 'unreconciled_count': fields.function(
1173+ _get_total_unrec, type='integer', string='Unreconciled Items'),
1174+ 'reconciled_partial_count': fields.function(
1175+ _get_partial_rec,
1176+ type='integer',
1177+ string='Partially Reconciled Items'),
1178+ 'history_ids': fields.one2many(
1179+ 'easy.reconcile.history',
1180+ 'easy_reconcile_id',
1181+ string='History',
1182+ readonly=True),
1183+ 'last_history':
1184+ fields.function(
1185+ _last_history,
1186+ string='Last History',
1187+ type='many2one',
1188+ relation='easy.reconcile.history',
1189+ readonly=True),
1190+ 'company_id': fields.many2one('res.company', 'Company'),
1191+ }
1192+
1193+ def _prepare_run_transient(self, cr, uid, rec_method, context=None):
1194+ return {'account_id': rec_method.task_id.account.id,
1195+ 'write_off': rec_method.write_off,
1196+ 'account_lost_id': (rec_method.account_lost_id and
1197+ rec_method.account_lost_id.id),
1198+ 'account_profit_id': (rec_method.account_profit_id and
1199+ rec_method.account_profit_id.id),
1200+ 'journal_id': (rec_method.journal_id and
1201+ rec_method.journal_id.id),
1202+ 'date_base_on': rec_method.date_base_on,
1203+ 'filter': rec_method.filter}
1204+
1205+ def run_reconcile(self, cr, uid, ids, context=None):
1206+ def find_reconcile_ids(fieldname, move_line_ids):
1207+ if not move_line_ids:
1208+ return []
1209+ sql = ("SELECT DISTINCT " + fieldname +
1210+ " FROM account_move_line "
1211+ " WHERE id in %s "
1212+ " AND " + fieldname + " IS NOT NULL")
1213+ cr.execute(sql, (tuple(move_line_ids),))
1214+ res = cr.fetchall()
1215+ return [row[0] for row in res]
1216+
1217+ for rec in self.browse(cr, uid, ids, context=context):
1218+ all_ml_rec_ids = []
1219+ all_ml_partial_ids = []
1220+
1221+ for method in rec.reconcile_method:
1222+ rec_model = self.pool.get(method.name)
1223+ auto_rec_id = rec_model.create(
1224+ cr, uid,
1225+ self._prepare_run_transient(
1226+ cr, uid, method, context=context),
1227+ context=context)
1228+
1229+ ml_rec_ids, ml_partial_ids = rec_model.automatic_reconcile(
1230+ cr, uid, auto_rec_id, context=context)
1231+
1232+ all_ml_rec_ids += ml_rec_ids
1233+ all_ml_partial_ids += ml_partial_ids
1234+
1235+ reconcile_ids = find_reconcile_ids(
1236+ 'reconcile_id', all_ml_rec_ids)
1237+ partial_ids = find_reconcile_ids(
1238+ 'reconcile_partial_id', all_ml_partial_ids)
1239+
1240+ self.pool.get('easy.reconcile.history').create(
1241+ cr,
1242+ uid,
1243+ {'easy_reconcile_id': rec.id,
1244+ 'date': fields.datetime.now(),
1245+ 'reconcile_ids': [(4, rid) for rid in reconcile_ids],
1246+ 'reconcile_partial_ids': [(4, rid) for rid in partial_ids]},
1247+ context=context)
1248+ return True
1249+
1250+ def _no_history(self, cr, uid, rec, context=None):
1251+ """ Raise an `osv.except_osv` error, supposed to
1252+ be called when there is no history on the reconciliation
1253+ task.
1254+ """
1255+ raise osv.except_osv(
1256+ _('Error'),
1257+ _('There is no history of reconciled '
1258+ 'items on the task: %s.') % rec.name)
1259+
1260+ def last_history_reconcile(self, cr, uid, rec_id, context=None):
1261+ """ Get the last history record for this reconciliation profile
1262+ and return the action which opens move lines reconciled
1263+ """
1264+ if isinstance(rec_id, (tuple, list)):
1265+ assert len(rec_id) == 1, \
1266+ "Only 1 id expected"
1267+ rec_id = rec_id[0]
1268+ rec = self.browse(cr, uid, rec_id, context=context)
1269+ if not rec.last_history:
1270+ self._no_history(cr, uid, rec, context=context)
1271+ return rec.last_history.open_reconcile()
1272+
1273+ def last_history_partial(self, cr, uid, rec_id, context=None):
1274+ """ Get the last history record for this reconciliation profile
1275+ and return the action which opens move lines reconciled
1276+ """
1277+ if isinstance(rec_id, (tuple, list)):
1278+ assert len(rec_id) == 1, \
1279+ "Only 1 id expected"
1280+ rec_id = rec_id[0]
1281+ rec = self.browse(cr, uid, rec_id, context=context)
1282+ if not rec.last_history:
1283+ self._no_history(cr, uid, rec, context=context)
1284+ return rec.last_history.open_partial()
1285
1286=== added file 'account_easy_reconcile/easy_reconcile.xml'
1287--- account_easy_reconcile/easy_reconcile.xml 1970-01-01 00:00:00 +0000
1288+++ account_easy_reconcile/easy_reconcile.xml 2013-07-17 14:55:41 +0000
1289@@ -0,0 +1,156 @@
1290+<?xml version="1.0" encoding="UTF-8"?>
1291+<openerp>
1292+<data>
1293+
1294+ <!-- account.easy.reconcile view -->
1295+ <record id="account_easy_reconcile_form" model="ir.ui.view">
1296+ <field name="name">account.easy.reconcile.form</field>
1297+ <field name="priority">20</field>
1298+ <field name="model">account.easy.reconcile</field>
1299+ <field name="arch" type="xml">
1300+ <form string="Automatic Easy Reconcile" version="7.0">
1301+ <header>
1302+ <button name="run_reconcile" class="oe_highlight"
1303+ string="Start Auto Reconciliation" type="object"/>
1304+ <button icon="STOCK_JUMP_TO" name="last_history_reconcile"
1305+ class="oe_highlight"
1306+ string="Display items reconciled on the last run"
1307+ type="object"/>
1308+ <button icon="STOCK_JUMP_TO" name="last_history_partial"
1309+ class="oe_highlight"
1310+ string="Display items partially reconciled on the last run"
1311+ type="object"/>
1312+ </header>
1313+ <sheet>
1314+ <separator colspan="4" string="Profile Information" />
1315+ <group>
1316+ <group>
1317+ <field name="name" select="1"/>
1318+ <field name="account"/>
1319+ <field name="company_id" groups="base.group_multi_company"/>
1320+ </group>
1321+ <group>
1322+ <field name="unreconciled_count"/>
1323+ <field name="reconciled_partial_count"/>
1324+ </group>
1325+ </group>
1326+ <notebook colspan="4">
1327+ <page name="methods" string="Configuration">
1328+ <field name="reconcile_method" colspan = "4" nolabel="1"/>
1329+ </page>
1330+ <page name="history" string="History">
1331+ <field name="history_ids" nolabel="1">
1332+ <tree string="Automatic Easy Reconcile History">
1333+ <field name="date"/>
1334+ <button icon="STOCK_JUMP_TO" name="open_reconcile"
1335+ string="Go to reconciled items" type="object"/>
1336+ <button icon="STOCK_JUMP_TO" name="open_partial"
1337+ string="Go to partially reconciled items" type="object"/>
1338+ </tree>
1339+ </field>
1340+ </page>
1341+ <page name="information" string="Information">
1342+ <separator colspan="4" string="Simple. Amount and Name"/>
1343+ <label string="Match one debit line vs one credit line. Do not allow partial reconciliation.
1344+The lines should have the same amount (with the write-off) and the same name to be reconciled." colspan="4"/>
1345+
1346+ <separator colspan="4" string="Simple. Amount and Partner"/>
1347+ <label string="Match one debit line vs one credit line. Do not allow partial reconciliation.
1348+The lines should have the same amount (with the write-off) and the same partner to be reconciled." colspan="4"/>
1349+
1350+ <separator colspan="4" string="Simple. Amount and Reference"/>
1351+ <label string="Match one debit line vs one credit line. Do not allow partial reconciliation.
1352+The lines should have the same amount (with the write-off) and the same reference to be reconciled." colspan="4"/>
1353+
1354+ </page>
1355+ </notebook>
1356+ </sheet>
1357+ </form>
1358+ </field>
1359+ </record>
1360+
1361+ <record id="account_easy_reconcile_tree" model="ir.ui.view">
1362+ <field name="name">account.easy.reconcile.tree</field>
1363+ <field name="priority">20</field>
1364+ <field name="model">account.easy.reconcile</field>
1365+ <field name="arch" type="xml">
1366+ <tree string="Automatic Easy Reconcile">
1367+ <field name="name"/>
1368+ <field name="account"/>
1369+ <field name="company_id" groups="base.group_multi_company"/>
1370+ <field name="unreconciled_count"/>
1371+ <field name="reconciled_partial_count"/>
1372+ <button icon="gtk-ok" name="run_reconcile" colspan="4"
1373+ string="Start Auto Reconcilation" type="object"/>
1374+ <button icon="STOCK_JUMP_TO" name="last_history_reconcile" colspan="2"
1375+ string="Display items reconciled on the last run" type="object"/>
1376+ <button icon="STOCK_JUMP_TO" name="last_history_partial" colspan="2"
1377+ string="Display items partially reconciled on the last run"
1378+ type="object"/>
1379+ </tree>
1380+ </field>
1381+ </record>
1382+
1383+ <record id="action_account_easy_reconcile" model="ir.actions.act_window">
1384+ <field name="name">Easy Automatic Reconcile</field>
1385+ <field name="type">ir.actions.act_window</field>
1386+ <field name="res_model">account.easy.reconcile</field>
1387+ <field name="view_type">form</field>
1388+ <field name="view_mode">tree,form</field>
1389+ <field name="help" type="html">
1390+ <p class="oe_view_nocontent_create">
1391+ Click to add a reconciliation profile.
1392+ </p><p>
1393+ A reconciliation profile specifies, for one account, how
1394+ the entries should be reconciled.
1395+ You can select one or many reconciliation methods which will
1396+ be run sequentially to match the entries between them.
1397+ </p>
1398+ </field>
1399+ </record>
1400+
1401+
1402+ <!-- account.easy.reconcile.method view -->
1403+
1404+ <record id="account_easy_reconcile_method_form" model="ir.ui.view">
1405+ <field name="name">account.easy.reconcile.method.form</field>
1406+ <field name="priority">20</field>
1407+ <field name="model">account.easy.reconcile.method</field>
1408+ <field name="arch" type="xml">
1409+ <form string="Automatic Easy Reconcile Method">
1410+ <field name="sequence"/>
1411+ <field name="name"/>
1412+ <field name="write_off"/>
1413+ <field name="account_lost_id" attrs="{'required':[('write_off','>',0)]}"/>
1414+ <field name="account_profit_id" attrs="{'required':[('write_off','>',0)]}"/>
1415+ <field name="journal_id" attrs="{'required':[('write_off','>',0)]}"/>
1416+ <field name="date_base_on"/>
1417+ </form>
1418+ </field>
1419+ </record>
1420+
1421+ <record id="account_easy_reconcile_method_tree" model="ir.ui.view">
1422+ <field name="name">account.easy.reconcile.method.tree</field>
1423+ <field name="priority">20</field>
1424+ <field name="model">account.easy.reconcile.method</field>
1425+ <field name="arch" type="xml">
1426+ <tree editable="top" string="Automatic Easy Reconcile Method">
1427+ <field name="sequence"/>
1428+ <field name="name"/>
1429+ <field name="write_off"/>
1430+ <field name="account_lost_id" attrs="{'required':[('write_off','>',0)]}"/>
1431+ <field name="account_profit_id" attrs="{'required':[('write_off','>',0)]}"/>
1432+ <field name="journal_id" attrs="{'required':[('write_off','>',0)]}"/>
1433+ <field name="date_base_on"/>
1434+ </tree>
1435+ </field>
1436+ </record>
1437+
1438+ <!-- menu item -->
1439+
1440+ <menuitem action="action_account_easy_reconcile"
1441+ id="menu_easy_reconcile"
1442+ parent="account.periodical_processing_reconciliation"/>
1443+
1444+</data>
1445+</openerp>
1446
1447=== added file 'account_easy_reconcile/easy_reconcile_history.py'
1448--- account_easy_reconcile/easy_reconcile_history.py 1970-01-01 00:00:00 +0000
1449+++ account_easy_reconcile/easy_reconcile_history.py 2013-07-17 14:55:41 +0000
1450@@ -0,0 +1,153 @@
1451+# -*- coding: utf-8 -*-
1452+##############################################################################
1453+#
1454+# Author: Guewen Baconnier
1455+# Copyright 2012 Camptocamp SA
1456+#
1457+# This program is free software: you can redistribute it and/or modify
1458+# it under the terms of the GNU Affero General Public License as
1459+# published by the Free Software Foundation, either version 3 of the
1460+# License, or (at your option) any later version.
1461+#
1462+# This program is distributed in the hope that it will be useful,
1463+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1464+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1465+# GNU Affero General Public License for more details.
1466+#
1467+# You should have received a copy of the GNU Affero General Public License
1468+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1469+#
1470+##############################################################################
1471+
1472+from openerp.osv import orm, fields
1473+from openerp.tools.translate import _
1474+
1475+
1476+class easy_reconcile_history(orm.Model):
1477+ """ Store an history of the runs per profile
1478+ Each history stores the list of reconciliations done"""
1479+
1480+ _name = 'easy.reconcile.history'
1481+ _rec_name = 'easy_reconcile_id'
1482+ _order = 'date DESC'
1483+
1484+ def _reconcile_line_ids(self, cr, uid, ids, name, args, context=None):
1485+ result = {}
1486+
1487+ for history in self.browse(cr, uid, ids, context=context):
1488+ result[history.id] = {}
1489+
1490+ move_line_ids = []
1491+ for reconcile in history.reconcile_ids:
1492+ move_line_ids += [line.id
1493+ for line
1494+ in reconcile.line_id]
1495+ result[history.id]['reconcile_line_ids'] = move_line_ids
1496+
1497+ move_line_ids = []
1498+ for reconcile in history.reconcile_partial_ids:
1499+ move_line_ids += [line.id
1500+ for line
1501+ in reconcile.line_partial_ids]
1502+ result[history.id]['partial_line_ids'] = move_line_ids
1503+
1504+ return result
1505+
1506+ _columns = {
1507+ 'easy_reconcile_id': fields.many2one(
1508+ 'account.easy.reconcile', 'Reconcile Profile', readonly=True),
1509+ 'date': fields.datetime('Run date', readonly=True),
1510+ 'reconcile_ids': fields.many2many(
1511+ 'account.move.reconcile',
1512+ 'account_move_reconcile_history_rel',
1513+ string='Reconciliations', readonly=True),
1514+ 'reconcile_partial_ids': fields.many2many(
1515+ 'account.move.reconcile',
1516+ 'account_move_reconcile_history_partial_rel',
1517+ string='Partial Reconciliations', readonly=True),
1518+ 'reconcile_line_ids':
1519+ fields.function(
1520+ _reconcile_line_ids,
1521+ string='Reconciled Items',
1522+ type='many2many',
1523+ relation='account.move.line',
1524+ readonly=True,
1525+ multi='lines'),
1526+ 'partial_line_ids':
1527+ fields.function(
1528+ _reconcile_line_ids,
1529+ string='Partially Reconciled Items',
1530+ type='many2many',
1531+ relation='account.move.line',
1532+ readonly=True,
1533+ multi='lines'),
1534+ 'company_id': fields.related('easy_reconcile_id','company_id',
1535+ relation='res.company',
1536+ type='many2one',
1537+ string='Company',
1538+ store=True,
1539+ readonly=True),
1540+
1541+ }
1542+
1543+ def _open_move_lines(self, cr, uid, history_id, rec_type='full', context=None):
1544+ """ For an history record, open the view of move line with
1545+ the reconciled or partially reconciled move lines
1546+
1547+ :param history_id: id of the history
1548+ :param rec_type: 'full' or 'partial'
1549+ :return: action to open the move lines
1550+ """
1551+ assert rec_type in ('full', 'partial'), \
1552+ "rec_type must be 'full' or 'partial'"
1553+
1554+ history = self.browse(cr, uid, history_id, context=context)
1555+
1556+ if rec_type == 'full':
1557+ field = 'reconcile_line_ids'
1558+ name = _('Reconciliations')
1559+ else:
1560+ field = 'partial_line_ids'
1561+ name = _('Partial Reconciliations')
1562+
1563+ move_line_ids = [line.id for line in getattr(history, field)]
1564+
1565+ return {
1566+ 'name': name,
1567+ 'view_mode': 'tree,form',
1568+ 'view_id': False,
1569+ 'view_type': 'form',
1570+ 'res_model': 'account.move.line',
1571+ 'type': 'ir.actions.act_window',
1572+ 'nodestroy': True,
1573+ 'target': 'current',
1574+ 'domain': unicode([('id', 'in', move_line_ids)]),
1575+ }
1576+
1577+ def open_reconcile(self, cr, uid, history_ids, context=None):
1578+ """ For an history record, open the view of move line
1579+ with the reconciled move lines
1580+
1581+ :param history_ids: id of the record as int or long
1582+ Accept a list with 1 id too to be
1583+ used from the client.
1584+ """
1585+ if isinstance(history_ids, (tuple, list)):
1586+ assert len(history_ids) == 1, "only 1 ID is accepted"
1587+ history_ids = history_ids[0]
1588+ return self._open_move_lines(
1589+ cr, uid, history_ids, rec_type='full', context=None)
1590+
1591+ def open_partial(self, cr, uid, history_ids, context=None):
1592+ """ For an history record, open the view of move line
1593+ with the partially reconciled move lines
1594+
1595+ :param history_ids: id of the record as int or long
1596+ Accept a list with 1 id too to be
1597+ used from the client.
1598+ """
1599+ if isinstance(history_ids, (tuple, list)):
1600+ assert len(history_ids) == 1, "only 1 ID is accepted"
1601+ history_ids = history_ids[0]
1602+ return self._open_move_lines(
1603+ cr, uid, history_ids, rec_type='partial', context=None)
1604
1605=== added file 'account_easy_reconcile/easy_reconcile_history_view.xml'
1606--- account_easy_reconcile/easy_reconcile_history_view.xml 1970-01-01 00:00:00 +0000
1607+++ account_easy_reconcile/easy_reconcile_history_view.xml 2013-07-17 14:55:41 +0000
1608@@ -0,0 +1,98 @@
1609+<?xml version="1.0" encoding="utf-8"?>
1610+<openerp>
1611+ <data noupdate="0">
1612+
1613+ <record id="view_easy_reconcile_history_search" model="ir.ui.view">
1614+ <field name="name">easy.reconcile.history.search</field>
1615+ <field name="model">easy.reconcile.history</field>
1616+ <field name="arch" type="xml">
1617+ <search string="Automatic Easy Reconcile History">
1618+ <filter icon="terp-go-today" string="Today"
1619+ domain="[('date','&lt;', time.strftime('%%Y-%%m-%%d 23:59:59')), ('date','&gt;=', time.strftime('%%Y-%%m-%%d 00:00:00'))]"
1620+ help="Todays' Reconcilations" />
1621+ <filter icon="terp-go-week" string="7 Days"
1622+ help="Reconciliations of last 7 days"
1623+ domain="[('date','&lt;', time.strftime('%%Y-%%m-%%d 23:59:59')),('date','&gt;=',(datetime.date.today()-datetime.timedelta(days=7)).strftime('%%Y-%%m-%%d 00:00:00'))]"
1624+ />
1625+
1626+ <separator orientation="vertical"/>
1627+ <field name="easy_reconcile_id"/>
1628+ <field name="date"/>
1629+
1630+ <newline/>
1631+ <group expand="0" string="Group By...">
1632+ <filter string="Reconciliation Profile"
1633+ icon="terp-stock_effects-object-colorize"
1634+ domain="[]" context="{'group_by': 'easy_reconcile_id'}"/>
1635+ <filter string="Date" icon="terp-go-month" domain="[]"
1636+ context="{'group_by': 'date'}"/>
1637+ </group>
1638+ </search>
1639+ </field>
1640+ </record>
1641+
1642+ <record id="easy_reconcile_history_form" model="ir.ui.view">
1643+ <field name="name">easy.reconcile.history.form</field>
1644+ <field name="model">easy.reconcile.history</field>
1645+ <field name="arch" type="xml">
1646+ <form string="Automatic Easy Reconcile History" version="7.0">
1647+ <header>
1648+ <button name="open_reconcile"
1649+ string="Go to reconciled items"
1650+ icon="STOCK_JUMP_TO" type="object"/>
1651+ <button name="open_partial"
1652+ string="Go to partially reconciled items"
1653+ icon="STOCK_JUMP_TO" type="object"/>
1654+ </header>
1655+ <sheet>
1656+ <group>
1657+ <field name="easy_reconcile_id"/>
1658+ <field name="date"/>
1659+ <field name="company_id" groups="base.group_multi_company"/>
1660+ </group>
1661+ <group col="2">
1662+ <separator colspan="2" string="Reconciliations"/>
1663+ <field name="reconcile_ids" nolabel="1"/>
1664+ </group>
1665+ <group col="2">
1666+ <separator colspan="2" string="Partial Reconciliations"/>
1667+ <field name="reconcile_partial_ids" nolabel="1"/>
1668+ </group>
1669+ </sheet>
1670+ </form>
1671+ </field>
1672+ </record>
1673+
1674+ <record id="easy_reconcile_history_tree" model="ir.ui.view">
1675+ <field name="name">easy.reconcile.history.tree</field>
1676+ <field name="model">easy.reconcile.history</field>
1677+ <field name="arch" type="xml">
1678+ <tree string="Automatic Easy Reconcile History">
1679+ <field name="easy_reconcile_id"/>
1680+ <field name="date"/>
1681+ <button icon="STOCK_JUMP_TO" name="open_reconcile"
1682+ string="Go to reconciled items" type="object"/>
1683+ <button icon="STOCK_JUMP_TO" name="open_partial"
1684+ string="Go to partially reconciled items" type="object"/>
1685+ </tree>
1686+ </field>
1687+ </record>
1688+
1689+ <record id="action_easy_reconcile_history" model="ir.actions.act_window">
1690+ <field name="name">Easy Automatic Reconcile History</field>
1691+ <field name="type">ir.actions.act_window</field>
1692+ <field name="res_model">easy.reconcile.history</field>
1693+ <field name="view_type">form</field>
1694+ <field name="view_mode">tree,form</field>
1695+ </record>
1696+
1697+ <act_window
1698+ context="{'search_default_easy_reconcile_id': [active_id], 'default_easy_reconcile_id': active_id}"
1699+ id="act_easy_reconcile_to_history"
1700+ name="History Details"
1701+ groups=""
1702+ res_model="easy.reconcile.history"
1703+ src_model="account.easy.reconcile"/>
1704+
1705+ </data>
1706+</openerp>
1707
1708=== added directory 'account_easy_reconcile/i18n'
1709=== added file 'account_easy_reconcile/i18n/fr.po'
1710--- account_easy_reconcile/i18n/fr.po 1970-01-01 00:00:00 +0000
1711+++ account_easy_reconcile/i18n/fr.po 2013-07-17 14:55:41 +0000
1712@@ -0,0 +1,425 @@
1713+# Translation of OpenERP Server.
1714+# This file contains the translation of the following modules:
1715+# * account_easy_reconcile
1716+#
1717+msgid ""
1718+msgstr ""
1719+"Project-Id-Version: OpenERP Server 6.1\n"
1720+"Report-Msgid-Bugs-To: \n"
1721+"POT-Creation-Date: 2013-01-04 08:39+0000\n"
1722+"PO-Revision-Date: 2013-01-04 09:55+0100\n"
1723+"Last-Translator: Guewen Baconnier <guewen.baconnier@camptocamp.com>\n"
1724+"Language-Team: \n"
1725+"Language: \n"
1726+"MIME-Version: 1.0\n"
1727+"Content-Type: text/plain; charset=UTF-8\n"
1728+"Content-Transfer-Encoding: 8bit\n"
1729+"Plural-Forms: \n"
1730+
1731+#. module: account_easy_reconcile
1732+#: code:addons/account_easy_reconcile/easy_reconcile_history.py:101
1733+#: view:easy.reconcile.history:0
1734+#: field:easy.reconcile.history,reconcile_ids:0
1735+#, python-format
1736+msgid "Reconciliations"
1737+msgstr "Lettrages"
1738+
1739+#. module: account_easy_reconcile
1740+#: view:account.easy.reconcile:0
1741+#: view:easy.reconcile.history:0
1742+msgid "Automatic Easy Reconcile History"
1743+msgstr "Historique des lettrages automatisés"
1744+
1745+#. module: account_easy_reconcile
1746+#: view:account.easy.reconcile:0
1747+msgid "Information"
1748+msgstr "Information"
1749+
1750+#. module: account_easy_reconcile
1751+#: view:account.easy.reconcile:0
1752+#: view:easy.reconcile.history:0
1753+msgid "Go to partially reconciled items"
1754+msgstr "Voir les entrées partiellement lettrées"
1755+
1756+#. module: account_easy_reconcile
1757+#: help:account.easy.reconcile.method,sequence:0
1758+msgid "The sequence field is used to order the reconcile method"
1759+msgstr "La séquence détermine l'ordre des méthodes de lettrage"
1760+
1761+#. module: account_easy_reconcile
1762+#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_history
1763+msgid "easy.reconcile.history"
1764+msgstr "easy.reconcile.history"
1765+
1766+#. module: account_easy_reconcile
1767+#: model:ir.actions.act_window,help:account_easy_reconcile.action_account_easy_reconcile
1768+msgid ""
1769+"<p class=\"oe_view_nocontent_create\">\n"
1770+" Click to add a reconciliation profile.\n"
1771+" </p><p>\n"
1772+" A reconciliation profile specifies, for one account, how\n"
1773+" the entries should be reconciled.\n"
1774+" You can select one or many reconciliation methods which will\n"
1775+" be run sequentially to match the entries between them.\n"
1776+" </p>\n"
1777+" "
1778+msgstr ""
1779+"<p class=\"oe_view_nocontent_create\">\n"
1780+" Cliquez pour ajouter un profil de lettrage.\n"
1781+" </p><p>\n"
1782+" Un profil de lettrage spécifie, pour un compte, comment\n"
1783+" les écritures doivent être lettrées.\n"
1784+" Vous pouvez sélectionner une ou plusieurs méthodes de lettrage\n"
1785+" qui seront lancées successivement pour identifier les écritures\n"
1786+" devant être lettrées. </p>\n"
1787+" "
1788+
1789+#. module: account_easy_reconcile
1790+#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_options
1791+msgid "easy.reconcile.options"
1792+msgstr "easy.reconcile.options"
1793+
1794+#. module: account_easy_reconcile
1795+#: view:easy.reconcile.history:0
1796+msgid "Group By..."
1797+msgstr "Grouper par..."
1798+
1799+#. module: account_easy_reconcile
1800+#: field:account.easy.reconcile,unreconciled_count:0
1801+msgid "Unreconciled Items"
1802+msgstr "Écritures non lettrées"
1803+
1804+#. module: account_easy_reconcile
1805+#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_base
1806+msgid "easy.reconcile.base"
1807+msgstr "easy.reconcile.base"
1808+
1809+#. module: account_easy_reconcile
1810+#: field:easy.reconcile.history,reconcile_line_ids:0
1811+msgid "Reconciled Items"
1812+msgstr "Écritures lettrées"
1813+
1814+#. module: account_easy_reconcile
1815+#: field:account.easy.reconcile,reconcile_method:0
1816+msgid "Method"
1817+msgstr "Méthode"
1818+
1819+#. module: account_easy_reconcile
1820+#: view:easy.reconcile.history:0
1821+msgid "7 Days"
1822+msgstr "7 jours"
1823+
1824+#. module: account_easy_reconcile
1825+#: model:ir.actions.act_window,name:account_easy_reconcile.action_easy_reconcile_history
1826+msgid "Easy Automatic Reconcile History"
1827+msgstr "Lettrage automatisé"
1828+
1829+#. module: account_easy_reconcile
1830+#: field:easy.reconcile.history,date:0
1831+msgid "Run date"
1832+msgstr "Date de lancement"
1833+
1834+#. module: account_easy_reconcile
1835+#: view:account.easy.reconcile:0
1836+msgid "Match one debit line vs one credit line. Do not allow partial reconciliation. The lines should have the same amount (with the write-off) and the same reference to be reconciled."
1837+msgstr "Lettre un débit avec un crédit ayant le même montant et la même référence. Le lettrage ne peut être partiel (écriture d'ajustement en cas d'écart)."
1838+
1839+#. module: account_easy_reconcile
1840+#: model:ir.actions.act_window,name:account_easy_reconcile.act_easy_reconcile_to_history
1841+msgid "History Details"
1842+msgstr "Détails de l'historique"
1843+
1844+#. module: account_easy_reconcile
1845+#: view:account.easy.reconcile:0
1846+msgid "Display items reconciled on the last run"
1847+msgstr "Voir les entrées lettrées au dernier lettrage"
1848+
1849+#. module: account_easy_reconcile
1850+#: field:account.easy.reconcile.method,name:0
1851+msgid "Type"
1852+msgstr "Type"
1853+
1854+#. module: account_easy_reconcile
1855+#: field:account.easy.reconcile.method,journal_id:0
1856+#: field:easy.reconcile.base,journal_id:0
1857+#: field:easy.reconcile.options,journal_id:0
1858+#: field:easy.reconcile.simple,journal_id:0
1859+#: field:easy.reconcile.simple.name,journal_id:0
1860+#: field:easy.reconcile.simple.partner,journal_id:0
1861+#: field:easy.reconcile.simple.reference,journal_id:0
1862+msgid "Journal"
1863+msgstr "Journal"
1864+
1865+#. module: account_easy_reconcile
1866+#: field:account.easy.reconcile.method,account_profit_id:0
1867+#: field:easy.reconcile.base,account_profit_id:0
1868+#: field:easy.reconcile.options,account_profit_id:0
1869+#: field:easy.reconcile.simple,account_profit_id:0
1870+#: field:easy.reconcile.simple.name,account_profit_id:0
1871+#: field:easy.reconcile.simple.partner,account_profit_id:0
1872+#: field:easy.reconcile.simple.reference,account_profit_id:0
1873+msgid "Account Profit"
1874+msgstr "Compte de profits"
1875+
1876+#. module: account_easy_reconcile
1877+#: view:easy.reconcile.history:0
1878+msgid "Todays' Reconcilations"
1879+msgstr "Lettrages du jour"
1880+
1881+#. module: account_easy_reconcile
1882+#: view:account.easy.reconcile:0
1883+msgid "Simple. Amount and Name"
1884+msgstr "Simple. Montant et description"
1885+
1886+#. module: account_easy_reconcile
1887+#: field:easy.reconcile.base,partner_ids:0
1888+#: field:easy.reconcile.simple,partner_ids:0
1889+#: field:easy.reconcile.simple.name,partner_ids:0
1890+#: field:easy.reconcile.simple.partner,partner_ids:0
1891+#: field:easy.reconcile.simple.reference,partner_ids:0
1892+msgid "Restrict on partners"
1893+msgstr "Filtrer sur des partenaires"
1894+
1895+#. module: account_easy_reconcile
1896+#: model:ir.actions.act_window,name:account_easy_reconcile.action_account_easy_reconcile
1897+#: model:ir.ui.menu,name:account_easy_reconcile.menu_easy_reconcile
1898+msgid "Easy Automatic Reconcile"
1899+msgstr "Lettrage automatisé"
1900+
1901+#. module: account_easy_reconcile
1902+#: view:easy.reconcile.history:0
1903+msgid "Today"
1904+msgstr "Aujourd'hui"
1905+
1906+#. module: account_easy_reconcile
1907+#: view:easy.reconcile.history:0
1908+msgid "Date"
1909+msgstr "Date"
1910+
1911+#. module: account_easy_reconcile
1912+#: field:account.easy.reconcile,last_history:0
1913+msgid "Last History"
1914+msgstr "Dernier historique"
1915+
1916+#. module: account_easy_reconcile
1917+#: view:account.easy.reconcile:0
1918+msgid "Configuration"
1919+msgstr "Configuration"
1920+
1921+#. module: account_easy_reconcile
1922+#: field:account.easy.reconcile,reconciled_partial_count:0
1923+#: field:easy.reconcile.history,partial_line_ids:0
1924+msgid "Partially Reconciled Items"
1925+msgstr "Écritures partiellement lettrées"
1926+
1927+#. module: account_easy_reconcile
1928+#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_simple_partner
1929+msgid "easy.reconcile.simple.partner"
1930+msgstr "easy.reconcile.simple.partner"
1931+
1932+#. module: account_easy_reconcile
1933+#: field:account.easy.reconcile.method,write_off:0
1934+#: field:easy.reconcile.base,write_off:0
1935+#: field:easy.reconcile.options,write_off:0
1936+#: field:easy.reconcile.simple,write_off:0
1937+#: field:easy.reconcile.simple.name,write_off:0
1938+#: field:easy.reconcile.simple.partner,write_off:0
1939+#: field:easy.reconcile.simple.reference,write_off:0
1940+msgid "Write off allowed"
1941+msgstr "Écart autorisé"
1942+
1943+#. module: account_easy_reconcile
1944+#: view:account.easy.reconcile:0
1945+msgid "Automatic Easy Reconcile"
1946+msgstr "Lettrage automatisé"
1947+
1948+#. module: account_easy_reconcile
1949+#: field:account.easy.reconcile,account:0
1950+#: field:easy.reconcile.base,account_id:0
1951+#: field:easy.reconcile.simple,account_id:0
1952+#: field:easy.reconcile.simple.name,account_id:0
1953+#: field:easy.reconcile.simple.partner,account_id:0
1954+#: field:easy.reconcile.simple.reference,account_id:0
1955+msgid "Account"
1956+msgstr "Compte"
1957+
1958+#. module: account_easy_reconcile
1959+#: field:account.easy.reconcile.method,task_id:0
1960+msgid "Task"
1961+msgstr "Tâche"
1962+
1963+#. module: account_easy_reconcile
1964+#: field:account.easy.reconcile,name:0
1965+msgid "Name"
1966+msgstr "Nom"
1967+
1968+#. module: account_easy_reconcile
1969+#: view:account.easy.reconcile:0
1970+msgid "Simple. Amount and Partner"
1971+msgstr "Simple. Montant et partenaire"
1972+
1973+#. module: account_easy_reconcile
1974+#: view:account.easy.reconcile:0
1975+msgid "Start Auto Reconcilation"
1976+msgstr "Lancer le lettrage automatisé"
1977+
1978+#. module: account_easy_reconcile
1979+#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_simple_name
1980+msgid "easy.reconcile.simple.name"
1981+msgstr "easy.reconcile.simple.name"
1982+
1983+#. module: account_easy_reconcile
1984+#: field:account.easy.reconcile.method,filter:0
1985+#: field:easy.reconcile.base,filter:0
1986+#: field:easy.reconcile.options,filter:0
1987+#: field:easy.reconcile.simple,filter:0
1988+#: field:easy.reconcile.simple.name,filter:0
1989+#: field:easy.reconcile.simple.partner,filter:0
1990+#: field:easy.reconcile.simple.reference,filter:0
1991+msgid "Filter"
1992+msgstr "Filtre"
1993+
1994+#. module: account_easy_reconcile
1995+#: view:account.easy.reconcile:0
1996+msgid "Match one debit line vs one credit line. Do not allow partial reconciliation. The lines should have the same amount (with the write-off) and the same partner to be reconciled."
1997+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)."
1998+
1999+#. module: account_easy_reconcile
2000+#: field:easy.reconcile.history,easy_reconcile_id:0
2001+msgid "Reconcile Profile"
2002+msgstr "Profil de réconciliation"
2003+
2004+#. module: account_easy_reconcile
2005+#: view:account.easy.reconcile:0
2006+msgid "Start Auto Reconciliation"
2007+msgstr "Lancer le lettrage automatisé"
2008+
2009+#. module: account_easy_reconcile
2010+#: code:addons/account_easy_reconcile/easy_reconcile.py:250
2011+#, python-format
2012+msgid "Error"
2013+msgstr "Erreur"
2014+
2015+#. module: account_easy_reconcile
2016+#: code:addons/account_easy_reconcile/easy_reconcile.py:251
2017+#, python-format
2018+msgid "There is no history of reconciled items on the task: %s."
2019+msgstr "Il n'y a pas d'historique d'écritures lettrées sur la tâche: %s."
2020+
2021+#. module: account_easy_reconcile
2022+#: view:account.easy.reconcile:0
2023+msgid "Match one debit line vs one credit line. Do not allow partial reconciliation. The lines should have the same amount (with the write-off) and the same name to be reconciled."
2024+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)."
2025+
2026+#. module: account_easy_reconcile
2027+#: field:account.easy.reconcile.method,account_lost_id:0
2028+#: field:easy.reconcile.base,account_lost_id:0
2029+#: field:easy.reconcile.options,account_lost_id:0
2030+#: field:easy.reconcile.simple,account_lost_id:0
2031+#: field:easy.reconcile.simple.name,account_lost_id:0
2032+#: field:easy.reconcile.simple.partner,account_lost_id:0
2033+#: field:easy.reconcile.simple.reference,account_lost_id:0
2034+msgid "Account Lost"
2035+msgstr "Compte de pertes"
2036+
2037+#. module: account_easy_reconcile
2038+#: view:easy.reconcile.history:0
2039+msgid "Reconciliation Profile"
2040+msgstr "Profil de réconciliation"
2041+
2042+#. module: account_easy_reconcile
2043+#: view:account.easy.reconcile:0
2044+#: field:account.easy.reconcile,history_ids:0
2045+msgid "History"
2046+msgstr "Historique"
2047+
2048+#. module: account_easy_reconcile
2049+#: view:account.easy.reconcile:0
2050+#: view:easy.reconcile.history:0
2051+msgid "Go to reconciled items"
2052+msgstr "Voir les entrées lettrées"
2053+
2054+#. module: account_easy_reconcile
2055+#: view:account.easy.reconcile:0
2056+msgid "Profile Information"
2057+msgstr "Information sur le profil"
2058+
2059+#. module: account_easy_reconcile
2060+#: view:account.easy.reconcile.method:0
2061+msgid "Automatic Easy Reconcile Method"
2062+msgstr "Méthode de lettrage automatisé"
2063+
2064+#. module: account_easy_reconcile
2065+#: view:account.easy.reconcile:0
2066+msgid "Simple. Amount and Reference"
2067+msgstr "Simple. Montant et référence"
2068+
2069+#. module: account_easy_reconcile
2070+#: view:account.easy.reconcile:0
2071+msgid "Display items partially reconciled on the last run"
2072+msgstr "Afficher les entrées partiellement lettrées au dernier lettrage"
2073+
2074+#. module: account_easy_reconcile
2075+#: field:account.easy.reconcile.method,sequence:0
2076+msgid "Sequence"
2077+msgstr "Séquence"
2078+
2079+#. module: account_easy_reconcile
2080+#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_simple
2081+msgid "easy.reconcile.simple"
2082+msgstr "easy.reconcile.simple"
2083+
2084+#. module: account_easy_reconcile
2085+#: view:easy.reconcile.history:0
2086+msgid "Reconciliations of last 7 days"
2087+msgstr "Lettrages des 7 derniers jours"
2088+
2089+#. module: account_easy_reconcile
2090+#: field:account.easy.reconcile.method,date_base_on:0
2091+#: field:easy.reconcile.base,date_base_on:0
2092+#: field:easy.reconcile.options,date_base_on:0
2093+#: field:easy.reconcile.simple,date_base_on:0
2094+#: field:easy.reconcile.simple.name,date_base_on:0
2095+#: field:easy.reconcile.simple.partner,date_base_on:0
2096+#: field:easy.reconcile.simple.reference,date_base_on:0
2097+msgid "Date of reconciliation"
2098+msgstr "Date de lettrage"
2099+
2100+#. module: account_easy_reconcile
2101+#: code:addons/account_easy_reconcile/easy_reconcile_history.py:104
2102+#: view:easy.reconcile.history:0
2103+#: field:easy.reconcile.history,reconcile_partial_ids:0
2104+#, python-format
2105+msgid "Partial Reconciliations"
2106+msgstr "Lettrages partiels"
2107+
2108+#. module: account_easy_reconcile
2109+#: model:ir.model,name:account_easy_reconcile.model_account_easy_reconcile_method
2110+msgid "reconcile method for account_easy_reconcile"
2111+msgstr "Méthode de lettrage"
2112+
2113+#. module: account_easy_reconcile
2114+#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_simple_reference
2115+msgid "easy.reconcile.simple.reference"
2116+msgstr "easy.reconcile.simple.reference"
2117+
2118+#. module: account_easy_reconcile
2119+#: model:ir.model,name:account_easy_reconcile.model_account_easy_reconcile
2120+msgid "account easy reconcile"
2121+msgstr "Lettrage automatisé"
2122+
2123+#~ msgid "Unreconciled Entries"
2124+#~ msgstr "Écritures non lettrées"
2125+
2126+#, fuzzy
2127+#~ msgid "Partially Reconciled Entries"
2128+#~ msgstr "Lettrages partiels"
2129+
2130+#~ msgid "Task Information"
2131+#~ msgstr "Information sur la tâche"
2132+
2133+#~ msgid "Reconcile Method"
2134+#~ msgstr "Méthode de lettrage"
2135+
2136+#~ msgid "Log"
2137+#~ msgstr "Historique"
2138
2139=== added directory 'account_easy_reconcile/security'
2140=== added file 'account_easy_reconcile/security/ir.model.access.csv'
2141--- account_easy_reconcile/security/ir.model.access.csv 1970-01-01 00:00:00 +0000
2142+++ account_easy_reconcile/security/ir.model.access.csv 2013-07-17 14:55:41 +0000
2143@@ -0,0 +1,15 @@
2144+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
2145+access_easy_reconcile_options_acc_user,easy.reconcile.options,model_easy_reconcile_options,account.group_account_user,1,0,0,0
2146+access_account_easy_reconcile_method_acc_user,account.easy.reconcile.method,model_account_easy_reconcile_method,account.group_account_user,1,0,0,0
2147+access_account_easy_reconcile_acc_user,account.easy.reconcile,model_account_easy_reconcile,account.group_account_user,1,1,0,0
2148+access_easy_reconcile_simple_name_acc_user,easy.reconcile.simple.name,model_easy_reconcile_simple_name,account.group_account_user,1,0,0,0
2149+access_easy_reconcile_simple_partner_acc_user,easy.reconcile.simple.partner,model_easy_reconcile_simple_partner,account.group_account_user,1,0,0,0
2150+access_easy_reconcile_simple_reference_acc_user,easy.reconcile.simple.reference,model_easy_reconcile_simple_reference,account.group_account_user,1,0,0,0
2151+access_easy_reconcile_options_acc_mgr,easy.reconcile.options,model_easy_reconcile_options,account.group_account_user,1,0,0,0
2152+access_account_easy_reconcile_method_acc_mgr,account.easy.reconcile.method,model_account_easy_reconcile_method,account.group_account_user,1,1,1,1
2153+access_account_easy_reconcile_acc_mgr,account.easy.reconcile,model_account_easy_reconcile,account.group_account_user,1,1,1,1
2154+access_easy_reconcile_simple_name_acc_mgr,easy.reconcile.simple.name,model_easy_reconcile_simple_name,account.group_account_user,1,0,0,0
2155+access_easy_reconcile_simple_partner_acc_mgr,easy.reconcile.simple.partner,model_easy_reconcile_simple_partner,account.group_account_user,1,0,0,0
2156+access_easy_reconcile_simple_reference_acc_mgr,easy.reconcile.simple.reference,model_easy_reconcile_simple_reference,account.group_account_user,1,0,0,0
2157+access_easy_reconcile_history_acc_user,easy.reconcile.history,model_easy_reconcile_history,account.group_account_user,1,1,1,0
2158+access_easy_reconcile_history_acc_mgr,easy.reconcile.history,model_easy_reconcile_history,account.group_account_manager,1,1,1,1
2159
2160=== added file 'account_easy_reconcile/security/ir_rule.xml'
2161--- account_easy_reconcile/security/ir_rule.xml 1970-01-01 00:00:00 +0000
2162+++ account_easy_reconcile/security/ir_rule.xml 2013-07-17 14:55:41 +0000
2163@@ -0,0 +1,25 @@
2164+<?xml version="1.0" encoding="utf-8"?>
2165+<openerp>
2166+ <data noupdate="1">
2167+ <record id="easy_reconcile_rule" model="ir.rule">
2168+ <field name="name">Easy reconcile multi-company</field>
2169+ <field name="model_id" ref="model_account_easy_reconcile"/>
2170+ <field name="global" eval="True"/>
2171+ <field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field>
2172+ </record>
2173+
2174+ <record id="easy_reconcile_history_rule" model="ir.rule">
2175+ <field name="name">Easy reconcile history multi-company</field>
2176+ <field name="model_id" ref="model_easy_reconcile_history"/>
2177+ <field name="global" eval="True"/>
2178+ <field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field>
2179+ </record>
2180+
2181+ <record id="easy_reconcile_method_rule" model="ir.rule">
2182+ <field name="name">Easy reconcile method multi-company</field>
2183+ <field name="model_id" ref="model_account_easy_reconcile_method"/>
2184+ <field name="global" eval="True"/>
2185+ <field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field>
2186+ </record>
2187+ </data>
2188+</openerp>
2189
2190=== added file 'account_easy_reconcile/simple_reconciliation.py'
2191--- account_easy_reconcile/simple_reconciliation.py 1970-01-01 00:00:00 +0000
2192+++ account_easy_reconcile/simple_reconciliation.py 2013-07-17 14:55:41 +0000
2193@@ -0,0 +1,120 @@
2194+# -*- coding: utf-8 -*-
2195+##############################################################################
2196+#
2197+# Copyright 2012-2013 Camptocamp SA (Guewen Baconnier)
2198+# Copyright (C) 2010 Sébastien Beau
2199+#
2200+# This program is free software: you can redistribute it and/or modify
2201+# it under the terms of the GNU Affero General Public License as
2202+# published by the Free Software Foundation, either version 3 of the
2203+# License, or (at your option) any later version.
2204+#
2205+# This program is distributed in the hope that it will be useful,
2206+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2207+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2208+# GNU Affero General Public License for more details.
2209+#
2210+# You should have received a copy of the GNU Affero General Public License
2211+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2212+#
2213+##############################################################################
2214+
2215+from openerp.osv.orm import AbstractModel, TransientModel
2216+
2217+
2218+class easy_reconcile_simple(AbstractModel):
2219+
2220+ _name = 'easy.reconcile.simple'
2221+ _inherit = 'easy.reconcile.base'
2222+
2223+ # has to be subclassed
2224+ # field name used as key for matching the move lines
2225+ _key_field = None
2226+
2227+ def rec_auto_lines_simple(self, cr, uid, rec, lines, context=None):
2228+ if context is None:
2229+ context = {}
2230+
2231+ if self._key_field is None:
2232+ raise ValueError("_key_field has to be defined")
2233+
2234+ count = 0
2235+ res = []
2236+ while (count < len(lines)):
2237+ for i in xrange(count+1, len(lines)):
2238+ writeoff_account_id = False
2239+ if lines[count][self._key_field] != lines[i][self._key_field]:
2240+ break
2241+
2242+ check = False
2243+ if lines[count]['credit'] > 0 and lines[i]['debit'] > 0:
2244+ credit_line = lines[count]
2245+ debit_line = lines[i]
2246+ check = True
2247+ elif lines[i]['credit'] > 0 and lines[count]['debit'] > 0:
2248+ credit_line = lines[i]
2249+ debit_line = lines[count]
2250+ check = True
2251+ if not check:
2252+ continue
2253+
2254+ reconciled, dummy = self._reconcile_lines(
2255+ cr, uid, rec, [credit_line, debit_line],
2256+ allow_partial=False, context=context)
2257+ if reconciled:
2258+ res += [credit_line['id'], debit_line['id']]
2259+ del lines[i]
2260+ break
2261+ count += 1
2262+ return res, [] # empty list for partial, only full rec in "simple" rec
2263+
2264+ def _simple_order(self, rec, *args, **kwargs):
2265+ return "ORDER BY account_move_line.%s" % self._key_field
2266+
2267+ def _action_rec(self, cr, uid, rec, context=None):
2268+ """Match only 2 move lines, do not allow partial reconcile"""
2269+ select = self._select(rec)
2270+ select += ", account_move_line.%s " % self._key_field
2271+ where, params = self._where(rec)
2272+ where += " AND account_move_line.%s IS NOT NULL " % self._key_field
2273+
2274+ where2, params2 = self._get_filter(cr, uid, rec, context=context)
2275+ query = ' '.join((
2276+ select,
2277+ self._from(rec),
2278+ where, where2,
2279+ self._simple_order(rec)))
2280+
2281+ cr.execute(query, params + params2)
2282+ lines = cr.dictfetchall()
2283+ return self.rec_auto_lines_simple(cr, uid, rec, lines, context)
2284+
2285+
2286+class easy_reconcile_simple_name(TransientModel):
2287+
2288+ _name = 'easy.reconcile.simple.name'
2289+ _inherit = 'easy.reconcile.simple'
2290+
2291+ # has to be subclassed
2292+ # field name used as key for matching the move lines
2293+ _key_field = 'name'
2294+
2295+
2296+class easy_reconcile_simple_partner(TransientModel):
2297+
2298+ _name = 'easy.reconcile.simple.partner'
2299+ _inherit = 'easy.reconcile.simple'
2300+
2301+ # has to be subclassed
2302+ # field name used as key for matching the move lines
2303+ _key_field = 'partner_id'
2304+
2305+
2306+class easy_reconcile_simple_reference(TransientModel):
2307+
2308+ _name = 'easy.reconcile.simple.reference'
2309+ _inherit = 'easy.reconcile.simple'
2310+
2311+ # has to be subclassed
2312+ # field name used as key for matching the move lines
2313+ _key_field = 'ref'
2314
2315=== added directory 'account_statement_base_completion'
2316=== added file 'account_statement_base_completion/__init__.py'
2317--- account_statement_base_completion/__init__.py 1970-01-01 00:00:00 +0000
2318+++ account_statement_base_completion/__init__.py 2013-07-17 14:55:41 +0000
2319@@ -0,0 +1,23 @@
2320+# -*- coding: utf-8 -*-
2321+##############################################################################
2322+#
2323+# Author: Joel Grand-Guillaume
2324+# Copyright 2011-2012 Camptocamp SA
2325+#
2326+# This program is free software: you can redistribute it and/or modify
2327+# it under the terms of the GNU Affero General Public License as
2328+# published by the Free Software Foundation, either version 3 of the
2329+# License, or (at your option) any later version.
2330+#
2331+# This program is distributed in the hope that it will be useful,
2332+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2333+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2334+# GNU Affero General Public License for more details.
2335+#
2336+# You should have received a copy of the GNU Affero General Public License
2337+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2338+#
2339+##############################################################################
2340+
2341+import statement
2342+import partner
2343
2344=== added file 'account_statement_base_completion/__openerp__.py'
2345--- account_statement_base_completion/__openerp__.py 1970-01-01 00:00:00 +0000
2346+++ account_statement_base_completion/__openerp__.py 2013-07-17 14:55:41 +0000
2347@@ -0,0 +1,74 @@
2348+# -*- coding: utf-8 -*-
2349+##############################################################################
2350+#
2351+# Author: Joel Grand-Guillaume
2352+# Copyright 2011-2012 Camptocamp SA
2353+#
2354+# This program is free software: you can redistribute it and/or modify
2355+# it under the terms of the GNU Affero General Public License as
2356+# published by the Free Software Foundation, either version 3 of the
2357+# License, or (at your option) any later version.
2358+#
2359+# This program is distributed in the hope that it will be useful,
2360+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2361+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2362+# GNU Affero General Public License for more details.
2363+#
2364+# You should have received a copy of the GNU Affero General Public License
2365+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2366+#
2367+##############################################################################
2368+
2369+{'name': "Bank statement base completion",
2370+ 'version': '1.0',
2371+ 'author': 'Camptocamp',
2372+ 'maintainer': 'Camptocamp',
2373+ 'category': 'Finance',
2374+ 'complexity': 'normal',
2375+ 'depends': ['account_statement_ext'],
2376+ 'description': """
2377+ The goal of this module is to improve the basic bank statement, help dealing with huge volume of
2378+ reconciliation by providing basic rules to identify the partner of a bank statement line.
2379+ Each bank statement profile can have its own rules to be applied according to a sequence order.
2380+
2381+ Some basic rules are provided in this module:
2382+
2383+ 1) Match from statement line label (based on partner field 'Bank Statement Label')
2384+ 2) Match from statement line label (based on partner name)
2385+ 3) Match from statement line reference (based on SO number)
2386+ 3) Match from statement line reference (based on Invoice number)
2387+
2388+ You can easily override this module and add your own rules in your own one. The basic rules only
2389+ fill in the partner, but you can use them to fill in any value of the line (in the future, we will
2390+ add a rule to automatically match and reconcile the line).
2391+
2392+ It adds as well a label on the bank statement line (on which the pre-define rules can match) and
2393+ a char field on the partner called 'Bank Statement Label'. Using the pre-define rules, you will be
2394+ able to match various labels for a partner.
2395+
2396+ The reference of the line is always used by the reconciliation process. We're supposed to copy
2397+ there (or write manually) the matching string. This can be: the order Number or an invoice number,
2398+ or anything that will be found in the invoice accounting entry part to make the match.
2399+
2400+ You can use it with our account_advanced_reconcile module to automatize the reconciliation process.
2401+
2402+
2403+ TODO: The rules that look for invoices to find out the partner should take back the payable / receivable
2404+ account from there directly instead of retrieving it from partner properties !
2405+
2406+ """,
2407+ 'website': 'http://www.camptocamp.com',
2408+ 'init_xml': [],
2409+ 'update_xml': [
2410+ 'statement_view.xml',
2411+ 'partner_view.xml',
2412+ 'data.xml',
2413+ 'security/ir.model.access.csv',
2414+ ],
2415+ 'demo_xml': [],
2416+ 'test': [],
2417+ 'installable': True,
2418+ 'images': [],
2419+ 'auto_install': False,
2420+ 'license': 'AGPL-3',
2421+}
2422
2423=== added file 'account_statement_base_completion/data.xml'
2424--- account_statement_base_completion/data.xml 1970-01-01 00:00:00 +0000
2425+++ account_statement_base_completion/data.xml 2013-07-17 14:55:41 +0000
2426@@ -0,0 +1,37 @@
2427+<?xml version="1.0" encoding="utf-8"?>
2428+<openerp>
2429+<data noupdate="1">
2430+
2431+ <record id="bank_statement_completion_rule_2" model="account.statement.completion.rule">
2432+ <field name="name">Match from line label (based on partner field 'Bank Statement Label')</field>
2433+ <field name="sequence">60</field>
2434+ <field name="function_to_call">get_from_label_and_partner_field</field>
2435+ </record>
2436+
2437+ <record id="bank_statement_completion_rule_3" model="account.statement.completion.rule">
2438+ <field name="name">Match from line label (based on partner name)</field>
2439+ <field name="sequence">70</field>
2440+ <field name="function_to_call">get_from_label_and_partner_name</field>
2441+ </record>
2442+
2443+ <record id="bank_statement_completion_rule_1" model="account.statement.completion.rule">
2444+ <field name="name">Match from line reference (based on SO number)</field>
2445+ <field name="sequence">50</field>
2446+ <field name="function_to_call">get_from_ref_and_so</field>
2447+ </record>
2448+
2449+ <record id="bank_statement_completion_rule_4" model="account.statement.completion.rule">
2450+ <field name="name">Match from line reference (based on Invoice number)</field>
2451+ <field name="sequence">40</field>
2452+ <field name="function_to_call">get_from_ref_and_invoice</field>
2453+ </record>
2454+
2455+ <record id="bank_statement_completion_rule_5" model="account.statement.completion.rule">
2456+ <field name="name">Match from line reference (based on Invoice Supplier number)</field>
2457+ <field name="sequence">45</field>
2458+ <field name="function_to_call">get_from_ref_and_supplier_invoice</field>
2459+ </record>
2460+
2461+
2462+</data>
2463+</openerp>
2464
2465=== added directory 'account_statement_base_completion/i18n'
2466=== added file 'account_statement_base_completion/i18n/fr.po'
2467--- account_statement_base_completion/i18n/fr.po 1970-01-01 00:00:00 +0000
2468+++ account_statement_base_completion/i18n/fr.po 2013-07-17 14:55:41 +0000
2469@@ -0,0 +1,180 @@
2470+#. module: account_statement_base_completion
2471+#: view:account.statement.completion.rule:0
2472+msgid "Related Profiles"
2473+msgstr "Profils liés"
2474+
2475+#. module: account_statement_base_completion
2476+#: help:account.statement.completion.rule,sequence:0
2477+msgid "Lower means parsed first."
2478+msgstr "Plus petite séquence analysée en premier."
2479+
2480+#. module: account_statement_base_completion
2481+#: code:addons/account_statement_base_completion/statement.py:150
2482+#: code:addons/account_statement_base_completion/statement.py:182
2483+#: code:addons/account_statement_base_completion/statement.py:219
2484+#: code:addons/account_statement_base_completion/statement.py:252
2485+#, python-format
2486+msgid "Line named \"%s\" (Ref:%s) was matched by more than one partner."
2487+msgstr "La ligne nommée \"%s\" (Ref:%s) correspond à plusieurs partenaires."
2488+
2489+#. module: account_statement_base_completion
2490+#: field:account.bank.statement,completion_logs:0
2491+msgid "Completion Log"
2492+msgstr "Journal des complétions"
2493+
2494+#. module: account_statement_base_completion
2495+#: field:account.bank.statement.line,label:0
2496+msgid "Label"
2497+msgstr "Description"
2498+
2499+#. module: account_statement_base_completion
2500+#: help:account.bank.statement.line,label:0
2501+msgid ""
2502+"Generiy field to store a label given from the bank/office on which we "
2503+"can base the default/standard providen rule."
2504+msgstr ""
2505+"Ce champs permet de stocker une description complémentaire fournie par la banque."
2506+"Le lettrage avancé pourra être effectué sur ce critère."
2507+
2508+#. module: account_statement_base_completion
2509+#: model:ir.model,name:account_statement_base_completion.model_account_bank_statement
2510+msgid "Bank Statement"
2511+msgstr "Relevé bancaire"
2512+
2513+#. module: account_statement_base_completion
2514+#: field:account.statement.completion.rule,function_to_call:0
2515+msgid "Method"
2516+msgstr "Méthode"
2517+
2518+#. module: account_statement_base_completion
2519+#: code:addons/account_statement_base_completion/statement.py:352
2520+#, python-format
2521+msgid "Bank Statement ID %s has %s lines completed by %s"
2522+msgstr "Le relevé bancaire avec l'ID %s a %s lignes completées par %s"
2523+
2524+#. module: account_statement_base_completion
2525+#: field:account.bank.statement.line,additionnal_bank_fields:0
2526+msgid "Additionnal infos from bank"
2527+msgstr "Informations additionnelles de la banque"
2528+
2529+#. module: account_statement_base_completion
2530+#: view:account.statement.profile:0
2531+msgid "Auto-Completion Rules"
2532+msgstr "Règles d'auto-complétion"
2533+
2534+#. module: account_statement_base_completion
2535+#: help:account.bank.statement.line,additionnal_bank_fields:0
2536+msgid ""
2537+"Used by completion and import system. Adds every field that is present in "
2538+"your bank/office statement file"
2539+msgstr ""
2540+"Utilisé au niveau de l'auto-complétion et de l'import. Permet l'ajout de n'importe quel "
2541+"champs additionnel communiqué par la banque"
2542+
2543+#. module: account_statement_base_completion
2544+#: view:account.bank.statement:0
2545+msgid "Importation related infos"
2546+msgstr "Importation des informations liées"
2547+
2548+#. module: account_statement_base_completion
2549+#: model:ir.model,name:account_statement_base_completion.model_account_statement_profile
2550+msgid "Statement Profil"
2551+msgstr "Profil de relevé"
2552+
2553+#. module: account_statement_base_completion
2554+#: field:account.statement.completion.rule,name:0
2555+msgid "Name"
2556+msgstr "Nom"
2557+
2558+#. module: account_statement_base_completion
2559+#: constraint:account.statement.profile:0
2560+msgid "You need to put a partner if you tic the 'Force partner on bank move' !"
2561+msgstr ""
2562+"Vous devez indiquer un partenaire si vous avez coché 'Indiquer un partenaire sur la ligne d'écriture de la banque' !"
2563+
2564+#. module: account_statement_base_completion
2565+#: model:ir.model,name:account_statement_base_completion.model_account_bank_statement_line
2566+msgid "Bank Statement Line"
2567+msgstr "Ligne de relevé bancaire"
2568+
2569+#. module: account_statement_base_completion
2570+#: view:account.statement.completion.rule:0
2571+#: model:ir.actions.act_window,name:account_statement_base_completion.action_st_completion_rule_tree
2572+#: model:ir.ui.menu,name:account_statement_base_completion.menu_action_st_completion_rule_tree_menu
2573+msgid "Statement Completion Rule"
2574+msgstr "Règle d'auto-complétion du relevé"
2575+
2576+#. module: account_statement_base_completion
2577+#: model:ir.model,name:account_statement_base_completion.model_account_statement_completion_rule
2578+msgid "account.statement.completion.rule"
2579+msgstr "account.statement.completion.rule"
2580+
2581+#. module: account_statement_base_completion
2582+#: field:account.statement.completion.rule,profile_ids:0
2583+#: field:account.statement.profile,rule_ids:0
2584+msgid "Related statement profiles"
2585+msgstr "Profils liés"
2586+
2587+#. module: account_statement_base_completion
2588+#: constraint:account.bank.statement.line:0
2589+msgid ""
2590+"The amount of the voucher must be the same amount as the one on the "
2591+"statement line"
2592+msgstr ""
2593+"Le montant du justificatif doit être identique à celui de la ligne le "
2594+"concernant sur le relevé"
2595+
2596+#. module: account_statement_base_completion
2597+#: field:account.bank.statement.line,already_completed:0
2598+msgid "Auto-Completed"
2599+msgstr "Auto-Completé"
2600+
2601+#. module: account_statement_base_completion
2602+#: view:account.bank.statement:0
2603+msgid "Auto Completion"
2604+msgstr "Auto-complétion"
2605+
2606+#. module: account_statement_base_completion
2607+#: field:account.statement.completion.rule,sequence:0
2608+msgid "Sequence"
2609+msgstr "Séquence"
2610+
2611+#. module: account_statement_base_completion
2612+#: constraint:account.bank.statement:0
2613+msgid "The journal and period chosen have to belong to the same company."
2614+msgstr "Le journal et la période doivent appartenir à la même société."
2615+
2616+#. module: account_statement_base_completion
2617+#: field:res.partner,bank_statement_label:0
2618+msgid "Bank Statement Label"
2619+msgstr "Description de relevé bancaire"
2620+
2621+#. module: account_statement_base_completion
2622+#: help:account.bank.statement.line,already_completed:0
2623+msgid ""
2624+"When this checkbox is ticked, the auto-completion process/button will ignore "
2625+"this line."
2626+msgstr ""
2627+"Les lignes cochées seront ignorées lorsque vous cliquez sur le bouton auto-complétion"
2628+
2629+#. module: account_statement_base_completion
2630+#: help:res.partner,bank_statement_label:0
2631+msgid ""
2632+"Enter the various label found on your bank statement separated by a ; "
2633+"If one of this label is include in the bank statement line, "
2634+"the partner will be automatically filled (as long as you "
2635+"use this method/rules in your statement profile)."
2636+msgstr ""
2637+"Entrez les différentes descriptions/informations sur votre relevé bancaire séparées par un ';' "
2638+"Si l'une d'entre elles figure dans la ligne du relevé, le partenaire correspondant pourra"
2639+"être automatiquement retrouvé (à condition d'utiliser un règle de lettrage dans le profil)."
2640+
2641+#. module: account_statement_base_completion
2642+#: model:ir.model,name:account_statement_base_completion.model_res_partner
2643+msgid "Partner"
2644+msgstr "Partenaire"
2645+
2646+#. module: account_statement_base_completion
2647+#: view:account.bank.statement:0
2648+msgid "Completion Logs"
2649+msgstr "Journaux d'auto-complétion"
2650
2651=== added file 'account_statement_base_completion/partner.py'
2652--- account_statement_base_completion/partner.py 1970-01-01 00:00:00 +0000
2653+++ account_statement_base_completion/partner.py 2013-07-17 14:55:41 +0000
2654@@ -0,0 +1,38 @@
2655+# -*- coding: utf-8 -*-
2656+#################################################################################
2657+# #
2658+# Copyright (C) 2011 Akretion & Camptocamp
2659+# Author : Sébastien BEAU, Joel Grand-Guillaume #
2660+# #
2661+# This program is free software: you can redistribute it and/or modify #
2662+# it under the terms of the GNU Affero General Public License as #
2663+# published by the Free Software Foundation, either version 3 of the #
2664+# License, or (at your option) any later version. #
2665+# #
2666+# This program is distributed in the hope that it will be useful, #
2667+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
2668+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
2669+# GNU Affero General Public License for more details. #
2670+# #
2671+# You should have received a copy of the GNU Affero General Public License #
2672+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
2673+# #
2674+#################################################################################
2675+
2676+from openerp.osv.orm import Model
2677+from openerp.osv import fields, osv
2678+
2679+
2680+class res_partner(Model):
2681+ """
2682+ Add a bank label on the partner so that we can use it to match
2683+ this partner when we found this in a statement line.
2684+ """
2685+ _inherit = 'res.partner'
2686+
2687+ _columns = {
2688+ 'bank_statement_label': fields.char('Bank Statement Label', size=100,
2689+ help="Enter the various label found on your bank statement separated by a ; If \
2690+ one of this label is include in the bank statement line, the partner will be automatically \
2691+ filled (as long as you use this method/rules in your statement profile)."),
2692+ }
2693
2694=== added file 'account_statement_base_completion/partner_view.xml'
2695--- account_statement_base_completion/partner_view.xml 1970-01-01 00:00:00 +0000
2696+++ account_statement_base_completion/partner_view.xml 2013-07-17 14:55:41 +0000
2697@@ -0,0 +1,22 @@
2698+<?xml version="1.0" encoding="UTF-8"?>
2699+
2700+
2701+<openerp>
2702+ <data>
2703+
2704+ <record id="bk_view_partner_form" model="ir.ui.view">
2705+ <field name="name">account_bank_statement_import.view.partner.form</field>
2706+ <field name="model">res.partner</field>
2707+ <field name="type">form</field>
2708+ <field name="priority">20</field>
2709+ <field name="inherit_id" ref="account.view_partner_property_form"/>
2710+ <field name="arch" type="xml">
2711+ <field name="property_account_payable" position="after">
2712+ <field name="bank_statement_label"/>
2713+ </field>
2714+ </field>
2715+ </record>
2716+
2717+
2718+ </data>
2719+</openerp>
2720
2721=== added directory 'account_statement_base_completion/security'
2722=== added file 'account_statement_base_completion/security/ir.model.access.csv'
2723--- account_statement_base_completion/security/ir.model.access.csv 1970-01-01 00:00:00 +0000
2724+++ account_statement_base_completion/security/ir.model.access.csv 2013-07-17 14:55:41 +0000
2725@@ -0,0 +1,3 @@
2726+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
2727+access_account_bank_st_cmpl_user,account.statement.completion.rule,model_account_statement_completion_rule,account.group_account_user,1,0,0,0
2728+access_account_bank_st_cmpl_manager,account.statement.completion.rule,model_account_statement_completion_rule,account.group_account_manager,1,1,1,1
2729
2730=== added file 'account_statement_base_completion/statement.py'
2731--- account_statement_base_completion/statement.py 1970-01-01 00:00:00 +0000
2732+++ account_statement_base_completion/statement.py 2013-07-17 14:55:41 +0000
2733@@ -0,0 +1,527 @@
2734+# -*- coding: utf-8 -*-
2735+##############################################################################
2736+#
2737+# Author: Nicolas Bessi, Joel Grand-Guillaume
2738+# Copyright 2011-2012 Camptocamp SA
2739+#
2740+# This program is free software: you can redistribute it and/or modify
2741+# it under the terms of the GNU Affero General Public License as
2742+# published by the Free Software Foundation, either version 3 of the
2743+# License, or (at your option) any later version.
2744+#
2745+# This program is distributed in the hope that it will be useful,
2746+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2747+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2748+# GNU Affero General Public License for more details.
2749+#
2750+# You should have received a copy of the GNU Affero General Public License
2751+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2752+#
2753+##############################################################################
2754+# TODO replace customer supplier by package constant
2755+import traceback
2756+import sys
2757+import logging
2758+
2759+from collections import defaultdict
2760+import re
2761+from tools.translate import _
2762+from openerp.osv import osv, orm, fields
2763+from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
2764+from operator import attrgetter
2765+import datetime
2766+
2767+_logger = logging.getLogger(__name__)
2768+
2769+
2770+class ErrorTooManyPartner(Exception):
2771+ """
2772+ New Exception definition that is raised when more than one partner is matched by
2773+ the completion rule.
2774+ """
2775+ def __init__(self, value):
2776+ self.value = value
2777+
2778+ def __str__(self):
2779+ return repr(self.value)
2780+
2781+ def __repr__(self):
2782+ return repr(self.value)
2783+
2784+
2785+class AccountStatementProfil(orm.Model):
2786+ """
2787+ Extend the class to add rules per profile that will match at least the partner,
2788+ but it could also be used to match other values as well.
2789+ """
2790+
2791+ _inherit = "account.statement.profile"
2792+
2793+ _columns = {
2794+ # @Akretion: For now, we don't implement this features, but this would probably be there:
2795+ # 'auto_completion': fields.text('Auto Completion'),
2796+ # 'transferts_account_id':fields.many2one('account.account', 'Transferts Account'),
2797+ # => You can implement it in a module easily, we design it with your needs in mind
2798+ # as well!
2799+
2800+ 'rule_ids': fields.many2many(
2801+ 'account.statement.completion.rule',
2802+ string='Related statement profiles',
2803+ rel='as_rul_st_prof_rel'),
2804+ }
2805+
2806+ def _get_callable(self, cr, uid, profile, context=None):
2807+ if isinstance(profile, (int, long)):
2808+ prof = self.browse(cr, uid, profile, context=context)
2809+ else:
2810+ prof = profile
2811+ # We need to respect the sequence order
2812+ sorted_array = sorted(prof.rule_ids, key=attrgetter('sequence'))
2813+ return tuple((x.function_to_call for x in sorted_array))
2814+
2815+ def _find_values_from_rules(self, cr, uid, calls, line, context=None):
2816+ """
2817+ This method will execute all related rules, in their sequence order,
2818+ to retrieve all the values returned by the first rules that will match.
2819+ :param calls: list of lookup function name available in rules
2820+ :param dict line: read of the concerned account.bank.statement.line
2821+ :return:
2822+ A dict of value that can be passed directly to the write method of
2823+ the statement line or {}
2824+ {'partner_id': value,
2825+ 'account_id: value,
2826+
2827+ ...}
2828+ """
2829+ if context is None:
2830+ context = {}
2831+ if not calls:
2832+ calls = self._get_callable(cr, uid, line['profile_id'], context=context)
2833+ rule_obj = self.pool.get('account.statement.completion.rule')
2834+
2835+ for call in calls:
2836+ method_to_call = getattr(rule_obj, call)
2837+ result = method_to_call(cr, uid, line, context)
2838+ if result:
2839+ result['already_completed'] = True
2840+ return result
2841+ return None
2842+
2843+
2844+class AccountStatementCompletionRule(orm.Model):
2845+ """
2846+ This will represent all the completion method that we can have to
2847+ fullfill the bank statement lines. You'll be able to extend them in you own module
2848+ and choose those to apply for every statement profile.
2849+ The goal of a rule is to fullfill at least the partner of the line, but
2850+ if possible also the reference because we'll use it in the reconciliation
2851+ process. The reference should contain the invoice number or the SO number
2852+ or any reference that will be matched by the invoice accounting move.
2853+ """
2854+
2855+ _name = "account.statement.completion.rule"
2856+ _order = "sequence asc"
2857+
2858+ def _get_functions(self, cr, uid, context=None):
2859+ """
2860+ List of available methods for rules. Override this to add you own.
2861+ """
2862+ return [
2863+ ('get_from_ref_and_invoice', 'From line reference (based on customer invoice number)'),
2864+ ('get_from_ref_and_supplier_invoice', 'From line reference (based on supplier invoice number)'),
2865+ ('get_from_ref_and_so', 'From line reference (based on SO number)'),
2866+ ('get_from_label_and_partner_field', 'From line label (based on partner field)'),
2867+ ('get_from_label_and_partner_name', 'From line label (based on partner name)')]
2868+
2869+ _columns = {
2870+ 'sequence': fields.integer('Sequence', help="Lower means parsed first."),
2871+ 'name': fields.char('Name', size=128),
2872+ 'profile_ids': fields.many2many(
2873+ 'account.statement.profile',
2874+ rel='as_rul_st_prof_rel',
2875+ string='Related statement profiles'),
2876+ 'function_to_call': fields.selection(_get_functions, 'Method'),
2877+ }
2878+
2879+ def _find_invoice(self, cr, uid, st_line, inv_type, context=None):
2880+ """Find invoice related to statement line"""
2881+ inv_obj = self.pool.get('account.invoice')
2882+ if inv_type == 'supplier':
2883+ type_domain = ('in_invoice', 'in_refund')
2884+ number_field = 'supplier_invoice_number'
2885+ elif inv_type == 'customer':
2886+ type_domain = ('out_invoice', 'out_refund')
2887+ number_field = 'number'
2888+ else:
2889+ raise osv.except_osv(_('System error'),
2890+ _('Invalid invoice type for completion: %') % inv_type)
2891+
2892+ inv_id = inv_obj.search(cr, uid,
2893+ [(number_field, '=', st_line['ref'].strip()),
2894+ ('type', 'in', type_domain)],
2895+ context=context)
2896+ if inv_id:
2897+ if len(inv_id) == 1:
2898+ inv = inv_obj.browse(cr, uid, inv_id[0], context=context)
2899+ else:
2900+ raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more '
2901+ 'than one partner while looking on %s invoices') %
2902+ (st_line['name'], st_line['ref'], inv_type))
2903+ return inv
2904+ return False
2905+
2906+ def _from_invoice(self, cr, uid, line, inv_type, context):
2907+ """Populate statement line values"""
2908+ if not inv_type in ('supplier', 'customer'):
2909+ raise osv.except_osv(_('System error'),
2910+ _('Invalid invoice type for completion: %') % inv_type)
2911+ res = {}
2912+ inv = self._find_invoice(cr, uid, line, inv_type, context=context)
2913+ if inv:
2914+ res = {'partner_id': inv.partner_id.id,
2915+ 'account_id': inv.account_id.id,
2916+ 'type': inv_type}
2917+ override_acc = line['master_account_id']
2918+ if override_acc:
2919+ res['account_id'] = override_acc
2920+ return res
2921+
2922+ # Should be private but data are initialised with no update XML
2923+ def get_from_ref_and_supplier_invoice(self, cr, uid, line, context=None):
2924+ """
2925+ Match the partner based on the invoice supplier invoice number and the reference of the statement
2926+ line. Then, call the generic get_values_for_line method to complete other values.
2927+ If more than one partner matched, raise the ErrorTooManyPartner error.
2928+
2929+ :param dict line: read of the concerned account.bank.statement.line
2930+ :return:
2931+ A dict of value that can be passed directly to the write method of
2932+ the statement line or {}
2933+ {'partner_id': value,
2934+ 'account_id': value,
2935+
2936+ ...}
2937+ """
2938+ return self._from_invoice(cr, uid, line, 'supplier', context=context)
2939+
2940+ # Should be private but data are initialised with no update XML
2941+ def get_from_ref_and_invoice(self, cr, uid, line, context=None):
2942+ """
2943+ Match the partner based on the invoice number and the reference of the statement
2944+ line. Then, call the generic get_values_for_line method to complete other values.
2945+ If more than one partner matched, raise the ErrorTooManyPartner error.
2946+
2947+ :param dict line: read of the concerned account.bank.statement.line
2948+ :return:
2949+ A dict of value that can be passed directly to the write method of
2950+ the statement line or {}
2951+ {'partner_id': value,
2952+ 'account_id': value,
2953+ ...}
2954+ """
2955+ return self._from_invoice(cr, uid, line, 'customer', context=context)
2956+
2957+ # Should be private but data are initialised with no update XML
2958+ def get_from_ref_and_so(self, cr, uid, st_line, context=None):
2959+ """
2960+ Match the partner based on the SO number and the reference of the statement
2961+ line. Then, call the generic get_values_for_line method to complete other values.
2962+ If more than one partner matched, raise the ErrorTooManyPartner error.
2963+
2964+ :param int/long st_line: read of the concerned account.bank.statement.line
2965+ :return:
2966+ A dict of value that can be passed directly to the write method of
2967+ the statement line or {}
2968+ {'partner_id': value,
2969+ 'account_id': value,
2970+
2971+ ...}
2972+ """
2973+ st_obj = self.pool.get('account.bank.statement.line')
2974+ res = {}
2975+ if st_line:
2976+ so_obj = self.pool.get('sale.order')
2977+ so_id = so_obj.search(cr,
2978+ uid,
2979+ [('name', '=', st_line['ref'])],
2980+ context=context)
2981+ if so_id:
2982+ if so_id and len(so_id) == 1:
2983+ so = so_obj.browse(cr, uid, so_id[0], context=context)
2984+ res['partner_id'] = so.partner_id.id
2985+ elif so_id and len(so_id) > 1:
2986+ raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more '
2987+ 'than one partner while looking on SO by ref.') %
2988+ (st_line['name'], st_line['ref']))
2989+ st_vals = st_obj.get_values_for_line(cr,
2990+ uid,
2991+ profile_id=st_line['profile_id'],
2992+ master_account_id=st_line['master_account_id'],
2993+ partner_id=res.get('partner_id', False),
2994+ line_type='customer',
2995+ amount=st_line['amount'] if st_line['amount'] else 0.0,
2996+ context=context)
2997+ res.update(st_vals)
2998+ return res
2999+
3000+ # Should be private but data are initialised with no update XML
3001+ def get_from_label_and_partner_field(self, cr, uid, st_line, context=None):
3002+ """
3003+ Match the partner based on the label field of the statement line
3004+ and the text defined in the 'bank_statement_label' field of the partner.
3005+ Remember that we can have values separated with ; Then, call the generic
3006+ get_values_for_line method to complete other values.
3007+ If more than one partner matched, raise the ErrorTooManyPartner error.
3008+
3009+ :param dict st_line: read of the concerned account.bank.statement.line
3010+ :return:
3011+ A dict of value that can be passed directly to the write method of
3012+ the statement line or {}
3013+ {'partner_id': value,
3014+ 'account_id': value,
3015+
3016+ ...}
3017+ """
3018+ partner_obj = self.pool.get('res.partner')
3019+ st_obj = self.pool.get('account.bank.statement.line')
3020+ res = {}
3021+ # As we have to iterate on each partner for each line,
3022+ # we memoize the pair to avoid
3023+ # to redo computation for each line.
3024+ # Following code can be done by a single SQL query
3025+ # but this option is not really maintanable
3026+ if not context.get('label_memoizer'):
3027+ context['label_memoizer'] = defaultdict(list)
3028+ partner_ids = partner_obj.search(cr,
3029+ uid,
3030+ [('bank_statement_label', '!=', False)])
3031+ line_ids = context.get('line_ids', [])
3032+ for partner in partner_obj.browse(cr, uid, partner_ids, context=context):
3033+ vals = '|'.join(re.escape(x.strip()) for x in partner.bank_statement_label.split(';'))
3034+ or_regex = ".*%s.*" % vals
3035+ sql = ("SELECT id from account_bank_statement_line"
3036+ " WHERE id in %s"
3037+ " AND name ~* %s")
3038+ cr.execute(sql, (line_ids, or_regex))
3039+ pairs = cr.fetchall()
3040+ for pair in pairs:
3041+ context['label_memoizer'][pair[0]].append(partner)
3042+
3043+ if st_line['id'] in context['label_memoizer']:
3044+ found_partner = context['label_memoizer'][st_line['id']]
3045+ if len(found_partner) > 1:
3046+ msg = (_('Line named "%s" (Ref:%s) was matched by '
3047+ 'more than one partner while looking on partner label: %s') %
3048+ (st_line['name'], st_line['ref'], ','.join([x.name for x in found_partner])))
3049+ raise ErrorTooManyPartner(msg)
3050+ res['partner_id'] = found_partner[0].id
3051+ st_vals = st_obj.get_values_for_line(cr,
3052+ uid,
3053+ profile_id=st_line['profile_id'],
3054+ master_account_id=st_line['master_account_id'],
3055+ partner_id=found_partner[0].id,
3056+ line_type=False,
3057+ amount=st_line['amount'] if st_line['amount'] else 0.0,
3058+ context=context)
3059+ res.update(st_vals)
3060+ return res
3061+
3062+ def get_from_label_and_partner_name(self, cr, uid, st_line, context=None):
3063+ """
3064+ Match the partner based on the label field of the statement line
3065+ and the name of the partner.
3066+ Then, call the generic get_values_for_line method to complete other values.
3067+ If more than one partner matched, raise the ErrorTooManyPartner error.
3068+
3069+ :param dict st_line: read of the concerned account.bank.statement.line
3070+ :return:
3071+ A dict of value that can be passed directly to the write method of
3072+ the statement line or {}
3073+ {'partner_id': value,
3074+ 'account_id': value,
3075+
3076+ ...}
3077+ """
3078+ res = {}
3079+ # We memoize allowed partner
3080+ if not context.get('partner_memoizer'):
3081+ context['partner_memoizer'] = tuple(self.pool['res.partner'].search(cr, uid, []))
3082+ if not context['partner_memoizer']:
3083+ return res
3084+ st_obj = self.pool.get('account.bank.statement.line')
3085+ sql = "SELECT id FROM res_partner WHERE name ~* %s and id in %s"
3086+ pattern = ".*%s.*" % re.escape(st_line['name'])
3087+ cr.execute(sql, (pattern, context['partner_memoizer']))
3088+ result = cr.fetchall()
3089+ if not result:
3090+ return res
3091+ if len(result) > 1:
3092+ raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more '
3093+ 'than one partner while looking on partner by name') %
3094+ (st_line['name'], st_line['ref']))
3095+ res['partner_id'] = result[0][0]
3096+ st_vals = st_obj.get_values_for_line(cr,
3097+ uid,
3098+ profile_id=st_line['porfile_id'],
3099+ master_account_id=st_line['master_account_id'],
3100+ partner_id=res['partner_id'],
3101+ line_type=False,
3102+ amount=st_line['amount'] if st_line['amount'] else 0.0,
3103+ context=context)
3104+ res.update(st_vals)
3105+ return res
3106+
3107+
3108+class AccountStatementLine(orm.Model):
3109+ """
3110+ Add sparse field on the statement line to allow to store all the
3111+ bank infos that are given by a bank/office. You can then add you own in your
3112+ module. The idea here is to store all bank/office infos in the additionnal_bank_fields
3113+ serialized field when importing the file. If many values, add a tab in the bank
3114+ statement line to store your specific one. Have a look in account_statement_base_import
3115+ module to see how we've done it.
3116+ """
3117+ _inherit = "account.bank.statement.line"
3118+
3119+ _columns = {
3120+ 'additionnal_bank_fields': fields.serialized(
3121+ 'Additionnal infos from bank',
3122+ help="Used by completion and import system. Adds every field that "
3123+ "is present in your bank/office statement file"),
3124+ 'label': fields.sparse(
3125+ type='char',
3126+ string='Label',
3127+ serialization_field='additionnal_bank_fields',
3128+ help="Generic field to store a label given from the "
3129+ "bank/office on which we can base the default/standard "
3130+ "providen rule."),
3131+ 'already_completed': fields.boolean(
3132+ "Auto-Completed",
3133+ help="When this checkbox is ticked, the auto-completion "
3134+ "process/button will ignore this line."),
3135+ }
3136+
3137+ _defaults = {
3138+ 'already_completed': False,
3139+ }
3140+
3141+ def _get_line_values_from_rules(self, cr, uid, line, rules, context=None):
3142+ """
3143+ We'll try to find out the values related to the line based on rules setted on
3144+ the profile.. We will ignore line for which already_completed is ticked.
3145+
3146+ :return:
3147+ A dict of dict value that can be passed directly to the write method of
3148+ the statement line or {}. The first dict has statement line ID as a key:
3149+ {117009: {'partner_id': 100997, 'account_id': 489L}}
3150+ """
3151+ profile_obj = self.pool.get('account.statement.profile')
3152+ if line.get('already_completed'):
3153+ return {}
3154+ # Ask the rule
3155+ vals = profile_obj._find_values_from_rules(cr, uid, rules, line, context)
3156+ if vals:
3157+ vals['id'] = line['id']
3158+ return vals
3159+ return {}
3160+
3161+
3162+class AccountBankSatement(orm.Model):
3163+ """
3164+ We add a basic button and stuff to support the auto-completion
3165+ of the bank statement once line have been imported or manually fullfill.
3166+ """
3167+ _inherit = "account.bank.statement"
3168+
3169+ _columns = {
3170+ 'completion_logs': fields.text('Completion Log', readonly=True),
3171+ }
3172+
3173+ def write_completion_log(self, cr, uid, stat_id, error_msg, number_imported, context=None):
3174+ """
3175+ Write the log in the completion_logs field of the bank statement to let the user
3176+ know what have been done. This is an append mode, so we don't overwrite what
3177+ already recoded.
3178+
3179+ :param int/long stat_id: ID of the account.bank.statement
3180+ :param char error_msg: Message to add
3181+ :number_imported int/long: Number of lines that have been completed
3182+ :return True
3183+ """
3184+ user_name = self.pool.get('res.users').read(cr, uid, uid,
3185+ ['name'], context=context)['name']
3186+
3187+ log = self.read(cr, uid, stat_id, ['completion_logs'],
3188+ context=context)['completion_logs']
3189+ log = log if log else ""
3190+
3191+ completion_date = datetime.datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT)
3192+ message = (_("%s Bank Statement ID %s has %s lines completed by %s \n%s\n%s\n") %
3193+ (completion_date, stat_id, number_imported, user_name, error_msg, log))
3194+ self.write(cr, uid, [stat_id], {'completion_logs': message}, context=context)
3195+
3196+ body = (_('Statement ID %s auto-completed for %s lines completed') %
3197+ (stat_id, number_imported)),
3198+ self.message_post(cr, uid,
3199+ [stat_id],
3200+ body=body,
3201+ context=context)
3202+ return True
3203+
3204+ def button_auto_completion(self, cr, uid, ids, context=None):
3205+ """
3206+ Complete line with values given by rules and tic the already_completed
3207+ checkbox so we won't compute them again unless the user untick them!
3208+ """
3209+ if context is None:
3210+ context = {}
3211+ stat_line_obj = self.pool['account.bank.statement.line']
3212+ profile_obj = self.pool.get('account.statement.profile')
3213+ compl_lines = 0
3214+ stat_line_obj.check_access_rule(cr, uid, [], 'create')
3215+ stat_line_obj.check_access_rights(cr, uid, 'create', raise_exception=True)
3216+ for stat in self.browse(cr, uid, ids, context=context):
3217+ msg_lines = []
3218+ ctx = context.copy()
3219+ ctx['line_ids'] = tuple((x.id for x in stat.line_ids))
3220+ b_profile = stat.profile_id
3221+ rules = profile_obj._get_callable(cr, uid, b_profile, context=context)
3222+ profile_id = b_profile.id # Only for perfo even it gains almost nothing
3223+ master_account_id = b_profile.receivable_account_id
3224+ master_account_id = master_account_id.id if master_account_id else False
3225+ res = False
3226+ for line in stat_line_obj.read(cr, uid, ctx['line_ids']):
3227+ try:
3228+ # performance trick
3229+ line['master_account_id'] = master_account_id
3230+ line['profile_id'] = profile_id
3231+ res = stat_line_obj._get_line_values_from_rules(cr, uid, line,
3232+ rules, context=ctx)
3233+ if res:
3234+ compl_lines += 1
3235+ except ErrorTooManyPartner, exc:
3236+ msg_lines.append(repr(exc))
3237+ except Exception, exc:
3238+ msg_lines.append(repr(exc))
3239+ error_type, error_value, trbk = sys.exc_info()
3240+ st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value)
3241+ st += ''.join(traceback.format_tb(trbk, 30))
3242+ _logger.error(st)
3243+ if res:
3244+ #stat_line_obj.write(cr, uid, [line.id], vals, context=ctx)
3245+ try:
3246+ stat_line_obj._update_line(cr, uid, res, context=context)
3247+ except Exception as exc:
3248+ msg_lines.append(repr(exc))
3249+ error_type, error_value, trbk = sys.exc_info()
3250+ st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value)
3251+ st += ''.join(traceback.format_tb(trbk, 30))
3252+ _logger.error(st)
3253+ # we can commit as it is not needed to be atomic
3254+ # commiting here adds a nice perfo boost
3255+ if not compl_lines % 500:
3256+ cr.commit()
3257+ msg = u'\n'.join(msg_lines)
3258+ self.write_completion_log(cr, uid, stat.id,
3259+ msg, compl_lines, context=context)
3260+ return True
3261
3262=== added file 'account_statement_base_completion/statement_view.xml'
3263--- account_statement_base_completion/statement_view.xml 1970-01-01 00:00:00 +0000
3264+++ account_statement_base_completion/statement_view.xml 2013-07-17 14:55:41 +0000
3265@@ -0,0 +1,104 @@
3266+<?xml version="1.0" encoding="utf-8"?>
3267+<openerp>
3268+<data>
3269+
3270+ <record id="bank_statement_view_form" model="ir.ui.view">
3271+ <field name="name">account_bank_statement_import_base.bank_statement.view_form</field>
3272+ <field name="model">account.bank.statement</field>
3273+ <field name="inherit_id" ref="account.view_bank_statement_form" />
3274+ <field eval="16" name="priority"/>
3275+ <field name="type">form</field>
3276+ <field name="arch" type="xml">
3277+ <data>
3278+ <xpath expr="/form/sheet/notebook/page/field[@name='line_ids']/form/group/field[@name='sequence']" position="after">
3279+ <separator colspan="4" string="Importation related infos"/>
3280+ <field name="label" />
3281+ <field name="already_completed" />
3282+ </xpath>
3283+
3284+ <!-- <xpath expr="/form/group[2]" position="attributes">
3285+ <attribute name="col">10</attribute>
3286+ </xpath> -->
3287+
3288+ <xpath expr="/form/sheet/div[@name='import_buttons']" position="after">
3289+ <button name="button_auto_completion" string="Auto Completion" states='draft,open' type="object" colspan="1"/>
3290+ </xpath>
3291+
3292+ <xpath expr="/form/sheet/notebook/page[@string='Transactions']" position="after">
3293+ <page string="Completion Logs" attrs="{'invisible':[('completion_logs','=',False)]}">
3294+ <field name="completion_logs" colspan="4" nolabel="1" attrs="{'invisible':[('completion_logs','=',False)]}"/>
3295+ </page>
3296+ </xpath>
3297+
3298+ </data>
3299+ </field>
3300+ </record>
3301+ <record id="bank_statement_view_form2" model="ir.ui.view">
3302+ <field name="name">account_bank_statement_import_base.bank_statement.auto_cmpl</field>
3303+ <field name="model">account.bank.statement</field>
3304+ <field name="inherit_id" ref="account.view_bank_statement_form" />
3305+ <field name="type">form</field>
3306+ <field name="arch" type="xml">
3307+ <data>
3308+ <xpath expr="/form/sheet/notebook/page/field[@name='line_ids']/tree/field[@name='amount']" position="after">
3309+ <field name="already_completed" />
3310+ </xpath>
3311+ </data>
3312+ </field>
3313+ </record>
3314+
3315+ <record id="statement_rules_view_form" model="ir.ui.view">
3316+ <field name="name">account.statement.profile.view</field>
3317+ <field name="model">account.statement.profile</field>
3318+ <field name="inherit_id" ref="account_statement_ext.statement_importer_view_form"/>
3319+ <field name="type">form</field>
3320+ <field name="arch" type="xml">
3321+ <field name="bank_statement_prefix" position="after">
3322+ <separator colspan="4" string="Auto-Completion Rules"/>
3323+ <field name="rule_ids" colspan="4" nolabel="1"/>
3324+ </field>
3325+ </field>
3326+ </record>
3327+
3328+
3329+ <record id="statement_st_completion_rule_view_form" model="ir.ui.view">
3330+ <field name="name">account.statement.completion.rule.view</field>
3331+ <field name="model">account.statement.completion.rule</field>
3332+ <field name="type">form</field>
3333+ <field name="arch" type="xml">
3334+ <form string="Statement Completion Rule">
3335+ <field name="sequence"/>
3336+ <field name="name" select="1" />
3337+ <field name="function_to_call"/>
3338+ <separator colspan="4" string="Related Profiles"/>
3339+ <field name="profile_ids" nolabel="1" colspan="4"/>
3340+ </form>
3341+ </field>
3342+ </record>
3343+
3344+ <record id="statement_st_completion_rule_view_tree" model="ir.ui.view">
3345+ <field name="name">account.statement.completion.rule.view</field>
3346+ <field name="model">account.statement.completion.rule</field>
3347+ <field name="type">tree</field>
3348+ <field name="arch" type="xml">
3349+ <tree string="Statement Completion Rule">
3350+ <field name="sequence"/>
3351+ <field name="name" select="1" />
3352+ <field name="profile_ids" />
3353+ <field name="function_to_call"/>
3354+ </tree>
3355+ </field>
3356+ </record>
3357+ <record id="action_st_completion_rule_tree" model="ir.actions.act_window">
3358+ <field name="name">Statement Completion Rule</field>
3359+ <field name="res_model">account.statement.completion.rule</field>
3360+ <field name="view_type">form</field>
3361+ <field name="view_mode">tree,form</field>
3362+ </record>
3363+
3364+ <menuitem string="Statement Completion Rule" action="action_st_completion_rule_tree"
3365+ id="menu_action_st_completion_rule_tree_menu" parent="account.menu_configuration_misc"
3366+ sequence="30"/>
3367+
3368+</data>
3369+</openerp>
3370
3371=== added directory 'account_statement_base_import'
3372=== added file 'account_statement_base_import/__init__.py'
3373--- account_statement_base_import/__init__.py 1970-01-01 00:00:00 +0000
3374+++ account_statement_base_import/__init__.py 2013-07-17 14:55:41 +0000
3375@@ -0,0 +1,23 @@
3376+# -*- coding: utf-8 -*-
3377+##############################################################################
3378+#
3379+# Author: Joel Grand-Guillaume
3380+# Copyright 2011-2012 Camptocamp SA
3381+#
3382+# This program is free software: you can redistribute it and/or modify
3383+# it under the terms of the GNU Affero General Public License as
3384+# published by the Free Software Foundation, either version 3 of the
3385+# License, or (at your option) any later version.
3386+#
3387+# This program is distributed in the hope that it will be useful,
3388+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3389+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3390+# GNU Affero General Public License for more details.
3391+#
3392+# You should have received a copy of the GNU Affero General Public License
3393+# along with this program. If not, see <http://www.gnu.org/licenses/>.
3394+#
3395+##############################################################################
3396+import parser
3397+import wizard
3398+import statement
3399
3400=== added file 'account_statement_base_import/__openerp__.py'
3401--- account_statement_base_import/__openerp__.py 1970-01-01 00:00:00 +0000
3402+++ account_statement_base_import/__openerp__.py 2013-07-17 14:55:41 +0000
3403@@ -0,0 +1,70 @@
3404+# -*- coding: utf-8 -*-
3405+##############################################################################
3406+#
3407+# Author: Joel Grand-Guillaume
3408+# Copyright 2011-2012 Camptocamp SA
3409+#
3410+# This program is free software: you can redistribute it and/or modify
3411+# it under the terms of the GNU Affero General Public License as
3412+# published by the Free Software Foundation, either version 3 of the
3413+# License, or (at your option) any later version.
3414+#
3415+# This program is distributed in the hope that it will be useful,
3416+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3417+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3418+# GNU Affero General Public License for more details.
3419+#
3420+# You should have received a copy of the GNU Affero General Public License
3421+# along with this program. If not, see <http://www.gnu.org/licenses/>.
3422+#
3423+##############################################################################
3424+
3425+{'name': "Bank statement base import",
3426+ 'version': '1.0',
3427+ 'author': 'Camptocamp',
3428+ 'maintainer': 'Camptocamp',
3429+ 'category': 'Finance',
3430+ 'complexity': 'normal',
3431+ 'depends': [
3432+ 'account_statement_ext',
3433+ 'account_statement_base_completion'
3434+ ],
3435+ 'description': """
3436+ This module brings basic methods and fields on bank statement to deal with
3437+ the importation of different bank and offices. A generic abstract method is defined and an
3438+ example that gives you a basic way of importing bank statement through a standard file is provided.
3439+
3440+ This module improves the bank statement and allows you to import your bank transactions with
3441+ a standard .csv or .xls file (you'll find it in the 'data' folder). It respects the profile
3442+ (provided by the accouhnt_statement_ext module) to pass the entries. That means,
3443+ you'll have to choose a file format for each profile.
3444+ In order to achieve this it uses the `xlrd` Python module which you will need to install
3445+ separately in your environment.
3446+
3447+ This module can handle a commission taken by the payment office and has the following format:
3448+
3449+ * ref : the SO number, INV number or any matching ref found. It'll be used as reference
3450+ in the generated entries and will be useful for reconciliation process
3451+ * date : date of the payment
3452+ * amount : amount paid in the currency of the journal used in the importation profile
3453+ * commission_amount : amount of the comission for each line
3454+ * label : the comunication given by the payment office, used as communication in the
3455+ generated entries.
3456+
3457+ The goal is here to populate the statement lines of a bank statement with the infos that the
3458+ bank or office give you. Fell free to inherit from this module to add your own format.Then,
3459+ if you need to complete data from there, add your own account_statement_*_completion module and implement
3460+ the needed rules.
3461+
3462+ """,
3463+ 'website': 'http://www.camptocamp.com',
3464+ 'data': [
3465+ "wizard/import_statement_view.xml",
3466+ "statement_view.xml",
3467+ ],
3468+ 'test': [],
3469+ 'installable': True,
3470+ 'images': [],
3471+ 'auto_install': False,
3472+ 'license': 'AGPL-3',
3473+}
3474
3475=== added directory 'account_statement_base_import/data'
3476=== added file 'account_statement_base_import/data/statement.csv'
3477--- account_statement_base_import/data/statement.csv 1970-01-01 00:00:00 +0000
3478+++ account_statement_base_import/data/statement.csv 2013-07-17 14:55:41 +0000
3479@@ -0,0 +1,4 @@
3480+"ref";"date";"amount";"commission_amount";"label"
3481+50969286;2011-03-07 13:45:14;118.4;-11.84;"label a"
3482+51065326;2011-03-05 13:45:14;189;-15.12;"label b"
3483+51179306;2011-03-02 17:45:14;189;-15.12;"label c"
3484
3485=== added file 'account_statement_base_import/data/statement.xls'
3486Binary files account_statement_base_import/data/statement.xls 1970-01-01 00:00:00 +0000 and account_statement_base_import/data/statement.xls 2013-07-17 14:55:41 +0000 differ
3487=== added directory 'account_statement_base_import/i18n'
3488=== added file 'account_statement_base_import/i18n/fr.po'
3489--- account_statement_base_import/i18n/fr.po 1970-01-01 00:00:00 +0000
3490+++ account_statement_base_import/i18n/fr.po 2013-07-17 14:55:41 +0000
3491@@ -0,0 +1,253 @@
3492+#. module: account_statement_base_import
3493+#: view:credit.statement.import:0
3494+#: model:ir.actions.act_window,name:account_statement_base_import.statement_importer_action
3495+msgid "Import statement"
3496+msgstr "Import de relevé"
3497+
3498+#. module: account_statement_base_import
3499+#: model:ir.model,name:account_statement_base_import.model_credit_statement_import
3500+msgid "credit.statement.import"
3501+msgstr "credit.statement.import"
3502+
3503+#. module: account_statement_base_import
3504+#: code:addons/account_statement_base_import/statement.py:218
3505+#, python-format
3506+msgid "The statement cannot be created : %s"
3507+msgstr "Le relevé ne peut être créé : %s"
3508+
3509+#. module: account_statement_base_import
3510+#: field:credit.statement.import,input_statement:0
3511+msgid "Statement file"
3512+msgstr "Fichier à importer"
3513+
3514+#. module: account_statement_base_import
3515+#: view:account.statement.profile:0
3516+msgid "Import Logs"
3517+msgstr "Journaux d'import"
3518+
3519+#. module: account_statement_base_import
3520+#: field:credit.statement.import,journal_id:0
3521+msgid "Financial journal to use transaction"
3522+msgstr "Journal"
3523+
3524+#. module: account_statement_base_import
3525+#: code:addons/account_statement_base_import/parser/file_parser.py:100
3526+#, python-format
3527+msgid "Column %s not present in file"
3528+msgstr "Colonne %s non présente dans le fichier"
3529+
3530+#. module: account_statement_base_import
3531+#: view:account.statement.profile:0
3532+#: model:ir.ui.menu,name:account_statement_base_import.statement_importer_menu
3533+msgid "Import Bank Statement"
3534+msgstr "Importation de relevé"
3535+
3536+#. module: account_statement_base_import
3537+#: model:ir.model,name:account_statement_base_import.model_account_statement_profile
3538+msgid "Statement Profil"
3539+msgstr "Profil du relevé"
3540+
3541+#. module: account_statement_base_import
3542+#: code:addons/account_statement_base_import/statement.py:100
3543+#, python-format
3544+msgid "Commission line"
3545+msgstr "Ligne de commission"
3546+
3547+#. module: account_statement_base_import
3548+#: field:credit.statement.import,commission_account_id:0
3549+msgid "Commission account"
3550+msgstr "Compte de commission"
3551+
3552+#. module: account_statement_base_import
3553+#: code:addons/account_statement_base_import/statement.py:172
3554+#, python-format
3555+msgid "Column %s you try to import is not present in the bank statement line !"
3556+msgstr ""
3557+"La colonne %s que vous essayez d'importer n'est pas présente dans la ligne de relevé !"
3558+
3559+#. module: account_statement_base_import
3560+#: code:addons/account_statement_base_import/statement.py:71
3561+#, python-format
3562+msgid "Bank Statement ID %s have been imported with %s lines "
3563+msgstr "Le relevé avec l'ID %s a été importé avec %s lignes "
3564+
3565+#. module: account_statement_base_import
3566+#: field:account.statement.profile,launch_import_completion:0
3567+msgid "Launch completion after import"
3568+msgstr "Lancer l'auto-complétion après import"
3569+
3570+#. module: account_statement_base_import
3571+#: field:credit.statement.import,partner_id:0
3572+msgid "Credit insitute partner"
3573+msgstr "Organisme bancaire"
3574+
3575+#. module: account_statement_base_import
3576+#: code:addons/account_statement_base_import/statement.py:160
3577+#, python-format
3578+msgid "No Profile !"
3579+msgstr "Aucun Profil !"
3580+
3581+#. module: account_statement_base_import
3582+#: view:account.statement.profile:0
3583+msgid "Import related infos"
3584+msgstr "Importation des informations liées"
3585+
3586+#. module: account_statement_base_import
3587+#: help:credit.statement.import,force_partner_on_bank:0
3588+msgid ""
3589+"Tic that box if you want to use the credit insitute "
3590+"partner in the "
3591+"counterpart of the treasury/banking move."
3592+msgstr ""
3593+"Cochez cette case si vous voulez utiliser comme partenaire "
3594+"l'organisme bancaire au niveau de la ligne de banque"
3595+
3596+#. module: account_statement_base_import
3597+#: code:addons/account_statement_base_import/wizard/import_statement.py:93
3598+#, python-format
3599+msgid "Please use a file with an extention"
3600+msgstr "Veuillez sélectionner un fichier avec une extension"
3601+
3602+#. module: account_statement_base_import
3603+#: code:addons/account_statement_base_import/statement.py:161
3604+#, python-format
3605+msgid "You must provide a valid profile to import a bank statement !"
3606+msgstr "Vous devez fournir un profil valide pour importer un relevé !"
3607+
3608+#. module: account_statement_base_import
3609+#: help:credit.statement.import,balance_check:0
3610+msgid ""
3611+"Tic that box if you want OpenERP to control the start/end "
3612+"balance before confirming "
3613+"a bank statement. If don't ticked, no balance control will be done."
3614+msgstr ""
3615+"Cochez cette case si vous souhaitez un contrôle du solde final "
3616+"avant la validation du relevé."
3617+
3618+#. module: account_statement_base_import
3619+#: code:addons/account_statement_base_import/parser/file_parser.py:31
3620+#: code:addons/account_statement_base_import/parser/generic_file_parser.py:31
3621+#, python-format
3622+msgid "Please install python lib xlrd"
3623+msgstr "Veuillez installer la bibliothèque python xlrd"
3624+
3625+#. module: account_statement_base_import
3626+#: code:addons/account_statement_base_import/parser/file_parser.py:58
3627+#, python-format
3628+msgid "Invalide file type %s. please use csv or xls"
3629+msgstr "Type de fichier invalide : %s. Veuillez utiliser un csv ou un xls"
3630+
3631+#. module: account_statement_base_import
3632+#: field:account.statement.profile,last_import_date:0
3633+msgid "Last Import Date"
3634+msgstr "Date de dernier import"
3635+
3636+#. module: account_statement_base_import
3637+#: field:credit.statement.import,commission_analytic_id:0
3638+msgid "Commission analytic account"
3639+msgstr "Compte analytique de la commission"
3640+
3641+#. module: account_statement_base_import
3642+#: constraint:account.statement.profile:0
3643+msgid "You need to put a partner if you tic the 'Force partner on bank move' !"
3644+msgstr ""
3645+"Vous devez spécifier un partenaire si vous avez coché 'Forcer un partenaire sur la ligne de la banque' !"
3646+
3647+#. module: account_statement_base_import
3648+#: code:addons/account_statement_base_import/statement.py:217
3649+#, python-format
3650+msgid "Statement import error"
3651+msgstr "Erreur d'import de relevé"
3652+
3653+#. module: account_statement_base_import
3654+#: field:account.bank.statement.line,commission_amount:0
3655+msgid "Line Commission Amount"
3656+msgstr "Montant de la commission de la ligne"
3657+
3658+#. module: account_statement_base_import
3659+#: code:addons/account_statement_base_import/statement.py:171
3660+#, python-format
3661+msgid "Missing column !"
3662+msgstr "Colonne manquante !"
3663+
3664+#. module: account_statement_base_import
3665+#: field:account.statement.profile,rec_log:0
3666+msgid "log"
3667+msgstr "journal"
3668+
3669+#. module: account_statement_base_import
3670+#: field:account.statement.profile,import_type:0
3671+msgid "Type of import"
3672+msgstr "Type d'import"
3673+
3674+#. module: account_statement_base_import
3675+#: constraint:account.bank.statement.line:0
3676+msgid ""
3677+"The amount of the voucher must be the same amount as the one on the "
3678+"statement line"
3679+msgstr ""
3680+"Le montant du justificatif doit être identique à celui de la ligne le "
3681+"concernant sur le relevé"
3682+
3683+#. module: account_statement_base_import
3684+#: help:account.statement.profile,launch_import_completion:0
3685+msgid ""
3686+"Tic that box to automatically launch the completion on each "
3687+"imported file using this profile."
3688+msgstr ""
3689+"Cocher cette case pour lancer automatiquement l'auto-complétion sur"
3690+"chaque fichier importé avec ce profil."
3691+
3692+#. module: account_statement_base_import
3693+#: field:credit.statement.import,profile_id:0
3694+msgid "Import configuration parameter"
3695+msgstr "Paramètres de configuration d'import"
3696+
3697+#. module: account_statement_base_import
3698+#: code:addons/account_statement_base_import/parser/parser.py:156
3699+#, python-format
3700+msgid "No buffer file given."
3701+msgstr "Pas de fichier tampon donné."
3702+
3703+#. module: account_statement_base_import
3704+#: view:credit.statement.import:0
3705+msgid "Import Parameters Summary"
3706+msgstr "Résumé des paramètres d'import"
3707+
3708+#. module: account_statement_base_import
3709+#: field:credit.statement.import,balance_check:0
3710+msgid "Balance check"
3711+msgstr "Vérification des soldes"
3712+
3713+#. module: account_statement_base_import
3714+#: model:ir.model,name:account_statement_base_import.model_account_bank_statement_line
3715+msgid "Bank Statement Line"
3716+msgstr "Ligne de relevé bancaire"
3717+
3718+#. module: account_statement_base_import
3719+#: field:credit.statement.import,force_partner_on_bank:0
3720+msgid "Force partner on bank move"
3721+msgstr "Forcer un partenaire sur la ligne du compte de banque"
3722+
3723+#. module: account_statement_base_import
3724+#: field:credit.statement.import,file_name:0
3725+msgid "File Name"
3726+msgstr "Nom du fichier"
3727+
3728+#. module: account_statement_base_import
3729+#: field:credit.statement.import,receivable_account_id:0
3730+msgid "Force Receivable/Payable Account"
3731+msgstr "Forcer le compte Client/Fournisseur"
3732+
3733+#. module: account_statement_base_import
3734+#: help:account.statement.profile,import_type:0
3735+msgid ""
3736+"Choose here the method by which you want to import bank statement for this "
3737+"profile."
3738+msgstr ""
3739+"Choisissez la méthode d'import de relevé pour ce profil."
3740+
3741+#. module: account_statement_base_import
3742+#: view:credit.statement.import:0
3743+msgid "Cancel"
3744+msgstr "Annulation"
3745
3746=== added directory 'account_statement_base_import/parser'
3747=== added file 'account_statement_base_import/parser/__init__.py'
3748--- account_statement_base_import/parser/__init__.py 1970-01-01 00:00:00 +0000
3749+++ account_statement_base_import/parser/__init__.py 2013-07-17 14:55:41 +0000
3750@@ -0,0 +1,25 @@
3751+# -*- coding: utf-8 -*-
3752+##############################################################################
3753+#
3754+# Author: Nicolas Bessi, Joel Grand-Guillaume
3755+# Copyright 2011-2012 Camptocamp SA
3756+#
3757+# This program is free software: you can redistribute it and/or modify
3758+# it under the terms of the GNU Affero General Public License as
3759+# published by the Free Software Foundation, either version 3 of the
3760+# License, or (at your option) any later version.
3761+#
3762+# This program is distributed in the hope that it will be useful,
3763+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3764+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3765+# GNU Affero General Public License for more details.
3766+#
3767+# You should have received a copy of the GNU Affero General Public License
3768+# along with this program. If not, see <http://www.gnu.org/licenses/>.
3769+#
3770+##############################################################################
3771+
3772+from parser import new_bank_statement_parser
3773+from parser import BankStatementImportParser
3774+import file_parser
3775+import generic_file_parser
3776
3777=== added file 'account_statement_base_import/parser/file_parser.py'
3778--- account_statement_base_import/parser/file_parser.py 1970-01-01 00:00:00 +0000
3779+++ account_statement_base_import/parser/file_parser.py 2013-07-17 14:55:41 +0000
3780@@ -0,0 +1,218 @@
3781+# -*- coding: utf-8 -*-
3782+##############################################################################
3783+#
3784+# Copyright Camptocamp SA
3785+# Author Nicolas Bessi, Joel Grand-Guillaume
3786+# This program is free software: you can redistribute it and/or modify
3787+# it under the terms of the GNU General Public License as published by
3788+# the Free Software Foundation, either version 3 of the License, or
3789+# (at your option) any later version.
3790+#
3791+# This program is distributed in the hope that it will be useful,
3792+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3793+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3794+# GNU General Public License for more details.
3795+#
3796+# You should have received a copy of the GNU General Public License
3797+# along with this program. If not, see <http://www.gnu.org/licenses/>.
3798+#
3799+##############################################################################
3800+from openerp.tools.translate import _
3801+from openerp.osv.osv import except_osv
3802+import tempfile
3803+import datetime
3804+from parser import BankStatementImportParser
3805+from parser import UnicodeDictReader
3806+try:
3807+ import xlrd
3808+except:
3809+ raise Exception(_('Please install python lib xlrd'))
3810+
3811+
3812+class FileParser(BankStatementImportParser):
3813+ """
3814+ Generic abstract class for defining parser for .csv or .xls file format.
3815+ """
3816+
3817+ def __init__(self, parse_name, keys_to_validate=None, ftype='csv', conversion_dict=None,
3818+ header=None, *args, **kwargs):
3819+ """
3820+ :param char: parse_name : The name of the parser
3821+ :param list: keys_to_validate : contain the key that need to be present in the file
3822+ :param char ftype: extension of the file (could be csv or xls)
3823+ :param: conversion_dict : keys and type to convert of every column in the file like
3824+ {
3825+ 'ref': unicode,
3826+ 'label': unicode,
3827+ 'date': datetime.datetime,
3828+ 'amount': float,
3829+ 'commission_amount': float
3830+ }
3831+ :param list: header : specify header fields if the csv file has no header
3832+ """
3833+
3834+ super(FileParser, self).__init__(parse_name, *args, **kwargs)
3835+ if ftype in ('csv', 'xls'):
3836+ self.ftype = ftype
3837+ else:
3838+ raise except_osv(_('User Error'),
3839+ _('Invalid file type %s. Please use csv or xls') % ftype)
3840+ self.keys_to_validate = keys_to_validate if keys_to_validate is not None else []
3841+ self.conversion_dict = conversion_dict
3842+ self.fieldnames = header
3843+ self._datemode = 0 # used only for xls documents,
3844+ # 0 means Windows mode (1900 based dates).
3845+ # Set in _parse_xls, from the contents of the file
3846+
3847+ def _custom_format(self, *args, **kwargs):
3848+ """
3849+ No other work on data are needed in this parser.
3850+ """
3851+ return True
3852+
3853+ def _pre(self, *args, **kwargs):
3854+ """
3855+ No pre-treatment needed for this parser.
3856+ """
3857+ return True
3858+
3859+ def _parse(self, *args, **kwargs):
3860+ """
3861+ Launch the parsing through .csv or .xls depending on the
3862+ given ftype
3863+ """
3864+
3865+ res = None
3866+ if self.ftype == 'csv':
3867+ res = self._parse_csv()
3868+ else:
3869+ res = self._parse_xls()
3870+ self.result_row_list = res
3871+ return True
3872+
3873+ def _validate(self, *args, **kwargs):
3874+ """
3875+ We check that all the key of the given file (means header) are present
3876+ in the validation key provided. Otherwise, we raise an Exception.
3877+ We skip the validation step if the file header is provided separately
3878+ (in the field: fieldnames).
3879+ """
3880+ if self.fieldnames is None:
3881+ parsed_cols = self.result_row_list[0].keys()
3882+ for col in self.keys_to_validate:
3883+ if col not in parsed_cols:
3884+ raise except_osv(_('Invalid data'),
3885+ _('Column %s not present in file') % col)
3886+ return True
3887+
3888+ def _post(self, *args, **kwargs):
3889+ """
3890+ Cast row type depending on the file format .csv or .xls after parsing the file.
3891+ """
3892+ self.result_row_list = self._cast_rows(*args, **kwargs)
3893+ return True
3894+
3895+ def _parse_csv(self):
3896+ """
3897+ :return: list of dict from csv file (line/rows)
3898+ """
3899+ csv_file = tempfile.NamedTemporaryFile()
3900+ csv_file.write(self.filebuffer)
3901+ csv_file.flush()
3902+ with open(csv_file.name, 'rU') as fobj:
3903+ reader = UnicodeDictReader(fobj, fieldnames=self.fieldnames)
3904+ return list(reader)
3905+
3906+ def _parse_xls(self):
3907+ """
3908+ :return: dict of dict from xls file (line/rows)
3909+ """
3910+ wb_file = tempfile.NamedTemporaryFile()
3911+ wb_file.write(self.filebuffer)
3912+ # We ensure that cursor is at beginig of file
3913+ wb_file.seek(0)
3914+ with xlrd.open_workbook(wb_file.name) as wb:
3915+ self._datemode = wb.datemode
3916+ sheet = wb.sheet_by_index(0)
3917+ header = sheet.row_values(0)
3918+ res = []
3919+ for rownum in range(1, sheet.nrows):
3920+ res.append(dict(zip(header, sheet.row_values(rownum))))
3921+ return res
3922+
3923+ def _from_csv(self, result_set, conversion_rules):
3924+ """
3925+ Handle the converstion from the dict and handle date format from
3926+ an .csv file.
3927+ """
3928+ for line in result_set:
3929+ for rule in conversion_rules:
3930+ if conversion_rules[rule] == datetime.datetime:
3931+ try:
3932+ date_string = line[rule].split(' ')[0]
3933+ line[rule] = datetime.datetime.strptime(date_string,
3934+ '%Y-%m-%d')
3935+ except ValueError as err:
3936+ raise except_osv(_("Date format is not valid."),
3937+ _(" It should be YYYY-MM-DD for column: %s"
3938+ " value: %s \n \n"
3939+ " \n Please check the line with ref: %s"
3940+ " \n \n Detail: %s") % (rule,
3941+ line.get(rule, _('Missing')),
3942+ line.get('ref', line),
3943+ repr(err)))
3944+ else:
3945+ try:
3946+ line[rule] = conversion_rules[rule](line[rule])
3947+ except Exception as err:
3948+ raise except_osv(_('Invalid data'),
3949+ _("Value %s of column %s is not valid."
3950+ "\n Please check the line with ref %s:"
3951+ "\n \n Detail: %s") % (line.get(rule, _('Missing')),
3952+ rule,
3953+ line.get('ref', line),
3954+ repr(err)))
3955+ return result_set
3956+
3957+ def _from_xls(self, result_set, conversion_rules):
3958+ """
3959+ Handle the converstion from the dict and handle date format from
3960+ an .xls file.
3961+ """
3962+ for line in result_set:
3963+ for rule in conversion_rules:
3964+ if conversion_rules[rule] == datetime.datetime:
3965+ try:
3966+ t_tuple = xlrd.xldate_as_tuple(line[rule], self._datemode)
3967+ line[rule] = datetime.datetime(*t_tuple)
3968+ except Exception as err:
3969+ raise except_osv(_("Date format is not valid"),
3970+ _("Please modify the cell formatting to date format"
3971+ " for column: %s"
3972+ " value: %s"
3973+ "\n Please check the line with ref: %s"
3974+ "\n \n Detail: %s") % (rule,
3975+ line.get(rule, _('Missing')),
3976+ line.get('ref', line),
3977+ repr(err)))
3978+ else:
3979+ try:
3980+ line[rule] = conversion_rules[rule](line[rule])
3981+ except Exception as err:
3982+ raise except_osv(_('Invalid data'),
3983+ _("Value %s of column %s is not valid."
3984+ "\n Please check the line with ref %s:"
3985+ "\n \n Detail: %s") % (line.get(rule, _('Missing')),
3986+ rule,
3987+ line.get('ref', line),
3988+ repr(err)))
3989+ return result_set
3990+
3991+ def _cast_rows(self, *args, **kwargs):
3992+ """
3993+ Convert the self.result_row_list using the self.conversion_dict providen.
3994+ We call here _from_xls or _from_csv depending on the self.ftype variable.
3995+ """
3996+ func = getattr(self, '_from_%s' % self.ftype)
3997+ res = func(self.result_row_list, self.conversion_dict)
3998+ return res
3999
4000=== added file 'account_statement_base_import/parser/generic_file_parser.py'
4001--- account_statement_base_import/parser/generic_file_parser.py 1970-01-01 00:00:00 +0000
4002+++ account_statement_base_import/parser/generic_file_parser.py 2013-07-17 14:55:41 +0000
4003@@ -0,0 +1,102 @@
4004+# -*- coding: utf-8 -*-
4005+##############################################################################
4006+#
4007+# Copyright Camptocamp SA
4008+# Author Joel Grand-Guillaume
4009+# This program is free software: you can redistribute it and/or modify
4010+# it under the terms of the GNU General Public License as published by
4011+# the Free Software Foundation, either version 3 of the License, or
4012+# (at your option) any later version.
4013+#
4014+# This program is distributed in the hope that it will be useful,
4015+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4016+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4017+# GNU General Public License for more details.
4018+#
4019+# You should have received a copy of the GNU General Public License
4020+# along with this program. If not, see <http://www.gnu.org/licenses/>.
4021+#
4022+##############################################################################
4023+
4024+from openerp.tools.translate import _
4025+import base64
4026+import csv
4027+import tempfile
4028+import datetime
4029+from file_parser import FileParser
4030+try:
4031+ import xlrd
4032+except:
4033+ raise Exception(_('Please install python lib xlrd'))
4034+
4035+
4036+def float_or_zero(val):
4037+ """ Conversion function used to manage
4038+ empty string into float usecase"""
4039+ return float(val) if val else 0.0
4040+
4041+
4042+class GenericFileParser(FileParser):
4043+ """
4044+ Standard parser that use a define format in csv or xls to import into a
4045+ bank statement. This is mostely an example of how to proceed to create a new
4046+ parser, but will also be useful as it allow to import a basic flat file.
4047+ """
4048+
4049+ def __init__(self, parse_name, ftype='csv'):
4050+ conversion_dict = {
4051+ 'ref': unicode,
4052+ 'label': unicode,
4053+ 'date': datetime.datetime,
4054+ 'amount': float_or_zero,
4055+ 'commission_amount': float_or_zero
4056+ }
4057+ # Order of cols does not matter but first row of the file has to be header
4058+ keys_to_validate = ['ref', 'label', 'date', 'amount', 'commission_amount']
4059+ super(GenericFileParser, self).__init__(parse_name, keys_to_validate=keys_to_validate, ftype=ftype, conversion_dict=conversion_dict)
4060+
4061+ @classmethod
4062+ def parser_for(cls, parser_name):
4063+ """
4064+ Used by the new_bank_statement_parser class factory. Return true if
4065+ the providen name is generic_csvxls_so
4066+ """
4067+ return parser_name == 'generic_csvxls_so'
4068+
4069+ def get_st_line_vals(self, line, *args, **kwargs):
4070+ """
4071+ This method must return a dict of vals that can be passed to create
4072+ method of statement line in order to record it. It is the responsibility
4073+ of every parser to give this dict of vals, so each one can implement his
4074+ own way of recording the lines.
4075+ :param: line: a dict of vals that represent a line of result_row_list
4076+ :return: dict of values to give to the create method of statement line,
4077+ it MUST contain at least:
4078+ {
4079+ 'name':value,
4080+ 'date':value,
4081+ 'amount':value,
4082+ 'ref':value,
4083+ 'label':value,
4084+ 'commission_amount':value,
4085+ }
4086+ In this generic parser, the commission is given for every line, so we store it
4087+ for each one.
4088+ """
4089+ return {'name': line.get('label', line.get('ref', '/')),
4090+ 'date': line.get('date', datetime.datetime.now().date()),
4091+ 'amount': line.get('amount', 0.0),
4092+ 'ref': line.get('ref', '/'),
4093+ 'label': line.get('label', ''),
4094+ 'commission_amount': line.get('commission_amount', 0.0)}
4095+
4096+ def _post(self, *args, **kwargs):
4097+ """
4098+ Compute the commission from value of each line
4099+ """
4100+ res = super(GenericFileParser, self)._post(*args, **kwargs)
4101+ val = 0.0
4102+ for row in self.result_row_list:
4103+ val += row.get('commission_amount', 0.0)
4104+ self.commission_global_amount = val
4105+ return res
4106
4107=== added file 'account_statement_base_import/parser/parser.py'
4108--- account_statement_base_import/parser/parser.py 1970-01-01 00:00:00 +0000
4109+++ account_statement_base_import/parser/parser.py 2013-07-17 14:55:41 +0000
4110@@ -0,0 +1,219 @@
4111+# -*- coding: utf-8 -*-
4112+##############################################################################
4113+#
4114+# Author: Joel Grand-Guillaume
4115+# Copyright 2011-2012 Camptocamp SA
4116+#
4117+# This program is free software: you can redistribute it and/or modify
4118+# it under the terms of the GNU Affero General Public License as
4119+# published by the Free Software Foundation, either version 3 of the
4120+# License, or (at your option) any later version.
4121+#
4122+# This program is distributed in the hope that it will be useful,
4123+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4124+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4125+# GNU Affero General Public License for more details.
4126+#
4127+# You should have received a copy of the GNU Affero General Public License
4128+# along with this program. If not, see <http://www.gnu.org/licenses/>.
4129+#
4130+##############################################################################
4131+import base64
4132+import csv
4133+
4134+
4135+def UnicodeDictReader(utf8_data, **kwargs):
4136+ sniffer = csv.Sniffer()
4137+ pos = utf8_data.tell()
4138+ sample_data = utf8_data.read(1024)
4139+ utf8_data.seek(pos)
4140+ dialect = sniffer.sniff(sample_data, delimiters=',;\t')
4141+ csv_reader = csv.DictReader(utf8_data, dialect=dialect, **kwargs)
4142+ for row in csv_reader:
4143+ yield dict([(key, unicode(value, 'utf-8')) for key, value in row.iteritems()])
4144+
4145+
4146+class BankStatementImportParser(object):
4147+ """
4148+ Generic abstract class for defining parser for different files and
4149+ format to import in a bank statement. Inherit from it to create your
4150+ own. If your file is a .csv or .xls format, you should consider inheirt
4151+ from the FileParser instead.
4152+ """
4153+
4154+ def __init__(self, parser_name, *args, **kwargs):
4155+ # The name of the parser as it will be called
4156+ self.parser_name = parser_name
4157+ # The result as a list of row. One row per line of data in the file, but
4158+ # not the commission one !
4159+ self.result_row_list = None
4160+ # The file buffer on which to work on
4161+ self.filebuffer = None
4162+ # Concatenate here the global commission taken by the bank/office
4163+ # for this statement.
4164+ self.commission_global_amount = None
4165+
4166+ @classmethod
4167+ def parser_for(cls, parser_name):
4168+ """
4169+ Override this method for every new parser, so that new_bank_statement_parser can
4170+ return the good class from his name.
4171+ """
4172+ return False
4173+
4174+ def _decode_64b_stream(self):
4175+ """
4176+ Decode self.filebuffer in base 64 and override it
4177+ """
4178+ self.filebuffer = base64.b64decode(self.filebuffer)
4179+ return True
4180+
4181+ def _format(self, decode_base_64=True, **kwargs):
4182+ """
4183+ Decode into base 64 if asked and Format the given filebuffer by calling
4184+ _custom_format method.
4185+ """
4186+ if decode_base_64:
4187+ self._decode_64b_stream()
4188+ self._custom_format(kwargs)
4189+ return True
4190+
4191+ def _custom_format(self, *args, **kwargs):
4192+ """
4193+ Implement a method in your parser to convert format, encoding and so on before
4194+ starting to work on datas. Work on self.filebuffer
4195+ """
4196+ return NotImplementedError
4197+
4198+ def _pre(self, *args, **kwargs):
4199+ """
4200+ Implement a method in your parser to make a pre-treatment on datas before parsing
4201+ them, like concatenate stuff, and so... Work on self.filebuffer
4202+ """
4203+ return NotImplementedError
4204+
4205+ def _parse(self, *args, **kwargs):
4206+ """
4207+ Implement a method in your parser to save the result of parsing self.filebuffer
4208+ in self.result_row_list instance property.
4209+ """
4210+ return NotImplementedError
4211+
4212+ def _validate(self, *args, **kwargs):
4213+ """
4214+ Implement a method in your parser to validate the self.result_row_list instance
4215+ property and raise an error if not valid.
4216+ """
4217+ return NotImplementedError
4218+
4219+ def _post(self, *args, **kwargs):
4220+ """
4221+ Implement a method in your parser to make some last changes on the result of parsing
4222+ the datas, like converting dates, computing commission, ...
4223+ Work on self.result_row_list and put the commission global amount if any
4224+ in the self.commission_global_amount one.
4225+ """
4226+ return NotImplementedError
4227+
4228+ def get_st_line_vals(self, line, *args, **kwargs):
4229+ """
4230+ Implement a method in your parser that must return a dict of vals that can be
4231+ passed to create method of statement line in order to record it. It is the responsibility
4232+ of every parser to give this dict of vals, so each one can implement his
4233+ own way of recording the lines.
4234+ :param: line: a dict of vals that represent a line of result_row_list
4235+ :return: dict of values to give to the create method of statement line,
4236+ it MUST contain at least:
4237+ {
4238+ 'name':value,
4239+ 'date':value,
4240+ 'amount':value,
4241+ 'ref':value,
4242+ }
4243+ """
4244+ return NotImplementedError
4245+
4246+ def get_st_line_commision(self, *args, **kwargs):
4247+ """
4248+ This is called by the importation method to create the commission line in
4249+ the bank statement. We will always create one line for the commission in the
4250+ bank statement, but it could be computated from a value of each line, or given
4251+ in a single line for the whole file.
4252+ return: float of the whole commission (self.commission_global_amount)
4253+ """
4254+ return self.commission_global_amount
4255+
4256+ def parse(self, filebuffer, *args, **kwargs):
4257+ """
4258+ This will be the method that will be called by wizard, button and so
4259+ to parse a filebuffer by calling successively all the private method
4260+ that need to be define for each parser.
4261+ Return:
4262+ [] of rows as {'key':value}
4263+
4264+ Note: The row_list must contain only value that are present in the account.
4265+ bank.statement.line object !!!
4266+ """
4267+ if filebuffer:
4268+ self.filebuffer = filebuffer
4269+ else:
4270+ raise Exception(_('No buffer file given.'))
4271+ self._format(*args, **kwargs)
4272+ self._pre(*args, **kwargs)
4273+ self._parse(*args, **kwargs)
4274+ self._validate(*args, **kwargs)
4275+ self._post(*args, **kwargs)
4276+ return self.result_row_list
4277+
4278+
4279+def itersubclasses(cls, _seen=None):
4280+ """
4281+ itersubclasses(cls)
4282+
4283+ Generator over all subclasses of a given class, in depth first order.
4284+
4285+ >>> list(itersubclasses(int)) == [bool]
4286+ True
4287+ >>> class A(object): pass
4288+ >>> class B(A): pass
4289+ >>> class C(A): pass
4290+ >>> class D(B,C): pass
4291+ >>> class E(D): pass
4292+ >>>
4293+ >>> for cls in itersubclasses(A):
4294+ ... print(cls.__name__)
4295+ B
4296+ D
4297+ E
4298+ C
4299+ >>> # get ALL (new-style) classes currently defined
4300+ >>> [cls.__name__ for cls in itersubclasses(object)] #doctest: +ELLIPSIS
4301+ ['type', ...'tuple', ...]
4302+ """
4303+ if not isinstance(cls, type):
4304+ raise TypeError('itersubclasses must be called with '
4305+ 'new-style classes, not %.100r' % cls)
4306+ if _seen is None:
4307+ _seen = set()
4308+ try:
4309+ subs = cls.__subclasses__()
4310+ except TypeError: # fails only when cls is type
4311+ subs = cls.__subclasses__(cls)
4312+ for sub in subs:
4313+ if sub not in _seen:
4314+ _seen.add(sub)
4315+ yield sub
4316+ for sub in itersubclasses(sub, _seen):
4317+ yield sub
4318+
4319+
4320+def new_bank_statement_parser(parser_name, *args, **kwargs):
4321+ """
4322+ Return an instance of the good parser class base on the providen name
4323+ :param char: parser_name
4324+ :return: class instance of parser_name providen.
4325+ """
4326+ for cls in itersubclasses(BankStatementImportParser):
4327+ if cls.parser_for(parser_name):
4328+ return cls(parser_name, *args, **kwargs)
4329+ raise ValueError
4330
4331=== added file 'account_statement_base_import/statement.py'
4332--- account_statement_base_import/statement.py 1970-01-01 00:00:00 +0000
4333+++ account_statement_base_import/statement.py 2013-07-17 14:55:41 +0000
4334@@ -0,0 +1,303 @@
4335+# -*- coding: utf-8 -*-
4336+##############################################################################
4337+#
4338+# Author: Joel Grand-Guillaume
4339+# Copyright 2011-2012 Camptocamp SA
4340+#
4341+# This program is free software: you can redistribute it and/or modify
4342+# it under the terms of the GNU Affero General Public License as
4343+# published by the Free Software Foundation, either version 3 of the
4344+# License, or (at your option) any later version.
4345+#
4346+# This program is distributed in the hope that it will be useful,
4347+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4348+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4349+# GNU Affero General Public License for more details.
4350+#
4351+# You should have received a copy of the GNU Affero General Public License
4352+# along with this program. If not, see <http://www.gnu.org/licenses/>.
4353+#
4354+##############################################################################
4355+import sys
4356+import traceback
4357+
4358+import psycopg2
4359+
4360+from openerp.tools.translate import _
4361+import datetime
4362+from openerp.osv.orm import Model
4363+from openerp.osv import fields, osv
4364+from parser import new_bank_statement_parser
4365+
4366+
4367+class AccountStatementProfil(Model):
4368+ _inherit = "account.statement.profile"
4369+
4370+ def get_import_type_selection(self, cr, uid, context=None):
4371+ """
4372+ Has to be inherited to add parser
4373+ """
4374+ return [('generic_csvxls_so', 'Generic .csv/.xls based on SO Name')]
4375+
4376+ _columns = {
4377+ 'launch_import_completion': fields.boolean(
4378+ "Launch completion after import",
4379+ help="Tic that box to automatically launch the completion "
4380+ "on each imported file using this profile."),
4381+ 'last_import_date': fields.datetime("Last Import Date"),
4382+ # we remove deprecated as it floods logs in standard/warning level sob...
4383+ 'rec_log': fields.text('log', readonly=True), # Deprecated
4384+ 'import_type': fields.selection(
4385+ get_import_type_selection,
4386+ 'Type of import',
4387+ required=True,
4388+ help="Choose here the method by which you want to import bank"
4389+ "statement for this profile."),
4390+
4391+ }
4392+
4393+ def write_logs_after_import(self, cr, uid, ids, statement_id, num_lines, context):
4394+ """
4395+ Write the log in the logger
4396+
4397+ :param int/long statement_id: ID of the concerned account.bank.statement
4398+ :param int/long num_lines: Number of line that have been parsed
4399+ :return: True
4400+ """
4401+ self.message_post(cr,
4402+ uid,
4403+ ids,
4404+ body=_('Statement ID %s have been imported with %s lines.') %
4405+ (statement_id, num_lines),
4406+ context=context)
4407+ return True
4408+
4409+ def prepare_global_commission_line_vals(
4410+ self, cr, uid, parser, result_row_list, profile, statement_id, context):
4411+ """
4412+ Prepare the global commission line if there is one. The global
4413+ commission is computed by by calling the get_st_line_commision
4414+ of the parser. Feel free to override the method to compute
4415+ your own commission line from the result_row_list.
4416+
4417+ :param: browse_record of the current parser
4418+ :param: result_row_list: [{'key':value}]
4419+ :param: profile: browserecord of account.statement.profile
4420+ :param: statement_id: int/long of the current importing statement ID
4421+ :param: context: global context
4422+ return: dict of vals that will be passed to create method of statement line.
4423+ """
4424+ comm_values = False
4425+ if parser.get_st_line_commision():
4426+ partner_id = profile.partner_id and profile.partner_id.id or False
4427+ commission_account_id = profile.commission_account_id and profile.commission_account_id.id or False
4428+ commission_analytic_id = profile.commission_analytic_id and profile.commission_analytic_id.id or False
4429+ comm_values = {
4430+ 'name': 'IN ' + _('Commission line'),
4431+ 'date': datetime.datetime.now().date(),
4432+ 'amount': parser.get_st_line_commision(),
4433+ 'partner_id': partner_id,
4434+ 'type': 'general',
4435+ 'statement_id': statement_id,
4436+ 'account_id': commission_account_id,
4437+ 'ref': 'commission',
4438+ 'analytic_account_id': commission_analytic_id,
4439+ # !! We set the already_completed so auto-completion will not update those values!
4440+ 'already_completed': True,
4441+ }
4442+ return comm_values
4443+
4444+ def prepare_statetement_lines_vals(
4445+ self, cr, uid, parser_vals, account_payable, account_receivable,
4446+ statement_id, context):
4447+ """
4448+ Hook to build the values of a line from the parser returned values. At
4449+ least it fullfill the statement_id and account_id. Overide it to add your
4450+ own completion if needed.
4451+
4452+ :param dict of vals from parser for account.bank.statement.line (called by
4453+ parser.get_st_line_vals)
4454+ :param int/long account_payable: ID of the receivable account to use
4455+ :param int/long account_receivable: ID of the payable account to use
4456+ :param int/long statement_id: ID of the concerned account.bank.statement
4457+ :return: dict of vals that will be passed to create method of statement line.
4458+ """
4459+ statement_obj = self.pool.get('account.bank.statement')
4460+ values = parser_vals
4461+ values['statement_id'] = statement_id
4462+ values['account_id'] = statement_obj.get_account_for_counterpart(cr,
4463+ uid,
4464+ parser_vals['amount'],
4465+ account_receivable,
4466+ account_payable)
4467+
4468+ date = values.get('date')
4469+ period_memoizer = context.get('period_memoizer')
4470+ if not period_memoizer:
4471+ period_memoizer = {}
4472+ context['period_memoizer'] = period_memoizer
4473+ if period_memoizer.get(date):
4474+ values['period_id'] = period_memoizer[date]
4475+ else:
4476+ # This is awfully slow...
4477+ periods = self.pool.get('account.period').find(cr, uid,
4478+ dt=values.get('date'),
4479+ context=context)
4480+ values['period_id'] = periods[0]
4481+ period_memoizer[date] = periods[0]
4482+ values['type'] = 'general'
4483+ return values
4484+
4485+ def statement_import(self, cr, uid, ids, profile_id, file_stream, ftype="csv", context=None):
4486+ """
4487+ Create a bank statement with the given profile and parser. It will fullfill the bank statement
4488+ with the values of the file providen, but will not complete data (like finding the partner, or
4489+ the right account). This will be done in a second step with the completion rules.
4490+ It will also create the commission line if it apply and record the providen file as
4491+ an attachement of the bank statement.
4492+
4493+ :param int/long profile_id: ID of the profile used to import the file
4494+ :param filebuffer file_stream: binary of the providen file
4495+ :param char: ftype represent the file exstension (csv by default)
4496+ :return: ID of the created account.bank.statemênt
4497+ """
4498+ statement_obj = self.pool.get('account.bank.statement')
4499+ statement_line_obj = self.pool.get('account.bank.statement.line')
4500+ attachment_obj = self.pool.get('ir.attachment')
4501+ prof_obj = self.pool.get("account.statement.profile")
4502+ if not profile_id:
4503+ raise osv.except_osv(_("No Profile!"),
4504+ _("You must provide a valid profile to import a bank statement!"))
4505+ prof = prof_obj.browse(cr, uid, profile_id, context=context)
4506+
4507+ parser = new_bank_statement_parser(prof.import_type, ftype=ftype)
4508+ result_row_list = parser.parse(file_stream)
4509+ # Check all key are present in account.bank.statement.line!!
4510+ if not result_row_list:
4511+ raise osv.except_osv(_("Nothing to import"),
4512+ _("The file is empty"))
4513+ parsed_cols = parser.get_st_line_vals(result_row_list[0]).keys()
4514+ for col in parsed_cols:
4515+ if col not in statement_line_obj._columns:
4516+ raise osv.except_osv(_("Missing column!"),
4517+ _("Column %s you try to import is not "
4518+ "present in the bank statement line!") % col)
4519+
4520+ statement_id = statement_obj.create(cr, uid,
4521+ {'profile_id': prof.id},
4522+ context=context)
4523+ if prof.receivable_account_id:
4524+ account_receivable = account_payable = prof.receivable_account_id.id
4525+ else:
4526+ account_receivable, account_payable = statement_obj.get_default_pay_receiv_accounts(
4527+ cr, uid, context)
4528+ try:
4529+ # Record every line in the bank statement and compute the global commission
4530+ # based on the commission_amount column
4531+ statement_store = []
4532+ for line in result_row_list:
4533+ parser_vals = parser.get_st_line_vals(line)
4534+ values = self.prepare_statetement_lines_vals(cr, uid, parser_vals, account_payable,
4535+ account_receivable, statement_id, context)
4536+ statement_store.append(values)
4537+ # Hack to bypass ORM poor perfomance. Sob...
4538+ statement_line_obj._insert_lines(cr, uid, statement_store, context=context)
4539+
4540+ # Build and create the global commission line for the whole statement
4541+ comm_vals = self.prepare_global_commission_line_vals(cr, uid, parser, result_row_list,
4542+ prof, statement_id, context)
4543+ if comm_vals:
4544+ statement_line_obj.create(cr, uid, comm_vals, context=context)
4545+ else:
4546+ # Trigger store field computation if someone has better idea
4547+ start_bal = statement_obj.read(cr, uid, statement_id,
4548+ ['balance_start'],
4549+ context=context)
4550+ start_bal = start_bal['balance_start']
4551+ statement_obj.write(cr, uid, [statement_id],
4552+ {'balance_start': start_bal})
4553+
4554+ attachment_obj.create(cr,
4555+ uid,
4556+ {'name': 'statement file',
4557+ 'datas': file_stream,
4558+ 'datas_fname': "%s.%s" % (
4559+ datetime.datetime.now().date(),
4560+ ftype),
4561+ 'res_model': 'account.bank.statement',
4562+ 'res_id': statement_id},
4563+ context=context)
4564+
4565+ # If user ask to launch completion at end of import, do it!
4566+ if prof.launch_import_completion:
4567+ statement_obj.button_auto_completion(cr, uid, [statement_id], context)
4568+
4569+ # Write the needed log infos on profile
4570+ self.write_logs_after_import(cr, uid, prof.id,
4571+ statement_id,
4572+ len(result_row_list),
4573+ context)
4574+
4575+ except Exception:
4576+ statement_obj.unlink(cr, uid, [statement_id], context=context)
4577+ error_type, error_value, trbk = sys.exc_info()
4578+ st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value)
4579+ st += ''.join(traceback.format_tb(trbk, 30))
4580+ raise osv.except_osv(_("Statement import error"),
4581+ _("The statement cannot be created: %s") % st)
4582+ return statement_id
4583+
4584+
4585+class AccountStatementLine(Model):
4586+ """
4587+ Add sparse field on the statement line to allow to store all the
4588+ bank infos that are given by an office. In this basic sample case
4589+ it concern only commission_amount.
4590+ """
4591+ _inherit = "account.bank.statement.line"
4592+
4593+ def _get_available_columns(self, statement_store):
4594+ """Return writeable by SQL columns"""
4595+ statement_line_obj = self.pool['account.bank.statement.line']
4596+ model_cols = statement_line_obj._columns
4597+ avail = [k for k, col in model_cols.iteritems() if not hasattr(col, '_fnct')]
4598+ keys = [k for k in statement_store[0].keys() if k in avail]
4599+ keys.sort()
4600+ return keys
4601+
4602+ def _insert_lines(self, cr, uid, statement_store, context=None):
4603+ """ Do raw insert into database because ORM is awfully slow
4604+ when doing batch write. It is a shame that batch function
4605+ does not exist"""
4606+ statement_line_obj = self.pool['account.bank.statement.line']
4607+ statement_line_obj.check_access_rule(cr, uid, [], 'create')
4608+ statement_line_obj.check_access_rights(cr, uid, 'create', raise_exception=True)
4609+ cols = self._get_available_columns(statement_store)
4610+ tmp_vals = (', '.join(cols), ', '.join(['%%(%s)s' % i for i in cols]))
4611+ sql = "INSERT INTO account_bank_statement_line (%s) VALUES (%s);" % tmp_vals
4612+ try:
4613+ cr.executemany(sql, tuple(statement_store))
4614+ except psycopg2.Error as sql_err:
4615+ cr.rollback()
4616+ raise osv.except_osv(_("ORM bypass error"),
4617+ sql_err.pgerror)
4618+
4619+ def _update_line(self, cr, uid, vals, context=None):
4620+ """ Do raw update into database because ORM is awfully slow
4621+ when cheking security."""
4622+ cols = self._get_available_columns([vals])
4623+ tmp_vals = (', '.join(['%s = %%(%s)s' % (i, i) for i in cols]))
4624+ sql = "UPDATE account_bank_statement_line SET %s where id = %%(id)s;" % tmp_vals
4625+ try:
4626+ cr.execute(sql, vals)
4627+ except psycopg2.Error as sql_err:
4628+ cr.rollback()
4629+ raise osv.except_osv(_("ORM bypass error"),
4630+ sql_err.pgerror)
4631+
4632+ _columns = {
4633+ 'commission_amount': fields.sparse(
4634+ type='float',
4635+ string='Line Commission Amount',
4636+ serialization_field='additionnal_bank_fields'),
4637+ }
4638
4639=== added file 'account_statement_base_import/statement_view.xml'
4640--- account_statement_base_import/statement_view.xml 1970-01-01 00:00:00 +0000
4641+++ account_statement_base_import/statement_view.xml 2013-07-17 14:55:41 +0000
4642@@ -0,0 +1,46 @@
4643+<?xml version="1.0" encoding="utf-8"?>
4644+<openerp>
4645+<data>
4646+
4647+
4648+ <record id="statement_importer_view_form" model="ir.ui.view">
4649+ <field name="name">account.statement.profile.view</field>
4650+ <field name="model">account.statement.profile</field>
4651+ <field name="inherit_id" ref="account_statement_ext.statement_importer_view_form"/>
4652+ <field name="type">form</field>
4653+ <field name="arch" type="xml">
4654+ <field name="bank_statement_prefix" position="after">
4655+ <separator colspan="4" string="Import related infos"/>
4656+ <field name="launch_import_completion"/>
4657+ <field name="last_import_date"/>
4658+ <field name="import_type"/>
4659+ <button name="%(account_statement_base_import.statement_importer_action)d"
4660+ string="Import Bank Statement"
4661+ type="action" icon="gtk-ok"
4662+ colspan = "2"/>
4663+ <group attrs="{'invisible': [('rec_log', '=', False)]}">
4664+ <separator colspan="4" string="Historical Import Logs"/>
4665+ <field name="rec_log" colspan="4" nolabel="1" />
4666+ </group>
4667+ </field>
4668+ </field>
4669+ </record>
4670+
4671+ <record id="bank_statement_view_form" model="ir.ui.view">
4672+ <field name="name">account_bank_statement.bank_statement.view_form</field>
4673+ <field name="model">account.bank.statement</field>
4674+ <field name="inherit_id" ref="account_statement_base_completion.bank_statement_view_form" />
4675+ <field name="type">form</field>
4676+ <field eval="20" name="priority"/>
4677+ <field name="arch" type="xml">
4678+ <data>
4679+ <xpath expr="/form/sheet/notebook/page/field[@name='line_ids']/form/group/field[@name='label']" position="after">
4680+ <field name="commission_amount" />
4681+ </xpath>
4682+ </data>
4683+ </field>
4684+ </record>
4685+
4686+
4687+</data>
4688+</openerp>
4689
4690=== added directory 'account_statement_base_import/wizard'
4691=== added file 'account_statement_base_import/wizard/__init__.py'
4692--- account_statement_base_import/wizard/__init__.py 1970-01-01 00:00:00 +0000
4693+++ account_statement_base_import/wizard/__init__.py 2013-07-17 14:55:41 +0000
4694@@ -0,0 +1,20 @@
4695+# -*- coding: utf-8 -*-
4696+##############################################################################
4697+#
4698+# Author Nicolas Bessi, Joel Grand-Guillaume. Copyright Camptocamp SA
4699+#
4700+# This program is free software: you can redistribute it and/or modify
4701+# it under the terms of the GNU General Public License as published by
4702+# the Free Software Foundation, either version 3 of the License, or
4703+# (at your option) any later version.
4704+#
4705+# This program is distributed in the hope that it will be useful,
4706+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4707+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4708+# GNU General Public License for more details.
4709+#
4710+# You should have received a copy of the GNU General Public License
4711+# along with this program. If not, see <http://www.gnu.org/licenses/>.
4712+#
4713+##############################################################################
4714+import import_statement
4715
4716=== added file 'account_statement_base_import/wizard/import_statement.py'
4717--- account_statement_base_import/wizard/import_statement.py 1970-01-01 00:00:00 +0000
4718+++ account_statement_base_import/wizard/import_statement.py 2013-07-17 14:55:41 +0000
4719@@ -0,0 +1,122 @@
4720+# -*- coding: utf-8 -*-
4721+##############################################################################
4722+#
4723+# Author: Nicolas Bessi, Joel Grand-Guillaume
4724+# Copyright 2011-2012 Camptocamp SA
4725+#
4726+# This program is free software: you can redistribute it and/or modify
4727+# it under the terms of the GNU Affero General Public License as
4728+# published by the Free Software Foundation, either version 3 of the
4729+# License, or (at your option) any later version.
4730+#
4731+# This program is distributed in the hope that it will be useful,
4732+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4733+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4734+# GNU Affero General Public License for more details.
4735+#
4736+# You should have received a copy of the GNU Affero General Public License
4737+# along with this program. If not, see <http://www.gnu.org/licenses/>.
4738+#
4739+##############################################################################
4740+
4741+"""
4742+Wizard to import financial institute date in bank statement
4743+"""
4744+
4745+from openerp.osv import orm, fields
4746+
4747+from openerp.tools.translate import _
4748+import os
4749+
4750+
4751+class CreditPartnerStatementImporter(orm.TransientModel):
4752+ _name = "credit.statement.import"
4753+
4754+ def default_get(self, cr, uid, fields, context=None):
4755+ if context is None:
4756+ context = {}
4757+ res = {}
4758+ if (context.get('active_model', False) == 'account.statement.profile' and
4759+ context.get('active_ids', False)):
4760+ ids = context['active_ids']
4761+ assert len(ids) == 1, 'You cannot use this on more than one profile !'
4762+ res['profile_id'] = ids[0]
4763+ other_vals = self.onchange_profile_id(cr, uid, [], res['profile_id'], context=context)
4764+ res.update(other_vals.get('value', {}))
4765+ return res
4766+
4767+ _columns = {
4768+ 'profile_id': fields.many2one('account.statement.profile',
4769+ 'Import configuration parameter',
4770+ required=True),
4771+ 'input_statement': fields.binary('Statement file', required=True),
4772+ 'partner_id': fields.many2one('res.partner',
4773+ 'Credit insitute partner'),
4774+ 'journal_id': fields.many2one('account.journal',
4775+ 'Financial journal to use transaction'),
4776+ 'file_name': fields.char('File Name', size=128),
4777+ 'commission_account_id': fields.many2one('account.account',
4778+ 'Commission account'),
4779+ 'commission_analytic_id': fields.many2one('account.analytic.account',
4780+ 'Commission analytic account'),
4781+ 'receivable_account_id': fields.many2one('account.account',
4782+ 'Force Receivable/Payable Account'),
4783+ 'force_partner_on_bank': fields.boolean(
4784+ 'Force partner on bank move',
4785+ help="Tic that box if you want to use the credit insitute partner "
4786+ "in the counterpart of the treasury/banking move."),
4787+ 'balance_check': fields.boolean(
4788+ 'Balance check',
4789+ help="Tic that box if you want OpenERP to control the "
4790+ "start/end balance before confirming a bank statement. "
4791+ "If don't ticked, no balance control will be done."),
4792+ }
4793+
4794+ def onchange_profile_id(self, cr, uid, ids, profile_id, context=None):
4795+ res = {}
4796+ if profile_id:
4797+ c = self.pool.get("account.statement.profile").browse(
4798+ cr, uid, profile_id, context=context)
4799+ res = {'value':
4800+ {'partner_id': c.partner_id and c.partner_id.id or False,
4801+ 'journal_id': c.journal_id and c.journal_id.id or False,
4802+ 'commission_account_id':
4803+ c.commission_account_id and c.commission_account_id.id or False,
4804+ 'receivable_account_id': c.receivable_account_id and c.receivable_account_id.id or False,
4805+ 'commission_a': c.commission_analytic_id and c.commission_analytic_id.id or False,
4806+ 'force_partner_on_bank': c.force_partner_on_bank,
4807+ 'balance_check': c.balance_check,
4808+ }
4809+ }
4810+ return res
4811+
4812+ def _check_extension(self, filename):
4813+ (__, ftype) = os.path.splitext(filename)
4814+ if not ftype:
4815+ #We do not use osv exception we do not want to have it logged
4816+ raise Exception(_('Please use a file with an extention'))
4817+ return ftype
4818+
4819+ def import_statement(self, cr, uid, req_id, context=None):
4820+ """This Function import credit card agency statement"""
4821+ context = context or {}
4822+ if isinstance(req_id, list):
4823+ req_id = req_id[0]
4824+ importer = self.browse(cr, uid, req_id, context)
4825+ ftype = self._check_extension(importer.file_name)
4826+ sid = self.pool.get(
4827+ 'account.statement.profile').statement_import(
4828+ cr,
4829+ uid,
4830+ False,
4831+ importer.profile_id.id,
4832+ importer.input_statement,
4833+ ftype.replace('.', ''),
4834+ context=context
4835+ )
4836+ model_obj = self.pool.get('ir.model.data')
4837+ action_obj = self.pool.get('ir.actions.act_window')
4838+ action_id = model_obj.get_object_reference(cr, uid, 'account', 'action_bank_statement_tree')[1]
4839+ res = action_obj.read(cr, uid, action_id)
4840+ res['domain'] = res['domain'][:-1] + ",('id', '=', %d)]" % sid
4841+ return res
4842
4843=== added file 'account_statement_base_import/wizard/import_statement_view.xml'
4844--- account_statement_base_import/wizard/import_statement_view.xml 1970-01-01 00:00:00 +0000
4845+++ account_statement_base_import/wizard/import_statement_view.xml 2013-07-17 14:55:41 +0000
4846@@ -0,0 +1,44 @@
4847+<?xml version="1.0" encoding="utf-8"?>
4848+<openerp>
4849+ <data>
4850+ <record id="statement_importer_view" model="ir.ui.view">
4851+ <field name="name">credit.statement.import.config.view</field>
4852+ <field name="model">credit.statement.import</field>
4853+ <field name="type">form</field>
4854+ <field name="arch" type="xml">
4855+ <form string="Import statement">
4856+ <group colspan="4" >
4857+ <field name="profile_id" on_change="onchange_profile_id(profile_id)"/>
4858+ <field name="input_statement" filename="file_name" colspan="2"/>
4859+ <field name="file_name" colspan="2" invisible="1"/>
4860+ <separator string="Import Parameters Summary" colspan="4"/>
4861+ <field name="partner_id" readonly="1"/>
4862+ <field name="journal_id" readonly="1"/>
4863+ <field name="commission_account_id" readonly="1"/>
4864+ <field name="commission_analytic_id" readonly="1"/>
4865+ <field name="receivable_account_id" readonly="1"/>
4866+ <field name="force_partner_on_bank" readonly="1"/>
4867+ <field name="balance_check" readonly="1"/>
4868+ </group>
4869+ <separator string="" colspan="4"/>
4870+ <group colspan="4" col="6">
4871+ <button icon="gtk-cancel" special="cancel" string="Cancel"/>
4872+ <button icon="gtk-ok" name="import_statement" string="Import statement" type="object"/>
4873+ </group>
4874+ </form>
4875+ </field>
4876+ </record>
4877+
4878+ <record id="statement_importer_action" model="ir.actions.act_window">
4879+ <field name="name">Import statement</field>
4880+ <field name="res_model">credit.statement.import</field>
4881+ <field name="view_type">form</field>
4882+ <field name="view_mode">tree,form</field>
4883+ <field name="view_id" ref="statement_importer_view"/>
4884+ <field name="target">new</field>
4885+ </record>
4886+
4887+ <menuitem id="statement_importer_menu" name="Import Bank Statement" action="statement_importer_action" parent="account.menu_finance_bank_and_cash"/>
4888+
4889+ </data>
4890+</openerp>
4891
4892=== added directory 'account_statement_completion_voucher'
4893=== added file 'account_statement_completion_voucher/__init__.py'
4894--- account_statement_completion_voucher/__init__.py 1970-01-01 00:00:00 +0000
4895+++ account_statement_completion_voucher/__init__.py 2013-07-17 14:55:41 +0000
4896@@ -0,0 +1,20 @@
4897+# -*- coding: utf-8 -*-
4898+##############################################################################
4899+#
4900+# Author: Joel Grand-Guillaume
4901+# Copyright 2011-2012 Camptocamp SA
4902+#
4903+# This program is free software: you can redistribute it and/or modify
4904+# it under the terms of the GNU Affero General Public License as
4905+# published by the Free Software Foundation, either version 3 of the
4906+# License, or (at your option) any later version.
4907+#
4908+# This program is distributed in the hope that it will be useful,
4909+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4910+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4911+# GNU Affero General Public License for more details.
4912+#
4913+# You should have received a copy of the GNU Affero General Public License
4914+# along with this program. If not, see <http://www.gnu.org/licenses/>.
4915+#
4916+##############################################################################
4917
4918=== added file 'account_statement_completion_voucher/__openerp__.py'
4919--- account_statement_completion_voucher/__openerp__.py 1970-01-01 00:00:00 +0000
4920+++ account_statement_completion_voucher/__openerp__.py 2013-07-17 14:55:41 +0000
4921@@ -0,0 +1,46 @@
4922+# -*- coding: utf-8 -*-
4923+##############################################################################
4924+#
4925+# Author: Joel Grand-Guillaume
4926+# Copyright 2011-2012 Camptocamp SA
4927+#
4928+# This program is free software: you can redistribute it and/or modify
4929+# it under the terms of the GNU Affero General Public License as
4930+# published by the Free Software Foundation, either version 3 of the
4931+# License, or (at your option) any later version.
4932+#
4933+# This program is distributed in the hope that it will be useful,
4934+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4935+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4936+# GNU Affero General Public License for more details.
4937+#
4938+# You should have received a copy of the GNU Affero General Public License
4939+# along with this program. If not, see <http://www.gnu.org/licenses/>.
4940+#
4941+##############################################################################
4942+
4943+{'name': "Bank statement extension with voucher",
4944+ 'version': '1.0',
4945+ 'author': 'Camptocamp',
4946+ 'maintainer': 'Camptocamp',
4947+ 'category': 'Finance',
4948+ 'complexity': 'normal',
4949+ 'depends': [
4950+ 'account_statement_base_completion',
4951+ 'account_voucher'
4952+ ],
4953+ 'description': """
4954+ This module is only needed when using account_statement_base_completion with voucher in order adapt the view correctly.
4955+ """,
4956+ 'website': 'http://www.camptocamp.com',
4957+ 'init_xml': [],
4958+ 'update_xml': [
4959+ "statement_view.xml",
4960+ ],
4961+ 'demo_xml': [],
4962+ 'test': [],
4963+ 'installable': False,
4964+ 'images': [],
4965+ 'auto_install': False,
4966+ 'license': 'AGPL-3',
4967+}
4968
4969=== added file 'account_statement_completion_voucher/statement_view.xml'
4970--- account_statement_completion_voucher/statement_view.xml 1970-01-01 00:00:00 +0000
4971+++ account_statement_completion_voucher/statement_view.xml 2013-07-17 14:55:41 +0000
4972@@ -0,0 +1,21 @@
4973+<?xml version="1.0" encoding="utf-8"?>
4974+<openerp>
4975+<data>
4976+
4977+ <!-- Override what we have in account_statement_base_completion to replace the one in the voucher -->
4978+ <!-- <record id="account_statement_base_completion.bank_statement_view_form2" model="ir.ui.view">
4979+ <field name="name">account_bank_statement_import_base.bank_statement.auto_cmpl</field>
4980+ <field name="model">account.bank.statement</field>
4981+ <field name="inherit_id" ref="account_voucher.view_bank_statement_tree_voucher" />
4982+ <field name="type">form</field>
4983+ <field name="arch" type="xml">
4984+ <data>
4985+ <xpath expr="/form/sheet/notebook/page/field[@name='line_ids']/tree/field[@name='voucher_id']" position="after">
4986+ <field name="already_completed" />
4987+ </xpath>
4988+ </data>
4989+ </field>
4990+ </record> -->
4991+
4992+</data>
4993+</openerp>
4994
4995=== added directory 'account_statement_ext'
4996=== added file 'account_statement_ext/__init__.py'
4997--- account_statement_ext/__init__.py 1970-01-01 00:00:00 +0000
4998+++ account_statement_ext/__init__.py 2013-07-17 14:55:41 +0000
4999@@ -0,0 +1,25 @@
5000+# -*- coding: utf-8 -*-
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches