Merge lp:~camptocamp-reviewer/banking-addons/fix-type-and-account-in-bk-st-line into lp:banking-addons/bank-statement-reconcile-70

Proposed by Joël Grand-Guillaume @ camptocamp
Status: Superseded
Proposed branch: lp:~camptocamp-reviewer/banking-addons/fix-type-and-account-in-bk-st-line
Merge into: lp:banking-addons/bank-statement-reconcile-70
Diff against target: 1355 lines (+499/-324)
6 files modified
account_statement_base_completion/__openerp__.py (+4/-0)
account_statement_base_completion/statement.py (+201/-176)
account_statement_base_import/parser/generic_file_parser.py (+7/-8)
account_statement_base_import/statement.py (+117/-49)
account_statement_ext/__openerp__.py (+4/-7)
account_statement_ext/statement.py (+166/-84)
To merge this branch: bzr merge lp:~camptocamp-reviewer/banking-addons/fix-type-and-account-in-bk-st-line
Reviewer Review Type Date Requested Status
Vincent Renaville@camptocamp (community) Needs Fixing
Nicolas Bessi - Camptocamp Pending
Review via email: mp+157294@code.launchpad.net

This proposal has been superseded by a proposal from 2013-04-24.

Commit message

As we saw that we have a difference in bank statement line type and account finding using manual entry and importation/completion method, we make this refactor implementing a method to determine the type following this rule:
 - 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

Description of the change

As we saw that we have a difference in bank statement line type and account finding using manual entry and importation/completion method, we make this refactor implementing a method to determine the type following this rule:
 - 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

To post a comment you must log in.
91. By Nicolas Bessi - Camptocamp

[PEP8]

92. By Nicolas Bessi - Camptocamp

[IMP] reactor of type computing + support of use case when partner is customer and supplier or none

93. By Nicolas Bessi - Camptocamp

[RM] dead comment

94. By Nicolas Bessi - Camptocamp

[FIX] pep8 + invalid syntax + undefine value.

95. By Nicolas Bessi - Camptocamp

[FIX] coding declaration

96. By Nicolas Bessi - Camptocamp

[PEP8]

97. By Nicolas Bessi - Camptocamp

[IMP] adresses massive import perfomrance by doing direct insert into database

98. By Nicolas Bessi - Camptocamp

[IMP] refactor invoice lookup function + fix the fact that they should lokk account_id on invoice not on partner

99. By Nicolas Bessi - Camptocamp

[IMP] moving insert function to more appropriate class

100. By Nicolas Bessi - Camptocamp

[ADD] refactoring the structure of completion in order to limit the number of browse.
We only tries to alter function that should not be inherited on strandard way of using the addons

101. By Nicolas Bessi - Camptocamp

[FIX] remove deprecated on rec_log as it literraly floods logs in normal/warning level.

102. By Nicolas Bessi - Camptocamp

[FIX] poor satement write function performance

103. By Nicolas Bessi - Camptocamp

[TYPO]

104. By Nicolas Bessi - Camptocamp

[TYPO]

105. By Nicolas Bessi - Camptocamp

[FIX] completion rules + to ensure minimum default and take in account the profile account as master value

106. By Nicolas Bessi - Camptocamp

[RM] debug statement

107. By Nicolas Bessi - Camptocamp

[FIX] auto completion line treated by auto completion != auto completed as if is actually the case

108. By Nicolas Bessi - Camptocamp

[FIX] no value found during completion result in bad SQL"

109. By Nicolas Bessi - Camptocamp

[FIX] typo supplier instead of customer

110. By Nicolas Bessi - Camptocamp

[IMP] ref are striped

111. By Nicolas Bessi - Camptocamp

[REFACTOR] to improve performance by diminishing reads

Revision history for this message
Vincent Renaville@camptocamp (vrenaville-c2c) wrote :

Hello,

just a little typo error at line 23, remove q letter before tag

review: Needs Fixing
112. By Nicolas Bessi - Camptocamp

[FIX] assignement

113. By Nicolas Bessi - Camptocamp

[FIX] error log + type

114. By Nicolas Bessi - Camptocamp

[FIX] complete from invoice does false positive if master account is set on profile

115. By Nicolas Bessi - Camptocamp

[TYPO]

116. By Nicolas Bessi - Camptocamp

[FIX] transaction_id completion to support optimization

117. By Nicolas Bessi - Camptocamp

[FIX] transaction id import styling

118. By Nicolas Bessi - Camptocamp

[FIX] incoherence in type lookup behavior

119. By Nicolas Bessi - Camptocamp

[IMP] completion messages

120. By Nicolas Bessi - Camptocamp

[FIX] by partner name completion rule if statement and wrong variable

121. By Nicolas Bessi - Camptocamp

[FIX] dead if statement

122. By Nicolas Bessi - Camptocamp

[FIX] dead error management code

123. By Nicolas Bessi - Camptocamp

[IMP] return value

124. By Nicolas Bessi - Camptocamp

[IMP] log code cleanup

125. By Nicolas Bessi - Camptocamp

[FIX] comment and forgotten print statement

126. By Nicolas Bessi - Camptocamp

[IMP] no loop when computing sequence

127. By Nicolas Bessi - Camptocamp

[TYPO]

128. By Nicolas Bessi - Camptocamp

[FIX] partner name lookup now respect security rules

129. By Nicolas Bessi - Camptocamp

[TYPO]

130. By Nicolas Bessi - Camptocamp

[FIX] po file

131. By Nicolas Bessi - Camptocamp

[IMP] message logging

132. By Alexandre Fayolle - camptocamp

[IMP] fix a few docstrings

small code improvement in account_statement_transactionid_completion/statement.py AccountStatementCompletionRule.get_from_transaction_id_and_so)

133. By Nicolas Bessi - Camptocamp

