Merge lp:~akretion-team/banking-addons/bank-statement-reconcile-61-ref-base-import into lp:banking-addons/bank-statement-reconcile-61

Proposed by Benoit Guillot - http://www.akretion.com
Status: Merged
Merged at revision: 81
Proposed branch: lp:~akretion-team/banking-addons/bank-statement-reconcile-61-ref-base-import
Merge into: lp:banking-addons/bank-statement-reconcile-61
Diff against target: 507 lines (+94/-74)
5 files modified
account_statement_base_import/parser/file_parser.py (+9/-8)
account_statement_base_import/parser/parser.py (+30/-26)
account_statement_base_import/statement.py (+47/-31)
account_statement_base_import/statement_view.xml (+1/-2)
account_statement_base_import/wizard/import_statement.py (+7/-7)
To merge this branch: bzr merge lp:~akretion-team/banking-addons/bank-statement-reconcile-61-ref-base-import
Reviewer Review Type Date Requested Status
Guewen Baconnier @ Camptocamp code review, no test Approve
Holger Brunn (Therp) code review, no test Approve
Review via email: mp+159104@code.launchpad.net

Description of the change

In this merge proposal, I submit some changes in the module account_statement_base_import.

 - Fix argument keys_to_validate in the init of the file_parser
 - add the possibility to force the dialect for the csv dictreader
 - fix the field : import_type of the class account_statement_profile, add a hook to add a new type
 - remove sapce at the end of lines due to my IDE
 - fix typo in the name of the method :prepare_statement_lines_vals

To post a comment you must log in.
Revision history for this message
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote :

"remove sapce at the end of lines due to my IDE"
Can you remove this option from your IDE please?

You removed the mutable in (l.11) the keyword argument, which is correct. But I don't think the change of the line 52 is fine. I would prefer to initialize `self.keys_to_validate` to an empty list when the argument is None. Thus, the change is backward compatible and you don't need to verify the attribute everywhere it could be used.

l.78: the del on the kwargs could fail with a KeyError

l.318: fixing the typo errors in method names is fine, but I think you should ensure backward compatibility at least for public methods, keeping the old method which delegate the call to the new one (ideally with a warning).

Thanks for your MP

review: Needs Fixing (code review, no test)
Revision history for this message
Holger Brunn (Therp) (hbrunn) wrote :

as of line 406: you can indeed remove the unlink, as the transaction is rolled back anyway

79. By Benoit Guillot - http://www.akretion.com

[IMP] fix keys_to_validate, remove useless unlink, fix dialect

Revision history for this message
Benoit Guillot - http://www.akretion.com (benoit-guillot-z) wrote :

I change with the several comments

Revision history for this message
Holger Brunn (Therp) (hbrunn) :
review: Approve (code review, no test)
Revision history for this message
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote :

LGTM

review: Approve (code review, no test)
Revision history for this message
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote :

Thanks Benoit.

I merged your proposal.

Did you created the MP for the version 7 somewhere?

Revision history for this message
Benoit Guillot - http://www.akretion.com (benoit-guillot-z) wrote :

Thanks Guewen for the merge !

