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

Proposed by Nicolas Bessi - Camptocamp
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
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.

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

To post a comment you must log in.
Revision history for this message
Vincent Renaville@camptocamp (vrenaville-c2c) wrote : Posted in a previous version of this proposal

Hello,

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

review: Needs Fixing
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

Revision history for this message
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_bank_statement_line SET sequence = account_bank_statement_line.id + 1 WHERE statement_id in %(list_of_ids)s" ?

review: Needs Fixing (code review, no test)
Revision history for this message
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

Revision history for this message
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 AccountStatementCompletionRule.get_from_transaction_id_and_so)

Revision history for this message
Alexandre Fayolle - camptocamp (alexandre-fayolle-c2c) wrote :

account_statement_base_completion/statement.py, AccountStatementProfil._find_values_from_rules

* 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_statement_base_completion/statement.py, AccountStatementCompletionRule.get_from_label_and_partner_name:

* security rule bypass still there AFAICT

I've pushed a few changes and improvements, don't forget to pull before fixing the points above)

review: Needs Fixing (code review, no test)
133. By Nicolas Bessi - Camptocamp

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

Revision history for this message
Nicolas Bessi - Camptocamp (nbessi-c2c-deactivatedaccount) wrote :

Hello,

Many thanks for the attentive review. I have pushed the last fixes

Normally get_from_label_and_partner_name should not bypass security rules as the memoizer partner set is limited by the first search statement:
 partner_ids = partner_obj.search(cr,
                                  uid,
                                  [('bank_statement_label', '!=', False)])

Revision history for this message
Alexandre Fayolle - camptocamp (alexandre-fayolle-c2c) wrote :

LGTM.