[FIX] wrong argument id instead of line['profile_id']

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'account_statement_base_completion/__openerp__.py'
2--- account_statement_base_completion/__openerp__.py 2013-01-10 15:09:55 +0000
3+++ account_statement_base_completion/__openerp__.py 2013-04-24 09:32:28 +0000
4@@ -52,6 +52,10 @@
5
6 You can use it with our account_advanced_reconcile module to automatize the reconciliation process.
7
8+
9+ TODO: The rules that look for invoices to find out the partner should take back the payable / receivable
10+ account from there directly instead of retrieving it from partner properties !
11+
12 """,
13 'website': 'http://www.camptocamp.com',
14 'init_xml': [],
15
16=== modified file 'account_statement_base_completion/statement.py'
17--- account_statement_base_completion/statement.py 2013-03-22 08:31:27 +0000
18+++ account_statement_base_completion/statement.py 2013-04-24 09:32:28 +0000
19@@ -18,15 +18,20 @@
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #
22 ##############################################################################
23+# TODO replace customer supplier by package constant
24+import traceback
25+import sys
26+import logging
27+
28 from collections import defaultdict
29 import re
30-
31 from tools.translate import _
32-from openerp.osv.orm import Model, fields
33+from openerp.osv import osv, orm, fields
34 from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
35 from operator import attrgetter
36 import datetime
37
38+_logger = logging.getLogger(__name__)
39
40 class ErrorTooManyPartner(Exception):
41 """
42@@ -40,7 +45,7 @@
43 return repr(self.value)
44
45
46-class AccountStatementProfil(Model):
47+class AccountStatementProfil(orm.Model):
48 """
49 Extend the class to add rules per profile that will match at least the partner,
50 but it could also be used to match other values as well.
51@@ -61,12 +66,21 @@
52 rel='as_rul_st_prof_rel'),
53 }
54
55- def find_values_from_rules(self, cr, uid, id, line_id, context=None):
56+ def _get_callable(self, cr, uid, profile, context=None):
57+ if isinstance(profile, (int, long)):
58+ prof = self.browse(cr, uid, profile, context=context)
59+ else:
60+ prof = profile
61+ # We need to respect the sequence order
62+ sorted_array = sorted(prof.rule_ids, key=attrgetter('sequence'))
63+ return tuple((x.function_to_call for x in sorted_array))
64+
65+ def _find_values_from_rules(self, cr, uid, calls, line, context=None):
66 """
67 This method will execute all related rules, in their sequence order,
68 to retrieve all the values returned by the first rules that will match.
69
70- :param int/long line_id: id of the concerned account.bank.statement.line
71+ :param int/long st_line: read of the concerned account.bank.statement.line
72 :return:
73 A dict of value that can be passed directly to the write method of
74 the statement line or {}
75@@ -77,20 +91,20 @@
76 """
77 if context is None:
78 context = {}
79- res = {}
80+ if not calls:
81+ calls = self._get_callable(cr, uid, id, context=context)
82 rule_obj = self.pool.get('account.statement.completion.rule')
83- profile = self.browse(cr, uid, id, context=context)
84- # We need to respect the sequence order
85- sorted_array = sorted(profile.rule_ids, key=attrgetter('sequence'))
86- for rule in sorted_array:
87- method_to_call = getattr(rule_obj, rule.function_to_call)
88- result = method_to_call(cr, uid, line_id, context)
89+
90+ for call in calls:
91+ method_to_call = getattr(rule_obj, call)
92+ result = method_to_call(cr, uid, line, context)
93 if result:
94+ result['already_completed'] = True
95 return result
96- return res
97-
98-
99-class AccountStatementCompletionRule(Model):
100+ return None
101+
102+
103+class AccountStatementCompletionRule(orm.Model):
104 """
105 This will represent all the completion method that we can have to
106 fullfill the bank statement lines. You'll be able to extend them in you own module
107@@ -109,12 +123,11 @@
108 List of available methods for rules. Override this to add you own.
109 """
110 return [
111- ('get_from_ref_and_invoice', 'From line reference (based on invoice number)'),
112+ ('get_from_ref_and_invoice', 'From line reference (based on customer invoice number)'),
113 ('get_from_ref_and_supplier_invoice', 'From line reference (based on supplier invoice number)'),
114 ('get_from_ref_and_so', 'From line reference (based on SO number)'),
115 ('get_from_label_and_partner_field', 'From line label (based on partner field)'),
116- ('get_from_label_and_partner_name', 'From line label (based on partner name)'),
117- ]
118+ ('get_from_label_and_partner_name', 'From line label (based on partner name)')]
119
120 _columns = {
121 'sequence': fields.integer('Sequence', help="Lower means parsed first."),
122@@ -126,13 +139,56 @@
123 'function_to_call': fields.selection(_get_functions, 'Method'),
124 }
125
126- def get_from_ref_and_supplier_invoice(self, cr, uid, line_id, context=None):
127+ def _find_invoice(self, cr, uid, st_line, inv_type, context=None):
128+ """Find invoice related to statement line"""
129+ inv_obj = self.pool.get('account.invoice')
130+ if inv_type == 'supplier':
131+ type_domain = ('in_invoice', 'in_refund')
132+ number_field = 'supplier_invoice_number'
133+ elif inv_type == 'customer':
134+ type_domain = ('out_invoice', 'out_refund')
135+ number_field = 'number'
136+ else:
137+ raise osv.except_osv(_('System error'),
138+ _('Invalid invoice type for completion: %') % inv_type)
139+
140+ inv_id = inv_obj.search(cr, uid,
141+ [(number_field, '=', st_line['ref'].strip()),
142+ ('type', 'in', type_domain)],
143+ context=context)
144+ if inv_id:
145+ if len(inv_id) == 1:
146+ inv = inv_obj.browse(cr, uid, inv_id[0], context=context)
147+ else:
148+ raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more '
149+ 'than one partner.') % (st_line['name'], st_line['ref']))
150+ return inv
151+ return False
152+
153+ def _from_invoice(self, cr, uid, line, inv_type, context):
154+ """Populate statement line values"""
155+ if not inv_type in ('supplier', 'customer'):
156+ raise osv.except_osv(_('System error'),
157+ _('Invalid invoice type for completion: %') % inv_type)
158+ res = {}
159+ inv = self._find_invoice(cr, uid, line, inv_type, context=context)
160+ if inv:
161+ res = {'partner_id': inv.partner_id.id,
162+ 'account_id': inv.account_id.id,
163+ 'type': inv_type}
164+ override_acc = line['master_account_id']
165+ if override_acc:
166+ res['account_id'] = override_acc
167+ return res
168+
169+ # Should be private but data are initialised with no update XML
170+ def get_from_ref_and_supplier_invoice(self, cr, uid, line, context=None):
171 """
172 Match the partner based on the invoice supplier invoice number and the reference of the statement
173 line. Then, call the generic get_values_for_line method to complete other values.
174 If more than one partner matched, raise the ErrorTooManyPartner error.
175
176- :param int/long line_id: id of the concerned account.bank.statement.line
177+ :param int/long st_line: read of the concerned account.bank.statement.line
178 :return:
179 A dict of value that can be passed directly to the write method of
180 the statement line or {}
181@@ -141,40 +197,16 @@
182
183 ...}
184 """
185- st_obj = self.pool['account.bank.statement.line']
186- st_line = st_obj.browse(cr, uid, line_id, context=context)
187- res = {}
188- inv_obj = self.pool.get('account.invoice')
189- if st_line:
190- inv_id = inv_obj.search(cr,
191- uid,
192- [('supplier_invoice_number', '=', st_line.ref),
193- ('type', 'in', ('in_invoice', 'in_refund'))],
194- context=context)
195- if inv_id:
196- if len(inv_id) == 1:
197- inv = inv_obj.browse(cr, uid, inv_id[0], context=context)
198- res['partner_id'] = inv.partner_id.id
199- else:
200- raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more '
201- 'than one partner.') % (st_line.name, st_line.ref))
202- st_vals = st_obj.get_values_for_line(cr,
203- uid,
204- profile_id=st_line.statement_id.profile_id.id,
205- partner_id=res.get('partner_id', False),
206- line_type="supplier",
207- amount=st_line.amount,
208- context=context)
209- res.update(st_vals)
210- return res
211+ return self._from_invoice(cr, uid, line, 'supplier', context=context)
212
213- def get_from_ref_and_invoice(self, cr, uid, line_id, context=None):
214+ # Should be private but data are initialised with no update XML
215+ def get_from_ref_and_invoice(self, cr, uid, line, context=None):
216 """
217 Match the partner based on the invoice number and the reference of the statement
218 line. Then, call the generic get_values_for_line method to complete other values.
219 If more than one partner matched, raise the ErrorTooManyPartner error.
220
221- :param int/long line_id: id of the concerned account.bank.statement.line
222+ :param int/long st_line: read of the concerned account.bank.statement.line
223 :return:
224 A dict of value that can be passed directly to the write method of
225 the statement line or {}
226@@ -182,39 +214,16 @@
227 'account_id' : value,
228 ...}
229 """
230- st_obj = self.pool.get('account.bank.statement.line')
231- st_line = st_obj.browse(cr, uid, line_id, context=context)
232- res = {}
233- if st_line:
234- inv_obj = self.pool.get('account.invoice')
235- inv_id = inv_obj.search(cr,
236- uid,
237- [('number', '=', st_line.ref)],
238- context=context)
239- if inv_id:
240- if len(inv_id) == 1:
241- inv = inv_obj.browse(cr, uid, inv_id[0], context=context)
242- res['partner_id'] = inv.partner_id.id
243- else:
244- raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more '
245- 'than one partner.') % (st_line.name, st_line.ref))
246- st_vals = st_obj.get_values_for_line(cr,
247- uid,
248- profile_id=st_line.statement_id.profile_id.id,
249- partner_id=res.get('partner_id', False),
250- line_type=st_line.type,
251- amount=st_line.amount,
252- context=context)
253- res.update(st_vals)
254- return res
255+ return self._from_invoice(cr, uid, line, 'customer', context=context)
256
257- def get_from_ref_and_so(self, cr, uid, line_id, context=None):
258+ # Should be private but data are initialised with no update XML
259+ def get_from_ref_and_so(self, cr, uid, st_line, context=None):
260 """
261 Match the partner based on the SO number and the reference of the statement
262 line. Then, call the generic get_values_for_line method to complete other values.
263 If more than one partner matched, raise the ErrorTooManyPartner error.
264
265- :param int/long line_id: id of the concerned account.bank.statement.line
266+ :param int/long st_line: read of the concerned account.bank.statement.line
267 :return:
268 A dict of value that can be passed directly to the write method of
269 the statement line or {}
270@@ -224,36 +233,34 @@
271 ...}
272 """
273 st_obj = self.pool.get('account.bank.statement.line')
274- st_line = st_obj.browse(cr, uid, line_id, context=context)
275 res = {}
276 if st_line:
277 so_obj = self.pool.get('sale.order')
278- so_id = so_obj.search(
279- cr,
280- uid,
281- [('name', '=', st_line.ref)],
282- context=context)
283+ so_id = so_obj.search(cr,
284+ uid,
285+ [('name', '=', st_line['ref'])],
286+ context=context)
287 if so_id:
288 if so_id and len(so_id) == 1:
289 so = so_obj.browse(cr, uid, so_id[0], context=context)
290 res['partner_id'] = so.partner_id.id
291 elif so_id and len(so_id) > 1:
292- raise ErrorTooManyPartner(
293- _('Line named "%s" (Ref:%s) was matched by more '
294- 'than one partner.') %
295- (st_line.name, st_line.ref))
296- st_vals = st_obj.get_values_for_line(
297- cr,
298- uid,
299- profile_id=st_line.statement_id.profile_id.id,
300- partner_id=res.get('partner_id', False),
301- line_type=st_line.type,
302- amount=st_line.amount,
303- context=context)
304+ raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more '
305+ 'than one partner.') %
306+ (st_line['name'], st_line['ref']))
307+ st_vals = st_obj.get_values_for_line(cr,
308+ uid,
309+ profile_id=st_line['profile_id'],
310+ master_account_id=st_line['master_account_id'],
311+ partner_id=res.get('partner_id', False),
312+ line_type=st_line['type'],
313+ amount=st_line['amount'] if st_line['amount'] else 0.0,
314+ context=context)
315 res.update(st_vals)
316 return res
317
318- def get_from_label_and_partner_field(self, cr, uid, line_id, context=None):
319+ # Should be private but data are initialised with no update XML
320+ def get_from_label_and_partner_field(self, cr, uid, st_line, context=None):
321 """
322 Match the partner based on the label field of the statement line
323 and the text defined in the 'bank_statement_label' field of the partner.
324@@ -261,7 +268,7 @@
325 get_values_for_line method to complete other values.
326 If more than one partner matched, raise the ErrorTooManyPartner error.
327
328- :param int/long line_id: id of the concerned account.bank.statement.line
329+ :param int/long st_line: read of the concerned account.bank.statement.line
330 :return:
331 A dict of value that can be passed directly to the write method of
332 the statement line or {}
333@@ -283,7 +290,7 @@
334 partner_ids = partner_obj.search(cr,
335 uid,
336 [('bank_statement_label', '!=', False)])
337- line_ids = tuple(x.id for x in context.get('line_ids', []))
338+ line_ids = context.get('line_ids', [])
339 for partner in partner_obj.browse(cr, uid, partner_ids, context=context):
340 vals = '|'.join(re.escape(x.strip()) for x in partner.bank_statement_label.split(';'))
341 or_regex = ".*%s*." % vals
342@@ -294,32 +301,32 @@
343 pairs = cr.fetchall()
344 for pair in pairs:
345 context['label_memoizer'][pair[0]].append(partner)
346- st_line = st_obj.browse(cr, uid, line_id, context=context)
347- if st_line and st_line.id in context['label_memoizer']:
348- found_partner = context['label_memoizer'][st_line.id]
349+ if st_line['id'] in context['label_memoizer']:
350+ found_partner = context['label_memoizer'][st_line['id']]
351 if len(found_partner) > 1:
352 raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by '
353 'more than one partner.') %
354- (st_line.name, st_line.ref))
355+ (st_line['name'], st_line['ref']))
356 res['partner_id'] = found_partner[0].id
357 st_vals = st_obj.get_values_for_line(cr,
358 uid,
359- profile_id=st_line.statement_id.profile_id.id,
360+ profile_id=st_line['profile_id'],
361+ master_account_id=st_line['master_account_id'],
362 partner_id=found_partner[0].id,
363- line_type=st_line.type,
364- amount=st_line.amount,
365+ line_type=st_line['type'],
366+ amount=st_line['amount'] if st_line['amount'] else 0.0,
367 context=context)
368 res.update(st_vals)
369 return res
370
371- def get_from_label_and_partner_name(self, cr, uid, line_id, context=None):
372+ def get_from_label_and_partner_name(self, cr, uid, st_line, context=None):
373 """
374 Match the partner based on the label field of the statement line
375 and the name of the partner.
376 Then, call the generic get_values_for_line method to complete other values.
377 If more than one partner matched, raise the ErrorTooManyPartner error.
378
379- :param int/long line_id: id of the concerned account.bank.statement.line
380+ :param int/long st_line: read of the concerned account.bank.statement.line
381 :return:
382 A dict of value that can be passed directly to the write method of
383 the statement line or {}
384@@ -328,36 +335,33 @@
385
386 ...}
387 """
388- # This Method has not been tested yet !
389 res = {}
390 st_obj = self.pool.get('account.bank.statement.line')
391- st_line = st_obj.browse(cr, uid, line_id, context=context)
392- if st_line:
393- sql = "SELECT id FROM res_partner WHERE name ~* %s"
394- pattern = ".*%s.*" % re.escape(st_line.label)
395- cr.execute(sql, (pattern,))
396- result = cr.fetchall()
397- if not result:
398- return res
399- if len(result) > 1:
400- raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more '
401- 'than one partner.') %
402- (st_line.name, st_line.ref))
403- res['partner_id'] = result[0][0] if result else False
404- if res:
405- st_vals = st_obj.get_values_for_line(
406- cr,
407- uid,
408- profile_id=st_line.statement_id.profile_id.id,
409- partner_id=res['partner_id'],
410- line_type=st_line.type,
411- amount=st_line.amount,
412- context=context)
413- res.update(st_vals)
414+ sql = "SELECT id FROM res_partner WHERE name ~* %s"
415+ pattern = ".*%s.*" % re.escape(st_line['name'])
416+ cr.execute(sql, (pattern,))
417+ result = cr.fetchall()
418+ if not result:
419+ return res
420+ if len(result) > 1:
421+ raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more '
422+ 'than one partner.') %
423+ (st_line['name'], st_line['ref']))
424+ res['partner_id'] = result[0][0] if result else False
425+ if res:
426+ st_vals = st_obj.get_values_for_line(cr,
427+ uid,
428+ profile_id=st_line['porfile_id'],
429+ master_account_id=profile['master_account_id'],
430+ partner_id=res['partner_id'],
431+ line_type=st_line['type'],
432+ amount=st_line['amount'] if st_line['amount'] else 0.0,
433+ context=context)
434+ res.update(st_vals)
435 return res
436
437
438-class AccountStatementLine(Model):
439+class AccountStatementLine(orm.Model):
440 """
441 Add sparse field on the statement line to allow to store all the
442 bank infos that are given by a bank/office. You can then add you own in your
443@@ -390,7 +394,7 @@
444 'already_completed': False,
445 }
446
447- def get_line_values_from_rules(self, cr, uid, ids, context=None):
448+ def _get_line_values_from_rules(self, cr, uid, line, rules, context=None):
449 """
450 We'll try to find out the values related to the line based on rules setted on
451 the profile.. We will ignore line for which already_completed is ticked.
452@@ -401,36 +405,26 @@
453 {117009: {'partner_id': 100997, 'account_id': 489L}}
454 """
455 profile_obj = self.pool.get('account.statement.profile')
456- st_obj = self.pool.get('account.bank.statement.line')
457 res = {}
458 errors_stack = []
459- for line in self.browse(cr, uid, ids, context=context):
460- if line.already_completed:
461- continue
462- try:
463- # Take the default values
464- res[line.id] = st_obj.get_values_for_line(
465- cr,
466- uid,
467- profile_id=line.statement_id.profile_id.id,
468- line_type=line.type,
469- amount=line.amount,
470- context=context)
471- # Ask the rule
472- vals = profile_obj.find_values_from_rules(
473- cr, uid, line.statement_id.profile_id.id, line.id, context)
474- # Merge the result
475- res[line.id].update(vals)
476- except ErrorTooManyPartner, exc:
477- msg = "Line ID %s had following error: %s" % (line.id, exc.value)
478- errors_stack.append(msg)
479+ if line.get('already_completed'):
480+ return res
481+ try:
482+ # Ask the rule
483+ vals = profile_obj._find_values_from_rules(cr, uid, rules, line, context)
484+ if vals:
485+ vals['id'] = line['id']
486+ return vals
487+ except ErrorTooManyPartner as exc:
488+ msg = "Line ID %s had following error: %s" % (line['id'], exc.value)
489+ errors_stack.append(msg)
490 if errors_stack:
491 msg = u"\n".join(errors_stack)
492 raise ErrorTooManyPartner(msg)
493 return res
494
495
496-class AccountBankSatement(Model):
497+class AccountBankSatement(orm.Model):
498 """
499 We add a basic button and stuff to support the auto-completion
500 of the bank statement once line have been imported or manually fullfill.
501@@ -453,24 +447,29 @@
502 :return : True
503 """
504 error_log = ""
505- user_name = self.pool.get('res.users').read(
506- cr, uid, uid, ['name'], context=context)['name']
507- log = self.read(
508- cr, uid, stat_id, ['completion_logs'], context=context)['completion_logs']
509+ user_name = self.pool.get('res.users').read(cr, uid, uid,
510+ ['name'], context=context)['name']
511+
512+ log = self.read(cr, uid, stat_id, ['completion_logs'],
513+ context=context)['completion_logs']
514+
515 log_line = log and log.split("\n") or []
516 completion_date = datetime.datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT)
517 if error_msg:
518 error_log = error_msg
519 log_line[0:0] = [completion_date + ' : '
520- + _("Bank Statement ID %s has %s lines completed by %s") % (stat_id, number_imported, user_name)
521- + "\n" + error_log + "-------------" + "\n"]
522+ + _("Bank Statement ID %s has %s lines completed by %s") %
523+ (stat_id, number_imported, user_name)
524+ + "\n" + error_log + "-------------" + "\n"]
525 log = "\n".join(log_line)
526 self.write(cr, uid, [stat_id], {'completion_logs': log}, context=context)
527- self.message_post(
528- cr, uid,
529- [stat_id],
530- body=_('Statement ID %s auto-completed for %s lines completed') % (stat_id, number_imported),
531- context=context)
532+
533+ body = (_('Statement ID %s auto-completed for %s lines completed') %
534+ (stat_id, number_imported)),
535+ self.message_post(cr, uid,
536+ [stat_id],
537+ body=body,
538+ context=context)
539 return True
540
541 def button_auto_completion(self, cr, uid, ids, context=None):
542@@ -480,29 +479,55 @@
543 """
544 if context is None:
545 context = {}
546- stat_line_obj = self.pool.get('account.bank.statement.line')
547+ stat_line_obj = self.pool['account.bank.statement.line']
548+ profile_obj = self.pool.get('account.statement.profile')
549 compl_lines = 0
550+ stat_line_obj.check_access_rule(cr, uid, [], 'create')
551+ stat_line_obj.check_access_rights(cr, uid, 'create', raise_exception=True)
552 for stat in self.browse(cr, uid, ids, context=context):
553 msg_lines = []
554 ctx = context.copy()
555- ctx['line_ids'] = stat.line_ids
556- for line in stat.line_ids:
557- res = {}
558+ ctx['line_ids'] = tuple((x.id for x in stat.line_ids))
559+ b_profile = stat.profile_id
560+ rules = profile_obj._get_callable(cr, uid, b_profile, context=context)
561+ profile_id = b_profile.id # Only for perfo even it gains almost nothing
562+ master_account_id = b_profile.receivable_account_id
563+ master_account_id = master_account_id.id if master_account_id else False
564+ res = False
565+ for line in stat_line_obj.read(cr, uid, ctx['line_ids']):
566 try:
567- res = stat_line_obj.get_line_values_from_rules(
568- cr, uid, [line.id], context=ctx)
569+ # performance trick
570+ line['master_account_id'] = master_account_id
571+ line['profile_id'] = profile_id
572+ res = stat_line_obj._get_line_values_from_rules(cr, uid, line,
573+ rules, context=ctx)
574 if res:
575 compl_lines += 1
576 except ErrorTooManyPartner, exc:
577 msg_lines.append(repr(exc))
578 except Exception, exc:
579 msg_lines.append(repr(exc))
580- # vals = res and res.keys() or False
581+ error_type, error_value, trbk = sys.exc_info()
582+ st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value)
583+ st += ''.join(traceback.format_tb(trbk, 30))
584+ print st
585+ _logger.error(st)
586 if res:
587- vals = res[line.id]
588- vals['already_completed'] = True
589- stat_line_obj.write(cr, uid, [line.id], vals, context=ctx)
590+ #stat_line_obj.write(cr, uid, [line.id], vals, context=ctx)
591+ try:
592+ stat_line_obj._update_line(cr, uid, res, context=context)
593+ except Exception as exc:
594+ msg_lines.append(repr(exc))
595+ error_type, error_value, trbk = sys.exc_info()
596+ st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value)
597+ st += ''.join(traceback.format_tb(trbk, 30))
598+ print st
599+ _logger.error(st)
600+ # we can commit as it is not needed to be atomic
601+ # commiting here adds a nice perfo boost
602+ if not compl_lines % 500:
603+ cr.commit()
604 msg = u'\n'.join(msg_lines)
605 self.write_completion_log(cr, uid, stat.id,
606- msg, compl_lines, context=context)
607+ msg, compl_lines, context=context)
608 return True
609
610=== modified file 'account_statement_base_import/parser/generic_file_parser.py'
611--- account_statement_base_import/parser/generic_file_parser.py 2013-02-26 08:22:25 +0000
612+++ account_statement_base_import/parser/generic_file_parser.py 2013-04-24 09:32:28 +0000
613@@ -29,6 +29,7 @@
614 except:
615 raise Exception(_('Please install python lib xlrd'))
616
617+
618 def float_or_zero(val):
619 """ Conversion function used to manage
620 empty string into float usecase"""
621@@ -82,14 +83,12 @@
622 In this generic parser, the commission is given for every line, so we store it
623 for each one.
624 """
625- return {
626- 'name': line.get('label', line.get('ref', '/')),
627- 'date': line.get('date', datetime.datetime.now().date()),
628- 'amount': line.get('amount', 0.0),
629- 'ref': line.get('ref', '/'),
630- 'label': line.get('label', ''),
631- 'commission_amount': line.get('commission_amount', 0.0),
632- }
633+ return {'name': line.get('label', line.get('ref', '/')),
634+ 'date': line.get('date', datetime.datetime.now().date()),
635+ 'amount': line.get('amount', 0.0),
636+ 'ref': line.get('ref', '/'),
637+ 'label': line.get('label', ''),
638+ 'commission_amount': line.get('commission_amount', 0.0)}
639
640 def _post(self, *args, **kwargs):
641 """
642
643=== modified file 'account_statement_base_import/statement.py'
644--- account_statement_base_import/statement.py 2013-02-26 08:45:03 +0000
645+++ account_statement_base_import/statement.py 2013-04-24 09:32:28 +0000
646@@ -18,14 +18,16 @@
647 # along with this program. If not, see <http://www.gnu.org/licenses/>.
648 #
649 ##############################################################################
650+import sys
651+import traceback
652+
653+import psycopg2
654
655 from openerp.tools.translate import _
656 import datetime
657 from openerp.osv.orm import Model
658 from openerp.osv import fields, osv
659 from parser import new_bank_statement_parser
660-import sys
661-import traceback
662
663
664 class AccountStatementProfil(Model):
665@@ -43,7 +45,8 @@
666 help="Tic that box to automatically launch the completion "
667 "on each imported file using this profile."),
668 'last_import_date': fields.datetime("Last Import Date"),
669- 'rec_log': fields.text('log', readonly=True, deprecated=True),
670+ # we remove deprecated as it floods logs in standard/warning level sob...
671+ 'rec_log': fields.text('log', readonly=True), # Deprecated
672 'import_type': fields.selection(
673 get_import_type_selection,
674 'Type of import',
675@@ -64,7 +67,8 @@
676 self.message_post(cr,
677 uid,
678 ids,
679- body=_('Statement ID %s have been imported with %s lines.') % (statement_id, num_lines),
680+ body=_('Statement ID %s have been imported with %s lines.') %
681+ (statement_id, num_lines),
682 context=context)
683 return True
684
685@@ -121,13 +125,27 @@
686 statement_obj = self.pool.get('account.bank.statement')
687 values = parser_vals
688 values['statement_id'] = statement_id
689- values['account_id'] = statement_obj.get_account_for_counterpart(
690- cr,
691- uid,
692- parser_vals['amount'],
693- account_receivable,
694- account_payable
695- )
696+ values['account_id'] = statement_obj.get_account_for_counterpart(cr,
697+ uid,
698+ parser_vals['amount'],
699+ account_receivable,
700+ account_payable)
701+
702+ date = values.get('date')
703+ period_memoizer = context.get('period_memoizer')
704+ if not period_memoizer:
705+ period_memoizer = {}
706+ context['period_memoizer'] = period_memoizer
707+ if period_memoizer.get(date):
708+ values['period_id'] = period_memoizer[date]
709+ else:
710+ # This is awfully slow...
711+ periods = self.pool.get('account.period').find(cr, uid,
712+ dt=values.get('date'),
713+ context=context)
714+ values['period_id'] = periods[0]
715+ period_memoizer[date] = periods[0]
716+ values['type'] = 'general'
717 return values
718
719 def statement_import(self, cr, uid, ids, profile_id, file_stream, ftype="csv", context=None):
720@@ -148,75 +166,85 @@
721 attachment_obj = self.pool.get('ir.attachment')
722 prof_obj = self.pool.get("account.statement.profile")
723 if not profile_id:
724- raise osv.except_osv(
725- _("No Profile !"),
726- _("You must provide a valid profile to import a bank statement !"))
727+ raise osv.except_osv(_("No Profile !"),
728+ _("You must provide a valid profile to import a bank statement !"))
729 prof = prof_obj.browse(cr, uid, profile_id, context=context)
730
731 parser = new_bank_statement_parser(prof.import_type, ftype=ftype)
732 result_row_list = parser.parse(file_stream)
733 # Check all key are present in account.bank.statement.line !!
734+ if not result_row_list:
735+ raise osv.except_osv(_("Nothing to import"),
736+ _("The file is empty"))
737 parsed_cols = parser.get_st_line_vals(result_row_list[0]).keys()
738 for col in parsed_cols:
739 if col not in statement_line_obj._columns:
740- raise osv.except_osv(
741- _("Missing column !"),
742- _("Column %s you try to import is not "
743- "present in the bank statement line !") % col)
744+ raise osv.except_osv(_("Missing column !"),
745+ _("Column %s you try to import is not "
746+ "present in the bank statement line !") % col)
747
748- statement_id = statement_obj.create(
749- cr, uid, {'profile_id': prof.id}, context=context)
750- account_receivable, account_payable = statement_obj.get_default_pay_receiv_accounts(
751- cr, uid, context)
752+ statement_id = statement_obj.create(cr, uid,
753+ {'profile_id': prof.id},
754+ context=context)
755+ if prof.receivable_account_id:
756+ account_receivable = account_payable = prof.receivable_account_id.id
757+ else:
758+ account_receivable, account_payable = statement_obj.get_default_pay_receiv_accounts(
759+ cr, uid, context)
760 try:
761 # Record every line in the bank statement and compute the global commission
762 # based on the commission_amount column
763 statement_store = []
764 for line in result_row_list:
765 parser_vals = parser.get_st_line_vals(line)
766- values = self.prepare_statetement_lines_vals(
767- cr, uid, parser_vals, account_payable,
768- account_receivable, statement_id, context)
769- # we finally create the line in system
770- statement_store.append((0, 0, values))
771+ values = self.prepare_statetement_lines_vals(cr, uid, parser_vals, account_payable,
772+ account_receivable, statement_id, context)
773+ statement_store.append(values)
774+ # Hack to bypass ORM poor perfomance. Sob...
775+ statement_line_obj._insert_lines(cr, uid, statement_store, context=context)
776+
777 # Build and create the global commission line for the whole statement
778- statement_obj.write(cr, uid, [statement_id],
779- {'line_ids': statement_store}, context=context)
780 comm_vals = self.prepare_global_commission_line_vals(cr, uid, parser, result_row_list,
781 prof, statement_id, context)
782 if comm_vals:
783 statement_line_obj.create(cr, uid, comm_vals, context=context)
784-
785- attachment_obj.create(
786- cr,
787- uid,
788- {
789- 'name': 'statement file',
790- 'datas': file_stream,
791- 'datas_fname': "%s.%s" % (
792- datetime.datetime.now().date(),
793- ftype),
794- 'res_model': 'account.bank.statement',
795- 'res_id': statement_id,
796- },
797- context=context
798- )
799+ else:
800+ # Trigger store field computation if someone has better idea
801+ start_bal = statement_obj.read(cr, uid, statement_id,
802+ ['balance_start'],
803+ context=context)
804+ start_bal = start_bal['balance_start']
805+ statement_obj.write(cr, uid, [statement_id],
806+ {'balance_start': start_bal})
807+
808+ attachment_obj.create(cr,
809+ uid,
810+ {'name': 'statement file',
811+ 'datas': file_stream,
812+ 'datas_fname': "%s.%s" % (
813+ datetime.datetime.now().date(),
814+ ftype),
815+ 'res_model': 'account.bank.statement',
816+ 'res_id': statement_id},
817+ context=context)
818+
819 # If user ask to launch completion at end of import, do it !
820 if prof.launch_import_completion:
821 statement_obj.button_auto_completion(cr, uid, [statement_id], context)
822
823 # Write the needed log infos on profile
824- self.write_logs_after_import(
825- cr, uid, prof.id, statement_id, len(result_row_list), context)
826+ self.write_logs_after_import(cr, uid, prof.id,
827+ statement_id,
828+ len(result_row_list),
829+ context)
830
831 except Exception:
832 statement_obj.unlink(cr, uid, [statement_id], context=context)
833 error_type, error_value, trbk = sys.exc_info()
834 st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value)
835 st += ''.join(traceback.format_tb(trbk, 30))
836- raise osv.except_osv(
837- _("Statement import error"),
838- _("The statement cannot be created : %s") % st)
839+ raise osv.except_osv(_("Statement import error"),
840+ _("The statement cannot be created : %s") % st)
841 return statement_id
842
843
844@@ -228,6 +256,46 @@
845 """
846 _inherit = "account.bank.statement.line"
847
848+ def _get_available_columns(self, statement_store):
849+ """Return writeable by SQL columns"""
850+ statement_line_obj = self.pool['account.bank.statement.line']
851+ model_cols = statement_line_obj._columns
852+ avail = [k for k, col in model_cols.iteritems() if not hasattr(col, '_fnct')]
853+ keys = [k for k in statement_store[0].keys() if k in avail]
854+ keys.sort()
855+ return keys
856+
857+ def _insert_lines(self, cr, uid, statement_store, context=None):
858+ """ Do raw insert into database because ORM is awfully slow
859+ when doing batch write. It is a shame that batch function
860+ does not exist"""
861+ statement_line_obj = self.pool['account.bank.statement.line']
862+ statement_line_obj.check_access_rule(cr, uid, [], 'create')
863+ statement_line_obj.check_access_rights(cr, uid, 'create', raise_exception=True)
864+ cols = self._get_available_columns(statement_store)
865+ tmp_vals = (', '.join(cols), ', '.join(['%%(%s)s' % i for i in cols]))
866+ sql = "INSERT INTO account_bank_statement_line (%s) VALUES (%s);" % tmp_vals
867+ try:
868+ cr.executemany(sql, tuple(statement_store))
869+ except psycopg2.Error as sql_err:
870+ cr.rollback()
871+ raise osv.except_osv(_("ORM bypass error"),
872+ sql_err.pgerror)
873+
874+ def _update_line(self, cr, uid, vals, context=None):
875+ """ Do raw update into database because ORM is awfully slow
876+ when doing batch write. It is a shame that batch function
877+ does not exist"""
878+ cols = self._get_available_columns([vals])
879+ tmp_vals = (', '.join(['%s = %%(%s)s' % (i, i) for i in cols]))
880+ sql = "UPDATE account_bank_statement_line SET %s where id = %%(id)s;" % tmp_vals
881+ try:
882+ cr.execute(sql, vals)
883+ except psycopg2.Error as sql_err:
884+ cr.rollback()
885+ raise osv.except_osv(_("ORM bypass error"),
886+ sql_err.pgerror)
887+
888 _columns = {
889 'commission_amount': fields.sparse(
890 type='float',
891
892=== modified file 'account_statement_ext/__openerp__.py'
893--- account_statement_ext/__openerp__.py 2013-02-14 08:13:49 +0000
894+++ account_statement_ext/__openerp__.py 2013-04-24 09:32:28 +0000
895@@ -25,11 +25,9 @@
896 'maintainer': 'Camptocamp',
897 'category': 'Finance',
898 'complexity': 'normal',
899- 'depends': [
900- 'account',
901- 'report_webkit',
902- 'account_voucher'
903- ],
904+ 'depends': ['account',
905+ 'report_webkit',
906+ 'account_voucher'],
907 'description': """
908 Improve the basic bank statement, by adding various new features,
909 and help dealing with huge volume of reconciliation through payment offices such as Paypal, Lazer,
910@@ -65,7 +63,6 @@
911 4) Remove the period on the bank statement, and compute it for each line based on their date instead.
912 It also adds this feature in the voucher in order to compute the period correctly.
913
914-
915 5) Cancelling a bank statement is much more easy and will cancel all related entries, unreconcile them,
916 and finally delete them.
917
918@@ -88,4 +85,4 @@
919 'auto_install': False,
920 'license': 'AGPL-3',
921 'active': False,
922-}
923+ }
924
925=== modified file 'account_statement_ext/statement.py'
926--- account_statement_ext/statement.py 2013-03-01 14:33:32 +0000
927+++ account_statement_ext/statement.py 2013-04-24 09:32:28 +0000
928@@ -1,4 +1,4 @@
929-# -*- coding: utf-8 -*-
930+#-*- coding: utf-8 -*-
931 ##############################################################################
932 #
933 # Author: Nicolas Bessi, Joel Grand-Guillaume
934@@ -18,12 +18,27 @@
935 # along with this program. If not, see <http://www.gnu.org/licenses/>.
936 #
937 ##############################################################################
938-
939+import openerp.addons.account.account_bank_statement as stat_mod
940 from openerp.osv.orm import Model
941 from openerp.osv import fields, osv
942 from openerp.tools.translate import _
943
944
945+# Monkey patch to fix bad write implementation...
946+def fixed_write(self, cr, uid, ids, vals, context=None):
947+ """ Fix performance desing of original function
948+ Ideally we should use a real postgres sequence or serial fields.
949+ Will do it when I have time."""
950+ res = super(stat_mod.account_bank_statement, self).write(cr, uid, ids,
951+ vals, context=context)
952+ for s_id in ids:
953+ cr.execute("UPDATE account_bank_statement_line"
954+ " SET sequence = account_bank_statement_line.id + 1"
955+ " where statement_id = %s", (s_id,))
956+ return res
957+stat_mod.account_bank_statement.write = fixed_write
958+
959+
960 class AccountStatementProfil(Model):
961 """
962 A Profile will contain all infos related to the type of
963@@ -44,38 +59,45 @@
964 "commission move (and optionaly on the counterpart "
965 "of the intermediate/banking move if you tick the "
966 "corresponding checkbox)."),
967+
968 'journal_id': fields.many2one(
969 'account.journal',
970 'Financial journal to use for transaction',
971 required=True),
972+
973 'commission_account_id': fields.many2one(
974 'account.account',
975 'Commission account',
976 required=True),
977+
978 'commission_analytic_id': fields.many2one(
979 'account.analytic.account',
980 'Commission analytic account'),
981+
982 'receivable_account_id': fields.many2one(
983 'account.account',
984 'Force Receivable/Payable Account',
985 help="Choose a receivable account to force the default "
986 "debit/credit account (eg. an intermediat bank account "
987 "instead of default debitors)."),
988+
989 'force_partner_on_bank': fields.boolean(
990 'Force partner on bank move',
991 help="Tick that box if you want to use the credit "
992 "institute partner in the counterpart of the "
993 "intermediate/banking move."),
994+
995 'balance_check': fields.boolean(
996 'Balance check',
997 help="Tick that box if you want OpenERP to control "
998 "the start/end balance before confirming a bank statement. "
999- "If don't ticked, no balance control will be done."
1000- ),
1001- 'bank_statement_prefix': fields.char(
1002- 'Bank Statement Prefix', size=32),
1003- 'bank_statement_ids': fields.one2many(
1004- 'account.bank.statement', 'profile_id', 'Bank Statement Imported'),
1005+ "If don't ticked, no balance control will be done."),
1006+
1007+ 'bank_statement_prefix': fields.char('Bank Statement Prefix', size=32),
1008+
1009+ 'bank_statement_ids': fields.one2many('account.bank.statement',
1010+ 'profile_id',
1011+ 'Bank Statement Imported'),
1012 'company_id': fields.many2one('res.company', 'Company'),
1013 }
1014
1015@@ -166,11 +188,11 @@
1016 """
1017 for statement in self.browse(cr, uid, ids, context=context):
1018 if (statement.period_id and
1019- statement.company_id.id != statement.period_id.company_id.id):
1020+ statement.company_id.id != statement.period_id.company_id.id):
1021 return False
1022 for line in statement.line_ids:
1023 if (line.period_id and
1024- statement.company_id.id != line.period_id.company_id.id):
1025+ statement.company_id.id != line.period_id.company_id.id):
1026 return False
1027 return True
1028
1029@@ -244,8 +266,10 @@
1030 create the move from.
1031 :return: int/long of the res.partner to use as counterpart
1032 """
1033- bank_partner_id = super(AccountBankSatement, self).\
1034- _get_counter_part_partner(cr, uid, st_line, context=context)
1035+ bank_partner_id = super(AccountBankSatement, self)._get_counter_part_partner(cr,
1036+ uid,
1037+ st_line,
1038+ context=context)
1039 # get the right partner according to the chosen profil
1040 if st_line.statement_id.profile_id.force_partner_on_bank:
1041 bank_partner_id = st_line.statement_id.profile_id.partner_id.id
1042@@ -298,7 +322,7 @@
1043 if (not st.journal_id.default_credit_account_id) \
1044 or (not st.journal_id.default_debit_account_id):
1045 raise osv.except_osv(_('Configuration Error !'),
1046- _('Please verify that an account is defined in the journal.'))
1047+ _('Please verify that an account is defined in the journal.'))
1048
1049 if not st.name == '/':
1050 st_number = st.name
1051@@ -309,7 +333,7 @@
1052 for line in st.move_line_ids:
1053 if line.state != 'valid':
1054 raise osv.except_osv(_('Error !'),
1055- _('The account entries lines are not in valid state.'))
1056+ _('The account entries lines are not in valid state.'))
1057 # begin changes
1058 errors_stack = []
1059 for st_line in st.line_ids:
1060@@ -317,11 +341,15 @@
1061 if st_line.analytic_account_id:
1062 if not st.journal_id.analytic_journal_id:
1063 raise osv.except_osv(_('No Analytic Journal !'),
1064- _("You have to assign an analytic journal on the '%s' journal!") % (st.journal_id.name,))
1065+ _("You have to assign an analytic"
1066+ " journal on the '%s' journal!") % st.journal_id.name)
1067 if not st_line.amount:
1068 continue
1069 st_line_number = self.get_next_st_line_number(cr, uid, st_number, st_line, context)
1070- self.create_move_from_st_line(cr, uid, st_line.id, company_currency_id, st_line_number, context)
1071+ self.create_move_from_st_line(cr, uid, st_line.id,
1072+ company_currency_id,
1073+ st_line_number,
1074+ context)
1075 except osv.except_osv, exc:
1076 msg = "Line ID %s with ref %s had following error: %s" % (st_line.id, st_line.ref, exc.value)
1077 errors_stack.append(msg)
1078@@ -332,37 +360,95 @@
1079 msg = u"\n".join(errors_stack)
1080 raise osv.except_osv(_('Error'), msg)
1081 #end changes
1082- self.write(cr, uid, [st.id], {
1083- 'name': st_number,
1084- 'balance_end_real': st.balance_end
1085- }, context=context)
1086- self.message_post(cr, uid, [st.id], body=_('Statement %s confirmed, journal items were created.') % (st_number,), context=context)
1087+ self.write(cr, uid, [st.id],
1088+ {'name': st_number,
1089+ 'balance_end_real': st.balance_end},
1090+ context=context)
1091+ body = _('Statement %s confirmed, journal items were created.') % st_number
1092+ self.message_post(cr, uid, [st.id],
1093+ body,
1094+ context=context)
1095 return self.write(cr, uid, ids, {'state': 'confirm'}, context=context)
1096
1097- def get_account_for_counterpart(
1098- self, cr, uid, amount, account_receivable, account_payable):
1099+ def get_account_for_counterpart(self, cr, uid, amount, account_receivable, account_payable):
1100+ """For backward compatibility."""
1101+ account_id, type = self.get_account_and_type_for_counterpart(cr, uid, amount,
1102+ account_receivable,
1103+ account_payable)
1104+ return account_id
1105+
1106+ def _compute_type_from_partner_profile(self, cr, uid, partner_id,
1107+ default_type, context=None):
1108+ """Compute the statement line type
1109+ from partner profile (customer, supplier)"""
1110+ obj_partner = self.pool.get('res.partner')
1111+ part = obj_partner.browse(cr, uid, partner_id, context=context)
1112+ if part.supplier == part.customer:
1113+ return default_type
1114+ if part.supplier:
1115+ return 'supplier'
1116+ else:
1117+ return 'customer'
1118+
1119+ def _compute_type_from_amount(self, cr, uid, amount):
1120+ """Compute the statement type based on amount"""
1121+ if amount < 0:
1122+ return 'supplier'
1123+ return 'customer'
1124+
1125+ def get_type_for_counterpart(self, cr, uid, amount, partner_id=False):
1126+ """Give the amount and receive the type to use for the line.
1127+ The rules are:
1128+ - If the customer checkbox is checked on the found partner, type customer
1129+ - If the supplier checkbox is checked on the found partner, typewill be supplier
1130+ - If both checkbox are checked or none of them, it'll be based on the amount :
1131+ If amount is positif the type customer,
1132+ If amount is negativ, the type supplier
1133+ :param float: amount of the line
1134+ :param int/long: partner_id the partner id
1135+ :return: type as string: the default type to use: 'customer' or 'supplier'.
1136+ """
1137+ s_line_type = self._compute_type_from_amount(cr, uid, amount)
1138+ if partner_id:
1139+ s_line_type = self._compute_type_from_partner_profile(cr, uid,
1140+ partner_id, s_line_type)
1141+ return s_line_type
1142+
1143+ def get_account_and_type_for_counterpart(self, cr, uid, amount, account_receivable,
1144+ account_payable, partner_id=False):
1145 """
1146 Give the amount, payable and receivable account (that can be found using
1147 get_default_pay_receiv_accounts method) and receive the one to use. This method
1148 should be use when there is no other way to know which one to take.
1149+ The rules are:
1150+ - If the customer checkbox is checked on the found partner, type and account will be customer and receivable
1151+ - If the supplier checkbox is checked on the found partner, type and account will be supplier and payable
1152+ - If both checkbox are checked or none of them, it'll be based on the amount :
1153+ If amount is positif the type and account will be customer and receivable,
1154+ If amount is negativ, the type and account will be supplier and payable
1155+ Note that we return the payable or receivable account from agrs and not from the optional partner_id
1156+ given !
1157
1158 :param float: amount of the line
1159 :param int/long: account_receivable the receivable account
1160 :param int/long: account_payable the payable account
1161- :return: int/long :the default account to be used by statement line as the counterpart
1162- of the journal account depending on the amount.
1163+ :param int/long: partner_id the partner id
1164+ :return: dict with [account_id as int/long,type as string]: the default account to be used by
1165+ statement line as the counterpart of the journal account depending on the amount and the type
1166+ as 'customer' or 'supplier'.
1167 """
1168 account_id = False
1169- if amount >= 0:
1170+ type = self.get_type_for_counterpart(cr, uid, amount, partner_id=partner_id)
1171+ if type == 'supplier':
1172+ account_id = account_payable
1173+ else:
1174 account_id = account_receivable
1175- else:
1176- account_id = account_payable
1177 if not account_id:
1178 raise osv.except_osv(
1179 _('Can not determine account'),
1180 _('Please ensure that minimal properties are set')
1181 )
1182- return account_id
1183+ return [account_id, type]
1184
1185 def get_default_pay_receiv_accounts(self, cr, uid, context=None):
1186 """
1187@@ -386,14 +472,11 @@
1188 ('model', '=', 'res.partner')],
1189 context=context
1190 )
1191- property_ids = property_obj.search(
1192- cr,
1193- uid,
1194- [('fields_id', 'in', model_fields_ids),
1195- ('res_id', '=', False),
1196- ],
1197- context=context
1198- )
1199+ property_ids = property_obj.search(cr,
1200+ uid,
1201+ [('fields_id', 'in', model_fields_ids),
1202+ ('res_id', '=', False)],
1203+ context=context)
1204
1205 for erp_property in property_obj.browse(
1206 cr, uid, property_ids, context=context):
1207@@ -433,13 +516,10 @@
1208 journal_id = import_config.journal_id.id
1209 account_id = import_config.journal_id.default_debit_account_id.id
1210 credit_partner_id = import_config.partner_id and import_config.partner_id.id or False
1211- return {'value':
1212- {'journal_id': journal_id,
1213- 'account_id': account_id,
1214- 'balance_check': import_config.balance_check,
1215- 'credit_partner_id': credit_partner_id,
1216- }
1217- }
1218+ return {'value': {'journal_id': journal_id,
1219+ 'account_id': account_id,
1220+ 'balance_check': import_config.balance_check,
1221+ 'credit_partner_id': credit_partner_id}}
1222
1223
1224 class AccountBankSatementLine(Model):
1225@@ -472,18 +552,24 @@
1226 'account_id': _get_default_account,
1227 }
1228
1229- def get_values_for_line(self, cr, uid, profile_id=False, partner_id=False, line_type=False, amount=False, context=None):
1230+ 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):
1231 """
1232 Return the account_id to be used in the line of a bank statement. It'll base the result as follow:
1233 - If a receivable_account_id is set in the profile, return this value and type = general
1234- - Elif line_type is given, take the partner receivable/payable property (payable if type= supplier, receivable
1235+ # TODO
1236+ - Elif how_get_type_account is set to force_supplier or force_customer, will take respectively payable and type=supplier,
1237+ receivable and type=customer otherwise
1238+ # END TODO
1239+ - Elif line_type is given, take the partner receivable/payable property (payable if type=supplier, receivable
1240 otherwise)
1241- - Elif amount is given, take the partner receivable/payable property (receivable if amount >= 0.0,
1242- payable otherwise). In that case, we also fullfill the type (receivable = customer, payable = supplier)
1243- so it is easier for the accountant to know why the receivable/payable has been chosen
1244+ - Elif amount is given:
1245+ - If the customer checkbox is checked on the found partner, type and account will be customer and receivable
1246+ - If the supplier checkbox is checked on the found partner, type and account will be supplier and payable
1247+ - If both checkbox are checked or none of them, it'll be based on the amount :
1248+ If amount is positif the type and account will be customer and receivable,
1249+ If amount is negativ, the type and account will be supplier an payable
1250 - Then, if no partner are given we look and take the property from the company so we always give a value
1251 for account_id. Note that in that case, we return the receivable one.
1252-
1253 :param int/long profile_id of the related bank statement
1254 :param int/long partner_id of the line
1255 :param char line_type: a value from: 'general', 'supplier', 'customer'
1256@@ -501,14 +587,19 @@
1257 obj_stat = self.pool.get('account.bank.statement')
1258 line_type = receiv_account = pay_account = account_id = False
1259 # If profile has a receivable_account_id, we return it in any case
1260- if profile_id:
1261+ if master_account_id:
1262+ res['account_id'] = master_account_id
1263+ res['type'] = 'general'
1264+ return res
1265+ # To obtimize we consider passing false means there is no account
1266+ # on profile
1267+ if profile_id and master_account_id is None:
1268 profile = self.pool.get("account.statement.profile").browse(
1269- cr, uid, profile_id, context=context)
1270+ cr, uid, profile_id, context=context)
1271 if profile.receivable_account_id:
1272- account_id = profile.receivable_account_id.id
1273- line_type = 'general'
1274+ res['account_id'] = profile.receivable_account_id.id
1275+ res['type'] = 'general'
1276 return res
1277- # If partner -> take from him
1278 if partner_id:
1279 part = obj_partner.browse(cr, uid, partner_id, context=context)
1280 pay_account = part.property_account_payable.id
1281@@ -523,12 +614,8 @@
1282 if line_type == 'supplier':
1283 account_id = pay_account
1284 elif amount is not False:
1285- if amount >= 0:
1286- account_id = receiv_account
1287- line_type = 'customer'
1288- else:
1289- account_id = pay_account
1290- line_type = 'supplier'
1291+ account_id, line_type = obj_stat.get_account_and_type_for_counterpart(cr, uid, amount,
1292+ receiv_account, pay_account, partner_id=partner_id)
1293 res['account_id'] = account_id if account_id else receiv_account
1294 res['type'] = line_type
1295 return res
1296@@ -537,41 +624,36 @@
1297 """
1298 Override of the basic method as we need to pass the profile_id in the on_change_type
1299 call.
1300+ Moreover, we now call the get_account_and_type_for_counterpart method now to get the
1301+ type to use.
1302 """
1303- obj_partner = self.pool.get('res.partner')
1304+ obj_stat = self.pool.get('account.bank.statement')
1305 if not partner_id:
1306 return {}
1307- part = obj_partner.browse(cr, uid, partner_id, context=context)
1308- if not part.supplier and not part.customer:
1309- type = 'general'
1310- elif part.supplier and part.customer:
1311- type = 'general'
1312- else:
1313- if part.supplier == True:
1314- type = 'supplier'
1315- if part.customer == True:
1316- type = 'customer'
1317- res_type = self.onchange_type(cr, uid, ids, partner_id, type, profile_id, context=context) # Chg
1318+ line_type = obj_stat.get_type_for_counterpart(cr, uid, 0.0, partner_id=partner_id)
1319+ res_type = self.onchange_type(cr, uid, ids, partner_id, line_type, profile_id, context=context)
1320 if res_type['value'] and res_type['value'].get('account_id', False):
1321- return {'value': {'type': type,
1322+ return {'value': {'type': line_type,
1323 'account_id': res_type['value']['account_id'],
1324 'voucher_id': False}}
1325- return {'value': {'type': type}}
1326+ return {'value': {'type': line_type}}
1327
1328- def onchange_type(self, cr, uid, line_id, partner_id, type, profile_id, context=None):
1329+ def onchange_type(self, cr, uid, line_id, partner_id, line_type, profile_id, context=None):
1330 """
1331 Keep the same features as in standard and call super. If an account is returned,
1332 call the method to compute line values.
1333 """
1334- res = super(AccountBankSatementLine, self).onchange_type(
1335- cr, uid, line_id, partner_id, type, context=context)
1336+ res = super(AccountBankSatementLine, self).onchange_type(cr, uid,
1337+ line_id,
1338+ partner_id,
1339+ line_type,
1340+ context=context)
1341 if 'account_id' in res['value']:
1342- result = self.get_values_for_line(
1343- cr, uid,
1344- profile_id=profile_id,
1345- partner_id=partner_id,
1346- line_type=type,
1347- context=context)
1348+ result = self.get_values_for_line(cr, uid,
1349+ profile_id=profile_id,
1350+ partner_id=partner_id,
1351+ line_type=line_type,
1352+ context=context)
1353 if result:
1354 res['value'].update({'account_id': result['account_id']})
1355 return res

Subscribers

People subscribed via source and target branches