I haven't created the MP for the v7.0 yet. Sebastien wants to make another MP on the v6.1, after that I will make the MP for the v7.0.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'account_statement_base_import/parser/file_parser.py'
2--- account_statement_base_import/parser/file_parser.py 2012-11-26 10:23:58 +0000
3+++ account_statement_base_import/parser/file_parser.py 2013-05-31 13:57:28 +0000
4@@ -32,13 +32,13 @@
5 """
6 Generic abstract class for defining parser for .csv or .xls file format.
7 """
8-
9- def __init__(self, parse_name, keys_to_validate=[], ftype='csv', convertion_dict=None, header=None, *args, **kwargs):
10+
11+ def __init__(self, parse_name, keys_to_validate=None, ftype='csv', convertion_dict=None, header=None, dialect=None, *args, **kwargs):
12 """
13 :param char: parse_name : The name of the parser
14 :param list: keys_to_validate : contain the key that need to be present in the file
15 :param char ftype: extension of the file (could be csv or xls)
16- :param: convertion_dict : keys and type to convert of every column in the file like
17+ :param: convertion_dict : keys and type to convert of every column in the file like
18 {
19 'ref': unicode,
20 'label': unicode,
21@@ -48,18 +48,19 @@
22 }
23 :param list: header : specify header fields if the csv file has no header
24 """
25-
26+
27 super(FileParser, self).__init__(parse_name, *args, **kwargs)
28 if ftype in ('csv', 'xls'):
29 self.ftype = ftype
30 else:
31 raise Exception(_('Invalide file type %s. please use csv or xls') % (ftype))
32- self.keys_to_validate = keys_to_validate
33+ self.keys_to_validate = keys_to_validate or []
34 self.convertion_dict = convertion_dict
35 self.fieldnames = header
36 self._datemode = 0 # used only for xls documents,
37 # 0 means Windows mode (1900 based dates).
38 # Set in _parse_xls, from the contents of the file
39+ self.dialect = dialect
40
41 def _custom_format(self, *args, **kwargs):
42 """
43@@ -78,7 +79,7 @@
44 Launch the parsing through .csv or .xls depending on the
45 given ftype
46 """
47-
48+
49 res = None
50 if self.ftype == 'csv':
51 res = self._parse_csv()
52@@ -116,8 +117,8 @@
53 csv_file = tempfile.NamedTemporaryFile()
54 csv_file.write(self.filebuffer)
55 csv_file.flush()
56- with open(csv_file.name, 'rU') as fobj:
57- reader = UnicodeDictReader(fobj, fieldnames=self.fieldnames)
58+ with open(csv_file.name, 'rU') as fobj:
59+ reader = UnicodeDictReader(fobj, fieldnames=self.fieldnames, dialect=self.dialect)
60 return list(reader)
61
62 def _parse_xls(self):
63
64=== modified file 'account_statement_base_import/parser/parser.py'
65--- account_statement_base_import/parser/parser.py 2012-11-23 16:27:24 +0000
66+++ account_statement_base_import/parser/parser.py 2013-05-31 13:57:28 +0000
67@@ -26,10 +26,15 @@
68 pos = utf8_data.tell()
69 sample_data = utf8_data.read(1024)
70 utf8_data.seek(pos)
71- dialect = sniffer.sniff(sample_data, delimiters=',;\t')
72+ if not kwargs.get('dialect'):
73+ dialect = sniffer.sniff(sample_data, delimiters=',;\t')
74+ if 'dialect' in kwargs:
75+ del kwargs['dialect']
76+ else:
77+ dialect = kwargs.pop('dialect')
78 csv_reader = csv.DictReader(utf8_data, dialect=dialect, **kwargs)
79 for row in csv_reader:
80- yield dict([(key, unicode(value, 'utf-8')) for key, value in row.iteritems()])
81+ yield dict([(unicode(key, 'utf-8'), unicode(value, 'utf-8')) for key, value in row.iteritems()])
82
83 class BankStatementImportParser(object):
84 """
85@@ -38,7 +43,7 @@
86 own. If your file is a .csv or .xls format, you should consider inheirt
87 from the FileParser instead.
88 """
89-
90+
91 def __init__(self, parser_name, *args, **kwargs):
92 # The name of the parser as it will be called
93 self.parser_name = parser_name
94@@ -50,7 +55,7 @@
95 # Concatenate here the global commission taken by the bank/office
96 # for this statement.
97 self.commission_global_amount = None
98-
99+
100 @classmethod
101 def parser_for(cls, parser_name):
102 """
103@@ -58,17 +63,17 @@
104 return the good class from his name.
105 """
106 return False
107-
108+
109 def _decode_64b_stream(self):
110 """
111 Decode self.filebuffer in base 64 and override it
112 """
113 self.filebuffer = base64.b64decode(self.filebuffer)
114 return True
115-
116+
117 def _format(self, decode_base_64=True, **kwargs):
118 """
119- Decode into base 64 if asked and Format the given filebuffer by calling
120+ Decode into base 64 if asked and Format the given filebuffer by calling
121 _custom_format method.
122 """
123 if decode_base_64:
124@@ -83,43 +88,43 @@
125 """
126 return NotImplementedError
127
128-
129+
130 def _pre(self, *args, **kwargs):
131 """
132- Implement a method in your parser to make a pre-treatment on datas before parsing
133+ Implement a method in your parser to make a pre-treatment on datas before parsing
134 them, like concatenate stuff, and so... Work on self.filebuffer
135 """
136 return NotImplementedError
137
138 def _parse(self, *args, **kwargs):
139 """
140- Implement a method in your parser to save the result of parsing self.filebuffer
141+ Implement a method in your parser to save the result of parsing self.filebuffer
142 in self.result_row_list instance property.
143 """
144 return NotImplementedError
145-
146+
147 def _validate(self, *args, **kwargs):
148 """
149 Implement a method in your parser to validate the self.result_row_list instance
150 property and raise an error if not valid.
151 """
152 return NotImplementedError
153-
154+
155 def _post(self, *args, **kwargs):
156 """
157 Implement a method in your parser to make some last changes on the result of parsing
158- the datas, like converting dates, computing commission, ...
159+ the datas, like converting dates, computing commission, ...
160 Work on self.result_row_list and put the commission global amount if any
161 in the self.commission_global_amount one.
162 """
163 return NotImplementedError
164-
165-
166-
167+
168+
169+
170 def get_st_line_vals(self, line, *args, **kwargs):
171 """
172- Implement a method in your parser that must return a dict of vals that can be
173- passed to create method of statement line in order to record it. It is the responsibility
174+ Implement a method in your parser that must return a dict of vals that can be
175+ passed to create method of statement line in order to record it. It is the responsibility
176 of every parser to give this dict of vals, so each one can implement his
177 own way of recording the lines.
178 :param: line: a dict of vals that represent a line of result_row_list
179@@ -133,17 +138,17 @@
180 }
181 """
182 return NotImplementedError
183-
184+
185 def get_st_line_commision(self, *args, **kwargs):
186 """
187 This is called by the importation method to create the commission line in
188 the bank statement. We will always create one line for the commission in the
189- bank statement, but it could be computated from a value of each line, or given
190+ bank statement, but it could be computated from a value of each line, or given
191 in a single line for the whole file.
192 return: float of the whole commission (self.commission_global_amount)
193 """
194 return self.commission_global_amount
195-
196+
197 def parse(self, filebuffer, *args, **kwargs):
198 """
199 This will be the method that will be called by wizard, button and so
200@@ -151,7 +156,7 @@
201 that need to be define for each parser.
202 Return:
203 [] of rows as {'key':value}
204-
205+
206 Note: The row_list must contain only value that are present in the account.
207 bank.statement.line object !!!
208 """
209@@ -165,7 +170,7 @@
210 self._validate(*args, **kwargs)
211 self._post(*args, **kwargs)
212 return self.result_row_list
213-
214+
215 def itersubclasses(cls, _seen=None):
216 """
217 itersubclasses(cls)
218@@ -179,7 +184,7 @@
219 >>> class C(A): pass
220 >>> class D(B,C): pass
221 >>> class E(D): pass
222- >>>
223+ >>>
224 >>> for cls in itersubclasses(A):
225 ... print(cls.__name__)
226 B
227@@ -204,7 +209,7 @@
228 yield sub
229 for sub in itersubclasses(sub, _seen):
230 yield sub
231-
232+
233 def new_bank_statement_parser(parser_name, *args, **kwargs):
234 """
235 Return an instance of the good parser class base on the providen name
236@@ -215,4 +220,3 @@
237 if cls.parser_for(parser_name):
238 return cls(parser_name, *args, **kwargs)
239 raise ValueError
240-
241
242=== modified file 'account_statement_base_import/statement.py'
243--- account_statement_base_import/statement.py 2012-09-25 08:05:34 +0000
244+++ account_statement_base_import/statement.py 2013-05-31 13:57:28 +0000
245@@ -29,34 +29,42 @@
246 from parser import new_bank_statement_parser
247 import sys
248 import traceback
249+import logging
250+_logger = logging.getLogger(__name__)
251+
252
253 class AccountStatementProfil(Model):
254 _inherit = "account.statement.profile"
255-
256-
257+
258+
259 def get_import_type_selection(self, cr, uid, context=None):
260 """
261 Has to be inherited to add parser
262 """
263 return [('generic_csvxls_so', 'Generic .csv/.xls based on SO Name')]
264-
265-
266+
267+ def _get_import_type_selection(self, cr, uid, context=None):
268+ """
269+ Has to be inherited to add parser
270+ """
271+ return self.get_import_type_selection(cr, uid, context=context)
272+
273 _columns = {
274- 'launch_import_completion': fields.boolean("Launch completion after import",
275+ 'launch_import_completion': fields.boolean("Launch completion after import",
276 help="Tic that box to automatically launch the completion on each imported\
277 file using this profile."),
278 'last_import_date': fields.datetime("Last Import Date"),
279 'rec_log': fields.text('log', readonly=True),
280- 'import_type': fields.selection(get_import_type_selection, 'Type of import', required=True,
281+ 'import_type': fields.selection(_get_import_type_selection, 'Type of import', required=True,
282 help = "Choose here the method by which you want to import bank statement for this profile."),
283-
284+
285 }
286-
287+
288 def write_logs_after_import(self, cr, uid, ids, statement_id, num_lines, context):
289 """
290- Write the log in the logger + in the log field of the profile to report the user about
291+ Write the log in the logger + in the log field of the profile to report the user about
292 what has been done.
293-
294+
295 :param int/long statement_id: ID of the concerned account.bank.statement
296 :param int/long num_lines: Number of line that have been parsed
297 :return: True
298@@ -71,11 +79,11 @@
299 + _("Bank Statement ID %s have been imported with %s lines ") %(statement_id, num_lines)]
300 log = "\n".join(log_line)
301 self.write(cr, uid, id, {'rec_log' : log, 'last_import_date':import_date}, context=context)
302- logger.notifyChannel('Bank Statement Import', netsvc.LOG_INFO,
303+ logger.notifyChannel('Bank Statement Import', netsvc.LOG_INFO,
304 "Bank Statement ID %s have been imported with %s lines "%(statement_id, num_lines))
305 return True
306-
307- def prepare_global_commission_line_vals(self, cr, uid, parser,
308+
309+ def prepare_global_commission_line_vals(self, cr, uid, parser,
310 result_row_list, profile, statement_id, context):
311 """
312 Prepare the global commission line if there is one. The global
313@@ -110,15 +118,25 @@
314 'already_completed': True,
315 }
316 return comm_values
317-
318- def prepare_statetement_lines_vals(self, cursor, uid, parser_vals,
319+
320+ def prepare_statetement_lines_vals(self, cursor, uid, parser_vals,
321+ account_payable, account_receivable, statement_id, context):
322+ """
323+ Method to ensure backward compatibility with the old name of the method.
324+ """
325+ _logger.warning(_("The method prepare_statetement_lines_vals shouldn't "
326+ "be used anymore, use : prepare_statement_lines_vals"))
327+ return self.prepare_statement_lines_vals(cursor, uid, parser_vals,
328+ account_payable, account_receivable, statement_id, context)
329+
330+ def prepare_statement_lines_vals(self, cursor, uid, parser_vals,
331 account_payable, account_receivable, statement_id, context):
332 """
333 Hook to build the values of a line from the parser returned values. At
334 least it fullfill the statement_id and account_id. Overide it to add your
335- own completion if needed.
336-
337- :param dict of vals from parser for account.bank.statement.line (called by
338+ own completion if needed.
339+
340+ :param dict of vals from parser for account.bank.statement.line (called by
341 parser.get_st_line_vals)
342 :param int/long account_payable: ID of the receivable account to use
343 :param int/long account_receivable: ID of the payable account to use
344@@ -136,7 +154,7 @@
345 account_payable
346 )
347 return values
348-
349+
350 def statement_import(self, cursor, uid, ids, profile_id, file_stream, ftype="csv", context=None):
351 """
352 Create a bank statement with the given profile and parser. It will fullfill the bank statement
353@@ -144,7 +162,7 @@
354 the right account). This will be done in a second step with the completion rules.
355 It will also create the commission line if it apply and record the providen file as
356 an attachement of the bank statement.
357-
358+
359 :param int/long profile_id: ID of the profile used to import the file
360 :param filebuffer file_stream: binary of the providen file
361 :param char: ftype represent the file exstension (csv by default)
362@@ -160,7 +178,7 @@
363 _("No Profile !"),
364 _("You must provide a valid profile to import a bank statement !"))
365 prof = prof_obj.browse(cursor,uid,profile_id,context)
366-
367+
368 parser = new_bank_statement_parser(prof.import_type, ftype=ftype)
369 result_row_list = parser.parse(file_stream)
370 # Check all key are present in account.bank.statement.line !!
371@@ -170,15 +188,15 @@
372 raise osv.except_osv(
373 _("Missing column !"),
374 _("Column %s you try to import is not present in the bank statement line !") %(col))
375-
376- statement_id = statement_obj.create(cursor,uid,{'profile_id':prof.id,},context)
377+
378+ statement_id = statement_obj.create(cursor,uid,{'profile_id':prof.id,},context)
379 account_receivable, account_payable = statement_obj.get_default_pay_receiv_accounts(cursor, uid, context)
380 try:
381 # Record every line in the bank statement and compute the global commission
382 # based on the commission_amount column
383 for line in result_row_list:
384 parser_vals = parser.get_st_line_vals(line)
385- values = self.prepare_statetement_lines_vals(cursor, uid, parser_vals, account_payable,
386+ values = self.prepare_statement_lines_vals(cursor, uid, parser_vals, account_payable,
387 account_receivable, statement_id, context)
388 # we finally create the line in system
389 statement_line_obj.create(cursor, uid, values, context=context)
390@@ -186,7 +204,7 @@
391 comm_vals = self.prepare_global_commission_line_vals(cursor, uid, parser, result_row_list, prof, statement_id, context)
392 if comm_vals:
393 res = statement_line_obj.create(cursor, uid, comm_vals,context=context)
394-
395+
396 attachment_obj.create(
397 cursor,
398 uid,
399@@ -199,17 +217,16 @@
400 'res_id': statement_id,
401 },
402 context=context
403- )
404+ )
405 # If user ask to launch completion at end of import, do it !
406 if prof.launch_import_completion:
407 statement_obj.button_auto_completion(cursor, uid, [statement_id], context)
408-
409+
410 # Write the needed log infos on profile
411 self.write_logs_after_import(cursor, uid, prof.id, statement_id,
412 len(result_row_list), context)
413-
414+
415 except Exception, exc:
416- statement_obj.unlink(cursor, uid, [statement_id])
417 error_type, error_value, trbk = sys.exc_info()
418 st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value)
419 st += ''.join(traceback.format_tb(trbk, 30))
420@@ -217,7 +234,6 @@
421 _("Statement import error"),
422 _("The statement cannot be created : %s") %(st))
423 return statement_id
424-
425
426
427 class AccountStatementLine(Model):
428@@ -229,7 +245,7 @@
429 _inherit = "account.bank.statement.line"
430
431 _columns={
432- 'commission_amount': fields.sparse(type='float', string='Line Commission Amount',
433+ 'commission_amount': fields.sparse(type='float', string='Line Commission Amount',
434 serialization_field='additionnal_bank_fields'),
435
436 }
437
438=== modified file 'account_statement_base_import/statement_view.xml'
439--- account_statement_base_import/statement_view.xml 2012-08-02 12:46:12 +0000
440+++ account_statement_base_import/statement_view.xml 2013-05-31 13:57:28 +0000
441@@ -16,7 +16,7 @@
442 <field name="import_type"/>
443 <button name="%(account_statement_base_import.statement_importer_action)d"
444 string="Import Bank Statement"
445- type="action" icon="gtk-ok"
446+ type="action" icon="gtk-ok"
447 colspan = "2"/>
448 <separator colspan="4" string="Import Logs"/>
449 <field name="rec_log" colspan="4" nolabel="1"/>
450@@ -39,6 +39,5 @@
451 </field>
452 </record>
453
454-
455 </data>
456 </openerp>
457
458=== modified file 'account_statement_base_import/wizard/import_statement.py'
459--- account_statement_base_import/wizard/import_statement.py 2012-11-27 10:51:47 +0000
460+++ account_statement_base_import/wizard/import_statement.py 2013-05-31 13:57:28 +0000
461@@ -29,7 +29,7 @@
462
463 class CreditPartnerStatementImporter(osv.osv_memory):
464 _name = "credit.statement.import"
465-
466+
467 def default_get(self, cr, uid, fields, context=None):
468 if context is None: context = {}
469 res = {}
470@@ -41,7 +41,7 @@
471 other_vals = self.onchange_profile_id(cr, uid, [], res['profile_id'], context=context)
472 res.update(other_vals.get('value',{}))
473 return res
474-
475+
476 _columns = {
477 'profile_id': fields.many2one('account.statement.profile',
478 'Import configuration parameter',
479@@ -63,23 +63,23 @@
480 ),
481 'receivable_account_id': fields.many2one('account.account',
482 'Force Receivable/Payable Account'),
483- 'force_partner_on_bank': fields.boolean('Force partner on bank move',
484+ 'force_partner_on_bank': fields.boolean('Force partner on bank move',
485 help="Tic that box if you want to use the credit insitute partner\
486 in the counterpart of the treasury/banking move."
487 ),
488- 'balance_check': fields.boolean('Balance check',
489+ 'balance_check': fields.boolean('Balance check',
490 help="Tic that box if you want OpenERP to control the start/end balance\
491 before confirming a bank statement. If don't ticked, no balance control will be done."
492 ),
493- }
494-
495+ }
496+
497 def onchange_profile_id(self, cr, uid, ids, profile_id, context=None):
498 res={}
499 if profile_id:
500 c = self.pool.get("account.statement.profile").browse(cr,uid,profile_id)
501 res = {'value': {'partner_id': c.partner_id and c.partner_id.id or False,
502 'journal_id': c.journal_id and c.journal_id.id or False, 'commission_account_id': \
503- c.commission_account_id and c.commission_account_id.id or False,
504+ c.commission_account_id and c.commission_account_id.id or False,
505 'receivable_account_id': c.receivable_account_id and c.receivable_account_id.id or False,
506 'commission_a':c.commission_analytic_id and c.commission_analytic_id.id or False,
507 'force_partner_on_bank':c.force_partner_on_bank,

Subscribers

People subscribed via source and target branches