review: Approve (code review, no test)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'account_statement_base_completion/__openerp__.py'
--- account_statement_base_completion/__openerp__.py 2013-01-10 15:09:55 +0000
+++ account_statement_base_completion/__openerp__.py 2013-04-25 11:52:27 +0000
@@ -52,6 +52,10 @@
5252
53 You can use it with our account_advanced_reconcile module to automatize the reconciliation process.53 You can use it with our account_advanced_reconcile module to automatize the reconciliation process.
5454
55
56 TODO: The rules that look for invoices to find out the partner should take back the payable / receivable
57 account from there directly instead of retrieving it from partner properties !
58
55 """,59 """,
56 'website': 'http://www.camptocamp.com',60 'website': 'http://www.camptocamp.com',
57 'init_xml': [],61 'init_xml': [],
5862
=== modified file 'account_statement_base_completion/statement.py'
--- account_statement_base_completion/statement.py 2013-03-22 08:31:27 +0000
+++ account_statement_base_completion/statement.py 2013-04-25 11:52:27 +0000
@@ -18,15 +18,21 @@
18# along with this program. If not, see <http://www.gnu.org/licenses/>.18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#19#
20##############################################################################20##############################################################################
21# TODO replace customer supplier by package constant
22import traceback
23import sys
24import logging
25
21from collections import defaultdict26from collections import defaultdict
22import re27import re
23
24from tools.translate import _28from tools.translate import _
25from openerp.osv.orm import Model, fields29from openerp.osv import osv, orm, fields
26from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT30from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
27from operator import attrgetter31from operator import attrgetter
28import datetime32import datetime
2933
34_logger = logging.getLogger(__name__)
35
3036
31class ErrorTooManyPartner(Exception):37class ErrorTooManyPartner(Exception):
32 """38 """
@@ -40,7 +46,7 @@
40 return repr(self.value)46 return repr(self.value)
4147
4248
43class AccountStatementProfil(Model):49class AccountStatementProfil(orm.Model):
44 """50 """
45 Extend the class to add rules per profile that will match at least the partner,51 Extend the class to add rules per profile that will match at least the partner,
46 but it could also be used to match other values as well.52 but it could also be used to match other values as well.
@@ -49,11 +55,11 @@
49 _inherit = "account.statement.profile"55 _inherit = "account.statement.profile"
5056
51 _columns = {57 _columns = {
52 # @Akretion : For now, we don't implement this features, but this would probably be there:58 # @Akretion: For now, we don't implement this features, but this would probably be there:
53 # 'auto_completion': fields.text('Auto Completion'),59 # 'auto_completion': fields.text('Auto Completion'),
54 # 'transferts_account_id':fields.many2one('account.account', 'Transferts Account'),60 # 'transferts_account_id':fields.many2one('account.account', 'Transferts Account'),
55 # => You can implement it in a module easily, we design it with your needs in mind61 # => You can implement it in a module easily, we design it with your needs in mind
56 # as well !62 # as well!
5763
58 'rule_ids': fields.many2many(64 'rule_ids': fields.many2many(
59 'account.statement.completion.rule',65 'account.statement.completion.rule',
@@ -61,36 +67,45 @@
61 rel='as_rul_st_prof_rel'),67 rel='as_rul_st_prof_rel'),
62 }68 }
6369
64 def find_values_from_rules(self, cr, uid, id, line_id, context=None):70 def _get_callable(self, cr, uid, profile, context=None):
71 if isinstance(profile, (int, long)):
72 prof = self.browse(cr, uid, profile, context=context)
73 else:
74 prof = profile
75 # We need to respect the sequence order
76 sorted_array = sorted(prof.rule_ids, key=attrgetter('sequence'))
77 return tuple((x.function_to_call for x in sorted_array))
78
79 def _find_values_from_rules(self, cr, uid, calls, line, context=None):
65 """80 """
66 This method will execute all related rules, in their sequence order,81 This method will execute all related rules, in their sequence order,
67 to retrieve all the values returned by the first rules that will match.82 to retrieve all the values returned by the first rules that will match.
6883 :param calls: list of lookup function name available in rules
69 :param int/long line_id: id of the concerned account.bank.statement.line84 :param dict line: read of the concerned account.bank.statement.line
70 :return:85 :return:
71 A dict of value that can be passed directly to the write method of86 A dict of value that can be passed directly to the write method of
72 the statement line or {}87 the statement line or {}
73 {'partner_id': value,88 {'partner_id': value,
74 'account_id' : value,89 'account_id: value,
7590
76 ...}91 ...}
77 """92 """
78 if context is None:93 if context is None:
79 context = {}94 context = {}
80 res = {}95 if not calls:
96 calls = self._get_callable(cr, uid, line['profile_id'], context=context)
81 rule_obj = self.pool.get('account.statement.completion.rule')97 rule_obj = self.pool.get('account.statement.completion.rule')
82 profile = self.browse(cr, uid, id, context=context)98
83 # We need to respect the sequence order99 for call in calls:
84 sorted_array = sorted(profile.rule_ids, key=attrgetter('sequence'))100 method_to_call = getattr(rule_obj, call)
85 for rule in sorted_array:101 result = method_to_call(cr, uid, line, context)
86 method_to_call = getattr(rule_obj, rule.function_to_call)
87 result = method_to_call(cr, uid, line_id, context)
88 if result:102 if result:
103 result['already_completed'] = True
89 return result104 return result
90 return res105 return None
91106
92107
93class AccountStatementCompletionRule(Model):108class AccountStatementCompletionRule(orm.Model):
94 """109 """
95 This will represent all the completion method that we can have to110 This will represent all the completion method that we can have to
96 fullfill the bank statement lines. You'll be able to extend them in you own module111 fullfill the bank statement lines. You'll be able to extend them in you own module
@@ -109,12 +124,11 @@
109 List of available methods for rules. Override this to add you own.124 List of available methods for rules. Override this to add you own.
110 """125 """
111 return [126 return [
112 ('get_from_ref_and_invoice', 'From line reference (based on invoice number)'),127 ('get_from_ref_and_invoice', 'From line reference (based on customer invoice number)'),
113 ('get_from_ref_and_supplier_invoice', 'From line reference (based on supplier invoice number)'),128 ('get_from_ref_and_supplier_invoice', 'From line reference (based on supplier invoice number)'),
114 ('get_from_ref_and_so', 'From line reference (based on SO number)'),129 ('get_from_ref_and_so', 'From line reference (based on SO number)'),
115 ('get_from_label_and_partner_field', 'From line label (based on partner field)'),130 ('get_from_label_and_partner_field', 'From line label (based on partner field)'),
116 ('get_from_label_and_partner_name', 'From line label (based on partner name)'),131 ('get_from_label_and_partner_name', 'From line label (based on partner name)')]
117 ]
118132
119 _columns = {133 _columns = {
120 'sequence': fields.integer('Sequence', help="Lower means parsed first."),134 'sequence': fields.integer('Sequence', help="Lower means parsed first."),
@@ -126,134 +140,129 @@
126 'function_to_call': fields.selection(_get_functions, 'Method'),140 'function_to_call': fields.selection(_get_functions, 'Method'),
127 }141 }
128142
129 def get_from_ref_and_supplier_invoice(self, cr, uid, line_id, context=None):143 def _find_invoice(self, cr, uid, st_line, inv_type, context=None):
144 """Find invoice related to statement line"""
145 inv_obj = self.pool.get('account.invoice')
146 if inv_type == 'supplier':
147 type_domain = ('in_invoice', 'in_refund')
148 number_field = 'supplier_invoice_number'
149 elif inv_type == 'customer':
150 type_domain = ('out_invoice', 'out_refund')
151 number_field = 'number'
152 else:
153 raise osv.except_osv(_('System error'),
154 _('Invalid invoice type for completion: %') % inv_type)
155
156 inv_id = inv_obj.search(cr, uid,
157 [(number_field, '=', st_line['ref'].strip()),
158 ('type', 'in', type_domain)],
159 context=context)
160 if inv_id:
161 if len(inv_id) == 1:
162 inv = inv_obj.browse(cr, uid, inv_id[0], context=context)
163 else:
164 raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more '
165 'than one partner while looking on %s invoices') %
166 (st_line['name'], st_line['ref'], inv_type))
167 return inv
168 return False
169
170 def _from_invoice(self, cr, uid, line, inv_type, context):
171 """Populate statement line values"""
172 if not inv_type in ('supplier', 'customer'):
173 raise osv.except_osv(_('System error'),
174 _('Invalid invoice type for completion: %') % inv_type)
175 res = {}
176 inv = self._find_invoice(cr, uid, line, inv_type, context=context)
177 if inv:
178 res = {'partner_id': inv.partner_id.id,
179 'account_id': inv.account_id.id,
180 'type': inv_type}
181 override_acc = line['master_account_id']
182 if override_acc:
183 res['account_id'] = override_acc
184 return res
185
186 # Should be private but data are initialised with no update XML
187 def get_from_ref_and_supplier_invoice(self, cr, uid, line, context=None):
130 """188 """
131 Match the partner based on the invoice supplier invoice number and the reference of the statement189 Match the partner based on the invoice supplier invoice number and the reference of the statement
132 line. Then, call the generic get_values_for_line method to complete other values.190 line. Then, call the generic get_values_for_line method to complete other values.
133 If more than one partner matched, raise the ErrorTooManyPartner error.191 If more than one partner matched, raise the ErrorTooManyPartner error.
134192
135 :param int/long line_id: id of the concerned account.bank.statement.line193 :param dict line: read of the concerned account.bank.statement.line
136 :return:194 :return:
137 A dict of value that can be passed directly to the write method of195 A dict of value that can be passed directly to the write method of
138 the statement line or {}196 the statement line or {}
139 {'partner_id': value,197 {'partner_id': value,
140 'account_id' : value,198 'account_id': value,
141199
142 ...}200 ...}
143 """201 """
144 st_obj = self.pool['account.bank.statement.line']202 return self._from_invoice(cr, uid, line, 'supplier', context=context)
145 st_line = st_obj.browse(cr, uid, line_id, context=context)
146 res = {}
147 inv_obj = self.pool.get('account.invoice')
148 if st_line:
149 inv_id = inv_obj.search(cr,
150 uid,
151 [('supplier_invoice_number', '=', st_line.ref),
152 ('type', 'in', ('in_invoice', 'in_refund'))],
153 context=context)
154 if inv_id:
155 if len(inv_id) == 1:
156 inv = inv_obj.browse(cr, uid, inv_id[0], context=context)
157 res['partner_id'] = inv.partner_id.id
158 else:
159 raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more '
160 'than one partner.') % (st_line.name, st_line.ref))
161 st_vals = st_obj.get_values_for_line(cr,
162 uid,
163 profile_id=st_line.statement_id.profile_id.id,
164 partner_id=res.get('partner_id', False),
165 line_type="supplier",
166 amount=st_line.amount,
167 context=context)
168 res.update(st_vals)
169 return res
170203
171 def get_from_ref_and_invoice(self, cr, uid, line_id, context=None):204 # Should be private but data are initialised with no update XML
205 def get_from_ref_and_invoice(self, cr, uid, line, context=None):
172 """206 """
173 Match the partner based on the invoice number and the reference of the statement207 Match the partner based on the invoice number and the reference of the statement
174 line. Then, call the generic get_values_for_line method to complete other values.208 line. Then, call the generic get_values_for_line method to complete other values.
175 If more than one partner matched, raise the ErrorTooManyPartner error.209 If more than one partner matched, raise the ErrorTooManyPartner error.
176210
177 :param int/long line_id: id of the concerned account.bank.statement.line211 :param dict line: read of the concerned account.bank.statement.line
178 :return:212 :return:
179 A dict of value that can be passed directly to the write method of213 A dict of value that can be passed directly to the write method of
180 the statement line or {}214 the statement line or {}
181 {'partner_id': value,215 {'partner_id': value,
182 'account_id' : value,216 'account_id': value,
183 ...}217 ...}
184 """218 """
185 st_obj = self.pool.get('account.bank.statement.line')219 return self._from_invoice(cr, uid, line, 'customer', context=context)
186 st_line = st_obj.browse(cr, uid, line_id, context=context)
187 res = {}
188 if st_line:
189 inv_obj = self.pool.get('account.invoice')
190 inv_id = inv_obj.search(cr,
191 uid,
192 [('number', '=', st_line.ref)],
193 context=context)
194 if inv_id:
195 if len(inv_id) == 1:
196 inv = inv_obj.browse(cr, uid, inv_id[0], context=context)
197 res['partner_id'] = inv.partner_id.id
198 else:
199 raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more '
200 'than one partner.') % (st_line.name, st_line.ref))
201 st_vals = st_obj.get_values_for_line(cr,
202 uid,
203 profile_id=st_line.statement_id.profile_id.id,
204 partner_id=res.get('partner_id', False),
205 line_type=st_line.type,
206 amount=st_line.amount,
207 context=context)
208 res.update(st_vals)
209 return res
210220
211 def get_from_ref_and_so(self, cr, uid, line_id, context=None):221 # Should be private but data are initialised with no update XML
222 def get_from_ref_and_so(self, cr, uid, st_line, context=None):
212 """223 """
213 Match the partner based on the SO number and the reference of the statement224 Match the partner based on the SO number and the reference of the statement
214 line. Then, call the generic get_values_for_line method to complete other values.225 line. Then, call the generic get_values_for_line method to complete other values.
215 If more than one partner matched, raise the ErrorTooManyPartner error.226 If more than one partner matched, raise the ErrorTooManyPartner error.
216227
217 :param int/long line_id: id of the concerned account.bank.statement.line228 :param int/long st_line: read of the concerned account.bank.statement.line
218 :return:229 :return:
219 A dict of value that can be passed directly to the write method of230 A dict of value that can be passed directly to the write method of
220 the statement line or {}231 the statement line or {}
221 {'partner_id': value,232 {'partner_id': value,
222 'account_id' : value,233 'account_id': value,
223234
224 ...}235 ...}
225 """236 """
226 st_obj = self.pool.get('account.bank.statement.line')237 st_obj = self.pool.get('account.bank.statement.line')
227 st_line = st_obj.browse(cr, uid, line_id, context=context)
228 res = {}238 res = {}
229 if st_line:239 if st_line:
230 so_obj = self.pool.get('sale.order')240 so_obj = self.pool.get('sale.order')
231 so_id = so_obj.search(241 so_id = so_obj.search(cr,
232 cr,242 uid,
233 uid,243 [('name', '=', st_line['ref'])],
234 [('name', '=', st_line.ref)],244 context=context)
235 context=context)
236 if so_id:245 if so_id:
237 if so_id and len(so_id) == 1:246 if so_id and len(so_id) == 1:
238 so = so_obj.browse(cr, uid, so_id[0], context=context)247 so = so_obj.browse(cr, uid, so_id[0], context=context)
239 res['partner_id'] = so.partner_id.id248 res['partner_id'] = so.partner_id.id
240 elif so_id and len(so_id) > 1:249 elif so_id and len(so_id) > 1:
241 raise ErrorTooManyPartner(250 raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more '
242 _('Line named "%s" (Ref:%s) was matched by more '251 'than one partner while looking on SO by ref.') %
243 'than one partner.') %252 (st_line['name'], st_line['ref']))
244 (st_line.name, st_line.ref))253 st_vals = st_obj.get_values_for_line(cr,
245 st_vals = st_obj.get_values_for_line(254 uid,
246 cr,255 profile_id=st_line['profile_id'],
247 uid,256 master_account_id=st_line['master_account_id'],
248 profile_id=st_line.statement_id.profile_id.id,257 partner_id=res.get('partner_id', False),
249 partner_id=res.get('partner_id', False),258 line_type='customer',
250 line_type=st_line.type,259 amount=st_line['amount'] if st_line['amount'] else 0.0,
251 amount=st_line.amount,260 context=context)
252 context=context)
253 res.update(st_vals)261 res.update(st_vals)
254 return res262 return res
255263
256 def get_from_label_and_partner_field(self, cr, uid, line_id, context=None):264 # Should be private but data are initialised with no update XML
265 def get_from_label_and_partner_field(self, cr, uid, st_line, context=None):
257 """266 """
258 Match the partner based on the label field of the statement line267 Match the partner based on the label field of the statement line
259 and the text defined in the 'bank_statement_label' field of the partner.268 and the text defined in the 'bank_statement_label' field of the partner.
@@ -261,12 +270,12 @@
261 get_values_for_line method to complete other values.270 get_values_for_line method to complete other values.
262 If more than one partner matched, raise the ErrorTooManyPartner error.271 If more than one partner matched, raise the ErrorTooManyPartner error.
263272
264 :param int/long line_id: id of the concerned account.bank.statement.line273 :param dict st_line: read of the concerned account.bank.statement.line
265 :return:274 :return:
266 A dict of value that can be passed directly to the write method of275 A dict of value that can be passed directly to the write method of
267 the statement line or {}276 the statement line or {}
268 {'partner_id': value,277 {'partner_id': value,
269 'account_id' : value,278 'account_id': value,
270279
271 ...}280 ...}
272 """281 """
@@ -283,7 +292,7 @@
283 partner_ids = partner_obj.search(cr,292 partner_ids = partner_obj.search(cr,
284 uid,293 uid,
285 [('bank_statement_label', '!=', False)])294 [('bank_statement_label', '!=', False)])
286 line_ids = tuple(x.id for x in context.get('line_ids', []))295 line_ids = context.get('line_ids', [])
287 for partner in partner_obj.browse(cr, uid, partner_ids, context=context):296 for partner in partner_obj.browse(cr, uid, partner_ids, context=context):
288 vals = '|'.join(re.escape(x.strip()) for x in partner.bank_statement_label.split(';'))297 vals = '|'.join(re.escape(x.strip()) for x in partner.bank_statement_label.split(';'))
289 or_regex = ".*%s*." % vals298 or_regex = ".*%s*." % vals
@@ -294,70 +303,71 @@
294 pairs = cr.fetchall()303 pairs = cr.fetchall()
295 for pair in pairs:304 for pair in pairs:
296 context['label_memoizer'][pair[0]].append(partner)305 context['label_memoizer'][pair[0]].append(partner)
297 st_line = st_obj.browse(cr, uid, line_id, context=context)306 if st_line['id'] in context['label_memoizer']:
298 if st_line and st_line.id in context['label_memoizer']:307 found_partner = context['label_memoizer'][st_line['id']]
299 found_partner = context['label_memoizer'][st_line.id]
300 if len(found_partner) > 1:308 if len(found_partner) > 1:
301 raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by '309 raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by '
302 'more than one partner.') %310 'more than one partner while looking on partner label') %
303 (st_line.name, st_line.ref))311 (st_line['name'], st_line['ref']))
304 res['partner_id'] = found_partner[0].id312 res['partner_id'] = found_partner[0].id
305 st_vals = st_obj.get_values_for_line(cr,313 st_vals = st_obj.get_values_for_line(cr,
306 uid,314 uid,
307 profile_id=st_line.statement_id.profile_id.id,315 profile_id=st_line['profile_id'],
316 master_account_id=st_line['master_account_id'],
308 partner_id=found_partner[0].id,317 partner_id=found_partner[0].id,
309 line_type=st_line.type,318 line_type=False,
310 amount=st_line.amount,319 amount=st_line['amount'] if st_line['amount'] else 0.0,
311 context=context)320 context=context)
312 res.update(st_vals)321 res.update(st_vals)
313 return res322 return res
314323
315 def get_from_label_and_partner_name(self, cr, uid, line_id, context=None):324 def get_from_label_and_partner_name(self, cr, uid, st_line, context=None):
316 """325 """
317 Match the partner based on the label field of the statement line326 Match the partner based on the label field of the statement line
318 and the name of the partner.327 and the name of the partner.
319 Then, call the generic get_values_for_line method to complete other values.328 Then, call the generic get_values_for_line method to complete other values.
320 If more than one partner matched, raise the ErrorTooManyPartner error.329 If more than one partner matched, raise the ErrorTooManyPartner error.
321330
322 :param int/long line_id: id of the concerned account.bank.statement.line331 :param dict st_line: read of the concerned account.bank.statement.line
323 :return:332 :return:
324 A dict of value that can be passed directly to the write method of333 A dict of value that can be passed directly to the write method of
325 the statement line or {}334 the statement line or {}
326 {'partner_id': value,335 {'partner_id': value,
327 'account_id' : value,336 'account_id': value,
328337
329 ...}338 ...}
330 """339 """
331 # This Method has not been tested yet !
332 res = {}340 res = {}
341 # We memoize allowed partner
342 if not context.get('partner_memoizer'):
343 context['partner_memoizer'] = tuple(self.pool['res.partner'].search(cr, uid, []))
344 if not context['partner_memoizer']:
345 return res
333 st_obj = self.pool.get('account.bank.statement.line')346 st_obj = self.pool.get('account.bank.statement.line')
334 st_line = st_obj.browse(cr, uid, line_id, context=context)347 sql = "SELECT id FROM res_partner WHERE name ~* %s and id in %s"
335 if st_line:348 pattern = ".*%s.*" % re.escape(st_line['name'])
336 sql = "SELECT id FROM res_partner WHERE name ~* %s"349 cr.execute(sql, (pattern, context['partner_memoizer']))
337 pattern = ".*%s.*" % re.escape(st_line.label)350 result = cr.fetchall()
338 cr.execute(sql, (pattern,))351 if not result:
339 result = cr.fetchall()352 return res
340 if not result:353 if len(result) > 1:
341 return res354 raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more '
342 if len(result) > 1:355 'than one partner while looking on partner by name') %
343 raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more '356 (st_line['name'], st_line['ref']))
344 'than one partner.') %357 res['partner_id'] = result[0][0]
345 (st_line.name, st_line.ref))358 st_vals = st_obj.get_values_for_line(cr,
346 res['partner_id'] = result[0][0] if result else False359 uid,
347 if res:360 profile_id=st_line['porfile_id'],
348 st_vals = st_obj.get_values_for_line(361 master_account_id=st_line['master_account_id'],
349 cr,362 partner_id=res['partner_id'],
350 uid,363 line_type=False,
351 profile_id=st_line.statement_id.profile_id.id,364 amount=st_line['amount'] if st_line['amount'] else 0.0,
352 partner_id=res['partner_id'],365 context=context)
353 line_type=st_line.type,366 res.update(st_vals)
354 amount=st_line.amount,
355 context=context)
356 res.update(st_vals)
357 return res367 return res
358368
359369
360class AccountStatementLine(Model):370class AccountStatementLine(orm.Model):
361 """371 """
362 Add sparse field on the statement line to allow to store all the372 Add sparse field on the statement line to allow to store all the
363 bank infos that are given by a bank/office. You can then add you own in your373 bank infos that are given by a bank/office. You can then add you own in your
@@ -390,7 +400,7 @@
390 'already_completed': False,400 'already_completed': False,
391 }401 }
392402
393 def get_line_values_from_rules(self, cr, uid, ids, context=None):403 def _get_line_values_from_rules(self, cr, uid, line, rules, context=None):
394 """404 """
395 We'll try to find out the values related to the line based on rules setted on405 We'll try to find out the values related to the line based on rules setted on
396 the profile.. We will ignore line for which already_completed is ticked.406 the profile.. We will ignore line for which already_completed is ticked.
@@ -401,36 +411,17 @@
401 {117009: {'partner_id': 100997, 'account_id': 489L}}411 {117009: {'partner_id': 100997, 'account_id': 489L}}
402 """412 """
403 profile_obj = self.pool.get('account.statement.profile')413 profile_obj = self.pool.get('account.statement.profile')
404 st_obj = self.pool.get('account.bank.statement.line')414 if line.get('already_completed'):
405 res = {}415 return {}
406 errors_stack = []416 # Ask the rule
407 for line in self.browse(cr, uid, ids, context=context):417 vals = profile_obj._find_values_from_rules(cr, uid, rules, line, context)
408 if line.already_completed:418 if vals:
409 continue419 vals['id'] = line['id']
410 try:420 return vals
411 # Take the default values421 return {}
412 res[line.id] = st_obj.get_values_for_line(422
413 cr,423
414 uid,424class AccountBankSatement(orm.Model):
415 profile_id=line.statement_id.profile_id.id,
416 line_type=line.type,
417 amount=line.amount,
418 context=context)
419 # Ask the rule
420 vals = profile_obj.find_values_from_rules(
421 cr, uid, line.statement_id.profile_id.id, line.id, context)
422 # Merge the result
423 res[line.id].update(vals)
424 except ErrorTooManyPartner, exc:
425 msg = "Line ID %s had following error: %s" % (line.id, exc.value)
426 errors_stack.append(msg)
427 if errors_stack:
428 msg = u"\n".join(errors_stack)
429 raise ErrorTooManyPartner(msg)
430 return res
431
432
433class AccountBankSatement(Model):
434 """425 """
435 We add a basic button and stuff to support the auto-completion426 We add a basic button and stuff to support the auto-completion
436 of the bank statement once line have been imported or manually fullfill.427 of the bank statement once line have been imported or manually fullfill.
@@ -450,59 +441,82 @@
450 :param int/long stat_id: ID of the account.bank.statement441 :param int/long stat_id: ID of the account.bank.statement
451 :param char error_msg: Message to add442 :param char error_msg: Message to add
452 :number_imported int/long: Number of lines that have been completed443 :number_imported int/long: Number of lines that have been completed
453 :return : True444 :return True
454 """445 """
455 error_log = ""446 user_name = self.pool.get('res.users').read(cr, uid, uid,
456 user_name = self.pool.get('res.users').read(447 ['name'], context=context)['name']
457 cr, uid, uid, ['name'], context=context)['name']448
458 log = self.read(449 log = self.read(cr, uid, stat_id, ['completion_logs'],
459 cr, uid, stat_id, ['completion_logs'], context=context)['completion_logs']450 context=context)['completion_logs']
460 log_line = log and log.split("\n") or []451 log = log if log else ""
452
461 completion_date = datetime.datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT)453 completion_date = datetime.datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT)
462 if error_msg:454 message = (_("%s Bank Statement ID %s has %s lines completed by %s \n%s\n") %
463 error_log = error_msg455 (completion_date, stat_id, number_imported, user_name, log))
464 log_line[0:0] = [completion_date + ' : '456 self.write(cr, uid, [stat_id], {'completion_logs': message}, context=context)
465 + _("Bank Statement ID %s has %s lines completed by %s") % (stat_id, number_imported, user_name)457
466 + "\n" + error_log + "-------------" + "\n"]458 body = (_('Statement ID %s auto-completed for %s lines completed') %
467 log = "\n".join(log_line)459 (stat_id, number_imported)),
468 self.write(cr, uid, [stat_id], {'completion_logs': log}, context=context)460 self.message_post(cr, uid,
469 self.message_post(461 [stat_id],
470 cr, uid,462 body=body,
471 [stat_id],463 context=context)
472 body=_('Statement ID %s auto-completed for %s lines completed') % (stat_id, number_imported),
473 context=context)
474 return True464 return True
475465
476 def button_auto_completion(self, cr, uid, ids, context=None):466 def button_auto_completion(self, cr, uid, ids, context=None):
477 """467 """
478 Complete line with values given by rules and tic the already_completed468 Complete line with values given by rules and tic the already_completed
479 checkbox so we won't compute them again unless the user untick them !469 checkbox so we won't compute them again unless the user untick them!
480 """470 """
481 if context is None:471 if context is None:
482 context = {}472 context = {}
483 stat_line_obj = self.pool.get('account.bank.statement.line')473 stat_line_obj = self.pool['account.bank.statement.line']
474 profile_obj = self.pool.get('account.statement.profile')
484 compl_lines = 0475 compl_lines = 0
476 stat_line_obj.check_access_rule(cr, uid, [], 'create')
477 stat_line_obj.check_access_rights(cr, uid, 'create', raise_exception=True)
485 for stat in self.browse(cr, uid, ids, context=context):478 for stat in self.browse(cr, uid, ids, context=context):
486 msg_lines = []479 msg_lines = []
487 ctx = context.copy()480 ctx = context.copy()
488 ctx['line_ids'] = stat.line_ids481 ctx['line_ids'] = tuple((x.id for x in stat.line_ids))
489 for line in stat.line_ids:482 b_profile = stat.profile_id
490 res = {}483 rules = profile_obj._get_callable(cr, uid, b_profile, context=context)
484 profile_id = b_profile.id # Only for perfo even it gains almost nothing
485 master_account_id = b_profile.receivable_account_id
486 master_account_id = master_account_id.id if master_account_id else False
487 res = False
488 for line in stat_line_obj.read(cr, uid, ctx['line_ids']):
491 try:489 try:
492 res = stat_line_obj.get_line_values_from_rules(490 # performance trick
493 cr, uid, [line.id], context=ctx)491 line['master_account_id'] = master_account_id
492 line['profile_id'] = profile_id
493 res = stat_line_obj._get_line_values_from_rules(cr, uid, line,
494 rules, context=ctx)
494 if res:495 if res:
495 compl_lines += 1496 compl_lines += 1
496 except ErrorTooManyPartner, exc:497 except ErrorTooManyPartner, exc:
497 msg_lines.append(repr(exc))498 msg_lines.append(repr(exc))
498 except Exception, exc:499 except Exception, exc:
499 msg_lines.append(repr(exc))500 msg_lines.append(repr(exc))
500 # vals = res and res.keys() or False501 error_type, error_value, trbk = sys.exc_info()
502 st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value)
503 st += ''.join(traceback.format_tb(trbk, 30))
504 _logger.error(st)
501 if res:505 if res:
502 vals = res[line.id]506 #stat_line_obj.write(cr, uid, [line.id], vals, context=ctx)
503 vals['already_completed'] = True507 try:
504 stat_line_obj.write(cr, uid, [line.id], vals, context=ctx)508 stat_line_obj._update_line(cr, uid, res, context=context)
509 except Exception as exc:
510 msg_lines.append(repr(exc))
511 error_type, error_value, trbk = sys.exc_info()
512 st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value)
513 st += ''.join(traceback.format_tb(trbk, 30))
514 _logger.error(st)
515 # we can commit as it is not needed to be atomic
516 # commiting here adds a nice perfo boost
517 if not compl_lines % 500:
518 cr.commit()
505 msg = u'\n'.join(msg_lines)519 msg = u'\n'.join(msg_lines)
506 self.write_completion_log(cr, uid, stat.id,520 self.write_completion_log(cr, uid, stat.id,
507 msg, compl_lines, context=context)521 msg, compl_lines, context=context)
508 return True522 return True
509523
=== modified file 'account_statement_base_import/parser/generic_file_parser.py'
--- account_statement_base_import/parser/generic_file_parser.py 2013-02-26 08:22:25 +0000
+++ account_statement_base_import/parser/generic_file_parser.py 2013-04-25 11:52:27 +0000
@@ -29,6 +29,7 @@
29except:29except:
30 raise Exception(_('Please install python lib xlrd'))30 raise Exception(_('Please install python lib xlrd'))
3131
32
32def float_or_zero(val):33def float_or_zero(val):
33 """ Conversion function used to manage34 """ Conversion function used to manage
34 empty string into float usecase"""35 empty string into float usecase"""
@@ -82,14 +83,12 @@
82 In this generic parser, the commission is given for every line, so we store it83 In this generic parser, the commission is given for every line, so we store it
83 for each one.84 for each one.
84 """85 """
85 return {86 return {'name': line.get('label', line.get('ref', '/')),
86 'name': line.get('label', line.get('ref', '/')),87 'date': line.get('date', datetime.datetime.now().date()),
87 'date': line.get('date', datetime.datetime.now().date()),88 'amount': line.get('amount', 0.0),
88 'amount': line.get('amount', 0.0),89 'ref': line.get('ref', '/'),
89 'ref': line.get('ref', '/'),90 'label': line.get('label', ''),
90 'label': line.get('label', ''),91 'commission_amount': line.get('commission_amount', 0.0)}
91 'commission_amount': line.get('commission_amount', 0.0),
92 }
9392
94 def _post(self, *args, **kwargs):93 def _post(self, *args, **kwargs):
95 """94 """
9695
=== modified file 'account_statement_base_import/statement.py'
--- account_statement_base_import/statement.py 2013-02-26 08:45:03 +0000
+++ account_statement_base_import/statement.py 2013-04-25 11:52:27 +0000
@@ -18,14 +18,16 @@
18# along with this program. If not, see <http://www.gnu.org/licenses/>.18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#19#
20##############################################################################20##############################################################################
21import sys
22import traceback
23
24import psycopg2
2125
22from openerp.tools.translate import _26from openerp.tools.translate import _
23import datetime27import datetime
24from openerp.osv.orm import Model28from openerp.osv.orm import Model
25from openerp.osv import fields, osv29from openerp.osv import fields, osv
26from parser import new_bank_statement_parser30from parser import new_bank_statement_parser
27import sys
28import traceback
2931
3032
31class AccountStatementProfil(Model):33class AccountStatementProfil(Model):
@@ -43,7 +45,8 @@
43 help="Tic that box to automatically launch the completion "45 help="Tic that box to automatically launch the completion "
44 "on each imported file using this profile."),46 "on each imported file using this profile."),
45 'last_import_date': fields.datetime("Last Import Date"),47 'last_import_date': fields.datetime("Last Import Date"),
46 'rec_log': fields.text('log', readonly=True, deprecated=True),48 # we remove deprecated as it floods logs in standard/warning level sob...
49 'rec_log': fields.text('log', readonly=True), # Deprecated
47 'import_type': fields.selection(50 'import_type': fields.selection(
48 get_import_type_selection,51 get_import_type_selection,
49 'Type of import',52 'Type of import',
@@ -64,7 +67,8 @@
64 self.message_post(cr,67 self.message_post(cr,
65 uid,68 uid,
66 ids,69 ids,
67 body=_('Statement ID %s have been imported with %s lines.') % (statement_id, num_lines),70 body=_('Statement ID %s have been imported with %s lines.') %
71 (statement_id, num_lines),
68 context=context)72 context=context)
69 return True73 return True
7074
@@ -79,7 +83,7 @@
79 :param: browse_record of the current parser83 :param: browse_record of the current parser
80 :param: result_row_list: [{'key':value}]84 :param: result_row_list: [{'key':value}]
81 :param: profile: browserecord of account.statement.profile85 :param: profile: browserecord of account.statement.profile
82 :param: statement_id : int/long of the current importing statement ID86 :param: statement_id: int/long of the current importing statement ID
83 :param: context: global context87 :param: context: global context
84 return: dict of vals that will be passed to create method of statement line.88 return: dict of vals that will be passed to create method of statement line.
85 """89 """
@@ -98,7 +102,7 @@
98 'account_id': commission_account_id,102 'account_id': commission_account_id,
99 'ref': 'commission',103 'ref': 'commission',
100 'analytic_account_id': commission_analytic_id,104 'analytic_account_id': commission_analytic_id,
101 # !! We set the already_completed so auto-completion will not update those values !105 # !! We set the already_completed so auto-completion will not update those values!
102 'already_completed': True,106 'already_completed': True,
103 }107 }
104 return comm_values108 return comm_values
@@ -116,18 +120,32 @@
116 :param int/long account_payable: ID of the receivable account to use120 :param int/long account_payable: ID of the receivable account to use
117 :param int/long account_receivable: ID of the payable account to use121 :param int/long account_receivable: ID of the payable account to use
118 :param int/long statement_id: ID of the concerned account.bank.statement122 :param int/long statement_id: ID of the concerned account.bank.statement
119 :return : dict of vals that will be passed to create method of statement line.123 :return: dict of vals that will be passed to create method of statement line.
120 """124 """
121 statement_obj = self.pool.get('account.bank.statement')125 statement_obj = self.pool.get('account.bank.statement')
122 values = parser_vals126 values = parser_vals
123 values['statement_id'] = statement_id127 values['statement_id'] = statement_id
124 values['account_id'] = statement_obj.get_account_for_counterpart(128 values['account_id'] = statement_obj.get_account_for_counterpart(cr,
125 cr,129 uid,
126 uid,130 parser_vals['amount'],
127 parser_vals['amount'],131 account_receivable,
128 account_receivable,132 account_payable)
129 account_payable133
130 )134 date = values.get('date')
135 period_memoizer = context.get('period_memoizer')
136 if not period_memoizer:
137 period_memoizer = {}
138 context['period_memoizer'] = period_memoizer
139 if period_memoizer.get(date):
140 values['period_id'] = period_memoizer[date]
141 else:
142 # This is awfully slow...
143 periods = self.pool.get('account.period').find(cr, uid,
144 dt=values.get('date'),
145 context=context)
146 values['period_id'] = periods[0]
147 period_memoizer[date] = periods[0]
148 values['type'] = 'general'
131 return values149 return values
132150
133 def statement_import(self, cr, uid, ids, profile_id, file_stream, ftype="csv", context=None):151 def statement_import(self, cr, uid, ids, profile_id, file_stream, ftype="csv", context=None):
@@ -148,75 +166,85 @@
148 attachment_obj = self.pool.get('ir.attachment')166 attachment_obj = self.pool.get('ir.attachment')
149 prof_obj = self.pool.get("account.statement.profile")167 prof_obj = self.pool.get("account.statement.profile")
150 if not profile_id:168 if not profile_id:
151 raise osv.except_osv(169 raise osv.except_osv(_("No Profile!"),
152 _("No Profile !"),170 _("You must provide a valid profile to import a bank statement!"))
153 _("You must provide a valid profile to import a bank statement !"))
154 prof = prof_obj.browse(cr, uid, profile_id, context=context)171 prof = prof_obj.browse(cr, uid, profile_id, context=context)
155172
156 parser = new_bank_statement_parser(prof.import_type, ftype=ftype)173 parser = new_bank_statement_parser(prof.import_type, ftype=ftype)
157 result_row_list = parser.parse(file_stream)174 result_row_list = parser.parse(file_stream)
158 # Check all key are present in account.bank.statement.line !!175 # Check all key are present in account.bank.statement.line!!
176 if not result_row_list:
177 raise osv.except_osv(_("Nothing to import"),
178 _("The file is empty"))
159 parsed_cols = parser.get_st_line_vals(result_row_list[0]).keys()179 parsed_cols = parser.get_st_line_vals(result_row_list[0]).keys()
160 for col in parsed_cols:180 for col in parsed_cols:
161 if col not in statement_line_obj._columns:181 if col not in statement_line_obj._columns:
162 raise osv.except_osv(182 raise osv.except_osv(_("Missing column!"),
163 _("Missing column !"),183 _("Column %s you try to import is not "
164 _("Column %s you try to import is not "184 "present in the bank statement line!") % col)
165 "present in the bank statement line !") % col)
166185
167 statement_id = statement_obj.create(186 statement_id = statement_obj.create(cr, uid,
168 cr, uid, {'profile_id': prof.id}, context=context)187 {'profile_id': prof.id},
169 account_receivable, account_payable = statement_obj.get_default_pay_receiv_accounts(188 context=context)
170 cr, uid, context)189 if prof.receivable_account_id:
190 account_receivable = account_payable = prof.receivable_account_id.id
191 else:
192 account_receivable, account_payable = statement_obj.get_default_pay_receiv_accounts(
193 cr, uid, context)
171 try:194 try:
172 # Record every line in the bank statement and compute the global commission195 # Record every line in the bank statement and compute the global commission
173 # based on the commission_amount column196 # based on the commission_amount column
174 statement_store = []197 statement_store = []
175 for line in result_row_list:198 for line in result_row_list:
176 parser_vals = parser.get_st_line_vals(line)199 parser_vals = parser.get_st_line_vals(line)
177 values = self.prepare_statetement_lines_vals(200 values = self.prepare_statetement_lines_vals(cr, uid, parser_vals, account_payable,
178 cr, uid, parser_vals, account_payable,201 account_receivable, statement_id, context)
179 account_receivable, statement_id, context)202 statement_store.append(values)
180 # we finally create the line in system203 # Hack to bypass ORM poor perfomance. Sob...
181 statement_store.append((0, 0, values))204 statement_line_obj._insert_lines(cr, uid, statement_store, context=context)
205
182 # Build and create the global commission line for the whole statement206 # Build and create the global commission line for the whole statement
183 statement_obj.write(cr, uid, [statement_id],
184 {'line_ids': statement_store}, context=context)
185 comm_vals = self.prepare_global_commission_line_vals(cr, uid, parser, result_row_list,207 comm_vals = self.prepare_global_commission_line_vals(cr, uid, parser, result_row_list,
186 prof, statement_id, context)208 prof, statement_id, context)
187 if comm_vals:209 if comm_vals:
188 statement_line_obj.create(cr, uid, comm_vals, context=context)210 statement_line_obj.create(cr, uid, comm_vals, context=context)
189211 else:
190 attachment_obj.create(212 # Trigger store field computation if someone has better idea
191 cr,213 start_bal = statement_obj.read(cr, uid, statement_id,
192 uid,214 ['balance_start'],
193 {215 context=context)
194 'name': 'statement file',216 start_bal = start_bal['balance_start']
195 'datas': file_stream,217 statement_obj.write(cr, uid, [statement_id],
196 'datas_fname': "%s.%s" % (218 {'balance_start': start_bal})
197 datetime.datetime.now().date(),219
198 ftype),220 attachment_obj.create(cr,
199 'res_model': 'account.bank.statement',221 uid,
200 'res_id': statement_id,222 {'name': 'statement file',
201 },223 'datas': file_stream,
202 context=context224 'datas_fname': "%s.%s" % (
203 )225 datetime.datetime.now().date(),
204 # If user ask to launch completion at end of import, do it !226 ftype),
227 'res_model': 'account.bank.statement',
228 'res_id': statement_id},
229 context=context)
230
231 # If user ask to launch completion at end of import, do it!
205 if prof.launch_import_completion:232 if prof.launch_import_completion:
206 statement_obj.button_auto_completion(cr, uid, [statement_id], context)233 statement_obj.button_auto_completion(cr, uid, [statement_id], context)
207234
208 # Write the needed log infos on profile235 # Write the needed log infos on profile
209 self.write_logs_after_import(236 self.write_logs_after_import(cr, uid, prof.id,
210 cr, uid, prof.id, statement_id, len(result_row_list), context)237 statement_id,
238 len(result_row_list),
239 context)
211240
212 except Exception:241 except Exception:
213 statement_obj.unlink(cr, uid, [statement_id], context=context)242 statement_obj.unlink(cr, uid, [statement_id], context=context)
214 error_type, error_value, trbk = sys.exc_info()243 error_type, error_value, trbk = sys.exc_info()
215 st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value)244 st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value)
216 st += ''.join(traceback.format_tb(trbk, 30))245 st += ''.join(traceback.format_tb(trbk, 30))
217 raise osv.except_osv(246 raise osv.except_osv(_("Statement import error"),
218 _("Statement import error"),247 _("The statement cannot be created: %s") % st)
219 _("The statement cannot be created : %s") % st)
220 return statement_id248 return statement_id
221249
222250
@@ -228,6 +256,45 @@
228 """256 """
229 _inherit = "account.bank.statement.line"257 _inherit = "account.bank.statement.line"
230258
259 def _get_available_columns(self, statement_store):
260 """Return writeable by SQL columns"""
261 statement_line_obj = self.pool['account.bank.statement.line']
262 model_cols = statement_line_obj._columns
263 avail = [k for k, col in model_cols.iteritems() if not hasattr(col, '_fnct')]
264 keys = [k for k in statement_store[0].keys() if k in avail]
265 keys.sort()
266 return keys
267
268 def _insert_lines(self, cr, uid, statement_store, context=None):
269 """ Do raw insert into database because ORM is awfully slow
270 when doing batch write. It is a shame that batch function
271 does not exist"""
272 statement_line_obj = self.pool['account.bank.statement.line']
273 statement_line_obj.check_access_rule(cr, uid, [], 'create')
274 statement_line_obj.check_access_rights(cr, uid, 'create', raise_exception=True)
275 cols = self._get_available_columns(statement_store)
276 tmp_vals = (', '.join(cols), ', '.join(['%%(%s)s' % i for i in cols]))
277 sql = "INSERT INTO account_bank_statement_line (%s) VALUES (%s);" % tmp_vals
278 try:
279 cr.executemany(sql, tuple(statement_store))
280 except psycopg2.Error as sql_err:
281 cr.rollback()
282 raise osv.except_osv(_("ORM bypass error"),
283 sql_err.pgerror)
284
285 def _update_line(self, cr, uid, vals, context=None):
286 """ Do raw update into database because ORM is awfully slow
287 when cheking security."""
288 cols = self._get_available_columns([vals])
289 tmp_vals = (', '.join(['%s = %%(%s)s' % (i, i) for i in cols]))
290 sql = "UPDATE account_bank_statement_line SET %s where id = %%(id)s;" % tmp_vals
291 try:
292 cr.execute(sql, vals)
293 except psycopg2.Error as sql_err:
294 cr.rollback()
295 raise osv.except_osv(_("ORM bypass error"),
296 sql_err.pgerror)
297
231 _columns = {298 _columns = {
232 'commission_amount': fields.sparse(299 'commission_amount': fields.sparse(
233 type='float',300 type='float',
234301
=== modified file 'account_statement_ext/__openerp__.py'
--- account_statement_ext/__openerp__.py 2013-02-14 08:13:49 +0000
+++ account_statement_ext/__openerp__.py 2013-04-25 11:52:27 +0000
@@ -25,11 +25,9 @@
25 'maintainer': 'Camptocamp',25 'maintainer': 'Camptocamp',
26 'category': 'Finance',26 'category': 'Finance',
27 'complexity': 'normal',27 'complexity': 'normal',
28 'depends': [28 'depends': ['account',
29 'account',29 'report_webkit',
30 'report_webkit',30 'account_voucher'],
31 'account_voucher'
32 ],
33 'description': """31 'description': """
34 Improve the basic bank statement, by adding various new features,32 Improve the basic bank statement, by adding various new features,
35 and help dealing with huge volume of reconciliation through payment offices such as Paypal, Lazer,33 and help dealing with huge volume of reconciliation through payment offices such as Paypal, Lazer,
@@ -65,7 +63,6 @@
65 4) Remove the period on the bank statement, and compute it for each line based on their date instead.63 4) Remove the period on the bank statement, and compute it for each line based on their date instead.
66 It also adds this feature in the voucher in order to compute the period correctly.64 It also adds this feature in the voucher in order to compute the period correctly.
6765
68
69 5) Cancelling a bank statement is much more easy and will cancel all related entries, unreconcile them,66 5) Cancelling a bank statement is much more easy and will cancel all related entries, unreconcile them,
70 and finally delete them.67 and finally delete them.
7168
@@ -88,4 +85,4 @@
88 'auto_install': False,85 'auto_install': False,
89 'license': 'AGPL-3',86 'license': 'AGPL-3',
90 'active': False,87 'active': False,
91}88 }
9289
=== modified file 'account_statement_ext/i18n/fr.po'
--- account_statement_ext/i18n/fr.po 2012-12-13 13:57:29 +0000
+++ account_statement_ext/i18n/fr.po 2013-04-25 11:52:27 +0000
@@ -41,7 +41,7 @@
41#. module: account_statement_ext41#. module: account_statement_ext
42#: code:addons/account_statement_ext/statement.py:36142#: code:addons/account_statement_ext/statement.py:361
43#, python-format43#, python-format
44msgid "Configuration Error !"44msgid "Configuration Error!"
45msgstr "Erreur de configuration !"45msgstr "Erreur de configuration !"
4646
47#. module: account_statement_ext47#. module: account_statement_ext
@@ -64,7 +64,7 @@
64#: code:addons/account_statement_ext/statement.py:30764#: code:addons/account_statement_ext/statement.py:307
65#: code:addons/account_statement_ext/statement.py:37265#: code:addons/account_statement_ext/statement.py:372
66#, python-format66#, python-format
67msgid "Error !"67msgid "Error!"
68msgstr "Erreur !"68msgstr "Erreur !"
6969
70#. module: account_statement_ext70#. module: account_statement_ext
7171
=== modified file 'account_statement_ext/statement.py'
--- account_statement_ext/statement.py 2013-03-01 14:33:32 +0000
+++ account_statement_ext/statement.py 2013-04-25 11:52:27 +0000
@@ -1,4 +1,4 @@
1# -*- coding: utf-8 -*-1#-*- coding: utf-8 -*-
2##############################################################################2##############################################################################
3#3#
4# Author: Nicolas Bessi, Joel Grand-Guillaume4# Author: Nicolas Bessi, Joel Grand-Guillaume
@@ -18,12 +18,26 @@
18# along with this program. If not, see <http://www.gnu.org/licenses/>.18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#19#
20##############################################################################20##############################################################################
2121import openerp.addons.account.account_bank_statement as stat_mod
22from openerp.osv.orm import Model22from openerp.osv.orm import Model
23from openerp.osv import fields, osv23from openerp.osv import fields, osv
24from openerp.tools.translate import _24from openerp.tools.translate import _
2525
2626
27# Monkey patch to fix bad write implementation...
28def fixed_write(self, cr, uid, ids, vals, context=None):
29 """ Fix performance desing of original function
30 Ideally we should use a real PostgreSQL sequence or serial fields.
31 I will do it when I have time."""
32 res = super(stat_mod.account_bank_statement, self).write(cr, uid, ids,
33 vals, context=context)
34 cr.execute("UPDATE account_bank_statement_line"
35 " SET sequence = account_bank_statement_line.id + 1"
36 " where statement_id in %s", (tuple(ids),))
37 return res
38stat_mod.account_bank_statement.write = fixed_write
39
40
27class AccountStatementProfil(Model):41class AccountStatementProfil(Model):
28 """42 """
29 A Profile will contain all infos related to the type of43 A Profile will contain all infos related to the type of
@@ -44,38 +58,45 @@
44 "commission move (and optionaly on the counterpart "58 "commission move (and optionaly on the counterpart "
45 "of the intermediate/banking move if you tick the "59 "of the intermediate/banking move if you tick the "
46 "corresponding checkbox)."),60 "corresponding checkbox)."),
61
47 'journal_id': fields.many2one(62 'journal_id': fields.many2one(
48 'account.journal',63 'account.journal',
49 'Financial journal to use for transaction',64 'Financial journal to use for transaction',
50 required=True),65 required=True),
66
51 'commission_account_id': fields.many2one(67 'commission_account_id': fields.many2one(
52 'account.account',68 'account.account',
53 'Commission account',69 'Commission account',
54 required=True),70 required=True),
71
55 'commission_analytic_id': fields.many2one(72 'commission_analytic_id': fields.many2one(
56 'account.analytic.account',73 'account.analytic.account',
57 'Commission analytic account'),74 'Commission analytic account'),
75
58 'receivable_account_id': fields.many2one(76 'receivable_account_id': fields.many2one(
59 'account.account',77 'account.account',
60 'Force Receivable/Payable Account',78 'Force Receivable/Payable Account',
61 help="Choose a receivable account to force the default "79 help="Choose a receivable account to force the default "
62 "debit/credit account (eg. an intermediat bank account "80 "debit/credit account (eg. an intermediat bank account "
63 "instead of default debitors)."),81 "instead of default debitors)."),
82
64 'force_partner_on_bank': fields.boolean(83 'force_partner_on_bank': fields.boolean(
65 'Force partner on bank move',84 'Force partner on bank move',
66 help="Tick that box if you want to use the credit "85 help="Tick that box if you want to use the credit "
67 "institute partner in the counterpart of the "86 "institute partner in the counterpart of the "
68 "intermediate/banking move."),87 "intermediate/banking move."),
88
69 'balance_check': fields.boolean(89 'balance_check': fields.boolean(
70 'Balance check',90 'Balance check',
71 help="Tick that box if you want OpenERP to control "91 help="Tick that box if you want OpenERP to control "
72 "the start/end balance before confirming a bank statement. "92 "the start/end balance before confirming a bank statement. "
73 "If don't ticked, no balance control will be done."93 "If don't ticked, no balance control will be done."),
74 ),94
75 'bank_statement_prefix': fields.char(95 'bank_statement_prefix': fields.char('Bank Statement Prefix', size=32),
76 'Bank Statement Prefix', size=32),96
77 'bank_statement_ids': fields.one2many(97 'bank_statement_ids': fields.one2many('account.bank.statement',
78 'account.bank.statement', 'profile_id', 'Bank Statement Imported'),98 'profile_id',
99 'Bank Statement Imported'),
79 'company_id': fields.many2one('res.company', 'Company'),100 'company_id': fields.many2one('res.company', 'Company'),
80 }101 }
81102
@@ -86,7 +107,7 @@
86 return True107 return True
87108
88 _constraints = [109 _constraints = [
89 (_check_partner, "You need to put a partner if you tic the 'Force partner on bank move' !", []),110 (_check_partner, "You need to put a partner if you tic the 'Force partner on bank move'!", []),
90 ]111 ]
91112
92113
@@ -166,11 +187,11 @@
166 """187 """
167 for statement in self.browse(cr, uid, ids, context=context):188 for statement in self.browse(cr, uid, ids, context=context):
168 if (statement.period_id and189 if (statement.period_id and
169 statement.company_id.id != statement.period_id.company_id.id):190 statement.company_id.id != statement.period_id.company_id.id):
170 return False191 return False
171 for line in statement.line_ids:192 for line in statement.line_ids:
172 if (line.period_id and193 if (line.period_id and
173 statement.company_id.id != line.period_id.company_id.id):194 statement.company_id.id != line.period_id.company_id.id):
174 return False195 return False
175 return True196 return True
176197
@@ -244,8 +265,10 @@
244 create the move from.265 create the move from.
245 :return: int/long of the res.partner to use as counterpart266 :return: int/long of the res.partner to use as counterpart
246 """267 """
247 bank_partner_id = super(AccountBankSatement, self).\268 bank_partner_id = super(AccountBankSatement, self)._get_counter_part_partner(cr,
248 _get_counter_part_partner(cr, uid, st_line, context=context)269 uid,
270 st_line,
271 context=context)
249 # get the right partner according to the chosen profil272 # get the right partner according to the chosen profil
250 if st_line.statement_id.profile_id.force_partner_on_bank:273 if st_line.statement_id.profile_id.force_partner_on_bank:
251 bank_partner_id = st_line.statement_id.profile_id.partner_id.id274 bank_partner_id = st_line.statement_id.profile_id.partner_id.id
@@ -285,7 +308,7 @@
285 We have to copy paste a big block of code, changing the error308 We have to copy paste a big block of code, changing the error
286 stack + managing period from date.309 stack + managing period from date.
287310
288 TODO: Log the error in a bank statement field instead of using a popup !311 TODO: Log the error in a bank statement field instead of using a popup!
289 """312 """
290 for st in self.browse(cr, uid, ids, context=context):313 for st in self.browse(cr, uid, ids, context=context):
291314
@@ -297,8 +320,8 @@
297 self.balance_check(cr, uid, st.id, journal_type=j_type, context=context)320 self.balance_check(cr, uid, st.id, journal_type=j_type, context=context)
298 if (not st.journal_id.default_credit_account_id) \321 if (not st.journal_id.default_credit_account_id) \
299 or (not st.journal_id.default_debit_account_id):322 or (not st.journal_id.default_debit_account_id):
300 raise osv.except_osv(_('Configuration Error !'),323 raise osv.except_osv(_('Configuration Error!'),
301 _('Please verify that an account is defined in the journal.'))324 _('Please verify that an account is defined in the journal.'))
302325
303 if not st.name == '/':326 if not st.name == '/':
304 st_number = st.name327 st_number = st.name
@@ -308,20 +331,24 @@
308# End Changes331# End Changes
309 for line in st.move_line_ids:332 for line in st.move_line_ids:
310 if line.state != 'valid':333 if line.state != 'valid':
311 raise osv.except_osv(_('Error !'),334 raise osv.except_osv(_('Error!'),
312 _('The account entries lines are not in valid state.'))335 _('The account entries lines are not in valid state.'))
313# begin changes336# begin changes
314 errors_stack = []337 errors_stack = []
315 for st_line in st.line_ids:338 for st_line in st.line_ids:
316 try:339 try:
317 if st_line.analytic_account_id:340 if st_line.analytic_account_id:
318 if not st.journal_id.analytic_journal_id:341 if not st.journal_id.analytic_journal_id:
319 raise osv.except_osv(_('No Analytic Journal !'),342 raise osv.except_osv(_('No Analytic Journal!'),
320 _("You have to assign an analytic journal on the '%s' journal!") % (st.journal_id.name,))343 _("You have to assign an analytic"
344 " journal on the '%s' journal!") % st.journal_id.name)
321 if not st_line.amount:345 if not st_line.amount:
322 continue346 continue
323 st_line_number = self.get_next_st_line_number(cr, uid, st_number, st_line, context)347 st_line_number = self.get_next_st_line_number(cr, uid, st_number, st_line, context)
324 self.create_move_from_st_line(cr, uid, st_line.id, company_currency_id, st_line_number, context)348 self.create_move_from_st_line(cr, uid, st_line.id,
349 company_currency_id,
350 st_line_number,
351 context)
325 except osv.except_osv, exc:352 except osv.except_osv, exc:
326 msg = "Line ID %s with ref %s had following error: %s" % (st_line.id, st_line.ref, exc.value)353 msg = "Line ID %s with ref %s had following error: %s" % (st_line.id, st_line.ref, exc.value)
327 errors_stack.append(msg)354 errors_stack.append(msg)
@@ -332,37 +359,97 @@
332 msg = u"\n".join(errors_stack)359 msg = u"\n".join(errors_stack)
333 raise osv.except_osv(_('Error'), msg)360 raise osv.except_osv(_('Error'), msg)
334#end changes361#end changes
335 self.write(cr, uid, [st.id], {362 self.write(cr, uid, [st.id],
336 'name': st_number,363 {'name': st_number,
337 'balance_end_real': st.balance_end364 'balance_end_real': st.balance_end},
338 }, context=context)365 context=context)
339 self.message_post(cr, uid, [st.id], body=_('Statement %s confirmed, journal items were created.') % (st_number,), context=context)366 body = _('Statement %s confirmed, journal items were created.') % st_number
367 self.message_post(cr, uid, [st.id],
368 body,
369 context=context)
340 return self.write(cr, uid, ids, {'state': 'confirm'}, context=context)370 return self.write(cr, uid, ids, {'state': 'confirm'}, context=context)
341371
342 def get_account_for_counterpart(372 def get_account_for_counterpart(self, cr, uid, amount, account_receivable, account_payable):
343 self, cr, uid, amount, account_receivable, account_payable):373 """For backward compatibility."""
374 account_id, type = self.get_account_and_type_for_counterpart(cr, uid, amount,
375 account_receivable,
376 account_payable)
377 return account_id
378
379 def _compute_type_from_partner_profile(self, cr, uid, partner_id,
380 default_type, context=None):
381 """Compute the statement line type
382 from partner profile (customer, supplier)"""
383 obj_partner = self.pool.get('res.partner')
384 part = obj_partner.browse(cr, uid, partner_id, context=context)
385 if part.supplier == part.customer:
386 return default_type
387 if part.supplier:
388 return 'supplier'
389 else:
390 return 'customer'
391
392 def _compute_type_from_amount(self, cr, uid, amount):
393 """Compute the statement type based on amount"""
394 if amount in (None, False):
395 return 'general'
396 if amount < 0:
397 return 'supplier'
398 return 'customer'
399
400 def get_type_for_counterpart(self, cr, uid, amount, partner_id=False):
401 """Give the amount and receive the type to use for the line.
402 The rules are:
403 - If the customer checkbox is checked on the found partner, type customer
404 - If the supplier checkbox is checked on the found partner, typewill be supplier
405 - If both checkbox are checked or none of them, it'll be based on the amount :
406 If amount is positif the type customer,
407 If amount is negativ, the type supplier
408 :param float: amount of the line
409 :param int/long: partner_id the partner id
410 :return: type as string: the default type to use: 'customer' or 'supplier'.
411 """
412 s_line_type = self._compute_type_from_amount(cr, uid, amount)
413 if partner_id:
414 s_line_type = self._compute_type_from_partner_profile(cr, uid,
415 partner_id, s_line_type)
416 return s_line_type
417
418 def get_account_and_type_for_counterpart(self, cr, uid, amount, account_receivable,
419 account_payable, partner_id=False):
344 """420 """
345 Give the amount, payable and receivable account (that can be found using421 Give the amount, payable and receivable account (that can be found using
346 get_default_pay_receiv_accounts method) and receive the one to use. This method422 get_default_pay_receiv_accounts method) and receive the one to use. This method
347 should be use when there is no other way to know which one to take.423 should be use when there is no other way to know which one to take.
424 The rules are:
425 - If the customer checkbox is checked on the found partner, type and account will be customer and receivable
426 - If the supplier checkbox is checked on the found partner, type and account will be supplier and payable
427 - If both checkbox are checked or none of them, it'll be based on the amount :
428 If amount is positive, the type and account will be customer and receivable,
429 If amount is negative, the type and account will be supplier and payable
430 Note that we return the payable or receivable account from agrs and not from the optional partner_id
431 given!
348432
349 :param float: amount of the line433 :param float: amount of the line
350 :param int/long: account_receivable the receivable account434 :param int/long: account_receivable the receivable account
351 :param int/long: account_payable the payable account435 :param int/long: account_payable the payable account
352 :return: int/long :the default account to be used by statement line as the counterpart436 :param int/long: partner_id the partner id
353 of the journal account depending on the amount.437 :return: dict with [account_id as int/long,type as string]: the default account to be used by
438 statement line as the counterpart of the journal account depending on the amount and the type
439 as 'customer' or 'supplier'.
354 """440 """
355 account_id = False441 account_id = False
356 if amount >= 0:442 ltype = self.get_type_for_counterpart(cr, uid, amount, partner_id=partner_id)
443 if ltype == 'supplier':
444 account_id = account_payable
445 else:
357 account_id = account_receivable446 account_id = account_receivable
358 else:
359 account_id = account_payable
360 if not account_id:447 if not account_id:
361 raise osv.except_osv(448 raise osv.except_osv(
362 _('Can not determine account'),449 _('Can not determine account'),
363 _('Please ensure that minimal properties are set')450 _('Please ensure that minimal properties are set')
364 )451 )
365 return account_id452 return [account_id, ltype]
366453
367 def get_default_pay_receiv_accounts(self, cr, uid, context=None):454 def get_default_pay_receiv_accounts(self, cr, uid, context=None):
368 """455 """
@@ -386,14 +473,11 @@
386 ('model', '=', 'res.partner')],473 ('model', '=', 'res.partner')],
387 context=context474 context=context
388 )475 )
389 property_ids = property_obj.search(476 property_ids = property_obj.search(cr,
390 cr,477 uid,
391 uid,478 [('fields_id', 'in', model_fields_ids),
392 [('fields_id', 'in', model_fields_ids),479 ('res_id', '=', False)],
393 ('res_id', '=', False),480 context=context)
394 ],
395 context=context
396 )
397481
398 for erp_property in property_obj.browse(482 for erp_property in property_obj.browse(
399 cr, uid, property_ids, context=context):483 cr, uid, property_ids, context=context):
@@ -433,13 +517,10 @@
433 journal_id = import_config.journal_id.id517 journal_id = import_config.journal_id.id
434 account_id = import_config.journal_id.default_debit_account_id.id518 account_id = import_config.journal_id.default_debit_account_id.id
435 credit_partner_id = import_config.partner_id and import_config.partner_id.id or False519 credit_partner_id = import_config.partner_id and import_config.partner_id.id or False
436 return {'value':520 return {'value': {'journal_id': journal_id,
437 {'journal_id': journal_id,521 'account_id': account_id,
438 'account_id': account_id,522 'balance_check': import_config.balance_check,
439 'balance_check': import_config.balance_check,523 'credit_partner_id': credit_partner_id}}
440 'credit_partner_id': credit_partner_id,
441 }
442 }
443524
444525
445class AccountBankSatementLine(Model):526class AccountBankSatementLine(Model):
@@ -472,18 +553,24 @@
472 'account_id': _get_default_account,553 'account_id': _get_default_account,
473 }554 }
474555
475 def get_values_for_line(self, cr, uid, profile_id=False, partner_id=False, line_type=False, amount=False, context=None):556 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):
476 """557 """
477 Return the account_id to be used in the line of a bank statement. It'll base the result as follow:558 Return the account_id to be used in the line of a bank statement. It'll base the result as follow:
478 - If a receivable_account_id is set in the profile, return this value and type = general559 - If a receivable_account_id is set in the profile, return this value and type = general
479 - Elif line_type is given, take the partner receivable/payable property (payable if type= supplier, receivable560 # TODO
561 - Elif how_get_type_account is set to force_supplier or force_customer, will take respectively payable and type=supplier,
562 receivable and type=customer otherwise
563 # END TODO
564 - Elif line_type is given, take the partner receivable/payable property (payable if type=supplier, receivable
480 otherwise)565 otherwise)
481 - Elif amount is given, take the partner receivable/payable property (receivable if amount >= 0.0,566 - Elif amount is given:
482 payable otherwise). In that case, we also fullfill the type (receivable = customer, payable = supplier)567 - If the customer checkbox is checked on the found partner, type and account will be customer and receivable
483 so it is easier for the accountant to know why the receivable/payable has been chosen568 - If the supplier checkbox is checked on the found partner, type and account will be supplier and payable
569 - If both checkbox are checked or none of them, it'll be based on the amount :
570 If amount is positive, the type and account will be customer and receivable,
571 If amount is negative, the type and account will be supplier an payable
484 - Then, if no partner are given we look and take the property from the company so we always give a value572 - Then, if no partner are given we look and take the property from the company so we always give a value
485 for account_id. Note that in that case, we return the receivable one.573 for account_id. Note that in that case, we return the receivable one.
486
487 :param int/long profile_id of the related bank statement574 :param int/long profile_id of the related bank statement
488 :param int/long partner_id of the line575 :param int/long partner_id of the line
489 :param char line_type: a value from: 'general', 'supplier', 'customer'576 :param char line_type: a value from: 'general', 'supplier', 'customer'
@@ -499,79 +586,77 @@
499 res = {}586 res = {}
500 obj_partner = self.pool.get('res.partner')587 obj_partner = self.pool.get('res.partner')
501 obj_stat = self.pool.get('account.bank.statement')588 obj_stat = self.pool.get('account.bank.statement')
502 line_type = receiv_account = pay_account = account_id = False589 receiv_account = pay_account = account_id = False
503 # If profile has a receivable_account_id, we return it in any case590 # If profile has a receivable_account_id, we return it in any case
504 if profile_id:591 if master_account_id:
592 res['account_id'] = master_account_id
593 # We return general as default instead of get_type_for_counterpart
594 # for perfomance reasons as line_type is not a meaningfull value
595 # as account is forced
596 res['type'] = line_type if line_type else 'general'
597 return res
598 # To optimize we consider passing false means there is no account
599 # on profile
600 if profile_id and master_account_id is None:
505 profile = self.pool.get("account.statement.profile").browse(601 profile = self.pool.get("account.statement.profile").browse(
506 cr, uid, profile_id, context=context)602 cr, uid, profile_id, context=context)
507 if profile.receivable_account_id:603 if profile.receivable_account_id:
508 account_id = profile.receivable_account_id.id604 res['account_id'] = profile.receivable_account_id.id
509 line_type = 'general'605 # We return general as default instead of get_type_for_counterpart
606 # for perfomance reasons as line_type is not a meaningfull value
607 # as account is forced
608 res['type'] = line_type if line_type else 'general'
510 return res609 return res
511 # If partner -> take from him610 # If no account is available on profile you have to do the lookup
611 # This can be quite a performance killer as we read ir.properity fields
512 if partner_id:612 if partner_id:
513 part = obj_partner.browse(cr, uid, partner_id, context=context)613 part = obj_partner.browse(cr, uid, partner_id, context=context)
514 pay_account = part.property_account_payable.id614 pay_account = part.property_account_payable.id
515 receiv_account = part.property_account_receivable.id615 receiv_account = part.property_account_receivable.id
516 # If no value, look on the default company property616 # If no value, look on the default company property
517 if not pay_account or not receiv_account:617 if not pay_account or not receiv_account:
518 receiv_account, pay_account = obj_stat.get_default_pay_receiv_accounts(618 receiv_account, pay_account = obj_stat.get_default_pay_receiv_accounts(cr, uid, context=None)
519 cr, uid, context=None)619 account_id, comp_line_type = obj_stat.get_account_and_type_for_counterpart(cr, uid, amount,
520 # Now we have both pay and receive account, choose the one to use620 receiv_account, pay_account,
521 # based on line_type first, then amount, otherwise take receivable one.621 partner_id=partner_id)
522 if line_type is not False:
523 if line_type == 'supplier':
524 account_id = pay_account
525 elif amount is not False:
526 if amount >= 0:
527 account_id = receiv_account
528 line_type = 'customer'
529 else:
530 account_id = pay_account
531 line_type = 'supplier'
532 res['account_id'] = account_id if account_id else receiv_account622 res['account_id'] = account_id if account_id else receiv_account
533 res['type'] = line_type623 res['type'] = line_type if line_type else comp_line_type
534 return res624 return res
535625
536 def onchange_partner_id(self, cr, uid, ids, partner_id, profile_id=None, context=None):626 def onchange_partner_id(self, cr, uid, ids, partner_id, profile_id=None, context=None):
537 """627 """
538 Override of the basic method as we need to pass the profile_id in the on_change_type628 Override of the basic method as we need to pass the profile_id in the on_change_type
539 call.629 call.
630 Moreover, we now call the get_account_and_type_for_counterpart method now to get the
631 type to use.
540 """632 """
541 obj_partner = self.pool.get('res.partner')633 obj_stat = self.pool.get('account.bank.statement')
542 if not partner_id:634 if not partner_id:
543 return {}635 return {}
544 part = obj_partner.browse(cr, uid, partner_id, context=context)636 line_type = obj_stat.get_type_for_counterpart(cr, uid, 0.0, partner_id=partner_id)
545 if not part.supplier and not part.customer:637 res_type = self.onchange_type(cr, uid, ids, partner_id, line_type, profile_id, context=context)
546 type = 'general'
547 elif part.supplier and part.customer:
548 type = 'general'
549 else:
550 if part.supplier == True:
551 type = 'supplier'
552 if part.customer == True:
553 type = 'customer'
554 res_type = self.onchange_type(cr, uid, ids, partner_id, type, profile_id, context=context) # Chg
555 if res_type['value'] and res_type['value'].get('account_id', False):638 if res_type['value'] and res_type['value'].get('account_id', False):
556 return {'value': {'type': type,639 return {'value': {'type': line_type,
557 'account_id': res_type['value']['account_id'],640 'account_id': res_type['value']['account_id'],
558 'voucher_id': False}}641 'voucher_id': False}}
559 return {'value': {'type': type}}642 return {'value': {'type': line_type}}
560643
561 def onchange_type(self, cr, uid, line_id, partner_id, type, profile_id, context=None):644 def onchange_type(self, cr, uid, line_id, partner_id, line_type, profile_id, context=None):
562 """645 """
563 Keep the same features as in standard and call super. If an account is returned,646 Keep the same features as in standard and call super. If an account is returned,
564 call the method to compute line values.647 call the method to compute line values.
565 """648 """
566 res = super(AccountBankSatementLine, self).onchange_type(649 res = super(AccountBankSatementLine, self).onchange_type(cr, uid,
567 cr, uid, line_id, partner_id, type, context=context)650 line_id,
651 partner_id,
652 line_type,
653 context=context)
568 if 'account_id' in res['value']:654 if 'account_id' in res['value']:
569 result = self.get_values_for_line(655 result = self.get_values_for_line(cr, uid,
570 cr, uid,656 profile_id=profile_id,
571 profile_id=profile_id,657 partner_id=partner_id,
572 partner_id=partner_id,658 line_type=line_type,
573 line_type=type,659 context=context)
574 context=context)
575 if result:660 if result:
576 res['value'].update({'account_id': result['account_id']})661 res['value'].update({'account_id': result['account_id']})
577 return res662 return res
578663
=== modified file 'account_statement_transactionid_completion/statement.py'
--- account_statement_transactionid_completion/statement.py 2012-12-20 13:37:01 +0000
+++ account_statement_transactionid_completion/statement.py 2013-04-25 11:52:27 +0000
@@ -32,7 +32,7 @@
3232
33 def _get_functions(self, cr, uid, context=None):33 def _get_functions(self, cr, uid, context=None):
34 res = super(AccountStatementCompletionRule, self)._get_functions(34 res = super(AccountStatementCompletionRule, self)._get_functions(
35 cr, uid, context=context)35 cr, uid, context=context)
36 res.append(('get_from_transaction_id_and_so',36 res.append(('get_from_transaction_id_and_so',
37 'From line reference (based on SO transaction ID)'))37 'From line reference (based on SO transaction ID)'))
38 return res38 return res
@@ -41,12 +41,12 @@
41 'function_to_call': fields.selection(_get_functions, 'Method'),41 'function_to_call': fields.selection(_get_functions, 'Method'),
42 }42 }
4343
44 def get_from_transaction_id_and_so(self, cr, uid, line_id, context=None):44 def get_from_transaction_id_and_so(self, cr, uid, st_line, context=None):
45 """45 """
46 Match the partner based on the transaction ID field of the SO.46 Match the partner based on the transaction ID field of the SO.
47 Then, call the generic st_line method to complete other values.47 Then, call the generic st_line method to complete other values.
48 In that case, we always fullfill the reference of the line with the SO name.48 In that case, we always fullfill the reference of the line with the SO name.
49 :param int/long line_id: ID of the concerned account.bank.statement.line49 :param dict st_line: read of the concerned account.bank.statement.line
50 :return:50 :return:
51 A dict of value that can be passed directly to the write method of51 A dict of value that can be passed directly to the write method of
52 the statement line or {}52 the statement line or {}
@@ -55,33 +55,28 @@
55 ...}55 ...}
56 """56 """
57 st_obj = self.pool.get('account.bank.statement.line')57 st_obj = self.pool.get('account.bank.statement.line')
58 st_line = st_obj.browse(cr, uid, line_id, context=context)
59 res = {}58 res = {}
60 if st_line:59 so_obj = self.pool.get('sale.order')
61 so_obj = self.pool.get('sale.order')60 so_id = so_obj.search(cr,
62 so_id = so_obj.search(61 uid,
63 cr,62 [('transaction_id', '=', st_line['transaction_id'])],
64 uid,63 context=context)
65 [('transaction_id', '=', st_line.transaction_id)],64 if len(so_id) > 1:
66 context=context)65 raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more than '
67 if so_id and len(so_id) == 1:66 'one partner.') % (st_line['name'], st_line['ref']))
68 so = so_obj.browse(cr, uid, so_id[0], context=context)67 if len(so_id) == 1:
69 res['partner_id'] = so.partner_id.id68 so = so_obj.browse(cr, uid, so_id[0], context=context)
70 res['ref'] = so.name69 res['partner_id'] = so.partner_id.id
71 elif so_id and len(so_id) > 1:70 res['ref'] = so.name
72 raise ErrorTooManyPartner(71 st_vals = st_obj.get_values_for_line(cr,
73 _('Line named "%s" (Ref:%s) was matched by more than '72 uid,
74 'one partner.') % (st_line.name, st_line.ref))73 profile_id=st_line['profile_id'],
75 if so_id:74 master_account_id=st_line['master_account_id'],
76 st_vals = st_obj.get_values_for_line(75 partner_id=res.get('partner_id', False),
77 cr,76 line_type=st_line['type'],
78 uid,77 amount=st_line['amount'] if st_line['amount'] else 0.0,
79 profile_id=st_line.statement_id.profile_id.id,78 context=context)
80 partner_id=res.get('partner_id', False),79 res.update(st_vals)
81 line_type=st_line.type,
82 amount=st_line.amount,
83 context=context)
84 res.update(st_vals)
85 return res80 return res
8681
8782
8883
=== modified file 'account_statement_transactionid_import/parser/transactionid_file_parser.py'
--- account_statement_transactionid_import/parser/transactionid_file_parser.py 2013-02-26 08:19:44 +0000
+++ account_statement_transactionid_import/parser/transactionid_file_parser.py 2013-04-25 11:52:27 +0000
@@ -17,8 +17,6 @@
17# along with this program. If not, see <http://www.gnu.org/licenses/>.17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#18#
19##############################################################################19##############################################################################
20
21from openerp.tools.translate import _
22import datetime20import datetime
23from account_statement_base_import.parser.file_parser import FileParser21from account_statement_base_import.parser.file_parser import FileParser
2422
@@ -30,13 +28,11 @@
30 """28 """
3129
32 def __init__(self, parse_name, ftype='csv'):30 def __init__(self, parse_name, ftype='csv'):
33 conversion_dict = {31 conversion_dict = {'transaction_id': unicode,
34 'transaction_id': unicode,32 'label': unicode,
35 'label': unicode,33 'date': datetime.datetime,
36 'date': datetime.datetime,34 'amount': float,
37 'amount': float,35 'commission_amount': float}
38 'commission_amount': float
39 }
40 # Order of cols does not matter but first row of the file has to be header36 # Order of cols does not matter but first row of the file has to be header
41 keys_to_validate = ['transaction_id', 'label', 'date', 'amount', 'commission_amount']37 keys_to_validate = ['transaction_id', 'label', 'date', 'amount', 'commission_amount']
42 super(TransactionIDFileParser, self).__init__(parse_name, keys_to_validate=keys_to_validate,38 super(TransactionIDFileParser, self).__init__(parse_name, keys_to_validate=keys_to_validate,
@@ -70,15 +66,13 @@
70 In this generic parser, the commission is given for every line, so we store it66 In this generic parser, the commission is given for every line, so we store it
71 for each one.67 for each one.
72 """68 """
73 return {69 return {'name': line.get('label', line.get('ref', '/')),
74 'name': line.get('label', line.get('ref', '/')),70 'date': line.get('date', datetime.datetime.now().date()),
75 'date': line.get('date', datetime.datetime.now().date()),71 'amount': line.get('amount', 0.0),
76 'amount': line.get('amount', 0.0),72 'ref': line.get('transaction_id', '/'),
77 'ref': line.get('transaction_id', '/'),73 'label': line.get('label', ''),
78 'label': line.get('label', ''),74 'transaction_id': line.get('transaction_id', '/'),
79 'transaction_id': line.get('transaction_id', '/'),75 'commission_amount': line.get('commission_amount', 0.0)}
80 'commission_amount': line.get('commission_amount', 0.0),
81 }
8276
83 def _post(self, *args, **kwargs):77 def _post(self, *args, **kwargs):
84 """78 """

Subscribers

People subscribed via source and target branches