Merge lp:~camptocamp/banking-addons/bank-statement-reconcile_vre into lp:banking-addons/bank-statement-reconcile-61

Proposed by Guewen Baconnier @ Camptocamp
Status: Superseded
Proposed branch: lp:~camptocamp/banking-addons/bank-statement-reconcile_vre
Merge into: lp:banking-addons/bank-statement-reconcile-61
Diff against target: 6036 lines (+2363/-1302) (has conflicts)
67 files modified
account_advanced_reconcile/__openerp__.py (+10/-14)
account_advanced_reconcile/advanced_reconciliation.py (+5/-7)
account_advanced_reconcile/base_advanced_reconciliation.py (+22/-24)
account_advanced_reconcile/easy_reconcile.py (+5/-6)
account_advanced_reconcile/easy_reconcile_view.xml (+1/-2)
account_advanced_reconcile/i18n/fr.po (+15/-14)
account_easy_reconcile/__openerp__.py (+16/-10)
account_easy_reconcile/base_reconciliation.py (+24/-23)
account_easy_reconcile/easy_reconcile.py (+78/-41)
account_easy_reconcile/easy_reconcile.xml (+71/-51)
account_easy_reconcile/easy_reconcile_history.py (+7/-0)
account_easy_reconcile/easy_reconcile_history_view.xml (+24/-24)
account_easy_reconcile/i18n/fr.po (+251/-47)
account_easy_reconcile/security/ir_rule.xml (+25/-0)
account_easy_reconcile/simple_reconciliation.py (+3/-5)
account_statement_base_completion/__init__.py (+1/-1)
account_statement_base_completion/__openerp__.py (+5/-2)
account_statement_base_completion/data.xml (+8/-3)
account_statement_base_completion/partner.py (+7/-7)
account_statement_base_completion/statement.py (+348/-209)
account_statement_base_completion/statement_view.xml (+7/-7)
account_statement_base_import/__init__.py (+1/-1)
account_statement_base_import/__openerp__.py (+8/-6)
account_statement_base_import/parser/__init__.py (+1/-1)
account_statement_base_import/parser/file_parser.py (+74/-36)
account_statement_base_import/parser/generic_file_parser.py (+21/-21)
account_statement_base_import/parser/parser.py (+26/-25)
account_statement_base_import/statement.py (+176/-108)
account_statement_base_import/statement_view.xml (+7/-5)
account_statement_base_import/wizard/import_statement.py (+46/-42)
account_statement_completion_voucher/__init__.py (+0/-1)
account_statement_completion_voucher/__openerp__.py (+7/-5)
account_statement_completion_voucher/statement_view.xml (+3/-3)
account_statement_ext/__init__.py (+2/-1)
account_statement_ext/__openerp__.py (+16/-10)
account_statement_ext/account.py (+6/-8)
account_statement_ext/i18n/fr.po (+2/-2)
account_statement_ext/report/__init__.py (+2/-7)
account_statement_ext/report/bank_statement_report.py (+19/-18)
account_statement_ext/security/ir.model.access.csv (+1/-1)
account_statement_ext/security/ir_rule.xml (+10/-0)
account_statement_ext/statement.py (+407/-342)
account_statement_ext/statement_view.xml (+78/-3)
account_statement_ext/voucher.py (+49/-0)
account_statement_ext_voucher/__init__.py (+0/-2)
account_statement_ext_voucher/__openerp__.py (+13/-6)
account_statement_ext_voucher/statement_voucher.py (+4/-3)
account_statement_ext_voucher/statement_voucher_view.xml (+0/-17)
account_statement_transactionid_completion/__openerp__.py (+5/-3)
account_statement_transactionid_completion/statement.py (+40/-36)
account_statement_transactionid_completion/statement_view.xml (+1/-1)
account_statement_transactionid_import/__init__.py (+1/-1)
account_statement_transactionid_import/__openerp__.py (+10/-8)
account_statement_transactionid_import/parser/__init__.py (+1/-3)
account_statement_transactionid_import/parser/transactionid_file_parser.py (+17/-30)
account_statement_transactionid_import/statement.py (+16/-12)
base_transaction_id/__openerp__.py (+19/-8)
base_transaction_id/invoice.py (+8/-7)
base_transaction_id/sale.py (+14/-10)
base_transaction_id/stock.py (+16/-12)
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)
Text conflict in account_easy_reconcile/__openerp__.py
Text conflict in account_easy_reconcile/easy_reconcile.py
Text conflict in account_statement_ext/__openerp__.py
Text conflict in account_statement_ext/statement.py
Text conflict in account_statement_ext/statement_view.xml
To merge this branch: bzr merge lp:~camptocamp/banking-addons/bank-statement-reconcile_vre
Reviewer Review Type Date Requested Status
Banking Addons Core Editors Pending
Review via email: mp+168363@code.launchpad.net

This proposal supersedes a proposal from 2013-06-10.

This proposal has been superseded by a proposal from 2013-06-10.

Description of the change

This fix prevent account_advanced_reconcile to crash if ref in the move is not set

To post a comment you must log in.

Unmerged revisions

93. By Vincent Renaville@camptocamp

[FIX] prevent crash if move ref is null

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

84. By Nicolas Bessi - Camptocamp

[MRG] Adds two add-ons that prevent voucher to interfer with the statement ext related add-ons flow

Preview Diff

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

Subscribers

People subscribed via source and target branches