Merge lp:~camptocamp/banking-addons/bank-statement-ext_report_lang into lp:banking-addons/6.1
- bank-statement-ext_report_lang
- Merge into 6.1
Status: | Superseded |
---|---|
Proposed branch: | lp:~camptocamp/banking-addons/bank-statement-ext_report_lang |
Merge into: | lp:banking-addons/6.1 |
Diff against target: |
8071 lines (+7614/-0) 85 files modified
account_advanced_reconcile/__init__.py (+24/-0) account_advanced_reconcile/__openerp__.py (+83/-0) account_advanced_reconcile/advanced_reconciliation.py (+118/-0) account_advanced_reconcile/base_advanced_reconciliation.py (+272/-0) account_advanced_reconcile/easy_reconcile.py (+36/-0) account_advanced_reconcile/easy_reconcile_view.xml (+19/-0) account_advanced_reconcile/i18n/fr.po (+91/-0) account_easy_reconcile/__init__.py (+25/-0) account_easy_reconcile/__openerp__.py (+66/-0) account_easy_reconcile/base_reconciliation.py (+208/-0) account_easy_reconcile/easy_reconcile.py (+285/-0) account_easy_reconcile/easy_reconcile.xml (+156/-0) account_easy_reconcile/easy_reconcile_history.py (+153/-0) account_easy_reconcile/easy_reconcile_history_view.xml (+98/-0) account_easy_reconcile/i18n/fr.po (+425/-0) account_easy_reconcile/security/ir.model.access.csv (+15/-0) account_easy_reconcile/security/ir_rule.xml (+25/-0) account_easy_reconcile/simple_reconciliation.py (+120/-0) account_statement_base_completion/__init__.py (+23/-0) account_statement_base_completion/__openerp__.py (+74/-0) account_statement_base_completion/data.xml (+37/-0) account_statement_base_completion/i18n/fr.po (+180/-0) account_statement_base_completion/partner.py (+38/-0) account_statement_base_completion/partner_view.xml (+22/-0) account_statement_base_completion/security/ir.model.access.csv (+3/-0) account_statement_base_completion/statement.py (+527/-0) account_statement_base_completion/statement_view.xml (+104/-0) account_statement_base_import/__init__.py (+23/-0) account_statement_base_import/__openerp__.py (+70/-0) account_statement_base_import/data/statement.csv (+4/-0) account_statement_base_import/i18n/fr.po (+253/-0) account_statement_base_import/parser/__init__.py (+25/-0) account_statement_base_import/parser/file_parser.py (+218/-0) account_statement_base_import/parser/generic_file_parser.py (+102/-0) account_statement_base_import/parser/parser.py (+219/-0) account_statement_base_import/statement.py (+303/-0) account_statement_base_import/statement_view.xml (+46/-0) account_statement_base_import/wizard/__init__.py (+20/-0) account_statement_base_import/wizard/import_statement.py (+122/-0) account_statement_base_import/wizard/import_statement_view.xml (+44/-0) account_statement_completion_voucher/__init__.py (+20/-0) account_statement_completion_voucher/__openerp__.py (+46/-0) account_statement_completion_voucher/statement_view.xml (+21/-0) account_statement_ext/__init__.py (+25/-0) account_statement_ext/__openerp__.py (+88/-0) account_statement_ext/account.py (+38/-0) account_statement_ext/i18n/fr.po (+301/-0) account_statement_ext/report.xml (+25/-0) account_statement_ext/report/__init__.py (+26/-0) account_statement_ext/report/bank_statement_report.mako (+69/-0) account_statement_ext/report/bank_statement_report.py (+71/-0) account_statement_ext/report/bank_statement_webkit_header.xml (+180/-0) account_statement_ext/security/ir.model.access.csv (+3/-0) account_statement_ext/security/ir_rule.xml (+10/-0) account_statement_ext/statement.py (+678/-0) account_statement_ext/statement_view.xml (+162/-0) account_statement_ext/voucher.py (+49/-0) account_statement_ext_voucher/__init__.py (+20/-0) account_statement_ext_voucher/__openerp__.py (+52/-0) account_statement_ext_voucher/statement_voucher.py (+51/-0) account_statement_transactionid_completion/__init__.py (+22/-0) account_statement_transactionid_completion/__openerp__.py (+56/-0) account_statement_transactionid_completion/data.xml (+12/-0) account_statement_transactionid_completion/statement.py (+94/-0) account_statement_transactionid_completion/statement_view.xml (+22/-0) account_statement_transactionid_import/__init__.py (+22/-0) account_statement_transactionid_import/__openerp__.py (+60/-0) account_statement_transactionid_import/data/statement.csv (+4/-0) account_statement_transactionid_import/parser/__init__.py (+22/-0) account_statement_transactionid_import/parser/transactionid_file_parser.py (+86/-0) account_statement_transactionid_import/statement.py (+47/-0) base_transaction_id/__init__.py (+24/-0) base_transaction_id/__openerp__.py (+57/-0) base_transaction_id/invoice.py (+36/-0) base_transaction_id/invoice_view.xml (+30/-0) base_transaction_id/sale.py (+43/-0) base_transaction_id/sale_view.xml (+21/-0) base_transaction_id/stock.py (+42/-0) invoicing_voucher_killer/__init__.py (+20/-0) invoicing_voucher_killer/__openerp__.py (+39/-0) invoicing_voucher_killer/invoice_data.xml (+7/-0) invoicing_voucher_killer/invoice_view.xml (+48/-0) statement_voucher_killer/__init__.py (+21/-0) statement_voucher_killer/__openerp__.py (+40/-0) statement_voucher_killer/voucher.py (+128/-0) |
To merge this branch: | bzr merge lp:~camptocamp/banking-addons/bank-statement-ext_report_lang |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Banking Addons Core Editors | Pending | ||
Review via email:
|
This proposal supersedes a proposal from 2013-07-17.
This proposal has been superseded by a proposal from 2013-07-17.
Commit message
Description of the change
This fix take the language on the partner linked to the current user, for the bank statement report
Unmerged revisions
- 94. By Vincent Renaville@camptocamp
-
[FIX] fix report lang
- 93. By Vincent Renaville@camptocamp
-
[MRG] prevent account_
advanced_ reconcile to crash if ref in the move is not set - 92. By Nicolas Bessi - Camptocamp
-
[MRG] [FIX] Restore default period in bank statement by taking context in account
- 91. By Nicolas Bessi - Camptocamp
-
[MRG] [IMP] Refine partner label lookup error message
[FIX] Restore error message in log
[FIX] Fix partner label lookup regexp - 90. By Nicolas Bessi - Camptocamp
-
[MRG] 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 payableCompletion:
-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 fieldRefactoring 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 - 89. By Alexandre Fayolle - camptocamp
-
[FIX] permissions on account.
statement. profil: an accountant needs write access on the model to be able to import a bank statement otherwise, the message.post call fails
- 88. By Nicolas Bessi - Camptocamp
-
[MRG] Fixes performance trouble when using bank_statement_
label based completion rules by using memoizer pattern. Add lines in context to be able to acces them in completion rules. It is not
mandatory as we can do line.satement_id.line_ ids but it is more efficient. Some minor cleanup
- 87. By Nicolas Bessi - Camptocamp
-
[MRG] Improve statement import wizard by using mass writing of statement line in order to avoid store field loop computation.
- 86. By Nicolas Bessi - Camptocamp
-
[MRG] Improve statement import global usability by retuning usable error message while parsing files.
Allows empty value for float in parsed CSV.Added note about the additional dependency on python-xlrd in the description.
- 85. By Nicolas Bessi - Camptocamp
-
[MRG] Add a completion rule to allows supplier invoice completion baser on invoice number
Preview Diff
1 | === added directory 'account_advanced_reconcile' |
2 | === added file 'account_advanced_reconcile/__init__.py' |
3 | --- account_advanced_reconcile/__init__.py 1970-01-01 00:00:00 +0000 |
4 | +++ account_advanced_reconcile/__init__.py 2013-07-17 14:55:41 +0000 |
5 | @@ -0,0 +1,24 @@ |
6 | +# -*- coding: utf-8 -*- |
7 | +############################################################################## |
8 | +# |
9 | +# Author: Guewen Baconnier |
10 | +# Copyright 2012 Camptocamp SA |
11 | +# |
12 | +# This program is free software: you can redistribute it and/or modify |
13 | +# it under the terms of the GNU Affero General Public License as |
14 | +# published by the Free Software Foundation, either version 3 of the |
15 | +# License, or (at your option) any later version. |
16 | +# |
17 | +# This program is distributed in the hope that it will be useful, |
18 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
19 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
20 | +# GNU Affero General Public License for more details. |
21 | +# |
22 | +# You should have received a copy of the GNU Affero General Public License |
23 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
24 | +# |
25 | +############################################################################## |
26 | + |
27 | +import easy_reconcile |
28 | +import base_advanced_reconciliation |
29 | +import advanced_reconciliation |
30 | |
31 | === added file 'account_advanced_reconcile/__openerp__.py' |
32 | --- account_advanced_reconcile/__openerp__.py 1970-01-01 00:00:00 +0000 |
33 | +++ account_advanced_reconcile/__openerp__.py 2013-07-17 14:55:41 +0000 |
34 | @@ -0,0 +1,83 @@ |
35 | +# -*- coding: utf-8 -*- |
36 | +############################################################################## |
37 | +# |
38 | +# Author: Guewen Baconnier |
39 | +# Copyright 2012 Camptocamp SA |
40 | +# |
41 | +# This program is free software: you can redistribute it and/or modify |
42 | +# it under the terms of the GNU Affero General Public License as |
43 | +# published by the Free Software Foundation, either version 3 of the |
44 | +# License, or (at your option) any later version. |
45 | +# |
46 | +# This program is distributed in the hope that it will be useful, |
47 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
48 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
49 | +# GNU Affero General Public License for more details. |
50 | +# |
51 | +# You should have received a copy of the GNU Affero General Public License |
52 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
53 | +# |
54 | +############################################################################## |
55 | + |
56 | +{'name': "Advanced Reconcile", |
57 | + 'version': '1.0', |
58 | + 'author': 'Camptocamp', |
59 | + 'maintainer': 'Camptocamp', |
60 | + 'category': 'Finance', |
61 | + 'complexity': 'normal', |
62 | + 'depends': ['account_easy_reconcile', |
63 | + ], |
64 | + 'description': """ |
65 | +Advanced reconciliation methods for the module account_easy_reconcile. |
66 | + |
67 | +In addition to the features implemented in account_easy_reconcile, which are: |
68 | + - reconciliation facilities for big volume of transactions |
69 | + - setup different profiles of reconciliation by account |
70 | + - each profile can use many methods of reconciliation |
71 | + - this module is also a base to create others reconciliation methods |
72 | + which can plug in the profiles |
73 | + - a profile a reconciliation can be run manually or by a cron |
74 | + - monitoring of reconcilation runs with an history |
75 | + |
76 | +It implements a basis to created advanced reconciliation methods in a few lines |
77 | +of code. |
78 | + |
79 | +Typically, such a method can be: |
80 | + - Reconcile Journal items if the partner and the ref are equal |
81 | + - Reconcile Journal items if the partner is equal and the ref |
82 | + is the same than ref or name |
83 | + - Reconcile Journal items if the partner is equal and the ref |
84 | + match with a pattern |
85 | + |
86 | +And they allows: |
87 | + - Reconciliations with multiple credit / multiple debit lines |
88 | + - Partial reconciliations |
89 | + - Write-off amount as well |
90 | + |
91 | +A method is already implemented in this module, it matches on items: |
92 | + - Partner |
93 | + - Ref on credit move lines should be case insensitive equals to the ref or |
94 | + the name of the debit move line |
95 | + |
96 | +The base class to find the reconciliations is built to be as efficient as |
97 | +possible. |
98 | + |
99 | +So basically, if you have an invoice with 3 payments (one per month), the first |
100 | +month, it will partial reconcile the debit move line with the first payment, the second |
101 | +month, it will partial reconcile the debit move line with 2 first payments, |
102 | +the third month, it will make the full reconciliation. |
103 | + |
104 | +This module is perfectly adapted for E-Commerce business where a big volume of |
105 | +move lines and so, reconciliations, are involved and payments often come from |
106 | +many offices. |
107 | + |
108 | + """, |
109 | + 'website': 'http://www.camptocamp.com', |
110 | + 'data': ['easy_reconcile_view.xml'], |
111 | + 'test': [], |
112 | + 'images': [], |
113 | + 'installable': True, |
114 | + 'auto_install': False, |
115 | + 'license': 'AGPL-3', |
116 | + 'application': True, |
117 | +} |
118 | |
119 | === added file 'account_advanced_reconcile/advanced_reconciliation.py' |
120 | --- account_advanced_reconcile/advanced_reconciliation.py 1970-01-01 00:00:00 +0000 |
121 | +++ account_advanced_reconcile/advanced_reconciliation.py 2013-07-17 14:55:41 +0000 |
122 | @@ -0,0 +1,118 @@ |
123 | +# -*- coding: utf-8 -*- |
124 | +############################################################################## |
125 | +# |
126 | +# Author: Guewen Baconnier |
127 | +# Copyright 2012 Camptocamp SA |
128 | +# |
129 | +# This program is free software: you can redistribute it and/or modify |
130 | +# it under the terms of the GNU Affero General Public License as |
131 | +# published by the Free Software Foundation, either version 3 of the |
132 | +# License, or (at your option) any later version. |
133 | +# |
134 | +# This program is distributed in the hope that it will be useful, |
135 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
136 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
137 | +# GNU Affero General Public License for more details. |
138 | +# |
139 | +# You should have received a copy of the GNU Affero General Public License |
140 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
141 | +# |
142 | +############################################################################## |
143 | + |
144 | +from openerp.osv import orm |
145 | + |
146 | + |
147 | +class easy_reconcile_advanced_ref(orm.TransientModel): |
148 | + |
149 | + _name = 'easy.reconcile.advanced.ref' |
150 | + _inherit = 'easy.reconcile.advanced' |
151 | + |
152 | + def _skip_line(self, cr, uid, rec, move_line, context=None): |
153 | + """ |
154 | + When True is returned on some conditions, the credit move line |
155 | + will be skipped for reconciliation. Can be inherited to |
156 | + skip on some conditions. ie: ref or partner_id is empty. |
157 | + """ |
158 | + return not (move_line.get('ref') and move_line.get('partner_id')) |
159 | + |
160 | + def _matchers(self, cr, uid, rec, move_line, context=None): |
161 | + """ |
162 | + Return the values used as matchers to find the opposite lines |
163 | + |
164 | + All the matcher keys in the dict must have their equivalent in |
165 | + the `_opposite_matchers`. |
166 | + |
167 | + The values of each matcher key will be searched in the |
168 | + one returned by the `_opposite_matchers` |
169 | + |
170 | + Must be inherited to implement the matchers for one method |
171 | + |
172 | + For instance, it can return: |
173 | + return ('ref', move_line['rec']) |
174 | + |
175 | + or |
176 | + return (('partner_id', move_line['partner_id']), |
177 | + ('ref', "prefix_%s" % move_line['rec'])) |
178 | + |
179 | + All the matchers have to be found in the opposite lines |
180 | + to consider them as "opposite" |
181 | + |
182 | + The matchers will be evaluated in the same order as declared |
183 | + vs the the opposite matchers, so you can gain performance by |
184 | + declaring first the partners with the less computation. |
185 | + |
186 | + All matchers should match with their opposite to be considered |
187 | + as "matching". |
188 | + So with the previous example, partner_id and ref have to be |
189 | + equals on the opposite line matchers. |
190 | + |
191 | + :return: tuple of tuples (key, value) where the keys are |
192 | + the matchers keys |
193 | + (must be the same than `_opposite_matchers` returns, |
194 | + and their values to match in the opposite lines. |
195 | + A matching key can have multiples values. |
196 | + """ |
197 | + return (('partner_id', move_line['partner_id']), |
198 | + ('ref', move_line['ref'].lower().strip())) |
199 | + |
200 | + def _opposite_matchers(self, cr, uid, rec, move_line, context=None): |
201 | + """ |
202 | + Return the values of the opposite line used as matchers |
203 | + so the line is matched |
204 | + |
205 | + Must be inherited to implement the matchers for one method |
206 | + It can be inherited to apply some formatting of fields |
207 | + (strip(), lower() and so on) |
208 | + |
209 | + This method is the counterpart of the `_matchers()` method. |
210 | + |
211 | + Each matcher has to yield its value respecting the order |
212 | + of the `_matchers()`. |
213 | + |
214 | + When a matcher does not correspond, the next matchers won't |
215 | + be evaluated so the ones which need the less computation |
216 | + have to be executed first. |
217 | + |
218 | + If the `_matchers()` returns: |
219 | + (('partner_id', move_line['partner_id']), |
220 | + ('ref', move_line['ref'])) |
221 | + |
222 | + Here, you should yield : |
223 | + yield ('partner_id', move_line['partner_id']) |
224 | + yield ('ref', move_line['ref']) |
225 | + |
226 | + Note that a matcher can contain multiple values, as instance, |
227 | + if for a move line, you want to search from its `ref` in the |
228 | + `ref` or `name` fields of the opposite move lines, you have to |
229 | + yield ('partner_id', move_line['partner_id']) |
230 | + yield ('ref', (move_line['ref'], move_line['name']) |
231 | + |
232 | + An OR is used between the values for the same key. |
233 | + An AND is used between the differents keys. |
234 | + |
235 | + :param dict move_line: values of the move_line |
236 | + :yield: matchers as tuple ('matcher key', value(s)) |
237 | + """ |
238 | + yield ('partner_id', move_line['partner_id']) |
239 | + yield ('ref', ((move_line['ref'] or '').lower().strip(), |
240 | + move_line['name'].lower().strip())) |
241 | |
242 | === added file 'account_advanced_reconcile/base_advanced_reconciliation.py' |
243 | --- account_advanced_reconcile/base_advanced_reconciliation.py 1970-01-01 00:00:00 +0000 |
244 | +++ account_advanced_reconcile/base_advanced_reconciliation.py 2013-07-17 14:55:41 +0000 |
245 | @@ -0,0 +1,272 @@ |
246 | +# -*- coding: utf-8 -*- |
247 | +############################################################################## |
248 | +# |
249 | +# Author: Guewen Baconnier |
250 | +# Copyright 2012 Camptocamp SA |
251 | +# |
252 | +# This program is free software: you can redistribute it and/or modify |
253 | +# it under the terms of the GNU Affero General Public License as |
254 | +# published by the Free Software Foundation, either version 3 of the |
255 | +# License, or (at your option) any later version. |
256 | +# |
257 | +# This program is distributed in the hope that it will be useful, |
258 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
259 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
260 | +# GNU Affero General Public License for more details. |
261 | +# |
262 | +# You should have received a copy of the GNU Affero General Public License |
263 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
264 | +# |
265 | +############################################################################## |
266 | + |
267 | +from itertools import product |
268 | +from openerp.osv import orm |
269 | + |
270 | + |
271 | +class easy_reconcile_advanced(orm.AbstractModel): |
272 | + |
273 | + _name = 'easy.reconcile.advanced' |
274 | + _inherit = 'easy.reconcile.base' |
275 | + |
276 | + def _query_debit(self, cr, uid, rec, context=None): |
277 | + """Select all move (debit>0) as candidate. """ |
278 | + select = self._select(rec) |
279 | + sql_from = self._from(rec) |
280 | + where, params = self._where(rec) |
281 | + where += " AND account_move_line.debit > 0 " |
282 | + |
283 | + where2, params2 = self._get_filter(cr, uid, rec, context=context) |
284 | + |
285 | + query = ' '.join((select, sql_from, where, where2)) |
286 | + |
287 | + cr.execute(query, params + params2) |
288 | + return cr.dictfetchall() |
289 | + |
290 | + def _query_credit(self, cr, uid, rec, context=None): |
291 | + """Select all move (credit>0) as candidate. """ |
292 | + select = self._select(rec) |
293 | + sql_from = self._from(rec) |
294 | + where, params = self._where(rec) |
295 | + where += " AND account_move_line.credit > 0 " |
296 | + |
297 | + where2, params2 = self._get_filter(cr, uid, rec, context=context) |
298 | + |
299 | + query = ' '.join((select, sql_from, where, where2)) |
300 | + |
301 | + cr.execute(query, params + params2) |
302 | + return cr.dictfetchall() |
303 | + |
304 | + def _matchers(self, cr, uid, rec, move_line, context=None): |
305 | + """ |
306 | + Return the values used as matchers to find the opposite lines |
307 | + |
308 | + All the matcher keys in the dict must have their equivalent in |
309 | + the `_opposite_matchers`. |
310 | + |
311 | + The values of each matcher key will be searched in the |
312 | + one returned by the `_opposite_matchers` |
313 | + |
314 | + Must be inherited to implement the matchers for one method |
315 | + |
316 | + As instance, it can return: |
317 | + return ('ref', move_line['rec']) |
318 | + |
319 | + or |
320 | + return (('partner_id', move_line['partner_id']), |
321 | + ('ref', "prefix_%s" % move_line['rec'])) |
322 | + |
323 | + All the matchers have to be found in the opposite lines |
324 | + to consider them as "opposite" |
325 | + |
326 | + The matchers will be evaluated in the same order as declared |
327 | + vs the the opposite matchers, so you can gain performance by |
328 | + declaring first the partners with the less computation. |
329 | + |
330 | + All matchers should match with their opposite to be considered |
331 | + as "matching". |
332 | + So with the previous example, partner_id and ref have to be |
333 | + equals on the opposite line matchers. |
334 | + |
335 | + :return: tuple of tuples (key, value) where the keys are |
336 | + the matchers keys |
337 | + (must be the same than `_opposite_matchers` returns, |
338 | + and their values to match in the opposite lines. |
339 | + A matching key can have multiples values. |
340 | + """ |
341 | + raise NotImplementedError |
342 | + |
343 | + def _opposite_matchers(self, cr, uid, rec, move_line, context=None): |
344 | + """ |
345 | + Return the values of the opposite line used as matchers |
346 | + so the line is matched |
347 | + |
348 | + Must be inherited to implement the matchers for one method |
349 | + It can be inherited to apply some formatting of fields |
350 | + (strip(), lower() and so on) |
351 | + |
352 | + This method is the counterpart of the `_matchers()` method. |
353 | + |
354 | + Each matcher has to yield its value respecting the order |
355 | + of the `_matchers()`. |
356 | + |
357 | + When a matcher does not correspond, the next matchers won't |
358 | + be evaluated so the ones which need the less computation |
359 | + have to be executed first. |
360 | + |
361 | + If the `_matchers()` returns: |
362 | + (('partner_id', move_line['partner_id']), |
363 | + ('ref', move_line['ref'])) |
364 | + |
365 | + Here, you should yield : |
366 | + yield ('partner_id', move_line['partner_id']) |
367 | + yield ('ref', move_line['ref']) |
368 | + |
369 | + Note that a matcher can contain multiple values, as instance, |
370 | + if for a move line, you want to search from its `ref` in the |
371 | + `ref` or `name` fields of the opposite move lines, you have to |
372 | + yield ('partner_id', move_line['partner_id']) |
373 | + yield ('ref', (move_line['ref'], move_line['name']) |
374 | + |
375 | + An OR is used between the values for the same key. |
376 | + An AND is used between the differents keys. |
377 | + |
378 | + :param dict move_line: values of the move_line |
379 | + :yield: matchers as tuple ('matcher key', value(s)) |
380 | + """ |
381 | + raise NotImplementedError |
382 | + |
383 | + @staticmethod |
384 | + def _compare_values(key, value, opposite_value): |
385 | + """Can be inherited to modify the equality condition |
386 | + specifically according to the matcher key (maybe using |
387 | + a like operator instead of equality on 'ref' as instance) |
388 | + """ |
389 | + # consider that empty vals are not valid matchers |
390 | + # it can still be inherited for some special cases |
391 | + # where it would be allowed |
392 | + if not (value and opposite_value): |
393 | + return False |
394 | + |
395 | + if value == opposite_value: |
396 | + return True |
397 | + return False |
398 | + |
399 | + @staticmethod |
400 | + def _compare_matcher_values(key, values, opposite_values): |
401 | + """ Compare every values from a matcher vs an opposite matcher |
402 | + and return True if it matches |
403 | + """ |
404 | + for value, ovalue in product(values, opposite_values): |
405 | + # we do not need to compare all values, if one matches |
406 | + # we are done |
407 | + if easy_reconcile_advanced._compare_values(key, value, ovalue): |
408 | + return True |
409 | + return False |
410 | + |
411 | + @staticmethod |
412 | + def _compare_matchers(matcher, opposite_matcher): |
413 | + """ |
414 | + Prepare and check the matchers to compare |
415 | + """ |
416 | + mkey, mvalue = matcher |
417 | + omkey, omvalue = opposite_matcher |
418 | + assert mkey == omkey, ("A matcher %s is compared with a matcher %s, " |
419 | + " the _matchers and _opposite_matchers are probably wrong" % |
420 | + (mkey, omkey)) |
421 | + if not isinstance(mvalue, (list, tuple)): |
422 | + mvalue = mvalue, |
423 | + if not isinstance(omvalue, (list, tuple)): |
424 | + omvalue = omvalue, |
425 | + return easy_reconcile_advanced._compare_matcher_values(mkey, mvalue, omvalue) |
426 | + |
427 | + def _compare_opposite(self, cr, uid, rec, move_line, opposite_move_line, |
428 | + matchers, context=None): |
429 | + """ Iterate over the matchers of the move lines vs opposite move lines |
430 | + and if they all match, return True. |
431 | + |
432 | + If all the matchers match for a move line and an opposite move line, |
433 | + they are candidate for a reconciliation. |
434 | + """ |
435 | + opp_matchers = self._opposite_matchers(cr, uid, rec, opposite_move_line, |
436 | + context=context) |
437 | + for matcher in matchers: |
438 | + try: |
439 | + opp_matcher = opp_matchers.next() |
440 | + except StopIteration: |
441 | + # if you fall here, you probably missed to put a `yield` |
442 | + # in `_opposite_matchers()` |
443 | + raise ValueError("Missing _opposite_matcher: %s" % matcher[0]) |
444 | + |
445 | + if not self._compare_matchers(matcher, opp_matcher): |
446 | + # if any of the matcher fails, the opposite line |
447 | + # is not a valid counterpart |
448 | + # directly returns so the next yield of _opposite_matchers |
449 | + # are not evaluated |
450 | + return False |
451 | + |
452 | + return True |
453 | + |
454 | + def _search_opposites(self, cr, uid, rec, move_line, opposite_move_lines, context=None): |
455 | + """ |
456 | + Search the opposite move lines for a move line |
457 | + |
458 | + :param dict move_line: the move line for which we search opposites |
459 | + :param list opposite_move_lines: list of dict of move lines values, the move |
460 | + lines we want to search for |
461 | + :return: list of matching lines |
462 | + """ |
463 | + matchers = self._matchers(cr, uid, rec, move_line, context=context) |
464 | + return [op for op in opposite_move_lines if |
465 | + self._compare_opposite( |
466 | + cr, uid, rec, move_line, op, matchers, context=context)] |
467 | + |
468 | + def _action_rec(self, cr, uid, rec, context=None): |
469 | + credit_lines = self._query_credit(cr, uid, rec, context=context) |
470 | + debit_lines = self._query_debit(cr, uid, rec, context=context) |
471 | + return self._rec_auto_lines_advanced( |
472 | + cr, uid, rec, credit_lines, debit_lines, context=context) |
473 | + |
474 | + def _skip_line(self, cr, uid, rec, move_line, context=None): |
475 | + """ |
476 | + When True is returned on some conditions, the credit move line |
477 | + will be skipped for reconciliation. Can be inherited to |
478 | + skip on some conditions. ie: ref or partner_id is empty. |
479 | + """ |
480 | + return False |
481 | + |
482 | + def _rec_auto_lines_advanced(self, cr, uid, rec, credit_lines, debit_lines, context=None): |
483 | + """ Advanced reconciliation main loop """ |
484 | + reconciled_ids = [] |
485 | + partial_reconciled_ids = [] |
486 | + reconcile_groups = [] |
487 | + |
488 | + for credit_line in credit_lines: |
489 | + if self._skip_line(cr, uid, rec, credit_line, context=context): |
490 | + continue |
491 | + |
492 | + opposite_lines = self._search_opposites( |
493 | + cr, uid, rec, credit_line, debit_lines, context=context) |
494 | + |
495 | + if not opposite_lines: |
496 | + continue |
497 | + |
498 | + opposite_ids = [l['id'] for l in opposite_lines] |
499 | + line_ids = opposite_ids + [credit_line['id']] |
500 | + for group in reconcile_groups: |
501 | + if any([lid in group for lid in opposite_ids]): |
502 | + group.update(line_ids) |
503 | + break |
504 | + else: |
505 | + reconcile_groups.append(set(line_ids)) |
506 | + |
507 | + lines_by_id = dict([(l['id'], l) for l in credit_lines + debit_lines]) |
508 | + for reconcile_group_ids in reconcile_groups: |
509 | + group_lines = [lines_by_id[lid] for lid in reconcile_group_ids] |
510 | + reconciled, full = self._reconcile_lines( |
511 | + cr, uid, rec, group_lines, allow_partial=True, context=context) |
512 | + if reconciled and full: |
513 | + reconciled_ids += reconcile_group_ids |
514 | + elif reconciled: |
515 | + partial_reconciled_ids += reconcile_group_ids |
516 | + |
517 | + return reconciled_ids, partial_reconciled_ids |
518 | |
519 | === added file 'account_advanced_reconcile/easy_reconcile.py' |
520 | --- account_advanced_reconcile/easy_reconcile.py 1970-01-01 00:00:00 +0000 |
521 | +++ account_advanced_reconcile/easy_reconcile.py 2013-07-17 14:55:41 +0000 |
522 | @@ -0,0 +1,36 @@ |
523 | +# -*- coding: utf-8 -*- |
524 | +############################################################################## |
525 | +# |
526 | +# Author: Guewen Baconnier |
527 | +# Copyright 2012 Camptocamp SA |
528 | +# |
529 | +# This program is free software: you can redistribute it and/or modify |
530 | +# it under the terms of the GNU Affero General Public License as |
531 | +# published by the Free Software Foundation, either version 3 of the |
532 | +# License, or (at your option) any later version. |
533 | +# |
534 | +# This program is distributed in the hope that it will be useful, |
535 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
536 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
537 | +# GNU Affero General Public License for more details. |
538 | +# |
539 | +# You should have received a copy of the GNU Affero General Public License |
540 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
541 | +# |
542 | +############################################################################## |
543 | + |
544 | +from openerp.osv import orm |
545 | + |
546 | + |
547 | +class account_easy_reconcile_method(orm.Model): |
548 | + |
549 | + _inherit = 'account.easy.reconcile.method' |
550 | + |
551 | + def _get_all_rec_method(self, cr, uid, context=None): |
552 | + methods = super(account_easy_reconcile_method, self).\ |
553 | + _get_all_rec_method(cr, uid, context=context) |
554 | + methods += [ |
555 | + ('easy.reconcile.advanced.ref', |
556 | + 'Advanced. Partner and Ref.'), |
557 | + ] |
558 | + return methods |
559 | |
560 | === added file 'account_advanced_reconcile/easy_reconcile_view.xml' |
561 | --- account_advanced_reconcile/easy_reconcile_view.xml 1970-01-01 00:00:00 +0000 |
562 | +++ account_advanced_reconcile/easy_reconcile_view.xml 2013-07-17 14:55:41 +0000 |
563 | @@ -0,0 +1,19 @@ |
564 | +<?xml version="1.0" encoding="utf-8"?> |
565 | +<openerp> |
566 | + <data noupdate="0"> |
567 | + <record id="view_easy_reconcile_form" model="ir.ui.view"> |
568 | + <field name="name">account.easy.reconcile.form</field> |
569 | + <field name="model">account.easy.reconcile</field> |
570 | + <field name="inherit_id" ref="account_easy_reconcile.account_easy_reconcile_form"/> |
571 | + <field name="arch" type="xml"> |
572 | + <page name="information" position="inside"> |
573 | + <group colspan="2" col="2"> |
574 | + <separator colspan="4" string="Advanced. Partner and Ref"/> |
575 | + <label string="Match multiple debit vs multiple credit entries. Allow partial reconciliation. |
576 | +The lines should have the partner, the credit entry ref. is matched vs the debit entry ref. or name." colspan="4"/> |
577 | + </group> |
578 | + </page> |
579 | + </field> |
580 | + </record> |
581 | + </data> |
582 | +</openerp> |
583 | |
584 | === added directory 'account_advanced_reconcile/i18n' |
585 | === added file 'account_advanced_reconcile/i18n/fr.po' |
586 | --- account_advanced_reconcile/i18n/fr.po 1970-01-01 00:00:00 +0000 |
587 | +++ account_advanced_reconcile/i18n/fr.po 2013-07-17 14:55:41 +0000 |
588 | @@ -0,0 +1,91 @@ |
589 | +# Translation of OpenERP Server. |
590 | +# This file contains the translation of the following modules: |
591 | +# * account_advanced_reconcile |
592 | +# |
593 | +msgid "" |
594 | +msgstr "" |
595 | +"Project-Id-Version: OpenERP Server 6.1\n" |
596 | +"Report-Msgid-Bugs-To: \n" |
597 | +"POT-Creation-Date: 2013-01-04 08:25+0000\n" |
598 | +"PO-Revision-Date: 2013-01-04 09:27+0100\n" |
599 | +"Last-Translator: Guewen Baconnier <guewen.baconnier@camptocamp.com>\n" |
600 | +"Language-Team: \n" |
601 | +"Language: \n" |
602 | +"MIME-Version: 1.0\n" |
603 | +"Content-Type: text/plain; charset=UTF-8\n" |
604 | +"Content-Transfer-Encoding: 8bit\n" |
605 | +"Plural-Forms: \n" |
606 | + |
607 | +#. module: account_advanced_reconcile |
608 | +#: field:easy.reconcile.advanced,partner_ids:0 |
609 | +#: field:easy.reconcile.advanced.ref,partner_ids:0 |
610 | +msgid "Restrict on partners" |
611 | +msgstr "Restriction sur les partenaires" |
612 | + |
613 | +#. module: account_advanced_reconcile |
614 | +#: field:easy.reconcile.advanced,account_id:0 |
615 | +#: field:easy.reconcile.advanced.ref,account_id:0 |
616 | +msgid "Account" |
617 | +msgstr "Compte" |
618 | + |
619 | +#. module: account_advanced_reconcile |
620 | +#: model:ir.model,name:account_advanced_reconcile.model_account_easy_reconcile_method |
621 | +msgid "reconcile method for account_easy_reconcile" |
622 | +msgstr "Méthode de lettrage pour le module account_easy_reconcile" |
623 | + |
624 | +#. module: account_advanced_reconcile |
625 | +#: field:easy.reconcile.advanced,journal_id:0 |
626 | +#: field:easy.reconcile.advanced.ref,journal_id:0 |
627 | +msgid "Journal" |
628 | +msgstr "Journal" |
629 | + |
630 | +#. module: account_advanced_reconcile |
631 | +#: field:easy.reconcile.advanced,account_profit_id:0 |
632 | +#: field:easy.reconcile.advanced.ref,account_profit_id:0 |
633 | +msgid "Account Profit" |
634 | +msgstr "Compte de produit" |
635 | + |
636 | +#. module: account_advanced_reconcile |
637 | +#: view:account.easy.reconcile:0 |
638 | +msgid "Match multiple debit vs multiple credit entries. Allow partial reconciliation. The lines should have the partner, the credit entry ref. is matched vs the debit entry ref. or name." |
639 | +msgstr "Le Lettrage peut s'effectuer sur plusieurs écritures de débit et crédit. Le Lettrage partiel est autorisé. Les écritures doivent avoir le même partenaire et la référence sur les écritures de crédit doit se retrouver dans la référence ou la description sur les écritures de débit." |
640 | + |
641 | +#. module: account_advanced_reconcile |
642 | +#: field:easy.reconcile.advanced,filter:0 |
643 | +#: field:easy.reconcile.advanced.ref,filter:0 |
644 | +msgid "Filter" |
645 | +msgstr "Filtre" |
646 | + |
647 | +#. module: account_advanced_reconcile |
648 | +#: view:account.easy.reconcile:0 |
649 | +msgid "Advanced. Partner and Ref" |
650 | +msgstr "Avancé. Partenaire et Réf." |
651 | + |
652 | +#. module: account_advanced_reconcile |
653 | +#: field:easy.reconcile.advanced,date_base_on:0 |
654 | +#: field:easy.reconcile.advanced.ref,date_base_on:0 |
655 | +msgid "Date of reconciliation" |
656 | +msgstr "Date de lettrage" |
657 | + |
658 | +#. module: account_advanced_reconcile |
659 | +#: model:ir.model,name:account_advanced_reconcile.model_easy_reconcile_advanced |
660 | +msgid "easy.reconcile.advanced" |
661 | +msgstr "easy.reconcile.advanced" |
662 | + |
663 | +#. module: account_advanced_reconcile |
664 | +#: field:easy.reconcile.advanced,account_lost_id:0 |
665 | +#: field:easy.reconcile.advanced.ref,account_lost_id:0 |
666 | +msgid "Account Lost" |
667 | +msgstr "Compte de charge" |
668 | + |
669 | +#. module: account_advanced_reconcile |
670 | +#: model:ir.model,name:account_advanced_reconcile.model_easy_reconcile_advanced_ref |
671 | +msgid "easy.reconcile.advanced.ref" |
672 | +msgstr "easy.reconcile.advanced.ref" |
673 | + |
674 | +#. module: account_advanced_reconcile |
675 | +#: field:easy.reconcile.advanced,write_off:0 |
676 | +#: field:easy.reconcile.advanced.ref,write_off:0 |
677 | +msgid "Write off allowed" |
678 | +msgstr "Écart autorisé" |
679 | + |
680 | |
681 | === added directory 'account_easy_reconcile' |
682 | === added file 'account_easy_reconcile/__init__.py' |
683 | --- account_easy_reconcile/__init__.py 1970-01-01 00:00:00 +0000 |
684 | +++ account_easy_reconcile/__init__.py 2013-07-17 14:55:41 +0000 |
685 | @@ -0,0 +1,25 @@ |
686 | +# -*- coding: utf-8 -*- |
687 | +############################################################################## |
688 | +# |
689 | +# Copyright 2012 Camptocamp SA (Guewen Baconnier) |
690 | +# Copyright (C) 2010 Sébastien Beau |
691 | +# |
692 | +# This program is free software: you can redistribute it and/or modify |
693 | +# it under the terms of the GNU Affero General Public License as |
694 | +# published by the Free Software Foundation, either version 3 of the |
695 | +# License, or (at your option) any later version. |
696 | +# |
697 | +# This program is distributed in the hope that it will be useful, |
698 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
699 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
700 | +# GNU Affero General Public License for more details. |
701 | +# |
702 | +# You should have received a copy of the GNU Affero General Public License |
703 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
704 | +# |
705 | +############################################################################## |
706 | + |
707 | +import easy_reconcile |
708 | +import base_reconciliation |
709 | +import simple_reconciliation |
710 | +import easy_reconcile_history |
711 | |
712 | === added file 'account_easy_reconcile/__openerp__.py' |
713 | --- account_easy_reconcile/__openerp__.py 1970-01-01 00:00:00 +0000 |
714 | +++ account_easy_reconcile/__openerp__.py 2013-07-17 14:55:41 +0000 |
715 | @@ -0,0 +1,66 @@ |
716 | +# -*- coding: utf-8 -*- |
717 | +############################################################################## |
718 | +# |
719 | +# Copyright 2012 Camptocamp SA (Guewen Baconnier) |
720 | +# Copyright (C) 2010 Sébastien Beau |
721 | +# |
722 | +# This program is free software: you can redistribute it and/or modify |
723 | +# it under the terms of the GNU Affero General Public License as |
724 | +# published by the Free Software Foundation, either version 3 of the |
725 | +# License, or (at your option) any later version. |
726 | +# |
727 | +# This program is distributed in the hope that it will be useful, |
728 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
729 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
730 | +# GNU Affero General Public License for more details. |
731 | +# |
732 | +# You should have received a copy of the GNU Affero General Public License |
733 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
734 | +# |
735 | +############################################################################## |
736 | + |
737 | +{ |
738 | + "name": "Easy Reconcile", |
739 | + "version": "1.3.0", |
740 | + "depends": ["account"], |
741 | + "author": "Akretion,Camptocamp", |
742 | + "description": """ |
743 | +Easy Reconcile |
744 | +============== |
745 | + |
746 | +This is a shared work between Akretion and Camptocamp |
747 | +in order to provide: |
748 | + - reconciliation facilities for big volume of transactions |
749 | + - setup different profiles of reconciliation by account |
750 | + - each profile can use many methods of reconciliation |
751 | + - this module is also a base to create others |
752 | + reconciliation methods which can plug in the profiles |
753 | + - a profile a reconciliation can be run manually |
754 | + or by a cron |
755 | + - monitoring of reconciliation runs with an history |
756 | + which keep track of the reconciled Journal items |
757 | + |
758 | +2 simple reconciliation methods are integrated |
759 | +in this module, the simple reconciliations works |
760 | +on 2 lines (1 debit / 1 credit) and do not allow |
761 | +partial reconcilation, they also match on 1 key, |
762 | +partner or Journal item name. |
763 | + |
764 | +You may be interested to install also the |
765 | +``account_advanced_reconciliation`` module. |
766 | +This latter add more complex reconciliations, |
767 | +allows multiple lines and partial. |
768 | + |
769 | +""", |
770 | + "website": "http://www.akretion.com/", |
771 | + "category": "Finance", |
772 | + "demo_xml": [], |
773 | + "data": ["easy_reconcile.xml", |
774 | + "easy_reconcile_history_view.xml", |
775 | + "security/ir_rule.xml", |
776 | + "security/ir.model.access.csv"], |
777 | + 'license': 'AGPL-3', |
778 | + "auto_install": False, |
779 | + "installable": True, |
780 | + |
781 | +} |
782 | |
783 | === added file 'account_easy_reconcile/base_reconciliation.py' |
784 | --- account_easy_reconcile/base_reconciliation.py 1970-01-01 00:00:00 +0000 |
785 | +++ account_easy_reconcile/base_reconciliation.py 2013-07-17 14:55:41 +0000 |
786 | @@ -0,0 +1,208 @@ |
787 | +# -*- coding: utf-8 -*- |
788 | +############################################################################## |
789 | +# |
790 | +# Copyright 2012-2013 Camptocamp SA (Guewen Baconnier) |
791 | +# Copyright (C) 2010 Sébastien Beau |
792 | +# |
793 | +# This program is free software: you can redistribute it and/or modify |
794 | +# it under the terms of the GNU Affero General Public License as |
795 | +# published by the Free Software Foundation, either version 3 of the |
796 | +# License, or (at your option) any later version. |
797 | +# |
798 | +# This program is distributed in the hope that it will be useful, |
799 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
800 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
801 | +# GNU Affero General Public License for more details. |
802 | +# |
803 | +# You should have received a copy of the GNU Affero General Public License |
804 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
805 | +# |
806 | +############################################################################## |
807 | + |
808 | +from openerp.osv import fields, orm |
809 | +from operator import itemgetter, attrgetter |
810 | + |
811 | + |
812 | +class easy_reconcile_base(orm.AbstractModel): |
813 | + """Abstract Model for reconciliation methods""" |
814 | + |
815 | + _name = 'easy.reconcile.base' |
816 | + |
817 | + _inherit = 'easy.reconcile.options' |
818 | + |
819 | + _columns = { |
820 | + 'account_id': fields.many2one( |
821 | + 'account.account', 'Account', required=True), |
822 | + 'partner_ids': fields.many2many( |
823 | + 'res.partner', string="Restrict on partners"), |
824 | + # other columns are inherited from easy.reconcile.options |
825 | + } |
826 | + |
827 | + def automatic_reconcile(self, cr, uid, ids, context=None): |
828 | + """ Reconciliation method called from the view. |
829 | + |
830 | + :return: list of reconciled ids, list of partially reconciled items |
831 | + """ |
832 | + if isinstance(ids, (int, long)): |
833 | + ids = [ids] |
834 | + assert len(ids) == 1, "Has to be called on one id" |
835 | + rec = self.browse(cr, uid, ids[0], context=context) |
836 | + return self._action_rec(cr, uid, rec, context=context) |
837 | + |
838 | + def _action_rec(self, cr, uid, rec, context=None): |
839 | + """ Must be inherited to implement the reconciliation |
840 | + |
841 | + :return: list of reconciled ids |
842 | + """ |
843 | + raise NotImplementedError |
844 | + |
845 | + def _base_columns(self, rec): |
846 | + """ Mandatory columns for move lines queries |
847 | + An extra column aliased as ``key`` should be defined |
848 | + in each query.""" |
849 | + aml_cols = ( |
850 | + 'id', |
851 | + 'debit', |
852 | + 'credit', |
853 | + 'date', |
854 | + 'period_id', |
855 | + 'ref', |
856 | + 'name', |
857 | + 'partner_id', |
858 | + 'account_id', |
859 | + 'move_id') |
860 | + return ["account_move_line.%s" % col for col in aml_cols] |
861 | + |
862 | + def _select(self, rec, *args, **kwargs): |
863 | + return "SELECT %s" % ', '.join(self._base_columns(rec)) |
864 | + |
865 | + def _from(self, rec, *args, **kwargs): |
866 | + return "FROM account_move_line" |
867 | + |
868 | + def _where(self, rec, *args, **kwargs): |
869 | + where = ("WHERE account_move_line.account_id = %s " |
870 | + "AND account_move_line.reconcile_id IS NULL ") |
871 | + # it would be great to use dict for params |
872 | + # but as we use _where_calc in _get_filter |
873 | + # which returns a list, we have to |
874 | + # accomodate with that |
875 | + params = [rec.account_id.id] |
876 | + |
877 | + if rec.partner_ids: |
878 | + where += " AND account_move_line.partner_id IN %s" |
879 | + params.append(tuple([l.id for l in rec.partner_ids])) |
880 | + return where, params |
881 | + |
882 | + def _get_filter(self, cr, uid, rec, context): |
883 | + ml_obj = self.pool.get('account.move.line') |
884 | + where = '' |
885 | + params = [] |
886 | + if rec.filter: |
887 | + dummy, where, params = ml_obj._where_calc( |
888 | + cr, uid, eval(rec.filter), context=context).get_sql() |
889 | + if where: |
890 | + where = " AND %s" % where |
891 | + return where, params |
892 | + |
893 | + def _below_writeoff_limit(self, cr, uid, rec, lines, |
894 | + writeoff_limit, context=None): |
895 | + precision = self.pool.get('decimal.precision').precision_get( |
896 | + cr, uid, 'Account') |
897 | + keys = ('debit', 'credit') |
898 | + sums = reduce( |
899 | + lambda line, memo: |
900 | + dict((key, value + memo[key]) |
901 | + for key, value |
902 | + in line.iteritems() |
903 | + if key in keys), lines) |
904 | + |
905 | + debit, credit = sums['debit'], sums['credit'] |
906 | + writeoff_amount = round(debit - credit, precision) |
907 | + return bool(writeoff_limit >= abs(writeoff_amount)), debit, credit |
908 | + |
909 | + def _get_rec_date(self, cr, uid, rec, lines, |
910 | + based_on='end_period_last_credit', context=None): |
911 | + period_obj = self.pool.get('account.period') |
912 | + |
913 | + def last_period(mlines): |
914 | + period_ids = [ml['period_id'] for ml in mlines] |
915 | + periods = period_obj.browse( |
916 | + cr, uid, period_ids, context=context) |
917 | + return max(periods, key=attrgetter('date_stop')) |
918 | + |
919 | + def last_date(mlines): |
920 | + return max(mlines, key=itemgetter('date')) |
921 | + |
922 | + def credit(mlines): |
923 | + return [l for l in mlines if l['credit'] > 0] |
924 | + |
925 | + def debit(mlines): |
926 | + return [l for l in mlines if l['debit'] > 0] |
927 | + |
928 | + if based_on == 'end_period_last_credit': |
929 | + return last_period(credit(lines)).date_stop |
930 | + if based_on == 'end_period': |
931 | + return last_period(lines).date_stop |
932 | + elif based_on == 'newest': |
933 | + return last_date(lines)['date'] |
934 | + elif based_on == 'newest_credit': |
935 | + return last_date(credit(lines))['date'] |
936 | + elif based_on == 'newest_debit': |
937 | + return last_date(debit(lines))['date'] |
938 | + # reconcilation date will be today |
939 | + # when date is None |
940 | + return None |
941 | + |
942 | + def _reconcile_lines(self, cr, uid, rec, lines, allow_partial=False, context=None): |
943 | + """ Try to reconcile given lines |
944 | + |
945 | + :param list lines: list of dict of move lines, they must at least |
946 | + contain values for : id, debit, credit |
947 | + :param boolean allow_partial: if True, partial reconciliation will be |
948 | + created, otherwise only Full |
949 | + reconciliation will be created |
950 | + :return: tuple of boolean values, first item is wether the items |
951 | + have been reconciled or not, |
952 | + the second is wether the reconciliation is full (True) |
953 | + or partial (False) |
954 | + """ |
955 | + if context is None: |
956 | + context = {} |
957 | + |
958 | + ml_obj = self.pool.get('account.move.line') |
959 | + writeoff = rec.write_off |
960 | + |
961 | + line_ids = [l['id'] for l in lines] |
962 | + below_writeoff, sum_debit, sum_credit = self._below_writeoff_limit( |
963 | + cr, uid, rec, lines, writeoff, context=context) |
964 | + date = self._get_rec_date( |
965 | + cr, uid, rec, lines, rec.date_base_on, context=context) |
966 | + |
967 | + rec_ctx = dict(context, date_p=date) |
968 | + if below_writeoff: |
969 | + if sum_credit < sum_debit: |
970 | + writeoff_account_id = rec.account_profit_id.id |
971 | + else: |
972 | + writeoff_account_id = rec.account_lost_id.id |
973 | + |
974 | + period_id = self.pool.get('account.period').find( |
975 | + cr, uid, dt=date, context=context)[0] |
976 | + |
977 | + ml_obj.reconcile( |
978 | + cr, uid, |
979 | + line_ids, |
980 | + type='auto', |
981 | + writeoff_acc_id=writeoff_account_id, |
982 | + writeoff_period_id=period_id, |
983 | + writeoff_journal_id=rec.journal_id.id, |
984 | + context=rec_ctx) |
985 | + return True, True |
986 | + elif allow_partial: |
987 | + ml_obj.reconcile_partial( |
988 | + cr, uid, |
989 | + line_ids, |
990 | + type='manual', |
991 | + context=rec_ctx) |
992 | + return True, False |
993 | + |
994 | + return False, False |
995 | |
996 | === added file 'account_easy_reconcile/easy_reconcile.py' |
997 | --- account_easy_reconcile/easy_reconcile.py 1970-01-01 00:00:00 +0000 |
998 | +++ account_easy_reconcile/easy_reconcile.py 2013-07-17 14:55:41 +0000 |
999 | @@ -0,0 +1,285 @@ |
1000 | +# -*- coding: utf-8 -*- |
1001 | +############################################################################## |
1002 | +# |
1003 | +# Copyright 2012-2013 Camptocamp SA (Guewen Baconnier) |
1004 | +# Copyright (C) 2010 Sébastien Beau |
1005 | +# |
1006 | +# This program is free software: you can redistribute it and/or modify |
1007 | +# it under the terms of the GNU Affero General Public License as |
1008 | +# published by the Free Software Foundation, either version 3 of the |
1009 | +# License, or (at your option) any later version. |
1010 | +# |
1011 | +# This program is distributed in the hope that it will be useful, |
1012 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1013 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1014 | +# GNU Affero General Public License for more details. |
1015 | +# |
1016 | +# You should have received a copy of the GNU Affero General Public License |
1017 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1018 | +# |
1019 | +############################################################################## |
1020 | + |
1021 | +from openerp.osv import fields, osv, orm |
1022 | +from openerp.tools.translate import _ |
1023 | +from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT |
1024 | + |
1025 | + |
1026 | +class easy_reconcile_options(orm.AbstractModel): |
1027 | + """Options of a reconciliation profile |
1028 | + |
1029 | + Columns shared by the configuration of methods |
1030 | + and by the reconciliation wizards. |
1031 | + This allows decoupling of the methods and the |
1032 | + wizards and allows to launch the wizards alone |
1033 | + """ |
1034 | + |
1035 | + _name = 'easy.reconcile.options' |
1036 | + |
1037 | + def _get_rec_base_date(self, cr, uid, context=None): |
1038 | + return [('end_period_last_credit', 'End of period of most recent credit'), |
1039 | + ('newest', 'Most recent move line'), |
1040 | + ('actual', 'Today'), |
1041 | + ('end_period', 'End of period of most recent move line'), |
1042 | + ('newest_credit', 'Date of most recent credit'), |
1043 | + ('newest_debit', 'Date of most recent debit')] |
1044 | + |
1045 | + _columns = { |
1046 | + 'write_off': fields.float('Write off allowed'), |
1047 | + 'account_lost_id': fields.many2one( |
1048 | + 'account.account', 'Account Lost'), |
1049 | + 'account_profit_id': fields.many2one( |
1050 | + 'account.account', 'Account Profit'), |
1051 | + 'journal_id': fields.many2one( |
1052 | + 'account.journal', 'Journal'), |
1053 | + 'date_base_on': fields.selection( |
1054 | + _get_rec_base_date, |
1055 | + required=True, |
1056 | + string='Date of reconciliation'), |
1057 | + 'filter': fields.char('Filter', size=128), |
1058 | + } |
1059 | + |
1060 | + _defaults = { |
1061 | + 'write_off': 0., |
1062 | + 'date_base_on': 'end_period_last_credit', |
1063 | + } |
1064 | + |
1065 | + |
1066 | +class account_easy_reconcile_method(orm.Model): |
1067 | + |
1068 | + _name = 'account.easy.reconcile.method' |
1069 | + _description = 'reconcile method for account_easy_reconcile' |
1070 | + |
1071 | + _inherit = 'easy.reconcile.options' |
1072 | + |
1073 | + _order = 'sequence' |
1074 | + |
1075 | + def _get_all_rec_method(self, cr, uid, context=None): |
1076 | + return [ |
1077 | + ('easy.reconcile.simple.name', 'Simple. Amount and Name'), |
1078 | + ('easy.reconcile.simple.partner', 'Simple. Amount and Partner'), |
1079 | + ('easy.reconcile.simple.reference', 'Simple. Amount and Reference'), |
1080 | + ] |
1081 | + |
1082 | + def _get_rec_method(self, cr, uid, context=None): |
1083 | + return self._get_all_rec_method(cr, uid, context=None) |
1084 | + |
1085 | + _columns = { |
1086 | + 'name': fields.selection( |
1087 | + _get_rec_method, 'Type', required=True), |
1088 | + 'sequence': fields.integer( |
1089 | + 'Sequence', |
1090 | + required=True, |
1091 | + help="The sequence field is used to order " |
1092 | + "the reconcile method"), |
1093 | + 'task_id': fields.many2one( |
1094 | + 'account.easy.reconcile', |
1095 | + string='Task', |
1096 | + required=True, |
1097 | + ondelete='cascade'), |
1098 | + 'company_id': fields.related('task_id','company_id', |
1099 | + relation='res.company', |
1100 | + type='many2one', |
1101 | + string='Company', |
1102 | + store=True, |
1103 | + readonly=True), |
1104 | + } |
1105 | + |
1106 | + _defaults = { |
1107 | + 'sequence': 1, |
1108 | + } |
1109 | + |
1110 | + def init(self, cr): |
1111 | + """ Migration stuff |
1112 | + |
1113 | + Name is not anymore methods names but the name |
1114 | + of the model which does the reconciliation |
1115 | + """ |
1116 | + cr.execute(""" |
1117 | + UPDATE account_easy_reconcile_method |
1118 | + SET name = 'easy.reconcile.simple.partner' |
1119 | + WHERE name = 'action_rec_auto_partner' |
1120 | + """) |
1121 | + cr.execute(""" |
1122 | + UPDATE account_easy_reconcile_method |
1123 | + SET name = 'easy.reconcile.simple.name' |
1124 | + WHERE name = 'action_rec_auto_name' |
1125 | + """) |
1126 | + |
1127 | + |
1128 | +class account_easy_reconcile(orm.Model): |
1129 | + |
1130 | + _name = 'account.easy.reconcile' |
1131 | + _description = 'account easy reconcile' |
1132 | + |
1133 | + def _get_total_unrec(self, cr, uid, ids, name, arg, context=None): |
1134 | + obj_move_line = self.pool.get('account.move.line') |
1135 | + res = {} |
1136 | + for task in self.browse(cr, uid, ids, context=context): |
1137 | + res[task.id] = len(obj_move_line.search( |
1138 | + cr, uid, |
1139 | + [('account_id', '=', task.account.id), |
1140 | + ('reconcile_id', '=', False), |
1141 | + ('reconcile_partial_id', '=', False)], |
1142 | + context=context)) |
1143 | + return res |
1144 | + |
1145 | + def _get_partial_rec(self, cr, uid, ids, name, arg, context=None): |
1146 | + obj_move_line = self.pool.get('account.move.line') |
1147 | + res = {} |
1148 | + for task in self.browse(cr, uid, ids, context=context): |
1149 | + res[task.id] = len(obj_move_line.search( |
1150 | + cr, uid, |
1151 | + [('account_id', '=', task.account.id), |
1152 | + ('reconcile_id', '=', False), |
1153 | + ('reconcile_partial_id', '!=', False)], |
1154 | + context=context)) |
1155 | + return res |
1156 | + |
1157 | + def _last_history(self, cr, uid, ids, name, args, context=None): |
1158 | + result = {} |
1159 | + for history in self.browse(cr, uid, ids, context=context): |
1160 | + result[history.id] = False |
1161 | + if history.history_ids: |
1162 | + # history is sorted by date desc |
1163 | + result[history.id] = history.history_ids[0].id |
1164 | + return result |
1165 | + |
1166 | + _columns = { |
1167 | + 'name': fields.char('Name', required=True), |
1168 | + 'account': fields.many2one( |
1169 | + 'account.account', 'Account', required=True), |
1170 | + 'reconcile_method': fields.one2many( |
1171 | + 'account.easy.reconcile.method', 'task_id', 'Method'), |
1172 | + 'unreconciled_count': fields.function( |
1173 | + _get_total_unrec, type='integer', string='Unreconciled Items'), |
1174 | + 'reconciled_partial_count': fields.function( |
1175 | + _get_partial_rec, |
1176 | + type='integer', |
1177 | + string='Partially Reconciled Items'), |
1178 | + 'history_ids': fields.one2many( |
1179 | + 'easy.reconcile.history', |
1180 | + 'easy_reconcile_id', |
1181 | + string='History', |
1182 | + readonly=True), |
1183 | + 'last_history': |
1184 | + fields.function( |
1185 | + _last_history, |
1186 | + string='Last History', |
1187 | + type='many2one', |
1188 | + relation='easy.reconcile.history', |
1189 | + readonly=True), |
1190 | + 'company_id': fields.many2one('res.company', 'Company'), |
1191 | + } |
1192 | + |
1193 | + def _prepare_run_transient(self, cr, uid, rec_method, context=None): |
1194 | + return {'account_id': rec_method.task_id.account.id, |
1195 | + 'write_off': rec_method.write_off, |
1196 | + 'account_lost_id': (rec_method.account_lost_id and |
1197 | + rec_method.account_lost_id.id), |
1198 | + 'account_profit_id': (rec_method.account_profit_id and |
1199 | + rec_method.account_profit_id.id), |
1200 | + 'journal_id': (rec_method.journal_id and |
1201 | + rec_method.journal_id.id), |
1202 | + 'date_base_on': rec_method.date_base_on, |
1203 | + 'filter': rec_method.filter} |
1204 | + |
1205 | + def run_reconcile(self, cr, uid, ids, context=None): |
1206 | + def find_reconcile_ids(fieldname, move_line_ids): |
1207 | + if not move_line_ids: |
1208 | + return [] |
1209 | + sql = ("SELECT DISTINCT " + fieldname + |
1210 | + " FROM account_move_line " |
1211 | + " WHERE id in %s " |
1212 | + " AND " + fieldname + " IS NOT NULL") |
1213 | + cr.execute(sql, (tuple(move_line_ids),)) |
1214 | + res = cr.fetchall() |
1215 | + return [row[0] for row in res] |
1216 | + |
1217 | + for rec in self.browse(cr, uid, ids, context=context): |
1218 | + all_ml_rec_ids = [] |
1219 | + all_ml_partial_ids = [] |
1220 | + |
1221 | + for method in rec.reconcile_method: |
1222 | + rec_model = self.pool.get(method.name) |
1223 | + auto_rec_id = rec_model.create( |
1224 | + cr, uid, |
1225 | + self._prepare_run_transient( |
1226 | + cr, uid, method, context=context), |
1227 | + context=context) |
1228 | + |
1229 | + ml_rec_ids, ml_partial_ids = rec_model.automatic_reconcile( |
1230 | + cr, uid, auto_rec_id, context=context) |
1231 | + |
1232 | + all_ml_rec_ids += ml_rec_ids |
1233 | + all_ml_partial_ids += ml_partial_ids |
1234 | + |
1235 | + reconcile_ids = find_reconcile_ids( |
1236 | + 'reconcile_id', all_ml_rec_ids) |
1237 | + partial_ids = find_reconcile_ids( |
1238 | + 'reconcile_partial_id', all_ml_partial_ids) |
1239 | + |
1240 | + self.pool.get('easy.reconcile.history').create( |
1241 | + cr, |
1242 | + uid, |
1243 | + {'easy_reconcile_id': rec.id, |
1244 | + 'date': fields.datetime.now(), |
1245 | + 'reconcile_ids': [(4, rid) for rid in reconcile_ids], |
1246 | + 'reconcile_partial_ids': [(4, rid) for rid in partial_ids]}, |
1247 | + context=context) |
1248 | + return True |
1249 | + |
1250 | + def _no_history(self, cr, uid, rec, context=None): |
1251 | + """ Raise an `osv.except_osv` error, supposed to |
1252 | + be called when there is no history on the reconciliation |
1253 | + task. |
1254 | + """ |
1255 | + raise osv.except_osv( |
1256 | + _('Error'), |
1257 | + _('There is no history of reconciled ' |
1258 | + 'items on the task: %s.') % rec.name) |
1259 | + |
1260 | + def last_history_reconcile(self, cr, uid, rec_id, context=None): |
1261 | + """ Get the last history record for this reconciliation profile |
1262 | + and return the action which opens move lines reconciled |
1263 | + """ |
1264 | + if isinstance(rec_id, (tuple, list)): |
1265 | + assert len(rec_id) == 1, \ |
1266 | + "Only 1 id expected" |
1267 | + rec_id = rec_id[0] |
1268 | + rec = self.browse(cr, uid, rec_id, context=context) |
1269 | + if not rec.last_history: |
1270 | + self._no_history(cr, uid, rec, context=context) |
1271 | + return rec.last_history.open_reconcile() |
1272 | + |
1273 | + def last_history_partial(self, cr, uid, rec_id, context=None): |
1274 | + """ Get the last history record for this reconciliation profile |
1275 | + and return the action which opens move lines reconciled |
1276 | + """ |
1277 | + if isinstance(rec_id, (tuple, list)): |
1278 | + assert len(rec_id) == 1, \ |
1279 | + "Only 1 id expected" |
1280 | + rec_id = rec_id[0] |
1281 | + rec = self.browse(cr, uid, rec_id, context=context) |
1282 | + if not rec.last_history: |
1283 | + self._no_history(cr, uid, rec, context=context) |
1284 | + return rec.last_history.open_partial() |
1285 | |
1286 | === added file 'account_easy_reconcile/easy_reconcile.xml' |
1287 | --- account_easy_reconcile/easy_reconcile.xml 1970-01-01 00:00:00 +0000 |
1288 | +++ account_easy_reconcile/easy_reconcile.xml 2013-07-17 14:55:41 +0000 |
1289 | @@ -0,0 +1,156 @@ |
1290 | +<?xml version="1.0" encoding="UTF-8"?> |
1291 | +<openerp> |
1292 | +<data> |
1293 | + |
1294 | + <!-- account.easy.reconcile view --> |
1295 | + <record id="account_easy_reconcile_form" model="ir.ui.view"> |
1296 | + <field name="name">account.easy.reconcile.form</field> |
1297 | + <field name="priority">20</field> |
1298 | + <field name="model">account.easy.reconcile</field> |
1299 | + <field name="arch" type="xml"> |
1300 | + <form string="Automatic Easy Reconcile" version="7.0"> |
1301 | + <header> |
1302 | + <button name="run_reconcile" class="oe_highlight" |
1303 | + string="Start Auto Reconciliation" type="object"/> |
1304 | + <button icon="STOCK_JUMP_TO" name="last_history_reconcile" |
1305 | + class="oe_highlight" |
1306 | + string="Display items reconciled on the last run" |
1307 | + type="object"/> |
1308 | + <button icon="STOCK_JUMP_TO" name="last_history_partial" |
1309 | + class="oe_highlight" |
1310 | + string="Display items partially reconciled on the last run" |
1311 | + type="object"/> |
1312 | + </header> |
1313 | + <sheet> |
1314 | + <separator colspan="4" string="Profile Information" /> |
1315 | + <group> |
1316 | + <group> |
1317 | + <field name="name" select="1"/> |
1318 | + <field name="account"/> |
1319 | + <field name="company_id" groups="base.group_multi_company"/> |
1320 | + </group> |
1321 | + <group> |
1322 | + <field name="unreconciled_count"/> |
1323 | + <field name="reconciled_partial_count"/> |
1324 | + </group> |
1325 | + </group> |
1326 | + <notebook colspan="4"> |
1327 | + <page name="methods" string="Configuration"> |
1328 | + <field name="reconcile_method" colspan = "4" nolabel="1"/> |
1329 | + </page> |
1330 | + <page name="history" string="History"> |
1331 | + <field name="history_ids" nolabel="1"> |
1332 | + <tree string="Automatic Easy Reconcile History"> |
1333 | + <field name="date"/> |
1334 | + <button icon="STOCK_JUMP_TO" name="open_reconcile" |
1335 | + string="Go to reconciled items" type="object"/> |
1336 | + <button icon="STOCK_JUMP_TO" name="open_partial" |
1337 | + string="Go to partially reconciled items" type="object"/> |
1338 | + </tree> |
1339 | + </field> |
1340 | + </page> |
1341 | + <page name="information" string="Information"> |
1342 | + <separator colspan="4" string="Simple. Amount and Name"/> |
1343 | + <label string="Match one debit line vs one credit line. Do not allow partial reconciliation. |
1344 | +The lines should have the same amount (with the write-off) and the same name to be reconciled." colspan="4"/> |
1345 | + |
1346 | + <separator colspan="4" string="Simple. Amount and Partner"/> |
1347 | + <label string="Match one debit line vs one credit line. Do not allow partial reconciliation. |
1348 | +The lines should have the same amount (with the write-off) and the same partner to be reconciled." colspan="4"/> |
1349 | + |
1350 | + <separator colspan="4" string="Simple. Amount and Reference"/> |
1351 | + <label string="Match one debit line vs one credit line. Do not allow partial reconciliation. |
1352 | +The lines should have the same amount (with the write-off) and the same reference to be reconciled." colspan="4"/> |
1353 | + |
1354 | + </page> |
1355 | + </notebook> |
1356 | + </sheet> |
1357 | + </form> |
1358 | + </field> |
1359 | + </record> |
1360 | + |
1361 | + <record id="account_easy_reconcile_tree" model="ir.ui.view"> |
1362 | + <field name="name">account.easy.reconcile.tree</field> |
1363 | + <field name="priority">20</field> |
1364 | + <field name="model">account.easy.reconcile</field> |
1365 | + <field name="arch" type="xml"> |
1366 | + <tree string="Automatic Easy Reconcile"> |
1367 | + <field name="name"/> |
1368 | + <field name="account"/> |
1369 | + <field name="company_id" groups="base.group_multi_company"/> |
1370 | + <field name="unreconciled_count"/> |
1371 | + <field name="reconciled_partial_count"/> |
1372 | + <button icon="gtk-ok" name="run_reconcile" colspan="4" |
1373 | + string="Start Auto Reconcilation" type="object"/> |
1374 | + <button icon="STOCK_JUMP_TO" name="last_history_reconcile" colspan="2" |
1375 | + string="Display items reconciled on the last run" type="object"/> |
1376 | + <button icon="STOCK_JUMP_TO" name="last_history_partial" colspan="2" |
1377 | + string="Display items partially reconciled on the last run" |
1378 | + type="object"/> |
1379 | + </tree> |
1380 | + </field> |
1381 | + </record> |
1382 | + |
1383 | + <record id="action_account_easy_reconcile" model="ir.actions.act_window"> |
1384 | + <field name="name">Easy Automatic Reconcile</field> |
1385 | + <field name="type">ir.actions.act_window</field> |
1386 | + <field name="res_model">account.easy.reconcile</field> |
1387 | + <field name="view_type">form</field> |
1388 | + <field name="view_mode">tree,form</field> |
1389 | + <field name="help" type="html"> |
1390 | + <p class="oe_view_nocontent_create"> |
1391 | + Click to add a reconciliation profile. |
1392 | + </p><p> |
1393 | + A reconciliation profile specifies, for one account, how |
1394 | + the entries should be reconciled. |
1395 | + You can select one or many reconciliation methods which will |
1396 | + be run sequentially to match the entries between them. |
1397 | + </p> |
1398 | + </field> |
1399 | + </record> |
1400 | + |
1401 | + |
1402 | + <!-- account.easy.reconcile.method view --> |
1403 | + |
1404 | + <record id="account_easy_reconcile_method_form" model="ir.ui.view"> |
1405 | + <field name="name">account.easy.reconcile.method.form</field> |
1406 | + <field name="priority">20</field> |
1407 | + <field name="model">account.easy.reconcile.method</field> |
1408 | + <field name="arch" type="xml"> |
1409 | + <form string="Automatic Easy Reconcile Method"> |
1410 | + <field name="sequence"/> |
1411 | + <field name="name"/> |
1412 | + <field name="write_off"/> |
1413 | + <field name="account_lost_id" attrs="{'required':[('write_off','>',0)]}"/> |
1414 | + <field name="account_profit_id" attrs="{'required':[('write_off','>',0)]}"/> |
1415 | + <field name="journal_id" attrs="{'required':[('write_off','>',0)]}"/> |
1416 | + <field name="date_base_on"/> |
1417 | + </form> |
1418 | + </field> |
1419 | + </record> |
1420 | + |
1421 | + <record id="account_easy_reconcile_method_tree" model="ir.ui.view"> |
1422 | + <field name="name">account.easy.reconcile.method.tree</field> |
1423 | + <field name="priority">20</field> |
1424 | + <field name="model">account.easy.reconcile.method</field> |
1425 | + <field name="arch" type="xml"> |
1426 | + <tree editable="top" string="Automatic Easy Reconcile Method"> |
1427 | + <field name="sequence"/> |
1428 | + <field name="name"/> |
1429 | + <field name="write_off"/> |
1430 | + <field name="account_lost_id" attrs="{'required':[('write_off','>',0)]}"/> |
1431 | + <field name="account_profit_id" attrs="{'required':[('write_off','>',0)]}"/> |
1432 | + <field name="journal_id" attrs="{'required':[('write_off','>',0)]}"/> |
1433 | + <field name="date_base_on"/> |
1434 | + </tree> |
1435 | + </field> |
1436 | + </record> |
1437 | + |
1438 | + <!-- menu item --> |
1439 | + |
1440 | + <menuitem action="action_account_easy_reconcile" |
1441 | + id="menu_easy_reconcile" |
1442 | + parent="account.periodical_processing_reconciliation"/> |
1443 | + |
1444 | +</data> |
1445 | +</openerp> |
1446 | |
1447 | === added file 'account_easy_reconcile/easy_reconcile_history.py' |
1448 | --- account_easy_reconcile/easy_reconcile_history.py 1970-01-01 00:00:00 +0000 |
1449 | +++ account_easy_reconcile/easy_reconcile_history.py 2013-07-17 14:55:41 +0000 |
1450 | @@ -0,0 +1,153 @@ |
1451 | +# -*- coding: utf-8 -*- |
1452 | +############################################################################## |
1453 | +# |
1454 | +# Author: Guewen Baconnier |
1455 | +# Copyright 2012 Camptocamp SA |
1456 | +# |
1457 | +# This program is free software: you can redistribute it and/or modify |
1458 | +# it under the terms of the GNU Affero General Public License as |
1459 | +# published by the Free Software Foundation, either version 3 of the |
1460 | +# License, or (at your option) any later version. |
1461 | +# |
1462 | +# This program is distributed in the hope that it will be useful, |
1463 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1464 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1465 | +# GNU Affero General Public License for more details. |
1466 | +# |
1467 | +# You should have received a copy of the GNU Affero General Public License |
1468 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1469 | +# |
1470 | +############################################################################## |
1471 | + |
1472 | +from openerp.osv import orm, fields |
1473 | +from openerp.tools.translate import _ |
1474 | + |
1475 | + |
1476 | +class easy_reconcile_history(orm.Model): |
1477 | + """ Store an history of the runs per profile |
1478 | + Each history stores the list of reconciliations done""" |
1479 | + |
1480 | + _name = 'easy.reconcile.history' |
1481 | + _rec_name = 'easy_reconcile_id' |
1482 | + _order = 'date DESC' |
1483 | + |
1484 | + def _reconcile_line_ids(self, cr, uid, ids, name, args, context=None): |
1485 | + result = {} |
1486 | + |
1487 | + for history in self.browse(cr, uid, ids, context=context): |
1488 | + result[history.id] = {} |
1489 | + |
1490 | + move_line_ids = [] |
1491 | + for reconcile in history.reconcile_ids: |
1492 | + move_line_ids += [line.id |
1493 | + for line |
1494 | + in reconcile.line_id] |
1495 | + result[history.id]['reconcile_line_ids'] = move_line_ids |
1496 | + |
1497 | + move_line_ids = [] |
1498 | + for reconcile in history.reconcile_partial_ids: |
1499 | + move_line_ids += [line.id |
1500 | + for line |
1501 | + in reconcile.line_partial_ids] |
1502 | + result[history.id]['partial_line_ids'] = move_line_ids |
1503 | + |
1504 | + return result |
1505 | + |
1506 | + _columns = { |
1507 | + 'easy_reconcile_id': fields.many2one( |
1508 | + 'account.easy.reconcile', 'Reconcile Profile', readonly=True), |
1509 | + 'date': fields.datetime('Run date', readonly=True), |
1510 | + 'reconcile_ids': fields.many2many( |
1511 | + 'account.move.reconcile', |
1512 | + 'account_move_reconcile_history_rel', |
1513 | + string='Reconciliations', readonly=True), |
1514 | + 'reconcile_partial_ids': fields.many2many( |
1515 | + 'account.move.reconcile', |
1516 | + 'account_move_reconcile_history_partial_rel', |
1517 | + string='Partial Reconciliations', readonly=True), |
1518 | + 'reconcile_line_ids': |
1519 | + fields.function( |
1520 | + _reconcile_line_ids, |
1521 | + string='Reconciled Items', |
1522 | + type='many2many', |
1523 | + relation='account.move.line', |
1524 | + readonly=True, |
1525 | + multi='lines'), |
1526 | + 'partial_line_ids': |
1527 | + fields.function( |
1528 | + _reconcile_line_ids, |
1529 | + string='Partially Reconciled Items', |
1530 | + type='many2many', |
1531 | + relation='account.move.line', |
1532 | + readonly=True, |
1533 | + multi='lines'), |
1534 | + 'company_id': fields.related('easy_reconcile_id','company_id', |
1535 | + relation='res.company', |
1536 | + type='many2one', |
1537 | + string='Company', |
1538 | + store=True, |
1539 | + readonly=True), |
1540 | + |
1541 | + } |
1542 | + |
1543 | + def _open_move_lines(self, cr, uid, history_id, rec_type='full', context=None): |
1544 | + """ For an history record, open the view of move line with |
1545 | + the reconciled or partially reconciled move lines |
1546 | + |
1547 | + :param history_id: id of the history |
1548 | + :param rec_type: 'full' or 'partial' |
1549 | + :return: action to open the move lines |
1550 | + """ |
1551 | + assert rec_type in ('full', 'partial'), \ |
1552 | + "rec_type must be 'full' or 'partial'" |
1553 | + |
1554 | + history = self.browse(cr, uid, history_id, context=context) |
1555 | + |
1556 | + if rec_type == 'full': |
1557 | + field = 'reconcile_line_ids' |
1558 | + name = _('Reconciliations') |
1559 | + else: |
1560 | + field = 'partial_line_ids' |
1561 | + name = _('Partial Reconciliations') |
1562 | + |
1563 | + move_line_ids = [line.id for line in getattr(history, field)] |
1564 | + |
1565 | + return { |
1566 | + 'name': name, |
1567 | + 'view_mode': 'tree,form', |
1568 | + 'view_id': False, |
1569 | + 'view_type': 'form', |
1570 | + 'res_model': 'account.move.line', |
1571 | + 'type': 'ir.actions.act_window', |
1572 | + 'nodestroy': True, |
1573 | + 'target': 'current', |
1574 | + 'domain': unicode([('id', 'in', move_line_ids)]), |
1575 | + } |
1576 | + |
1577 | + def open_reconcile(self, cr, uid, history_ids, context=None): |
1578 | + """ For an history record, open the view of move line |
1579 | + with the reconciled move lines |
1580 | + |
1581 | + :param history_ids: id of the record as int or long |
1582 | + Accept a list with 1 id too to be |
1583 | + used from the client. |
1584 | + """ |
1585 | + if isinstance(history_ids, (tuple, list)): |
1586 | + assert len(history_ids) == 1, "only 1 ID is accepted" |
1587 | + history_ids = history_ids[0] |
1588 | + return self._open_move_lines( |
1589 | + cr, uid, history_ids, rec_type='full', context=None) |
1590 | + |
1591 | + def open_partial(self, cr, uid, history_ids, context=None): |
1592 | + """ For an history record, open the view of move line |
1593 | + with the partially reconciled move lines |
1594 | + |
1595 | + :param history_ids: id of the record as int or long |
1596 | + Accept a list with 1 id too to be |
1597 | + used from the client. |
1598 | + """ |
1599 | + if isinstance(history_ids, (tuple, list)): |
1600 | + assert len(history_ids) == 1, "only 1 ID is accepted" |
1601 | + history_ids = history_ids[0] |
1602 | + return self._open_move_lines( |
1603 | + cr, uid, history_ids, rec_type='partial', context=None) |
1604 | |
1605 | === added file 'account_easy_reconcile/easy_reconcile_history_view.xml' |
1606 | --- account_easy_reconcile/easy_reconcile_history_view.xml 1970-01-01 00:00:00 +0000 |
1607 | +++ account_easy_reconcile/easy_reconcile_history_view.xml 2013-07-17 14:55:41 +0000 |
1608 | @@ -0,0 +1,98 @@ |
1609 | +<?xml version="1.0" encoding="utf-8"?> |
1610 | +<openerp> |
1611 | + <data noupdate="0"> |
1612 | + |
1613 | + <record id="view_easy_reconcile_history_search" model="ir.ui.view"> |
1614 | + <field name="name">easy.reconcile.history.search</field> |
1615 | + <field name="model">easy.reconcile.history</field> |
1616 | + <field name="arch" type="xml"> |
1617 | + <search string="Automatic Easy Reconcile History"> |
1618 | + <filter icon="terp-go-today" string="Today" |
1619 | + domain="[('date','<', time.strftime('%%Y-%%m-%%d 23:59:59')), ('date','>=', time.strftime('%%Y-%%m-%%d 00:00:00'))]" |
1620 | + help="Todays' Reconcilations" /> |
1621 | + <filter icon="terp-go-week" string="7 Days" |
1622 | + help="Reconciliations of last 7 days" |
1623 | + domain="[('date','<', time.strftime('%%Y-%%m-%%d 23:59:59')),('date','>=',(datetime.date.today()-datetime.timedelta(days=7)).strftime('%%Y-%%m-%%d 00:00:00'))]" |
1624 | + /> |
1625 | + |
1626 | + <separator orientation="vertical"/> |
1627 | + <field name="easy_reconcile_id"/> |
1628 | + <field name="date"/> |
1629 | + |
1630 | + <newline/> |
1631 | + <group expand="0" string="Group By..."> |
1632 | + <filter string="Reconciliation Profile" |
1633 | + icon="terp-stock_effects-object-colorize" |
1634 | + domain="[]" context="{'group_by': 'easy_reconcile_id'}"/> |
1635 | + <filter string="Date" icon="terp-go-month" domain="[]" |
1636 | + context="{'group_by': 'date'}"/> |
1637 | + </group> |
1638 | + </search> |
1639 | + </field> |
1640 | + </record> |
1641 | + |
1642 | + <record id="easy_reconcile_history_form" model="ir.ui.view"> |
1643 | + <field name="name">easy.reconcile.history.form</field> |
1644 | + <field name="model">easy.reconcile.history</field> |
1645 | + <field name="arch" type="xml"> |
1646 | + <form string="Automatic Easy Reconcile History" version="7.0"> |
1647 | + <header> |
1648 | + <button name="open_reconcile" |
1649 | + string="Go to reconciled items" |
1650 | + icon="STOCK_JUMP_TO" type="object"/> |
1651 | + <button name="open_partial" |
1652 | + string="Go to partially reconciled items" |
1653 | + icon="STOCK_JUMP_TO" type="object"/> |
1654 | + </header> |
1655 | + <sheet> |
1656 | + <group> |
1657 | + <field name="easy_reconcile_id"/> |
1658 | + <field name="date"/> |
1659 | + <field name="company_id" groups="base.group_multi_company"/> |
1660 | + </group> |
1661 | + <group col="2"> |
1662 | + <separator colspan="2" string="Reconciliations"/> |
1663 | + <field name="reconcile_ids" nolabel="1"/> |
1664 | + </group> |
1665 | + <group col="2"> |
1666 | + <separator colspan="2" string="Partial Reconciliations"/> |
1667 | + <field name="reconcile_partial_ids" nolabel="1"/> |
1668 | + </group> |
1669 | + </sheet> |
1670 | + </form> |
1671 | + </field> |
1672 | + </record> |
1673 | + |
1674 | + <record id="easy_reconcile_history_tree" model="ir.ui.view"> |
1675 | + <field name="name">easy.reconcile.history.tree</field> |
1676 | + <field name="model">easy.reconcile.history</field> |
1677 | + <field name="arch" type="xml"> |
1678 | + <tree string="Automatic Easy Reconcile History"> |
1679 | + <field name="easy_reconcile_id"/> |
1680 | + <field name="date"/> |
1681 | + <button icon="STOCK_JUMP_TO" name="open_reconcile" |
1682 | + string="Go to reconciled items" type="object"/> |
1683 | + <button icon="STOCK_JUMP_TO" name="open_partial" |
1684 | + string="Go to partially reconciled items" type="object"/> |
1685 | + </tree> |
1686 | + </field> |
1687 | + </record> |
1688 | + |
1689 | + <record id="action_easy_reconcile_history" model="ir.actions.act_window"> |
1690 | + <field name="name">Easy Automatic Reconcile History</field> |
1691 | + <field name="type">ir.actions.act_window</field> |
1692 | + <field name="res_model">easy.reconcile.history</field> |
1693 | + <field name="view_type">form</field> |
1694 | + <field name="view_mode">tree,form</field> |
1695 | + </record> |
1696 | + |
1697 | + <act_window |
1698 | + context="{'search_default_easy_reconcile_id': [active_id], 'default_easy_reconcile_id': active_id}" |
1699 | + id="act_easy_reconcile_to_history" |
1700 | + name="History Details" |
1701 | + groups="" |
1702 | + res_model="easy.reconcile.history" |
1703 | + src_model="account.easy.reconcile"/> |
1704 | + |
1705 | + </data> |
1706 | +</openerp> |
1707 | |
1708 | === added directory 'account_easy_reconcile/i18n' |
1709 | === added file 'account_easy_reconcile/i18n/fr.po' |
1710 | --- account_easy_reconcile/i18n/fr.po 1970-01-01 00:00:00 +0000 |
1711 | +++ account_easy_reconcile/i18n/fr.po 2013-07-17 14:55:41 +0000 |
1712 | @@ -0,0 +1,425 @@ |
1713 | +# Translation of OpenERP Server. |
1714 | +# This file contains the translation of the following modules: |
1715 | +# * account_easy_reconcile |
1716 | +# |
1717 | +msgid "" |
1718 | +msgstr "" |
1719 | +"Project-Id-Version: OpenERP Server 6.1\n" |
1720 | +"Report-Msgid-Bugs-To: \n" |
1721 | +"POT-Creation-Date: 2013-01-04 08:39+0000\n" |
1722 | +"PO-Revision-Date: 2013-01-04 09:55+0100\n" |
1723 | +"Last-Translator: Guewen Baconnier <guewen.baconnier@camptocamp.com>\n" |
1724 | +"Language-Team: \n" |
1725 | +"Language: \n" |
1726 | +"MIME-Version: 1.0\n" |
1727 | +"Content-Type: text/plain; charset=UTF-8\n" |
1728 | +"Content-Transfer-Encoding: 8bit\n" |
1729 | +"Plural-Forms: \n" |
1730 | + |
1731 | +#. module: account_easy_reconcile |
1732 | +#: code:addons/account_easy_reconcile/easy_reconcile_history.py:101 |
1733 | +#: view:easy.reconcile.history:0 |
1734 | +#: field:easy.reconcile.history,reconcile_ids:0 |
1735 | +#, python-format |
1736 | +msgid "Reconciliations" |
1737 | +msgstr "Lettrages" |
1738 | + |
1739 | +#. module: account_easy_reconcile |
1740 | +#: view:account.easy.reconcile:0 |
1741 | +#: view:easy.reconcile.history:0 |
1742 | +msgid "Automatic Easy Reconcile History" |
1743 | +msgstr "Historique des lettrages automatisés" |
1744 | + |
1745 | +#. module: account_easy_reconcile |
1746 | +#: view:account.easy.reconcile:0 |
1747 | +msgid "Information" |
1748 | +msgstr "Information" |
1749 | + |
1750 | +#. module: account_easy_reconcile |
1751 | +#: view:account.easy.reconcile:0 |
1752 | +#: view:easy.reconcile.history:0 |
1753 | +msgid "Go to partially reconciled items" |
1754 | +msgstr "Voir les entrées partiellement lettrées" |
1755 | + |
1756 | +#. module: account_easy_reconcile |
1757 | +#: help:account.easy.reconcile.method,sequence:0 |
1758 | +msgid "The sequence field is used to order the reconcile method" |
1759 | +msgstr "La séquence détermine l'ordre des méthodes de lettrage" |
1760 | + |
1761 | +#. module: account_easy_reconcile |
1762 | +#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_history |
1763 | +msgid "easy.reconcile.history" |
1764 | +msgstr "easy.reconcile.history" |
1765 | + |
1766 | +#. module: account_easy_reconcile |
1767 | +#: model:ir.actions.act_window,help:account_easy_reconcile.action_account_easy_reconcile |
1768 | +msgid "" |
1769 | +"<p class=\"oe_view_nocontent_create\">\n" |
1770 | +" Click to add a reconciliation profile.\n" |
1771 | +" </p><p>\n" |
1772 | +" A reconciliation profile specifies, for one account, how\n" |
1773 | +" the entries should be reconciled.\n" |
1774 | +" You can select one or many reconciliation methods which will\n" |
1775 | +" be run sequentially to match the entries between them.\n" |
1776 | +" </p>\n" |
1777 | +" " |
1778 | +msgstr "" |
1779 | +"<p class=\"oe_view_nocontent_create\">\n" |
1780 | +" Cliquez pour ajouter un profil de lettrage.\n" |
1781 | +" </p><p>\n" |
1782 | +" Un profil de lettrage spécifie, pour un compte, comment\n" |
1783 | +" les écritures doivent être lettrées.\n" |
1784 | +" Vous pouvez sélectionner une ou plusieurs méthodes de lettrage\n" |
1785 | +" qui seront lancées successivement pour identifier les écritures\n" |
1786 | +" devant être lettrées. </p>\n" |
1787 | +" " |
1788 | + |
1789 | +#. module: account_easy_reconcile |
1790 | +#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_options |
1791 | +msgid "easy.reconcile.options" |
1792 | +msgstr "easy.reconcile.options" |
1793 | + |
1794 | +#. module: account_easy_reconcile |
1795 | +#: view:easy.reconcile.history:0 |
1796 | +msgid "Group By..." |
1797 | +msgstr "Grouper par..." |
1798 | + |
1799 | +#. module: account_easy_reconcile |
1800 | +#: field:account.easy.reconcile,unreconciled_count:0 |
1801 | +msgid "Unreconciled Items" |
1802 | +msgstr "Écritures non lettrées" |
1803 | + |
1804 | +#. module: account_easy_reconcile |
1805 | +#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_base |
1806 | +msgid "easy.reconcile.base" |
1807 | +msgstr "easy.reconcile.base" |
1808 | + |
1809 | +#. module: account_easy_reconcile |
1810 | +#: field:easy.reconcile.history,reconcile_line_ids:0 |
1811 | +msgid "Reconciled Items" |
1812 | +msgstr "Écritures lettrées" |
1813 | + |
1814 | +#. module: account_easy_reconcile |
1815 | +#: field:account.easy.reconcile,reconcile_method:0 |
1816 | +msgid "Method" |
1817 | +msgstr "Méthode" |
1818 | + |
1819 | +#. module: account_easy_reconcile |
1820 | +#: view:easy.reconcile.history:0 |
1821 | +msgid "7 Days" |
1822 | +msgstr "7 jours" |
1823 | + |
1824 | +#. module: account_easy_reconcile |
1825 | +#: model:ir.actions.act_window,name:account_easy_reconcile.action_easy_reconcile_history |
1826 | +msgid "Easy Automatic Reconcile History" |
1827 | +msgstr "Lettrage automatisé" |
1828 | + |
1829 | +#. module: account_easy_reconcile |
1830 | +#: field:easy.reconcile.history,date:0 |
1831 | +msgid "Run date" |
1832 | +msgstr "Date de lancement" |
1833 | + |
1834 | +#. module: account_easy_reconcile |
1835 | +#: view:account.easy.reconcile:0 |
1836 | +msgid "Match one debit line vs one credit line. Do not allow partial reconciliation. The lines should have the same amount (with the write-off) and the same reference to be reconciled." |
1837 | +msgstr "Lettre un débit avec un crédit ayant le même montant et la même référence. Le lettrage ne peut être partiel (écriture d'ajustement en cas d'écart)." |
1838 | + |
1839 | +#. module: account_easy_reconcile |
1840 | +#: model:ir.actions.act_window,name:account_easy_reconcile.act_easy_reconcile_to_history |
1841 | +msgid "History Details" |
1842 | +msgstr "Détails de l'historique" |
1843 | + |
1844 | +#. module: account_easy_reconcile |
1845 | +#: view:account.easy.reconcile:0 |
1846 | +msgid "Display items reconciled on the last run" |
1847 | +msgstr "Voir les entrées lettrées au dernier lettrage" |
1848 | + |
1849 | +#. module: account_easy_reconcile |
1850 | +#: field:account.easy.reconcile.method,name:0 |
1851 | +msgid "Type" |
1852 | +msgstr "Type" |
1853 | + |
1854 | +#. module: account_easy_reconcile |
1855 | +#: field:account.easy.reconcile.method,journal_id:0 |
1856 | +#: field:easy.reconcile.base,journal_id:0 |
1857 | +#: field:easy.reconcile.options,journal_id:0 |
1858 | +#: field:easy.reconcile.simple,journal_id:0 |
1859 | +#: field:easy.reconcile.simple.name,journal_id:0 |
1860 | +#: field:easy.reconcile.simple.partner,journal_id:0 |
1861 | +#: field:easy.reconcile.simple.reference,journal_id:0 |
1862 | +msgid "Journal" |
1863 | +msgstr "Journal" |
1864 | + |
1865 | +#. module: account_easy_reconcile |
1866 | +#: field:account.easy.reconcile.method,account_profit_id:0 |
1867 | +#: field:easy.reconcile.base,account_profit_id:0 |
1868 | +#: field:easy.reconcile.options,account_profit_id:0 |
1869 | +#: field:easy.reconcile.simple,account_profit_id:0 |
1870 | +#: field:easy.reconcile.simple.name,account_profit_id:0 |
1871 | +#: field:easy.reconcile.simple.partner,account_profit_id:0 |
1872 | +#: field:easy.reconcile.simple.reference,account_profit_id:0 |
1873 | +msgid "Account Profit" |
1874 | +msgstr "Compte de profits" |
1875 | + |
1876 | +#. module: account_easy_reconcile |
1877 | +#: view:easy.reconcile.history:0 |
1878 | +msgid "Todays' Reconcilations" |
1879 | +msgstr "Lettrages du jour" |
1880 | + |
1881 | +#. module: account_easy_reconcile |
1882 | +#: view:account.easy.reconcile:0 |
1883 | +msgid "Simple. Amount and Name" |
1884 | +msgstr "Simple. Montant et description" |
1885 | + |
1886 | +#. module: account_easy_reconcile |
1887 | +#: field:easy.reconcile.base,partner_ids:0 |
1888 | +#: field:easy.reconcile.simple,partner_ids:0 |
1889 | +#: field:easy.reconcile.simple.name,partner_ids:0 |
1890 | +#: field:easy.reconcile.simple.partner,partner_ids:0 |
1891 | +#: field:easy.reconcile.simple.reference,partner_ids:0 |
1892 | +msgid "Restrict on partners" |
1893 | +msgstr "Filtrer sur des partenaires" |
1894 | + |
1895 | +#. module: account_easy_reconcile |
1896 | +#: model:ir.actions.act_window,name:account_easy_reconcile.action_account_easy_reconcile |
1897 | +#: model:ir.ui.menu,name:account_easy_reconcile.menu_easy_reconcile |
1898 | +msgid "Easy Automatic Reconcile" |
1899 | +msgstr "Lettrage automatisé" |
1900 | + |
1901 | +#. module: account_easy_reconcile |
1902 | +#: view:easy.reconcile.history:0 |
1903 | +msgid "Today" |
1904 | +msgstr "Aujourd'hui" |
1905 | + |
1906 | +#. module: account_easy_reconcile |
1907 | +#: view:easy.reconcile.history:0 |
1908 | +msgid "Date" |
1909 | +msgstr "Date" |
1910 | + |
1911 | +#. module: account_easy_reconcile |
1912 | +#: field:account.easy.reconcile,last_history:0 |
1913 | +msgid "Last History" |
1914 | +msgstr "Dernier historique" |
1915 | + |
1916 | +#. module: account_easy_reconcile |
1917 | +#: view:account.easy.reconcile:0 |
1918 | +msgid "Configuration" |
1919 | +msgstr "Configuration" |
1920 | + |
1921 | +#. module: account_easy_reconcile |
1922 | +#: field:account.easy.reconcile,reconciled_partial_count:0 |
1923 | +#: field:easy.reconcile.history,partial_line_ids:0 |
1924 | +msgid "Partially Reconciled Items" |
1925 | +msgstr "Écritures partiellement lettrées" |
1926 | + |
1927 | +#. module: account_easy_reconcile |
1928 | +#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_simple_partner |
1929 | +msgid "easy.reconcile.simple.partner" |
1930 | +msgstr "easy.reconcile.simple.partner" |
1931 | + |
1932 | +#. module: account_easy_reconcile |
1933 | +#: field:account.easy.reconcile.method,write_off:0 |
1934 | +#: field:easy.reconcile.base,write_off:0 |
1935 | +#: field:easy.reconcile.options,write_off:0 |
1936 | +#: field:easy.reconcile.simple,write_off:0 |
1937 | +#: field:easy.reconcile.simple.name,write_off:0 |
1938 | +#: field:easy.reconcile.simple.partner,write_off:0 |
1939 | +#: field:easy.reconcile.simple.reference,write_off:0 |
1940 | +msgid "Write off allowed" |
1941 | +msgstr "Écart autorisé" |
1942 | + |
1943 | +#. module: account_easy_reconcile |
1944 | +#: view:account.easy.reconcile:0 |
1945 | +msgid "Automatic Easy Reconcile" |
1946 | +msgstr "Lettrage automatisé" |
1947 | + |
1948 | +#. module: account_easy_reconcile |
1949 | +#: field:account.easy.reconcile,account:0 |
1950 | +#: field:easy.reconcile.base,account_id:0 |
1951 | +#: field:easy.reconcile.simple,account_id:0 |
1952 | +#: field:easy.reconcile.simple.name,account_id:0 |
1953 | +#: field:easy.reconcile.simple.partner,account_id:0 |
1954 | +#: field:easy.reconcile.simple.reference,account_id:0 |
1955 | +msgid "Account" |
1956 | +msgstr "Compte" |
1957 | + |
1958 | +#. module: account_easy_reconcile |
1959 | +#: field:account.easy.reconcile.method,task_id:0 |
1960 | +msgid "Task" |
1961 | +msgstr "Tâche" |
1962 | + |
1963 | +#. module: account_easy_reconcile |
1964 | +#: field:account.easy.reconcile,name:0 |
1965 | +msgid "Name" |
1966 | +msgstr "Nom" |
1967 | + |
1968 | +#. module: account_easy_reconcile |
1969 | +#: view:account.easy.reconcile:0 |
1970 | +msgid "Simple. Amount and Partner" |
1971 | +msgstr "Simple. Montant et partenaire" |
1972 | + |
1973 | +#. module: account_easy_reconcile |
1974 | +#: view:account.easy.reconcile:0 |
1975 | +msgid "Start Auto Reconcilation" |
1976 | +msgstr "Lancer le lettrage automatisé" |
1977 | + |
1978 | +#. module: account_easy_reconcile |
1979 | +#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_simple_name |
1980 | +msgid "easy.reconcile.simple.name" |
1981 | +msgstr "easy.reconcile.simple.name" |
1982 | + |
1983 | +#. module: account_easy_reconcile |
1984 | +#: field:account.easy.reconcile.method,filter:0 |
1985 | +#: field:easy.reconcile.base,filter:0 |
1986 | +#: field:easy.reconcile.options,filter:0 |
1987 | +#: field:easy.reconcile.simple,filter:0 |
1988 | +#: field:easy.reconcile.simple.name,filter:0 |
1989 | +#: field:easy.reconcile.simple.partner,filter:0 |
1990 | +#: field:easy.reconcile.simple.reference,filter:0 |
1991 | +msgid "Filter" |
1992 | +msgstr "Filtre" |
1993 | + |
1994 | +#. module: account_easy_reconcile |
1995 | +#: view:account.easy.reconcile:0 |
1996 | +msgid "Match one debit line vs one credit line. Do not allow partial reconciliation. The lines should have the same amount (with the write-off) and the same partner to be reconciled." |
1997 | +msgstr "Lettre un débit avec un crédit ayant le même montant et le même partenaire. Le lettrage ne peut être partiel (écriture d'ajustement en cas d'écart)." |
1998 | + |
1999 | +#. module: account_easy_reconcile |
2000 | +#: field:easy.reconcile.history,easy_reconcile_id:0 |
2001 | +msgid "Reconcile Profile" |
2002 | +msgstr "Profil de réconciliation" |
2003 | + |
2004 | +#. module: account_easy_reconcile |
2005 | +#: view:account.easy.reconcile:0 |
2006 | +msgid "Start Auto Reconciliation" |
2007 | +msgstr "Lancer le lettrage automatisé" |
2008 | + |
2009 | +#. module: account_easy_reconcile |
2010 | +#: code:addons/account_easy_reconcile/easy_reconcile.py:250 |
2011 | +#, python-format |
2012 | +msgid "Error" |
2013 | +msgstr "Erreur" |
2014 | + |
2015 | +#. module: account_easy_reconcile |
2016 | +#: code:addons/account_easy_reconcile/easy_reconcile.py:251 |
2017 | +#, python-format |
2018 | +msgid "There is no history of reconciled items on the task: %s." |
2019 | +msgstr "Il n'y a pas d'historique d'écritures lettrées sur la tâche: %s." |
2020 | + |
2021 | +#. module: account_easy_reconcile |
2022 | +#: view:account.easy.reconcile:0 |
2023 | +msgid "Match one debit line vs one credit line. Do not allow partial reconciliation. The lines should have the same amount (with the write-off) and the same name to be reconciled." |
2024 | +msgstr "Lettre un débit avec un crédit ayant le même montant et la même description. Le lettrage ne peut être partiel (écriture d'ajustement en cas d'écart)." |
2025 | + |
2026 | +#. module: account_easy_reconcile |
2027 | +#: field:account.easy.reconcile.method,account_lost_id:0 |
2028 | +#: field:easy.reconcile.base,account_lost_id:0 |
2029 | +#: field:easy.reconcile.options,account_lost_id:0 |
2030 | +#: field:easy.reconcile.simple,account_lost_id:0 |
2031 | +#: field:easy.reconcile.simple.name,account_lost_id:0 |
2032 | +#: field:easy.reconcile.simple.partner,account_lost_id:0 |
2033 | +#: field:easy.reconcile.simple.reference,account_lost_id:0 |
2034 | +msgid "Account Lost" |
2035 | +msgstr "Compte de pertes" |
2036 | + |
2037 | +#. module: account_easy_reconcile |
2038 | +#: view:easy.reconcile.history:0 |
2039 | +msgid "Reconciliation Profile" |
2040 | +msgstr "Profil de réconciliation" |
2041 | + |
2042 | +#. module: account_easy_reconcile |
2043 | +#: view:account.easy.reconcile:0 |
2044 | +#: field:account.easy.reconcile,history_ids:0 |
2045 | +msgid "History" |
2046 | +msgstr "Historique" |
2047 | + |
2048 | +#. module: account_easy_reconcile |
2049 | +#: view:account.easy.reconcile:0 |
2050 | +#: view:easy.reconcile.history:0 |
2051 | +msgid "Go to reconciled items" |
2052 | +msgstr "Voir les entrées lettrées" |
2053 | + |
2054 | +#. module: account_easy_reconcile |
2055 | +#: view:account.easy.reconcile:0 |
2056 | +msgid "Profile Information" |
2057 | +msgstr "Information sur le profil" |
2058 | + |
2059 | +#. module: account_easy_reconcile |
2060 | +#: view:account.easy.reconcile.method:0 |
2061 | +msgid "Automatic Easy Reconcile Method" |
2062 | +msgstr "Méthode de lettrage automatisé" |
2063 | + |
2064 | +#. module: account_easy_reconcile |
2065 | +#: view:account.easy.reconcile:0 |
2066 | +msgid "Simple. Amount and Reference" |
2067 | +msgstr "Simple. Montant et référence" |
2068 | + |
2069 | +#. module: account_easy_reconcile |
2070 | +#: view:account.easy.reconcile:0 |
2071 | +msgid "Display items partially reconciled on the last run" |
2072 | +msgstr "Afficher les entrées partiellement lettrées au dernier lettrage" |
2073 | + |
2074 | +#. module: account_easy_reconcile |
2075 | +#: field:account.easy.reconcile.method,sequence:0 |
2076 | +msgid "Sequence" |
2077 | +msgstr "Séquence" |
2078 | + |
2079 | +#. module: account_easy_reconcile |
2080 | +#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_simple |
2081 | +msgid "easy.reconcile.simple" |
2082 | +msgstr "easy.reconcile.simple" |
2083 | + |
2084 | +#. module: account_easy_reconcile |
2085 | +#: view:easy.reconcile.history:0 |
2086 | +msgid "Reconciliations of last 7 days" |
2087 | +msgstr "Lettrages des 7 derniers jours" |
2088 | + |
2089 | +#. module: account_easy_reconcile |
2090 | +#: field:account.easy.reconcile.method,date_base_on:0 |
2091 | +#: field:easy.reconcile.base,date_base_on:0 |
2092 | +#: field:easy.reconcile.options,date_base_on:0 |
2093 | +#: field:easy.reconcile.simple,date_base_on:0 |
2094 | +#: field:easy.reconcile.simple.name,date_base_on:0 |
2095 | +#: field:easy.reconcile.simple.partner,date_base_on:0 |
2096 | +#: field:easy.reconcile.simple.reference,date_base_on:0 |
2097 | +msgid "Date of reconciliation" |
2098 | +msgstr "Date de lettrage" |
2099 | + |
2100 | +#. module: account_easy_reconcile |
2101 | +#: code:addons/account_easy_reconcile/easy_reconcile_history.py:104 |
2102 | +#: view:easy.reconcile.history:0 |
2103 | +#: field:easy.reconcile.history,reconcile_partial_ids:0 |
2104 | +#, python-format |
2105 | +msgid "Partial Reconciliations" |
2106 | +msgstr "Lettrages partiels" |
2107 | + |
2108 | +#. module: account_easy_reconcile |
2109 | +#: model:ir.model,name:account_easy_reconcile.model_account_easy_reconcile_method |
2110 | +msgid "reconcile method for account_easy_reconcile" |
2111 | +msgstr "Méthode de lettrage" |
2112 | + |
2113 | +#. module: account_easy_reconcile |
2114 | +#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_simple_reference |
2115 | +msgid "easy.reconcile.simple.reference" |
2116 | +msgstr "easy.reconcile.simple.reference" |
2117 | + |
2118 | +#. module: account_easy_reconcile |
2119 | +#: model:ir.model,name:account_easy_reconcile.model_account_easy_reconcile |
2120 | +msgid "account easy reconcile" |
2121 | +msgstr "Lettrage automatisé" |
2122 | + |
2123 | +#~ msgid "Unreconciled Entries" |
2124 | +#~ msgstr "Écritures non lettrées" |
2125 | + |
2126 | +#, fuzzy |
2127 | +#~ msgid "Partially Reconciled Entries" |
2128 | +#~ msgstr "Lettrages partiels" |
2129 | + |
2130 | +#~ msgid "Task Information" |
2131 | +#~ msgstr "Information sur la tâche" |
2132 | + |
2133 | +#~ msgid "Reconcile Method" |
2134 | +#~ msgstr "Méthode de lettrage" |
2135 | + |
2136 | +#~ msgid "Log" |
2137 | +#~ msgstr "Historique" |
2138 | |
2139 | === added directory 'account_easy_reconcile/security' |
2140 | === added file 'account_easy_reconcile/security/ir.model.access.csv' |
2141 | --- account_easy_reconcile/security/ir.model.access.csv 1970-01-01 00:00:00 +0000 |
2142 | +++ account_easy_reconcile/security/ir.model.access.csv 2013-07-17 14:55:41 +0000 |
2143 | @@ -0,0 +1,15 @@ |
2144 | +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink |
2145 | +access_easy_reconcile_options_acc_user,easy.reconcile.options,model_easy_reconcile_options,account.group_account_user,1,0,0,0 |
2146 | +access_account_easy_reconcile_method_acc_user,account.easy.reconcile.method,model_account_easy_reconcile_method,account.group_account_user,1,0,0,0 |
2147 | +access_account_easy_reconcile_acc_user,account.easy.reconcile,model_account_easy_reconcile,account.group_account_user,1,1,0,0 |
2148 | +access_easy_reconcile_simple_name_acc_user,easy.reconcile.simple.name,model_easy_reconcile_simple_name,account.group_account_user,1,0,0,0 |
2149 | +access_easy_reconcile_simple_partner_acc_user,easy.reconcile.simple.partner,model_easy_reconcile_simple_partner,account.group_account_user,1,0,0,0 |
2150 | +access_easy_reconcile_simple_reference_acc_user,easy.reconcile.simple.reference,model_easy_reconcile_simple_reference,account.group_account_user,1,0,0,0 |
2151 | +access_easy_reconcile_options_acc_mgr,easy.reconcile.options,model_easy_reconcile_options,account.group_account_user,1,0,0,0 |
2152 | +access_account_easy_reconcile_method_acc_mgr,account.easy.reconcile.method,model_account_easy_reconcile_method,account.group_account_user,1,1,1,1 |
2153 | +access_account_easy_reconcile_acc_mgr,account.easy.reconcile,model_account_easy_reconcile,account.group_account_user,1,1,1,1 |
2154 | +access_easy_reconcile_simple_name_acc_mgr,easy.reconcile.simple.name,model_easy_reconcile_simple_name,account.group_account_user,1,0,0,0 |
2155 | +access_easy_reconcile_simple_partner_acc_mgr,easy.reconcile.simple.partner,model_easy_reconcile_simple_partner,account.group_account_user,1,0,0,0 |
2156 | +access_easy_reconcile_simple_reference_acc_mgr,easy.reconcile.simple.reference,model_easy_reconcile_simple_reference,account.group_account_user,1,0,0,0 |
2157 | +access_easy_reconcile_history_acc_user,easy.reconcile.history,model_easy_reconcile_history,account.group_account_user,1,1,1,0 |
2158 | +access_easy_reconcile_history_acc_mgr,easy.reconcile.history,model_easy_reconcile_history,account.group_account_manager,1,1,1,1 |
2159 | |
2160 | === added file 'account_easy_reconcile/security/ir_rule.xml' |
2161 | --- account_easy_reconcile/security/ir_rule.xml 1970-01-01 00:00:00 +0000 |
2162 | +++ account_easy_reconcile/security/ir_rule.xml 2013-07-17 14:55:41 +0000 |
2163 | @@ -0,0 +1,25 @@ |
2164 | +<?xml version="1.0" encoding="utf-8"?> |
2165 | +<openerp> |
2166 | + <data noupdate="1"> |
2167 | + <record id="easy_reconcile_rule" model="ir.rule"> |
2168 | + <field name="name">Easy reconcile multi-company</field> |
2169 | + <field name="model_id" ref="model_account_easy_reconcile"/> |
2170 | + <field name="global" eval="True"/> |
2171 | + <field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field> |
2172 | + </record> |
2173 | + |
2174 | + <record id="easy_reconcile_history_rule" model="ir.rule"> |
2175 | + <field name="name">Easy reconcile history multi-company</field> |
2176 | + <field name="model_id" ref="model_easy_reconcile_history"/> |
2177 | + <field name="global" eval="True"/> |
2178 | + <field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field> |
2179 | + </record> |
2180 | + |
2181 | + <record id="easy_reconcile_method_rule" model="ir.rule"> |
2182 | + <field name="name">Easy reconcile method multi-company</field> |
2183 | + <field name="model_id" ref="model_account_easy_reconcile_method"/> |
2184 | + <field name="global" eval="True"/> |
2185 | + <field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field> |
2186 | + </record> |
2187 | + </data> |
2188 | +</openerp> |
2189 | |
2190 | === added file 'account_easy_reconcile/simple_reconciliation.py' |
2191 | --- account_easy_reconcile/simple_reconciliation.py 1970-01-01 00:00:00 +0000 |
2192 | +++ account_easy_reconcile/simple_reconciliation.py 2013-07-17 14:55:41 +0000 |
2193 | @@ -0,0 +1,120 @@ |
2194 | +# -*- coding: utf-8 -*- |
2195 | +############################################################################## |
2196 | +# |
2197 | +# Copyright 2012-2013 Camptocamp SA (Guewen Baconnier) |
2198 | +# Copyright (C) 2010 Sébastien Beau |
2199 | +# |
2200 | +# This program is free software: you can redistribute it and/or modify |
2201 | +# it under the terms of the GNU Affero General Public License as |
2202 | +# published by the Free Software Foundation, either version 3 of the |
2203 | +# License, or (at your option) any later version. |
2204 | +# |
2205 | +# This program is distributed in the hope that it will be useful, |
2206 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2207 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2208 | +# GNU Affero General Public License for more details. |
2209 | +# |
2210 | +# You should have received a copy of the GNU Affero General Public License |
2211 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2212 | +# |
2213 | +############################################################################## |
2214 | + |
2215 | +from openerp.osv.orm import AbstractModel, TransientModel |
2216 | + |
2217 | + |
2218 | +class easy_reconcile_simple(AbstractModel): |
2219 | + |
2220 | + _name = 'easy.reconcile.simple' |
2221 | + _inherit = 'easy.reconcile.base' |
2222 | + |
2223 | + # has to be subclassed |
2224 | + # field name used as key for matching the move lines |
2225 | + _key_field = None |
2226 | + |
2227 | + def rec_auto_lines_simple(self, cr, uid, rec, lines, context=None): |
2228 | + if context is None: |
2229 | + context = {} |
2230 | + |
2231 | + if self._key_field is None: |
2232 | + raise ValueError("_key_field has to be defined") |
2233 | + |
2234 | + count = 0 |
2235 | + res = [] |
2236 | + while (count < len(lines)): |
2237 | + for i in xrange(count+1, len(lines)): |
2238 | + writeoff_account_id = False |
2239 | + if lines[count][self._key_field] != lines[i][self._key_field]: |
2240 | + break |
2241 | + |
2242 | + check = False |
2243 | + if lines[count]['credit'] > 0 and lines[i]['debit'] > 0: |
2244 | + credit_line = lines[count] |
2245 | + debit_line = lines[i] |
2246 | + check = True |
2247 | + elif lines[i]['credit'] > 0 and lines[count]['debit'] > 0: |
2248 | + credit_line = lines[i] |
2249 | + debit_line = lines[count] |
2250 | + check = True |
2251 | + if not check: |
2252 | + continue |
2253 | + |
2254 | + reconciled, dummy = self._reconcile_lines( |
2255 | + cr, uid, rec, [credit_line, debit_line], |
2256 | + allow_partial=False, context=context) |
2257 | + if reconciled: |
2258 | + res += [credit_line['id'], debit_line['id']] |
2259 | + del lines[i] |
2260 | + break |
2261 | + count += 1 |
2262 | + return res, [] # empty list for partial, only full rec in "simple" rec |
2263 | + |
2264 | + def _simple_order(self, rec, *args, **kwargs): |
2265 | + return "ORDER BY account_move_line.%s" % self._key_field |
2266 | + |
2267 | + def _action_rec(self, cr, uid, rec, context=None): |
2268 | + """Match only 2 move lines, do not allow partial reconcile""" |
2269 | + select = self._select(rec) |
2270 | + select += ", account_move_line.%s " % self._key_field |
2271 | + where, params = self._where(rec) |
2272 | + where += " AND account_move_line.%s IS NOT NULL " % self._key_field |
2273 | + |
2274 | + where2, params2 = self._get_filter(cr, uid, rec, context=context) |
2275 | + query = ' '.join(( |
2276 | + select, |
2277 | + self._from(rec), |
2278 | + where, where2, |
2279 | + self._simple_order(rec))) |
2280 | + |
2281 | + cr.execute(query, params + params2) |
2282 | + lines = cr.dictfetchall() |
2283 | + return self.rec_auto_lines_simple(cr, uid, rec, lines, context) |
2284 | + |
2285 | + |
2286 | +class easy_reconcile_simple_name(TransientModel): |
2287 | + |
2288 | + _name = 'easy.reconcile.simple.name' |
2289 | + _inherit = 'easy.reconcile.simple' |
2290 | + |
2291 | + # has to be subclassed |
2292 | + # field name used as key for matching the move lines |
2293 | + _key_field = 'name' |
2294 | + |
2295 | + |
2296 | +class easy_reconcile_simple_partner(TransientModel): |
2297 | + |
2298 | + _name = 'easy.reconcile.simple.partner' |
2299 | + _inherit = 'easy.reconcile.simple' |
2300 | + |
2301 | + # has to be subclassed |
2302 | + # field name used as key for matching the move lines |
2303 | + _key_field = 'partner_id' |
2304 | + |
2305 | + |
2306 | +class easy_reconcile_simple_reference(TransientModel): |
2307 | + |
2308 | + _name = 'easy.reconcile.simple.reference' |
2309 | + _inherit = 'easy.reconcile.simple' |
2310 | + |
2311 | + # has to be subclassed |
2312 | + # field name used as key for matching the move lines |
2313 | + _key_field = 'ref' |
2314 | |
2315 | === added directory 'account_statement_base_completion' |
2316 | === added file 'account_statement_base_completion/__init__.py' |
2317 | --- account_statement_base_completion/__init__.py 1970-01-01 00:00:00 +0000 |
2318 | +++ account_statement_base_completion/__init__.py 2013-07-17 14:55:41 +0000 |
2319 | @@ -0,0 +1,23 @@ |
2320 | +# -*- coding: utf-8 -*- |
2321 | +############################################################################## |
2322 | +# |
2323 | +# Author: Joel Grand-Guillaume |
2324 | +# Copyright 2011-2012 Camptocamp SA |
2325 | +# |
2326 | +# This program is free software: you can redistribute it and/or modify |
2327 | +# it under the terms of the GNU Affero General Public License as |
2328 | +# published by the Free Software Foundation, either version 3 of the |
2329 | +# License, or (at your option) any later version. |
2330 | +# |
2331 | +# This program is distributed in the hope that it will be useful, |
2332 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2333 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2334 | +# GNU Affero General Public License for more details. |
2335 | +# |
2336 | +# You should have received a copy of the GNU Affero General Public License |
2337 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2338 | +# |
2339 | +############################################################################## |
2340 | + |
2341 | +import statement |
2342 | +import partner |
2343 | |
2344 | === added file 'account_statement_base_completion/__openerp__.py' |
2345 | --- account_statement_base_completion/__openerp__.py 1970-01-01 00:00:00 +0000 |
2346 | +++ account_statement_base_completion/__openerp__.py 2013-07-17 14:55:41 +0000 |
2347 | @@ -0,0 +1,74 @@ |
2348 | +# -*- coding: utf-8 -*- |
2349 | +############################################################################## |
2350 | +# |
2351 | +# Author: Joel Grand-Guillaume |
2352 | +# Copyright 2011-2012 Camptocamp SA |
2353 | +# |
2354 | +# This program is free software: you can redistribute it and/or modify |
2355 | +# it under the terms of the GNU Affero General Public License as |
2356 | +# published by the Free Software Foundation, either version 3 of the |
2357 | +# License, or (at your option) any later version. |
2358 | +# |
2359 | +# This program is distributed in the hope that it will be useful, |
2360 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2361 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2362 | +# GNU Affero General Public License for more details. |
2363 | +# |
2364 | +# You should have received a copy of the GNU Affero General Public License |
2365 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2366 | +# |
2367 | +############################################################################## |
2368 | + |
2369 | +{'name': "Bank statement base completion", |
2370 | + 'version': '1.0', |
2371 | + 'author': 'Camptocamp', |
2372 | + 'maintainer': 'Camptocamp', |
2373 | + 'category': 'Finance', |
2374 | + 'complexity': 'normal', |
2375 | + 'depends': ['account_statement_ext'], |
2376 | + 'description': """ |
2377 | + The goal of this module is to improve the basic bank statement, help dealing with huge volume of |
2378 | + reconciliation by providing basic rules to identify the partner of a bank statement line. |
2379 | + Each bank statement profile can have its own rules to be applied according to a sequence order. |
2380 | + |
2381 | + Some basic rules are provided in this module: |
2382 | + |
2383 | + 1) Match from statement line label (based on partner field 'Bank Statement Label') |
2384 | + 2) Match from statement line label (based on partner name) |
2385 | + 3) Match from statement line reference (based on SO number) |
2386 | + 3) Match from statement line reference (based on Invoice number) |
2387 | + |
2388 | + You can easily override this module and add your own rules in your own one. The basic rules only |
2389 | + fill in the partner, but you can use them to fill in any value of the line (in the future, we will |
2390 | + add a rule to automatically match and reconcile the line). |
2391 | + |
2392 | + It adds as well a label on the bank statement line (on which the pre-define rules can match) and |
2393 | + a char field on the partner called 'Bank Statement Label'. Using the pre-define rules, you will be |
2394 | + able to match various labels for a partner. |
2395 | + |
2396 | + The reference of the line is always used by the reconciliation process. We're supposed to copy |
2397 | + there (or write manually) the matching string. This can be: the order Number or an invoice number, |
2398 | + or anything that will be found in the invoice accounting entry part to make the match. |
2399 | + |
2400 | + You can use it with our account_advanced_reconcile module to automatize the reconciliation process. |
2401 | + |
2402 | + |
2403 | + TODO: The rules that look for invoices to find out the partner should take back the payable / receivable |
2404 | + account from there directly instead of retrieving it from partner properties ! |
2405 | + |
2406 | + """, |
2407 | + 'website': 'http://www.camptocamp.com', |
2408 | + 'init_xml': [], |
2409 | + 'update_xml': [ |
2410 | + 'statement_view.xml', |
2411 | + 'partner_view.xml', |
2412 | + 'data.xml', |
2413 | + 'security/ir.model.access.csv', |
2414 | + ], |
2415 | + 'demo_xml': [], |
2416 | + 'test': [], |
2417 | + 'installable': True, |
2418 | + 'images': [], |
2419 | + 'auto_install': False, |
2420 | + 'license': 'AGPL-3', |
2421 | +} |
2422 | |
2423 | === added file 'account_statement_base_completion/data.xml' |
2424 | --- account_statement_base_completion/data.xml 1970-01-01 00:00:00 +0000 |
2425 | +++ account_statement_base_completion/data.xml 2013-07-17 14:55:41 +0000 |
2426 | @@ -0,0 +1,37 @@ |
2427 | +<?xml version="1.0" encoding="utf-8"?> |
2428 | +<openerp> |
2429 | +<data noupdate="1"> |
2430 | + |
2431 | + <record id="bank_statement_completion_rule_2" model="account.statement.completion.rule"> |
2432 | + <field name="name">Match from line label (based on partner field 'Bank Statement Label')</field> |
2433 | + <field name="sequence">60</field> |
2434 | + <field name="function_to_call">get_from_label_and_partner_field</field> |
2435 | + </record> |
2436 | + |
2437 | + <record id="bank_statement_completion_rule_3" model="account.statement.completion.rule"> |
2438 | + <field name="name">Match from line label (based on partner name)</field> |
2439 | + <field name="sequence">70</field> |
2440 | + <field name="function_to_call">get_from_label_and_partner_name</field> |
2441 | + </record> |
2442 | + |
2443 | + <record id="bank_statement_completion_rule_1" model="account.statement.completion.rule"> |
2444 | + <field name="name">Match from line reference (based on SO number)</field> |
2445 | + <field name="sequence">50</field> |
2446 | + <field name="function_to_call">get_from_ref_and_so</field> |
2447 | + </record> |
2448 | + |
2449 | + <record id="bank_statement_completion_rule_4" model="account.statement.completion.rule"> |
2450 | + <field name="name">Match from line reference (based on Invoice number)</field> |
2451 | + <field name="sequence">40</field> |
2452 | + <field name="function_to_call">get_from_ref_and_invoice</field> |
2453 | + </record> |
2454 | + |
2455 | + <record id="bank_statement_completion_rule_5" model="account.statement.completion.rule"> |
2456 | + <field name="name">Match from line reference (based on Invoice Supplier number)</field> |
2457 | + <field name="sequence">45</field> |
2458 | + <field name="function_to_call">get_from_ref_and_supplier_invoice</field> |
2459 | + </record> |
2460 | + |
2461 | + |
2462 | +</data> |
2463 | +</openerp> |
2464 | |
2465 | === added directory 'account_statement_base_completion/i18n' |
2466 | === added file 'account_statement_base_completion/i18n/fr.po' |
2467 | --- account_statement_base_completion/i18n/fr.po 1970-01-01 00:00:00 +0000 |
2468 | +++ account_statement_base_completion/i18n/fr.po 2013-07-17 14:55:41 +0000 |
2469 | @@ -0,0 +1,180 @@ |
2470 | +#. module: account_statement_base_completion |
2471 | +#: view:account.statement.completion.rule:0 |
2472 | +msgid "Related Profiles" |
2473 | +msgstr "Profils liés" |
2474 | + |
2475 | +#. module: account_statement_base_completion |
2476 | +#: help:account.statement.completion.rule,sequence:0 |
2477 | +msgid "Lower means parsed first." |
2478 | +msgstr "Plus petite séquence analysée en premier." |
2479 | + |
2480 | +#. module: account_statement_base_completion |
2481 | +#: code:addons/account_statement_base_completion/statement.py:150 |
2482 | +#: code:addons/account_statement_base_completion/statement.py:182 |
2483 | +#: code:addons/account_statement_base_completion/statement.py:219 |
2484 | +#: code:addons/account_statement_base_completion/statement.py:252 |
2485 | +#, python-format |
2486 | +msgid "Line named \"%s\" (Ref:%s) was matched by more than one partner." |
2487 | +msgstr "La ligne nommée \"%s\" (Ref:%s) correspond à plusieurs partenaires." |
2488 | + |
2489 | +#. module: account_statement_base_completion |
2490 | +#: field:account.bank.statement,completion_logs:0 |
2491 | +msgid "Completion Log" |
2492 | +msgstr "Journal des complétions" |
2493 | + |
2494 | +#. module: account_statement_base_completion |
2495 | +#: field:account.bank.statement.line,label:0 |
2496 | +msgid "Label" |
2497 | +msgstr "Description" |
2498 | + |
2499 | +#. module: account_statement_base_completion |
2500 | +#: help:account.bank.statement.line,label:0 |
2501 | +msgid "" |
2502 | +"Generiy field to store a label given from the bank/office on which we " |
2503 | +"can base the default/standard providen rule." |
2504 | +msgstr "" |
2505 | +"Ce champs permet de stocker une description complémentaire fournie par la banque." |
2506 | +"Le lettrage avancé pourra être effectué sur ce critère." |
2507 | + |
2508 | +#. module: account_statement_base_completion |
2509 | +#: model:ir.model,name:account_statement_base_completion.model_account_bank_statement |
2510 | +msgid "Bank Statement" |
2511 | +msgstr "Relevé bancaire" |
2512 | + |
2513 | +#. module: account_statement_base_completion |
2514 | +#: field:account.statement.completion.rule,function_to_call:0 |
2515 | +msgid "Method" |
2516 | +msgstr "Méthode" |
2517 | + |
2518 | +#. module: account_statement_base_completion |
2519 | +#: code:addons/account_statement_base_completion/statement.py:352 |
2520 | +#, python-format |
2521 | +msgid "Bank Statement ID %s has %s lines completed by %s" |
2522 | +msgstr "Le relevé bancaire avec l'ID %s a %s lignes completées par %s" |
2523 | + |
2524 | +#. module: account_statement_base_completion |
2525 | +#: field:account.bank.statement.line,additionnal_bank_fields:0 |
2526 | +msgid "Additionnal infos from bank" |
2527 | +msgstr "Informations additionnelles de la banque" |
2528 | + |
2529 | +#. module: account_statement_base_completion |
2530 | +#: view:account.statement.profile:0 |
2531 | +msgid "Auto-Completion Rules" |
2532 | +msgstr "Règles d'auto-complétion" |
2533 | + |
2534 | +#. module: account_statement_base_completion |
2535 | +#: help:account.bank.statement.line,additionnal_bank_fields:0 |
2536 | +msgid "" |
2537 | +"Used by completion and import system. Adds every field that is present in " |
2538 | +"your bank/office statement file" |
2539 | +msgstr "" |
2540 | +"Utilisé au niveau de l'auto-complétion et de l'import. Permet l'ajout de n'importe quel " |
2541 | +"champs additionnel communiqué par la banque" |
2542 | + |
2543 | +#. module: account_statement_base_completion |
2544 | +#: view:account.bank.statement:0 |
2545 | +msgid "Importation related infos" |
2546 | +msgstr "Importation des informations liées" |
2547 | + |
2548 | +#. module: account_statement_base_completion |
2549 | +#: model:ir.model,name:account_statement_base_completion.model_account_statement_profile |
2550 | +msgid "Statement Profil" |
2551 | +msgstr "Profil de relevé" |
2552 | + |
2553 | +#. module: account_statement_base_completion |
2554 | +#: field:account.statement.completion.rule,name:0 |
2555 | +msgid "Name" |
2556 | +msgstr "Nom" |
2557 | + |
2558 | +#. module: account_statement_base_completion |
2559 | +#: constraint:account.statement.profile:0 |
2560 | +msgid "You need to put a partner if you tic the 'Force partner on bank move' !" |
2561 | +msgstr "" |
2562 | +"Vous devez indiquer un partenaire si vous avez coché 'Indiquer un partenaire sur la ligne d'écriture de la banque' !" |
2563 | + |
2564 | +#. module: account_statement_base_completion |
2565 | +#: model:ir.model,name:account_statement_base_completion.model_account_bank_statement_line |
2566 | +msgid "Bank Statement Line" |
2567 | +msgstr "Ligne de relevé bancaire" |
2568 | + |
2569 | +#. module: account_statement_base_completion |
2570 | +#: view:account.statement.completion.rule:0 |
2571 | +#: model:ir.actions.act_window,name:account_statement_base_completion.action_st_completion_rule_tree |
2572 | +#: model:ir.ui.menu,name:account_statement_base_completion.menu_action_st_completion_rule_tree_menu |
2573 | +msgid "Statement Completion Rule" |
2574 | +msgstr "Règle d'auto-complétion du relevé" |
2575 | + |
2576 | +#. module: account_statement_base_completion |
2577 | +#: model:ir.model,name:account_statement_base_completion.model_account_statement_completion_rule |
2578 | +msgid "account.statement.completion.rule" |
2579 | +msgstr "account.statement.completion.rule" |
2580 | + |
2581 | +#. module: account_statement_base_completion |
2582 | +#: field:account.statement.completion.rule,profile_ids:0 |
2583 | +#: field:account.statement.profile,rule_ids:0 |
2584 | +msgid "Related statement profiles" |
2585 | +msgstr "Profils liés" |
2586 | + |
2587 | +#. module: account_statement_base_completion |
2588 | +#: constraint:account.bank.statement.line:0 |
2589 | +msgid "" |
2590 | +"The amount of the voucher must be the same amount as the one on the " |
2591 | +"statement line" |
2592 | +msgstr "" |
2593 | +"Le montant du justificatif doit être identique à celui de la ligne le " |
2594 | +"concernant sur le relevé" |
2595 | + |
2596 | +#. module: account_statement_base_completion |
2597 | +#: field:account.bank.statement.line,already_completed:0 |
2598 | +msgid "Auto-Completed" |
2599 | +msgstr "Auto-Completé" |
2600 | + |
2601 | +#. module: account_statement_base_completion |
2602 | +#: view:account.bank.statement:0 |
2603 | +msgid "Auto Completion" |
2604 | +msgstr "Auto-complétion" |
2605 | + |
2606 | +#. module: account_statement_base_completion |
2607 | +#: field:account.statement.completion.rule,sequence:0 |
2608 | +msgid "Sequence" |
2609 | +msgstr "Séquence" |
2610 | + |
2611 | +#. module: account_statement_base_completion |
2612 | +#: constraint:account.bank.statement:0 |
2613 | +msgid "The journal and period chosen have to belong to the same company." |
2614 | +msgstr "Le journal et la période doivent appartenir à la même société." |
2615 | + |
2616 | +#. module: account_statement_base_completion |
2617 | +#: field:res.partner,bank_statement_label:0 |
2618 | +msgid "Bank Statement Label" |
2619 | +msgstr "Description de relevé bancaire" |
2620 | + |
2621 | +#. module: account_statement_base_completion |
2622 | +#: help:account.bank.statement.line,already_completed:0 |
2623 | +msgid "" |
2624 | +"When this checkbox is ticked, the auto-completion process/button will ignore " |
2625 | +"this line." |
2626 | +msgstr "" |
2627 | +"Les lignes cochées seront ignorées lorsque vous cliquez sur le bouton auto-complétion" |
2628 | + |
2629 | +#. module: account_statement_base_completion |
2630 | +#: help:res.partner,bank_statement_label:0 |
2631 | +msgid "" |
2632 | +"Enter the various label found on your bank statement separated by a ; " |
2633 | +"If one of this label is include in the bank statement line, " |
2634 | +"the partner will be automatically filled (as long as you " |
2635 | +"use this method/rules in your statement profile)." |
2636 | +msgstr "" |
2637 | +"Entrez les différentes descriptions/informations sur votre relevé bancaire séparées par un ';' " |
2638 | +"Si l'une d'entre elles figure dans la ligne du relevé, le partenaire correspondant pourra" |
2639 | +"être automatiquement retrouvé (à condition d'utiliser un règle de lettrage dans le profil)." |
2640 | + |
2641 | +#. module: account_statement_base_completion |
2642 | +#: model:ir.model,name:account_statement_base_completion.model_res_partner |
2643 | +msgid "Partner" |
2644 | +msgstr "Partenaire" |
2645 | + |
2646 | +#. module: account_statement_base_completion |
2647 | +#: view:account.bank.statement:0 |
2648 | +msgid "Completion Logs" |
2649 | +msgstr "Journaux d'auto-complétion" |
2650 | |
2651 | === added file 'account_statement_base_completion/partner.py' |
2652 | --- account_statement_base_completion/partner.py 1970-01-01 00:00:00 +0000 |
2653 | +++ account_statement_base_completion/partner.py 2013-07-17 14:55:41 +0000 |
2654 | @@ -0,0 +1,38 @@ |
2655 | +# -*- coding: utf-8 -*- |
2656 | +################################################################################# |
2657 | +# # |
2658 | +# Copyright (C) 2011 Akretion & Camptocamp |
2659 | +# Author : Sébastien BEAU, Joel Grand-Guillaume # |
2660 | +# # |
2661 | +# This program is free software: you can redistribute it and/or modify # |
2662 | +# it under the terms of the GNU Affero General Public License as # |
2663 | +# published by the Free Software Foundation, either version 3 of the # |
2664 | +# License, or (at your option) any later version. # |
2665 | +# # |
2666 | +# This program is distributed in the hope that it will be useful, # |
2667 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of # |
2668 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # |
2669 | +# GNU Affero General Public License for more details. # |
2670 | +# # |
2671 | +# You should have received a copy of the GNU Affero General Public License # |
2672 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. # |
2673 | +# # |
2674 | +################################################################################# |
2675 | + |
2676 | +from openerp.osv.orm import Model |
2677 | +from openerp.osv import fields, osv |
2678 | + |
2679 | + |
2680 | +class res_partner(Model): |
2681 | + """ |
2682 | + Add a bank label on the partner so that we can use it to match |
2683 | + this partner when we found this in a statement line. |
2684 | + """ |
2685 | + _inherit = 'res.partner' |
2686 | + |
2687 | + _columns = { |
2688 | + 'bank_statement_label': fields.char('Bank Statement Label', size=100, |
2689 | + help="Enter the various label found on your bank statement separated by a ; If \ |
2690 | + one of this label is include in the bank statement line, the partner will be automatically \ |
2691 | + filled (as long as you use this method/rules in your statement profile)."), |
2692 | + } |
2693 | |
2694 | === added file 'account_statement_base_completion/partner_view.xml' |
2695 | --- account_statement_base_completion/partner_view.xml 1970-01-01 00:00:00 +0000 |
2696 | +++ account_statement_base_completion/partner_view.xml 2013-07-17 14:55:41 +0000 |
2697 | @@ -0,0 +1,22 @@ |
2698 | +<?xml version="1.0" encoding="UTF-8"?> |
2699 | + |
2700 | + |
2701 | +<openerp> |
2702 | + <data> |
2703 | + |
2704 | + <record id="bk_view_partner_form" model="ir.ui.view"> |
2705 | + <field name="name">account_bank_statement_import.view.partner.form</field> |
2706 | + <field name="model">res.partner</field> |
2707 | + <field name="type">form</field> |
2708 | + <field name="priority">20</field> |
2709 | + <field name="inherit_id" ref="account.view_partner_property_form"/> |
2710 | + <field name="arch" type="xml"> |
2711 | + <field name="property_account_payable" position="after"> |
2712 | + <field name="bank_statement_label"/> |
2713 | + </field> |
2714 | + </field> |
2715 | + </record> |
2716 | + |
2717 | + |
2718 | + </data> |
2719 | +</openerp> |
2720 | |
2721 | === added directory 'account_statement_base_completion/security' |
2722 | === added file 'account_statement_base_completion/security/ir.model.access.csv' |
2723 | --- account_statement_base_completion/security/ir.model.access.csv 1970-01-01 00:00:00 +0000 |
2724 | +++ account_statement_base_completion/security/ir.model.access.csv 2013-07-17 14:55:41 +0000 |
2725 | @@ -0,0 +1,3 @@ |
2726 | +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink |
2727 | +access_account_bank_st_cmpl_user,account.statement.completion.rule,model_account_statement_completion_rule,account.group_account_user,1,0,0,0 |
2728 | +access_account_bank_st_cmpl_manager,account.statement.completion.rule,model_account_statement_completion_rule,account.group_account_manager,1,1,1,1 |
2729 | |
2730 | === added file 'account_statement_base_completion/statement.py' |
2731 | --- account_statement_base_completion/statement.py 1970-01-01 00:00:00 +0000 |
2732 | +++ account_statement_base_completion/statement.py 2013-07-17 14:55:41 +0000 |
2733 | @@ -0,0 +1,527 @@ |
2734 | +# -*- coding: utf-8 -*- |
2735 | +############################################################################## |
2736 | +# |
2737 | +# Author: Nicolas Bessi, Joel Grand-Guillaume |
2738 | +# Copyright 2011-2012 Camptocamp SA |
2739 | +# |
2740 | +# This program is free software: you can redistribute it and/or modify |
2741 | +# it under the terms of the GNU Affero General Public License as |
2742 | +# published by the Free Software Foundation, either version 3 of the |
2743 | +# License, or (at your option) any later version. |
2744 | +# |
2745 | +# This program is distributed in the hope that it will be useful, |
2746 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2747 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2748 | +# GNU Affero General Public License for more details. |
2749 | +# |
2750 | +# You should have received a copy of the GNU Affero General Public License |
2751 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2752 | +# |
2753 | +############################################################################## |
2754 | +# TODO replace customer supplier by package constant |
2755 | +import traceback |
2756 | +import sys |
2757 | +import logging |
2758 | + |
2759 | +from collections import defaultdict |
2760 | +import re |
2761 | +from tools.translate import _ |
2762 | +from openerp.osv import osv, orm, fields |
2763 | +from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT |
2764 | +from operator import attrgetter |
2765 | +import datetime |
2766 | + |
2767 | +_logger = logging.getLogger(__name__) |
2768 | + |
2769 | + |
2770 | +class ErrorTooManyPartner(Exception): |
2771 | + """ |
2772 | + New Exception definition that is raised when more than one partner is matched by |
2773 | + the completion rule. |
2774 | + """ |
2775 | + def __init__(self, value): |
2776 | + self.value = value |
2777 | + |
2778 | + def __str__(self): |
2779 | + return repr(self.value) |
2780 | + |
2781 | + def __repr__(self): |
2782 | + return repr(self.value) |
2783 | + |
2784 | + |
2785 | +class AccountStatementProfil(orm.Model): |
2786 | + """ |
2787 | + Extend the class to add rules per profile that will match at least the partner, |
2788 | + but it could also be used to match other values as well. |
2789 | + """ |
2790 | + |
2791 | + _inherit = "account.statement.profile" |
2792 | + |
2793 | + _columns = { |
2794 | + # @Akretion: For now, we don't implement this features, but this would probably be there: |
2795 | + # 'auto_completion': fields.text('Auto Completion'), |
2796 | + # 'transferts_account_id':fields.many2one('account.account', 'Transferts Account'), |
2797 | + # => You can implement it in a module easily, we design it with your needs in mind |
2798 | + # as well! |
2799 | + |
2800 | + 'rule_ids': fields.many2many( |
2801 | + 'account.statement.completion.rule', |
2802 | + string='Related statement profiles', |
2803 | + rel='as_rul_st_prof_rel'), |
2804 | + } |
2805 | + |
2806 | + def _get_callable(self, cr, uid, profile, context=None): |
2807 | + if isinstance(profile, (int, long)): |
2808 | + prof = self.browse(cr, uid, profile, context=context) |
2809 | + else: |
2810 | + prof = profile |
2811 | + # We need to respect the sequence order |
2812 | + sorted_array = sorted(prof.rule_ids, key=attrgetter('sequence')) |
2813 | + return tuple((x.function_to_call for x in sorted_array)) |
2814 | + |
2815 | + def _find_values_from_rules(self, cr, uid, calls, line, context=None): |
2816 | + """ |
2817 | + This method will execute all related rules, in their sequence order, |
2818 | + to retrieve all the values returned by the first rules that will match. |
2819 | + :param calls: list of lookup function name available in rules |
2820 | + :param dict line: read of the concerned account.bank.statement.line |
2821 | + :return: |
2822 | + A dict of value that can be passed directly to the write method of |
2823 | + the statement line or {} |
2824 | + {'partner_id': value, |
2825 | + 'account_id: value, |
2826 | + |
2827 | + ...} |
2828 | + """ |
2829 | + if context is None: |
2830 | + context = {} |
2831 | + if not calls: |
2832 | + calls = self._get_callable(cr, uid, line['profile_id'], context=context) |
2833 | + rule_obj = self.pool.get('account.statement.completion.rule') |
2834 | + |
2835 | + for call in calls: |
2836 | + method_to_call = getattr(rule_obj, call) |
2837 | + result = method_to_call(cr, uid, line, context) |
2838 | + if result: |
2839 | + result['already_completed'] = True |
2840 | + return result |
2841 | + return None |
2842 | + |
2843 | + |
2844 | +class AccountStatementCompletionRule(orm.Model): |
2845 | + """ |
2846 | + This will represent all the completion method that we can have to |
2847 | + fullfill the bank statement lines. You'll be able to extend them in you own module |
2848 | + and choose those to apply for every statement profile. |
2849 | + The goal of a rule is to fullfill at least the partner of the line, but |
2850 | + if possible also the reference because we'll use it in the reconciliation |
2851 | + process. The reference should contain the invoice number or the SO number |
2852 | + or any reference that will be matched by the invoice accounting move. |
2853 | + """ |
2854 | + |
2855 | + _name = "account.statement.completion.rule" |
2856 | + _order = "sequence asc" |
2857 | + |
2858 | + def _get_functions(self, cr, uid, context=None): |
2859 | + """ |
2860 | + List of available methods for rules. Override this to add you own. |
2861 | + """ |
2862 | + return [ |
2863 | + ('get_from_ref_and_invoice', 'From line reference (based on customer invoice number)'), |
2864 | + ('get_from_ref_and_supplier_invoice', 'From line reference (based on supplier invoice number)'), |
2865 | + ('get_from_ref_and_so', 'From line reference (based on SO number)'), |
2866 | + ('get_from_label_and_partner_field', 'From line label (based on partner field)'), |
2867 | + ('get_from_label_and_partner_name', 'From line label (based on partner name)')] |
2868 | + |
2869 | + _columns = { |
2870 | + 'sequence': fields.integer('Sequence', help="Lower means parsed first."), |
2871 | + 'name': fields.char('Name', size=128), |
2872 | + 'profile_ids': fields.many2many( |
2873 | + 'account.statement.profile', |
2874 | + rel='as_rul_st_prof_rel', |
2875 | + string='Related statement profiles'), |
2876 | + 'function_to_call': fields.selection(_get_functions, 'Method'), |
2877 | + } |
2878 | + |
2879 | + def _find_invoice(self, cr, uid, st_line, inv_type, context=None): |
2880 | + """Find invoice related to statement line""" |
2881 | + inv_obj = self.pool.get('account.invoice') |
2882 | + if inv_type == 'supplier': |
2883 | + type_domain = ('in_invoice', 'in_refund') |
2884 | + number_field = 'supplier_invoice_number' |
2885 | + elif inv_type == 'customer': |
2886 | + type_domain = ('out_invoice', 'out_refund') |
2887 | + number_field = 'number' |
2888 | + else: |
2889 | + raise osv.except_osv(_('System error'), |
2890 | + _('Invalid invoice type for completion: %') % inv_type) |
2891 | + |
2892 | + inv_id = inv_obj.search(cr, uid, |
2893 | + [(number_field, '=', st_line['ref'].strip()), |
2894 | + ('type', 'in', type_domain)], |
2895 | + context=context) |
2896 | + if inv_id: |
2897 | + if len(inv_id) == 1: |
2898 | + inv = inv_obj.browse(cr, uid, inv_id[0], context=context) |
2899 | + else: |
2900 | + raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more ' |
2901 | + 'than one partner while looking on %s invoices') % |
2902 | + (st_line['name'], st_line['ref'], inv_type)) |
2903 | + return inv |
2904 | + return False |
2905 | + |
2906 | + def _from_invoice(self, cr, uid, line, inv_type, context): |
2907 | + """Populate statement line values""" |
2908 | + if not inv_type in ('supplier', 'customer'): |
2909 | + raise osv.except_osv(_('System error'), |
2910 | + _('Invalid invoice type for completion: %') % inv_type) |
2911 | + res = {} |
2912 | + inv = self._find_invoice(cr, uid, line, inv_type, context=context) |
2913 | + if inv: |
2914 | + res = {'partner_id': inv.partner_id.id, |
2915 | + 'account_id': inv.account_id.id, |
2916 | + 'type': inv_type} |
2917 | + override_acc = line['master_account_id'] |
2918 | + if override_acc: |
2919 | + res['account_id'] = override_acc |
2920 | + return res |
2921 | + |
2922 | + # Should be private but data are initialised with no update XML |
2923 | + def get_from_ref_and_supplier_invoice(self, cr, uid, line, context=None): |
2924 | + """ |
2925 | + Match the partner based on the invoice supplier invoice number and the reference of the statement |
2926 | + line. Then, call the generic get_values_for_line method to complete other values. |
2927 | + If more than one partner matched, raise the ErrorTooManyPartner error. |
2928 | + |
2929 | + :param dict line: read of the concerned account.bank.statement.line |
2930 | + :return: |
2931 | + A dict of value that can be passed directly to the write method of |
2932 | + the statement line or {} |
2933 | + {'partner_id': value, |
2934 | + 'account_id': value, |
2935 | + |
2936 | + ...} |
2937 | + """ |
2938 | + return self._from_invoice(cr, uid, line, 'supplier', context=context) |
2939 | + |
2940 | + # Should be private but data are initialised with no update XML |
2941 | + def get_from_ref_and_invoice(self, cr, uid, line, context=None): |
2942 | + """ |
2943 | + Match the partner based on the invoice number and the reference of the statement |
2944 | + line. Then, call the generic get_values_for_line method to complete other values. |
2945 | + If more than one partner matched, raise the ErrorTooManyPartner error. |
2946 | + |
2947 | + :param dict line: read of the concerned account.bank.statement.line |
2948 | + :return: |
2949 | + A dict of value that can be passed directly to the write method of |
2950 | + the statement line or {} |
2951 | + {'partner_id': value, |
2952 | + 'account_id': value, |
2953 | + ...} |
2954 | + """ |
2955 | + return self._from_invoice(cr, uid, line, 'customer', context=context) |
2956 | + |
2957 | + # Should be private but data are initialised with no update XML |
2958 | + def get_from_ref_and_so(self, cr, uid, st_line, context=None): |
2959 | + """ |
2960 | + Match the partner based on the SO number and the reference of the statement |
2961 | + line. Then, call the generic get_values_for_line method to complete other values. |
2962 | + If more than one partner matched, raise the ErrorTooManyPartner error. |
2963 | + |
2964 | + :param int/long st_line: read of the concerned account.bank.statement.line |
2965 | + :return: |
2966 | + A dict of value that can be passed directly to the write method of |
2967 | + the statement line or {} |
2968 | + {'partner_id': value, |
2969 | + 'account_id': value, |
2970 | + |
2971 | + ...} |
2972 | + """ |
2973 | + st_obj = self.pool.get('account.bank.statement.line') |
2974 | + res = {} |
2975 | + if st_line: |
2976 | + so_obj = self.pool.get('sale.order') |
2977 | + so_id = so_obj.search(cr, |
2978 | + uid, |
2979 | + [('name', '=', st_line['ref'])], |
2980 | + context=context) |
2981 | + if so_id: |
2982 | + if so_id and len(so_id) == 1: |
2983 | + so = so_obj.browse(cr, uid, so_id[0], context=context) |
2984 | + res['partner_id'] = so.partner_id.id |
2985 | + elif so_id and len(so_id) > 1: |
2986 | + raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more ' |
2987 | + 'than one partner while looking on SO by ref.') % |
2988 | + (st_line['name'], st_line['ref'])) |
2989 | + st_vals = st_obj.get_values_for_line(cr, |
2990 | + uid, |
2991 | + profile_id=st_line['profile_id'], |
2992 | + master_account_id=st_line['master_account_id'], |
2993 | + partner_id=res.get('partner_id', False), |
2994 | + line_type='customer', |
2995 | + amount=st_line['amount'] if st_line['amount'] else 0.0, |
2996 | + context=context) |
2997 | + res.update(st_vals) |
2998 | + return res |
2999 | + |
3000 | + # Should be private but data are initialised with no update XML |
3001 | + def get_from_label_and_partner_field(self, cr, uid, st_line, context=None): |
3002 | + """ |
3003 | + Match the partner based on the label field of the statement line |
3004 | + and the text defined in the 'bank_statement_label' field of the partner. |
3005 | + Remember that we can have values separated with ; Then, call the generic |
3006 | + get_values_for_line method to complete other values. |
3007 | + If more than one partner matched, raise the ErrorTooManyPartner error. |
3008 | + |
3009 | + :param dict st_line: read of the concerned account.bank.statement.line |
3010 | + :return: |
3011 | + A dict of value that can be passed directly to the write method of |
3012 | + the statement line or {} |
3013 | + {'partner_id': value, |
3014 | + 'account_id': value, |
3015 | + |
3016 | + ...} |
3017 | + """ |
3018 | + partner_obj = self.pool.get('res.partner') |
3019 | + st_obj = self.pool.get('account.bank.statement.line') |
3020 | + res = {} |
3021 | + # As we have to iterate on each partner for each line, |
3022 | + #Â we memoize the pair to avoid |
3023 | + # to redo computation for each line. |
3024 | + # Following code can be done by a single SQL query |
3025 | + # but this option is not really maintanable |
3026 | + if not context.get('label_memoizer'): |
3027 | + context['label_memoizer'] = defaultdict(list) |
3028 | + partner_ids = partner_obj.search(cr, |
3029 | + uid, |
3030 | + [('bank_statement_label', '!=', False)]) |
3031 | + line_ids = context.get('line_ids', []) |
3032 | + for partner in partner_obj.browse(cr, uid, partner_ids, context=context): |
3033 | + vals = '|'.join(re.escape(x.strip()) for x in partner.bank_statement_label.split(';')) |
3034 | + or_regex = ".*%s.*" % vals |
3035 | + sql = ("SELECT id from account_bank_statement_line" |
3036 | + " WHERE id in %s" |
3037 | + " AND name ~* %s") |
3038 | + cr.execute(sql, (line_ids, or_regex)) |
3039 | + pairs = cr.fetchall() |
3040 | + for pair in pairs: |
3041 | + context['label_memoizer'][pair[0]].append(partner) |
3042 | + |
3043 | + if st_line['id'] in context['label_memoizer']: |
3044 | + found_partner = context['label_memoizer'][st_line['id']] |
3045 | + if len(found_partner) > 1: |
3046 | + msg = (_('Line named "%s" (Ref:%s) was matched by ' |
3047 | + 'more than one partner while looking on partner label: %s') % |
3048 | + (st_line['name'], st_line['ref'], ','.join([x.name for x in found_partner]))) |
3049 | + raise ErrorTooManyPartner(msg) |
3050 | + res['partner_id'] = found_partner[0].id |
3051 | + st_vals = st_obj.get_values_for_line(cr, |
3052 | + uid, |
3053 | + profile_id=st_line['profile_id'], |
3054 | + master_account_id=st_line['master_account_id'], |
3055 | + partner_id=found_partner[0].id, |
3056 | + line_type=False, |
3057 | + amount=st_line['amount'] if st_line['amount'] else 0.0, |
3058 | + context=context) |
3059 | + res.update(st_vals) |
3060 | + return res |
3061 | + |
3062 | + def get_from_label_and_partner_name(self, cr, uid, st_line, context=None): |
3063 | + """ |
3064 | + Match the partner based on the label field of the statement line |
3065 | + and the name of the partner. |
3066 | + Then, call the generic get_values_for_line method to complete other values. |
3067 | + If more than one partner matched, raise the ErrorTooManyPartner error. |
3068 | + |
3069 | + :param dict st_line: read of the concerned account.bank.statement.line |
3070 | + :return: |
3071 | + A dict of value that can be passed directly to the write method of |
3072 | + the statement line or {} |
3073 | + {'partner_id': value, |
3074 | + 'account_id': value, |
3075 | + |
3076 | + ...} |
3077 | + """ |
3078 | + res = {} |
3079 | + # We memoize allowed partner |
3080 | + if not context.get('partner_memoizer'): |
3081 | + context['partner_memoizer'] = tuple(self.pool['res.partner'].search(cr, uid, [])) |
3082 | + if not context['partner_memoizer']: |
3083 | + return res |
3084 | + st_obj = self.pool.get('account.bank.statement.line') |
3085 | + sql = "SELECT id FROM res_partner WHERE name ~* %s and id in %s" |
3086 | + pattern = ".*%s.*" % re.escape(st_line['name']) |
3087 | + cr.execute(sql, (pattern, context['partner_memoizer'])) |
3088 | + result = cr.fetchall() |
3089 | + if not result: |
3090 | + return res |
3091 | + if len(result) > 1: |
3092 | + raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more ' |
3093 | + 'than one partner while looking on partner by name') % |
3094 | + (st_line['name'], st_line['ref'])) |
3095 | + res['partner_id'] = result[0][0] |
3096 | + st_vals = st_obj.get_values_for_line(cr, |
3097 | + uid, |
3098 | + profile_id=st_line['porfile_id'], |
3099 | + master_account_id=st_line['master_account_id'], |
3100 | + partner_id=res['partner_id'], |
3101 | + line_type=False, |
3102 | + amount=st_line['amount'] if st_line['amount'] else 0.0, |
3103 | + context=context) |
3104 | + res.update(st_vals) |
3105 | + return res |
3106 | + |
3107 | + |
3108 | +class AccountStatementLine(orm.Model): |
3109 | + """ |
3110 | + Add sparse field on the statement line to allow to store all the |
3111 | + bank infos that are given by a bank/office. You can then add you own in your |
3112 | + module. The idea here is to store all bank/office infos in the additionnal_bank_fields |
3113 | + serialized field when importing the file. If many values, add a tab in the bank |
3114 | + statement line to store your specific one. Have a look in account_statement_base_import |
3115 | + module to see how we've done it. |
3116 | + """ |
3117 | + _inherit = "account.bank.statement.line" |
3118 | + |
3119 | + _columns = { |
3120 | + 'additionnal_bank_fields': fields.serialized( |
3121 | + 'Additionnal infos from bank', |
3122 | + help="Used by completion and import system. Adds every field that " |
3123 | + "is present in your bank/office statement file"), |
3124 | + 'label': fields.sparse( |
3125 | + type='char', |
3126 | + string='Label', |
3127 | + serialization_field='additionnal_bank_fields', |
3128 | + help="Generic field to store a label given from the " |
3129 | + "bank/office on which we can base the default/standard " |
3130 | + "providen rule."), |
3131 | + 'already_completed': fields.boolean( |
3132 | + "Auto-Completed", |
3133 | + help="When this checkbox is ticked, the auto-completion " |
3134 | + "process/button will ignore this line."), |
3135 | + } |
3136 | + |
3137 | + _defaults = { |
3138 | + 'already_completed': False, |
3139 | + } |
3140 | + |
3141 | + def _get_line_values_from_rules(self, cr, uid, line, rules, context=None): |
3142 | + """ |
3143 | + We'll try to find out the values related to the line based on rules setted on |
3144 | + the profile.. We will ignore line for which already_completed is ticked. |
3145 | + |
3146 | + :return: |
3147 | + A dict of dict value that can be passed directly to the write method of |
3148 | + the statement line or {}. The first dict has statement line ID as a key: |
3149 | + {117009: {'partner_id': 100997, 'account_id': 489L}} |
3150 | + """ |
3151 | + profile_obj = self.pool.get('account.statement.profile') |
3152 | + if line.get('already_completed'): |
3153 | + return {} |
3154 | + # Ask the rule |
3155 | + vals = profile_obj._find_values_from_rules(cr, uid, rules, line, context) |
3156 | + if vals: |
3157 | + vals['id'] = line['id'] |
3158 | + return vals |
3159 | + return {} |
3160 | + |
3161 | + |
3162 | +class AccountBankSatement(orm.Model): |
3163 | + """ |
3164 | + We add a basic button and stuff to support the auto-completion |
3165 | + of the bank statement once line have been imported or manually fullfill. |
3166 | + """ |
3167 | + _inherit = "account.bank.statement" |
3168 | + |
3169 | + _columns = { |
3170 | + 'completion_logs': fields.text('Completion Log', readonly=True), |
3171 | + } |
3172 | + |
3173 | + def write_completion_log(self, cr, uid, stat_id, error_msg, number_imported, context=None): |
3174 | + """ |
3175 | + Write the log in the completion_logs field of the bank statement to let the user |
3176 | + know what have been done. This is an append mode, so we don't overwrite what |
3177 | + already recoded. |
3178 | + |
3179 | + :param int/long stat_id: ID of the account.bank.statement |
3180 | + :param char error_msg: Message to add |
3181 | + :number_imported int/long: Number of lines that have been completed |
3182 | + :return True |
3183 | + """ |
3184 | + user_name = self.pool.get('res.users').read(cr, uid, uid, |
3185 | + ['name'], context=context)['name'] |
3186 | + |
3187 | + log = self.read(cr, uid, stat_id, ['completion_logs'], |
3188 | + context=context)['completion_logs'] |
3189 | + log = log if log else "" |
3190 | + |
3191 | + completion_date = datetime.datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT) |
3192 | + message = (_("%s Bank Statement ID %s has %s lines completed by %s \n%s\n%s\n") % |
3193 | + (completion_date, stat_id, number_imported, user_name, error_msg, log)) |
3194 | + self.write(cr, uid, [stat_id], {'completion_logs': message}, context=context) |
3195 | + |
3196 | + body = (_('Statement ID %s auto-completed for %s lines completed') % |
3197 | + (stat_id, number_imported)), |
3198 | + self.message_post(cr, uid, |
3199 | + [stat_id], |
3200 | + body=body, |
3201 | + context=context) |
3202 | + return True |
3203 | + |
3204 | + def button_auto_completion(self, cr, uid, ids, context=None): |
3205 | + """ |
3206 | + Complete line with values given by rules and tic the already_completed |
3207 | + checkbox so we won't compute them again unless the user untick them! |
3208 | + """ |
3209 | + if context is None: |
3210 | + context = {} |
3211 | + stat_line_obj = self.pool['account.bank.statement.line'] |
3212 | + profile_obj = self.pool.get('account.statement.profile') |
3213 | + compl_lines = 0 |
3214 | + stat_line_obj.check_access_rule(cr, uid, [], 'create') |
3215 | + stat_line_obj.check_access_rights(cr, uid, 'create', raise_exception=True) |
3216 | + for stat in self.browse(cr, uid, ids, context=context): |
3217 | + msg_lines = [] |
3218 | + ctx = context.copy() |
3219 | + ctx['line_ids'] = tuple((x.id for x in stat.line_ids)) |
3220 | + b_profile = stat.profile_id |
3221 | + rules = profile_obj._get_callable(cr, uid, b_profile, context=context) |
3222 | + profile_id = b_profile.id # Only for perfo even it gains almost nothing |
3223 | + master_account_id = b_profile.receivable_account_id |
3224 | + master_account_id = master_account_id.id if master_account_id else False |
3225 | + res = False |
3226 | + for line in stat_line_obj.read(cr, uid, ctx['line_ids']): |
3227 | + try: |
3228 | + # performance trick |
3229 | + line['master_account_id'] = master_account_id |
3230 | + line['profile_id'] = profile_id |
3231 | + res = stat_line_obj._get_line_values_from_rules(cr, uid, line, |
3232 | + rules, context=ctx) |
3233 | + if res: |
3234 | + compl_lines += 1 |
3235 | + except ErrorTooManyPartner, exc: |
3236 | + msg_lines.append(repr(exc)) |
3237 | + except Exception, exc: |
3238 | + msg_lines.append(repr(exc)) |
3239 | + error_type, error_value, trbk = sys.exc_info() |
3240 | + st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value) |
3241 | + st += ''.join(traceback.format_tb(trbk, 30)) |
3242 | + _logger.error(st) |
3243 | + if res: |
3244 | + #stat_line_obj.write(cr, uid, [line.id], vals, context=ctx) |
3245 | + try: |
3246 | + stat_line_obj._update_line(cr, uid, res, context=context) |
3247 | + except Exception as exc: |
3248 | + msg_lines.append(repr(exc)) |
3249 | + error_type, error_value, trbk = sys.exc_info() |
3250 | + st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value) |
3251 | + st += ''.join(traceback.format_tb(trbk, 30)) |
3252 | + _logger.error(st) |
3253 | + # we can commit as it is not needed to be atomic |
3254 | + # commiting here adds a nice perfo boost |
3255 | + if not compl_lines % 500: |
3256 | + cr.commit() |
3257 | + msg = u'\n'.join(msg_lines) |
3258 | + self.write_completion_log(cr, uid, stat.id, |
3259 | + msg, compl_lines, context=context) |
3260 | + return True |
3261 | |
3262 | === added file 'account_statement_base_completion/statement_view.xml' |
3263 | --- account_statement_base_completion/statement_view.xml 1970-01-01 00:00:00 +0000 |
3264 | +++ account_statement_base_completion/statement_view.xml 2013-07-17 14:55:41 +0000 |
3265 | @@ -0,0 +1,104 @@ |
3266 | +<?xml version="1.0" encoding="utf-8"?> |
3267 | +<openerp> |
3268 | +<data> |
3269 | + |
3270 | + <record id="bank_statement_view_form" model="ir.ui.view"> |
3271 | + <field name="name">account_bank_statement_import_base.bank_statement.view_form</field> |
3272 | + <field name="model">account.bank.statement</field> |
3273 | + <field name="inherit_id" ref="account.view_bank_statement_form" /> |
3274 | + <field eval="16" name="priority"/> |
3275 | + <field name="type">form</field> |
3276 | + <field name="arch" type="xml"> |
3277 | + <data> |
3278 | + <xpath expr="/form/sheet/notebook/page/field[@name='line_ids']/form/group/field[@name='sequence']" position="after"> |
3279 | + <separator colspan="4" string="Importation related infos"/> |
3280 | + <field name="label" /> |
3281 | + <field name="already_completed" /> |
3282 | + </xpath> |
3283 | + |
3284 | + <!-- <xpath expr="/form/group[2]" position="attributes"> |
3285 | + <attribute name="col">10</attribute> |
3286 | + </xpath> --> |
3287 | + |
3288 | + <xpath expr="/form/sheet/div[@name='import_buttons']" position="after"> |
3289 | + <button name="button_auto_completion" string="Auto Completion" states='draft,open' type="object" colspan="1"/> |
3290 | + </xpath> |
3291 | + |
3292 | + <xpath expr="/form/sheet/notebook/page[@string='Transactions']" position="after"> |
3293 | + <page string="Completion Logs" attrs="{'invisible':[('completion_logs','=',False)]}"> |
3294 | + <field name="completion_logs" colspan="4" nolabel="1" attrs="{'invisible':[('completion_logs','=',False)]}"/> |
3295 | + </page> |
3296 | + </xpath> |
3297 | + |
3298 | + </data> |
3299 | + </field> |
3300 | + </record> |
3301 | + <record id="bank_statement_view_form2" model="ir.ui.view"> |
3302 | + <field name="name">account_bank_statement_import_base.bank_statement.auto_cmpl</field> |
3303 | + <field name="model">account.bank.statement</field> |
3304 | + <field name="inherit_id" ref="account.view_bank_statement_form" /> |
3305 | + <field name="type">form</field> |
3306 | + <field name="arch" type="xml"> |
3307 | + <data> |
3308 | + <xpath expr="/form/sheet/notebook/page/field[@name='line_ids']/tree/field[@name='amount']" position="after"> |
3309 | + <field name="already_completed" /> |
3310 | + </xpath> |
3311 | + </data> |
3312 | + </field> |
3313 | + </record> |
3314 | + |
3315 | + <record id="statement_rules_view_form" model="ir.ui.view"> |
3316 | + <field name="name">account.statement.profile.view</field> |
3317 | + <field name="model">account.statement.profile</field> |
3318 | + <field name="inherit_id" ref="account_statement_ext.statement_importer_view_form"/> |
3319 | + <field name="type">form</field> |
3320 | + <field name="arch" type="xml"> |
3321 | + <field name="bank_statement_prefix" position="after"> |
3322 | + <separator colspan="4" string="Auto-Completion Rules"/> |
3323 | + <field name="rule_ids" colspan="4" nolabel="1"/> |
3324 | + </field> |
3325 | + </field> |
3326 | + </record> |
3327 | + |
3328 | + |
3329 | + <record id="statement_st_completion_rule_view_form" model="ir.ui.view"> |
3330 | + <field name="name">account.statement.completion.rule.view</field> |
3331 | + <field name="model">account.statement.completion.rule</field> |
3332 | + <field name="type">form</field> |
3333 | + <field name="arch" type="xml"> |
3334 | + <form string="Statement Completion Rule"> |
3335 | + <field name="sequence"/> |
3336 | + <field name="name" select="1" /> |
3337 | + <field name="function_to_call"/> |
3338 | + <separator colspan="4" string="Related Profiles"/> |
3339 | + <field name="profile_ids" nolabel="1" colspan="4"/> |
3340 | + </form> |
3341 | + </field> |
3342 | + </record> |
3343 | + |
3344 | + <record id="statement_st_completion_rule_view_tree" model="ir.ui.view"> |
3345 | + <field name="name">account.statement.completion.rule.view</field> |
3346 | + <field name="model">account.statement.completion.rule</field> |
3347 | + <field name="type">tree</field> |
3348 | + <field name="arch" type="xml"> |
3349 | + <tree string="Statement Completion Rule"> |
3350 | + <field name="sequence"/> |
3351 | + <field name="name" select="1" /> |
3352 | + <field name="profile_ids" /> |
3353 | + <field name="function_to_call"/> |
3354 | + </tree> |
3355 | + </field> |
3356 | + </record> |
3357 | + <record id="action_st_completion_rule_tree" model="ir.actions.act_window"> |
3358 | + <field name="name">Statement Completion Rule</field> |
3359 | + <field name="res_model">account.statement.completion.rule</field> |
3360 | + <field name="view_type">form</field> |
3361 | + <field name="view_mode">tree,form</field> |
3362 | + </record> |
3363 | + |
3364 | + <menuitem string="Statement Completion Rule" action="action_st_completion_rule_tree" |
3365 | + id="menu_action_st_completion_rule_tree_menu" parent="account.menu_configuration_misc" |
3366 | + sequence="30"/> |
3367 | + |
3368 | +</data> |
3369 | +</openerp> |
3370 | |
3371 | === added directory 'account_statement_base_import' |
3372 | === added file 'account_statement_base_import/__init__.py' |
3373 | --- account_statement_base_import/__init__.py 1970-01-01 00:00:00 +0000 |
3374 | +++ account_statement_base_import/__init__.py 2013-07-17 14:55:41 +0000 |
3375 | @@ -0,0 +1,23 @@ |
3376 | +# -*- coding: utf-8 -*- |
3377 | +############################################################################## |
3378 | +# |
3379 | +# Author: Joel Grand-Guillaume |
3380 | +# Copyright 2011-2012 Camptocamp SA |
3381 | +# |
3382 | +# This program is free software: you can redistribute it and/or modify |
3383 | +# it under the terms of the GNU Affero General Public License as |
3384 | +# published by the Free Software Foundation, either version 3 of the |
3385 | +# License, or (at your option) any later version. |
3386 | +# |
3387 | +# This program is distributed in the hope that it will be useful, |
3388 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3389 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3390 | +# GNU Affero General Public License for more details. |
3391 | +# |
3392 | +# You should have received a copy of the GNU Affero General Public License |
3393 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
3394 | +# |
3395 | +############################################################################## |
3396 | +import parser |
3397 | +import wizard |
3398 | +import statement |
3399 | |
3400 | === added file 'account_statement_base_import/__openerp__.py' |
3401 | --- account_statement_base_import/__openerp__.py 1970-01-01 00:00:00 +0000 |
3402 | +++ account_statement_base_import/__openerp__.py 2013-07-17 14:55:41 +0000 |
3403 | @@ -0,0 +1,70 @@ |
3404 | +# -*- coding: utf-8 -*- |
3405 | +############################################################################## |
3406 | +# |
3407 | +# Author: Joel Grand-Guillaume |
3408 | +# Copyright 2011-2012 Camptocamp SA |
3409 | +# |
3410 | +# This program is free software: you can redistribute it and/or modify |
3411 | +# it under the terms of the GNU Affero General Public License as |
3412 | +# published by the Free Software Foundation, either version 3 of the |
3413 | +# License, or (at your option) any later version. |
3414 | +# |
3415 | +# This program is distributed in the hope that it will be useful, |
3416 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3417 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3418 | +# GNU Affero General Public License for more details. |
3419 | +# |
3420 | +# You should have received a copy of the GNU Affero General Public License |
3421 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
3422 | +# |
3423 | +############################################################################## |
3424 | + |
3425 | +{'name': "Bank statement base import", |
3426 | + 'version': '1.0', |
3427 | + 'author': 'Camptocamp', |
3428 | + 'maintainer': 'Camptocamp', |
3429 | + 'category': 'Finance', |
3430 | + 'complexity': 'normal', |
3431 | + 'depends': [ |
3432 | + 'account_statement_ext', |
3433 | + 'account_statement_base_completion' |
3434 | + ], |
3435 | + 'description': """ |
3436 | + This module brings basic methods and fields on bank statement to deal with |
3437 | + the importation of different bank and offices. A generic abstract method is defined and an |
3438 | + example that gives you a basic way of importing bank statement through a standard file is provided. |
3439 | + |
3440 | + This module improves the bank statement and allows you to import your bank transactions with |
3441 | + a standard .csv or .xls file (you'll find it in the 'data' folder). It respects the profile |
3442 | + (provided by the accouhnt_statement_ext module) to pass the entries. That means, |
3443 | + you'll have to choose a file format for each profile. |
3444 | + In order to achieve this it uses the `xlrd` Python module which you will need to install |
3445 | + separately in your environment. |
3446 | + |
3447 | + This module can handle a commission taken by the payment office and has the following format: |
3448 | + |
3449 | + * ref : the SO number, INV number or any matching ref found. It'll be used as reference |
3450 | + in the generated entries and will be useful for reconciliation process |
3451 | + * date : date of the payment |
3452 | + * amount : amount paid in the currency of the journal used in the importation profile |
3453 | + * commission_amount : amount of the comission for each line |
3454 | + * label : the comunication given by the payment office, used as communication in the |
3455 | + generated entries. |
3456 | + |
3457 | + The goal is here to populate the statement lines of a bank statement with the infos that the |
3458 | + bank or office give you. Fell free to inherit from this module to add your own format.Then, |
3459 | + if you need to complete data from there, add your own account_statement_*_completion module and implement |
3460 | + the needed rules. |
3461 | + |
3462 | + """, |
3463 | + 'website': 'http://www.camptocamp.com', |
3464 | + 'data': [ |
3465 | + "wizard/import_statement_view.xml", |
3466 | + "statement_view.xml", |
3467 | + ], |
3468 | + 'test': [], |
3469 | + 'installable': True, |
3470 | + 'images': [], |
3471 | + 'auto_install': False, |
3472 | + 'license': 'AGPL-3', |
3473 | +} |
3474 | |
3475 | === added directory 'account_statement_base_import/data' |
3476 | === added file 'account_statement_base_import/data/statement.csv' |
3477 | --- account_statement_base_import/data/statement.csv 1970-01-01 00:00:00 +0000 |
3478 | +++ account_statement_base_import/data/statement.csv 2013-07-17 14:55:41 +0000 |
3479 | @@ -0,0 +1,4 @@ |
3480 | +"ref";"date";"amount";"commission_amount";"label" |
3481 | +50969286;2011-03-07 13:45:14;118.4;-11.84;"label a" |
3482 | +51065326;2011-03-05 13:45:14;189;-15.12;"label b" |
3483 | +51179306;2011-03-02 17:45:14;189;-15.12;"label c" |
3484 | |
3485 | === added file 'account_statement_base_import/data/statement.xls' |
3486 | Binary files account_statement_base_import/data/statement.xls 1970-01-01 00:00:00 +0000 and account_statement_base_import/data/statement.xls 2013-07-17 14:55:41 +0000 differ |
3487 | === added directory 'account_statement_base_import/i18n' |
3488 | === added file 'account_statement_base_import/i18n/fr.po' |
3489 | --- account_statement_base_import/i18n/fr.po 1970-01-01 00:00:00 +0000 |
3490 | +++ account_statement_base_import/i18n/fr.po 2013-07-17 14:55:41 +0000 |
3491 | @@ -0,0 +1,253 @@ |
3492 | +#. module: account_statement_base_import |
3493 | +#: view:credit.statement.import:0 |
3494 | +#: model:ir.actions.act_window,name:account_statement_base_import.statement_importer_action |
3495 | +msgid "Import statement" |
3496 | +msgstr "Import de relevé" |
3497 | + |
3498 | +#. module: account_statement_base_import |
3499 | +#: model:ir.model,name:account_statement_base_import.model_credit_statement_import |
3500 | +msgid "credit.statement.import" |
3501 | +msgstr "credit.statement.import" |
3502 | + |
3503 | +#. module: account_statement_base_import |
3504 | +#: code:addons/account_statement_base_import/statement.py:218 |
3505 | +#, python-format |
3506 | +msgid "The statement cannot be created : %s" |
3507 | +msgstr "Le relevé ne peut être créé : %s" |
3508 | + |
3509 | +#. module: account_statement_base_import |
3510 | +#: field:credit.statement.import,input_statement:0 |
3511 | +msgid "Statement file" |
3512 | +msgstr "Fichier à importer" |
3513 | + |
3514 | +#. module: account_statement_base_import |
3515 | +#: view:account.statement.profile:0 |
3516 | +msgid "Import Logs" |
3517 | +msgstr "Journaux d'import" |
3518 | + |
3519 | +#. module: account_statement_base_import |
3520 | +#: field:credit.statement.import,journal_id:0 |
3521 | +msgid "Financial journal to use transaction" |
3522 | +msgstr "Journal" |
3523 | + |
3524 | +#. module: account_statement_base_import |
3525 | +#: code:addons/account_statement_base_import/parser/file_parser.py:100 |
3526 | +#, python-format |
3527 | +msgid "Column %s not present in file" |
3528 | +msgstr "Colonne %s non présente dans le fichier" |
3529 | + |
3530 | +#. module: account_statement_base_import |
3531 | +#: view:account.statement.profile:0 |
3532 | +#: model:ir.ui.menu,name:account_statement_base_import.statement_importer_menu |
3533 | +msgid "Import Bank Statement" |
3534 | +msgstr "Importation de relevé" |
3535 | + |
3536 | +#. module: account_statement_base_import |
3537 | +#: model:ir.model,name:account_statement_base_import.model_account_statement_profile |
3538 | +msgid "Statement Profil" |
3539 | +msgstr "Profil du relevé" |
3540 | + |
3541 | +#. module: account_statement_base_import |
3542 | +#: code:addons/account_statement_base_import/statement.py:100 |
3543 | +#, python-format |
3544 | +msgid "Commission line" |
3545 | +msgstr "Ligne de commission" |
3546 | + |
3547 | +#. module: account_statement_base_import |
3548 | +#: field:credit.statement.import,commission_account_id:0 |
3549 | +msgid "Commission account" |
3550 | +msgstr "Compte de commission" |
3551 | + |
3552 | +#. module: account_statement_base_import |
3553 | +#: code:addons/account_statement_base_import/statement.py:172 |
3554 | +#, python-format |
3555 | +msgid "Column %s you try to import is not present in the bank statement line !" |
3556 | +msgstr "" |
3557 | +"La colonne %s que vous essayez d'importer n'est pas présente dans la ligne de relevé !" |
3558 | + |
3559 | +#. module: account_statement_base_import |
3560 | +#: code:addons/account_statement_base_import/statement.py:71 |
3561 | +#, python-format |
3562 | +msgid "Bank Statement ID %s have been imported with %s lines " |
3563 | +msgstr "Le relevé avec l'ID %s a été importé avec %s lignes " |
3564 | + |
3565 | +#. module: account_statement_base_import |
3566 | +#: field:account.statement.profile,launch_import_completion:0 |
3567 | +msgid "Launch completion after import" |
3568 | +msgstr "Lancer l'auto-complétion après import" |
3569 | + |
3570 | +#. module: account_statement_base_import |
3571 | +#: field:credit.statement.import,partner_id:0 |
3572 | +msgid "Credit insitute partner" |
3573 | +msgstr "Organisme bancaire" |
3574 | + |
3575 | +#. module: account_statement_base_import |
3576 | +#: code:addons/account_statement_base_import/statement.py:160 |
3577 | +#, python-format |
3578 | +msgid "No Profile !" |
3579 | +msgstr "Aucun Profil !" |
3580 | + |
3581 | +#. module: account_statement_base_import |
3582 | +#: view:account.statement.profile:0 |
3583 | +msgid "Import related infos" |
3584 | +msgstr "Importation des informations liées" |
3585 | + |
3586 | +#. module: account_statement_base_import |
3587 | +#: help:credit.statement.import,force_partner_on_bank:0 |
3588 | +msgid "" |
3589 | +"Tic that box if you want to use the credit insitute " |
3590 | +"partner in the " |
3591 | +"counterpart of the treasury/banking move." |
3592 | +msgstr "" |
3593 | +"Cochez cette case si vous voulez utiliser comme partenaire " |
3594 | +"l'organisme bancaire au niveau de la ligne de banque" |
3595 | + |
3596 | +#. module: account_statement_base_import |
3597 | +#: code:addons/account_statement_base_import/wizard/import_statement.py:93 |
3598 | +#, python-format |
3599 | +msgid "Please use a file with an extention" |
3600 | +msgstr "Veuillez sélectionner un fichier avec une extension" |
3601 | + |
3602 | +#. module: account_statement_base_import |
3603 | +#: code:addons/account_statement_base_import/statement.py:161 |
3604 | +#, python-format |
3605 | +msgid "You must provide a valid profile to import a bank statement !" |
3606 | +msgstr "Vous devez fournir un profil valide pour importer un relevé !" |
3607 | + |
3608 | +#. module: account_statement_base_import |
3609 | +#: help:credit.statement.import,balance_check:0 |
3610 | +msgid "" |
3611 | +"Tic that box if you want OpenERP to control the start/end " |
3612 | +"balance before confirming " |
3613 | +"a bank statement. If don't ticked, no balance control will be done." |
3614 | +msgstr "" |
3615 | +"Cochez cette case si vous souhaitez un contrôle du solde final " |
3616 | +"avant la validation du relevé." |
3617 | + |
3618 | +#. module: account_statement_base_import |
3619 | +#: code:addons/account_statement_base_import/parser/file_parser.py:31 |
3620 | +#: code:addons/account_statement_base_import/parser/generic_file_parser.py:31 |
3621 | +#, python-format |
3622 | +msgid "Please install python lib xlrd" |
3623 | +msgstr "Veuillez installer la bibliothèque python xlrd" |
3624 | + |
3625 | +#. module: account_statement_base_import |
3626 | +#: code:addons/account_statement_base_import/parser/file_parser.py:58 |
3627 | +#, python-format |
3628 | +msgid "Invalide file type %s. please use csv or xls" |
3629 | +msgstr "Type de fichier invalide : %s. Veuillez utiliser un csv ou un xls" |
3630 | + |
3631 | +#. module: account_statement_base_import |
3632 | +#: field:account.statement.profile,last_import_date:0 |
3633 | +msgid "Last Import Date" |
3634 | +msgstr "Date de dernier import" |
3635 | + |
3636 | +#. module: account_statement_base_import |
3637 | +#: field:credit.statement.import,commission_analytic_id:0 |
3638 | +msgid "Commission analytic account" |
3639 | +msgstr "Compte analytique de la commission" |
3640 | + |
3641 | +#. module: account_statement_base_import |
3642 | +#: constraint:account.statement.profile:0 |
3643 | +msgid "You need to put a partner if you tic the 'Force partner on bank move' !" |
3644 | +msgstr "" |
3645 | +"Vous devez spécifier un partenaire si vous avez coché 'Forcer un partenaire sur la ligne de la banque' !" |
3646 | + |
3647 | +#. module: account_statement_base_import |
3648 | +#: code:addons/account_statement_base_import/statement.py:217 |
3649 | +#, python-format |
3650 | +msgid "Statement import error" |
3651 | +msgstr "Erreur d'import de relevé" |
3652 | + |
3653 | +#. module: account_statement_base_import |
3654 | +#: field:account.bank.statement.line,commission_amount:0 |
3655 | +msgid "Line Commission Amount" |
3656 | +msgstr "Montant de la commission de la ligne" |
3657 | + |
3658 | +#. module: account_statement_base_import |
3659 | +#: code:addons/account_statement_base_import/statement.py:171 |
3660 | +#, python-format |
3661 | +msgid "Missing column !" |
3662 | +msgstr "Colonne manquante !" |
3663 | + |
3664 | +#. module: account_statement_base_import |
3665 | +#: field:account.statement.profile,rec_log:0 |
3666 | +msgid "log" |
3667 | +msgstr "journal" |
3668 | + |
3669 | +#. module: account_statement_base_import |
3670 | +#: field:account.statement.profile,import_type:0 |
3671 | +msgid "Type of import" |
3672 | +msgstr "Type d'import" |
3673 | + |
3674 | +#. module: account_statement_base_import |
3675 | +#: constraint:account.bank.statement.line:0 |
3676 | +msgid "" |
3677 | +"The amount of the voucher must be the same amount as the one on the " |
3678 | +"statement line" |
3679 | +msgstr "" |
3680 | +"Le montant du justificatif doit être identique à celui de la ligne le " |
3681 | +"concernant sur le relevé" |
3682 | + |
3683 | +#. module: account_statement_base_import |
3684 | +#: help:account.statement.profile,launch_import_completion:0 |
3685 | +msgid "" |
3686 | +"Tic that box to automatically launch the completion on each " |
3687 | +"imported file using this profile." |
3688 | +msgstr "" |
3689 | +"Cocher cette case pour lancer automatiquement l'auto-complétion sur" |
3690 | +"chaque fichier importé avec ce profil." |
3691 | + |
3692 | +#. module: account_statement_base_import |
3693 | +#: field:credit.statement.import,profile_id:0 |
3694 | +msgid "Import configuration parameter" |
3695 | +msgstr "Paramètres de configuration d'import" |
3696 | + |
3697 | +#. module: account_statement_base_import |
3698 | +#: code:addons/account_statement_base_import/parser/parser.py:156 |
3699 | +#, python-format |
3700 | +msgid "No buffer file given." |
3701 | +msgstr "Pas de fichier tampon donné." |
3702 | + |
3703 | +#. module: account_statement_base_import |
3704 | +#: view:credit.statement.import:0 |
3705 | +msgid "Import Parameters Summary" |
3706 | +msgstr "Résumé des paramètres d'import" |
3707 | + |
3708 | +#. module: account_statement_base_import |
3709 | +#: field:credit.statement.import,balance_check:0 |
3710 | +msgid "Balance check" |
3711 | +msgstr "Vérification des soldes" |
3712 | + |
3713 | +#. module: account_statement_base_import |
3714 | +#: model:ir.model,name:account_statement_base_import.model_account_bank_statement_line |
3715 | +msgid "Bank Statement Line" |
3716 | +msgstr "Ligne de relevé bancaire" |
3717 | + |
3718 | +#. module: account_statement_base_import |
3719 | +#: field:credit.statement.import,force_partner_on_bank:0 |
3720 | +msgid "Force partner on bank move" |
3721 | +msgstr "Forcer un partenaire sur la ligne du compte de banque" |
3722 | + |
3723 | +#. module: account_statement_base_import |
3724 | +#: field:credit.statement.import,file_name:0 |
3725 | +msgid "File Name" |
3726 | +msgstr "Nom du fichier" |
3727 | + |
3728 | +#. module: account_statement_base_import |
3729 | +#: field:credit.statement.import,receivable_account_id:0 |
3730 | +msgid "Force Receivable/Payable Account" |
3731 | +msgstr "Forcer le compte Client/Fournisseur" |
3732 | + |
3733 | +#. module: account_statement_base_import |
3734 | +#: help:account.statement.profile,import_type:0 |
3735 | +msgid "" |
3736 | +"Choose here the method by which you want to import bank statement for this " |
3737 | +"profile." |
3738 | +msgstr "" |
3739 | +"Choisissez la méthode d'import de relevé pour ce profil." |
3740 | + |
3741 | +#. module: account_statement_base_import |
3742 | +#: view:credit.statement.import:0 |
3743 | +msgid "Cancel" |
3744 | +msgstr "Annulation" |
3745 | |
3746 | === added directory 'account_statement_base_import/parser' |
3747 | === added file 'account_statement_base_import/parser/__init__.py' |
3748 | --- account_statement_base_import/parser/__init__.py 1970-01-01 00:00:00 +0000 |
3749 | +++ account_statement_base_import/parser/__init__.py 2013-07-17 14:55:41 +0000 |
3750 | @@ -0,0 +1,25 @@ |
3751 | +# -*- coding: utf-8 -*- |
3752 | +############################################################################## |
3753 | +# |
3754 | +# Author: Nicolas Bessi, Joel Grand-Guillaume |
3755 | +# Copyright 2011-2012 Camptocamp SA |
3756 | +# |
3757 | +# This program is free software: you can redistribute it and/or modify |
3758 | +# it under the terms of the GNU Affero General Public License as |
3759 | +# published by the Free Software Foundation, either version 3 of the |
3760 | +# License, or (at your option) any later version. |
3761 | +# |
3762 | +# This program is distributed in the hope that it will be useful, |
3763 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3764 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3765 | +# GNU Affero General Public License for more details. |
3766 | +# |
3767 | +# You should have received a copy of the GNU Affero General Public License |
3768 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
3769 | +# |
3770 | +############################################################################## |
3771 | + |
3772 | +from parser import new_bank_statement_parser |
3773 | +from parser import BankStatementImportParser |
3774 | +import file_parser |
3775 | +import generic_file_parser |
3776 | |
3777 | === added file 'account_statement_base_import/parser/file_parser.py' |
3778 | --- account_statement_base_import/parser/file_parser.py 1970-01-01 00:00:00 +0000 |
3779 | +++ account_statement_base_import/parser/file_parser.py 2013-07-17 14:55:41 +0000 |
3780 | @@ -0,0 +1,218 @@ |
3781 | +# -*- coding: utf-8 -*- |
3782 | +############################################################################## |
3783 | +# |
3784 | +# Copyright Camptocamp SA |
3785 | +# Author Nicolas Bessi, Joel Grand-Guillaume |
3786 | +# This program is free software: you can redistribute it and/or modify |
3787 | +# it under the terms of the GNU General Public License as published by |
3788 | +# the Free Software Foundation, either version 3 of the License, or |
3789 | +# (at your option) any later version. |
3790 | +# |
3791 | +# This program is distributed in the hope that it will be useful, |
3792 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3793 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3794 | +# GNU General Public License for more details. |
3795 | +# |
3796 | +# You should have received a copy of the GNU General Public License |
3797 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
3798 | +# |
3799 | +############################################################################## |
3800 | +from openerp.tools.translate import _ |
3801 | +from openerp.osv.osv import except_osv |
3802 | +import tempfile |
3803 | +import datetime |
3804 | +from parser import BankStatementImportParser |
3805 | +from parser import UnicodeDictReader |
3806 | +try: |
3807 | + import xlrd |
3808 | +except: |
3809 | + raise Exception(_('Please install python lib xlrd')) |
3810 | + |
3811 | + |
3812 | +class FileParser(BankStatementImportParser): |
3813 | + """ |
3814 | + Generic abstract class for defining parser for .csv or .xls file format. |
3815 | + """ |
3816 | + |
3817 | + def __init__(self, parse_name, keys_to_validate=None, ftype='csv', conversion_dict=None, |
3818 | + header=None, *args, **kwargs): |
3819 | + """ |
3820 | + :param char: parse_name : The name of the parser |
3821 | + :param list: keys_to_validate : contain the key that need to be present in the file |
3822 | + :param char ftype: extension of the file (could be csv or xls) |
3823 | + :param: conversion_dict : keys and type to convert of every column in the file like |
3824 | + { |
3825 | + 'ref': unicode, |
3826 | + 'label': unicode, |
3827 | + 'date': datetime.datetime, |
3828 | + 'amount': float, |
3829 | + 'commission_amount': float |
3830 | + } |
3831 | + :param list: header : specify header fields if the csv file has no header |
3832 | + """ |
3833 | + |
3834 | + super(FileParser, self).__init__(parse_name, *args, **kwargs) |
3835 | + if ftype in ('csv', 'xls'): |
3836 | + self.ftype = ftype |
3837 | + else: |
3838 | + raise except_osv(_('User Error'), |
3839 | + _('Invalid file type %s. Please use csv or xls') % ftype) |
3840 | + self.keys_to_validate = keys_to_validate if keys_to_validate is not None else [] |
3841 | + self.conversion_dict = conversion_dict |
3842 | + self.fieldnames = header |
3843 | + self._datemode = 0 # used only for xls documents, |
3844 | + # 0 means Windows mode (1900 based dates). |
3845 | + # Set in _parse_xls, from the contents of the file |
3846 | + |
3847 | + def _custom_format(self, *args, **kwargs): |
3848 | + """ |
3849 | + No other work on data are needed in this parser. |
3850 | + """ |
3851 | + return True |
3852 | + |
3853 | + def _pre(self, *args, **kwargs): |
3854 | + """ |
3855 | + No pre-treatment needed for this parser. |
3856 | + """ |
3857 | + return True |
3858 | + |
3859 | + def _parse(self, *args, **kwargs): |
3860 | + """ |
3861 | + Launch the parsing through .csv or .xls depending on the |
3862 | + given ftype |
3863 | + """ |
3864 | + |
3865 | + res = None |
3866 | + if self.ftype == 'csv': |
3867 | + res = self._parse_csv() |
3868 | + else: |
3869 | + res = self._parse_xls() |
3870 | + self.result_row_list = res |
3871 | + return True |
3872 | + |
3873 | + def _validate(self, *args, **kwargs): |
3874 | + """ |
3875 | + We check that all the key of the given file (means header) are present |
3876 | + in the validation key provided. Otherwise, we raise an Exception. |
3877 | + We skip the validation step if the file header is provided separately |
3878 | + (in the field: fieldnames). |
3879 | + """ |
3880 | + if self.fieldnames is None: |
3881 | + parsed_cols = self.result_row_list[0].keys() |
3882 | + for col in self.keys_to_validate: |
3883 | + if col not in parsed_cols: |
3884 | + raise except_osv(_('Invalid data'), |
3885 | + _('Column %s not present in file') % col) |
3886 | + return True |
3887 | + |
3888 | + def _post(self, *args, **kwargs): |
3889 | + """ |
3890 | + Cast row type depending on the file format .csv or .xls after parsing the file. |
3891 | + """ |
3892 | + self.result_row_list = self._cast_rows(*args, **kwargs) |
3893 | + return True |
3894 | + |
3895 | + def _parse_csv(self): |
3896 | + """ |
3897 | + :return: list of dict from csv file (line/rows) |
3898 | + """ |
3899 | + csv_file = tempfile.NamedTemporaryFile() |
3900 | + csv_file.write(self.filebuffer) |
3901 | + csv_file.flush() |
3902 | + with open(csv_file.name, 'rU') as fobj: |
3903 | + reader = UnicodeDictReader(fobj, fieldnames=self.fieldnames) |
3904 | + return list(reader) |
3905 | + |
3906 | + def _parse_xls(self): |
3907 | + """ |
3908 | + :return: dict of dict from xls file (line/rows) |
3909 | + """ |
3910 | + wb_file = tempfile.NamedTemporaryFile() |
3911 | + wb_file.write(self.filebuffer) |
3912 | + # We ensure that cursor is at beginig of file |
3913 | + wb_file.seek(0) |
3914 | + with xlrd.open_workbook(wb_file.name) as wb: |
3915 | + self._datemode = wb.datemode |
3916 | + sheet = wb.sheet_by_index(0) |
3917 | + header = sheet.row_values(0) |
3918 | + res = [] |
3919 | + for rownum in range(1, sheet.nrows): |
3920 | + res.append(dict(zip(header, sheet.row_values(rownum)))) |
3921 | + return res |
3922 | + |
3923 | + def _from_csv(self, result_set, conversion_rules): |
3924 | + """ |
3925 | + Handle the converstion from the dict and handle date format from |
3926 | + an .csv file. |
3927 | + """ |
3928 | + for line in result_set: |
3929 | + for rule in conversion_rules: |
3930 | + if conversion_rules[rule] == datetime.datetime: |
3931 | + try: |
3932 | + date_string = line[rule].split(' ')[0] |
3933 | + line[rule] = datetime.datetime.strptime(date_string, |
3934 | + '%Y-%m-%d') |
3935 | + except ValueError as err: |
3936 | + raise except_osv(_("Date format is not valid."), |
3937 | + _(" It should be YYYY-MM-DD for column: %s" |
3938 | + " value: %s \n \n" |
3939 | + " \n Please check the line with ref: %s" |
3940 | + " \n \n Detail: %s") % (rule, |
3941 | + line.get(rule, _('Missing')), |
3942 | + line.get('ref', line), |
3943 | + repr(err))) |
3944 | + else: |
3945 | + try: |
3946 | + line[rule] = conversion_rules[rule](line[rule]) |
3947 | + except Exception as err: |
3948 | + raise except_osv(_('Invalid data'), |
3949 | + _("Value %s of column %s is not valid." |
3950 | + "\n Please check the line with ref %s:" |
3951 | + "\n \n Detail: %s") % (line.get(rule, _('Missing')), |
3952 | + rule, |
3953 | + line.get('ref', line), |
3954 | + repr(err))) |
3955 | + return result_set |
3956 | + |
3957 | + def _from_xls(self, result_set, conversion_rules): |
3958 | + """ |
3959 | + Handle the converstion from the dict and handle date format from |
3960 | + an .xls file. |
3961 | + """ |
3962 | + for line in result_set: |
3963 | + for rule in conversion_rules: |
3964 | + if conversion_rules[rule] == datetime.datetime: |
3965 | + try: |
3966 | + t_tuple = xlrd.xldate_as_tuple(line[rule], self._datemode) |
3967 | + line[rule] = datetime.datetime(*t_tuple) |
3968 | + except Exception as err: |
3969 | + raise except_osv(_("Date format is not valid"), |
3970 | + _("Please modify the cell formatting to date format" |
3971 | + " for column: %s" |
3972 | + " value: %s" |
3973 | + "\n Please check the line with ref: %s" |
3974 | + "\n \n Detail: %s") % (rule, |
3975 | + line.get(rule, _('Missing')), |
3976 | + line.get('ref', line), |
3977 | + repr(err))) |
3978 | + else: |
3979 | + try: |
3980 | + line[rule] = conversion_rules[rule](line[rule]) |
3981 | + except Exception as err: |
3982 | + raise except_osv(_('Invalid data'), |
3983 | + _("Value %s of column %s is not valid." |
3984 | + "\n Please check the line with ref %s:" |
3985 | + "\n \n Detail: %s") % (line.get(rule, _('Missing')), |
3986 | + rule, |
3987 | + line.get('ref', line), |
3988 | + repr(err))) |
3989 | + return result_set |
3990 | + |
3991 | + def _cast_rows(self, *args, **kwargs): |
3992 | + """ |
3993 | + Convert the self.result_row_list using the self.conversion_dict providen. |
3994 | + We call here _from_xls or _from_csv depending on the self.ftype variable. |
3995 | + """ |
3996 | + func = getattr(self, '_from_%s' % self.ftype) |
3997 | + res = func(self.result_row_list, self.conversion_dict) |
3998 | + return res |
3999 | |
4000 | === added file 'account_statement_base_import/parser/generic_file_parser.py' |
4001 | --- account_statement_base_import/parser/generic_file_parser.py 1970-01-01 00:00:00 +0000 |
4002 | +++ account_statement_base_import/parser/generic_file_parser.py 2013-07-17 14:55:41 +0000 |
4003 | @@ -0,0 +1,102 @@ |
4004 | +# -*- coding: utf-8 -*- |
4005 | +############################################################################## |
4006 | +# |
4007 | +# Copyright Camptocamp SA |
4008 | +# Author Joel Grand-Guillaume |
4009 | +# This program is free software: you can redistribute it and/or modify |
4010 | +# it under the terms of the GNU General Public License as published by |
4011 | +# the Free Software Foundation, either version 3 of the License, or |
4012 | +# (at your option) any later version. |
4013 | +# |
4014 | +# This program is distributed in the hope that it will be useful, |
4015 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
4016 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4017 | +# GNU General Public License for more details. |
4018 | +# |
4019 | +# You should have received a copy of the GNU General Public License |
4020 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
4021 | +# |
4022 | +############################################################################## |
4023 | + |
4024 | +from openerp.tools.translate import _ |
4025 | +import base64 |
4026 | +import csv |
4027 | +import tempfile |
4028 | +import datetime |
4029 | +from file_parser import FileParser |
4030 | +try: |
4031 | + import xlrd |
4032 | +except: |
4033 | + raise Exception(_('Please install python lib xlrd')) |
4034 | + |
4035 | + |
4036 | +def float_or_zero(val): |
4037 | + """ Conversion function used to manage |
4038 | + empty string into float usecase""" |
4039 | + return float(val) if val else 0.0 |
4040 | + |
4041 | + |
4042 | +class GenericFileParser(FileParser): |
4043 | + """ |
4044 | + Standard parser that use a define format in csv or xls to import into a |
4045 | + bank statement. This is mostely an example of how to proceed to create a new |
4046 | + parser, but will also be useful as it allow to import a basic flat file. |
4047 | + """ |
4048 | + |
4049 | + def __init__(self, parse_name, ftype='csv'): |
4050 | + conversion_dict = { |
4051 | + 'ref': unicode, |
4052 | + 'label': unicode, |
4053 | + 'date': datetime.datetime, |
4054 | + 'amount': float_or_zero, |
4055 | + 'commission_amount': float_or_zero |
4056 | + } |
4057 | + # Order of cols does not matter but first row of the file has to be header |
4058 | + keys_to_validate = ['ref', 'label', 'date', 'amount', 'commission_amount'] |
4059 | + super(GenericFileParser, self).__init__(parse_name, keys_to_validate=keys_to_validate, ftype=ftype, conversion_dict=conversion_dict) |
4060 | + |
4061 | + @classmethod |
4062 | + def parser_for(cls, parser_name): |
4063 | + """ |
4064 | + Used by the new_bank_statement_parser class factory. Return true if |
4065 | + the providen name is generic_csvxls_so |
4066 | + """ |
4067 | + return parser_name == 'generic_csvxls_so' |
4068 | + |
4069 | + def get_st_line_vals(self, line, *args, **kwargs): |
4070 | + """ |
4071 | + This method must return a dict of vals that can be passed to create |
4072 | + method of statement line in order to record it. It is the responsibility |
4073 | + of every parser to give this dict of vals, so each one can implement his |
4074 | + own way of recording the lines. |
4075 | + :param: line: a dict of vals that represent a line of result_row_list |
4076 | + :return: dict of values to give to the create method of statement line, |
4077 | + it MUST contain at least: |
4078 | + { |
4079 | + 'name':value, |
4080 | + 'date':value, |
4081 | + 'amount':value, |
4082 | + 'ref':value, |
4083 | + 'label':value, |
4084 | + 'commission_amount':value, |
4085 | + } |
4086 | + In this generic parser, the commission is given for every line, so we store it |
4087 | + for each one. |
4088 | + """ |
4089 | + return {'name': line.get('label', line.get('ref', '/')), |
4090 | + 'date': line.get('date', datetime.datetime.now().date()), |
4091 | + 'amount': line.get('amount', 0.0), |
4092 | + 'ref': line.get('ref', '/'), |
4093 | + 'label': line.get('label', ''), |
4094 | + 'commission_amount': line.get('commission_amount', 0.0)} |
4095 | + |
4096 | + def _post(self, *args, **kwargs): |
4097 | + """ |
4098 | + Compute the commission from value of each line |
4099 | + """ |
4100 | + res = super(GenericFileParser, self)._post(*args, **kwargs) |
4101 | + val = 0.0 |
4102 | + for row in self.result_row_list: |
4103 | + val += row.get('commission_amount', 0.0) |
4104 | + self.commission_global_amount = val |
4105 | + return res |
4106 | |
4107 | === added file 'account_statement_base_import/parser/parser.py' |
4108 | --- account_statement_base_import/parser/parser.py 1970-01-01 00:00:00 +0000 |
4109 | +++ account_statement_base_import/parser/parser.py 2013-07-17 14:55:41 +0000 |
4110 | @@ -0,0 +1,219 @@ |
4111 | +# -*- coding: utf-8 -*- |
4112 | +############################################################################## |
4113 | +# |
4114 | +# Author: Joel Grand-Guillaume |
4115 | +# Copyright 2011-2012 Camptocamp SA |
4116 | +# |
4117 | +# This program is free software: you can redistribute it and/or modify |
4118 | +# it under the terms of the GNU Affero General Public License as |
4119 | +# published by the Free Software Foundation, either version 3 of the |
4120 | +# License, or (at your option) any later version. |
4121 | +# |
4122 | +# This program is distributed in the hope that it will be useful, |
4123 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
4124 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4125 | +# GNU Affero General Public License for more details. |
4126 | +# |
4127 | +# You should have received a copy of the GNU Affero General Public License |
4128 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
4129 | +# |
4130 | +############################################################################## |
4131 | +import base64 |
4132 | +import csv |
4133 | + |
4134 | + |
4135 | +def UnicodeDictReader(utf8_data, **kwargs): |
4136 | + sniffer = csv.Sniffer() |
4137 | + pos = utf8_data.tell() |
4138 | + sample_data = utf8_data.read(1024) |
4139 | + utf8_data.seek(pos) |
4140 | + dialect = sniffer.sniff(sample_data, delimiters=',;\t') |
4141 | + csv_reader = csv.DictReader(utf8_data, dialect=dialect, **kwargs) |
4142 | + for row in csv_reader: |
4143 | + yield dict([(key, unicode(value, 'utf-8')) for key, value in row.iteritems()]) |
4144 | + |
4145 | + |
4146 | +class BankStatementImportParser(object): |
4147 | + """ |
4148 | + Generic abstract class for defining parser for different files and |
4149 | + format to import in a bank statement. Inherit from it to create your |
4150 | + own. If your file is a .csv or .xls format, you should consider inheirt |
4151 | + from the FileParser instead. |
4152 | + """ |
4153 | + |
4154 | + def __init__(self, parser_name, *args, **kwargs): |
4155 | + # The name of the parser as it will be called |
4156 | + self.parser_name = parser_name |
4157 | + # The result as a list of row. One row per line of data in the file, but |
4158 | + # not the commission one ! |
4159 | + self.result_row_list = None |
4160 | + # The file buffer on which to work on |
4161 | + self.filebuffer = None |
4162 | + # Concatenate here the global commission taken by the bank/office |
4163 | + # for this statement. |
4164 | + self.commission_global_amount = None |
4165 | + |
4166 | + @classmethod |
4167 | + def parser_for(cls, parser_name): |
4168 | + """ |
4169 | + Override this method for every new parser, so that new_bank_statement_parser can |
4170 | + return the good class from his name. |
4171 | + """ |
4172 | + return False |
4173 | + |
4174 | + def _decode_64b_stream(self): |
4175 | + """ |
4176 | + Decode self.filebuffer in base 64 and override it |
4177 | + """ |
4178 | + self.filebuffer = base64.b64decode(self.filebuffer) |
4179 | + return True |
4180 | + |
4181 | + def _format(self, decode_base_64=True, **kwargs): |
4182 | + """ |
4183 | + Decode into base 64 if asked and Format the given filebuffer by calling |
4184 | + _custom_format method. |
4185 | + """ |
4186 | + if decode_base_64: |
4187 | + self._decode_64b_stream() |
4188 | + self._custom_format(kwargs) |
4189 | + return True |
4190 | + |
4191 | + def _custom_format(self, *args, **kwargs): |
4192 | + """ |
4193 | + Implement a method in your parser to convert format, encoding and so on before |
4194 | + starting to work on datas. Work on self.filebuffer |
4195 | + """ |
4196 | + return NotImplementedError |
4197 | + |
4198 | + def _pre(self, *args, **kwargs): |
4199 | + """ |
4200 | + Implement a method in your parser to make a pre-treatment on datas before parsing |
4201 | + them, like concatenate stuff, and so... Work on self.filebuffer |
4202 | + """ |
4203 | + return NotImplementedError |
4204 | + |
4205 | + def _parse(self, *args, **kwargs): |
4206 | + """ |
4207 | + Implement a method in your parser to save the result of parsing self.filebuffer |
4208 | + in self.result_row_list instance property. |
4209 | + """ |
4210 | + return NotImplementedError |
4211 | + |
4212 | + def _validate(self, *args, **kwargs): |
4213 | + """ |
4214 | + Implement a method in your parser to validate the self.result_row_list instance |
4215 | + property and raise an error if not valid. |
4216 | + """ |
4217 | + return NotImplementedError |
4218 | + |
4219 | + def _post(self, *args, **kwargs): |
4220 | + """ |
4221 | + Implement a method in your parser to make some last changes on the result of parsing |
4222 | + the datas, like converting dates, computing commission, ... |
4223 | + Work on self.result_row_list and put the commission global amount if any |
4224 | + in the self.commission_global_amount one. |
4225 | + """ |
4226 | + return NotImplementedError |
4227 | + |
4228 | + def get_st_line_vals(self, line, *args, **kwargs): |
4229 | + """ |
4230 | + Implement a method in your parser that must return a dict of vals that can be |
4231 | + passed to create method of statement line in order to record it. It is the responsibility |
4232 | + of every parser to give this dict of vals, so each one can implement his |
4233 | + own way of recording the lines. |
4234 | + :param: line: a dict of vals that represent a line of result_row_list |
4235 | + :return: dict of values to give to the create method of statement line, |
4236 | + it MUST contain at least: |
4237 | + { |
4238 | + 'name':value, |
4239 | + 'date':value, |
4240 | + 'amount':value, |
4241 | + 'ref':value, |
4242 | + } |
4243 | + """ |
4244 | + return NotImplementedError |
4245 | + |
4246 | + def get_st_line_commision(self, *args, **kwargs): |
4247 | + """ |
4248 | + This is called by the importation method to create the commission line in |
4249 | + the bank statement. We will always create one line for the commission in the |
4250 | + bank statement, but it could be computated from a value of each line, or given |
4251 | + in a single line for the whole file. |
4252 | + return: float of the whole commission (self.commission_global_amount) |
4253 | + """ |
4254 | + return self.commission_global_amount |
4255 | + |
4256 | + def parse(self, filebuffer, *args, **kwargs): |
4257 | + """ |
4258 | + This will be the method that will be called by wizard, button and so |
4259 | + to parse a filebuffer by calling successively all the private method |
4260 | + that need to be define for each parser. |
4261 | + Return: |
4262 | + [] of rows as {'key':value} |
4263 | + |
4264 | + Note: The row_list must contain only value that are present in the account. |
4265 | + bank.statement.line object !!! |
4266 | + """ |
4267 | + if filebuffer: |
4268 | + self.filebuffer = filebuffer |
4269 | + else: |
4270 | + raise Exception(_('No buffer file given.')) |
4271 | + self._format(*args, **kwargs) |
4272 | + self._pre(*args, **kwargs) |
4273 | + self._parse(*args, **kwargs) |
4274 | + self._validate(*args, **kwargs) |
4275 | + self._post(*args, **kwargs) |
4276 | + return self.result_row_list |
4277 | + |
4278 | + |
4279 | +def itersubclasses(cls, _seen=None): |
4280 | + """ |
4281 | + itersubclasses(cls) |
4282 | + |
4283 | + Generator over all subclasses of a given class, in depth first order. |
4284 | + |
4285 | + >>> list(itersubclasses(int)) == [bool] |
4286 | + True |
4287 | + >>> class A(object): pass |
4288 | + >>> class B(A): pass |
4289 | + >>> class C(A): pass |
4290 | + >>> class D(B,C): pass |
4291 | + >>> class E(D): pass |
4292 | + >>> |
4293 | + >>> for cls in itersubclasses(A): |
4294 | + ... print(cls.__name__) |
4295 | + B |
4296 | + D |
4297 | + E |
4298 | + C |
4299 | + >>> # get ALL (new-style) classes currently defined |
4300 | + >>> [cls.__name__ for cls in itersubclasses(object)] #doctest: +ELLIPSIS |
4301 | + ['type', ...'tuple', ...] |
4302 | + """ |
4303 | + if not isinstance(cls, type): |
4304 | + raise TypeError('itersubclasses must be called with ' |
4305 | + 'new-style classes, not %.100r' % cls) |
4306 | + if _seen is None: |
4307 | + _seen = set() |
4308 | + try: |
4309 | + subs = cls.__subclasses__() |
4310 | + except TypeError: # fails only when cls is type |
4311 | + subs = cls.__subclasses__(cls) |
4312 | + for sub in subs: |
4313 | + if sub not in _seen: |
4314 | + _seen.add(sub) |
4315 | + yield sub |
4316 | + for sub in itersubclasses(sub, _seen): |
4317 | + yield sub |
4318 | + |
4319 | + |
4320 | +def new_bank_statement_parser(parser_name, *args, **kwargs): |
4321 | + """ |
4322 | + Return an instance of the good parser class base on the providen name |
4323 | + :param char: parser_name |
4324 | + :return: class instance of parser_name providen. |
4325 | + """ |
4326 | + for cls in itersubclasses(BankStatementImportParser): |
4327 | + if cls.parser_for(parser_name): |
4328 | + return cls(parser_name, *args, **kwargs) |
4329 | + raise ValueError |
4330 | |
4331 | === added file 'account_statement_base_import/statement.py' |
4332 | --- account_statement_base_import/statement.py 1970-01-01 00:00:00 +0000 |
4333 | +++ account_statement_base_import/statement.py 2013-07-17 14:55:41 +0000 |
4334 | @@ -0,0 +1,303 @@ |
4335 | +# -*- coding: utf-8 -*- |
4336 | +############################################################################## |
4337 | +# |
4338 | +# Author: Joel Grand-Guillaume |
4339 | +# Copyright 2011-2012 Camptocamp SA |
4340 | +# |
4341 | +# This program is free software: you can redistribute it and/or modify |
4342 | +# it under the terms of the GNU Affero General Public License as |
4343 | +# published by the Free Software Foundation, either version 3 of the |
4344 | +# License, or (at your option) any later version. |
4345 | +# |
4346 | +# This program is distributed in the hope that it will be useful, |
4347 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
4348 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4349 | +# GNU Affero General Public License for more details. |
4350 | +# |
4351 | +# You should have received a copy of the GNU Affero General Public License |
4352 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
4353 | +# |
4354 | +############################################################################## |
4355 | +import sys |
4356 | +import traceback |
4357 | + |
4358 | +import psycopg2 |
4359 | + |
4360 | +from openerp.tools.translate import _ |
4361 | +import datetime |
4362 | +from openerp.osv.orm import Model |
4363 | +from openerp.osv import fields, osv |
4364 | +from parser import new_bank_statement_parser |
4365 | + |
4366 | + |
4367 | +class AccountStatementProfil(Model): |
4368 | + _inherit = "account.statement.profile" |
4369 | + |
4370 | + def get_import_type_selection(self, cr, uid, context=None): |
4371 | + """ |
4372 | + Has to be inherited to add parser |
4373 | + """ |
4374 | + return [('generic_csvxls_so', 'Generic .csv/.xls based on SO Name')] |
4375 | + |
4376 | + _columns = { |
4377 | + 'launch_import_completion': fields.boolean( |
4378 | + "Launch completion after import", |
4379 | + help="Tic that box to automatically launch the completion " |
4380 | + "on each imported file using this profile."), |
4381 | + 'last_import_date': fields.datetime("Last Import Date"), |
4382 | + # we remove deprecated as it floods logs in standard/warning level sob... |
4383 | + 'rec_log': fields.text('log', readonly=True), # Deprecated |
4384 | + 'import_type': fields.selection( |
4385 | + get_import_type_selection, |
4386 | + 'Type of import', |
4387 | + required=True, |
4388 | + help="Choose here the method by which you want to import bank" |
4389 | + "statement for this profile."), |
4390 | + |
4391 | + } |
4392 | + |
4393 | + def write_logs_after_import(self, cr, uid, ids, statement_id, num_lines, context): |
4394 | + """ |
4395 | + Write the log in the logger |
4396 | + |
4397 | + :param int/long statement_id: ID of the concerned account.bank.statement |
4398 | + :param int/long num_lines: Number of line that have been parsed |
4399 | + :return: True |
4400 | + """ |
4401 | + self.message_post(cr, |
4402 | + uid, |
4403 | + ids, |
4404 | + body=_('Statement ID %s have been imported with %s lines.') % |
4405 | + (statement_id, num_lines), |
4406 | + context=context) |
4407 | + return True |
4408 | + |
4409 | + def prepare_global_commission_line_vals( |
4410 | + self, cr, uid, parser, result_row_list, profile, statement_id, context): |
4411 | + """ |
4412 | + Prepare the global commission line if there is one. The global |
4413 | + commission is computed by by calling the get_st_line_commision |
4414 | + of the parser. Feel free to override the method to compute |
4415 | + your own commission line from the result_row_list. |
4416 | + |
4417 | + :param: browse_record of the current parser |
4418 | + :param: result_row_list: [{'key':value}] |
4419 | + :param: profile: browserecord of account.statement.profile |
4420 | + :param: statement_id: int/long of the current importing statement ID |
4421 | + :param: context: global context |
4422 | + return: dict of vals that will be passed to create method of statement line. |
4423 | + """ |
4424 | + comm_values = False |
4425 | + if parser.get_st_line_commision(): |
4426 | + partner_id = profile.partner_id and profile.partner_id.id or False |
4427 | + commission_account_id = profile.commission_account_id and profile.commission_account_id.id or False |
4428 | + commission_analytic_id = profile.commission_analytic_id and profile.commission_analytic_id.id or False |
4429 | + comm_values = { |
4430 | + 'name': 'IN ' + _('Commission line'), |
4431 | + 'date': datetime.datetime.now().date(), |
4432 | + 'amount': parser.get_st_line_commision(), |
4433 | + 'partner_id': partner_id, |
4434 | + 'type': 'general', |
4435 | + 'statement_id': statement_id, |
4436 | + 'account_id': commission_account_id, |
4437 | + 'ref': 'commission', |
4438 | + 'analytic_account_id': commission_analytic_id, |
4439 | + # !! We set the already_completed so auto-completion will not update those values! |
4440 | + 'already_completed': True, |
4441 | + } |
4442 | + return comm_values |
4443 | + |
4444 | + def prepare_statetement_lines_vals( |
4445 | + self, cr, uid, parser_vals, account_payable, account_receivable, |
4446 | + statement_id, context): |
4447 | + """ |
4448 | + Hook to build the values of a line from the parser returned values. At |
4449 | + least it fullfill the statement_id and account_id. Overide it to add your |
4450 | + own completion if needed. |
4451 | + |
4452 | + :param dict of vals from parser for account.bank.statement.line (called by |
4453 | + parser.get_st_line_vals) |
4454 | + :param int/long account_payable: ID of the receivable account to use |
4455 | + :param int/long account_receivable: ID of the payable account to use |
4456 | + :param int/long statement_id: ID of the concerned account.bank.statement |
4457 | + :return: dict of vals that will be passed to create method of statement line. |
4458 | + """ |
4459 | + statement_obj = self.pool.get('account.bank.statement') |
4460 | + values = parser_vals |
4461 | + values['statement_id'] = statement_id |
4462 | + values['account_id'] = statement_obj.get_account_for_counterpart(cr, |
4463 | + uid, |
4464 | + parser_vals['amount'], |
4465 | + account_receivable, |
4466 | + account_payable) |
4467 | + |
4468 | + date = values.get('date') |
4469 | + period_memoizer = context.get('period_memoizer') |
4470 | + if not period_memoizer: |
4471 | + period_memoizer = {} |
4472 | + context['period_memoizer'] = period_memoizer |
4473 | + if period_memoizer.get(date): |
4474 | + values['period_id'] = period_memoizer[date] |
4475 | + else: |
4476 | + # This is awfully slow... |
4477 | + periods = self.pool.get('account.period').find(cr, uid, |
4478 | + dt=values.get('date'), |
4479 | + context=context) |
4480 | + values['period_id'] = periods[0] |
4481 | + period_memoizer[date] = periods[0] |
4482 | + values['type'] = 'general' |
4483 | + return values |
4484 | + |
4485 | + def statement_import(self, cr, uid, ids, profile_id, file_stream, ftype="csv", context=None): |
4486 | + """ |
4487 | + Create a bank statement with the given profile and parser. It will fullfill the bank statement |
4488 | + with the values of the file providen, but will not complete data (like finding the partner, or |
4489 | + the right account). This will be done in a second step with the completion rules. |
4490 | + It will also create the commission line if it apply and record the providen file as |
4491 | + an attachement of the bank statement. |
4492 | + |
4493 | + :param int/long profile_id: ID of the profile used to import the file |
4494 | + :param filebuffer file_stream: binary of the providen file |
4495 | + :param char: ftype represent the file exstension (csv by default) |
4496 | + :return: ID of the created account.bank.statemênt |
4497 | + """ |
4498 | + statement_obj = self.pool.get('account.bank.statement') |
4499 | + statement_line_obj = self.pool.get('account.bank.statement.line') |
4500 | + attachment_obj = self.pool.get('ir.attachment') |
4501 | + prof_obj = self.pool.get("account.statement.profile") |
4502 | + if not profile_id: |
4503 | + raise osv.except_osv(_("No Profile!"), |
4504 | + _("You must provide a valid profile to import a bank statement!")) |
4505 | + prof = prof_obj.browse(cr, uid, profile_id, context=context) |
4506 | + |
4507 | + parser = new_bank_statement_parser(prof.import_type, ftype=ftype) |
4508 | + result_row_list = parser.parse(file_stream) |
4509 | + # Check all key are present in account.bank.statement.line!! |
4510 | + if not result_row_list: |
4511 | + raise osv.except_osv(_("Nothing to import"), |
4512 | + _("The file is empty")) |
4513 | + parsed_cols = parser.get_st_line_vals(result_row_list[0]).keys() |
4514 | + for col in parsed_cols: |
4515 | + if col not in statement_line_obj._columns: |
4516 | + raise osv.except_osv(_("Missing column!"), |
4517 | + _("Column %s you try to import is not " |
4518 | + "present in the bank statement line!") % col) |
4519 | + |
4520 | + statement_id = statement_obj.create(cr, uid, |
4521 | + {'profile_id': prof.id}, |
4522 | + context=context) |
4523 | + if prof.receivable_account_id: |
4524 | + account_receivable = account_payable = prof.receivable_account_id.id |
4525 | + else: |
4526 | + account_receivable, account_payable = statement_obj.get_default_pay_receiv_accounts( |
4527 | + cr, uid, context) |
4528 | + try: |
4529 | + # Record every line in the bank statement and compute the global commission |
4530 | + # based on the commission_amount column |
4531 | + statement_store = [] |
4532 | + for line in result_row_list: |
4533 | + parser_vals = parser.get_st_line_vals(line) |
4534 | + values = self.prepare_statetement_lines_vals(cr, uid, parser_vals, account_payable, |
4535 | + account_receivable, statement_id, context) |
4536 | + statement_store.append(values) |
4537 | + # Hack to bypass ORM poor perfomance. Sob... |
4538 | + statement_line_obj._insert_lines(cr, uid, statement_store, context=context) |
4539 | + |
4540 | + # Build and create the global commission line for the whole statement |
4541 | + comm_vals = self.prepare_global_commission_line_vals(cr, uid, parser, result_row_list, |
4542 | + prof, statement_id, context) |
4543 | + if comm_vals: |
4544 | + statement_line_obj.create(cr, uid, comm_vals, context=context) |
4545 | + else: |
4546 | + # Trigger store field computation if someone has better idea |
4547 | + start_bal = statement_obj.read(cr, uid, statement_id, |
4548 | + ['balance_start'], |
4549 | + context=context) |
4550 | + start_bal = start_bal['balance_start'] |
4551 | + statement_obj.write(cr, uid, [statement_id], |
4552 | + {'balance_start': start_bal}) |
4553 | + |
4554 | + attachment_obj.create(cr, |
4555 | + uid, |
4556 | + {'name': 'statement file', |
4557 | + 'datas': file_stream, |
4558 | + 'datas_fname': "%s.%s" % ( |
4559 | + datetime.datetime.now().date(), |
4560 | + ftype), |
4561 | + 'res_model': 'account.bank.statement', |
4562 | + 'res_id': statement_id}, |
4563 | + context=context) |
4564 | + |
4565 | + # If user ask to launch completion at end of import, do it! |
4566 | + if prof.launch_import_completion: |
4567 | + statement_obj.button_auto_completion(cr, uid, [statement_id], context) |
4568 | + |
4569 | + # Write the needed log infos on profile |
4570 | + self.write_logs_after_import(cr, uid, prof.id, |
4571 | + statement_id, |
4572 | + len(result_row_list), |
4573 | + context) |
4574 | + |
4575 | + except Exception: |
4576 | + statement_obj.unlink(cr, uid, [statement_id], context=context) |
4577 | + error_type, error_value, trbk = sys.exc_info() |
4578 | + st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value) |
4579 | + st += ''.join(traceback.format_tb(trbk, 30)) |
4580 | + raise osv.except_osv(_("Statement import error"), |
4581 | + _("The statement cannot be created: %s") % st) |
4582 | + return statement_id |
4583 | + |
4584 | + |
4585 | +class AccountStatementLine(Model): |
4586 | + """ |
4587 | + Add sparse field on the statement line to allow to store all the |
4588 | + bank infos that are given by an office. In this basic sample case |
4589 | + it concern only commission_amount. |
4590 | + """ |
4591 | + _inherit = "account.bank.statement.line" |
4592 | + |
4593 | + def _get_available_columns(self, statement_store): |
4594 | + """Return writeable by SQL columns""" |
4595 | + statement_line_obj = self.pool['account.bank.statement.line'] |
4596 | + model_cols = statement_line_obj._columns |
4597 | + avail = [k for k, col in model_cols.iteritems() if not hasattr(col, '_fnct')] |
4598 | + keys = [k for k in statement_store[0].keys() if k in avail] |
4599 | + keys.sort() |
4600 | + return keys |
4601 | + |
4602 | + def _insert_lines(self, cr, uid, statement_store, context=None): |
4603 | + """ Do raw insert into database because ORM is awfully slow |
4604 | + when doing batch write. It is a shame that batch function |
4605 | + does not exist""" |
4606 | + statement_line_obj = self.pool['account.bank.statement.line'] |
4607 | + statement_line_obj.check_access_rule(cr, uid, [], 'create') |
4608 | + statement_line_obj.check_access_rights(cr, uid, 'create', raise_exception=True) |
4609 | + cols = self._get_available_columns(statement_store) |
4610 | + tmp_vals = (', '.join(cols), ', '.join(['%%(%s)s' % i for i in cols])) |
4611 | + sql = "INSERT INTO account_bank_statement_line (%s) VALUES (%s);" % tmp_vals |
4612 | + try: |
4613 | + cr.executemany(sql, tuple(statement_store)) |
4614 | + except psycopg2.Error as sql_err: |
4615 | + cr.rollback() |
4616 | + raise osv.except_osv(_("ORM bypass error"), |
4617 | + sql_err.pgerror) |
4618 | + |
4619 | + def _update_line(self, cr, uid, vals, context=None): |
4620 | + """ Do raw update into database because ORM is awfully slow |
4621 | + when cheking security.""" |
4622 | + cols = self._get_available_columns([vals]) |
4623 | + tmp_vals = (', '.join(['%s = %%(%s)s' % (i, i) for i in cols])) |
4624 | + sql = "UPDATE account_bank_statement_line SET %s where id = %%(id)s;" % tmp_vals |
4625 | + try: |
4626 | + cr.execute(sql, vals) |
4627 | + except psycopg2.Error as sql_err: |
4628 | + cr.rollback() |
4629 | + raise osv.except_osv(_("ORM bypass error"), |
4630 | + sql_err.pgerror) |
4631 | + |
4632 | + _columns = { |
4633 | + 'commission_amount': fields.sparse( |
4634 | + type='float', |
4635 | + string='Line Commission Amount', |
4636 | + serialization_field='additionnal_bank_fields'), |
4637 | + } |
4638 | |
4639 | === added file 'account_statement_base_import/statement_view.xml' |
4640 | --- account_statement_base_import/statement_view.xml 1970-01-01 00:00:00 +0000 |
4641 | +++ account_statement_base_import/statement_view.xml 2013-07-17 14:55:41 +0000 |
4642 | @@ -0,0 +1,46 @@ |
4643 | +<?xml version="1.0" encoding="utf-8"?> |
4644 | +<openerp> |
4645 | +<data> |
4646 | + |
4647 | + |
4648 | + <record id="statement_importer_view_form" model="ir.ui.view"> |
4649 | + <field name="name">account.statement.profile.view</field> |
4650 | + <field name="model">account.statement.profile</field> |
4651 | + <field name="inherit_id" ref="account_statement_ext.statement_importer_view_form"/> |
4652 | + <field name="type">form</field> |
4653 | + <field name="arch" type="xml"> |
4654 | + <field name="bank_statement_prefix" position="after"> |
4655 | + <separator colspan="4" string="Import related infos"/> |
4656 | + <field name="launch_import_completion"/> |
4657 | + <field name="last_import_date"/> |
4658 | + <field name="import_type"/> |
4659 | + <button name="%(account_statement_base_import.statement_importer_action)d" |
4660 | + string="Import Bank Statement" |
4661 | + type="action" icon="gtk-ok" |
4662 | + colspan = "2"/> |
4663 | + <group attrs="{'invisible': [('rec_log', '=', False)]}"> |
4664 | + <separator colspan="4" string="Historical Import Logs"/> |
4665 | + <field name="rec_log" colspan="4" nolabel="1" /> |
4666 | + </group> |
4667 | + </field> |
4668 | + </field> |
4669 | + </record> |
4670 | + |
4671 | + <record id="bank_statement_view_form" model="ir.ui.view"> |
4672 | + <field name="name">account_bank_statement.bank_statement.view_form</field> |
4673 | + <field name="model">account.bank.statement</field> |
4674 | + <field name="inherit_id" ref="account_statement_base_completion.bank_statement_view_form" /> |
4675 | + <field name="type">form</field> |
4676 | + <field eval="20" name="priority"/> |
4677 | + <field name="arch" type="xml"> |
4678 | + <data> |
4679 | + <xpath expr="/form/sheet/notebook/page/field[@name='line_ids']/form/group/field[@name='label']" position="after"> |
4680 | + <field name="commission_amount" /> |
4681 | + </xpath> |
4682 | + </data> |
4683 | + </field> |
4684 | + </record> |
4685 | + |
4686 | + |
4687 | +</data> |
4688 | +</openerp> |
4689 | |
4690 | === added directory 'account_statement_base_import/wizard' |
4691 | === added file 'account_statement_base_import/wizard/__init__.py' |
4692 | --- account_statement_base_import/wizard/__init__.py 1970-01-01 00:00:00 +0000 |
4693 | +++ account_statement_base_import/wizard/__init__.py 2013-07-17 14:55:41 +0000 |
4694 | @@ -0,0 +1,20 @@ |
4695 | +# -*- coding: utf-8 -*- |
4696 | +############################################################################## |
4697 | +# |
4698 | +# Author Nicolas Bessi, Joel Grand-Guillaume. Copyright Camptocamp SA |
4699 | +# |
4700 | +# This program is free software: you can redistribute it and/or modify |
4701 | +# it under the terms of the GNU General Public License as published by |
4702 | +# the Free Software Foundation, either version 3 of the License, or |
4703 | +# (at your option) any later version. |
4704 | +# |
4705 | +# This program is distributed in the hope that it will be useful, |
4706 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
4707 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4708 | +# GNU General Public License for more details. |
4709 | +# |
4710 | +# You should have received a copy of the GNU General Public License |
4711 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
4712 | +# |
4713 | +############################################################################## |
4714 | +import import_statement |
4715 | |
4716 | === added file 'account_statement_base_import/wizard/import_statement.py' |
4717 | --- account_statement_base_import/wizard/import_statement.py 1970-01-01 00:00:00 +0000 |
4718 | +++ account_statement_base_import/wizard/import_statement.py 2013-07-17 14:55:41 +0000 |
4719 | @@ -0,0 +1,122 @@ |
4720 | +# -*- coding: utf-8 -*- |
4721 | +############################################################################## |
4722 | +# |
4723 | +# Author: Nicolas Bessi, Joel Grand-Guillaume |
4724 | +# Copyright 2011-2012 Camptocamp SA |
4725 | +# |
4726 | +# This program is free software: you can redistribute it and/or modify |
4727 | +# it under the terms of the GNU Affero General Public License as |
4728 | +# published by the Free Software Foundation, either version 3 of the |
4729 | +# License, or (at your option) any later version. |
4730 | +# |
4731 | +# This program is distributed in the hope that it will be useful, |
4732 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
4733 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4734 | +# GNU Affero General Public License for more details. |
4735 | +# |
4736 | +# You should have received a copy of the GNU Affero General Public License |
4737 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
4738 | +# |
4739 | +############################################################################## |
4740 | + |
4741 | +""" |
4742 | +Wizard to import financial institute date in bank statement |
4743 | +""" |
4744 | + |
4745 | +from openerp.osv import orm, fields |
4746 | + |
4747 | +from openerp.tools.translate import _ |
4748 | +import os |
4749 | + |
4750 | + |
4751 | +class CreditPartnerStatementImporter(orm.TransientModel): |
4752 | + _name = "credit.statement.import" |
4753 | + |
4754 | + def default_get(self, cr, uid, fields, context=None): |
4755 | + if context is None: |
4756 | + context = {} |
4757 | + res = {} |
4758 | + if (context.get('active_model', False) == 'account.statement.profile' and |
4759 | + context.get('active_ids', False)): |
4760 | + ids = context['active_ids'] |
4761 | + assert len(ids) == 1, 'You cannot use this on more than one profile !' |
4762 | + res['profile_id'] = ids[0] |
4763 | + other_vals = self.onchange_profile_id(cr, uid, [], res['profile_id'], context=context) |
4764 | + res.update(other_vals.get('value', {})) |
4765 | + return res |
4766 | + |
4767 | + _columns = { |
4768 | + 'profile_id': fields.many2one('account.statement.profile', |
4769 | + 'Import configuration parameter', |
4770 | + required=True), |
4771 | + 'input_statement': fields.binary('Statement file', required=True), |
4772 | + 'partner_id': fields.many2one('res.partner', |
4773 | + 'Credit insitute partner'), |
4774 | + 'journal_id': fields.many2one('account.journal', |
4775 | + 'Financial journal to use transaction'), |
4776 | + 'file_name': fields.char('File Name', size=128), |
4777 | + 'commission_account_id': fields.many2one('account.account', |
4778 | + 'Commission account'), |
4779 | + 'commission_analytic_id': fields.many2one('account.analytic.account', |
4780 | + 'Commission analytic account'), |
4781 | + 'receivable_account_id': fields.many2one('account.account', |
4782 | + 'Force Receivable/Payable Account'), |
4783 | + 'force_partner_on_bank': fields.boolean( |
4784 | + 'Force partner on bank move', |
4785 | + help="Tic that box if you want to use the credit insitute partner " |
4786 | + "in the counterpart of the treasury/banking move."), |
4787 | + 'balance_check': fields.boolean( |
4788 | + 'Balance check', |
4789 | + help="Tic that box if you want OpenERP to control the " |
4790 | + "start/end balance before confirming a bank statement. " |
4791 | + "If don't ticked, no balance control will be done."), |
4792 | + } |
4793 | + |
4794 | + def onchange_profile_id(self, cr, uid, ids, profile_id, context=None): |
4795 | + res = {} |
4796 | + if profile_id: |
4797 | + c = self.pool.get("account.statement.profile").browse( |
4798 | + cr, uid, profile_id, context=context) |
4799 | + res = {'value': |
4800 | + {'partner_id': c.partner_id and c.partner_id.id or False, |
4801 | + 'journal_id': c.journal_id and c.journal_id.id or False, |
4802 | + 'commission_account_id': |
4803 | + c.commission_account_id and c.commission_account_id.id or False, |
4804 | + 'receivable_account_id': c.receivable_account_id and c.receivable_account_id.id or False, |
4805 | + 'commission_a': c.commission_analytic_id and c.commission_analytic_id.id or False, |
4806 | + 'force_partner_on_bank': c.force_partner_on_bank, |
4807 | + 'balance_check': c.balance_check, |
4808 | + } |
4809 | + } |
4810 | + return res |
4811 | + |
4812 | + def _check_extension(self, filename): |
4813 | + (__, ftype) = os.path.splitext(filename) |
4814 | + if not ftype: |
4815 | + #We do not use osv exception we do not want to have it logged |
4816 | + raise Exception(_('Please use a file with an extention')) |
4817 | + return ftype |
4818 | + |
4819 | + def import_statement(self, cr, uid, req_id, context=None): |
4820 | + """This Function import credit card agency statement""" |
4821 | + context = context or {} |
4822 | + if isinstance(req_id, list): |
4823 | + req_id = req_id[0] |
4824 | + importer = self.browse(cr, uid, req_id, context) |
4825 | + ftype = self._check_extension(importer.file_name) |
4826 | + sid = self.pool.get( |
4827 | + 'account.statement.profile').statement_import( |
4828 | + cr, |
4829 | + uid, |
4830 | + False, |
4831 | + importer.profile_id.id, |
4832 | + importer.input_statement, |
4833 | + ftype.replace('.', ''), |
4834 | + context=context |
4835 | + ) |
4836 | + model_obj = self.pool.get('ir.model.data') |
4837 | + action_obj = self.pool.get('ir.actions.act_window') |
4838 | + action_id = model_obj.get_object_reference(cr, uid, 'account', 'action_bank_statement_tree')[1] |
4839 | + res = action_obj.read(cr, uid, action_id) |
4840 | + res['domain'] = res['domain'][:-1] + ",('id', '=', %d)]" % sid |
4841 | + return res |
4842 | |
4843 | === added file 'account_statement_base_import/wizard/import_statement_view.xml' |
4844 | --- account_statement_base_import/wizard/import_statement_view.xml 1970-01-01 00:00:00 +0000 |
4845 | +++ account_statement_base_import/wizard/import_statement_view.xml 2013-07-17 14:55:41 +0000 |
4846 | @@ -0,0 +1,44 @@ |
4847 | +<?xml version="1.0" encoding="utf-8"?> |
4848 | +<openerp> |
4849 | + <data> |
4850 | + <record id="statement_importer_view" model="ir.ui.view"> |
4851 | + <field name="name">credit.statement.import.config.view</field> |
4852 | + <field name="model">credit.statement.import</field> |
4853 | + <field name="type">form</field> |
4854 | + <field name="arch" type="xml"> |
4855 | + <form string="Import statement"> |
4856 | + <group colspan="4" > |
4857 | + <field name="profile_id" on_change="onchange_profile_id(profile_id)"/> |
4858 | + <field name="input_statement" filename="file_name" colspan="2"/> |
4859 | + <field name="file_name" colspan="2" invisible="1"/> |
4860 | + <separator string="Import Parameters Summary" colspan="4"/> |
4861 | + <field name="partner_id" readonly="1"/> |
4862 | + <field name="journal_id" readonly="1"/> |
4863 | + <field name="commission_account_id" readonly="1"/> |
4864 | + <field name="commission_analytic_id" readonly="1"/> |
4865 | + <field name="receivable_account_id" readonly="1"/> |
4866 | + <field name="force_partner_on_bank" readonly="1"/> |
4867 | + <field name="balance_check" readonly="1"/> |
4868 | + </group> |
4869 | + <separator string="" colspan="4"/> |
4870 | + <group colspan="4" col="6"> |
4871 | + <button icon="gtk-cancel" special="cancel" string="Cancel"/> |
4872 | + <button icon="gtk-ok" name="import_statement" string="Import statement" type="object"/> |
4873 | + </group> |
4874 | + </form> |
4875 | + </field> |
4876 | + </record> |
4877 | + |
4878 | + <record id="statement_importer_action" model="ir.actions.act_window"> |
4879 | + <field name="name">Import statement</field> |
4880 | + <field name="res_model">credit.statement.import</field> |
4881 | + <field name="view_type">form</field> |
4882 | + <field name="view_mode">tree,form</field> |
4883 | + <field name="view_id" ref="statement_importer_view"/> |
4884 | + <field name="target">new</field> |
4885 | + </record> |
4886 | + |
4887 | + <menuitem id="statement_importer_menu" name="Import Bank Statement" action="statement_importer_action" parent="account.menu_finance_bank_and_cash"/> |
4888 | + |
4889 | + </data> |
4890 | +</openerp> |
4891 | |
4892 | === added directory 'account_statement_completion_voucher' |
4893 | === added file 'account_statement_completion_voucher/__init__.py' |
4894 | --- account_statement_completion_voucher/__init__.py 1970-01-01 00:00:00 +0000 |
4895 | +++ account_statement_completion_voucher/__init__.py 2013-07-17 14:55:41 +0000 |
4896 | @@ -0,0 +1,20 @@ |
4897 | +# -*- coding: utf-8 -*- |
4898 | +############################################################################## |
4899 | +# |
4900 | +# Author: Joel Grand-Guillaume |
4901 | +# Copyright 2011-2012 Camptocamp SA |
4902 | +# |
4903 | +# This program is free software: you can redistribute it and/or modify |
4904 | +# it under the terms of the GNU Affero General Public License as |
4905 | +# published by the Free Software Foundation, either version 3 of the |
4906 | +# License, or (at your option) any later version. |
4907 | +# |
4908 | +# This program is distributed in the hope that it will be useful, |
4909 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
4910 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4911 | +# GNU Affero General Public License for more details. |
4912 | +# |
4913 | +# You should have received a copy of the GNU Affero General Public License |
4914 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
4915 | +# |
4916 | +############################################################################## |
4917 | |
4918 | === added file 'account_statement_completion_voucher/__openerp__.py' |
4919 | --- account_statement_completion_voucher/__openerp__.py 1970-01-01 00:00:00 +0000 |
4920 | +++ account_statement_completion_voucher/__openerp__.py 2013-07-17 14:55:41 +0000 |
4921 | @@ -0,0 +1,46 @@ |
4922 | +# -*- coding: utf-8 -*- |
4923 | +############################################################################## |
4924 | +# |
4925 | +# Author: Joel Grand-Guillaume |
4926 | +# Copyright 2011-2012 Camptocamp SA |
4927 | +# |
4928 | +# This program is free software: you can redistribute it and/or modify |
4929 | +# it under the terms of the GNU Affero General Public License as |
4930 | +# published by the Free Software Foundation, either version 3 of the |
4931 | +# License, or (at your option) any later version. |
4932 | +# |
4933 | +# This program is distributed in the hope that it will be useful, |
4934 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
4935 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4936 | +# GNU Affero General Public License for more details. |
4937 | +# |
4938 | +# You should have received a copy of the GNU Affero General Public License |
4939 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
4940 | +# |
4941 | +############################################################################## |
4942 | + |
4943 | +{'name': "Bank statement extension with voucher", |
4944 | + 'version': '1.0', |
4945 | + 'author': 'Camptocamp', |
4946 | + 'maintainer': 'Camptocamp', |
4947 | + 'category': 'Finance', |
4948 | + 'complexity': 'normal', |
4949 | + 'depends': [ |
4950 | + 'account_statement_base_completion', |
4951 | + 'account_voucher' |
4952 | + ], |
4953 | + 'description': """ |
4954 | + This module is only needed when using account_statement_base_completion with voucher in order adapt the view correctly. |
4955 | + """, |
4956 | + 'website': 'http://www.camptocamp.com', |
4957 | + 'init_xml': [], |
4958 | + 'update_xml': [ |
4959 | + "statement_view.xml", |
4960 | + ], |
4961 | + 'demo_xml': [], |
4962 | + 'test': [], |
4963 | + 'installable': False, |
4964 | + 'images': [], |
4965 | + 'auto_install': False, |
4966 | + 'license': 'AGPL-3', |
4967 | +} |
4968 | |
4969 | === added file 'account_statement_completion_voucher/statement_view.xml' |
4970 | --- account_statement_completion_voucher/statement_view.xml 1970-01-01 00:00:00 +0000 |
4971 | +++ account_statement_completion_voucher/statement_view.xml 2013-07-17 14:55:41 +0000 |
4972 | @@ -0,0 +1,21 @@ |
4973 | +<?xml version="1.0" encoding="utf-8"?> |
4974 | +<openerp> |
4975 | +<data> |
4976 | + |
4977 | + <!-- Override what we have in account_statement_base_completion to replace the one in the voucher --> |
4978 | + <!-- <record id="account_statement_base_completion.bank_statement_view_form2" model="ir.ui.view"> |
4979 | + <field name="name">account_bank_statement_import_base.bank_statement.auto_cmpl</field> |
4980 | + <field name="model">account.bank.statement</field> |
4981 | + <field name="inherit_id" ref="account_voucher.view_bank_statement_tree_voucher" /> |
4982 | + <field name="type">form</field> |
4983 | + <field name="arch" type="xml"> |
4984 | + <data> |
4985 | + <xpath expr="/form/sheet/notebook/page/field[@name='line_ids']/tree/field[@name='voucher_id']" position="after"> |
4986 | + <field name="already_completed" /> |
4987 | + </xpath> |
4988 | + </data> |
4989 | + </field> |
4990 | + </record> --> |
4991 | + |
4992 | +</data> |
4993 | +</openerp> |
4994 | |
4995 | === added directory 'account_statement_ext' |
4996 | === added file 'account_statement_ext/__init__.py' |
4997 | --- account_statement_ext/__init__.py 1970-01-01 00:00:00 +0000 |
4998 | +++ account_statement_ext/__init__.py 2013-07-17 14:55:41 +0000 |
4999 | @@ -0,0 +1,25 @@ |
5000 | +# -*- coding: utf-8 -*- |