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

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

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

Commit message

As we saw that we have a difference in bank statement line type and account finding using manual entry and importation/completion method, we make this refactor implementing a method to determine the type following this rule:
 - If the customer checkbox is checked on the found partner, type and account will be customer and receivable
 - If the supplier checkbox is checked on the found partner, type and account will be supplier and payable
 - If both checkbox are checked or none of them, it'll be based on the amount :
    If amount is positif the type and account will be customer and receivable,
    If amount is negativ, the type and account will be supplier and payable

Description of the change

As we saw that we have a difference in bank statement line type and account finding using manual entry and importation/completion method, we make this refactor implementing a method to determine the type following this rule:
 - If the customer checkbox is checked on the found partner, type and account will be customer and receivable
 - If the supplier checkbox is checked on the found partner, type and account will be supplier and payable
 - If both checkbox are checked or none of them, it'll be based on the amount :
    If amount is positif the type and account will be customer and receivable,
    If amount is negativ, the type and account will be supplier and payable

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

[PEP8]

92. By Nicolas Bessi - Camptocamp

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

93. By Nicolas Bessi - Camptocamp

[RM] dead comment

94. By Nicolas Bessi - Camptocamp

[FIX] pep8 + invalid syntax + undefine value.

95. By Nicolas Bessi - Camptocamp

[FIX] coding declaration

96. By Nicolas Bessi - Camptocamp

[PEP8]

97. By Nicolas Bessi - Camptocamp

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

98. By Nicolas Bessi - Camptocamp

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

99. By Nicolas Bessi - Camptocamp

[IMP] moving insert function to more appropriate class

100. By Nicolas Bessi - Camptocamp

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

101. By Nicolas Bessi - Camptocamp

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

102. By Nicolas Bessi - Camptocamp

[FIX] poor satement write function performance

103. By Nicolas Bessi - Camptocamp

[TYPO]

104. By Nicolas Bessi - Camptocamp

[TYPO]

105. By Nicolas Bessi - Camptocamp

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

106. By Nicolas Bessi - Camptocamp

[RM] debug statement

107. By Nicolas Bessi - Camptocamp

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

108. By Nicolas Bessi - Camptocamp

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

109. By Nicolas Bessi - Camptocamp

[FIX] typo supplier instead of customer

110. By Nicolas Bessi - Camptocamp

[IMP] ref are striped

111. By Nicolas Bessi - Camptocamp

[REFACTOR] to improve performance by diminishing reads

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

Hello,

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

review: Needs Fixing
112. By Nicolas Bessi - Camptocamp

[FIX] assignement

113. By Nicolas Bessi - Camptocamp

[FIX] error log + type

114. By Nicolas Bessi - Camptocamp

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

115. By Nicolas Bessi - Camptocamp

[TYPO]

116. By Nicolas Bessi - Camptocamp

[FIX] transaction_id completion to support optimization

117. By Nicolas Bessi - Camptocamp

[FIX] transaction id import styling

118. By Nicolas Bessi - Camptocamp

[FIX] incoherence in type lookup behavior

119. By Nicolas Bessi - Camptocamp

[IMP] completion messages

120. By Nicolas Bessi - Camptocamp

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

121. By Nicolas Bessi - Camptocamp

[FIX] dead if statement

122. By Nicolas Bessi - Camptocamp

[FIX] dead error management code

123. By Nicolas Bessi - Camptocamp

[IMP] return value

124. By Nicolas Bessi - Camptocamp

[IMP] log code cleanup

125. By Nicolas Bessi - Camptocamp

[FIX] comment and forgotten print statement

126. By Nicolas Bessi - Camptocamp

[IMP] no loop when computing sequence

127. By Nicolas Bessi - Camptocamp

[TYPO]

128. By Nicolas Bessi - Camptocamp

[FIX] partner name lookup now respect security rules

129. By Nicolas Bessi - Camptocamp

[TYPO]

130. By Nicolas Bessi - Camptocamp

[FIX] po file

131. By Nicolas Bessi - Camptocamp

[IMP] message logging

132. By Alexandre Fayolle - camptocamp

[IMP] fix a few docstrings

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

