Merge lp:~camptocamp-reviewer/banking-addons/fix-type-and-account-in-bk-st-line into lp:banking-addons/bank-statement-reconcile-70
- fix-type-and-account-in-bk-st-line
- Merge into bank-statement-reconcile-70
Status: | Merged |
---|---|
Merged at revision: | 90 |
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: |
1631 lines (+575/-420) 9 files modified
account_statement_base_completion/__openerp__.py (+4/-0) account_statement_base_completion/statement.py (+217/-203) account_statement_base_import/parser/generic_file_parser.py (+7/-8) account_statement_base_import/statement.py (+121/-54) account_statement_ext/__openerp__.py (+4/-7) account_statement_ext/i18n/fr.po (+2/-2) account_statement_ext/statement.py (+184/-99) account_statement_transactionid_completion/statement.py (+24/-29) account_statement_transactionid_import/parser/transactionid_file_parser.py (+12/-18) |
To merge this branch: | bzr merge lp:~camptocamp-reviewer/banking-addons/fix-type-and-account-in-bk-st-line |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Alexandre Fayolle - camptocamp | code review, no test | Approve | |
Vincent Renaville@camptocamp | Pending | ||
Nicolas Bessi - Camptocamp | Pending | ||
Review via email: mp+160591@code.launchpad.net |
This proposal supersedes a proposal from 2013-04-05.
Commit message
Description of the change
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
Vincent Renaville@camptocamp (vrenaville-c2c) wrote : Posted in a previous version of this proposal | # |
- 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
Alexandre Fayolle - camptocamp (alexandre-fayolle-c2c) wrote : | # |
General: no space before "!" "?" ":" or ";" in english -> fix needed for the error messages
line 148-149: I find this message confusing, since we are not searching by partner. This message is repeated in several places in the code.
line 418: is there not a security rules bypass here ? Eg in a multicompany context with non shares res.partners, I can imagine the query returning several rows, yet only one partner being availalble to the current company.
line 424: the else False part will never execute : the function has returned on line 419.
line 425: this test is always true (the execution of line 424 sets res to an non empty dictionary, which is True)
line 487-492: there is still a little cleanup to be done here: the previous logic was handling several lines in a loop (hence the need for error_stack). The current logic deals with a single line, so error_stack can be removed and the exception raised in the except block. In the same function, I'd change the "return res" statements to "return {}" as res is never changed.
line 519: use log_line.insert(0, value) to insert a single element at the beginning of the line (I have not seen anything indicating the the RHS arg is multi element list).
line 598: leftover print statement
line 874: no batch update is performed. Docstring update required :-)
line 952: any reason for doing this in a loop rather than using UPDATE account_
Nicolas Bessi - Camptocamp (nbessi-c2c-deactivatedaccount) wrote : | # |
Hello,
Many thanks for your review.
Line 148-149 the message is done this way because even if the lookup is done on invoice, it's the partner we try to complete. But that right we can add the name of the rule inside the message.
For the rest I agree with you I will do the fixes ASAP.
- 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
Nicolas Bessi - Camptocamp (nbessi-c2c-deactivatedaccount) wrote : | # |
Corrections applied.
Alexandre please feel free to do an english correction round. This way I can regenerate PO files.
Regards
Nicolas
- 132. By Alexandre Fayolle - camptocamp
-
[IMP] fix a few docstrings
small code improvement in account_
statement_ transactionid_ completion/ statement. py AccountStatemen tCompletionRule .get_from_ transaction_ id_and_ so)
Alexandre Fayolle - camptocamp (alexandre-fayolle-c2c) wrote : | # |
account_
* the docstring does not match the arguments of the method (no st_line, and calls and line are not described)
* the "if not calls branch" will crash, it uses 'id' which is a Python built in function, not shadowed by a local variable here
account_
* security rule bypass still there AFAICT
I've pushed a few changes and improvements, don't forget to pull before fixing the points above)
- 133. By Nicolas Bessi - Camptocamp
-
[FIX] wrong argument id instead of line['profile_id']
Nicolas Bessi - Camptocamp (nbessi-c2c-deactivatedaccount) wrote : | # |
Hello,
Many thanks for the attentive review. I have pushed the last fixes
Normally get_from_
partner_ids = partner_
Alexandre Fayolle - camptocamp (alexandre-fayolle-c2c) wrote : | # |
LGTM.
Preview Diff
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-25 11:52:27 +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-25 11:52:27 +0000 |
19 | @@ -18,15 +18,21 @@ |
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 | |
41 | class ErrorTooManyPartner(Exception): |
42 | """ |
43 | @@ -40,7 +46,7 @@ |
44 | return repr(self.value) |
45 | |
46 | |
47 | -class AccountStatementProfil(Model): |
48 | +class AccountStatementProfil(orm.Model): |
49 | """ |
50 | Extend the class to add rules per profile that will match at least the partner, |
51 | but it could also be used to match other values as well. |
52 | @@ -49,11 +55,11 @@ |
53 | _inherit = "account.statement.profile" |
54 | |
55 | _columns = { |
56 | - # @Akretion : For now, we don't implement this features, but this would probably be there: |
57 | + # @Akretion: For now, we don't implement this features, but this would probably be there: |
58 | # 'auto_completion': fields.text('Auto Completion'), |
59 | # 'transferts_account_id':fields.many2one('account.account', 'Transferts Account'), |
60 | # => You can implement it in a module easily, we design it with your needs in mind |
61 | - # as well ! |
62 | + # as well! |
63 | |
64 | 'rule_ids': fields.many2many( |
65 | 'account.statement.completion.rule', |
66 | @@ -61,36 +67,45 @@ |
67 | rel='as_rul_st_prof_rel'), |
68 | } |
69 | |
70 | - def find_values_from_rules(self, cr, uid, id, line_id, context=None): |
71 | + def _get_callable(self, cr, uid, profile, context=None): |
72 | + if isinstance(profile, (int, long)): |
73 | + prof = self.browse(cr, uid, profile, context=context) |
74 | + else: |
75 | + prof = profile |
76 | + # We need to respect the sequence order |
77 | + sorted_array = sorted(prof.rule_ids, key=attrgetter('sequence')) |
78 | + return tuple((x.function_to_call for x in sorted_array)) |
79 | + |
80 | + def _find_values_from_rules(self, cr, uid, calls, line, context=None): |
81 | """ |
82 | This method will execute all related rules, in their sequence order, |
83 | to retrieve all the values returned by the first rules that will match. |
84 | - |
85 | - :param int/long line_id: id of the concerned account.bank.statement.line |
86 | + :param calls: list of lookup function name available in rules |
87 | + :param dict line: read of the concerned account.bank.statement.line |
88 | :return: |
89 | A dict of value that can be passed directly to the write method of |
90 | the statement line or {} |
91 | {'partner_id': value, |
92 | - 'account_id' : value, |
93 | + 'account_id: value, |
94 | |
95 | ...} |
96 | """ |
97 | if context is None: |
98 | context = {} |
99 | - res = {} |
100 | + if not calls: |
101 | + calls = self._get_callable(cr, uid, line['profile_id'], context=context) |
102 | rule_obj = self.pool.get('account.statement.completion.rule') |
103 | - profile = self.browse(cr, uid, id, context=context) |
104 | - # We need to respect the sequence order |
105 | - sorted_array = sorted(profile.rule_ids, key=attrgetter('sequence')) |
106 | - for rule in sorted_array: |
107 | - method_to_call = getattr(rule_obj, rule.function_to_call) |
108 | - result = method_to_call(cr, uid, line_id, context) |
109 | + |
110 | + for call in calls: |
111 | + method_to_call = getattr(rule_obj, call) |
112 | + result = method_to_call(cr, uid, line, context) |
113 | if result: |
114 | + result['already_completed'] = True |
115 | return result |
116 | - return res |
117 | - |
118 | - |
119 | -class AccountStatementCompletionRule(Model): |
120 | + return None |
121 | + |
122 | + |
123 | +class AccountStatementCompletionRule(orm.Model): |
124 | """ |
125 | This will represent all the completion method that we can have to |
126 | fullfill the bank statement lines. You'll be able to extend them in you own module |
127 | @@ -109,12 +124,11 @@ |
128 | List of available methods for rules. Override this to add you own. |
129 | """ |
130 | return [ |
131 | - ('get_from_ref_and_invoice', 'From line reference (based on invoice number)'), |
132 | + ('get_from_ref_and_invoice', 'From line reference (based on customer invoice number)'), |
133 | ('get_from_ref_and_supplier_invoice', 'From line reference (based on supplier invoice number)'), |
134 | ('get_from_ref_and_so', 'From line reference (based on SO number)'), |
135 | ('get_from_label_and_partner_field', 'From line label (based on partner field)'), |
136 | - ('get_from_label_and_partner_name', 'From line label (based on partner name)'), |
137 | - ] |
138 | + ('get_from_label_and_partner_name', 'From line label (based on partner name)')] |
139 | |
140 | _columns = { |
141 | 'sequence': fields.integer('Sequence', help="Lower means parsed first."), |
142 | @@ -126,134 +140,129 @@ |
143 | 'function_to_call': fields.selection(_get_functions, 'Method'), |
144 | } |
145 | |
146 | - def get_from_ref_and_supplier_invoice(self, cr, uid, line_id, context=None): |
147 | + def _find_invoice(self, cr, uid, st_line, inv_type, context=None): |
148 | + """Find invoice related to statement line""" |
149 | + inv_obj = self.pool.get('account.invoice') |
150 | + if inv_type == 'supplier': |
151 | + type_domain = ('in_invoice', 'in_refund') |
152 | + number_field = 'supplier_invoice_number' |
153 | + elif inv_type == 'customer': |
154 | + type_domain = ('out_invoice', 'out_refund') |
155 | + number_field = 'number' |
156 | + else: |
157 | + raise osv.except_osv(_('System error'), |
158 | + _('Invalid invoice type for completion: %') % inv_type) |
159 | + |
160 | + inv_id = inv_obj.search(cr, uid, |
161 | + [(number_field, '=', st_line['ref'].strip()), |
162 | + ('type', 'in', type_domain)], |
163 | + context=context) |
164 | + if inv_id: |
165 | + if len(inv_id) == 1: |
166 | + inv = inv_obj.browse(cr, uid, inv_id[0], context=context) |
167 | + else: |
168 | + raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more ' |
169 | + 'than one partner while looking on %s invoices') % |
170 | + (st_line['name'], st_line['ref'], inv_type)) |
171 | + return inv |
172 | + return False |
173 | + |
174 | + def _from_invoice(self, cr, uid, line, inv_type, context): |
175 | + """Populate statement line values""" |
176 | + if not inv_type in ('supplier', 'customer'): |
177 | + raise osv.except_osv(_('System error'), |
178 | + _('Invalid invoice type for completion: %') % inv_type) |
179 | + res = {} |
180 | + inv = self._find_invoice(cr, uid, line, inv_type, context=context) |
181 | + if inv: |
182 | + res = {'partner_id': inv.partner_id.id, |
183 | + 'account_id': inv.account_id.id, |
184 | + 'type': inv_type} |
185 | + override_acc = line['master_account_id'] |
186 | + if override_acc: |
187 | + res['account_id'] = override_acc |
188 | + return res |
189 | + |
190 | + # Should be private but data are initialised with no update XML |
191 | + def get_from_ref_and_supplier_invoice(self, cr, uid, line, context=None): |
192 | """ |
193 | Match the partner based on the invoice supplier invoice number and the reference of the statement |
194 | line. Then, call the generic get_values_for_line method to complete other values. |
195 | If more than one partner matched, raise the ErrorTooManyPartner error. |
196 | |
197 | - :param int/long line_id: id of the concerned account.bank.statement.line |
198 | + :param dict line: read of the concerned account.bank.statement.line |
199 | :return: |
200 | A dict of value that can be passed directly to the write method of |
201 | the statement line or {} |
202 | {'partner_id': value, |
203 | - 'account_id' : value, |
204 | + 'account_id': value, |
205 | |
206 | ...} |
207 | """ |
208 | - st_obj = self.pool['account.bank.statement.line'] |
209 | - st_line = st_obj.browse(cr, uid, line_id, context=context) |
210 | - res = {} |
211 | - inv_obj = self.pool.get('account.invoice') |
212 | - if st_line: |
213 | - inv_id = inv_obj.search(cr, |
214 | - uid, |
215 | - [('supplier_invoice_number', '=', st_line.ref), |
216 | - ('type', 'in', ('in_invoice', 'in_refund'))], |
217 | - context=context) |
218 | - if inv_id: |
219 | - if len(inv_id) == 1: |
220 | - inv = inv_obj.browse(cr, uid, inv_id[0], context=context) |
221 | - res['partner_id'] = inv.partner_id.id |
222 | - else: |
223 | - raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more ' |
224 | - 'than one partner.') % (st_line.name, st_line.ref)) |
225 | - st_vals = st_obj.get_values_for_line(cr, |
226 | - uid, |
227 | - profile_id=st_line.statement_id.profile_id.id, |
228 | - partner_id=res.get('partner_id', False), |
229 | - line_type="supplier", |
230 | - amount=st_line.amount, |
231 | - context=context) |
232 | - res.update(st_vals) |
233 | - return res |
234 | + return self._from_invoice(cr, uid, line, 'supplier', context=context) |
235 | |
236 | - def get_from_ref_and_invoice(self, cr, uid, line_id, context=None): |
237 | + # Should be private but data are initialised with no update XML |
238 | + def get_from_ref_and_invoice(self, cr, uid, line, context=None): |
239 | """ |
240 | Match the partner based on the invoice number and the reference of the statement |
241 | line. Then, call the generic get_values_for_line method to complete other values. |
242 | If more than one partner matched, raise the ErrorTooManyPartner error. |
243 | |
244 | - :param int/long line_id: id of the concerned account.bank.statement.line |
245 | + :param dict line: read of the concerned account.bank.statement.line |
246 | :return: |
247 | A dict of value that can be passed directly to the write method of |
248 | the statement line or {} |
249 | {'partner_id': value, |
250 | - 'account_id' : value, |
251 | + 'account_id': value, |
252 | ...} |
253 | """ |
254 | - st_obj = self.pool.get('account.bank.statement.line') |
255 | - st_line = st_obj.browse(cr, uid, line_id, context=context) |
256 | - res = {} |
257 | - if st_line: |
258 | - inv_obj = self.pool.get('account.invoice') |
259 | - inv_id = inv_obj.search(cr, |
260 | - uid, |
261 | - [('number', '=', st_line.ref)], |
262 | - context=context) |
263 | - if inv_id: |
264 | - if len(inv_id) == 1: |
265 | - inv = inv_obj.browse(cr, uid, inv_id[0], context=context) |
266 | - res['partner_id'] = inv.partner_id.id |
267 | - else: |
268 | - raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more ' |
269 | - 'than one partner.') % (st_line.name, st_line.ref)) |
270 | - st_vals = st_obj.get_values_for_line(cr, |
271 | - uid, |
272 | - profile_id=st_line.statement_id.profile_id.id, |
273 | - partner_id=res.get('partner_id', False), |
274 | - line_type=st_line.type, |
275 | - amount=st_line.amount, |
276 | - context=context) |
277 | - res.update(st_vals) |
278 | - return res |
279 | + return self._from_invoice(cr, uid, line, 'customer', context=context) |
280 | |
281 | - def get_from_ref_and_so(self, cr, uid, line_id, context=None): |
282 | + # Should be private but data are initialised with no update XML |
283 | + def get_from_ref_and_so(self, cr, uid, st_line, context=None): |
284 | """ |
285 | Match the partner based on the SO number and the reference of the statement |
286 | line. Then, call the generic get_values_for_line method to complete other values. |
287 | If more than one partner matched, raise the ErrorTooManyPartner error. |
288 | |
289 | - :param int/long line_id: id of the concerned account.bank.statement.line |
290 | + :param int/long st_line: read of the concerned account.bank.statement.line |
291 | :return: |
292 | A dict of value that can be passed directly to the write method of |
293 | the statement line or {} |
294 | {'partner_id': value, |
295 | - 'account_id' : value, |
296 | + 'account_id': value, |
297 | |
298 | ...} |
299 | """ |
300 | st_obj = self.pool.get('account.bank.statement.line') |
301 | - st_line = st_obj.browse(cr, uid, line_id, context=context) |
302 | res = {} |
303 | if st_line: |
304 | so_obj = self.pool.get('sale.order') |
305 | - so_id = so_obj.search( |
306 | - cr, |
307 | - uid, |
308 | - [('name', '=', st_line.ref)], |
309 | - context=context) |
310 | + so_id = so_obj.search(cr, |
311 | + uid, |
312 | + [('name', '=', st_line['ref'])], |
313 | + context=context) |
314 | if so_id: |
315 | if so_id and len(so_id) == 1: |
316 | so = so_obj.browse(cr, uid, so_id[0], context=context) |
317 | res['partner_id'] = so.partner_id.id |
318 | elif so_id and len(so_id) > 1: |
319 | - raise ErrorTooManyPartner( |
320 | - _('Line named "%s" (Ref:%s) was matched by more ' |
321 | - 'than one partner.') % |
322 | - (st_line.name, st_line.ref)) |
323 | - st_vals = st_obj.get_values_for_line( |
324 | - cr, |
325 | - uid, |
326 | - profile_id=st_line.statement_id.profile_id.id, |
327 | - partner_id=res.get('partner_id', False), |
328 | - line_type=st_line.type, |
329 | - amount=st_line.amount, |
330 | - context=context) |
331 | + raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more ' |
332 | + 'than one partner while looking on SO by ref.') % |
333 | + (st_line['name'], st_line['ref'])) |
334 | + st_vals = st_obj.get_values_for_line(cr, |
335 | + uid, |
336 | + profile_id=st_line['profile_id'], |
337 | + master_account_id=st_line['master_account_id'], |
338 | + partner_id=res.get('partner_id', False), |
339 | + line_type='customer', |
340 | + amount=st_line['amount'] if st_line['amount'] else 0.0, |
341 | + context=context) |
342 | res.update(st_vals) |
343 | return res |
344 | |
345 | - def get_from_label_and_partner_field(self, cr, uid, line_id, context=None): |
346 | + # Should be private but data are initialised with no update XML |
347 | + def get_from_label_and_partner_field(self, cr, uid, st_line, context=None): |
348 | """ |
349 | Match the partner based on the label field of the statement line |
350 | and the text defined in the 'bank_statement_label' field of the partner. |
351 | @@ -261,12 +270,12 @@ |
352 | get_values_for_line method to complete other values. |
353 | If more than one partner matched, raise the ErrorTooManyPartner error. |
354 | |
355 | - :param int/long line_id: id of the concerned account.bank.statement.line |
356 | + :param dict st_line: read of the concerned account.bank.statement.line |
357 | :return: |
358 | A dict of value that can be passed directly to the write method of |
359 | the statement line or {} |
360 | {'partner_id': value, |
361 | - 'account_id' : value, |
362 | + 'account_id': value, |
363 | |
364 | ...} |
365 | """ |
366 | @@ -283,7 +292,7 @@ |
367 | partner_ids = partner_obj.search(cr, |
368 | uid, |
369 | [('bank_statement_label', '!=', False)]) |
370 | - line_ids = tuple(x.id for x in context.get('line_ids', [])) |
371 | + line_ids = context.get('line_ids', []) |
372 | for partner in partner_obj.browse(cr, uid, partner_ids, context=context): |
373 | vals = '|'.join(re.escape(x.strip()) for x in partner.bank_statement_label.split(';')) |
374 | or_regex = ".*%s*." % vals |
375 | @@ -294,70 +303,71 @@ |
376 | pairs = cr.fetchall() |
377 | for pair in pairs: |
378 | context['label_memoizer'][pair[0]].append(partner) |
379 | - st_line = st_obj.browse(cr, uid, line_id, context=context) |
380 | - if st_line and st_line.id in context['label_memoizer']: |
381 | - found_partner = context['label_memoizer'][st_line.id] |
382 | + if st_line['id'] in context['label_memoizer']: |
383 | + found_partner = context['label_memoizer'][st_line['id']] |
384 | if len(found_partner) > 1: |
385 | raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by ' |
386 | - 'more than one partner.') % |
387 | - (st_line.name, st_line.ref)) |
388 | + 'more than one partner while looking on partner label') % |
389 | + (st_line['name'], st_line['ref'])) |
390 | res['partner_id'] = found_partner[0].id |
391 | st_vals = st_obj.get_values_for_line(cr, |
392 | uid, |
393 | - profile_id=st_line.statement_id.profile_id.id, |
394 | + profile_id=st_line['profile_id'], |
395 | + master_account_id=st_line['master_account_id'], |
396 | partner_id=found_partner[0].id, |
397 | - line_type=st_line.type, |
398 | - amount=st_line.amount, |
399 | + line_type=False, |
400 | + amount=st_line['amount'] if st_line['amount'] else 0.0, |
401 | context=context) |
402 | res.update(st_vals) |
403 | return res |
404 | |
405 | - def get_from_label_and_partner_name(self, cr, uid, line_id, context=None): |
406 | + def get_from_label_and_partner_name(self, cr, uid, st_line, context=None): |
407 | """ |
408 | Match the partner based on the label field of the statement line |
409 | and the name of the partner. |
410 | Then, call the generic get_values_for_line method to complete other values. |
411 | If more than one partner matched, raise the ErrorTooManyPartner error. |
412 | |
413 | - :param int/long line_id: id of the concerned account.bank.statement.line |
414 | + :param dict st_line: read of the concerned account.bank.statement.line |
415 | :return: |
416 | A dict of value that can be passed directly to the write method of |
417 | the statement line or {} |
418 | {'partner_id': value, |
419 | - 'account_id' : value, |
420 | + 'account_id': value, |
421 | |
422 | ...} |
423 | """ |
424 | - # This Method has not been tested yet ! |
425 | res = {} |
426 | + # We memoize allowed partner |
427 | + if not context.get('partner_memoizer'): |
428 | + context['partner_memoizer'] = tuple(self.pool['res.partner'].search(cr, uid, [])) |
429 | + if not context['partner_memoizer']: |
430 | + return res |
431 | st_obj = self.pool.get('account.bank.statement.line') |
432 | - st_line = st_obj.browse(cr, uid, line_id, context=context) |
433 | - if st_line: |
434 | - sql = "SELECT id FROM res_partner WHERE name ~* %s" |
435 | - pattern = ".*%s.*" % re.escape(st_line.label) |
436 | - cr.execute(sql, (pattern,)) |
437 | - result = cr.fetchall() |
438 | - if not result: |
439 | - return res |
440 | - if len(result) > 1: |
441 | - raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more ' |
442 | - 'than one partner.') % |
443 | - (st_line.name, st_line.ref)) |
444 | - res['partner_id'] = result[0][0] if result else False |
445 | - if res: |
446 | - st_vals = st_obj.get_values_for_line( |
447 | - cr, |
448 | - uid, |
449 | - profile_id=st_line.statement_id.profile_id.id, |
450 | - partner_id=res['partner_id'], |
451 | - line_type=st_line.type, |
452 | - amount=st_line.amount, |
453 | - context=context) |
454 | - res.update(st_vals) |
455 | + sql = "SELECT id FROM res_partner WHERE name ~* %s and id in %s" |
456 | + pattern = ".*%s.*" % re.escape(st_line['name']) |
457 | + cr.execute(sql, (pattern, context['partner_memoizer'])) |
458 | + result = cr.fetchall() |
459 | + if not result: |
460 | + return res |
461 | + if len(result) > 1: |
462 | + raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more ' |
463 | + 'than one partner while looking on partner by name') % |
464 | + (st_line['name'], st_line['ref'])) |
465 | + res['partner_id'] = result[0][0] |
466 | + st_vals = st_obj.get_values_for_line(cr, |
467 | + uid, |
468 | + profile_id=st_line['porfile_id'], |
469 | + master_account_id=st_line['master_account_id'], |
470 | + partner_id=res['partner_id'], |
471 | + line_type=False, |
472 | + amount=st_line['amount'] if st_line['amount'] else 0.0, |
473 | + context=context) |
474 | + res.update(st_vals) |
475 | return res |
476 | |
477 | |
478 | -class AccountStatementLine(Model): |
479 | +class AccountStatementLine(orm.Model): |
480 | """ |
481 | Add sparse field on the statement line to allow to store all the |
482 | bank infos that are given by a bank/office. You can then add you own in your |
483 | @@ -390,7 +400,7 @@ |
484 | 'already_completed': False, |
485 | } |
486 | |
487 | - def get_line_values_from_rules(self, cr, uid, ids, context=None): |
488 | + def _get_line_values_from_rules(self, cr, uid, line, rules, context=None): |
489 | """ |
490 | We'll try to find out the values related to the line based on rules setted on |
491 | the profile.. We will ignore line for which already_completed is ticked. |
492 | @@ -401,36 +411,17 @@ |
493 | {117009: {'partner_id': 100997, 'account_id': 489L}} |
494 | """ |
495 | profile_obj = self.pool.get('account.statement.profile') |
496 | - st_obj = self.pool.get('account.bank.statement.line') |
497 | - res = {} |
498 | - errors_stack = [] |
499 | - for line in self.browse(cr, uid, ids, context=context): |
500 | - if line.already_completed: |
501 | - continue |
502 | - try: |
503 | - # Take the default values |
504 | - res[line.id] = st_obj.get_values_for_line( |
505 | - cr, |
506 | - uid, |
507 | - profile_id=line.statement_id.profile_id.id, |
508 | - line_type=line.type, |
509 | - amount=line.amount, |
510 | - context=context) |
511 | - # Ask the rule |
512 | - vals = profile_obj.find_values_from_rules( |
513 | - cr, uid, line.statement_id.profile_id.id, line.id, context) |
514 | - # Merge the result |
515 | - res[line.id].update(vals) |
516 | - except ErrorTooManyPartner, exc: |
517 | - msg = "Line ID %s had following error: %s" % (line.id, exc.value) |
518 | - errors_stack.append(msg) |
519 | - if errors_stack: |
520 | - msg = u"\n".join(errors_stack) |
521 | - raise ErrorTooManyPartner(msg) |
522 | - return res |
523 | - |
524 | - |
525 | -class AccountBankSatement(Model): |
526 | + if line.get('already_completed'): |
527 | + return {} |
528 | + # Ask the rule |
529 | + vals = profile_obj._find_values_from_rules(cr, uid, rules, line, context) |
530 | + if vals: |
531 | + vals['id'] = line['id'] |
532 | + return vals |
533 | + return {} |
534 | + |
535 | + |
536 | +class AccountBankSatement(orm.Model): |
537 | """ |
538 | We add a basic button and stuff to support the auto-completion |
539 | of the bank statement once line have been imported or manually fullfill. |
540 | @@ -450,59 +441,82 @@ |
541 | :param int/long stat_id: ID of the account.bank.statement |
542 | :param char error_msg: Message to add |
543 | :number_imported int/long: Number of lines that have been completed |
544 | - :return : True |
545 | + :return True |
546 | """ |
547 | - error_log = "" |
548 | - user_name = self.pool.get('res.users').read( |
549 | - cr, uid, uid, ['name'], context=context)['name'] |
550 | - log = self.read( |
551 | - cr, uid, stat_id, ['completion_logs'], context=context)['completion_logs'] |
552 | - log_line = log and log.split("\n") or [] |
553 | + user_name = self.pool.get('res.users').read(cr, uid, uid, |
554 | + ['name'], context=context)['name'] |
555 | + |
556 | + log = self.read(cr, uid, stat_id, ['completion_logs'], |
557 | + context=context)['completion_logs'] |
558 | + log = log if log else "" |
559 | + |
560 | completion_date = datetime.datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT) |
561 | - if error_msg: |
562 | - error_log = error_msg |
563 | - log_line[0:0] = [completion_date + ' : ' |
564 | - + _("Bank Statement ID %s has %s lines completed by %s") % (stat_id, number_imported, user_name) |
565 | - + "\n" + error_log + "-------------" + "\n"] |
566 | - log = "\n".join(log_line) |
567 | - self.write(cr, uid, [stat_id], {'completion_logs': log}, context=context) |
568 | - self.message_post( |
569 | - cr, uid, |
570 | - [stat_id], |
571 | - body=_('Statement ID %s auto-completed for %s lines completed') % (stat_id, number_imported), |
572 | - context=context) |
573 | + message = (_("%s Bank Statement ID %s has %s lines completed by %s \n%s\n") % |
574 | + (completion_date, stat_id, number_imported, user_name, log)) |
575 | + self.write(cr, uid, [stat_id], {'completion_logs': message}, context=context) |
576 | + |
577 | + body = (_('Statement ID %s auto-completed for %s lines completed') % |
578 | + (stat_id, number_imported)), |
579 | + self.message_post(cr, uid, |
580 | + [stat_id], |
581 | + body=body, |
582 | + context=context) |
583 | return True |
584 | |
585 | def button_auto_completion(self, cr, uid, ids, context=None): |
586 | """ |
587 | Complete line with values given by rules and tic the already_completed |
588 | - checkbox so we won't compute them again unless the user untick them ! |
589 | + checkbox so we won't compute them again unless the user untick them! |
590 | """ |
591 | if context is None: |
592 | context = {} |
593 | - stat_line_obj = self.pool.get('account.bank.statement.line') |
594 | + stat_line_obj = self.pool['account.bank.statement.line'] |
595 | + profile_obj = self.pool.get('account.statement.profile') |
596 | compl_lines = 0 |
597 | + stat_line_obj.check_access_rule(cr, uid, [], 'create') |
598 | + stat_line_obj.check_access_rights(cr, uid, 'create', raise_exception=True) |
599 | for stat in self.browse(cr, uid, ids, context=context): |
600 | msg_lines = [] |
601 | ctx = context.copy() |
602 | - ctx['line_ids'] = stat.line_ids |
603 | - for line in stat.line_ids: |
604 | - res = {} |
605 | + ctx['line_ids'] = tuple((x.id for x in stat.line_ids)) |
606 | + b_profile = stat.profile_id |
607 | + rules = profile_obj._get_callable(cr, uid, b_profile, context=context) |
608 | + profile_id = b_profile.id # Only for perfo even it gains almost nothing |
609 | + master_account_id = b_profile.receivable_account_id |
610 | + master_account_id = master_account_id.id if master_account_id else False |
611 | + res = False |
612 | + for line in stat_line_obj.read(cr, uid, ctx['line_ids']): |
613 | try: |
614 | - res = stat_line_obj.get_line_values_from_rules( |
615 | - cr, uid, [line.id], context=ctx) |
616 | + # performance trick |
617 | + line['master_account_id'] = master_account_id |
618 | + line['profile_id'] = profile_id |
619 | + res = stat_line_obj._get_line_values_from_rules(cr, uid, line, |
620 | + rules, context=ctx) |
621 | if res: |
622 | compl_lines += 1 |
623 | except ErrorTooManyPartner, exc: |
624 | msg_lines.append(repr(exc)) |
625 | except Exception, exc: |
626 | msg_lines.append(repr(exc)) |
627 | - # vals = res and res.keys() or False |
628 | + error_type, error_value, trbk = sys.exc_info() |
629 | + st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value) |
630 | + st += ''.join(traceback.format_tb(trbk, 30)) |
631 | + _logger.error(st) |
632 | if res: |
633 | - vals = res[line.id] |
634 | - vals['already_completed'] = True |
635 | - stat_line_obj.write(cr, uid, [line.id], vals, context=ctx) |
636 | + #stat_line_obj.write(cr, uid, [line.id], vals, context=ctx) |
637 | + try: |
638 | + stat_line_obj._update_line(cr, uid, res, context=context) |
639 | + except Exception as exc: |
640 | + msg_lines.append(repr(exc)) |
641 | + error_type, error_value, trbk = sys.exc_info() |
642 | + st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value) |
643 | + st += ''.join(traceback.format_tb(trbk, 30)) |
644 | + _logger.error(st) |
645 | + # we can commit as it is not needed to be atomic |
646 | + # commiting here adds a nice perfo boost |
647 | + if not compl_lines % 500: |
648 | + cr.commit() |
649 | msg = u'\n'.join(msg_lines) |
650 | self.write_completion_log(cr, uid, stat.id, |
651 | - msg, compl_lines, context=context) |
652 | + msg, compl_lines, context=context) |
653 | return True |
654 | |
655 | === modified file 'account_statement_base_import/parser/generic_file_parser.py' |
656 | --- account_statement_base_import/parser/generic_file_parser.py 2013-02-26 08:22:25 +0000 |
657 | +++ account_statement_base_import/parser/generic_file_parser.py 2013-04-25 11:52:27 +0000 |
658 | @@ -29,6 +29,7 @@ |
659 | except: |
660 | raise Exception(_('Please install python lib xlrd')) |
661 | |
662 | + |
663 | def float_or_zero(val): |
664 | """ Conversion function used to manage |
665 | empty string into float usecase""" |
666 | @@ -82,14 +83,12 @@ |
667 | In this generic parser, the commission is given for every line, so we store it |
668 | for each one. |
669 | """ |
670 | - return { |
671 | - 'name': line.get('label', line.get('ref', '/')), |
672 | - 'date': line.get('date', datetime.datetime.now().date()), |
673 | - 'amount': line.get('amount', 0.0), |
674 | - 'ref': line.get('ref', '/'), |
675 | - 'label': line.get('label', ''), |
676 | - 'commission_amount': line.get('commission_amount', 0.0), |
677 | - } |
678 | + return {'name': line.get('label', line.get('ref', '/')), |
679 | + 'date': line.get('date', datetime.datetime.now().date()), |
680 | + 'amount': line.get('amount', 0.0), |
681 | + 'ref': line.get('ref', '/'), |
682 | + 'label': line.get('label', ''), |
683 | + 'commission_amount': line.get('commission_amount', 0.0)} |
684 | |
685 | def _post(self, *args, **kwargs): |
686 | """ |
687 | |
688 | === modified file 'account_statement_base_import/statement.py' |
689 | --- account_statement_base_import/statement.py 2013-02-26 08:45:03 +0000 |
690 | +++ account_statement_base_import/statement.py 2013-04-25 11:52:27 +0000 |
691 | @@ -18,14 +18,16 @@ |
692 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
693 | # |
694 | ############################################################################## |
695 | +import sys |
696 | +import traceback |
697 | + |
698 | +import psycopg2 |
699 | |
700 | from openerp.tools.translate import _ |
701 | import datetime |
702 | from openerp.osv.orm import Model |
703 | from openerp.osv import fields, osv |
704 | from parser import new_bank_statement_parser |
705 | -import sys |
706 | -import traceback |
707 | |
708 | |
709 | class AccountStatementProfil(Model): |
710 | @@ -43,7 +45,8 @@ |
711 | help="Tic that box to automatically launch the completion " |
712 | "on each imported file using this profile."), |
713 | 'last_import_date': fields.datetime("Last Import Date"), |
714 | - 'rec_log': fields.text('log', readonly=True, deprecated=True), |
715 | + # we remove deprecated as it floods logs in standard/warning level sob... |
716 | + 'rec_log': fields.text('log', readonly=True), # Deprecated |
717 | 'import_type': fields.selection( |
718 | get_import_type_selection, |
719 | 'Type of import', |
720 | @@ -64,7 +67,8 @@ |
721 | self.message_post(cr, |
722 | uid, |
723 | ids, |
724 | - body=_('Statement ID %s have been imported with %s lines.') % (statement_id, num_lines), |
725 | + body=_('Statement ID %s have been imported with %s lines.') % |
726 | + (statement_id, num_lines), |
727 | context=context) |
728 | return True |
729 | |
730 | @@ -79,7 +83,7 @@ |
731 | :param: browse_record of the current parser |
732 | :param: result_row_list: [{'key':value}] |
733 | :param: profile: browserecord of account.statement.profile |
734 | - :param: statement_id : int/long of the current importing statement ID |
735 | + :param: statement_id: int/long of the current importing statement ID |
736 | :param: context: global context |
737 | return: dict of vals that will be passed to create method of statement line. |
738 | """ |
739 | @@ -98,7 +102,7 @@ |
740 | 'account_id': commission_account_id, |
741 | 'ref': 'commission', |
742 | 'analytic_account_id': commission_analytic_id, |
743 | - # !! We set the already_completed so auto-completion will not update those values ! |
744 | + # !! We set the already_completed so auto-completion will not update those values! |
745 | 'already_completed': True, |
746 | } |
747 | return comm_values |
748 | @@ -116,18 +120,32 @@ |
749 | :param int/long account_payable: ID of the receivable account to use |
750 | :param int/long account_receivable: ID of the payable account to use |
751 | :param int/long statement_id: ID of the concerned account.bank.statement |
752 | - :return : dict of vals that will be passed to create method of statement line. |
753 | + :return: dict of vals that will be passed to create method of statement line. |
754 | """ |
755 | statement_obj = self.pool.get('account.bank.statement') |
756 | values = parser_vals |
757 | values['statement_id'] = statement_id |
758 | - values['account_id'] = statement_obj.get_account_for_counterpart( |
759 | - cr, |
760 | - uid, |
761 | - parser_vals['amount'], |
762 | - account_receivable, |
763 | - account_payable |
764 | - ) |
765 | + values['account_id'] = statement_obj.get_account_for_counterpart(cr, |
766 | + uid, |
767 | + parser_vals['amount'], |
768 | + account_receivable, |
769 | + account_payable) |
770 | + |
771 | + date = values.get('date') |
772 | + period_memoizer = context.get('period_memoizer') |
773 | + if not period_memoizer: |
774 | + period_memoizer = {} |
775 | + context['period_memoizer'] = period_memoizer |
776 | + if period_memoizer.get(date): |
777 | + values['period_id'] = period_memoizer[date] |
778 | + else: |
779 | + # This is awfully slow... |
780 | + periods = self.pool.get('account.period').find(cr, uid, |
781 | + dt=values.get('date'), |
782 | + context=context) |
783 | + values['period_id'] = periods[0] |
784 | + period_memoizer[date] = periods[0] |
785 | + values['type'] = 'general' |
786 | return values |
787 | |
788 | def statement_import(self, cr, uid, ids, profile_id, file_stream, ftype="csv", context=None): |
789 | @@ -148,75 +166,85 @@ |
790 | attachment_obj = self.pool.get('ir.attachment') |
791 | prof_obj = self.pool.get("account.statement.profile") |
792 | if not profile_id: |
793 | - raise osv.except_osv( |
794 | - _("No Profile !"), |
795 | - _("You must provide a valid profile to import a bank statement !")) |
796 | + raise osv.except_osv(_("No Profile!"), |
797 | + _("You must provide a valid profile to import a bank statement!")) |
798 | prof = prof_obj.browse(cr, uid, profile_id, context=context) |
799 | |
800 | parser = new_bank_statement_parser(prof.import_type, ftype=ftype) |
801 | result_row_list = parser.parse(file_stream) |
802 | - # Check all key are present in account.bank.statement.line !! |
803 | + # Check all key are present in account.bank.statement.line!! |
804 | + if not result_row_list: |
805 | + raise osv.except_osv(_("Nothing to import"), |
806 | + _("The file is empty")) |
807 | parsed_cols = parser.get_st_line_vals(result_row_list[0]).keys() |
808 | for col in parsed_cols: |
809 | if col not in statement_line_obj._columns: |
810 | - raise osv.except_osv( |
811 | - _("Missing column !"), |
812 | - _("Column %s you try to import is not " |
813 | - "present in the bank statement line !") % col) |
814 | + raise osv.except_osv(_("Missing column!"), |
815 | + _("Column %s you try to import is not " |
816 | + "present in the bank statement line!") % col) |
817 | |
818 | - statement_id = statement_obj.create( |
819 | - cr, uid, {'profile_id': prof.id}, context=context) |
820 | - account_receivable, account_payable = statement_obj.get_default_pay_receiv_accounts( |
821 | - cr, uid, context) |
822 | + statement_id = statement_obj.create(cr, uid, |
823 | + {'profile_id': prof.id}, |
824 | + context=context) |
825 | + if prof.receivable_account_id: |
826 | + account_receivable = account_payable = prof.receivable_account_id.id |
827 | + else: |
828 | + account_receivable, account_payable = statement_obj.get_default_pay_receiv_accounts( |
829 | + cr, uid, context) |
830 | try: |
831 | # Record every line in the bank statement and compute the global commission |
832 | # based on the commission_amount column |
833 | statement_store = [] |
834 | for line in result_row_list: |
835 | parser_vals = parser.get_st_line_vals(line) |
836 | - values = self.prepare_statetement_lines_vals( |
837 | - cr, uid, parser_vals, account_payable, |
838 | - account_receivable, statement_id, context) |
839 | - # we finally create the line in system |
840 | - statement_store.append((0, 0, values)) |
841 | + values = self.prepare_statetement_lines_vals(cr, uid, parser_vals, account_payable, |
842 | + account_receivable, statement_id, context) |
843 | + statement_store.append(values) |
844 | + # Hack to bypass ORM poor perfomance. Sob... |
845 | + statement_line_obj._insert_lines(cr, uid, statement_store, context=context) |
846 | + |
847 | # Build and create the global commission line for the whole statement |
848 | - statement_obj.write(cr, uid, [statement_id], |
849 | - {'line_ids': statement_store}, context=context) |
850 | comm_vals = self.prepare_global_commission_line_vals(cr, uid, parser, result_row_list, |
851 | prof, statement_id, context) |
852 | if comm_vals: |
853 | statement_line_obj.create(cr, uid, comm_vals, context=context) |
854 | - |
855 | - attachment_obj.create( |
856 | - cr, |
857 | - uid, |
858 | - { |
859 | - 'name': 'statement file', |
860 | - 'datas': file_stream, |
861 | - 'datas_fname': "%s.%s" % ( |
862 | - datetime.datetime.now().date(), |
863 | - ftype), |
864 | - 'res_model': 'account.bank.statement', |
865 | - 'res_id': statement_id, |
866 | - }, |
867 | - context=context |
868 | - ) |
869 | - # If user ask to launch completion at end of import, do it ! |
870 | + else: |
871 | + # Trigger store field computation if someone has better idea |
872 | + start_bal = statement_obj.read(cr, uid, statement_id, |
873 | + ['balance_start'], |
874 | + context=context) |
875 | + start_bal = start_bal['balance_start'] |
876 | + statement_obj.write(cr, uid, [statement_id], |
877 | + {'balance_start': start_bal}) |
878 | + |
879 | + attachment_obj.create(cr, |
880 | + uid, |
881 | + {'name': 'statement file', |
882 | + 'datas': file_stream, |
883 | + 'datas_fname': "%s.%s" % ( |
884 | + datetime.datetime.now().date(), |
885 | + ftype), |
886 | + 'res_model': 'account.bank.statement', |
887 | + 'res_id': statement_id}, |
888 | + context=context) |
889 | + |
890 | + # If user ask to launch completion at end of import, do it! |
891 | if prof.launch_import_completion: |
892 | statement_obj.button_auto_completion(cr, uid, [statement_id], context) |
893 | |
894 | # Write the needed log infos on profile |
895 | - self.write_logs_after_import( |
896 | - cr, uid, prof.id, statement_id, len(result_row_list), context) |
897 | + self.write_logs_after_import(cr, uid, prof.id, |
898 | + statement_id, |
899 | + len(result_row_list), |
900 | + context) |
901 | |
902 | except Exception: |
903 | statement_obj.unlink(cr, uid, [statement_id], context=context) |
904 | error_type, error_value, trbk = sys.exc_info() |
905 | st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value) |
906 | st += ''.join(traceback.format_tb(trbk, 30)) |
907 | - raise osv.except_osv( |
908 | - _("Statement import error"), |
909 | - _("The statement cannot be created : %s") % st) |
910 | + raise osv.except_osv(_("Statement import error"), |
911 | + _("The statement cannot be created: %s") % st) |
912 | return statement_id |
913 | |
914 | |
915 | @@ -228,6 +256,45 @@ |
916 | """ |
917 | _inherit = "account.bank.statement.line" |
918 | |
919 | + def _get_available_columns(self, statement_store): |
920 | + """Return writeable by SQL columns""" |
921 | + statement_line_obj = self.pool['account.bank.statement.line'] |
922 | + model_cols = statement_line_obj._columns |
923 | + avail = [k for k, col in model_cols.iteritems() if not hasattr(col, '_fnct')] |
924 | + keys = [k for k in statement_store[0].keys() if k in avail] |
925 | + keys.sort() |
926 | + return keys |
927 | + |
928 | + def _insert_lines(self, cr, uid, statement_store, context=None): |
929 | + """ Do raw insert into database because ORM is awfully slow |
930 | + when doing batch write. It is a shame that batch function |
931 | + does not exist""" |
932 | + statement_line_obj = self.pool['account.bank.statement.line'] |
933 | + statement_line_obj.check_access_rule(cr, uid, [], 'create') |
934 | + statement_line_obj.check_access_rights(cr, uid, 'create', raise_exception=True) |
935 | + cols = self._get_available_columns(statement_store) |
936 | + tmp_vals = (', '.join(cols), ', '.join(['%%(%s)s' % i for i in cols])) |
937 | + sql = "INSERT INTO account_bank_statement_line (%s) VALUES (%s);" % tmp_vals |
938 | + try: |
939 | + cr.executemany(sql, tuple(statement_store)) |
940 | + except psycopg2.Error as sql_err: |
941 | + cr.rollback() |
942 | + raise osv.except_osv(_("ORM bypass error"), |
943 | + sql_err.pgerror) |
944 | + |
945 | + def _update_line(self, cr, uid, vals, context=None): |
946 | + """ Do raw update into database because ORM is awfully slow |
947 | + when cheking security.""" |
948 | + cols = self._get_available_columns([vals]) |
949 | + tmp_vals = (', '.join(['%s = %%(%s)s' % (i, i) for i in cols])) |
950 | + sql = "UPDATE account_bank_statement_line SET %s where id = %%(id)s;" % tmp_vals |
951 | + try: |
952 | + cr.execute(sql, vals) |
953 | + except psycopg2.Error as sql_err: |
954 | + cr.rollback() |
955 | + raise osv.except_osv(_("ORM bypass error"), |
956 | + sql_err.pgerror) |
957 | + |
958 | _columns = { |
959 | 'commission_amount': fields.sparse( |
960 | type='float', |
961 | |
962 | === modified file 'account_statement_ext/__openerp__.py' |
963 | --- account_statement_ext/__openerp__.py 2013-02-14 08:13:49 +0000 |
964 | +++ account_statement_ext/__openerp__.py 2013-04-25 11:52:27 +0000 |
965 | @@ -25,11 +25,9 @@ |
966 | 'maintainer': 'Camptocamp', |
967 | 'category': 'Finance', |
968 | 'complexity': 'normal', |
969 | - 'depends': [ |
970 | - 'account', |
971 | - 'report_webkit', |
972 | - 'account_voucher' |
973 | - ], |
974 | + 'depends': ['account', |
975 | + 'report_webkit', |
976 | + 'account_voucher'], |
977 | 'description': """ |
978 | Improve the basic bank statement, by adding various new features, |
979 | and help dealing with huge volume of reconciliation through payment offices such as Paypal, Lazer, |
980 | @@ -65,7 +63,6 @@ |
981 | 4) Remove the period on the bank statement, and compute it for each line based on their date instead. |
982 | It also adds this feature in the voucher in order to compute the period correctly. |
983 | |
984 | - |
985 | 5) Cancelling a bank statement is much more easy and will cancel all related entries, unreconcile them, |
986 | and finally delete them. |
987 | |
988 | @@ -88,4 +85,4 @@ |
989 | 'auto_install': False, |
990 | 'license': 'AGPL-3', |
991 | 'active': False, |
992 | -} |
993 | + } |
994 | |
995 | === modified file 'account_statement_ext/i18n/fr.po' |
996 | --- account_statement_ext/i18n/fr.po 2012-12-13 13:57:29 +0000 |
997 | +++ account_statement_ext/i18n/fr.po 2013-04-25 11:52:27 +0000 |
998 | @@ -41,7 +41,7 @@ |
999 | #. module: account_statement_ext |
1000 | #: code:addons/account_statement_ext/statement.py:361 |
1001 | #, python-format |
1002 | -msgid "Configuration Error !" |
1003 | +msgid "Configuration Error!" |
1004 | msgstr "Erreur de configuration !" |
1005 | |
1006 | #. module: account_statement_ext |
1007 | @@ -64,7 +64,7 @@ |
1008 | #: code:addons/account_statement_ext/statement.py:307 |
1009 | #: code:addons/account_statement_ext/statement.py:372 |
1010 | #, python-format |
1011 | -msgid "Error !" |
1012 | +msgid "Error!" |
1013 | msgstr "Erreur !" |
1014 | |
1015 | #. module: account_statement_ext |
1016 | |
1017 | === modified file 'account_statement_ext/statement.py' |
1018 | --- account_statement_ext/statement.py 2013-03-01 14:33:32 +0000 |
1019 | +++ account_statement_ext/statement.py 2013-04-25 11:52:27 +0000 |
1020 | @@ -1,4 +1,4 @@ |
1021 | -# -*- coding: utf-8 -*- |
1022 | +#-*- coding: utf-8 -*- |
1023 | ############################################################################## |
1024 | # |
1025 | # Author: Nicolas Bessi, Joel Grand-Guillaume |
1026 | @@ -18,12 +18,26 @@ |
1027 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
1028 | # |
1029 | ############################################################################## |
1030 | - |
1031 | +import openerp.addons.account.account_bank_statement as stat_mod |
1032 | from openerp.osv.orm import Model |
1033 | from openerp.osv import fields, osv |
1034 | from openerp.tools.translate import _ |
1035 | |
1036 | |
1037 | +# Monkey patch to fix bad write implementation... |
1038 | +def fixed_write(self, cr, uid, ids, vals, context=None): |
1039 | + """ Fix performance desing of original function |
1040 | + Ideally we should use a real PostgreSQL sequence or serial fields. |
1041 | + I will do it when I have time.""" |
1042 | + res = super(stat_mod.account_bank_statement, self).write(cr, uid, ids, |
1043 | + vals, context=context) |
1044 | + cr.execute("UPDATE account_bank_statement_line" |
1045 | + " SET sequence = account_bank_statement_line.id + 1" |
1046 | + " where statement_id in %s", (tuple(ids),)) |
1047 | + return res |
1048 | +stat_mod.account_bank_statement.write = fixed_write |
1049 | + |
1050 | + |
1051 | class AccountStatementProfil(Model): |
1052 | """ |
1053 | A Profile will contain all infos related to the type of |
1054 | @@ -44,38 +58,45 @@ |
1055 | "commission move (and optionaly on the counterpart " |
1056 | "of the intermediate/banking move if you tick the " |
1057 | "corresponding checkbox)."), |
1058 | + |
1059 | 'journal_id': fields.many2one( |
1060 | 'account.journal', |
1061 | 'Financial journal to use for transaction', |
1062 | required=True), |
1063 | + |
1064 | 'commission_account_id': fields.many2one( |
1065 | 'account.account', |
1066 | 'Commission account', |
1067 | required=True), |
1068 | + |
1069 | 'commission_analytic_id': fields.many2one( |
1070 | 'account.analytic.account', |
1071 | 'Commission analytic account'), |
1072 | + |
1073 | 'receivable_account_id': fields.many2one( |
1074 | 'account.account', |
1075 | 'Force Receivable/Payable Account', |
1076 | help="Choose a receivable account to force the default " |
1077 | "debit/credit account (eg. an intermediat bank account " |
1078 | "instead of default debitors)."), |
1079 | + |
1080 | 'force_partner_on_bank': fields.boolean( |
1081 | 'Force partner on bank move', |
1082 | help="Tick that box if you want to use the credit " |
1083 | "institute partner in the counterpart of the " |
1084 | "intermediate/banking move."), |
1085 | + |
1086 | 'balance_check': fields.boolean( |
1087 | 'Balance check', |
1088 | help="Tick that box if you want OpenERP to control " |
1089 | "the start/end balance before confirming a bank statement. " |
1090 | - "If don't ticked, no balance control will be done." |
1091 | - ), |
1092 | - 'bank_statement_prefix': fields.char( |
1093 | - 'Bank Statement Prefix', size=32), |
1094 | - 'bank_statement_ids': fields.one2many( |
1095 | - 'account.bank.statement', 'profile_id', 'Bank Statement Imported'), |
1096 | + "If don't ticked, no balance control will be done."), |
1097 | + |
1098 | + 'bank_statement_prefix': fields.char('Bank Statement Prefix', size=32), |
1099 | + |
1100 | + 'bank_statement_ids': fields.one2many('account.bank.statement', |
1101 | + 'profile_id', |
1102 | + 'Bank Statement Imported'), |
1103 | 'company_id': fields.many2one('res.company', 'Company'), |
1104 | } |
1105 | |
1106 | @@ -86,7 +107,7 @@ |
1107 | return True |
1108 | |
1109 | _constraints = [ |
1110 | - (_check_partner, "You need to put a partner if you tic the 'Force partner on bank move' !", []), |
1111 | + (_check_partner, "You need to put a partner if you tic the 'Force partner on bank move'!", []), |
1112 | ] |
1113 | |
1114 | |
1115 | @@ -166,11 +187,11 @@ |
1116 | """ |
1117 | for statement in self.browse(cr, uid, ids, context=context): |
1118 | if (statement.period_id and |
1119 | - statement.company_id.id != statement.period_id.company_id.id): |
1120 | + statement.company_id.id != statement.period_id.company_id.id): |
1121 | return False |
1122 | for line in statement.line_ids: |
1123 | if (line.period_id and |
1124 | - statement.company_id.id != line.period_id.company_id.id): |
1125 | + statement.company_id.id != line.period_id.company_id.id): |
1126 | return False |
1127 | return True |
1128 | |
1129 | @@ -244,8 +265,10 @@ |
1130 | create the move from. |
1131 | :return: int/long of the res.partner to use as counterpart |
1132 | """ |
1133 | - bank_partner_id = super(AccountBankSatement, self).\ |
1134 | - _get_counter_part_partner(cr, uid, st_line, context=context) |
1135 | + bank_partner_id = super(AccountBankSatement, self)._get_counter_part_partner(cr, |
1136 | + uid, |
1137 | + st_line, |
1138 | + context=context) |
1139 | # get the right partner according to the chosen profil |
1140 | if st_line.statement_id.profile_id.force_partner_on_bank: |
1141 | bank_partner_id = st_line.statement_id.profile_id.partner_id.id |
1142 | @@ -285,7 +308,7 @@ |
1143 | We have to copy paste a big block of code, changing the error |
1144 | stack + managing period from date. |
1145 | |
1146 | - TODO: Log the error in a bank statement field instead of using a popup ! |
1147 | + TODO: Log the error in a bank statement field instead of using a popup! |
1148 | """ |
1149 | for st in self.browse(cr, uid, ids, context=context): |
1150 | |
1151 | @@ -297,8 +320,8 @@ |
1152 | self.balance_check(cr, uid, st.id, journal_type=j_type, context=context) |
1153 | if (not st.journal_id.default_credit_account_id) \ |
1154 | or (not st.journal_id.default_debit_account_id): |
1155 | - raise osv.except_osv(_('Configuration Error !'), |
1156 | - _('Please verify that an account is defined in the journal.')) |
1157 | + raise osv.except_osv(_('Configuration Error!'), |
1158 | + _('Please verify that an account is defined in the journal.')) |
1159 | |
1160 | if not st.name == '/': |
1161 | st_number = st.name |
1162 | @@ -308,20 +331,24 @@ |
1163 | # End Changes |
1164 | for line in st.move_line_ids: |
1165 | if line.state != 'valid': |
1166 | - raise osv.except_osv(_('Error !'), |
1167 | - _('The account entries lines are not in valid state.')) |
1168 | + raise osv.except_osv(_('Error!'), |
1169 | + _('The account entries lines are not in valid state.')) |
1170 | # begin changes |
1171 | errors_stack = [] |
1172 | for st_line in st.line_ids: |
1173 | try: |
1174 | if st_line.analytic_account_id: |
1175 | if not st.journal_id.analytic_journal_id: |
1176 | - raise osv.except_osv(_('No Analytic Journal !'), |
1177 | - _("You have to assign an analytic journal on the '%s' journal!") % (st.journal_id.name,)) |
1178 | + raise osv.except_osv(_('No Analytic Journal!'), |
1179 | + _("You have to assign an analytic" |
1180 | + " journal on the '%s' journal!") % st.journal_id.name) |
1181 | if not st_line.amount: |
1182 | continue |
1183 | st_line_number = self.get_next_st_line_number(cr, uid, st_number, st_line, context) |
1184 | - self.create_move_from_st_line(cr, uid, st_line.id, company_currency_id, st_line_number, context) |
1185 | + self.create_move_from_st_line(cr, uid, st_line.id, |
1186 | + company_currency_id, |
1187 | + st_line_number, |
1188 | + context) |
1189 | except osv.except_osv, exc: |
1190 | msg = "Line ID %s with ref %s had following error: %s" % (st_line.id, st_line.ref, exc.value) |
1191 | errors_stack.append(msg) |
1192 | @@ -332,37 +359,97 @@ |
1193 | msg = u"\n".join(errors_stack) |
1194 | raise osv.except_osv(_('Error'), msg) |
1195 | #end changes |
1196 | - self.write(cr, uid, [st.id], { |
1197 | - 'name': st_number, |
1198 | - 'balance_end_real': st.balance_end |
1199 | - }, context=context) |
1200 | - self.message_post(cr, uid, [st.id], body=_('Statement %s confirmed, journal items were created.') % (st_number,), context=context) |
1201 | + self.write(cr, uid, [st.id], |
1202 | + {'name': st_number, |
1203 | + 'balance_end_real': st.balance_end}, |
1204 | + context=context) |
1205 | + body = _('Statement %s confirmed, journal items were created.') % st_number |
1206 | + self.message_post(cr, uid, [st.id], |
1207 | + body, |
1208 | + context=context) |
1209 | return self.write(cr, uid, ids, {'state': 'confirm'}, context=context) |
1210 | |
1211 | - def get_account_for_counterpart( |
1212 | - self, cr, uid, amount, account_receivable, account_payable): |
1213 | + def get_account_for_counterpart(self, cr, uid, amount, account_receivable, account_payable): |
1214 | + """For backward compatibility.""" |
1215 | + account_id, type = self.get_account_and_type_for_counterpart(cr, uid, amount, |
1216 | + account_receivable, |
1217 | + account_payable) |
1218 | + return account_id |
1219 | + |
1220 | + def _compute_type_from_partner_profile(self, cr, uid, partner_id, |
1221 | + default_type, context=None): |
1222 | + """Compute the statement line type |
1223 | + from partner profile (customer, supplier)""" |
1224 | + obj_partner = self.pool.get('res.partner') |
1225 | + part = obj_partner.browse(cr, uid, partner_id, context=context) |
1226 | + if part.supplier == part.customer: |
1227 | + return default_type |
1228 | + if part.supplier: |
1229 | + return 'supplier' |
1230 | + else: |
1231 | + return 'customer' |
1232 | + |
1233 | + def _compute_type_from_amount(self, cr, uid, amount): |
1234 | + """Compute the statement type based on amount""" |
1235 | + if amount in (None, False): |
1236 | + return 'general' |
1237 | + if amount < 0: |
1238 | + return 'supplier' |
1239 | + return 'customer' |
1240 | + |
1241 | + def get_type_for_counterpart(self, cr, uid, amount, partner_id=False): |
1242 | + """Give the amount and receive the type to use for the line. |
1243 | + The rules are: |
1244 | + - If the customer checkbox is checked on the found partner, type customer |
1245 | + - If the supplier checkbox is checked on the found partner, typewill be supplier |
1246 | + - If both checkbox are checked or none of them, it'll be based on the amount : |
1247 | + If amount is positif the type customer, |
1248 | + If amount is negativ, the type supplier |
1249 | + :param float: amount of the line |
1250 | + :param int/long: partner_id the partner id |
1251 | + :return: type as string: the default type to use: 'customer' or 'supplier'. |
1252 | + """ |
1253 | + s_line_type = self._compute_type_from_amount(cr, uid, amount) |
1254 | + if partner_id: |
1255 | + s_line_type = self._compute_type_from_partner_profile(cr, uid, |
1256 | + partner_id, s_line_type) |
1257 | + return s_line_type |
1258 | + |
1259 | + def get_account_and_type_for_counterpart(self, cr, uid, amount, account_receivable, |
1260 | + account_payable, partner_id=False): |
1261 | """ |
1262 | Give the amount, payable and receivable account (that can be found using |
1263 | get_default_pay_receiv_accounts method) and receive the one to use. This method |
1264 | should be use when there is no other way to know which one to take. |
1265 | + The rules are: |
1266 | + - If the customer checkbox is checked on the found partner, type and account will be customer and receivable |
1267 | + - If the supplier checkbox is checked on the found partner, type and account will be supplier and payable |
1268 | + - If both checkbox are checked or none of them, it'll be based on the amount : |
1269 | + If amount is positive, the type and account will be customer and receivable, |
1270 | + If amount is negative, the type and account will be supplier and payable |
1271 | + Note that we return the payable or receivable account from agrs and not from the optional partner_id |
1272 | + given! |
1273 | |
1274 | :param float: amount of the line |
1275 | :param int/long: account_receivable the receivable account |
1276 | :param int/long: account_payable the payable account |
1277 | - :return: int/long :the default account to be used by statement line as the counterpart |
1278 | - of the journal account depending on the amount. |
1279 | + :param int/long: partner_id the partner id |
1280 | + :return: dict with [account_id as int/long,type as string]: the default account to be used by |
1281 | + statement line as the counterpart of the journal account depending on the amount and the type |
1282 | + as 'customer' or 'supplier'. |
1283 | """ |
1284 | account_id = False |
1285 | - if amount >= 0: |
1286 | + ltype = self.get_type_for_counterpart(cr, uid, amount, partner_id=partner_id) |
1287 | + if ltype == 'supplier': |
1288 | + account_id = account_payable |
1289 | + else: |
1290 | account_id = account_receivable |
1291 | - else: |
1292 | - account_id = account_payable |
1293 | if not account_id: |
1294 | raise osv.except_osv( |
1295 | _('Can not determine account'), |
1296 | _('Please ensure that minimal properties are set') |
1297 | ) |
1298 | - return account_id |
1299 | + return [account_id, ltype] |
1300 | |
1301 | def get_default_pay_receiv_accounts(self, cr, uid, context=None): |
1302 | """ |
1303 | @@ -386,14 +473,11 @@ |
1304 | ('model', '=', 'res.partner')], |
1305 | context=context |
1306 | ) |
1307 | - property_ids = property_obj.search( |
1308 | - cr, |
1309 | - uid, |
1310 | - [('fields_id', 'in', model_fields_ids), |
1311 | - ('res_id', '=', False), |
1312 | - ], |
1313 | - context=context |
1314 | - ) |
1315 | + property_ids = property_obj.search(cr, |
1316 | + uid, |
1317 | + [('fields_id', 'in', model_fields_ids), |
1318 | + ('res_id', '=', False)], |
1319 | + context=context) |
1320 | |
1321 | for erp_property in property_obj.browse( |
1322 | cr, uid, property_ids, context=context): |
1323 | @@ -433,13 +517,10 @@ |
1324 | journal_id = import_config.journal_id.id |
1325 | account_id = import_config.journal_id.default_debit_account_id.id |
1326 | credit_partner_id = import_config.partner_id and import_config.partner_id.id or False |
1327 | - return {'value': |
1328 | - {'journal_id': journal_id, |
1329 | - 'account_id': account_id, |
1330 | - 'balance_check': import_config.balance_check, |
1331 | - 'credit_partner_id': credit_partner_id, |
1332 | - } |
1333 | - } |
1334 | + return {'value': {'journal_id': journal_id, |
1335 | + 'account_id': account_id, |
1336 | + 'balance_check': import_config.balance_check, |
1337 | + 'credit_partner_id': credit_partner_id}} |
1338 | |
1339 | |
1340 | class AccountBankSatementLine(Model): |
1341 | @@ -472,18 +553,24 @@ |
1342 | 'account_id': _get_default_account, |
1343 | } |
1344 | |
1345 | - def get_values_for_line(self, cr, uid, profile_id=False, partner_id=False, line_type=False, amount=False, context=None): |
1346 | + 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): |
1347 | """ |
1348 | Return the account_id to be used in the line of a bank statement. It'll base the result as follow: |
1349 | - If a receivable_account_id is set in the profile, return this value and type = general |
1350 | - - Elif line_type is given, take the partner receivable/payable property (payable if type= supplier, receivable |
1351 | + # TODO |
1352 | + - Elif how_get_type_account is set to force_supplier or force_customer, will take respectively payable and type=supplier, |
1353 | + receivable and type=customer otherwise |
1354 | + # END TODO |
1355 | + - Elif line_type is given, take the partner receivable/payable property (payable if type=supplier, receivable |
1356 | otherwise) |
1357 | - - Elif amount is given, take the partner receivable/payable property (receivable if amount >= 0.0, |
1358 | - payable otherwise). In that case, we also fullfill the type (receivable = customer, payable = supplier) |
1359 | - so it is easier for the accountant to know why the receivable/payable has been chosen |
1360 | + - Elif amount is given: |
1361 | + - If the customer checkbox is checked on the found partner, type and account will be customer and receivable |
1362 | + - If the supplier checkbox is checked on the found partner, type and account will be supplier and payable |
1363 | + - If both checkbox are checked or none of them, it'll be based on the amount : |
1364 | + If amount is positive, the type and account will be customer and receivable, |
1365 | + If amount is negative, the type and account will be supplier an payable |
1366 | - Then, if no partner are given we look and take the property from the company so we always give a value |
1367 | for account_id. Note that in that case, we return the receivable one. |
1368 | - |
1369 | :param int/long profile_id of the related bank statement |
1370 | :param int/long partner_id of the line |
1371 | :param char line_type: a value from: 'general', 'supplier', 'customer' |
1372 | @@ -499,79 +586,77 @@ |
1373 | res = {} |
1374 | obj_partner = self.pool.get('res.partner') |
1375 | obj_stat = self.pool.get('account.bank.statement') |
1376 | - line_type = receiv_account = pay_account = account_id = False |
1377 | + receiv_account = pay_account = account_id = False |
1378 | # If profile has a receivable_account_id, we return it in any case |
1379 | - if profile_id: |
1380 | + if master_account_id: |
1381 | + res['account_id'] = master_account_id |
1382 | + # We return general as default instead of get_type_for_counterpart |
1383 | + # for perfomance reasons as line_type is not a meaningfull value |
1384 | + # as account is forced |
1385 | + res['type'] = line_type if line_type else 'general' |
1386 | + return res |
1387 | + # To optimize we consider passing false means there is no account |
1388 | + # on profile |
1389 | + if profile_id and master_account_id is None: |
1390 | profile = self.pool.get("account.statement.profile").browse( |
1391 | - cr, uid, profile_id, context=context) |
1392 | + cr, uid, profile_id, context=context) |
1393 | if profile.receivable_account_id: |
1394 | - account_id = profile.receivable_account_id.id |
1395 | - line_type = 'general' |
1396 | + res['account_id'] = profile.receivable_account_id.id |
1397 | + # We return general as default instead of get_type_for_counterpart |
1398 | + # for perfomance reasons as line_type is not a meaningfull value |
1399 | + # as account is forced |
1400 | + res['type'] = line_type if line_type else 'general' |
1401 | return res |
1402 | - # If partner -> take from him |
1403 | + # If no account is available on profile you have to do the lookup |
1404 | + # This can be quite a performance killer as we read ir.properity fields |
1405 | if partner_id: |
1406 | part = obj_partner.browse(cr, uid, partner_id, context=context) |
1407 | pay_account = part.property_account_payable.id |
1408 | receiv_account = part.property_account_receivable.id |
1409 | # If no value, look on the default company property |
1410 | if not pay_account or not receiv_account: |
1411 | - receiv_account, pay_account = obj_stat.get_default_pay_receiv_accounts( |
1412 | - cr, uid, context=None) |
1413 | - # Now we have both pay and receive account, choose the one to use |
1414 | - # based on line_type first, then amount, otherwise take receivable one. |
1415 | - if line_type is not False: |
1416 | - if line_type == 'supplier': |
1417 | - account_id = pay_account |
1418 | - elif amount is not False: |
1419 | - if amount >= 0: |
1420 | - account_id = receiv_account |
1421 | - line_type = 'customer' |
1422 | - else: |
1423 | - account_id = pay_account |
1424 | - line_type = 'supplier' |
1425 | + receiv_account, pay_account = obj_stat.get_default_pay_receiv_accounts(cr, uid, context=None) |
1426 | + account_id, comp_line_type = obj_stat.get_account_and_type_for_counterpart(cr, uid, amount, |
1427 | + receiv_account, pay_account, |
1428 | + partner_id=partner_id) |
1429 | res['account_id'] = account_id if account_id else receiv_account |
1430 | - res['type'] = line_type |
1431 | + res['type'] = line_type if line_type else comp_line_type |
1432 | return res |
1433 | |
1434 | def onchange_partner_id(self, cr, uid, ids, partner_id, profile_id=None, context=None): |
1435 | """ |
1436 | Override of the basic method as we need to pass the profile_id in the on_change_type |
1437 | call. |
1438 | + Moreover, we now call the get_account_and_type_for_counterpart method now to get the |
1439 | + type to use. |
1440 | """ |
1441 | - obj_partner = self.pool.get('res.partner') |
1442 | + obj_stat = self.pool.get('account.bank.statement') |
1443 | if not partner_id: |
1444 | return {} |
1445 | - part = obj_partner.browse(cr, uid, partner_id, context=context) |
1446 | - if not part.supplier and not part.customer: |
1447 | - type = 'general' |
1448 | - elif part.supplier and part.customer: |
1449 | - type = 'general' |
1450 | - else: |
1451 | - if part.supplier == True: |
1452 | - type = 'supplier' |
1453 | - if part.customer == True: |
1454 | - type = 'customer' |
1455 | - res_type = self.onchange_type(cr, uid, ids, partner_id, type, profile_id, context=context) # Chg |
1456 | + line_type = obj_stat.get_type_for_counterpart(cr, uid, 0.0, partner_id=partner_id) |
1457 | + res_type = self.onchange_type(cr, uid, ids, partner_id, line_type, profile_id, context=context) |
1458 | if res_type['value'] and res_type['value'].get('account_id', False): |
1459 | - return {'value': {'type': type, |
1460 | + return {'value': {'type': line_type, |
1461 | 'account_id': res_type['value']['account_id'], |
1462 | 'voucher_id': False}} |
1463 | - return {'value': {'type': type}} |
1464 | + return {'value': {'type': line_type}} |
1465 | |
1466 | - def onchange_type(self, cr, uid, line_id, partner_id, type, profile_id, context=None): |
1467 | + def onchange_type(self, cr, uid, line_id, partner_id, line_type, profile_id, context=None): |
1468 | """ |
1469 | Keep the same features as in standard and call super. If an account is returned, |
1470 | call the method to compute line values. |
1471 | """ |
1472 | - res = super(AccountBankSatementLine, self).onchange_type( |
1473 | - cr, uid, line_id, partner_id, type, context=context) |
1474 | + res = super(AccountBankSatementLine, self).onchange_type(cr, uid, |
1475 | + line_id, |
1476 | + partner_id, |
1477 | + line_type, |
1478 | + context=context) |
1479 | if 'account_id' in res['value']: |
1480 | - result = self.get_values_for_line( |
1481 | - cr, uid, |
1482 | - profile_id=profile_id, |
1483 | - partner_id=partner_id, |
1484 | - line_type=type, |
1485 | - context=context) |
1486 | + result = self.get_values_for_line(cr, uid, |
1487 | + profile_id=profile_id, |
1488 | + partner_id=partner_id, |
1489 | + line_type=line_type, |
1490 | + context=context) |
1491 | if result: |
1492 | res['value'].update({'account_id': result['account_id']}) |
1493 | return res |
1494 | |
1495 | === modified file 'account_statement_transactionid_completion/statement.py' |
1496 | --- account_statement_transactionid_completion/statement.py 2012-12-20 13:37:01 +0000 |
1497 | +++ account_statement_transactionid_completion/statement.py 2013-04-25 11:52:27 +0000 |
1498 | @@ -32,7 +32,7 @@ |
1499 | |
1500 | def _get_functions(self, cr, uid, context=None): |
1501 | res = super(AccountStatementCompletionRule, self)._get_functions( |
1502 | - cr, uid, context=context) |
1503 | + cr, uid, context=context) |
1504 | res.append(('get_from_transaction_id_and_so', |
1505 | 'From line reference (based on SO transaction ID)')) |
1506 | return res |
1507 | @@ -41,12 +41,12 @@ |
1508 | 'function_to_call': fields.selection(_get_functions, 'Method'), |
1509 | } |
1510 | |
1511 | - def get_from_transaction_id_and_so(self, cr, uid, line_id, context=None): |
1512 | + def get_from_transaction_id_and_so(self, cr, uid, st_line, context=None): |
1513 | """ |
1514 | Match the partner based on the transaction ID field of the SO. |
1515 | Then, call the generic st_line method to complete other values. |
1516 | In that case, we always fullfill the reference of the line with the SO name. |
1517 | - :param int/long line_id: ID of the concerned account.bank.statement.line |
1518 | + :param dict st_line: read of the concerned account.bank.statement.line |
1519 | :return: |
1520 | A dict of value that can be passed directly to the write method of |
1521 | the statement line or {} |
1522 | @@ -55,33 +55,28 @@ |
1523 | ...} |
1524 | """ |
1525 | st_obj = self.pool.get('account.bank.statement.line') |
1526 | - st_line = st_obj.browse(cr, uid, line_id, context=context) |
1527 | res = {} |
1528 | - if st_line: |
1529 | - so_obj = self.pool.get('sale.order') |
1530 | - so_id = so_obj.search( |
1531 | - cr, |
1532 | - uid, |
1533 | - [('transaction_id', '=', st_line.transaction_id)], |
1534 | - context=context) |
1535 | - if so_id and len(so_id) == 1: |
1536 | - so = so_obj.browse(cr, uid, so_id[0], context=context) |
1537 | - res['partner_id'] = so.partner_id.id |
1538 | - res['ref'] = so.name |
1539 | - elif so_id and len(so_id) > 1: |
1540 | - raise ErrorTooManyPartner( |
1541 | - _('Line named "%s" (Ref:%s) was matched by more than ' |
1542 | - 'one partner.') % (st_line.name, st_line.ref)) |
1543 | - if so_id: |
1544 | - st_vals = st_obj.get_values_for_line( |
1545 | - cr, |
1546 | - uid, |
1547 | - profile_id=st_line.statement_id.profile_id.id, |
1548 | - partner_id=res.get('partner_id', False), |
1549 | - line_type=st_line.type, |
1550 | - amount=st_line.amount, |
1551 | - context=context) |
1552 | - res.update(st_vals) |
1553 | + so_obj = self.pool.get('sale.order') |
1554 | + so_id = so_obj.search(cr, |
1555 | + uid, |
1556 | + [('transaction_id', '=', st_line['transaction_id'])], |
1557 | + context=context) |
1558 | + if len(so_id) > 1: |
1559 | + raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more than ' |
1560 | + 'one partner.') % (st_line['name'], st_line['ref'])) |
1561 | + if len(so_id) == 1: |
1562 | + so = so_obj.browse(cr, uid, so_id[0], context=context) |
1563 | + res['partner_id'] = so.partner_id.id |
1564 | + res['ref'] = so.name |
1565 | + st_vals = st_obj.get_values_for_line(cr, |
1566 | + uid, |
1567 | + profile_id=st_line['profile_id'], |
1568 | + master_account_id=st_line['master_account_id'], |
1569 | + partner_id=res.get('partner_id', False), |
1570 | + line_type=st_line['type'], |
1571 | + amount=st_line['amount'] if st_line['amount'] else 0.0, |
1572 | + context=context) |
1573 | + res.update(st_vals) |
1574 | return res |
1575 | |
1576 | |
1577 | |
1578 | === modified file 'account_statement_transactionid_import/parser/transactionid_file_parser.py' |
1579 | --- account_statement_transactionid_import/parser/transactionid_file_parser.py 2013-02-26 08:19:44 +0000 |
1580 | +++ account_statement_transactionid_import/parser/transactionid_file_parser.py 2013-04-25 11:52:27 +0000 |
1581 | @@ -17,8 +17,6 @@ |
1582 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
1583 | # |
1584 | ############################################################################## |
1585 | - |
1586 | -from openerp.tools.translate import _ |
1587 | import datetime |
1588 | from account_statement_base_import.parser.file_parser import FileParser |
1589 | |
1590 | @@ -30,13 +28,11 @@ |
1591 | """ |
1592 | |
1593 | def __init__(self, parse_name, ftype='csv'): |
1594 | - conversion_dict = { |
1595 | - 'transaction_id': unicode, |
1596 | - 'label': unicode, |
1597 | - 'date': datetime.datetime, |
1598 | - 'amount': float, |
1599 | - 'commission_amount': float |
1600 | - } |
1601 | + conversion_dict = {'transaction_id': unicode, |
1602 | + 'label': unicode, |
1603 | + 'date': datetime.datetime, |
1604 | + 'amount': float, |
1605 | + 'commission_amount': float} |
1606 | # Order of cols does not matter but first row of the file has to be header |
1607 | keys_to_validate = ['transaction_id', 'label', 'date', 'amount', 'commission_amount'] |
1608 | super(TransactionIDFileParser, self).__init__(parse_name, keys_to_validate=keys_to_validate, |
1609 | @@ -70,15 +66,13 @@ |
1610 | In this generic parser, the commission is given for every line, so we store it |
1611 | for each one. |
1612 | """ |
1613 | - return { |
1614 | - 'name': line.get('label', line.get('ref', '/')), |
1615 | - 'date': line.get('date', datetime.datetime.now().date()), |
1616 | - 'amount': line.get('amount', 0.0), |
1617 | - 'ref': line.get('transaction_id', '/'), |
1618 | - 'label': line.get('label', ''), |
1619 | - 'transaction_id': line.get('transaction_id', '/'), |
1620 | - 'commission_amount': line.get('commission_amount', 0.0), |
1621 | - } |
1622 | + return {'name': line.get('label', line.get('ref', '/')), |
1623 | + 'date': line.get('date', datetime.datetime.now().date()), |
1624 | + 'amount': line.get('amount', 0.0), |
1625 | + 'ref': line.get('transaction_id', '/'), |
1626 | + 'label': line.get('label', ''), |
1627 | + 'transaction_id': line.get('transaction_id', '/'), |
1628 | + 'commission_amount': line.get('commission_amount', 0.0)} |
1629 | |
1630 | def _post(self, *args, **kwargs): |
1631 | """ |
Hello,
just a little typo error at line 23, remove q letter before tag