133. By Nicolas Bessi - Camptocamp

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

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== 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-24 09:32:28 +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-24 09:32:28 +0000
@@ -18,15 +18,20 @@
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__)
3035
31class ErrorTooManyPartner(Exception):36class ErrorTooManyPartner(Exception):
32 """37 """
@@ -40,7 +45,7 @@
40 return repr(self.value)45 return repr(self.value)
4146
4247
43class AccountStatementProfil(Model):48class AccountStatementProfil(orm.Model):
44 """49 """
45 Extend the class to add rules per profile that will match at least the partner,50 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.51 but it could also be used to match other values as well.
@@ -61,12 +66,21 @@
61 rel='as_rul_st_prof_rel'),66 rel='as_rul_st_prof_rel'),
62 }67 }
6368
64 def find_values_from_rules(self, cr, uid, id, line_id, context=None):69 def _get_callable(self, cr, uid, profile, context=None):
70 if isinstance(profile, (int, long)):
71 prof = self.browse(cr, uid, profile, context=context)
72 else:
73 prof = profile
74 # We need to respect the sequence order
75 sorted_array = sorted(prof.rule_ids, key=attrgetter('sequence'))
76 return tuple((x.function_to_call for x in sorted_array))
77
78 def _find_values_from_rules(self, cr, uid, calls, line, context=None):
65 """79 """
66 This method will execute all related rules, in their sequence order,80 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.81 to retrieve all the values returned by the first rules that will match.
6882
69 :param int/long line_id: id of the concerned account.bank.statement.line83 :param int/long st_line: read of the concerned account.bank.statement.line
70 :return:84 :return:
71 A dict of value that can be passed directly to the write method of85 A dict of value that can be passed directly to the write method of
72 the statement line or {}86 the statement line or {}
@@ -77,20 +91,20 @@
77 """91 """
78 if context is None:92 if context is None:
79 context = {}93 context = {}
80 res = {}94 if not calls:
95 calls = self._get_callable(cr, uid, id, context=context)
81 rule_obj = self.pool.get('account.statement.completion.rule')96 rule_obj = self.pool.get('account.statement.completion.rule')
82 profile = self.browse(cr, uid, id, context=context)97
83 # We need to respect the sequence order98 for call in calls:
84 sorted_array = sorted(profile.rule_ids, key=attrgetter('sequence'))99 method_to_call = getattr(rule_obj, call)
85 for rule in sorted_array:100 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:101 if result:
102 result['already_completed'] = True
89 return result103 return result
90 return res104 return None
91105
92106
93class AccountStatementCompletionRule(Model):107class AccountStatementCompletionRule(orm.Model):
94 """108 """
95 This will represent all the completion method that we can have to109 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 module110 fullfill the bank statement lines. You'll be able to extend them in you own module
@@ -109,12 +123,11 @@
109 List of available methods for rules. Override this to add you own.123 List of available methods for rules. Override this to add you own.
110 """124 """
111 return [125 return [
112 ('get_from_ref_and_invoice', 'From line reference (based on invoice number)'),126 ('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)'),127 ('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)'),128 ('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)'),129 ('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)'),130 ('get_from_label_and_partner_name', 'From line label (based on partner name)')]
117 ]
118131
119 _columns = {132 _columns = {
120 'sequence': fields.integer('Sequence', help="Lower means parsed first."),133 'sequence': fields.integer('Sequence', help="Lower means parsed first."),
@@ -126,13 +139,56 @@
126 'function_to_call': fields.selection(_get_functions, 'Method'),139 'function_to_call': fields.selection(_get_functions, 'Method'),
127 }140 }
128141
129 def get_from_ref_and_supplier_invoice(self, cr, uid, line_id, context=None):142 def _find_invoice(self, cr, uid, st_line, inv_type, context=None):
143 """Find invoice related to statement line"""
144 inv_obj = self.pool.get('account.invoice')
145 if inv_type == 'supplier':
146 type_domain = ('in_invoice', 'in_refund')
147 number_field = 'supplier_invoice_number'
148 elif inv_type == 'customer':
149 type_domain = ('out_invoice', 'out_refund')
150 number_field = 'number'
151 else:
152 raise osv.except_osv(_('System error'),
153 _('Invalid invoice type for completion: %') % inv_type)
154
155 inv_id = inv_obj.search(cr, uid,
156 [(number_field, '=', st_line['ref'].strip()),
157 ('type', 'in', type_domain)],
158 context=context)
159 if inv_id:
160 if len(inv_id) == 1:
161 inv = inv_obj.browse(cr, uid, inv_id[0], context=context)
162 else:
163 raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more '
164 'than one partner.') % (st_line['name'], st_line['ref']))
165 return inv
166 return False
167
168 def _from_invoice(self, cr, uid, line, inv_type, context):
169 """Populate statement line values"""
170 if not inv_type in ('supplier', 'customer'):
171 raise osv.except_osv(_('System error'),
172 _('Invalid invoice type for completion: %') % inv_type)
173 res = {}
174 inv = self._find_invoice(cr, uid, line, inv_type, context=context)
175 if inv:
176 res = {'partner_id': inv.partner_id.id,
177 'account_id': inv.account_id.id,
178 'type': inv_type}
179 override_acc = line['master_account_id']
180 if override_acc:
181 res['account_id'] = override_acc
182 return res
183
184 # Should be private but data are initialised with no update XML
185 def get_from_ref_and_supplier_invoice(self, cr, uid, line, context=None):
130 """186 """
131 Match the partner based on the invoice supplier invoice number and the reference of the statement187 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.188 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.189 If more than one partner matched, raise the ErrorTooManyPartner error.
134190
135 :param int/long line_id: id of the concerned account.bank.statement.line191 :param int/long st_line: read of the concerned account.bank.statement.line
136 :return:192 :return:
137 A dict of value that can be passed directly to the write method of193 A dict of value that can be passed directly to the write method of
138 the statement line or {}194 the statement line or {}
@@ -141,40 +197,16 @@
141197
142 ...}198 ...}
143 """199 """
144 st_obj = self.pool['account.bank.statement.line']200 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
170201
171 def get_from_ref_and_invoice(self, cr, uid, line_id, context=None):202 # Should be private but data are initialised with no update XML
203 def get_from_ref_and_invoice(self, cr, uid, line, context=None):
172 """204 """
173 Match the partner based on the invoice number and the reference of the statement205 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.206 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.207 If more than one partner matched, raise the ErrorTooManyPartner error.
176208
177 :param int/long line_id: id of the concerned account.bank.statement.line209 :param int/long st_line: read of the concerned account.bank.statement.line
178 :return:210 :return:
179 A dict of value that can be passed directly to the write method of211 A dict of value that can be passed directly to the write method of
180 the statement line or {}212 the statement line or {}
@@ -182,39 +214,16 @@
182 'account_id' : value,214 'account_id' : value,
183 ...}215 ...}
184 """216 """
185 st_obj = self.pool.get('account.bank.statement.line')217 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
210218
211 def get_from_ref_and_so(self, cr, uid, line_id, context=None):219 # Should be private but data are initialised with no update XML
220 def get_from_ref_and_so(self, cr, uid, st_line, context=None):
212 """221 """
213 Match the partner based on the SO number and the reference of the statement222 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.223 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.224 If more than one partner matched, raise the ErrorTooManyPartner error.
216225
217 :param int/long line_id: id of the concerned account.bank.statement.line226 :param int/long st_line: read of the concerned account.bank.statement.line
218 :return:227 :return:
219 A dict of value that can be passed directly to the write method of228 A dict of value that can be passed directly to the write method of
220 the statement line or {}229 the statement line or {}
@@ -224,36 +233,34 @@
224 ...}233 ...}
225 """234 """
226 st_obj = self.pool.get('account.bank.statement.line')235 st_obj = self.pool.get('account.bank.statement.line')
227 st_line = st_obj.browse(cr, uid, line_id, context=context)
228 res = {}236 res = {}
229 if st_line:237 if st_line:
230 so_obj = self.pool.get('sale.order')238 so_obj = self.pool.get('sale.order')
231 so_id = so_obj.search(239 so_id = so_obj.search(cr,
232 cr,240 uid,
233 uid,241 [('name', '=', st_line['ref'])],
234 [('name', '=', st_line.ref)],242 context=context)
235 context=context)
236 if so_id:243 if so_id:
237 if so_id and len(so_id) == 1:244 if so_id and len(so_id) == 1:
238 so = so_obj.browse(cr, uid, so_id[0], context=context)245 so = so_obj.browse(cr, uid, so_id[0], context=context)
239 res['partner_id'] = so.partner_id.id246 res['partner_id'] = so.partner_id.id
240 elif so_id and len(so_id) > 1:247 elif so_id and len(so_id) > 1:
241 raise ErrorTooManyPartner(248 raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more '
242 _('Line named "%s" (Ref:%s) was matched by more '249 'than one partner.') %
243 'than one partner.') %250 (st_line['name'], st_line['ref']))
244 (st_line.name, st_line.ref))251 st_vals = st_obj.get_values_for_line(cr,
245 st_vals = st_obj.get_values_for_line(252 uid,
246 cr,253 profile_id=st_line['profile_id'],
247 uid,254 master_account_id=st_line['master_account_id'],
248 profile_id=st_line.statement_id.profile_id.id,255 partner_id=res.get('partner_id', False),
249 partner_id=res.get('partner_id', False),256 line_type=st_line['type'],
250 line_type=st_line.type,257 amount=st_line['amount'] if st_line['amount'] else 0.0,
251 amount=st_line.amount,258 context=context)
252 context=context)
253 res.update(st_vals)259 res.update(st_vals)
254 return res260 return res
255261
256 def get_from_label_and_partner_field(self, cr, uid, line_id, context=None):262 # Should be private but data are initialised with no update XML
263 def get_from_label_and_partner_field(self, cr, uid, st_line, context=None):
257 """264 """
258 Match the partner based on the label field of the statement line265 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.266 and the text defined in the 'bank_statement_label' field of the partner.
@@ -261,7 +268,7 @@
261 get_values_for_line method to complete other values.268 get_values_for_line method to complete other values.
262 If more than one partner matched, raise the ErrorTooManyPartner error.269 If more than one partner matched, raise the ErrorTooManyPartner error.
263270
264 :param int/long line_id: id of the concerned account.bank.statement.line271 :param int/long st_line: read of the concerned account.bank.statement.line
265 :return:272 :return:
266 A dict of value that can be passed directly to the write method of273 A dict of value that can be passed directly to the write method of
267 the statement line or {}274 the statement line or {}
@@ -283,7 +290,7 @@
283 partner_ids = partner_obj.search(cr,290 partner_ids = partner_obj.search(cr,
284 uid,291 uid,
285 [('bank_statement_label', '!=', False)])292 [('bank_statement_label', '!=', False)])
286 line_ids = tuple(x.id for x in context.get('line_ids', []))293 line_ids = context.get('line_ids', [])
287 for partner in partner_obj.browse(cr, uid, partner_ids, context=context):294 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(';'))295 vals = '|'.join(re.escape(x.strip()) for x in partner.bank_statement_label.split(';'))
289 or_regex = ".*%s*." % vals296 or_regex = ".*%s*." % vals
@@ -294,32 +301,32 @@
294 pairs = cr.fetchall()301 pairs = cr.fetchall()
295 for pair in pairs:302 for pair in pairs:
296 context['label_memoizer'][pair[0]].append(partner)303 context['label_memoizer'][pair[0]].append(partner)
297 st_line = st_obj.browse(cr, uid, line_id, context=context)304 if st_line['id'] in context['label_memoizer']:
298 if st_line and st_line.id in context['label_memoizer']:305 found_partner = context['label_memoizer'][st_line['id']]
299 found_partner = context['label_memoizer'][st_line.id]
300 if len(found_partner) > 1:306 if len(found_partner) > 1:
301 raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by '307 raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by '
302 'more than one partner.') %308 'more than one partner.') %
303 (st_line.name, st_line.ref))309 (st_line['name'], st_line['ref']))
304 res['partner_id'] = found_partner[0].id310 res['partner_id'] = found_partner[0].id
305 st_vals = st_obj.get_values_for_line(cr,311 st_vals = st_obj.get_values_for_line(cr,
306 uid,312 uid,
307 profile_id=st_line.statement_id.profile_id.id,313 profile_id=st_line['profile_id'],
314 master_account_id=st_line['master_account_id'],
308 partner_id=found_partner[0].id,315 partner_id=found_partner[0].id,
309 line_type=st_line.type,316 line_type=st_line['type'],
310 amount=st_line.amount,317 amount=st_line['amount'] if st_line['amount'] else 0.0,
311 context=context)318 context=context)
312 res.update(st_vals)319 res.update(st_vals)
313 return res320 return res
314321
315 def get_from_label_and_partner_name(self, cr, uid, line_id, context=None):322 def get_from_label_and_partner_name(self, cr, uid, st_line, context=None):
316 """323 """
317 Match the partner based on the label field of the statement line324 Match the partner based on the label field of the statement line
318 and the name of the partner.325 and the name of the partner.
319 Then, call the generic get_values_for_line method to complete other values.326 Then, call the generic get_values_for_line method to complete other values.
320 If more than one partner matched, raise the ErrorTooManyPartner error.327 If more than one partner matched, raise the ErrorTooManyPartner error.
321328
322 :param int/long line_id: id of the concerned account.bank.statement.line329 :param int/long st_line: read of the concerned account.bank.statement.line
323 :return:330 :return:
324 A dict of value that can be passed directly to the write method of331 A dict of value that can be passed directly to the write method of
325 the statement line or {}332 the statement line or {}
@@ -328,36 +335,33 @@
328335
329 ...}336 ...}
330 """337 """
331 # This Method has not been tested yet !
332 res = {}338 res = {}
333 st_obj = self.pool.get('account.bank.statement.line')339 st_obj = self.pool.get('account.bank.statement.line')
334 st_line = st_obj.browse(cr, uid, line_id, context=context)340 sql = "SELECT id FROM res_partner WHERE name ~* %s"
335 if st_line:341 pattern = ".*%s.*" % re.escape(st_line['name'])
336 sql = "SELECT id FROM res_partner WHERE name ~* %s"342 cr.execute(sql, (pattern,))
337 pattern = ".*%s.*" % re.escape(st_line.label)343 result = cr.fetchall()
338 cr.execute(sql, (pattern,))344 if not result:
339 result = cr.fetchall()345 return res
340 if not result:346 if len(result) > 1:
341 return res347 raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more '
342 if len(result) > 1:348 'than one partner.') %
343 raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more '349 (st_line['name'], st_line['ref']))
344 'than one partner.') %350 res['partner_id'] = result[0][0] if result else False
345 (st_line.name, st_line.ref))351 if res:
346 res['partner_id'] = result[0][0] if result else False352 st_vals = st_obj.get_values_for_line(cr,
347 if res:353 uid,
348 st_vals = st_obj.get_values_for_line(354 profile_id=st_line['porfile_id'],
349 cr,355 master_account_id=profile['master_account_id'],
350 uid,356 partner_id=res['partner_id'],
351 profile_id=st_line.statement_id.profile_id.id,357 line_type=st_line['type'],
352 partner_id=res['partner_id'],358 amount=st_line['amount'] if st_line['amount'] else 0.0,
353 line_type=st_line.type,359 context=context)
354 amount=st_line.amount,360 res.update(st_vals)
355 context=context)
356 res.update(st_vals)
357 return res361 return res
358362
359363
360class AccountStatementLine(Model):364class AccountStatementLine(orm.Model):
361 """365 """
362 Add sparse field on the statement line to allow to store all the366 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 your367 bank infos that are given by a bank/office. You can then add you own in your
@@ -390,7 +394,7 @@
390 'already_completed': False,394 'already_completed': False,
391 }395 }
392396
393 def get_line_values_from_rules(self, cr, uid, ids, context=None):397 def _get_line_values_from_rules(self, cr, uid, line, rules, context=None):
394 """398 """
395 We'll try to find out the values related to the line based on rules setted on399 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.400 the profile.. We will ignore line for which already_completed is ticked.
@@ -401,36 +405,26 @@
401 {117009: {'partner_id': 100997, 'account_id': 489L}}405 {117009: {'partner_id': 100997, 'account_id': 489L}}
402 """406 """
403 profile_obj = self.pool.get('account.statement.profile')407 profile_obj = self.pool.get('account.statement.profile')
404 st_obj = self.pool.get('account.bank.statement.line')
405 res = {}408 res = {}
406 errors_stack = []409 errors_stack = []
407 for line in self.browse(cr, uid, ids, context=context):410 if line.get('already_completed'):
408 if line.already_completed:411 return res
409 continue412 try:
410 try:413 # Ask the rule
411 # Take the default values414 vals = profile_obj._find_values_from_rules(cr, uid, rules, line, context)
412 res[line.id] = st_obj.get_values_for_line(415 if vals:
413 cr,416 vals['id'] = line['id']
414 uid,417 return vals
415 profile_id=line.statement_id.profile_id.id,418 except ErrorTooManyPartner as exc:
416 line_type=line.type,419 msg = "Line ID %s had following error: %s" % (line['id'], exc.value)
417 amount=line.amount,420 errors_stack.append(msg)
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:421 if errors_stack:
428 msg = u"\n".join(errors_stack)422 msg = u"\n".join(errors_stack)
429 raise ErrorTooManyPartner(msg)423 raise ErrorTooManyPartner(msg)
430 return res424 return res
431425
432426
433class AccountBankSatement(Model):427class AccountBankSatement(orm.Model):
434 """428 """
435 We add a basic button and stuff to support the auto-completion429 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.430 of the bank statement once line have been imported or manually fullfill.
@@ -453,24 +447,29 @@
453 :return : True447 :return : True
454 """448 """
455 error_log = ""449 error_log = ""
456 user_name = self.pool.get('res.users').read(450 user_name = self.pool.get('res.users').read(cr, uid, uid,
457 cr, uid, uid, ['name'], context=context)['name']451 ['name'], context=context)['name']
458 log = self.read(452
459 cr, uid, stat_id, ['completion_logs'], context=context)['completion_logs']453 log = self.read(cr, uid, stat_id, ['completion_logs'],
454 context=context)['completion_logs']
455
460 log_line = log and log.split("\n") or []456 log_line = log and log.split("\n") or []
461 completion_date = datetime.datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT)457 completion_date = datetime.datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT)
462 if error_msg:458 if error_msg:
463 error_log = error_msg459 error_log = error_msg
464 log_line[0:0] = [completion_date + ' : '460 log_line[0:0] = [completion_date + ' : '
465 + _("Bank Statement ID %s has %s lines completed by %s") % (stat_id, number_imported, user_name)461 + _("Bank Statement ID %s has %s lines completed by %s") %
466 + "\n" + error_log + "-------------" + "\n"]462 (stat_id, number_imported, user_name)
463 + "\n" + error_log + "-------------" + "\n"]
467 log = "\n".join(log_line)464 log = "\n".join(log_line)
468 self.write(cr, uid, [stat_id], {'completion_logs': log}, context=context)465 self.write(cr, uid, [stat_id], {'completion_logs': log}, context=context)
469 self.message_post(466
470 cr, uid,467 body = (_('Statement ID %s auto-completed for %s lines completed') %
471 [stat_id],468 (stat_id, number_imported)),
472 body=_('Statement ID %s auto-completed for %s lines completed') % (stat_id, number_imported),469 self.message_post(cr, uid,
473 context=context)470 [stat_id],
471 body=body,
472 context=context)
474 return True473 return True
475474
476 def button_auto_completion(self, cr, uid, ids, context=None):475 def button_auto_completion(self, cr, uid, ids, context=None):
@@ -480,29 +479,55 @@
480 """479 """
481 if context is None:480 if context is None:
482 context = {}481 context = {}
483 stat_line_obj = self.pool.get('account.bank.statement.line')482 stat_line_obj = self.pool['account.bank.statement.line']
483 profile_obj = self.pool.get('account.statement.profile')
484 compl_lines = 0484 compl_lines = 0
485 stat_line_obj.check_access_rule(cr, uid, [], 'create')
486 stat_line_obj.check_access_rights(cr, uid, 'create', raise_exception=True)
485 for stat in self.browse(cr, uid, ids, context=context):487 for stat in self.browse(cr, uid, ids, context=context):
486 msg_lines = []488 msg_lines = []
487 ctx = context.copy()489 ctx = context.copy()
488 ctx['line_ids'] = stat.line_ids490 ctx['line_ids'] = tuple((x.id for x in stat.line_ids))
489 for line in stat.line_ids:491 b_profile = stat.profile_id
490 res = {}492 rules = profile_obj._get_callable(cr, uid, b_profile, context=context)
493 profile_id = b_profile.id # Only for perfo even it gains almost nothing
494 master_account_id = b_profile.receivable_account_id
495 master_account_id = master_account_id.id if master_account_id else False
496 res = False
497 for line in stat_line_obj.read(cr, uid, ctx['line_ids']):
491 try:498 try:
492 res = stat_line_obj.get_line_values_from_rules(499 # performance trick
493 cr, uid, [line.id], context=ctx)500 line['master_account_id'] = master_account_id
501 line['profile_id'] = profile_id
502 res = stat_line_obj._get_line_values_from_rules(cr, uid, line,
503 rules, context=ctx)
494 if res:504 if res:
495 compl_lines += 1505 compl_lines += 1
496 except ErrorTooManyPartner, exc:506 except ErrorTooManyPartner, exc:
497 msg_lines.append(repr(exc))507 msg_lines.append(repr(exc))
498 except Exception, exc:508 except Exception, exc:
499 msg_lines.append(repr(exc))509 msg_lines.append(repr(exc))
500 # vals = res and res.keys() or False510 error_type, error_value, trbk = sys.exc_info()
511 st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value)
512 st += ''.join(traceback.format_tb(trbk, 30))
513 print st
514 _logger.error(st)
501 if res:515 if res:
502 vals = res[line.id]516 #stat_line_obj.write(cr, uid, [line.id], vals, context=ctx)
503 vals['already_completed'] = True517 try:
504 stat_line_obj.write(cr, uid, [line.id], vals, context=ctx)518 stat_line_obj._update_line(cr, uid, res, context=context)
519 except Exception as exc:
520 msg_lines.append(repr(exc))
521 error_type, error_value, trbk = sys.exc_info()
522 st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value)
523 st += ''.join(traceback.format_tb(trbk, 30))
524 print st
525 _logger.error(st)
526 # we can commit as it is not needed to be atomic
527 # commiting here adds a nice perfo boost
528 if not compl_lines % 500:
529 cr.commit()
505 msg = u'\n'.join(msg_lines)530 msg = u'\n'.join(msg_lines)
506 self.write_completion_log(cr, uid, stat.id,531 self.write_completion_log(cr, uid, stat.id,
507 msg, compl_lines, context=context)532 msg, compl_lines, context=context)
508 return True533 return True
509534
=== 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-24 09:32:28 +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-24 09:32:28 +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
@@ -121,13 +125,27 @@
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(),
226 ftype),
227 'res_model': 'account.bank.statement',
228 'res_id': statement_id},
229 context=context)
230
204 # If user ask to launch completion at end of import, do it !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,46 @@
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 doing batch write. It is a shame that batch function
288 does not exist"""
289 cols = self._get_available_columns([vals])
290 tmp_vals = (', '.join(['%s = %%(%s)s' % (i, i) for i in cols]))
291 sql = "UPDATE account_bank_statement_line SET %s where id = %%(id)s;" % tmp_vals
292 try:
293 cr.execute(sql, vals)
294 except psycopg2.Error as sql_err:
295 cr.rollback()
296 raise osv.except_osv(_("ORM bypass error"),
297 sql_err.pgerror)
298
231 _columns = {299 _columns = {
232 'commission_amount': fields.sparse(300 'commission_amount': fields.sparse(
233 type='float',301 type='float',
234302
=== 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-24 09:32:28 +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/statement.py'
--- account_statement_ext/statement.py 2013-03-01 14:33:32 +0000
+++ account_statement_ext/statement.py 2013-04-24 09:32:28 +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,27 @@
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 postgres sequence or serial fields.
31 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 for s_id in ids:
35 cr.execute("UPDATE account_bank_statement_line"
36 " SET sequence = account_bank_statement_line.id + 1"
37 " where statement_id = %s", (s_id,))
38 return res
39stat_mod.account_bank_statement.write = fixed_write
40
41
27class AccountStatementProfil(Model):42class AccountStatementProfil(Model):
28 """43 """
29 A Profile will contain all infos related to the type of44 A Profile will contain all infos related to the type of
@@ -44,38 +59,45 @@
44 "commission move (and optionaly on the counterpart "59 "commission move (and optionaly on the counterpart "
45 "of the intermediate/banking move if you tick the "60 "of the intermediate/banking move if you tick the "
46 "corresponding checkbox)."),61 "corresponding checkbox)."),
62
47 'journal_id': fields.many2one(63 'journal_id': fields.many2one(
48 'account.journal',64 'account.journal',
49 'Financial journal to use for transaction',65 'Financial journal to use for transaction',
50 required=True),66 required=True),
67
51 'commission_account_id': fields.many2one(68 'commission_account_id': fields.many2one(
52 'account.account',69 'account.account',
53 'Commission account',70 'Commission account',
54 required=True),71 required=True),
72
55 'commission_analytic_id': fields.many2one(73 'commission_analytic_id': fields.many2one(
56 'account.analytic.account',74 'account.analytic.account',
57 'Commission analytic account'),75 'Commission analytic account'),
76
58 'receivable_account_id': fields.many2one(77 'receivable_account_id': fields.many2one(
59 'account.account',78 'account.account',
60 'Force Receivable/Payable Account',79 'Force Receivable/Payable Account',
61 help="Choose a receivable account to force the default "80 help="Choose a receivable account to force the default "
62 "debit/credit account (eg. an intermediat bank account "81 "debit/credit account (eg. an intermediat bank account "
63 "instead of default debitors)."),82 "instead of default debitors)."),
83
64 'force_partner_on_bank': fields.boolean(84 'force_partner_on_bank': fields.boolean(
65 'Force partner on bank move',85 'Force partner on bank move',
66 help="Tick that box if you want to use the credit "86 help="Tick that box if you want to use the credit "
67 "institute partner in the counterpart of the "87 "institute partner in the counterpart of the "
68 "intermediate/banking move."),88 "intermediate/banking move."),
89
69 'balance_check': fields.boolean(90 'balance_check': fields.boolean(
70 'Balance check',91 'Balance check',
71 help="Tick that box if you want OpenERP to control "92 help="Tick that box if you want OpenERP to control "
72 "the start/end balance before confirming a bank statement. "93 "the start/end balance before confirming a bank statement. "
73 "If don't ticked, no balance control will be done."94 "If don't ticked, no balance control will be done."),
74 ),95
75 'bank_statement_prefix': fields.char(96 'bank_statement_prefix': fields.char('Bank Statement Prefix', size=32),
76 'Bank Statement Prefix', size=32),97
77 'bank_statement_ids': fields.one2many(98 'bank_statement_ids': fields.one2many('account.bank.statement',
78 'account.bank.statement', 'profile_id', 'Bank Statement Imported'),99 'profile_id',
100 'Bank Statement Imported'),
79 'company_id': fields.many2one('res.company', 'Company'),101 'company_id': fields.many2one('res.company', 'Company'),
80 }102 }
81103
@@ -166,11 +188,11 @@
166 """188 """
167 for statement in self.browse(cr, uid, ids, context=context):189 for statement in self.browse(cr, uid, ids, context=context):
168 if (statement.period_id and190 if (statement.period_id and
169 statement.company_id.id != statement.period_id.company_id.id):191 statement.company_id.id != statement.period_id.company_id.id):
170 return False192 return False
171 for line in statement.line_ids:193 for line in statement.line_ids:
172 if (line.period_id and194 if (line.period_id and
173 statement.company_id.id != line.period_id.company_id.id):195 statement.company_id.id != line.period_id.company_id.id):
174 return False196 return False
175 return True197 return True
176198
@@ -244,8 +266,10 @@
244 create the move from.266 create the move from.
245 :return: int/long of the res.partner to use as counterpart267 :return: int/long of the res.partner to use as counterpart
246 """268 """
247 bank_partner_id = super(AccountBankSatement, self).\269 bank_partner_id = super(AccountBankSatement, self)._get_counter_part_partner(cr,
248 _get_counter_part_partner(cr, uid, st_line, context=context)270 uid,
271 st_line,
272 context=context)
249 # get the right partner according to the chosen profil273 # get the right partner according to the chosen profil
250 if st_line.statement_id.profile_id.force_partner_on_bank:274 if st_line.statement_id.profile_id.force_partner_on_bank:
251 bank_partner_id = st_line.statement_id.profile_id.partner_id.id275 bank_partner_id = st_line.statement_id.profile_id.partner_id.id
@@ -298,7 +322,7 @@
298 if (not st.journal_id.default_credit_account_id) \322 if (not st.journal_id.default_credit_account_id) \
299 or (not st.journal_id.default_debit_account_id):323 or (not st.journal_id.default_debit_account_id):
300 raise osv.except_osv(_('Configuration Error !'),324 raise osv.except_osv(_('Configuration Error !'),
301 _('Please verify that an account is defined in the journal.'))325 _('Please verify that an account is defined in the journal.'))
302326
303 if not st.name == '/':327 if not st.name == '/':
304 st_number = st.name328 st_number = st.name
@@ -309,7 +333,7 @@
309 for line in st.move_line_ids:333 for line in st.move_line_ids:
310 if line.state != 'valid':334 if line.state != 'valid':
311 raise osv.except_osv(_('Error !'),335 raise osv.except_osv(_('Error !'),
312 _('The account entries lines are not in valid state.'))336 _('The account entries lines are not in valid state.'))
313# begin changes337# begin changes
314 errors_stack = []338 errors_stack = []
315 for st_line in st.line_ids:339 for st_line in st.line_ids:
@@ -317,11 +341,15 @@
317 if st_line.analytic_account_id:341 if st_line.analytic_account_id:
318 if not st.journal_id.analytic_journal_id:342 if not st.journal_id.analytic_journal_id:
319 raise osv.except_osv(_('No Analytic Journal !'),343 raise osv.except_osv(_('No Analytic Journal !'),
320 _("You have to assign an analytic journal on the '%s' journal!") % (st.journal_id.name,))344 _("You have to assign an analytic"
345 " journal on the '%s' journal!") % st.journal_id.name)
321 if not st_line.amount:346 if not st_line.amount:
322 continue347 continue
323 st_line_number = self.get_next_st_line_number(cr, uid, st_number, st_line, context)348 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)349 self.create_move_from_st_line(cr, uid, st_line.id,
350 company_currency_id,
351 st_line_number,
352 context)
325 except osv.except_osv, exc:353 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)354 msg = "Line ID %s with ref %s had following error: %s" % (st_line.id, st_line.ref, exc.value)
327 errors_stack.append(msg)355 errors_stack.append(msg)
@@ -332,37 +360,95 @@
332 msg = u"\n".join(errors_stack)360 msg = u"\n".join(errors_stack)
333 raise osv.except_osv(_('Error'), msg)361 raise osv.except_osv(_('Error'), msg)
334#end changes362#end changes
335 self.write(cr, uid, [st.id], {363 self.write(cr, uid, [st.id],
336 'name': st_number,364 {'name': st_number,
337 'balance_end_real': st.balance_end365 'balance_end_real': st.balance_end},
338 }, context=context)366 context=context)
339 self.message_post(cr, uid, [st.id], body=_('Statement %s confirmed, journal items were created.') % (st_number,), context=context)367 body = _('Statement %s confirmed, journal items were created.') % st_number
368 self.message_post(cr, uid, [st.id],
369 body,
370 context=context)
340 return self.write(cr, uid, ids, {'state': 'confirm'}, context=context)371 return self.write(cr, uid, ids, {'state': 'confirm'}, context=context)
341372
342 def get_account_for_counterpart(373 def get_account_for_counterpart(self, cr, uid, amount, account_receivable, account_payable):
343 self, cr, uid, amount, account_receivable, account_payable):374 """For backward compatibility."""
375 account_id, type = self.get_account_and_type_for_counterpart(cr, uid, amount,
376 account_receivable,
377 account_payable)
378 return account_id
379
380 def _compute_type_from_partner_profile(self, cr, uid, partner_id,
381 default_type, context=None):
382 """Compute the statement line type
383 from partner profile (customer, supplier)"""
384 obj_partner = self.pool.get('res.partner')
385 part = obj_partner.browse(cr, uid, partner_id, context=context)
386 if part.supplier == part.customer:
387 return default_type
388 if part.supplier:
389 return 'supplier'
390 else:
391 return 'customer'
392
393 def _compute_type_from_amount(self, cr, uid, amount):
394 """Compute the statement type based on amount"""
395 if amount < 0:
396 return 'supplier'
397 return 'customer'
398
399 def get_type_for_counterpart(self, cr, uid, amount, partner_id=False):
400 """Give the amount and receive the type to use for the line.
401 The rules are:
402 - If the customer checkbox is checked on the found partner, type customer
403 - If the supplier checkbox is checked on the found partner, typewill be supplier
404 - If both checkbox are checked or none of them, it'll be based on the amount :
405 If amount is positif the type customer,
406 If amount is negativ, the type supplier
407 :param float: amount of the line
408 :param int/long: partner_id the partner id
409 :return: type as string: the default type to use: 'customer' or 'supplier'.
410 """
411 s_line_type = self._compute_type_from_amount(cr, uid, amount)
412 if partner_id:
413 s_line_type = self._compute_type_from_partner_profile(cr, uid,
414 partner_id, s_line_type)
415 return s_line_type
416
417 def get_account_and_type_for_counterpart(self, cr, uid, amount, account_receivable,
418 account_payable, partner_id=False):
344 """419 """
345 Give the amount, payable and receivable account (that can be found using420 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 method421 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.422 should be use when there is no other way to know which one to take.
423 The rules are:
424 - If the customer checkbox is checked on the found partner, type and account will be customer and receivable
425 - If the supplier checkbox is checked on the found partner, type and account will be supplier and payable
426 - If both checkbox are checked or none of them, it'll be based on the amount :
427 If amount is positif the type and account will be customer and receivable,
428 If amount is negativ, the type and account will be supplier and payable
429 Note that we return the payable or receivable account from agrs and not from the optional partner_id
430 given !
348431
349 :param float: amount of the line432 :param float: amount of the line
350 :param int/long: account_receivable the receivable account433 :param int/long: account_receivable the receivable account
351 :param int/long: account_payable the payable account434 :param int/long: account_payable the payable account
352 :return: int/long :the default account to be used by statement line as the counterpart435 :param int/long: partner_id the partner id
353 of the journal account depending on the amount.436 :return: dict with [account_id as int/long,type as string]: the default account to be used by
437 statement line as the counterpart of the journal account depending on the amount and the type
438 as 'customer' or 'supplier'.
354 """439 """
355 account_id = False440 account_id = False
356 if amount >= 0:441 type = self.get_type_for_counterpart(cr, uid, amount, partner_id=partner_id)
442 if type == 'supplier':
443 account_id = account_payable
444 else:
357 account_id = account_receivable445 account_id = account_receivable
358 else:
359 account_id = account_payable
360 if not account_id:446 if not account_id:
361 raise osv.except_osv(447 raise osv.except_osv(
362 _('Can not determine account'),448 _('Can not determine account'),
363 _('Please ensure that minimal properties are set')449 _('Please ensure that minimal properties are set')
364 )450 )
365 return account_id451 return [account_id, type]
366452
367 def get_default_pay_receiv_accounts(self, cr, uid, context=None):453 def get_default_pay_receiv_accounts(self, cr, uid, context=None):
368 """454 """
@@ -386,14 +472,11 @@
386 ('model', '=', 'res.partner')],472 ('model', '=', 'res.partner')],
387 context=context473 context=context
388 )474 )
389 property_ids = property_obj.search(475 property_ids = property_obj.search(cr,
390 cr,476 uid,
391 uid,477 [('fields_id', 'in', model_fields_ids),
392 [('fields_id', 'in', model_fields_ids),478 ('res_id', '=', False)],
393 ('res_id', '=', False),479 context=context)
394 ],
395 context=context
396 )
397480
398 for erp_property in property_obj.browse(481 for erp_property in property_obj.browse(
399 cr, uid, property_ids, context=context):482 cr, uid, property_ids, context=context):
@@ -433,13 +516,10 @@
433 journal_id = import_config.journal_id.id516 journal_id = import_config.journal_id.id
434 account_id = import_config.journal_id.default_debit_account_id.id517 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 False518 credit_partner_id = import_config.partner_id and import_config.partner_id.id or False
436 return {'value':519 return {'value': {'journal_id': journal_id,
437 {'journal_id': journal_id,520 'account_id': account_id,
438 'account_id': account_id,521 'balance_check': import_config.balance_check,
439 'balance_check': import_config.balance_check,522 'credit_partner_id': credit_partner_id}}
440 'credit_partner_id': credit_partner_id,
441 }
442 }
443523
444524
445class AccountBankSatementLine(Model):525class AccountBankSatementLine(Model):
@@ -472,18 +552,24 @@
472 'account_id': _get_default_account,552 'account_id': _get_default_account,
473 }553 }
474554
475 def get_values_for_line(self, cr, uid, profile_id=False, partner_id=False, line_type=False, amount=False, context=None):555 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 """556 """
477 Return the account_id to be used in the line of a bank statement. It'll base the result as follow:557 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 = general558 - 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, receivable559 # TODO
560 - Elif how_get_type_account is set to force_supplier or force_customer, will take respectively payable and type=supplier,
561 receivable and type=customer otherwise
562 # END TODO
563 - Elif line_type is given, take the partner receivable/payable property (payable if type=supplier, receivable
480 otherwise)564 otherwise)
481 - Elif amount is given, take the partner receivable/payable property (receivable if amount >= 0.0,565 - Elif amount is given:
482 payable otherwise). In that case, we also fullfill the type (receivable = customer, payable = supplier)566 - 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 chosen567 - If the supplier checkbox is checked on the found partner, type and account will be supplier and payable
568 - If both checkbox are checked or none of them, it'll be based on the amount :
569 If amount is positif the type and account will be customer and receivable,
570 If amount is negativ, 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 value571 - 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.572 for account_id. Note that in that case, we return the receivable one.
486
487 :param int/long profile_id of the related bank statement573 :param int/long profile_id of the related bank statement
488 :param int/long partner_id of the line574 :param int/long partner_id of the line
489 :param char line_type: a value from: 'general', 'supplier', 'customer'575 :param char line_type: a value from: 'general', 'supplier', 'customer'
@@ -501,14 +587,19 @@
501 obj_stat = self.pool.get('account.bank.statement')587 obj_stat = self.pool.get('account.bank.statement')
502 line_type = receiv_account = pay_account = account_id = False588 line_type = receiv_account = pay_account = account_id = False
503 # If profile has a receivable_account_id, we return it in any case589 # If profile has a receivable_account_id, we return it in any case
504 if profile_id:590 if master_account_id:
591 res['account_id'] = master_account_id
592 res['type'] = 'general'
593 return res
594 # To obtimize we consider passing false means there is no account
595 # on profile
596 if profile_id and master_account_id is None:
505 profile = self.pool.get("account.statement.profile").browse(597 profile = self.pool.get("account.statement.profile").browse(
506 cr, uid, profile_id, context=context)598 cr, uid, profile_id, context=context)
507 if profile.receivable_account_id:599 if profile.receivable_account_id:
508 account_id = profile.receivable_account_id.id600 res['account_id'] = profile.receivable_account_id.id
509 line_type = 'general'601 res['type'] = 'general'
510 return res602 return res
511 # If partner -> take from him
512 if partner_id:603 if partner_id:
513 part = obj_partner.browse(cr, uid, partner_id, context=context)604 part = obj_partner.browse(cr, uid, partner_id, context=context)
514 pay_account = part.property_account_payable.id605 pay_account = part.property_account_payable.id
@@ -523,12 +614,8 @@
523 if line_type == 'supplier':614 if line_type == 'supplier':
524 account_id = pay_account615 account_id = pay_account
525 elif amount is not False:616 elif amount is not False:
526 if amount >= 0:617 account_id, line_type = obj_stat.get_account_and_type_for_counterpart(cr, uid, amount,
527 account_id = receiv_account618 receiv_account, pay_account, partner_id=partner_id)
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_account619 res['account_id'] = account_id if account_id else receiv_account
533 res['type'] = line_type620 res['type'] = line_type
534 return res621 return res
@@ -537,41 +624,36 @@
537 """624 """
538 Override of the basic method as we need to pass the profile_id in the on_change_type625 Override of the basic method as we need to pass the profile_id in the on_change_type
539 call.626 call.
627 Moreover, we now call the get_account_and_type_for_counterpart method now to get the
628 type to use.
540 """629 """
541 obj_partner = self.pool.get('res.partner')630 obj_stat = self.pool.get('account.bank.statement')
542 if not partner_id:631 if not partner_id:
543 return {}632 return {}
544 part = obj_partner.browse(cr, uid, partner_id, context=context)633 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:634 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):635 if res_type['value'] and res_type['value'].get('account_id', False):
556 return {'value': {'type': type,636 return {'value': {'type': line_type,
557 'account_id': res_type['value']['account_id'],637 'account_id': res_type['value']['account_id'],
558 'voucher_id': False}}638 'voucher_id': False}}
559 return {'value': {'type': type}}639 return {'value': {'type': line_type}}
560640
561 def onchange_type(self, cr, uid, line_id, partner_id, type, profile_id, context=None):641 def onchange_type(self, cr, uid, line_id, partner_id, line_type, profile_id, context=None):
562 """642 """
563 Keep the same features as in standard and call super. If an account is returned,643 Keep the same features as in standard and call super. If an account is returned,
564 call the method to compute line values.644 call the method to compute line values.
565 """645 """
566 res = super(AccountBankSatementLine, self).onchange_type(646 res = super(AccountBankSatementLine, self).onchange_type(cr, uid,
567 cr, uid, line_id, partner_id, type, context=context)647 line_id,
648 partner_id,
649 line_type,
650 context=context)
568 if 'account_id' in res['value']:651 if 'account_id' in res['value']:
569 result = self.get_values_for_line(652 result = self.get_values_for_line(cr, uid,
570 cr, uid,653 profile_id=profile_id,
571 profile_id=profile_id,654 partner_id=partner_id,
572 partner_id=partner_id,655 line_type=line_type,
573 line_type=type,656 context=context)
574 context=context)
575 if result:657 if result:
576 res['value'].update({'account_id': result['account_id']})658 res['value'].update({'account_id': result['account_id']})
577 return res659 return res

Subscribers

People subscribed via source and target branches