Merge lp:~pexego/account-financial-tools/adding_account_tools_from_extra_addons_to into lp:~account-core-editors/account-financial-tools/6.1
- adding_account_tools_from_extra_addons_to
- Merge into 6.1
Status: | Rejected |
---|---|
Rejected by: | Joël Grand-Guillaume @ camptocamp |
Proposed branch: | lp:~pexego/account-financial-tools/adding_account_tools_from_extra_addons_to |
Merge into: | lp:~account-core-editors/account-financial-tools/6.1 |
Diff against target: |
12097 lines (+11835/-0) 51 files modified
account_admin_tools/__init__.py (+35/-0) account_admin_tools/__openerp__.py (+86/-0) account_admin_tools/account_chart_checker.py (+211/-0) account_admin_tools/account_chart_checker.xml (+54/-0) account_admin_tools/account_importer.py (+261/-0) account_admin_tools/account_importer.xml (+63/-0) account_admin_tools/account_importer_wizard.py (+268/-0) account_admin_tools/account_importer_wizard.xml (+62/-0) account_admin_tools/account_move_importer.py (+300/-0) account_admin_tools/account_move_importer.xml (+75/-0) account_admin_tools/account_move_importer_wizard.py (+266/-0) account_admin_tools/account_move_importer_wizard.xml (+74/-0) account_admin_tools/admin_tools_menu.xml (+20/-0) account_admin_tools/move_partner_account.py (+188/-0) account_admin_tools/move_partner_account.xml (+58/-0) account_admin_tools/move_partner_account_wizard.py (+145/-0) account_admin_tools/move_partner_account_wizard.xml (+42/-0) account_admin_tools/revalidate_moves.py (+157/-0) account_admin_tools/revalidate_moves.xml (+82/-0) account_admin_tools/revalidate_moves_wizard.py (+100/-0) account_admin_tools/revalidate_moves_wizard.xml (+45/-0) account_admin_tools/set_invoice_ref_in_moves.py (+205/-0) account_admin_tools/set_invoice_ref_in_moves.xml (+82/-0) account_admin_tools/set_partner_in_moves.py (+214/-0) account_admin_tools/set_partner_in_moves.xml (+82/-0) account_chart_update/__init__.py (+28/-0) account_chart_update/__openerp__.py (+64/-0) account_chart_update/account.py (+1329/-0) account_chart_update/account_view.xml (+159/-0) account_chart_update/i18n/account_chart_update.pot (+744/-0) account_chart_update/i18n/ca.po (+835/-0) account_chart_update/i18n/ca_ES.po (+323/-0) account_chart_update/i18n/es.po (+841/-0) account_chart_update/i18n/es_ES.po (+770/-0) account_chart_update/i18n/pt.po (+844/-0) account_chart_update/i18n/sv.po (+765/-0) account_renumber/__init__.py (+28/-0) account_renumber/__openerp__.py (+52/-0) account_renumber/i18n/account_renumber.pot (+148/-0) account_renumber/i18n/bg.po (+154/-0) account_renumber/i18n/ca.po (+175/-0) account_renumber/i18n/ca_ES.po (+161/-0) account_renumber/i18n/es.po (+171/-0) account_renumber/i18n/es_ES.po (+159/-0) account_renumber/i18n/gl.po (+170/-0) account_renumber/i18n/pt.po (+172/-0) account_renumber/i18n/sv.po (+153/-0) account_renumber/test/create_moves.py (+113/-0) account_renumber/wizard/__init__.py (+29/-0) account_renumber/wizard/wizard_renumber.py (+220/-0) account_renumber/wizard/wizard_renumber_view.xml (+53/-0) |
To merge this branch: | bzr merge lp:~pexego/account-financial-tools/adding_account_tools_from_extra_addons_to |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Joël Grand-Guillaume @ camptocamp | Disapprove | ||
Review via email: mp+138676@code.launchpad.net |
Commit message
Description of the change
Moving pxgo_account_
pxgo_account_
pxgo_account_
PEP8
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote : | # |
Santi Argüeso(Pexego) (santiago-pexego) wrote : | # |
Just a little explanation:
The case of account_renumber is something special. In spanish localization, move number is not related to invoice number and is normal for accountants renumber theses moves ata the end of the period of year . So i think we have a conflict here. I'm not sure wich could be the best solution, maybe Include this module in spanish localization if you think that is not usefull for other accounting sistems
I totally agree with you about account_
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote : | # |
Hi Santi,
Thanks for the explanation on account_renumber. An easy solution will be to put the default menu in Administration and add one in your spanish localization in the account section, what do you think ?
For the account_
I don't want to be to restrictive, but honestly, I don't want to include a module in which we have >70% of the code that will not be used, what do you think ?
Other though here ?
Regards,
Joël
Santi Argüeso(Pexego) (santiago-pexego) wrote : | # |
Hi Joël,
First of all, thanks a lot for your incredible work.
We can do what you propose for account_renumber. That's a good idea.
And yes, I think we are not using this tools in v6.1. I can not say if anybody use it (anyone?) but we can do what you propose, we can live with this. I don't think at all you are being restrictive, you are doing the best for all community to have a good set of modules. Thanks again.
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote : | # |
Hi Santi,
So, I mark this merge as rejected to have a better visibility. We start to have quite a lot a review to do ;) Don't hesitate to resubmit.
Thanks for your understanding, have a nice end of year !
Regards,
Joël
Unmerged revisions
Preview Diff
1 | === added directory 'account_admin_tools' |
2 | === added file 'account_admin_tools/__init__.py' |
3 | --- account_admin_tools/__init__.py 1970-01-01 00:00:00 +0000 |
4 | +++ account_admin_tools/__init__.py 2012-12-07 10:00:37 +0000 |
5 | @@ -0,0 +1,35 @@ |
6 | +# -*- coding: utf-8 -*- |
7 | +############################################################################## |
8 | +# |
9 | +# OpenERP, Open Source Management Solution |
10 | +# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved |
11 | +# $Id$ |
12 | +# |
13 | +# This program is free software: you can redistribute it and/or modify |
14 | +# it under the terms of the GNU Affero General Public License as published |
15 | +# by the Free Software Foundation, either version 3 of the License, or |
16 | +# (at your option) any later version. |
17 | +# |
18 | +# This program is distributed in the hope that it will be useful, |
19 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
20 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
21 | +# GNU Affero General Public License for more details. |
22 | +# |
23 | +# You should have received a copy of the GNU Affero General Public License |
24 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
25 | +# |
26 | +############################################################################## |
27 | + |
28 | +""" |
29 | +Account Admin Tools |
30 | +""" |
31 | + |
32 | +__author__ = "Borja López Soilán (Pexego)" |
33 | + |
34 | +import account_importer |
35 | +import account_move_importer |
36 | +import account_chart_checker |
37 | +import revalidate_moves |
38 | +import move_partner_account |
39 | +import set_partner_in_moves |
40 | +import set_invoice_ref_in_moves |
41 | |
42 | === added file 'account_admin_tools/__openerp__.py' |
43 | --- account_admin_tools/__openerp__.py 1970-01-01 00:00:00 +0000 |
44 | +++ account_admin_tools/__openerp__.py 2012-12-07 10:00:37 +0000 |
45 | @@ -0,0 +1,86 @@ |
46 | +# -*- coding: utf-8 -*- |
47 | +############################################################################## |
48 | +# |
49 | +# OpenERP, Open Source Management Solution |
50 | +# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved |
51 | +# $Id$ |
52 | +# |
53 | +# This program is free software: you can redistribute it and/or modify |
54 | +# it under the terms of the GNU Affero General Public License as published |
55 | +# by the Free Software Foundation, either version 3 of the License, or |
56 | +# (at your option) any later version. |
57 | +# |
58 | +# This program is distributed in the hope that it will be useful, |
59 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
60 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
61 | +# GNU Affero General Public License for more details. |
62 | +# |
63 | +# You should have received a copy of the GNU Affero General Public License |
64 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
65 | +# |
66 | +############################################################################## |
67 | + |
68 | +{ |
69 | + "name": "Account Admin Tools", |
70 | + "version": "6.1", |
71 | + "author": "Pexego", |
72 | + "website": "http://www.pexego.es", |
73 | + "category": "Enterprise Specific Modules", |
74 | + "description": """Account Tools for Administrators |
75 | + |
76 | +Import tools: |
77 | + |
78 | +- Import accounts from CSV files. This may be useful to import the initial |
79 | + accounts into OpenERP. |
80 | + |
81 | +- Import account moves from CSV files. This may be useful to import the initial |
82 | + balance into OpenERP. |
83 | + |
84 | + |
85 | +Check and Repair tools: |
86 | + |
87 | +- Check the Chart of Accounts for problems in its structure. This will allow |
88 | + you to detect incoherences like the ones caused by bugs like |
89 | + https://bugs.launchpad.net/openobject-server/+bug/581137 |
90 | + (the preordered tree [parent_left/parent_right] not matching the |
91 | + parent-child structure [parent_id]). |
92 | + |
93 | +- Revalidate confirmed account moves so their analytic lines are regenerated. |
94 | + This may be used to fix the data after bugs like |
95 | + https://bugs.launchpad.net/openobject-addons/+bug/582988 |
96 | + The wizard also lets you find account moves missing their analytic lines. |
97 | + |
98 | +- Set the receivable/payable account of the partners, in moves and invoices |
99 | + where a generic receivable/payable account was used instead. |
100 | + |
101 | +- Set the parent reference in account move lines where the receivable/payable |
102 | + account associated with the partner was used, but a partner reference wasn't |
103 | + set. This may fix cases where the receivable/payable amounts displayed in the |
104 | + partner form does not match the balance of the receivable/payable accounts. |
105 | + |
106 | +- Set the reference in account moves, associated with invoices, that do not |
107 | + have the right reference (the reference from the invoice if it was a supplier |
108 | + invoice, or the number from the invoice if it was a customer invoice). |
109 | + This is useful to fix the account moves after changing the invoice |
110 | + references. |
111 | + """, |
112 | + "depends": [ |
113 | + 'base', |
114 | + 'account', |
115 | + ], |
116 | + "init_xml": [], |
117 | + "demo_xml": [], |
118 | + "update_xml": [ |
119 | + 'admin_tools_menu.xml', |
120 | + 'account_importer.xml', |
121 | + 'account_move_importer.xml', |
122 | + 'account_chart_checker.xml', |
123 | + 'revalidate_moves.xml', |
124 | + 'move_partner_account.xml', |
125 | + 'set_partner_in_moves.xml', |
126 | + 'set_invoice_ref_in_moves.xml', |
127 | + ], |
128 | + "installable": True, |
129 | + 'active': False |
130 | + |
131 | +} |
132 | |
133 | === added file 'account_admin_tools/account_chart_checker.py' |
134 | --- account_admin_tools/account_chart_checker.py 1970-01-01 00:00:00 +0000 |
135 | +++ account_admin_tools/account_chart_checker.py 2012-12-07 10:00:37 +0000 |
136 | @@ -0,0 +1,211 @@ |
137 | +# -*- coding: utf-8 -*- |
138 | +############################################################################## |
139 | +# |
140 | +# OpenERP, Open Source Management Solution |
141 | +# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved |
142 | +# $Id$ |
143 | +# |
144 | +# This program is free software: you can redistribute it and/or modify |
145 | +# it under the terms of the GNU Affero General Public License as published |
146 | +# by the Free Software Foundation, either version 3 of the License, or |
147 | +# (at your option) any later version. |
148 | +# |
149 | +# This program is distributed in the hope that it will be useful, |
150 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
151 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
152 | +# GNU Affero General Public License for more details. |
153 | +# |
154 | +# You should have received a copy of the GNU Affero General Public License |
155 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
156 | +# |
157 | +############################################################################## |
158 | +""" |
159 | +Account Chart Checker Wizard |
160 | +""" |
161 | +__author__ = "Borja López Soilán (Pexego)" |
162 | + |
163 | +import re |
164 | +from osv import fields, osv |
165 | +from tools.translate import _ |
166 | + |
167 | + |
168 | +class account_chart_checker_problem(osv.osv_memory): |
169 | + """ |
170 | + A problem found in the account chart |
171 | + """ |
172 | + _name = "account_admin_tools.account_chart_checker_problem" |
173 | + _description = "Account Chart Problem" |
174 | + |
175 | + _columns = { |
176 | + 'wizard_id': fields.many2one('account_admin_tools.account_chart_checker', |
177 | + 'Wizard', required=True, readonly=True), |
178 | + 'account_id': fields.many2one('account.account', 'Account', required=True, |
179 | + readonly=True), |
180 | + 'severity': fields.selection([('informative', 'Informative'), |
181 | + ('low', 'Low'), ('medium', 'Medium'), ('high', 'High')], |
182 | + 'Severity', readonly=True), |
183 | + 'problem': fields.selection([ |
184 | + ('not_parent_of_children', 'Not parent of its children'), |
185 | + ('not_children_of_parent', 'Not children of its parent'), |
186 | + ], 'Problem', readonly=True), |
187 | + 'description': fields.text('Description') |
188 | + } |
189 | + |
190 | + def search(self, cr, uid, args, offset=0, limit=None, order=None, |
191 | + context=None, count=False): |
192 | + """ |
193 | + Redefinition of the search method (as osv_memory wizards currently |
194 | + don't support domain filters by themselves. |
195 | + """ |
196 | + problem_ids = super(account_chart_checker_problem, self).search(cr, |
197 | + uid, args, offset=offset, limit=limit, order=order, |
198 | + context=context, count=count) |
199 | + |
200 | + for arg in args: |
201 | + if arg[0] == 'wizard_id' and arg[1] == '=': |
202 | + wizard_id = arg[2] |
203 | + problems = self.browse(cr, uid, problem_ids, context=context) |
204 | + problem_ids = [problem.id for problem in problems |
205 | + if problem.wizard_id.id == wizard_id] |
206 | + |
207 | + return problem_ids |
208 | + |
209 | + |
210 | +account_chart_checker_problem() |
211 | + |
212 | + |
213 | +class account_chart_checker(osv.osv_memory): |
214 | + """ |
215 | + Account Chart Checker |
216 | + """ |
217 | + _name = "account_admin_tools.account_chart_checker" |
218 | + _description = "Account Chart Checker Wizard" |
219 | + |
220 | + _columns = { |
221 | + 'company_id': fields.many2one('res.company', 'Company', |
222 | + required=True, readonly=True), |
223 | + 'problem_ids': fields.one2many('account_admin_tools.account_chart_checker_problem', |
224 | + 'wizard_id', 'Problems'), |
225 | + 'state': fields.selection([('new', 'New'), ('done', 'Done')], |
226 | + 'Status', readonly=True), |
227 | + } |
228 | + |
229 | + _defaults = { |
230 | + 'state': lambda *a: 'new', |
231 | + 'company_id': lambda self, cr, uid, context:\ |
232 | + self.pool.get('res.users').browse(cr, uid, uid,\ |
233 | + context).company_id.id, |
234 | + } |
235 | + |
236 | + def action_check(self, cr, uid, ids, context=None): |
237 | + """ |
238 | + Checks the account chart and reports the problems it finds. |
239 | + """ |
240 | + for wiz in self.browse(cr, uid, ids, context): |
241 | + problems = [] |
242 | + |
243 | + account_ids = self.pool.get('account.account').search(cr, |
244 | + uid, [], context=context) |
245 | + |
246 | + for account in self.pool.get('account.account').browse(cr, |
247 | + uid, account_ids, context=context): |
248 | + self._check_parent_of_children(cr, uid, account, problems) |
249 | + self._check_child_of_parent(cr, uid, account, problems) |
250 | + |
251 | + self.write(cr, uid, [wiz.id], { |
252 | + 'problem_ids': [(0, 0, problem) |
253 | + for problem in problems] or None, |
254 | + 'state': 'done' |
255 | + }) |
256 | + |
257 | + # |
258 | + # Return the next view: Show the problems |
259 | + # |
260 | + model_data_ids = self.pool.get('ir.model.data').search(cr, uid, [ |
261 | + ('model', '=', 'ir.ui.view'), |
262 | + ('module', '=', 'account_admin_tools'), |
263 | + ('name', '=', 'account_chart_checker_problem_tree') |
264 | + ]) |
265 | + resource_id = self.pool.get('ir.model.data').read(cr, uid, |
266 | + model_data_ids, fields=['res_id'], context=context)[0]['res_id'] |
267 | + |
268 | + return { |
269 | + 'name': _("Problems Found in the Chart of Accounts"), |
270 | + 'type': 'ir.actions.act_window', |
271 | + 'res_model': 'account_admin_tools.account_chart_checker_problem', |
272 | + 'view_type': 'form', |
273 | + 'view_mode': 'tree', |
274 | + 'views': [(resource_id, 'tree')], |
275 | + 'domain': "[('wizard_id', '=', %s)]" % wiz.id, |
276 | + 'context': context, |
277 | + } |
278 | + |
279 | + def _check_parent_of_children(self, cr, uid, account, problems=[], |
280 | + context=None): |
281 | + """ |
282 | + Checks that for a parent account, every children has that account |
283 | + as its parent. |
284 | + """ |
285 | + query = """ |
286 | + SELECT id FROM account_account WHERE parent_left > %s |
287 | + and parent_right < %s AND COALESCE(parent_id,0) NOT IN |
288 | + (SELECT id FROM account_account WHERE parent_left > %s |
289 | + and parent_right < %s) AND COALESCE(parent_id,0) != %s |
290 | + """ |
291 | + cr.execute(query, (account.parent_left or 0, account.parent_right |
292 | + or 0, account.parent_left or 0, account.parent_right or 0, |
293 | + account.id)) |
294 | + problematic_ids = filter(None, map(lambda x: x[0], cr.fetchall())) |
295 | + |
296 | + for child in self.pool.get('account.account').browse(cr, uid, |
297 | + problematic_ids, context=context): |
298 | + problems.append({ |
299 | + 'problem': 'not_parent_of_children', |
300 | + 'severity': 'high', |
301 | + 'account_id': account.id, |
302 | + 'description': _('The account %d (%s) is listed as \ |
303 | + children of %d (%s) in the preordered tree, \ |
304 | + but its parent is %d (%s)') |
305 | + % (child.id, child.code, account.id, |
306 | + account.code, child.parent_id and |
307 | + child.parent_id.id, child.parent_id |
308 | + and child.parent_id.code) |
309 | + }) |
310 | + |
311 | + def _check_child_of_parent(self, cr, uid, account, problems=[], |
312 | + context=None): |
313 | + """ |
314 | + Checks that for a child account, his parent has that account |
315 | + in its children. |
316 | + """ |
317 | + query = """ |
318 | + SELECT id FROM account_account WHERE parent_left < %s |
319 | + and parent_right > %s |
320 | + """ |
321 | + cr.execute(query, (account.parent_left or 0, account.parent_right |
322 | + or 0)) |
323 | + parent_ids = filter(None, map(lambda x: x[0], cr.fetchall())) |
324 | + |
325 | + if account.parent_id and (account.parent_id.id not in parent_ids): |
326 | + problems.append({ |
327 | + 'problem': 'not_children_of_parent', |
328 | + 'severity': 'high', |
329 | + 'account_id': account.id, |
330 | + 'description': _('The account %d (%s) is children of %d\ |
331 | + (%s), but is not listed as its children on\ |
332 | + the preordered tree') |
333 | + % (account.id, account.code, |
334 | + account.parent_id.id, account.parent_id.code) |
335 | + }) |
336 | + elif parent_ids and not account.parent_id: |
337 | + problems.append({ |
338 | + 'problem': 'not_children_of_parent', |
339 | + 'severity': 'high', |
340 | + 'account_id': account.id, |
341 | + 'description': _('The account %d (%s) is a top level account,\ |
342 | + but is listed as a child on the preordered tree') |
343 | + % (account.id, account.code) |
344 | + }) |
345 | + |
346 | + |
347 | +account_chart_checker() |
348 | |
349 | === added file 'account_admin_tools/account_chart_checker.xml' |
350 | --- account_admin_tools/account_chart_checker.xml 1970-01-01 00:00:00 +0000 |
351 | +++ account_admin_tools/account_chart_checker.xml 2012-12-07 10:00:37 +0000 |
352 | @@ -0,0 +1,54 @@ |
353 | +<?xml version="1.0" encoding="utf-8"?> |
354 | +<openerp> |
355 | + <data> |
356 | + <record id="view_account_chart_checker_form" model="ir.ui.view"> |
357 | + <field name="name">account_chart_checker.form</field> |
358 | + <field name="model">account_admin_tools.account_chart_checker</field> |
359 | + <field name="type">form</field> |
360 | + <field name="arch" type="xml"> |
361 | + <form string="Check the Chart of Accounts"> |
362 | + <label string="This wizard will search for problems in the Chart of Accounts:" colspan="4"/> |
363 | + <label string="" colspan="4"/> |
364 | + <label string="- It will verify that the preordered tree of accounts, that OpenERP uses to calculate the amounts, matches the parent-children structure" colspan="4"/> |
365 | + <label string="" colspan="4"/> |
366 | + <label string="A list with the problems found (if any) will be shown afterwards." colspan="4"/> |
367 | + <label string="" colspan="4"/> |
368 | + <group colspan="4"> |
369 | + <button string="Cancel" special="cancel" icon="gtk-cancel" states="new"/> |
370 | + <button string="Check" name="action_check" type="object" icon="gtk-apply" states="new"/> |
371 | + </group> |
372 | + |
373 | + <field name="state" invisible="1"/> |
374 | + </form> |
375 | + </field> |
376 | + </record> |
377 | + |
378 | + <record id="account_chart_checker_problem_tree" model="ir.ui.view"> |
379 | + <field name="name">account_chart_checker_problem.tree</field> |
380 | + <field name="model">account_admin_tools.account_chart_checker_problem</field> |
381 | + <field name="type">tree</field> |
382 | + <field name="arch" type="xml"> |
383 | + <tree string="Problems" colors="red:severity=='high';green:severity=='informative'"> |
384 | + <field name="problem"/> |
385 | + <field name="severity"/> |
386 | + <field name="account_id"/> |
387 | + <field name="description"/> |
388 | + </tree> |
389 | + </field> |
390 | + </record> |
391 | + |
392 | + <record id="action_account_chart_checker" model="ir.actions.act_window"> |
393 | + <field name="name">Check the Chart of Accounts</field> |
394 | + <field name="res_model">account_admin_tools.account_chart_checker</field> |
395 | + <field name="view_type">form</field> |
396 | + <field name="view_mode">form</field> |
397 | + <field name="view_id" ref="view_account_chart_checker_form"/> |
398 | + <field name="target">new</field> |
399 | + </record> |
400 | + <menuitem id="menu_action_account_chart_checker" |
401 | + parent="menu_action_account_admin_tools_repair" |
402 | + action="action_account_chart_checker" |
403 | + sequence="10"/> |
404 | + |
405 | + </data> |
406 | +</openerp> |
407 | |
408 | === added file 'account_admin_tools/account_importer.py' |
409 | --- account_admin_tools/account_importer.py 1970-01-01 00:00:00 +0000 |
410 | +++ account_admin_tools/account_importer.py 2012-12-07 10:00:37 +0000 |
411 | @@ -0,0 +1,261 @@ |
412 | +# -*- coding: utf-8 -*- |
413 | +############################################################################## |
414 | +# |
415 | +# OpenERP, Open Source Management Solution |
416 | +# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved |
417 | +# $Id$ |
418 | +# |
419 | +# This program is free software: you can redistribute it and/or modify |
420 | +# it under the terms of the GNU Affero General Public License as published |
421 | +# by the Free Software Foundation, either version 3 of the License, or |
422 | +# (at your option) any later version. |
423 | +# |
424 | +# This program is distributed in the hope that it will be useful, |
425 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
426 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
427 | +# GNU Affero General Public License for more details. |
428 | +# |
429 | +# You should have received a copy of the GNU Affero General Public License |
430 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
431 | +# |
432 | +############################################################################## |
433 | +""" |
434 | +Account Importer |
435 | +""" |
436 | +__author__ = "Borja López Soilán (Pexego)" |
437 | + |
438 | +import logging |
439 | +import time |
440 | +import csv |
441 | +import base64 |
442 | +import StringIO |
443 | +import re |
444 | +from osv import fields, osv |
445 | +from tools.translate import _ |
446 | + |
447 | + |
448 | +class account_importer(osv.osv_memory): |
449 | + """ |
450 | + Account Importer |
451 | + |
452 | + Creates accounts from a CSV file. |
453 | + |
454 | + The CSV file lines are expected to have at least the code and name of the |
455 | + account. |
456 | + |
457 | + The wizard will find the account brothers (or parent) having the same |
458 | + account code sufix, and will autocomplete the rest of the account |
459 | + parameters (account type, reconcile, parent account...). |
460 | + |
461 | + The CSV file lines are tested to be valid account lines using the regular |
462 | + expresion options of the wizard. |
463 | + """ |
464 | + _name = "account_admin_tools.account_importer" |
465 | + _description = "Account importation wizard" |
466 | + |
467 | + _columns = { |
468 | + # |
469 | + # Account move parameters |
470 | + # |
471 | + 'company_id': fields.many2one('res.company', 'Company', required=True), |
472 | + 'overwrite': fields.boolean('Overwrite', help="If the account already\ |
473 | + exists, overwrite its name?"), |
474 | + # |
475 | + # Input file |
476 | + # |
477 | + 'input_file': fields.binary('File', filters="*.csv", required=True), |
478 | + 'input_file_name': fields.char('File name', size=256), |
479 | + 'csv_delimiter': fields.char('Delimiter', size=1, required=True), |
480 | + 'csv_quotechar': fields.char('Quote', size=1, required=True), |
481 | + 'csv_code_index': fields.integer('Code field', required=True), |
482 | + 'csv_code_regexp': fields.char('Code regexp', size=32, required=True), |
483 | + 'csv_name_index': fields.integer('Name field', required=True), |
484 | + 'csv_name_regexp': fields.char('Name regexp', size=32, required=True), |
485 | + } |
486 | + |
487 | + _defaults = { |
488 | + 'company_id': lambda self, cr, uid, context:\ |
489 | + self.pool.get('res.users').browse(cr, uid, uid,\ |
490 | + context).company_id.id, |
491 | + 'csv_delimiter': lambda *a: ';', |
492 | + 'csv_quotechar': lambda *a: '"', |
493 | + 'csv_code_index': lambda *a: 0, |
494 | + 'csv_name_index': lambda *a: 1, |
495 | + 'csv_code_regexp': lambda *a: r'^[0-9]+$', |
496 | + 'csv_name_regexp': lambda *a: r'^.*$', |
497 | + } |
498 | + |
499 | + def _find_parent_account_id(self, cr, uid, wiz, account_code, context=None): |
500 | + """ |
501 | + Finds the parent account given an account code. |
502 | + It will remove the last digit of the code until it finds an |
503 | + account that matches exactly the code. |
504 | + """ |
505 | + if len(account_code) > 0: |
506 | + parent_account_code = account_code[:-1] |
507 | + while len(parent_account_code) > 0: |
508 | + account_ids = self.pool.get('account.account').search(cr,\ |
509 | + uid, [('code', '=', parent_account_code), |
510 | + ('company_id', '=', wiz.company_id.id) |
511 | + ]) |
512 | + if account_ids and len(account_ids) > 0: |
513 | + return account_ids[0] |
514 | + parent_account_code = parent_account_code[:-1] |
515 | + # No parent found |
516 | + return None |
517 | + |
518 | + def _find_brother_account_id(self, cr, uid, wiz, account_code, context=None): |
519 | + """ |
520 | + Finds a brother account given an account code. |
521 | + It will remove the last digit of the code until it finds an |
522 | + account that matches the begin of the code. |
523 | + """ |
524 | + if len(account_code) > 0: |
525 | + brother_account_code = account_code[:-1] |
526 | + while len(brother_account_code) > 0: |
527 | + account_ids = self.pool.get('account.account').search(cr,\ |
528 | + uid, [ |
529 | + ('code', '=like', brother_account_code + '%%'), |
530 | + ('company_id', '=', wiz.company_id.id) |
531 | + ]) |
532 | + if account_ids and len(account_ids) > 0: |
533 | + return account_ids[0] |
534 | + brother_account_code = brother_account_code[:-1] |
535 | + # No brother found |
536 | + return None |
537 | + |
538 | + def action_import(self, cr, uid, ids, context=None): |
539 | + """ |
540 | + Imports the accounts from the CSV file using the options from the |
541 | + wizard. |
542 | + """ |
543 | + # List of the imported accounts |
544 | + imported_account_ids = [] |
545 | + |
546 | + logger = logging.getLogger("account_importer") |
547 | + for wiz in self.browse(cr, uid, ids, context): |
548 | + if not wiz.input_file: |
549 | + raise osv.except_osv(_('UserError'),\ |
550 | + _("You need to select a file!")) |
551 | + |
552 | + # Decode the file data |
553 | + data = base64.b64decode(wiz.input_file) |
554 | + |
555 | + # |
556 | + # Read the file |
557 | + # |
558 | + reader = csv.reader(StringIO.StringIO(data), |
559 | + delimiter=str(wiz.csv_delimiter), |
560 | + quotechar=str(wiz.csv_quotechar)) |
561 | + |
562 | + for record in reader: |
563 | + # Ignore short records |
564 | + if len(record) > wiz.csv_code_index \ |
565 | + and len(record) > wiz.csv_name_index: |
566 | + |
567 | + record_code = record[wiz.csv_code_index] |
568 | + record_name = record[wiz.csv_name_index] |
569 | + |
570 | + # |
571 | + # Ignore invalid records |
572 | + # |
573 | + if re.match(wiz.csv_code_regexp, record_code) \ |
574 | + and re.match(wiz.csv_name_regexp, record_name): |
575 | + |
576 | + # |
577 | + # Search for the account |
578 | + # |
579 | + account_ids = self.pool.get('account.account').search(cr,\ |
580 | + uid, [ |
581 | + ('code', '=', record_code), |
582 | + ('company_id', '=', wiz.company_id.id) |
583 | + ]) |
584 | + if account_ids: |
585 | + if wiz.overwrite: |
586 | + logger.debug("Overwriting \ |
587 | + account: %s %s" % (record_code,\ |
588 | + record_name)) |
589 | + self.pool.get('account.account').write(cr,\ |
590 | + uid, account_ids, { |
591 | + 'name': record_name |
592 | + }) |
593 | + imported_account_ids.extend(account_ids) |
594 | + else: |
595 | + # |
596 | + # Find the account's parent |
597 | + # |
598 | + parent_account_id = self._find_parent_account_id(cr,\ |
599 | + uid, wiz, record_code) |
600 | + |
601 | + if not parent_account_id: |
602 | + logger.warning("Couldn't find a\ |
603 | + parent account for: %s" % record_code) |
604 | + |
605 | + # |
606 | + # Find the account's brother |
607 | + # (will be used as template) |
608 | + # |
609 | + brother_account_id = self._find_brother_account_id(cr,\ |
610 | + uid, wiz, record_code) |
611 | + |
612 | + if not brother_account_id: |
613 | + logger.warning("Couldn't find a\ |
614 | + brother account for: %s" % record_code) |
615 | + |
616 | + brother_account = self.pool.get('account.account').browse(cr, \ |
617 | + uid, brother_account_id) |
618 | + |
619 | + # |
620 | + # Create the new account |
621 | + # |
622 | + logger.debug("Creating new account:\ |
623 | + %s %s" % (record_code, record_name)) |
624 | + account_id = self.pool.get('account.account').create(cr,\ |
625 | + uid, { |
626 | + 'code': record_code, |
627 | + 'name': record_name, |
628 | + 'parent_id': parent_account_id, |
629 | + 'type': brother_account.type, |
630 | + 'user_type': brother_account.user_type.id, |
631 | + 'reconcile': brother_account.reconcile, |
632 | + 'company_id': wiz.company_id.id, |
633 | + 'currency_id': brother_account.currency_id.id, |
634 | + 'currency_mode': brother_account.currency_mode, |
635 | + 'active': 1, |
636 | + 'tax_ids': [(6, 0, [tax.id for \ |
637 | + tax in brother_account.tax_ids])], |
638 | + 'note': False, |
639 | + }) |
640 | + |
641 | + imported_account_ids.append(account_id) |
642 | + else: |
643 | + logger.warning("Invalid record format\ |
644 | + (ignoring line): %s" % repr(record)) |
645 | + else: |
646 | + logger.warning("Too short record \ |
647 | + (ignoring line): %s" % repr(record)) |
648 | + |
649 | + # |
650 | + # Show the accounts to the user |
651 | + # |
652 | + model_data_ids = self.pool.get('ir.model.data').search(cr, uid, [ |
653 | + ('model', '=', 'ir.ui.view'), |
654 | + ('module', '=', 'account'), |
655 | + ('name', '=', 'view_account_form') |
656 | + ]) |
657 | + resource_id = self.pool.get('ir.model.data').read(cr, uid,\ |
658 | + model_data_ids, fields=['res_id'], context=context)[0]['res_id'] |
659 | + |
660 | + return { |
661 | + 'name': _("Imported accounts"), |
662 | + 'type': 'ir.actions.act_window', |
663 | + 'res_model': 'account.account', |
664 | + 'view_type': 'form', |
665 | + 'view_mode': 'tree,form', |
666 | + 'views': [(False, 'tree'), (resource_id, 'form')], |
667 | + 'domain': "[('id', 'in', %s)]" % imported_account_ids, |
668 | + 'context': context, |
669 | + } |
670 | + |
671 | + |
672 | +account_importer() |
673 | |
674 | === added file 'account_admin_tools/account_importer.xml' |
675 | --- account_admin_tools/account_importer.xml 1970-01-01 00:00:00 +0000 |
676 | +++ account_admin_tools/account_importer.xml 2012-12-07 10:00:37 +0000 |
677 | @@ -0,0 +1,63 @@ |
678 | +<?xml version="1.0" encoding="utf-8"?> |
679 | +<openerp> |
680 | + <data> |
681 | + |
682 | + <record id="view_account_importer_form" model="ir.ui.view"> |
683 | + <field name="name">account_importer.form</field> |
684 | + <field name="model">account_admin_tools.account_importer</field> |
685 | + <field name="type">form</field> |
686 | + <field name="arch" type="xml"> |
687 | + <form string="Account importer"> |
688 | + <label string="This wizard will import accounts from a CSV file." colspan="4"/> |
689 | + <label string="Only the account code and name are needed, the rest of the required account data will be filled based on its brother accounts (same code begining)." colspan="4"/> |
690 | + <label string="" colspan="4"/> |
691 | + <newline/> |
692 | + <group string="Account parameters" colspan="4"> |
693 | + <label string="Select the parameters for the account"/> |
694 | + <group colspan="4"> |
695 | + <field name="company_id"/> |
696 | + <field name="overwrite"/> |
697 | + </group> |
698 | + </group> |
699 | + <group string="Input file" colspan="4"> |
700 | + <label string="Select the CSV file with the lines for the account move"/> |
701 | + <group colspan="4"> |
702 | + <field name="input_file_name" string="File"/> |
703 | + <field name="input_file" filename="input_file_name" nolabel="1"/> |
704 | + <group colspan="2"> |
705 | + <separator string="File format" colspan="4"/> |
706 | + <field name="csv_delimiter"/> |
707 | + <field name="csv_quotechar"/> |
708 | + </group> |
709 | + <group colspan="2"> |
710 | + <separator string="Record format" colspan="4"/> |
711 | + <field name="csv_code_index"/> |
712 | + <field name="csv_code_regexp"/> |
713 | + <field name="csv_name_index"/> |
714 | + <field name="csv_name_regexp"/> |
715 | + </group> |
716 | + </group> |
717 | + </group> |
718 | + <group colspan="4"> |
719 | + <button string="Cancel" special="cancel" icon="gtk-cancel"/> |
720 | + <button string="Import" name="action_import" type="object" icon="gtk-ok"/> |
721 | + </group> |
722 | + </form> |
723 | + </field> |
724 | + </record> |
725 | + |
726 | + <record id="action_account_importer" model="ir.actions.act_window"> |
727 | + <field name="name">Import Accounts from CSV</field> |
728 | + <field name="res_model">account_admin_tools.account_importer</field> |
729 | + <field name="view_type">form</field> |
730 | + <field name="view_mode">form</field> |
731 | + <field name="view_id" ref="view_account_importer_form"/> |
732 | + <field name="target">new</field> |
733 | + </record> |
734 | + <menuitem id="menu_action_account_importer" |
735 | + parent="menu_action_account_admin_tools_import" |
736 | + action="action_account_importer" |
737 | + sequence="10"/> |
738 | + |
739 | + </data> |
740 | +</openerp> |
741 | |
742 | === added file 'account_admin_tools/account_importer_wizard.py' |
743 | --- account_admin_tools/account_importer_wizard.py 1970-01-01 00:00:00 +0000 |
744 | +++ account_admin_tools/account_importer_wizard.py 2012-12-07 10:00:37 +0000 |
745 | @@ -0,0 +1,268 @@ |
746 | +# -*- coding: utf-8 -*- |
747 | +############################################################################## |
748 | +# |
749 | +# OpenERP, Open Source Management Solution |
750 | +# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved |
751 | +# $Id$ |
752 | +# |
753 | +# This program is free software: you can redistribute it and/or modify |
754 | +# it under the terms of the GNU Affero General Public License as published |
755 | +# by the Free Software Foundation, either version 3 of the License, or |
756 | +# (at your option) any later version. |
757 | +# |
758 | +# This program is distributed in the hope that it will be useful, |
759 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
760 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
761 | +# GNU Affero General Public License for more details. |
762 | +# |
763 | +# You should have received a copy of the GNU Affero General Public License |
764 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
765 | +# |
766 | +############################################################################## |
767 | +""" |
768 | +Account Importer |
769 | +""" |
770 | +__author__ = "Borja López Soilán (Pexego)" |
771 | + |
772 | +import time |
773 | +import csv |
774 | +import base64 |
775 | +import StringIO |
776 | +import logging |
777 | +import re |
778 | +from osv import fields, osv |
779 | +from tools.translate import _ |
780 | + |
781 | + |
782 | +class account_importer_wizard(osv.osv_memory): |
783 | + """ |
784 | + Account Importer |
785 | + |
786 | + Creates accounts from a CSV file. |
787 | + |
788 | + The CSV file lines are expected to have at least the code and name of the |
789 | + account. |
790 | + |
791 | + The wizard will find the account brothers (or parent) having the same |
792 | + account code sufix, and will autocomplete the rest of the account |
793 | + parameters (account type, reconcile, parent account...). |
794 | + |
795 | + The CSV file lines are tested to be valid account lines using the regular |
796 | + expresion options of the wizard. |
797 | + """ |
798 | + _name = "account_importer_wizard" |
799 | + _description = "Account importation wizard" |
800 | + |
801 | + _columns = { |
802 | + # |
803 | + # Account move parameters |
804 | + # |
805 | + 'company_id': fields.many2one('res.company', 'Company', required=True), |
806 | + 'overwrite': fields.boolean('Overwrite', |
807 | + help="If the account already\ exists, overwrite its name?"), |
808 | + # |
809 | + # Input file |
810 | + # |
811 | + 'input_file': fields.binary('File', filters="*.csv", required=True), |
812 | + 'input_file_name': fields.char('File name', size=256), |
813 | + 'csv_delimiter': fields.char('Delimiter', size=1, required=True), |
814 | + 'csv_quotechar': fields.char('Quote', size=1, required=True), |
815 | + 'csv_code_index': fields.integer('Code field', required=True), |
816 | + 'csv_code_regexp': fields.char('Code regexp', size=32, required=True), |
817 | + 'csv_name_index': fields.integer('Name field', required=True), |
818 | + 'csv_name_regexp': fields.char('Name regexp', size=32, required=True), |
819 | + |
820 | + } |
821 | + |
822 | + _defaults = { |
823 | + 'company_id': lambda self, cr, uid, context: |
824 | + self.pool.get('res.users').browse(cr, uid, |
825 | + uid, context).company_id.id, |
826 | + 'csv_delimiter': lambda *a: ';', |
827 | + 'csv_quotechar': lambda *a: '"', |
828 | + 'csv_code_index': lambda *a: 0, |
829 | + 'csv_name_index': lambda *a: 1, |
830 | + 'csv_code_regexp': lambda *a: r'^[0-9]+$', |
831 | + 'csv_name_regexp': lambda *a: r'^.*$', |
832 | + } |
833 | + |
834 | + def _find_parent_account_id(self, cr, uid, wiz, account_code, context=None): |
835 | + """ |
836 | + Finds the parent account given an account code. |
837 | + It will remove the last digit of the code until it finds an account that |
838 | + matches exactly the code. |
839 | + """ |
840 | + if len(account_code) > 0: |
841 | + parent_account_code = account_code[:-1] |
842 | + while len(parent_account_code) > 0: |
843 | + account_ids = self.pool.get('account.account').search(cr, uid, [ |
844 | + ('code', '=', parent_account_code), |
845 | + ('company_id', '=', wiz.company_id.id) |
846 | + ]) |
847 | + if account_ids and len(account_ids) > 0: |
848 | + return account_ids[0] |
849 | + parent_account_code = parent_account_code[:-1] |
850 | + # No parent found |
851 | + return None |
852 | + |
853 | + def _find_brother_account_id(self, cr, uid, wiz, account_code, context=None): |
854 | + """ |
855 | + Finds a brother account given an account code. |
856 | + It will remove the last digit of the code until it finds |
857 | + an account that matches the begin of the code. |
858 | + """ |
859 | + if len(account_code) > 0: |
860 | + brother_account_code = account_code[:-1] |
861 | + while len(brother_account_code) > 0: |
862 | + account_ids = self.pool.get('account.account').search(cr, |
863 | + uid, [ |
864 | + ('code', '=like', brother_account_code + '%%'), |
865 | + ('company_id', |
866 | + '=', wiz.company_id.id) |
867 | + ]) |
868 | + if account_ids and len(account_ids) > 0: |
869 | + return account_ids[0] |
870 | + brother_account_code = brother_account_code[:-1] |
871 | + # No brother found |
872 | + return None |
873 | + |
874 | + def action_import(self, cr, uid, ids, context=None): |
875 | + """ |
876 | + Imports the accounts from the CSV file using the options from the |
877 | + wizard. |
878 | + """ |
879 | + # List of the imported accounts |
880 | + imported_account_ids = [] |
881 | + |
882 | + logger = logging.getLogger("account_importer") |
883 | + for wiz in self.browse(cr, uid, ids, context): |
884 | + if not wiz.input_file: |
885 | + raise osv.except_osv(_('UserError'), |
886 | + _("You need to select a file!")) |
887 | + |
888 | + # Decode the file data |
889 | + data = base64.b64decode(wiz.input_file) |
890 | + |
891 | + # |
892 | + # Read the file |
893 | + # |
894 | + reader = csv.reader(StringIO.StringIO(data), |
895 | + delimiter=str(wiz.csv_delimiter), |
896 | + quotechar=str(wiz.csv_quotechar)) |
897 | + |
898 | + for record in reader: |
899 | + # Ignore short records |
900 | + if len(record) > wiz.csv_code_index \ |
901 | + and len(record) > wiz.csv_name_index: |
902 | + |
903 | + record_code = record[wiz.csv_code_index] |
904 | + record_name = record[wiz.csv_name_index] |
905 | + |
906 | + # |
907 | + # Ignore invalid records |
908 | + # |
909 | + if re.match(wiz.csv_code_regexp, record_code) \ |
910 | + and re.match(wiz.csv_name_regexp, record_name): |
911 | + |
912 | + # |
913 | + # Search for the account |
914 | + # |
915 | + account_ids = self.pool.get( |
916 | + 'account.account').search(cr, |
917 | + uid, [ |
918 | + ('code', |
919 | + '=', record_code), |
920 | + ('company_id', |
921 | + '=', wiz.company_id.id) |
922 | + ]) |
923 | + if account_ids: |
924 | + if wiz.overwrite: |
925 | + logger.debug("Overwriting account: %s %s" |
926 | + % (record_code, record_name)) |
927 | + self.pool.get('account.account').write(cr, |
928 | + uid, account_ids, { |
929 | + 'name': record_name |
930 | + }) |
931 | + imported_account_ids.extend(account_ids) |
932 | + else: |
933 | + # |
934 | + # Find the account's parent |
935 | + # |
936 | + parent_account_id = self._find_parent_account_id( |
937 | + cr, |
938 | + uid, wiz, record_code) |
939 | + |
940 | + if not parent_account_id: |
941 | + logger.warning("Couldn't find a parent\ |
942 | + account for: %s" % record_code) |
943 | + |
944 | + # |
945 | + # Find the account's brother (will be used as template) |
946 | + # |
947 | + brother_account_id = self._find_brother_account_id(cr, |
948 | + uid, wiz, record_code) |
949 | + |
950 | + if not brother_account_id: |
951 | + logger.warning("Couldn't find a\ |
952 | + brother account for: %s" % record_code) |
953 | + |
954 | + brother_account = self.pool.get( |
955 | + 'account.account').browse(cr, |
956 | + uid, brother_account_id) |
957 | + |
958 | + # |
959 | + # Create the new account |
960 | + # |
961 | + logger.debug("Creating new account:\ |
962 | + %s %s" % (record_code, record_name)) |
963 | + account_id = self.pool.get( |
964 | + 'account.account').create(cr, |
965 | + uid, { |
966 | + 'code': record_code, |
967 | + 'name': record_name, |
968 | + 'parent_id': parent_account_id, |
969 | + 'type': brother_account.type, |
970 | + 'user_type': brother_account.user_type.id, |
971 | + 'reconcile': brother_account.reconcile, |
972 | + 'company_id': wiz.company_id.id, |
973 | + 'currency_id': brother_account.currency_id.id, |
974 | + 'currency_mode': brother_account.currency_mode, |
975 | + 'check_history': brother_account.check_history, |
976 | + 'active': 1, |
977 | + 'tax_ids': [(6, 0, [tax.id for tax |
978 | + in brother_account.tax_ids])], |
979 | + 'note': False, |
980 | + }) |
981 | + |
982 | + imported_account_ids.append(account_id) |
983 | + else: |
984 | + logger.warning("Invalid record format \ |
985 | + (ignoring line): %s" % repr(record)) |
986 | + else: |
987 | + logger.warning("Too short record \ |
988 | + (ignoring line): %s" % repr(record)) |
989 | + |
990 | + # |
991 | + # Show the accounts to the user |
992 | + # |
993 | + model_data_ids = self.pool.get('ir.model.data').search(cr, uid, |
994 | + [ |
995 | + ('model', '=', 'ir.ui.view'), |
996 | + ('module', '=', 'account'), |
997 | + ('name', '=', 'view_account_form') |
998 | + ]) |
999 | + resource_id = self.pool.get('ir.model.data').read(cr, uid, |
1000 | + model_data_ids, fields=['res_id'])[0]['res_id'] |
1001 | + |
1002 | + return { |
1003 | + 'name': _("Imported accounts"), |
1004 | + 'type': 'ir.actions.act_window', |
1005 | + 'res_model': 'account.account', |
1006 | + 'view_type': 'form', |
1007 | + 'view_mode': 'tree, form', |
1008 | + 'views': [(False, 'tree'), (resource_id, 'form')], |
1009 | + 'domain': "[('id', 'in', %s)]" % imported_account_ids, |
1010 | + } |
1011 | + |
1012 | + |
1013 | +account_importer_wizard() |
1014 | |
1015 | === added file 'account_admin_tools/account_importer_wizard.xml' |
1016 | --- account_admin_tools/account_importer_wizard.xml 1970-01-01 00:00:00 +0000 |
1017 | +++ account_admin_tools/account_importer_wizard.xml 2012-12-07 10:00:37 +0000 |
1018 | @@ -0,0 +1,62 @@ |
1019 | +<?xml version="1.0" encoding="utf-8"?> |
1020 | +<openerp> |
1021 | + <data> |
1022 | + |
1023 | + <record id="view_account_importer_wizard_form" model="ir.ui.view"> |
1024 | + <field name="name">account_importer_wizard.form</field> |
1025 | + <field name="model">account_importer_wizard</field> |
1026 | + <field name="type">form</field> |
1027 | + <field name="arch" type="xml"> |
1028 | + <form string="Account importer"> |
1029 | + <label string="This wizard will import accounts from a CSV file." colspan="4"/> |
1030 | + <label string="Only the account code and name are needed, the rest of the required account data will be filled based on its brother accounts (same code begining)." colspan="4"/> |
1031 | + <label string=""/> |
1032 | + <newline/> |
1033 | + <group string="Account parameters" colspan="4"> |
1034 | + <label string="Select the parameters for the account"/> |
1035 | + <group colspan="4"> |
1036 | + <field name="company_id"/> |
1037 | + <field name="overwrite"/> |
1038 | + </group> |
1039 | + </group> |
1040 | + <group string="Input file" colspan="4"> |
1041 | + <label string="Select the CSV file with the lines for the account move"/> |
1042 | + <group colspan="4"> |
1043 | + <field name="input_file_name" string="File"/> |
1044 | + <field name="input_file" filename="input_file_name" nolabel="1"/> |
1045 | + <group colspan="2"> |
1046 | + <separator string="File format" colspan="4"/> |
1047 | + <field name="csv_delimiter"/> |
1048 | + <field name="csv_quotechar"/> |
1049 | + </group> |
1050 | + <group colspan="2"> |
1051 | + <separator string="Record format" colspan="4"/> |
1052 | + <field name="csv_code_index"/> |
1053 | + <field name="csv_code_regexp"/> |
1054 | + <field name="csv_name_index"/> |
1055 | + <field name="csv_name_regexp"/> |
1056 | + </group> |
1057 | + </group> |
1058 | + </group> |
1059 | + <group colspan="4"> |
1060 | + <button string="Cancel" special="cancel" icon="gtk-cancel"/> |
1061 | + <button string="Import" name="action_import" type="object" icon="gtk-ok"/> |
1062 | + </group> |
1063 | + </form> |
1064 | + </field> |
1065 | + </record> |
1066 | + |
1067 | + <record id="action_account_importer_wizard" model="ir.actions.act_window"> |
1068 | + <field name="name">Import Accounts from CSV</field> |
1069 | + <field name="res_model">account_importer_wizard</field> |
1070 | + <field name="view_type">form</field> |
1071 | + <field name="view_mode">form</field> |
1072 | + <field name="target">new</field> |
1073 | + </record> |
1074 | + <menuitem id="menu_action_account_importer_wizard" |
1075 | + parent="menu_action_account_admin_tools" |
1076 | + action="action_account_importer_wizard" |
1077 | + sequence="10"/> |
1078 | + |
1079 | + </data> |
1080 | +</openerp> |
1081 | |
1082 | === added file 'account_admin_tools/account_move_importer.py' |
1083 | --- account_admin_tools/account_move_importer.py 1970-01-01 00:00:00 +0000 |
1084 | +++ account_admin_tools/account_move_importer.py 2012-12-07 10:00:37 +0000 |
1085 | @@ -0,0 +1,300 @@ |
1086 | +# -*- coding: utf-8 -*- |
1087 | +############################################################################## |
1088 | +# |
1089 | +# OpenERP, Open Source Management Solution |
1090 | +# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved |
1091 | +# $Id$ |
1092 | +# |
1093 | +# This program is free software: you can redistribute it and/or modify |
1094 | +# it under the terms of the GNU Affero General Public License as published |
1095 | +# by the Free Software Foundation, either version 3 of the License, or |
1096 | +# (at your option) any later version. |
1097 | +# |
1098 | +# This program is distributed in the hope that it will be useful, |
1099 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1100 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1101 | +# GNU Affero General Public License for more details. |
1102 | +# |
1103 | +# You should have received a copy of the GNU Affero General Public License |
1104 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1105 | +# |
1106 | +############################################################################## |
1107 | +""" |
1108 | +Account Move Importer |
1109 | +""" |
1110 | +__author__ = "Borja López Soilán (Pexego)" |
1111 | + |
1112 | +import time |
1113 | +import logging |
1114 | +import csv |
1115 | +import base64 |
1116 | +import StringIO |
1117 | +import re |
1118 | +from osv import fields, osv |
1119 | +from tools.translate import _ |
1120 | + |
1121 | + |
1122 | +class account_move_importer(osv.osv_memory): |
1123 | + """ |
1124 | + Account Move Importer |
1125 | + |
1126 | + Wizard that imports a CSV file into a new account move. |
1127 | + |
1128 | + The CSV file is expected to have at least the account code, a reference |
1129 | + (description of the move line), the debit and the credit. |
1130 | + |
1131 | + The lines of the CSV file are tested to be valid account move lines |
1132 | + using the regular expresions set on the wizard. |
1133 | + """ |
1134 | + _name = "account_admin_tools.account_move_importer" |
1135 | + _description = "Account move importation wizard" |
1136 | + |
1137 | + _columns = { |
1138 | + # |
1139 | + # Account move parameters |
1140 | + # |
1141 | + 'company_id': fields.many2one('res.company', 'Company', |
1142 | + required=True), |
1143 | + 'ref': fields.char('Ref', size=64, required=True), |
1144 | + 'period_id': fields.many2one('account.period', 'Period', |
1145 | + required=True), |
1146 | + 'journal_id': fields.many2one('account.journal', 'Journal', |
1147 | + required=True), |
1148 | + 'date': fields.date('Date', required=True), |
1149 | + 'type': fields.selection([ |
1150 | + ('pay_voucher', 'Cash Payment'), |
1151 | + ('bank_pay_voucher', 'Bank Payment'), |
1152 | + ('rec_voucher', 'Cash Receipt'), |
1153 | + ('bank_rec_voucher', 'Bank Receipt'), |
1154 | + ('cont_voucher', 'Contra'), |
1155 | + ('journal_sale_vou', 'Journal Sale'), |
1156 | + ('journal_pur_voucher', 'Journal Purchase'), |
1157 | + ('journal_voucher', 'Journal Voucher'), |
1158 | + ], 'Type', select=True, required=True), |
1159 | + # |
1160 | + # Input file |
1161 | + # |
1162 | + 'input_file': fields.binary('File', filters="*.csv", required=True), |
1163 | + 'input_file_name': fields.char('File name', size=256), |
1164 | + 'csv_delimiter': fields.char('Delimiter', size=1, required=True), |
1165 | + 'csv_quotechar': fields.char('Quote', size=1, required=True), |
1166 | + 'csv_decimal_separator': fields.char('Decimal sep.', size=1, |
1167 | + required=True), |
1168 | + 'csv_thousands_separator': fields.char('Thousands sep.', size=1, |
1169 | + required=True), |
1170 | + 'csv_code_index': fields.integer('Code field', required=True), |
1171 | + 'csv_code_regexp': fields.char('Code regexp', size=32, required=True), |
1172 | + 'csv_ref_index': fields.integer('Ref field', required=True), |
1173 | + 'csv_ref_regexp': fields.char('Ref regexp', size=32, required=True), |
1174 | + 'csv_debit_index': fields.integer('Debit field', required=True), |
1175 | + 'csv_debit_regexp': fields.char('Debit regexp', size=32, |
1176 | + required=True), |
1177 | + 'csv_credit_index': fields.integer('Credit field', required=True), |
1178 | + 'csv_credit_regexp': fields.char('Credit regexp', size=32, |
1179 | + required=True), |
1180 | + } |
1181 | + |
1182 | + def _get_default_period_id(self, cr, uid, context=None): |
1183 | + """ |
1184 | + Returns the default period to use (based on account.move) |
1185 | + """ |
1186 | + period_ids = self.pool.get('account.period').find(cr, uid) |
1187 | + return period_ids and period_ids[0] or False |
1188 | + |
1189 | + _defaults = { |
1190 | + 'company_id': lambda self, cr, uid, context: |
1191 | + self.pool.get('res.users').browse(cr, uid, uid, |
1192 | + context).company_id.id, |
1193 | + 'period_id': _get_default_period_id, |
1194 | + 'date': lambda *a: time.strftime('%Y-%m-%d'), |
1195 | + 'type': lambda *a: 'journal_voucher', # Based on account move |
1196 | + 'csv_delimiter': lambda *a: ';', |
1197 | + 'csv_quotechar': lambda *a: '"', |
1198 | + 'csv_decimal_separator': lambda *a: '.', |
1199 | + 'csv_thousands_separator': lambda *a: ',', |
1200 | + 'csv_code_index': lambda *a: 0, |
1201 | + 'csv_ref_index': lambda *a: 1, |
1202 | + 'csv_debit_index': lambda *a: 2, |
1203 | + 'csv_credit_index': lambda *a: 3, |
1204 | + 'csv_code_regexp': lambda *a: r'^[0-9]+$', |
1205 | + 'csv_ref_regexp': lambda *a: r'^.*$', |
1206 | + 'csv_debit_regexp': lambda *a: r'^[0-9\-\.\,]*$', |
1207 | + 'csv_credit_regexp': lambda *a: r'^[0-9\-\.\,]*$', |
1208 | + } |
1209 | + |
1210 | + def _get_accounts_map(self, cr, uid, context=None): |
1211 | + """ |
1212 | + Find the receivable/payable accounts that are associated with |
1213 | + a single partner and return a (account.id, partner.id) map |
1214 | + """ |
1215 | + partner_ids = self.pool.get('res.partner').search(cr, uid, [], |
1216 | + context=context) |
1217 | + accounts_map = {} |
1218 | + for partner in self.pool.get('res.partner').browse(cr, uid, |
1219 | + partner_ids, context=context): |
1220 | + # |
1221 | + # Add the receivable account to the map |
1222 | + # |
1223 | + if accounts_map.get(partner.property_account_receivable.id, |
1224 | + None) is None: |
1225 | + accounts_map[ |
1226 | + partner.property_account_receivable.id] = partner.id |
1227 | + else: |
1228 | + # Two partners with the same receivable account: ignore |
1229 | + # this account! |
1230 | + accounts_map[partner.property_account_receivable.id] = 0 |
1231 | + # |
1232 | + # Add the payable account to the map |
1233 | + # |
1234 | + if accounts_map.get(partner.property_account_payable.id, |
1235 | + None) is None: |
1236 | + accounts_map[partner.property_account_payable.id] = partner.id |
1237 | + else: |
1238 | + # Two partners with the same receivable account: ignore |
1239 | + # this account! |
1240 | + accounts_map[partner.property_account_payable.id] = 0 |
1241 | + return accounts_map |
1242 | + |
1243 | + def action_import(self, cr, uid, ids, context=None): |
1244 | + """ |
1245 | + Imports a CSV file into a new account move using the options from |
1246 | + the wizard. |
1247 | + """ |
1248 | + accounts_map = self._get_accounts_map(cr, uid, context=context) |
1249 | + logger = logging.getLogger("account_move_importer") |
1250 | + |
1251 | + for wiz in self.browse(cr, uid, ids, context=context): |
1252 | + if not wiz.input_file: |
1253 | + raise osv.except_osv(_('UserError'), |
1254 | + _("You need to select a file!")) |
1255 | + |
1256 | + account_move_data = self.pool.get('account.move').default_get(cr, |
1257 | + uid, ['state', 'name']) |
1258 | + account_move_data.update({ |
1259 | + 'ref': wiz.ref, |
1260 | + 'journal_id': wiz.journal_id.id, |
1261 | + 'period_id': wiz.period_id.id, |
1262 | + 'date': wiz.date, |
1263 | + 'type': wiz.type, |
1264 | + 'line_id': [], |
1265 | + 'partner_id': False, |
1266 | + 'to_check': 0 |
1267 | + }) |
1268 | + |
1269 | + lines_data = account_move_data['line_id'] |
1270 | + |
1271 | + # Decode the file data |
1272 | + data = base64.b64decode(wiz.input_file) |
1273 | + |
1274 | + # |
1275 | + # Read the file |
1276 | + # |
1277 | + reader = csv.reader(StringIO.StringIO(data), |
1278 | + delimiter=str(wiz.csv_delimiter), |
1279 | + quotechar=str(wiz.csv_quotechar)) |
1280 | + |
1281 | + for record in reader: |
1282 | + # Ignore short records |
1283 | + if len(record) > wiz.csv_code_index \ |
1284 | + and len(record) > wiz.csv_ref_index \ |
1285 | + and len(record) > wiz.csv_debit_index \ |
1286 | + and len(record) > wiz.csv_credit_index: |
1287 | + |
1288 | + record_code = record[wiz.csv_code_index] |
1289 | + record_ref = record[wiz.csv_ref_index] |
1290 | + record_debit = record[wiz.csv_debit_index] |
1291 | + record_credit = record[wiz.csv_credit_index] |
1292 | + |
1293 | + # |
1294 | + # Ignore invalid records |
1295 | + # |
1296 | + if re.match(wiz.csv_code_regexp, record_code) \ |
1297 | + and re.match(wiz.csv_ref_regexp, record_ref) \ |
1298 | + and re.match(wiz.csv_debit_regexp, record_debit) \ |
1299 | + and re.match(wiz.csv_credit_regexp, record_credit): |
1300 | + # |
1301 | + # Clean the input amounts |
1302 | + # |
1303 | + record_debit = float(record_debit.replace(wiz.csv_thousands_separator, '').replace(wiz.csv_decimal_separator, '.')) |
1304 | + record_credit = float(record_credit.replace(wiz.csv_thousands_separator, '').replace(wiz.csv_decimal_separator, '.')) |
1305 | + |
1306 | + # |
1307 | + # Find the account (or fail!) |
1308 | + # |
1309 | + account_ids = self.pool.get( |
1310 | + 'account.account').search(cr, |
1311 | + uid, [ |
1312 | + ('code', |
1313 | + '=', record_code), |
1314 | + ('company_id', |
1315 | + '=', wiz.company_id.id) |
1316 | + ]) |
1317 | + if not account_ids: |
1318 | + raise osv.except_osv(_('Error'), _("Account\ |
1319 | + not found: %s!") % record_code) |
1320 | + |
1321 | + # |
1322 | + # Prepare the line data |
1323 | + # |
1324 | + line_data = { |
1325 | + 'account_id': account_ids[0], |
1326 | + 'debit': 0.0, |
1327 | + 'credit': 0.0, |
1328 | + 'name': record_ref, |
1329 | + 'ref': False, |
1330 | + 'currency_id': False, |
1331 | + 'tax_amount': False, |
1332 | + 'partner_id': accounts_map.get(account_ids[0]) |
1333 | + or False, |
1334 | + 'tax_code_id': False, |
1335 | + 'date_maturity': False, |
1336 | + 'amount_currency': False, |
1337 | + 'analytic_account_id': False, |
1338 | + } |
1339 | + |
1340 | + # |
1341 | + # Create a debit line + a credit line if needed |
1342 | + # |
1343 | + line_data_debit = line_data.copy() |
1344 | + line_data_credit = line_data |
1345 | + if record_debit != 0.0: |
1346 | + line_data_debit['debit'] = record_debit |
1347 | + lines_data.append((0, 0, line_data_debit)) |
1348 | + if record_credit != 0.0: |
1349 | + line_data_credit['credit'] = record_credit |
1350 | + lines_data.append((0, 0, line_data_credit)) |
1351 | + else: |
1352 | + logger.warning("Invalid record format\ |
1353 | + (ignoring line): %s" % repr(record)) |
1354 | + else: |
1355 | + logger.warning("Too short record\ |
1356 | + (ignoring line): %s" % repr(record)) |
1357 | + |
1358 | + # Finally create the move |
1359 | + move_id = self.pool.get('account.move').create(cr, uid, |
1360 | + account_move_data) |
1361 | + |
1362 | + # |
1363 | + # Show the move to the user |
1364 | + # |
1365 | + model_data_ids = self.pool.get('ir.model.data').search(cr, uid, [ |
1366 | + ('model', '=', 'ir.ui.view'), |
1367 | + ('module', '=', 'account'), |
1368 | + ('name', '=', 'view_move_form') |
1369 | + ]) |
1370 | + resource_id = self.pool.get('ir.model.data').read(cr, uid, |
1371 | + model_data_ids, fields=['res_id'], context=context)[0]['res_id'] |
1372 | + |
1373 | + return { |
1374 | + 'name': _("Imported account moves"), |
1375 | + 'type': 'ir.actions.act_window', |
1376 | + 'res_model': 'account.move', |
1377 | + 'view_type': 'form', |
1378 | + 'view_mode': 'form, tree', |
1379 | + #'view_id': (resource_id, 'View'), |
1380 | + 'views': [(False, 'tree'), (resource_id, 'form')], |
1381 | + 'domain': "[('id', '=', %s)]" % move_id, |
1382 | + 'context': context, |
1383 | + } |
1384 | + |
1385 | +account_move_importer() |
1386 | |
1387 | === added file 'account_admin_tools/account_move_importer.xml' |
1388 | --- account_admin_tools/account_move_importer.xml 1970-01-01 00:00:00 +0000 |
1389 | +++ account_admin_tools/account_move_importer.xml 2012-12-07 10:00:37 +0000 |
1390 | @@ -0,0 +1,75 @@ |
1391 | +<?xml version="1.0" encoding="utf-8"?> |
1392 | +<openerp> |
1393 | + <data> |
1394 | + |
1395 | + <record id="view_account_move_importer_form" model="ir.ui.view"> |
1396 | + <field name="name">account_move_importer.form</field> |
1397 | + <field name="model">account_admin_tools.account_move_importer</field> |
1398 | + <field name="type">form</field> |
1399 | + <field name="arch" type="xml"> |
1400 | + <form string="Account move importer"> |
1401 | + <label string="This wizard will import one account move from a CSV file." colspan="4"/> |
1402 | + <label string="Note: It will fail if any of the accounts do not exist in OpenERP." colspan="4"/> |
1403 | + <label string="" colspan="4"/> |
1404 | + <newline/> |
1405 | + <group string="Account move parameters" colspan="4"> |
1406 | + <label string="Select the parameters for the account move"/> |
1407 | + <group colspan="4"> |
1408 | + <field name="ref"/> |
1409 | + <field name="company_id"/> |
1410 | + <newline/> |
1411 | + <field name="journal_id"/> |
1412 | + <field name="type"/> |
1413 | + <newline/> |
1414 | + <field name="period_id"/> |
1415 | + <field name="date"/> |
1416 | + </group> |
1417 | + </group> |
1418 | + <group string="Input file" colspan="4"> |
1419 | + <label string="Select the CSV file with the lines for the account move"/> |
1420 | + <group colspan="4"> |
1421 | + <field name="input_file_name" string="File"/> |
1422 | + <field name="input_file" filename="input_file_name" nolabel="1"/> |
1423 | + <group colspan="2"> |
1424 | + <separator string="File format" colspan="4"/> |
1425 | + <field name="csv_delimiter"/> |
1426 | + <field name="csv_quotechar"/> |
1427 | + <field name="csv_thousands_separator"/> |
1428 | + <field name="csv_decimal_separator"/> |
1429 | + </group> |
1430 | + <group colspan="2"> |
1431 | + <separator string="Record format" colspan="4"/> |
1432 | + <field name="csv_code_index"/> |
1433 | + <field name="csv_code_regexp"/> |
1434 | + <field name="csv_ref_index"/> |
1435 | + <field name="csv_ref_regexp"/> |
1436 | + <field name="csv_debit_index"/> |
1437 | + <field name="csv_debit_regexp"/> |
1438 | + <field name="csv_credit_index"/> |
1439 | + <field name="csv_credit_regexp"/> |
1440 | + </group> |
1441 | + </group> |
1442 | + </group> |
1443 | + <group colspan="4"> |
1444 | + <button string="Cancel" special="cancel" icon="gtk-cancel"/> |
1445 | + <button string="Import" name="action_import" type="object" icon="gtk-ok"/> |
1446 | + </group> |
1447 | + </form> |
1448 | + </field> |
1449 | + </record> |
1450 | + |
1451 | + <record id="action_account_move_importer" model="ir.actions.act_window"> |
1452 | + <field name="name">Import Account Move from CSV</field> |
1453 | + <field name="res_model">account_admin_tools.account_move_importer</field> |
1454 | + <field name="view_type">form</field> |
1455 | + <field name="view_mode">form</field> |
1456 | + <field name="view_id" ref="view_account_move_importer_form"/> |
1457 | + <field name="target">new</field> |
1458 | + </record> |
1459 | + <menuitem id="menu_action_account_move_importer" |
1460 | + parent="menu_action_account_admin_tools_import" |
1461 | + action="action_account_move_importer" |
1462 | + sequence="20"/> |
1463 | + |
1464 | + </data> |
1465 | +</openerp> |
1466 | |
1467 | === added file 'account_admin_tools/account_move_importer_wizard.py' |
1468 | --- account_admin_tools/account_move_importer_wizard.py 1970-01-01 00:00:00 +0000 |
1469 | +++ account_admin_tools/account_move_importer_wizard.py 2012-12-07 10:00:37 +0000 |
1470 | @@ -0,0 +1,266 @@ |
1471 | +# -*- coding: utf-8 -*- |
1472 | +############################################################################## |
1473 | +# |
1474 | +# OpenERP, Open Source Management Solution |
1475 | +# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved |
1476 | +# $Id$ |
1477 | +# |
1478 | +# This program is free software: you can redistribute it and/or modify |
1479 | +# it under the terms of the GNU Affero General Public License as published |
1480 | +# by the Free Software Foundation, either version 3 of the License, or |
1481 | +# (at your option) any later version. |
1482 | +# |
1483 | +# This program is distributed in the hope that it will be useful, |
1484 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1485 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1486 | +# GNU Affero General Public License for more details. |
1487 | +# |
1488 | +# You should have received a copy of the GNU Affero General Public License |
1489 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1490 | +# |
1491 | +############################################################################## |
1492 | +""" |
1493 | +Account Move Importer |
1494 | +""" |
1495 | +__author__ = "Borja López Soilán (Pexego)" |
1496 | + |
1497 | +import time |
1498 | +import csv |
1499 | +import base64 |
1500 | +import logging |
1501 | +import StringIO |
1502 | +import re |
1503 | +from osv import fields, osv |
1504 | +from tools.translate import _ |
1505 | + |
1506 | + |
1507 | +class account_move_importer_wizard(osv.osv_memory): |
1508 | + """ |
1509 | + Account Move Importer |
1510 | + |
1511 | + Wizard that imports a CSV file into a new account move. |
1512 | + |
1513 | + The CSV file is expected to have at least the account code, a reference |
1514 | + (description of the move line), the debit and the credit. |
1515 | + |
1516 | + The lines of the CSV file are tested to be valid account move lines |
1517 | + using the regular expresions set on the wizard. |
1518 | + """ |
1519 | + _name = "account_move_importer_wizard" |
1520 | + _description = "Account move importation wizard" |
1521 | + |
1522 | + _columns = { |
1523 | + # |
1524 | + # Account move parameters |
1525 | + # |
1526 | + 'company_id': fields.many2one('res.company', 'Company', |
1527 | + required=True), |
1528 | + 'ref': fields.char('Ref', size=64, required=True), |
1529 | + 'period_id': fields.many2one('account.period', 'Period', |
1530 | + required=True), |
1531 | + 'journal_id': fields.many2one('account.journal', 'Journal', |
1532 | + required=True), |
1533 | + 'date': fields.date('Date', required=True), |
1534 | + |
1535 | + 'type': fields.selection([ |
1536 | + ('pay_voucher', 'Cash Payment'), |
1537 | + ('bank_pay_voucher', 'Bank Payment'), |
1538 | + ('rec_voucher', 'Cash Receipt'), |
1539 | + ('bank_rec_voucher', 'Bank Receipt'), |
1540 | + ('cont_voucher', 'Contra'), |
1541 | + ('journal_sale_vou', 'Journal Sale'), |
1542 | + ('journal_pur_voucher', 'Journal Purchase'), |
1543 | + ('journal_voucher', 'Journal Voucher'), |
1544 | + ], 'Type', select=True, required=True), |
1545 | + # |
1546 | + # Input file |
1547 | + # |
1548 | + 'input_file': fields.binary('File', filters="*.csv", |
1549 | + required=True), |
1550 | + 'input_file_name': fields.char('File name', size=256), |
1551 | + 'csv_delimiter': fields.char('Delimiter', size=1, |
1552 | + required=True), |
1553 | + 'csv_quotechar': fields.char('Quote', size=1, required=True), |
1554 | + 'csv_decimal_separator': fields.char('Decimal sep.', size=1, |
1555 | + required=True), |
1556 | + 'csv_thousands_separator': fields.char('Thousands sep.', size=1, |
1557 | + required=True), |
1558 | + 'csv_code_index': fields.integer('Code field', required=True), |
1559 | + 'csv_code_regexp': fields.char('Code regexp', size=32, required=True), |
1560 | + 'csv_ref_index': fields.integer('Ref field', required=True), |
1561 | + 'csv_ref_regexp': fields.char('Ref regexp', size=32, required=True), |
1562 | + 'csv_debit_index': fields.integer('Debit field', required=True), |
1563 | + 'csv_debit_regexp': fields.char('Debit regexp', size=32, |
1564 | + required=True), |
1565 | + 'csv_credit_index': fields.integer('Credit field', required=True), |
1566 | + 'csv_credit_regexp': fields.char('Credit regexp', size=32, |
1567 | + required=True), |
1568 | + } |
1569 | + |
1570 | + def _get_default_period_id(self, cr, uid, context=None): |
1571 | + """ |
1572 | + Returns the default period to use (based on account.move) |
1573 | + """ |
1574 | + period_ids = self.pool.get('account.period').find(cr, uid) |
1575 | + return period_ids and period_ids[0] or False |
1576 | + |
1577 | + _defaults = { |
1578 | + 'company_id': lambda self, cr, uid, context: |
1579 | + self.pool.get('res.users').browse(cr, uid, uid, |
1580 | + context).company_id.id, |
1581 | + 'period_id': _get_default_period_id, |
1582 | + 'date': lambda *a: time.strftime('%Y-%m-%d'), |
1583 | + 'type': lambda *a: 'journal_voucher', # Based on account move |
1584 | + 'csv_delimiter': lambda *a: ';', |
1585 | + 'csv_quotechar': lambda *a: '"', |
1586 | + 'csv_decimal_separator': lambda *a: '.', |
1587 | + 'csv_thousands_separator': lambda *a: ',', |
1588 | + 'csv_code_index': lambda *a: 0, |
1589 | + 'csv_ref_index': lambda *a: 1, |
1590 | + 'csv_debit_index': lambda *a: 2, |
1591 | + 'csv_credit_index': lambda *a: 3, |
1592 | + 'csv_code_regexp': lambda *a: r'^[0-9]+$', |
1593 | + 'csv_ref_regexp': lambda *a: r'^.*$', |
1594 | + 'csv_debit_regexp': lambda *a: r'^[0-9\-\.\,]*$', |
1595 | + 'csv_credit_regexp': lambda *a: r'^[0-9\-\.\,]*$', |
1596 | + } |
1597 | + |
1598 | + def action_import(self, cr, uid, ids, context=None): |
1599 | + """ |
1600 | + Imports a CSV file into a new account move using the options from |
1601 | + the wizard. |
1602 | + """ |
1603 | + logger = logging.getLogger("account_move_importer") |
1604 | + for wiz in self.browse(cr, uid, ids, context): |
1605 | + if not wiz.input_file: |
1606 | + raise osv.except_osv(_('UserError'), |
1607 | + _("You need to select a file!")) |
1608 | + |
1609 | + account_move_data = self.pool.get('account.move').default_get(cr, |
1610 | + uid, ['state', 'name']) |
1611 | + account_move_data.update({ |
1612 | + 'ref': wiz.ref, |
1613 | + 'journal_id': wiz.journal_id.id, |
1614 | + 'period_id': wiz.period_id.id, |
1615 | + 'date': wiz.date, |
1616 | + 'type': wiz.type, |
1617 | + 'line_id': [], |
1618 | + 'partner_id': False, |
1619 | + 'to_check': 0 |
1620 | + }) |
1621 | + |
1622 | + lines_data = account_move_data['line_id'] |
1623 | + |
1624 | + # Decode the file data |
1625 | + data = base64.b64decode(wiz.input_file) |
1626 | + |
1627 | + # |
1628 | + # Read the file |
1629 | + # |
1630 | + reader = csv.reader(StringIO.StringIO(data), |
1631 | + delimiter=str(wiz.csv_delimiter), |
1632 | + quotechar=str(wiz.csv_quotechar)) |
1633 | + |
1634 | + for record in reader: |
1635 | + # Ignore short records |
1636 | + if len(record) > wiz.csv_code_index \ |
1637 | + and len(record) > wiz.csv_ref_index \ |
1638 | + and len(record) > wiz.csv_debit_index \ |
1639 | + and len(record) > wiz.csv_credit_index: |
1640 | + |
1641 | + record_code = record[wiz.csv_code_index] |
1642 | + record_ref = record[wiz.csv_ref_index] |
1643 | + record_debit = record[wiz.csv_debit_index] |
1644 | + record_credit = record[wiz.csv_credit_index] |
1645 | + |
1646 | + # |
1647 | + # Ignore invalid records |
1648 | + # |
1649 | + if re.match(wiz.csv_code_regexp, record_code) \ |
1650 | + and re.match(wiz.csv_ref_regexp, record_ref) \ |
1651 | + and re.match(wiz.csv_debit_regexp, record_debit) \ |
1652 | + and re.match(wiz.csv_credit_regexp, record_credit): |
1653 | + # |
1654 | + # Clean the input amounts |
1655 | + # |
1656 | + record_debit = float(record_debit.replace(wiz.csv_thousands_separator, '').replace(wiz.csv_decimal_separator, '.')) |
1657 | + record_credit = float(record_credit.replace(wiz.csv_thousands_separator, '').replace(wiz.csv_decimal_separator, '.')) |
1658 | + |
1659 | + # |
1660 | + # Find the account (or fail!) |
1661 | + # |
1662 | + account_ids = self.pool.get( |
1663 | + 'account.account').search(cr, |
1664 | + uid, [ |
1665 | + ('code', |
1666 | + '=', record_code), |
1667 | + ('company_id', |
1668 | + '=', wiz.company_id.id) |
1669 | + ]) |
1670 | + if not account_ids: |
1671 | + raise osv.except_osv(_('Error'), |
1672 | + _("Account not found: %s!") % record_code) |
1673 | + |
1674 | + # |
1675 | + # Prepare the line data |
1676 | + # |
1677 | + line_data = { |
1678 | + 'account_id': account_ids[0], |
1679 | + 'debit': 0.0, |
1680 | + 'credit': 0.0, |
1681 | + 'name': record_ref, |
1682 | + 'ref': False, |
1683 | + 'currency_id': False, |
1684 | + 'tax_amount': False, |
1685 | + 'partner_id': False, |
1686 | + 'tax_code_id': False, |
1687 | + 'date_maturity': False, |
1688 | + 'amount_currency': False, |
1689 | + 'analytic_account_id': False, |
1690 | + } |
1691 | + |
1692 | + # |
1693 | + # Create a debit line + a credit line if needed |
1694 | + # |
1695 | + line_data_debit = line_data.copy() |
1696 | + line_data_credit = line_data |
1697 | + if record_debit != 0.0: |
1698 | + line_data_debit['debit'] = record_debit |
1699 | + lines_data.append((0, 0, line_data_debit)) |
1700 | + if record_credit != 0.0: |
1701 | + line_data_credit['credit'] = record_credit |
1702 | + lines_data.append((0, 0, line_data_credit)) |
1703 | + else: |
1704 | + logger.warning("Invalid record format\ |
1705 | + (ignoring line): %s" % repr(record)) |
1706 | + else: |
1707 | + logger.warning("Too short record\ |
1708 | + (ignoring line): %s" % repr(record)) |
1709 | + |
1710 | + # Finally create the move |
1711 | + move_id = self.pool.get('account.move').create(cr, uid, |
1712 | + account_move_data) |
1713 | + |
1714 | + # |
1715 | + # Show the move to the user |
1716 | + # |
1717 | + model_data_ids = self.pool.get('ir.model.data').search(cr, uid, [ |
1718 | + ('model', '=', 'ir.ui.view'), |
1719 | + ('module', '=', 'account'), |
1720 | + ('name', '=', 'view_move_form') |
1721 | + ]) |
1722 | + resource_id = self.pool.get('ir.model.data').read(cr, uid, |
1723 | + model_data_ids, fields=['res_id'])[0]['res_id'] |
1724 | + |
1725 | + return { |
1726 | + 'name': _("Imported account moves"), |
1727 | + 'type': 'ir.actions.act_window', |
1728 | + 'res_model': 'account.move', |
1729 | + 'view_type': 'form', |
1730 | + 'view_mode': 'form,tree', |
1731 | + #'view_id': (resource_id, 'View'), |
1732 | + 'views': [(False, 'tree'), (resource_id, 'form')], |
1733 | + 'domain': "[('id', '=', %s)]" % move_id, |
1734 | + } |
1735 | + |
1736 | +account_move_importer_wizard() |
1737 | |
1738 | === added file 'account_admin_tools/account_move_importer_wizard.xml' |
1739 | --- account_admin_tools/account_move_importer_wizard.xml 1970-01-01 00:00:00 +0000 |
1740 | +++ account_admin_tools/account_move_importer_wizard.xml 2012-12-07 10:00:37 +0000 |
1741 | @@ -0,0 +1,74 @@ |
1742 | +<?xml version="1.0" encoding="utf-8"?> |
1743 | +<openerp> |
1744 | + <data> |
1745 | + |
1746 | + <record id="view_account_move_importer_wizard_form" model="ir.ui.view"> |
1747 | + <field name="name">account_move_importer_wizard.form</field> |
1748 | + <field name="model">account_move_importer_wizard</field> |
1749 | + <field name="type">form</field> |
1750 | + <field name="arch" type="xml"> |
1751 | + <form string="Account move importer"> |
1752 | + <label string="This wizard will import one account move from a CSV file." colspan="4"/> |
1753 | + <label string="Note: It will fail if any of the accounts do not exist in OpenERP." colspan="4"/> |
1754 | + <label string=""/> |
1755 | + <newline/> |
1756 | + <group string="Account move parameters" colspan="4"> |
1757 | + <label string="Select the parameters for the account move"/> |
1758 | + <group colspan="4"> |
1759 | + <field name="ref"/> |
1760 | + <field name="company_id"/> |
1761 | + <newline/> |
1762 | + <field name="journal_id"/> |
1763 | + <field name="type"/> |
1764 | + <newline/> |
1765 | + <field name="period_id"/> |
1766 | + <field name="date"/> |
1767 | + </group> |
1768 | + </group> |
1769 | + <group string="Input file" colspan="4"> |
1770 | + <label string="Select the CSV file with the lines for the account move"/> |
1771 | + <group colspan="4"> |
1772 | + <field name="input_file_name" string="File"/> |
1773 | + <field name="input_file" filename="input_file_name" nolabel="1"/> |
1774 | + <group colspan="2"> |
1775 | + <separator string="File format" colspan="4"/> |
1776 | + <field name="csv_delimiter"/> |
1777 | + <field name="csv_quotechar"/> |
1778 | + <field name="csv_thousands_separator"/> |
1779 | + <field name="csv_decimal_separator"/> |
1780 | + </group> |
1781 | + <group colspan="2"> |
1782 | + <separator string="Record format" colspan="4"/> |
1783 | + <field name="csv_code_index"/> |
1784 | + <field name="csv_code_regexp"/> |
1785 | + <field name="csv_ref_index"/> |
1786 | + <field name="csv_ref_regexp"/> |
1787 | + <field name="csv_debit_index"/> |
1788 | + <field name="csv_debit_regexp"/> |
1789 | + <field name="csv_credit_index"/> |
1790 | + <field name="csv_credit_regexp"/> |
1791 | + </group> |
1792 | + </group> |
1793 | + </group> |
1794 | + <group colspan="4"> |
1795 | + <button string="Cancel" special="cancel" icon="gtk-cancel"/> |
1796 | + <button string="Import" name="action_import" type="object" icon="gtk-ok"/> |
1797 | + </group> |
1798 | + </form> |
1799 | + </field> |
1800 | + </record> |
1801 | + |
1802 | + <record id="action_account_move_importer_wizard" model="ir.actions.act_window"> |
1803 | + <field name="name">Import Account Move from CSV</field> |
1804 | + <field name="res_model">account_move_importer_wizard</field> |
1805 | + <field name="view_type">form</field> |
1806 | + <field name="view_mode">form</field> |
1807 | + <field name="target">new</field> |
1808 | + </record> |
1809 | + <menuitem id="menu_action_account_move_importer_wizard" |
1810 | + parent="menu_action_account_admin_tools" |
1811 | + action="action_account_move_importer_wizard" |
1812 | + sequence="20"/> |
1813 | + |
1814 | + </data> |
1815 | +</openerp> |
1816 | |
1817 | === added file 'account_admin_tools/admin_tools_menu.xml' |
1818 | --- account_admin_tools/admin_tools_menu.xml 1970-01-01 00:00:00 +0000 |
1819 | +++ account_admin_tools/admin_tools_menu.xml 2012-12-07 10:00:37 +0000 |
1820 | @@ -0,0 +1,20 @@ |
1821 | +<?xml version="1.0" encoding="utf-8"?> |
1822 | +<openerp> |
1823 | + <data> |
1824 | + |
1825 | + <menuitem id="menu_action_account_admin_tools" |
1826 | + name="Admin Tools" |
1827 | + parent="account.menu_finance" |
1828 | + sequence="999"/> |
1829 | + |
1830 | + <menuitem id="menu_action_account_admin_tools_import" |
1831 | + name="Import" |
1832 | + parent="menu_action_account_admin_tools" |
1833 | + sequence="10"/> |
1834 | + |
1835 | + <menuitem id="menu_action_account_admin_tools_repair" |
1836 | + name="Check and Repair" |
1837 | + parent="menu_action_account_admin_tools" |
1838 | + sequence="20"/> |
1839 | + </data> |
1840 | +</openerp> |
1841 | |
1842 | === added directory 'account_admin_tools/i18n' |
1843 | === added file 'account_admin_tools/move_partner_account.py' |
1844 | --- account_admin_tools/move_partner_account.py 1970-01-01 00:00:00 +0000 |
1845 | +++ account_admin_tools/move_partner_account.py 2012-12-07 10:00:37 +0000 |
1846 | @@ -0,0 +1,188 @@ |
1847 | +# -*- coding: utf-8 -*- |
1848 | +############################################################################## |
1849 | +# |
1850 | +# OpenERP, Open Source Management Solution |
1851 | +# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved |
1852 | +# $Id$ |
1853 | +# |
1854 | +# This program is free software: you can redistribute it and/or modify |
1855 | +# it under the terms of the GNU Affero General Public License as published |
1856 | +# by the Free Software Foundation, either version 3 of the License, or |
1857 | +# (at your option) any later version. |
1858 | +# |
1859 | +# This program is distributed in the hope that it will be useful, |
1860 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1861 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1862 | +# GNU Affero General Public License for more details. |
1863 | +# |
1864 | +# You should have received a copy of the GNU Affero General Public License |
1865 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1866 | +# |
1867 | +############################################################################## |
1868 | +""" |
1869 | +Move Partner Account Wizard |
1870 | + |
1871 | +Checks that the account moves use the partner account instead of a |
1872 | +generic account. |
1873 | +""" |
1874 | +__author__ = "Borja López Soilán (Pexego)" |
1875 | + |
1876 | +import re |
1877 | +from osv import fields, osv |
1878 | +from tools.translate import _ |
1879 | + |
1880 | + |
1881 | +class move_partner_account(osv.osv_memory): |
1882 | + """ |
1883 | + Move Partner Account Wizard |
1884 | + |
1885 | + Checks that the account moves use the partner account instead of a |
1886 | + generic account. |
1887 | + """ |
1888 | + _name = "account_admin_tools.move_partner_account" |
1889 | + _description = "Move Partner Account Wizard" |
1890 | + |
1891 | + _columns = { |
1892 | + 'state': fields.selection([('new', 'New'), ('done', 'Done')], |
1893 | + 'Status', readonly=True), |
1894 | + # |
1895 | + # Account move parameters |
1896 | + # |
1897 | + 'company_id': fields.many2one('res.company', 'Company', |
1898 | + required=True, readonly=True), |
1899 | + 'period_ids': fields.many2many('account.period', |
1900 | + 'move_partner_account_period_rel', 'wizard_id', |
1901 | + 'period_id', "Periods"), |
1902 | + 'account_payable_id': fields.many2one('account.account', |
1903 | + 'Account Payable', required=True), |
1904 | + 'account_receivable_id': fields.many2one('account.account', |
1905 | + 'Account Receivable', required=True), |
1906 | + } |
1907 | + |
1908 | + def _get_payable_account_id(self, cr, uid, context=None): |
1909 | + """ |
1910 | + Gets the default payable (supplier) account property value for the |
1911 | + current (user's) company. |
1912 | + """ |
1913 | + if context is None: |
1914 | + context = {} |
1915 | + company_id = self.pool.get('res.users').browse(cr, uid, uid, |
1916 | + context).company_id.id |
1917 | + res = None |
1918 | + property_ids = self.pool.get('ir.property').search(cr, uid, [ |
1919 | + '|', |
1920 | + ('company_id', '=', company_id), |
1921 | + ('company_id', '=', False), |
1922 | + ('name', '=', 'property_account_payable'), |
1923 | + ('res_id', '=', False) |
1924 | + ]) |
1925 | + if property_ids: |
1926 | + property = self.pool.get( |
1927 | + 'ir.property').browse(cr, uid, property_ids[0]) |
1928 | + if property: |
1929 | + try: |
1930 | + # OpenERP 5.0 and 5.2/6.0 revno <= 2236 |
1931 | + res = int(property.value.split(',')[1]) |
1932 | + except AttributeError: |
1933 | + # OpenERP 6.0 revno >= 2236 |
1934 | + res = property.value_reference.id |
1935 | + return res |
1936 | + |
1937 | + def _get_receivable_account_id(self, cr, uid, context=None): |
1938 | + """ |
1939 | + Gets the default receivable (customer) account property value for the |
1940 | + current (user's) company. |
1941 | + """ |
1942 | + company_id = self.pool.get('res.users').browse(cr, uid, uid, |
1943 | + context).company_id.id |
1944 | + res = None |
1945 | + property_ids = self.pool.get('ir.property').search(cr, uid, [ |
1946 | + '|', |
1947 | + ('company_id', '=', company_id), |
1948 | + ('company_id', '=', False), |
1949 | + ('name', '=', 'property_account_receivable'), |
1950 | + ('res_id', '=', False) |
1951 | + ]) |
1952 | + if property_ids: |
1953 | + property = self.pool.get('ir.property').browse(cr, uid, |
1954 | + property_ids[0]) |
1955 | + if property: |
1956 | + try: |
1957 | + # OpenERP 5.0 and 5.2/6.0 revno <= 2236 |
1958 | + res = int(property.value.split(',')[1]) |
1959 | + except AttributeError: |
1960 | + # OpenERP 6.0 revno >= 2236 |
1961 | + res = property.value_reference.id |
1962 | + return res |
1963 | + |
1964 | + _defaults = { |
1965 | + 'company_id': lambda self, cr, uid, context: |
1966 | + self.pool.get('res.users').browse(cr, uid, uid, |
1967 | + context).company_id.id, |
1968 | + 'account_payable_id': _get_payable_account_id, |
1969 | + 'account_receivable_id': _get_receivable_account_id, |
1970 | + } |
1971 | + |
1972 | + def action_set_partner_accounts_in_moves(self, cr, uid, ids, context=None): |
1973 | + """ |
1974 | + Action that searchs for the account moves that do not use the partner |
1975 | + account, but the generic payable/receivable one, and sets the partner |
1976 | + account instead. |
1977 | + """ |
1978 | + for wiz in self.browse(cr, uid, ids, context): |
1979 | + period_ids = [period.id for period in wiz.period_ids] |
1980 | + query_acc = """ |
1981 | + UPDATE account_move_line |
1982 | + SET account_id=%s |
1983 | + WHERE partner_id=%s |
1984 | + AND account_id=%s |
1985 | + """ |
1986 | + query_inv = """ |
1987 | + UPDATE account_invoice |
1988 | + SET account_id=%s |
1989 | + WHERE partner_id=%s |
1990 | + AND account_id=%s |
1991 | + """ |
1992 | + periods_str = ','.join(map(str, period_ids)) |
1993 | + if period_ids: |
1994 | + query_acc += """ AND period_id IN (%s)""" % periods_str |
1995 | + query_inv += """ AND period_id IN (%s)""" % periods_str |
1996 | + |
1997 | + partner_ids = self.pool.get('res.partner').search(cr, uid, []) |
1998 | + for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids): |
1999 | + # Receivable account |
2000 | + if partner.property_account_receivable.id != wiz.account_receivable_id.id: |
2001 | + cr.execute(query_acc % (partner.property_account_receivable.id, partner.id, wiz.account_receivable_id.id)) |
2002 | + cr.execute(query_inv % (partner.property_account_receivable.id, partner.id, wiz.account_receivable_id.id)) |
2003 | + # Payable account |
2004 | + if partner.property_account_payable.id != wiz.account_payable_id.id: |
2005 | + cr.execute(query_acc % (partner.property_account_payable.id, partner.id, wiz.account_payable_id.id)) |
2006 | + cr.execute(query_inv % (partner.property_account_payable.id, partner.id, wiz.account_payable_id.id)) |
2007 | + |
2008 | + # Update the wizard status |
2009 | + self.write(cr, uid, [wiz.id], {'state': 'done'}) |
2010 | + |
2011 | + # |
2012 | + # Return the next view: Show 'done' view |
2013 | + # |
2014 | + model_data_ids = self.pool.get('ir.model.data').search(cr, uid, [ |
2015 | + ('model', '=', 'ir.ui.view'), |
2016 | + ('module', '=', 'account_admin_tools'), |
2017 | + ('name', '=', 'view_move_partner_account_done_form') |
2018 | + ]) |
2019 | + resource_id = self.pool.get('ir.model.data').read(cr, uid, model_data_ids, fields=['res_id'], context=context)[0]['res_id'] |
2020 | + |
2021 | + return { |
2022 | + 'name': _("Set Partner Accounts in Moves"), |
2023 | + 'type': 'ir.actions.act_window', |
2024 | + 'res_model': 'account_admin_tools.move_partner_account', |
2025 | + 'view_type': 'form', |
2026 | + 'view_mode': 'form', |
2027 | + 'views': [(resource_id, 'form')], |
2028 | + 'domain': "[('id', 'in', %s)]" % ids, |
2029 | + 'context': context, |
2030 | + 'target': 'new', |
2031 | + } |
2032 | + |
2033 | + |
2034 | +move_partner_account() |
2035 | |
2036 | === added file 'account_admin_tools/move_partner_account.xml' |
2037 | --- account_admin_tools/move_partner_account.xml 1970-01-01 00:00:00 +0000 |
2038 | +++ account_admin_tools/move_partner_account.xml 2012-12-07 10:00:37 +0000 |
2039 | @@ -0,0 +1,58 @@ |
2040 | +<?xml version="1.0" encoding="utf-8"?> |
2041 | +<openerp> |
2042 | + <data> |
2043 | + <record id="view_move_partner_account_form" model="ir.ui.view"> |
2044 | + <field name="name">move_partner_account.form</field> |
2045 | + <field name="model">account_admin_tools.move_partner_account</field> |
2046 | + <field name="type">form</field> |
2047 | + <field name="arch" type="xml"> |
2048 | + <form string="Set Partner Accounts in Moves"> |
2049 | + <label string="This wizard will set the receivable/payable account of the partners, in moves and invoices where a generic receivable/payable account was used instead." colspan="4"/> |
2050 | + <label string="" colspan="4"/> |
2051 | + <newline/> |
2052 | + <group string="Parameters" colspan="4"> |
2053 | + <separator string="Accounts to replace with partner accounts" colspan="4"/> |
2054 | + <field name="account_receivable_id"/> |
2055 | + <field name="account_payable_id"/> |
2056 | + <newline/> |
2057 | + <separator string="Filter for the moves to update" colspan="4"/> |
2058 | + <field name="period_ids" colspan="4"/> |
2059 | + </group> |
2060 | + <group colspan="4"> |
2061 | + <button string="Cancel" special="cancel" icon="gtk-cancel"/> |
2062 | + <button string="Set partner accounts" name="action_set_partner_accounts_in_moves" type="object" icon="gtk-apply"/> |
2063 | + </group> |
2064 | + </form> |
2065 | + </field> |
2066 | + </record> |
2067 | + |
2068 | + <record id="view_move_partner_account_done_form" model="ir.ui.view"> |
2069 | + <field name="name">move_partner_account.done.form</field> |
2070 | + <field name="model">account_admin_tools.move_partner_account</field> |
2071 | + <field name="type">form</field> |
2072 | + <field name="arch" type="xml"> |
2073 | + <form string="Set Partner Accounts in Moves"> |
2074 | + <label string="The partner's receivable/payable accounts have been set succesfuly on the account moves!" colspan="4"/> |
2075 | + <label string="" colspan="4"/> |
2076 | + <group colspan="4"> |
2077 | + <button string="Done" special="cancel" icon="gtk-ok"/> |
2078 | + </group> |
2079 | + </form> |
2080 | + </field> |
2081 | + </record> |
2082 | + |
2083 | + <record id="action_move_partner_account" model="ir.actions.act_window"> |
2084 | + <field name="name">Set Partner Accounts in Moves</field> |
2085 | + <field name="res_model">account_admin_tools.move_partner_account</field> |
2086 | + <field name="view_type">form</field> |
2087 | + <field name="view_mode">form</field> |
2088 | + <field name="view_id" ref="view_move_partner_account_form"/> |
2089 | + <field name="target">new</field> |
2090 | + </record> |
2091 | + <menuitem id="menu_action_move_partner_account" |
2092 | + parent="menu_action_account_admin_tools_repair" |
2093 | + action="action_move_partner_account" |
2094 | + sequence="110"/> |
2095 | + |
2096 | + </data> |
2097 | +</openerp> |
2098 | |
2099 | === added file 'account_admin_tools/move_partner_account_wizard.py' |
2100 | --- account_admin_tools/move_partner_account_wizard.py 1970-01-01 00:00:00 +0000 |
2101 | +++ account_admin_tools/move_partner_account_wizard.py 2012-12-07 10:00:37 +0000 |
2102 | @@ -0,0 +1,145 @@ |
2103 | +# -*- coding: utf-8 -*- |
2104 | +############################################################################## |
2105 | +# |
2106 | +# OpenERP, Open Source Management Solution |
2107 | +# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved |
2108 | +# $Id$ |
2109 | +# |
2110 | +# This program is free software: you can redistribute it and/or modify |
2111 | +# it under the terms of the GNU Affero General Public License as published |
2112 | +# by the Free Software Foundation, either version 3 of the License, or |
2113 | +# (at your option) any later version. |
2114 | +# |
2115 | +# This program is distributed in the hope that it will be useful, |
2116 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2117 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2118 | +# GNU Affero General Public License for more details. |
2119 | +# |
2120 | +# You should have received a copy of the GNU Affero General Public License |
2121 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2122 | +# |
2123 | +############################################################################## |
2124 | +""" |
2125 | +Move Partner Account Wizard |
2126 | + |
2127 | +Checks that the account moves use the partner account instead of a |
2128 | +generic account. |
2129 | +""" |
2130 | +__author__ = "Borja López Soilán (Pexego)" |
2131 | + |
2132 | +import re |
2133 | +from osv import fields, osv |
2134 | +from tools.translate import _ |
2135 | + |
2136 | + |
2137 | +class move_partner_account_wizard(osv.osv_memory): |
2138 | + """ |
2139 | + Move Partner Account Wizard |
2140 | + |
2141 | + Checks that the account moves use the partner account instead of a |
2142 | + generic account. |
2143 | + """ |
2144 | + _name = "move_partner_account_wizard" |
2145 | + _description = "Move Partner Account Wizard" |
2146 | + |
2147 | + _columns = { |
2148 | + # |
2149 | + # Account move parameters |
2150 | + # |
2151 | + 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True), |
2152 | + |
2153 | + 'period_ids': fields.many2many('account.period', 'move_partner_account_wizard_period_rel', 'wizard_id', 'period_id', "Periods"), |
2154 | + |
2155 | + 'account_payable_id': fields.many2one('account.account', 'Account Payable', required=True), |
2156 | + 'account_receivable_id': fields.many2one('account.account', 'Account Receivable', required=True), |
2157 | + } |
2158 | + |
2159 | + def _get_payable_account_id(self, cr, uid, context=None): |
2160 | + if context is None: |
2161 | + context = {} |
2162 | + company_id = self.pool.get( |
2163 | + 'res.users').browse(cr, uid, uid, context).company_id.id |
2164 | + res = None |
2165 | + property_ids = self.pool.get('ir.property').search(cr, uid, [ |
2166 | + '|', |
2167 | + ('company_id', '=', company_id), |
2168 | + ('company_id', '=', False), |
2169 | + ('name', '=', 'property_account_payable'), |
2170 | + ('res_id', '=', False) |
2171 | + ]) |
2172 | + if property_ids: |
2173 | + property = self.pool.get( |
2174 | + 'ir.property').browse(cr, uid, property_ids[0]) |
2175 | + if property: |
2176 | + try: |
2177 | + # OpenERP 5.0 and 5.2/6.0 revno <= 2236 |
2178 | + res = int(property.value.split(',')[1]) |
2179 | + except AttributeError: |
2180 | + # OpenERP 6.0 revno >= 2236 |
2181 | + res = property.value_reference.id |
2182 | + return res |
2183 | + |
2184 | + def _get_receivable_account_id(self, cr, uid, context=None): |
2185 | + company_id = self.pool.get( |
2186 | + 'res.users').browse(cr, uid, uid, context).company_id.id |
2187 | + res = None |
2188 | + property_ids = self.pool.get('ir.property').search(cr, uid, [ |
2189 | + '|', |
2190 | + ('company_id', '=', company_id), |
2191 | + ('company_id', '=', False), |
2192 | + ('name', '=', 'property_account_receivable'), |
2193 | + ('res_id', '=', False) |
2194 | + ]) |
2195 | + if property_ids: |
2196 | + property = self.pool.get( |
2197 | + 'ir.property').browse(cr, uid, property_ids[0]) |
2198 | + if property: |
2199 | + try: |
2200 | + # OpenERP 5.0 and 5.2/6.0 revno <= 2236 |
2201 | + res = int(property.value.split(',')[1]) |
2202 | + except AttributeError: |
2203 | + # OpenERP 6.0 revno >= 2236 |
2204 | + res = property.value_reference.id |
2205 | + return res |
2206 | + |
2207 | + _defaults = { |
2208 | + 'company_id': lambda self, cr, uid, context: self.pool.get('res.users').browse(cr, uid, uid, context).company_id.id, |
2209 | + 'account_payable_id': _get_payable_account_id, |
2210 | + 'account_receivable_id': _get_receivable_account_id, |
2211 | + } |
2212 | + |
2213 | + def action_set_partner_accounts_in_moves(self, cr, uid, ids, context=None): |
2214 | + for wiz in self.browse(cr, uid, ids, context): |
2215 | + period_ids = [period.id for period in wiz.period_ids] |
2216 | + query_acc = """ |
2217 | + UPDATE account_move_line |
2218 | + SET account_id=%s |
2219 | + WHERE partner_id=%s |
2220 | + AND account_id=%s |
2221 | + """ |
2222 | + query_inv = """ |
2223 | + UPDATE account_invoice |
2224 | + SET account_id=%s |
2225 | + WHERE partner_id=%s |
2226 | + AND account_id=%s |
2227 | + """ |
2228 | + periods_str = ','.join(map(str, period_ids)) |
2229 | + if period_ids: |
2230 | + query_acc += """ AND period_id IN (%s)""" % periods_str |
2231 | + query_inv += """ AND period_id IN (%s)""" % periods_str |
2232 | + |
2233 | + partner_ids = self.pool.get('res.partner').search(cr, uid, []) |
2234 | + for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids): |
2235 | + # Receivable account |
2236 | + if partner.property_account_receivable.id != wiz.account_receivable_id.id: |
2237 | + cr.execute(query_acc % (partner.property_account_receivable.id, partner.id, wiz.account_receivable_id.id)) |
2238 | + cr.execute(query_inv % (partner.property_account_receivable.id, partner.id, wiz.account_receivable_id.id)) |
2239 | + # Payable account |
2240 | + if partner.property_account_payable.id != wiz.account_payable_id.id: |
2241 | + cr.execute(query_acc % (partner.property_account_payable.id, partner.id, wiz.account_payable_id.id)) |
2242 | + cr.execute(query_inv % (partner.property_account_payable.id, partner.id, wiz.account_payable_id.id)) |
2243 | + |
2244 | + return {} |
2245 | + |
2246 | + |
2247 | +move_partner_account_wizard() |
2248 | |
2249 | === added file 'account_admin_tools/move_partner_account_wizard.xml' |
2250 | --- account_admin_tools/move_partner_account_wizard.xml 1970-01-01 00:00:00 +0000 |
2251 | +++ account_admin_tools/move_partner_account_wizard.xml 2012-12-07 10:00:37 +0000 |
2252 | @@ -0,0 +1,42 @@ |
2253 | +<?xml version="1.0" encoding="utf-8"?> |
2254 | +<openerp> |
2255 | + <data> |
2256 | + <record id="view_move_partner_account_wizard_form" model="ir.ui.view"> |
2257 | + <field name="name">move_partner_account_wizard.form</field> |
2258 | + <field name="model">move_partner_account_wizard</field> |
2259 | + <field name="type">form</field> |
2260 | + <field name="arch" type="xml"> |
2261 | + <form string="Set partner account in moves"> |
2262 | + <label string="This wizard will set the receivable/payable account of the partners, in moves and invoices where a generic receivable/payable account was used instead." colspan="4"/> |
2263 | + <label string=""/> |
2264 | + <newline/> |
2265 | + <group string="Parameters" colspan="4"> |
2266 | + <separator string="Accounts to replace with partner accounts" colspan="4"/> |
2267 | + <field name="account_receivable_id"/> |
2268 | + <field name="account_payable_id"/> |
2269 | + <newline/> |
2270 | + <separator string="Filter for the moves to update" colspan="4"/> |
2271 | + <field name="period_ids" colspan="4"/> |
2272 | + </group> |
2273 | + <group colspan="4"> |
2274 | + <button string="Cancel" special="cancel" icon="gtk-cancel"/> |
2275 | + <button string="Set partner accounts" name="action_set_partner_accounts_in_moves" type="object" icon="gtk-ok"/> |
2276 | + </group> |
2277 | + </form> |
2278 | + </field> |
2279 | + </record> |
2280 | + |
2281 | + <record id="action_move_partner_account_wizard" model="ir.actions.act_window"> |
2282 | + <field name="name">Set Partner Account in Moves</field> |
2283 | + <field name="res_model">move_partner_account_wizard</field> |
2284 | + <field name="view_type">form</field> |
2285 | + <field name="view_mode">form</field> |
2286 | + <field name="target">new</field> |
2287 | + </record> |
2288 | + <menuitem id="menu_action_move_partner_account_wizard" |
2289 | + parent="menu_action_account_admin_tools" |
2290 | + action="action_move_partner_account_wizard" |
2291 | + sequence="20"/> |
2292 | + |
2293 | + </data> |
2294 | +</openerp> |
2295 | |
2296 | === added file 'account_admin_tools/revalidate_moves.py' |
2297 | --- account_admin_tools/revalidate_moves.py 1970-01-01 00:00:00 +0000 |
2298 | +++ account_admin_tools/revalidate_moves.py 2012-12-07 10:00:37 +0000 |
2299 | @@ -0,0 +1,157 @@ |
2300 | +# -*- coding: utf-8 -*- |
2301 | +############################################################################## |
2302 | +# |
2303 | +# OpenERP, Open Source Management Solution |
2304 | +# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved |
2305 | +# $Id$ |
2306 | +# |
2307 | +# This program is free software: you can redistribute it and/or modify |
2308 | +# it under the terms of the GNU Affero General Public License as published |
2309 | +# by the Free Software Foundation, either version 3 of the License, or |
2310 | +# (at your option) any later version. |
2311 | +# |
2312 | +# This program is distributed in the hope that it will be useful, |
2313 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2314 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2315 | +# GNU Affero General Public License for more details. |
2316 | +# |
2317 | +# You should have received a copy of the GNU Affero General Public License |
2318 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2319 | +# |
2320 | +############################################################################## |
2321 | +""" |
2322 | +Revalidate Account Moves Wizard |
2323 | +""" |
2324 | +__author__ = "Borja López Soilán (Pexego)" |
2325 | + |
2326 | +import re |
2327 | +from osv import fields, osv |
2328 | +from tools.translate import _ |
2329 | + |
2330 | + |
2331 | +class revalidate_moves(osv.osv_memory): |
2332 | + """ |
2333 | + Revalidate Account Moves Wizard |
2334 | + |
2335 | + Revalidates all the (already confirmed) moves, so their analytic lines |
2336 | + are recomputed (to fix the data after problems like this: |
2337 | + https://bugs.launchpad.net/openobject-addons/+bug/582988). |
2338 | + """ |
2339 | + _name = "account_admin_tools.revalidate_moves" |
2340 | + _description = "Revalidate Account Moves Wizard" |
2341 | + |
2342 | + _columns = { |
2343 | + 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True), |
2344 | + 'period_ids': fields.many2many('account.period', 'revalidate_moves_period_rel', 'wizard_id', 'period_id', "Periods"), |
2345 | + 'move_ids': fields.many2many('account.move', 'revalidate_moves_moves_rel', 'wizard_id', 'move_id', 'Moves'), |
2346 | + 'state': fields.selection([('new', 'New'), ('ready', 'Ready'), ('done', 'Done')], 'Status', readonly=True), |
2347 | + } |
2348 | + |
2349 | + _defaults = { |
2350 | + 'company_id': lambda self, cr, uid, context: self.pool.get('res.users').browse(cr, uid, uid, context).company_id.id, |
2351 | + 'move_ids': lambda self, cr, uid, context: context and context.get('move_ids', None), |
2352 | + 'period_ids': lambda self, cr, uid, context: context and context.get('period_ids', None), |
2353 | + 'state': lambda self, cr, uid, context: context and context.get('state', 'new'), |
2354 | + } |
2355 | + |
2356 | + def _next_view(self, cr, uid, ids, view_name, args=None, context=None): |
2357 | + """ |
2358 | + Return the next view |
2359 | + """ |
2360 | + if context is None: |
2361 | + context = {} |
2362 | + if args is None: |
2363 | + args = {} |
2364 | + ctx = context.copy() |
2365 | + ctx.update(args) |
2366 | + |
2367 | + model_data_ids = self.pool.get('ir.model.data').search(cr, uid, [ |
2368 | + ('model', '=', 'ir.ui.view'), |
2369 | + ('module', '=', 'account_admin_tools'), |
2370 | + ('name', '=', view_name) |
2371 | + ]) |
2372 | + resource_id = self.pool.get('ir.model.data').read(cr, uid, model_data_ids, fields=['res_id'], context=context)[0]['res_id'] |
2373 | + return { |
2374 | + 'name': _("Revalidate Moves"), |
2375 | + 'type': 'ir.actions.act_window', |
2376 | + 'res_model': 'account_admin_tools.revalidate_moves', |
2377 | + 'view_type': 'form', |
2378 | + 'view_mode': 'form', |
2379 | + 'views': [(resource_id, 'form')], |
2380 | + 'domain': "[('id', 'in', %s)]" % ids, |
2381 | + 'context': ctx, |
2382 | + 'target': 'new', |
2383 | + } |
2384 | + |
2385 | + def action_skip_new(self, cr, uid, ids, context=None): |
2386 | + """ |
2387 | + Action that just skips the to the ready state |
2388 | + """ |
2389 | + return self._next_view(cr, uid, ids, 'view_revalidate_moves_ready_form', {'state': 'ready'}, context) |
2390 | + |
2391 | + def action_find_moves_missing_analytic_lines(self, cr, uid, ids, context=None): |
2392 | + """ |
2393 | + Finds account moves with missing analytic lines and adds them |
2394 | + to the move_ids many2many field. |
2395 | + """ |
2396 | + wiz = self.browse(cr, uid, ids[0], context) |
2397 | + |
2398 | + # FIXME: The next block of code is a workaround to the lp:586252 bug of the 6.0 client. |
2399 | + # (https://bugs.launchpad.net/openobject-client/+bug/586252) |
2400 | + if wiz.period_ids: |
2401 | + period_ids = [period.id for period in wiz.period_ids] |
2402 | + else: |
2403 | + period_ids = context and context.get('period_ids') |
2404 | + |
2405 | + query = """ |
2406 | + SELECT account_move_line.move_id FROM account_move_line |
2407 | + LEFT JOIN account_analytic_line |
2408 | + ON account_analytic_line.move_id = account_move_line.id |
2409 | + WHERE account_move_line.analytic_account_id IS NOT NULL AND account_analytic_line.id IS NULL |
2410 | + """ |
2411 | + if period_ids: |
2412 | + query += """ AND period_id IN %s""" |
2413 | + cr.execute(query, (tuple(period_ids),)) |
2414 | + else: |
2415 | + cr.execute(query) |
2416 | + |
2417 | + move_ids = filter(None, map(lambda x: x[0], cr.fetchall())) |
2418 | + |
2419 | + # |
2420 | + # Return the next view: Show 'ready' view |
2421 | + # |
2422 | + args = { |
2423 | + 'move_ids': move_ids, |
2424 | + 'state': 'ready', |
2425 | + } |
2426 | + return self._next_view(cr, uid, ids, 'view_revalidate_moves_ready_form', args, context) |
2427 | + |
2428 | + def action_revalidate_moves(self, cr, uid, ids, context=None): |
2429 | + """ |
2430 | + Calls the validate method of the account moves for each move in the |
2431 | + move_ids many2many. |
2432 | + """ |
2433 | + wiz = self.browse(cr, uid, ids[0], context) |
2434 | + |
2435 | + # FIXME: The next block of code is a workaround to the lp:586252 bug of the 6.0 client. |
2436 | + # (https://bugs.launchpad.net/openobject-client/+bug/586252) |
2437 | + if wiz.move_ids: |
2438 | + move_ids = [line.id for line in wiz.move_ids] |
2439 | + else: |
2440 | + move_ids = context and context.get('move_ids') |
2441 | + |
2442 | + for move in self.pool.get('account.move').browse(cr, uid, move_ids, context=context): |
2443 | + # We validate the moves one by one to prevent problems |
2444 | + self.pool.get('account.move').validate(cr, uid, [move.id], context) |
2445 | + |
2446 | + # |
2447 | + # Return the next view: Show 'done' view |
2448 | + # |
2449 | + args = { |
2450 | + 'move_ids': move_ids, |
2451 | + 'state': 'done', |
2452 | + } |
2453 | + return self._next_view(cr, uid, ids, 'view_revalidate_moves_done_form', args, context) |
2454 | + |
2455 | + |
2456 | +revalidate_moves() |
2457 | |
2458 | === added file 'account_admin_tools/revalidate_moves.xml' |
2459 | --- account_admin_tools/revalidate_moves.xml 1970-01-01 00:00:00 +0000 |
2460 | +++ account_admin_tools/revalidate_moves.xml 2012-12-07 10:00:37 +0000 |
2461 | @@ -0,0 +1,82 @@ |
2462 | +<?xml version="1.0" encoding="utf-8"?> |
2463 | +<openerp> |
2464 | + <data> |
2465 | + <record id="view_revalidate_moves_form" model="ir.ui.view"> |
2466 | + <field name="name">revalidate_moves.form</field> |
2467 | + <field name="model">account_admin_tools.revalidate_moves</field> |
2468 | + <field name="type">form</field> |
2469 | + <field name="arch" type="xml"> |
2470 | + <form string="Revalidate Account Moves"> |
2471 | + <label string="This wizard will revalidate the account moves, so their analytic lines are regenerated." colspan="4"/> |
2472 | + <label string="" colspan="4"/> |
2473 | + <group colspan="4"> |
2474 | + <separator string="Find moves with missing analytic lines" colspan="4"/> |
2475 | + <label string="You may now search for account moves with missing analytic lines on the given periods, or you may skip this step and select the moves by hand." colspan="4"/> |
2476 | + <label string="" colspan="4"/> |
2477 | + <newline/> |
2478 | + <field name="period_ids" colspan="4"/> |
2479 | + <label string="" colspan="4"/> |
2480 | + <newline/> |
2481 | + <button string="Search for moves" name="action_find_moves_missing_analytic_lines" type="object" icon="gtk-ok" colspan="4"/> |
2482 | + </group> |
2483 | + <label string="" colspan="4"/> |
2484 | + <group colspan="4"> |
2485 | + <button string="Cancel" special="cancel" icon="gtk-cancel"/> |
2486 | + <button string="Skip" name="action_skip_new" type="object" icon="gtk-go-forward"/> |
2487 | + </group> |
2488 | + </form> |
2489 | + </field> |
2490 | + </record> |
2491 | + |
2492 | + <record id="view_revalidate_moves_ready_form" model="ir.ui.view"> |
2493 | + <field name="name">revalidate_moves.ready.form</field> |
2494 | + <field name="model">account_admin_tools.revalidate_moves</field> |
2495 | + <field name="type">form</field> |
2496 | + <field name="arch" type="xml"> |
2497 | + <form string="Revalidate Account Moves"> |
2498 | + <label string="The selected moves will be revalidated, that will regenerate their analytic lines." colspan="4"/> |
2499 | + <label string="" colspan="4"/> |
2500 | + <separator string="Account moves to revalidate" colspan="4"/> |
2501 | + <field name="move_ids" colspan="4" nolabel="1"/> |
2502 | + <label string="" colspan="4"/> |
2503 | + <group colspan="4"> |
2504 | + <button string="Cancel" special="cancel" icon="gtk-cancel"/> |
2505 | + <button string="Revalidate selected moves" name="action_revalidate_moves" type="object" icon="gtk-apply"/> |
2506 | + </group> |
2507 | + </form> |
2508 | + </field> |
2509 | + </record> |
2510 | + |
2511 | + <record id="view_revalidate_moves_done_form" model="ir.ui.view"> |
2512 | + <field name="name">revalidate_moves.done.form</field> |
2513 | + <field name="model">account_admin_tools.revalidate_moves</field> |
2514 | + <field name="type">form</field> |
2515 | + <field name="arch" type="xml"> |
2516 | + <form string="Revalidate Account Moves"> |
2517 | + <label string="The moves have been revalidated sucessfully!" colspan="4"/> |
2518 | + <label string="" colspan="4"/> |
2519 | + <separator string="Revalidated account moves" colspan="4"/> |
2520 | + <field name="move_ids" colspan="4" nolabel="1" readonly="1"/> |
2521 | + <label string="" colspan="4"/> |
2522 | + <group colspan="4"> |
2523 | + <button string="Done" special="cancel" icon="gtk-ok"/> |
2524 | + </group> |
2525 | + </form> |
2526 | + </field> |
2527 | + </record> |
2528 | + |
2529 | + <record id="action_revalidate_moves" model="ir.actions.act_window"> |
2530 | + <field name="name">Revalidate Account Moves (Regenerate Analytic Lines)</field> |
2531 | + <field name="res_model">account_admin_tools.revalidate_moves</field> |
2532 | + <field name="view_type">form</field> |
2533 | + <field name="view_mode">form</field> |
2534 | + <field name="view_id" ref="view_revalidate_moves_form"/> |
2535 | + <field name="target">new</field> |
2536 | + </record> |
2537 | + <menuitem id="menu_action_revalidate_moves" |
2538 | + parent="menu_action_account_admin_tools_repair" |
2539 | + action="action_revalidate_moves" |
2540 | + sequence="20"/> |
2541 | + |
2542 | + </data> |
2543 | +</openerp> |
2544 | |
2545 | === added file 'account_admin_tools/revalidate_moves_wizard.py' |
2546 | --- account_admin_tools/revalidate_moves_wizard.py 1970-01-01 00:00:00 +0000 |
2547 | +++ account_admin_tools/revalidate_moves_wizard.py 2012-12-07 10:00:37 +0000 |
2548 | @@ -0,0 +1,100 @@ |
2549 | +# -*- coding: utf-8 -*- |
2550 | +############################################################################## |
2551 | +# |
2552 | +# OpenERP, Open Source Management Solution |
2553 | +# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved |
2554 | +# $Id$ |
2555 | +# |
2556 | +# This program is free software: you can redistribute it and/or modify |
2557 | +# it under the terms of the GNU Affero General Public License as published |
2558 | +# by the Free Software Foundation, either version 3 of the License, or |
2559 | +# (at your option) any later version. |
2560 | +# |
2561 | +# This program is distributed in the hope that it will be useful, |
2562 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2563 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2564 | +# GNU Affero General Public License for more details. |
2565 | +# |
2566 | +# You should have received a copy of the GNU Affero General Public License |
2567 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2568 | +# |
2569 | +############################################################################## |
2570 | +""" |
2571 | +Revalidate Account Moves Wizard |
2572 | +""" |
2573 | +__author__ = "Borja López Soilán (Pexego)" |
2574 | + |
2575 | +import re |
2576 | +from osv import fields, osv |
2577 | +from tools.translate import _ |
2578 | + |
2579 | + |
2580 | +class revalidate_moves_wizard(osv.osv_memory): |
2581 | + """ |
2582 | + Revalidate Account Moves Wizard |
2583 | + |
2584 | + Revalidates all the (already confirmed) moves, so their analytic lines |
2585 | + are recomputed (to fix the data after problems like this: |
2586 | + https://bugs.launchpad.net/openobject-addons/+bug/582988). |
2587 | + """ |
2588 | + _name = "revalidate_moves_wizard" |
2589 | + _description = "Revalidate Account Moves Wizard" |
2590 | + |
2591 | + _columns = { |
2592 | + 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True), |
2593 | + 'period_ids': fields.many2many('account.period', 'revalidate_moves_wizard_period_rel', 'wizard_id', 'period_id', "Periods"), |
2594 | + 'move_ids': fields.many2many('account.move', 'revalidate_moves_wizard_moves_rel', 'wizard_id', 'move_id', 'Moves'), |
2595 | + 'state': fields.selection([('new', 'New'), ('ready', 'Ready'), ('done', 'Done')], 'Status', readonly=True), |
2596 | + } |
2597 | + |
2598 | + _defaults = { |
2599 | + 'state': lambda *a: 'new', |
2600 | + } |
2601 | + |
2602 | + def action_skip_new(self, cr, uid, ids, context=None): |
2603 | + """ |
2604 | + Skips to the ready state. |
2605 | + """ |
2606 | + for wiz in self.browse(cr, uid, ids, context): |
2607 | + self.write(cr, uid, [wiz.id], {'state': 'ready'}) |
2608 | + return True |
2609 | + |
2610 | + def action_find_moves_missing_analytic_lines(self, cr, uid, ids, context=None): |
2611 | + """ |
2612 | + Finds account moves with missing analytic lines and adds them |
2613 | + to the move_ids many2many field. |
2614 | + """ |
2615 | + for wiz in self.browse(cr, uid, ids, context): |
2616 | + period_ids = [period.id for period in wiz.period_ids] |
2617 | + query = """ |
2618 | + SELECT account_move_line.move_id FROM account_move_line |
2619 | + LEFT JOIN account_analytic_line |
2620 | + ON account_analytic_line.move_id = account_move_line.id |
2621 | + WHERE account_move_line.analytic_account_id IS NOT NULL AND account_analytic_line.id IS NULL |
2622 | + """ |
2623 | + periods_str = ','.join(map(str, period_ids)) |
2624 | + if period_ids: |
2625 | + query += """ AND period_id IN (%s)""" % periods_str |
2626 | + |
2627 | + cr.execute(query) |
2628 | + move_ids = filter(None, map(lambda x: x[0], cr.fetchall())) |
2629 | + self.write(cr, uid, [wiz.id], { |
2630 | + 'move_ids': [(6, 0, move_ids)], |
2631 | + 'state': 'ready' |
2632 | + }) |
2633 | + return True |
2634 | + |
2635 | + def action_revalidate_moves(self, cr, uid, ids, context=None): |
2636 | + """ |
2637 | + Calls the validate method of the account moves for each move in the |
2638 | + move_ids many2many. |
2639 | + """ |
2640 | + for wiz in self.browse(cr, uid, ids, context): |
2641 | + for move in wiz.move_ids: |
2642 | + # We validate the moves one by one to prevent problems |
2643 | + self.pool.get( |
2644 | + 'account.move').validate(cr, uid, [move.id], context) |
2645 | + self.write(cr, uid, [wiz.id], {'state': 'done'}) |
2646 | + return True |
2647 | + |
2648 | +revalidate_moves_wizard() |
2649 | |
2650 | === added file 'account_admin_tools/revalidate_moves_wizard.xml' |
2651 | --- account_admin_tools/revalidate_moves_wizard.xml 1970-01-01 00:00:00 +0000 |
2652 | +++ account_admin_tools/revalidate_moves_wizard.xml 2012-12-07 10:00:37 +0000 |
2653 | @@ -0,0 +1,45 @@ |
2654 | +<?xml version="1.0" encoding="utf-8"?> |
2655 | +<openerp> |
2656 | + <data> |
2657 | + <record id="view_revalidate_moves_wizard_form" model="ir.ui.view"> |
2658 | + <field name="name">revalidate_moves_wizard.form</field> |
2659 | + <field name="model">revalidate_moves_wizard</field> |
2660 | + <field name="type">form</field> |
2661 | + <field name="arch" type="xml"> |
2662 | + <form string="Revalidate account moves"> |
2663 | + <label string="This wizard will revalidate the given account moves, so their analytic lines are regenerated." colspan="4"/> |
2664 | + <label string=""/> |
2665 | + <newline/> |
2666 | + <group string="Moves to revalidate" attrs="{'invisible': [('state','!=','ready')]}"> |
2667 | + <field name="move_ids" colspan="4" nolabel="1"/> |
2668 | + </group> |
2669 | + <group string="Find moves with missing analytic lines" colspan="4" attrs="{'invisible': [('state','!=','new')]}"> |
2670 | + <button string="Search for moves" name="action_find_moves_missing_analytic_lines" type="object" icon="gtk-ok" states="new" colspan="4"/> |
2671 | + <separator string="Filter for the moves" colspan="4"/> |
2672 | + <field name="period_ids" colspan="4"/> |
2673 | + </group> |
2674 | + <group colspan="4"> |
2675 | + <button string="Cancel" special="cancel" icon="gtk-cancel" states="new,ready"/> |
2676 | + <button string="Skip" name="action_skip_new" type="object" icon="gtk-go-forward" states="new"/> |
2677 | + <button string="Revalidate selected moves" name="action_revalidate_moves" type="object" icon="gtk-apply" states="ready"/> |
2678 | + <button string="Done" special="cancel" icon="gtk-ok" states="done"/> |
2679 | + </group> |
2680 | + <field name="state" invisible="1"/> |
2681 | + </form> |
2682 | + </field> |
2683 | + </record> |
2684 | + |
2685 | + <record id="action_revalidate_moves_wizard" model="ir.actions.act_window"> |
2686 | + <field name="name">Revalidate Account Moves (Regenerate Analytic Lines)</field> |
2687 | + <field name="res_model">revalidate_moves_wizard</field> |
2688 | + <field name="view_type">form</field> |
2689 | + <field name="view_mode">form</field> |
2690 | + <field name="target">new</field> |
2691 | + </record> |
2692 | + <menuitem id="menu_action_revalidate_moves_wizard" |
2693 | + parent="menu_action_account_admin_tools" |
2694 | + action="action_revalidate_moves_wizard" |
2695 | + sequence="50"/> |
2696 | + |
2697 | + </data> |
2698 | +</openerp> |
2699 | |
2700 | === added file 'account_admin_tools/set_invoice_ref_in_moves.py' |
2701 | --- account_admin_tools/set_invoice_ref_in_moves.py 1970-01-01 00:00:00 +0000 |
2702 | +++ account_admin_tools/set_invoice_ref_in_moves.py 2012-12-07 10:00:37 +0000 |
2703 | @@ -0,0 +1,205 @@ |
2704 | +# -*- coding: utf-8 -*- |
2705 | +############################################################################## |
2706 | +# |
2707 | +# OpenERP, Open Source Management Solution |
2708 | +# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved |
2709 | +# $Id$ |
2710 | +# |
2711 | +# This program is free software: you can redistribute it and/or modify |
2712 | +# it under the terms of the GNU Affero General Public License as published |
2713 | +# by the Free Software Foundation, either version 3 of the License, or |
2714 | +# (at your option) any later version. |
2715 | +# |
2716 | +# This program is distributed in the hope that it will be useful, |
2717 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2718 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2719 | +# GNU Affero General Public License for more details. |
2720 | +# |
2721 | +# You should have received a copy of the GNU Affero General Public License |
2722 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2723 | +# |
2724 | +############################################################################## |
2725 | +""" |
2726 | +Set Invoice Reference in Moves |
2727 | +""" |
2728 | +__author__ = "Borja López Soilán (Pexego)" |
2729 | + |
2730 | +import re |
2731 | +from osv import fields, osv |
2732 | +from tools.translate import _ |
2733 | + |
2734 | + |
2735 | +class set_invoice_ref_in_moves(osv.osv_memory): |
2736 | + """ |
2737 | + Set Invoice Reference in Moves |
2738 | + |
2739 | + Searchs for account moves associated with invoices that do not have the |
2740 | + right reference (the reference from a supplier invoice or the number from |
2741 | + a customer invoice) and lets the user fix them. |
2742 | + """ |
2743 | + _name = "account_admin_tools.set_invoice_ref_in_moves" |
2744 | + _description = "Set Invoice Reference in Moves" |
2745 | + |
2746 | + _columns = { |
2747 | + 'state': fields.selection([('new', 'New'), ('ready', 'Ready'), ('done', 'Done')], 'Status', readonly=True), |
2748 | + 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True), |
2749 | + 'period_ids': fields.many2many('account.period', 'set_invoice_ref_in_moves_period_rel', 'wizard_id', 'period_id', "Periods"), |
2750 | + 'move_ids': fields.many2many('account.move', 'set_invoice_ref_in_move_move_rel', 'wizard_id', 'move_id', 'Moves'), |
2751 | + } |
2752 | + |
2753 | + _defaults = { |
2754 | + 'company_id': lambda self, cr, uid, context: self.pool.get('res.users').browse(cr, uid, uid, context).company_id.id, |
2755 | + 'move_ids': lambda self, cr, uid, context: context and context.get('move_ids', None), |
2756 | + 'period_ids': lambda self, cr, uid, context: context and context.get('period_ids', None), |
2757 | + 'state': lambda self, cr, uid, context: context and context.get('state', 'new'), |
2758 | + } |
2759 | + |
2760 | + def _next_view(self, cr, uid, ids, view_name, args=None, context=None): |
2761 | + """ |
2762 | + Return the next wizard view |
2763 | + """ |
2764 | + if context is None: |
2765 | + context = {} |
2766 | + if args is None: |
2767 | + args = {} |
2768 | + ctx = context.copy() |
2769 | + ctx.update(args) |
2770 | + |
2771 | + model_data_ids = self.pool.get('ir.model.data').search(cr, uid, [ |
2772 | + ('model', '=', 'ir.ui.view'), |
2773 | + ('module', '=', 'account_admin_tools'), |
2774 | + ('name', '=', view_name) |
2775 | + ]) |
2776 | + resource_id = self.pool.get('ir.model.data').read(cr, uid, model_data_ids, fields=['res_id'], context=context)[0]['res_id'] |
2777 | + return { |
2778 | + 'name': _("Set Invoice Reference in Moves"), |
2779 | + 'type': 'ir.actions.act_window', |
2780 | + 'res_model': 'account_admin_tools.set_invoice_ref_in_moves', |
2781 | + 'view_type': 'form', |
2782 | + 'view_mode': 'form', |
2783 | + 'views': [(resource_id, 'form')], |
2784 | + 'domain': "[('id', 'in', %s)]" % ids, |
2785 | + 'context': ctx, |
2786 | + 'target': 'new', |
2787 | + } |
2788 | + |
2789 | + def _get_reference(self, cr, uid, invoice, context=None): |
2790 | + """ |
2791 | + Get's the reference for an account move given the related invoice. |
2792 | + """ |
2793 | + if invoice.type in ('in_invoice', 'in_refund'): |
2794 | + return invoice.reference |
2795 | + else: |
2796 | + return self.pool.get('account.invoice')._convert_ref(cr, uid, invoice.number) |
2797 | + |
2798 | + def _is_valid_reference(self, cr, uid, reference, invoice, context=None): |
2799 | + """ |
2800 | + Checks that the given reference matches the invoice reference or number. |
2801 | + """ |
2802 | + if reference == invoice.reference: |
2803 | + return True |
2804 | + elif reference == self.pool.get('account.invoice')._convert_ref(cr, uid, invoice.number): |
2805 | + return True |
2806 | + else: |
2807 | + return False |
2808 | + |
2809 | + def action_skip_new(self, cr, uid, ids, context=None): |
2810 | + """ |
2811 | + Action that just skips the to the ready state |
2812 | + """ |
2813 | + return self._next_view(cr, uid, ids, 'view_set_invoice_ref_in_moves_ready_form', {'state': 'ready'}, context) |
2814 | + |
2815 | + def action_find_moves_with_wrong_invoice_ref(self, cr, uid, ids, context=None): |
2816 | + """ |
2817 | + Action that searchs for account moves associated with invoices, |
2818 | + that do not have the right reference. |
2819 | + """ |
2820 | + wiz = self.browse(cr, uid, ids[0], context) |
2821 | + |
2822 | + # FIXME: The next block of code is a workaround to the lp:586252 bug of the 6.0 client. |
2823 | + # (https://bugs.launchpad.net/openobject-client/+bug/586252) |
2824 | + if wiz.period_ids: |
2825 | + period_ids = [period.id for period in wiz.period_ids] |
2826 | + else: |
2827 | + period_ids = context and context.get('period_ids') |
2828 | + |
2829 | + # |
2830 | + # Find the invoices (on the given periods) |
2831 | + # |
2832 | + args = [] |
2833 | + if period_ids: |
2834 | + args = [('period_id', 'in', period_ids)] |
2835 | + invoice_ids = self.pool.get( |
2836 | + 'account.invoice').search(cr, uid, args, context=context) |
2837 | + |
2838 | + # |
2839 | + # Get the moves with references not matching the desired ones |
2840 | + # |
2841 | + move_ids = [] |
2842 | + for invoice in self.pool.get('account.invoice').browse(cr, uid, invoice_ids, context=context): |
2843 | + if invoice.move_id: |
2844 | + if not self._is_valid_reference(cr, uid, invoice.move_id.ref, invoice, context=context): |
2845 | + reference = self._get_reference( |
2846 | + cr, uid, invoice, context=context) |
2847 | + if reference and len(reference): |
2848 | + move_ids.append(invoice.move_id.id) |
2849 | + |
2850 | + # |
2851 | + # Return the next view: Show 'ready' view |
2852 | + # |
2853 | + args = { |
2854 | + 'move_ids': move_ids, |
2855 | + 'state': 'ready', |
2856 | + } |
2857 | + return self._next_view(cr, uid, ids, 'view_set_invoice_ref_in_moves_ready_form', args, context) |
2858 | + |
2859 | + def action_set_invoice_ref_in_moves(self, cr, uid, ids, context=None): |
2860 | + """ |
2861 | + Action that sets the invoice reference or number as the account move |
2862 | + reference for the selected moves. |
2863 | + """ |
2864 | + wiz = self.browse(cr, uid, ids[0], context) |
2865 | + |
2866 | + # FIXME: The next block of code is a workaround to the lp:586252 bug of the 6.0 client. |
2867 | + # (https://bugs.launchpad.net/openobject-client/+bug/586252) |
2868 | + if wiz.move_ids: |
2869 | + move_ids = [line.id for line in wiz.move_ids] |
2870 | + else: |
2871 | + move_ids = context and context.get('move_ids') |
2872 | + |
2873 | + # |
2874 | + # Find the invoices of the moves |
2875 | + # |
2876 | + args = [('move_id', 'in', move_ids)] |
2877 | + invoice_ids = self.pool.get( |
2878 | + 'account.invoice').search(cr, uid, args, context=context) |
2879 | + |
2880 | + # |
2881 | + # Update the moves with the reference of the invoice. |
2882 | + # |
2883 | + for invoice in self.pool.get('account.invoice').browse(cr, uid, invoice_ids, context=context): |
2884 | + if invoice.move_id: |
2885 | + reference = self._get_reference( |
2886 | + cr, uid, invoice, context=context) |
2887 | + if invoice.move_id.ref != reference: |
2888 | + self.pool.get('account.move').write(cr, uid, [invoice.move_id.id], {'ref': reference}, context=context) |
2889 | + |
2890 | + # |
2891 | + # Update the move line references too |
2892 | + # (if they where equal to the move reference) |
2893 | + # |
2894 | + for line in invoice.move_id.line_id: |
2895 | + if line.ref == invoice.move_id.ref: |
2896 | + self.pool.get('account.move.line').write(cr, uid, [line.id], {'ref': reference}, context=context) |
2897 | + |
2898 | + # |
2899 | + # Return the next view: Show 'done' view |
2900 | + # |
2901 | + args = { |
2902 | + 'move_ids': move_ids, |
2903 | + 'state': 'done', |
2904 | + } |
2905 | + return self._next_view(cr, uid, ids, 'view_set_invoice_ref_in_moves_done_form', args, context) |
2906 | + |
2907 | + |
2908 | +set_invoice_ref_in_moves() |
2909 | |
2910 | === added file 'account_admin_tools/set_invoice_ref_in_moves.xml' |
2911 | --- account_admin_tools/set_invoice_ref_in_moves.xml 1970-01-01 00:00:00 +0000 |
2912 | +++ account_admin_tools/set_invoice_ref_in_moves.xml 2012-12-07 10:00:37 +0000 |
2913 | @@ -0,0 +1,82 @@ |
2914 | +<?xml version="1.0" encoding="utf-8"?> |
2915 | +<openerp> |
2916 | + <data> |
2917 | + <record id="view_set_invoice_ref_in_moves_form" model="ir.ui.view"> |
2918 | + <field name="name">set_invoice_ref_in_moves.form</field> |
2919 | + <field name="model">account_admin_tools.set_invoice_ref_in_moves</field> |
2920 | + <field name="type">form</field> |
2921 | + <field name="arch" type="xml"> |
2922 | + <form string="Set Invoice Reference in Moves"> |
2923 | + <label string="This wizard will set the reference in account moves associated with invoices, that don't match the invoice reference/number." colspan="4"/> |
2924 | + <label string="" colspan="4"/> |
2925 | + <group colspan="4"> |
2926 | + <separator string="Find moves not matching the invoice reference/number" colspan="4"/> |
2927 | + <label string="You may now search for account moves, on the given periods, whose reference does not match the invoice reference or number; or you may skip this step and select the moves by hand." colspan="4"/> |
2928 | + <label string="" colspan="4"/> |
2929 | + <newline/> |
2930 | + <field name="period_ids" colspan="4"/> |
2931 | + <label string="" colspan="4"/> |
2932 | + <newline/> |
2933 | + <button string="Search for moves" name="action_find_moves_with_wrong_invoice_ref" type="object" icon="gtk-ok" colspan="4"/> |
2934 | + </group> |
2935 | + <label string="" colspan="4"/> |
2936 | + <group colspan="4"> |
2937 | + <button string="Cancel" special="cancel" icon="gtk-cancel"/> |
2938 | + <button string="Skip" name="action_skip_new" type="object" icon="gtk-go-forward"/> |
2939 | + </group> |
2940 | + </form> |
2941 | + </field> |
2942 | + </record> |
2943 | + |
2944 | + <record id="view_set_invoice_ref_in_moves_ready_form" model="ir.ui.view"> |
2945 | + <field name="name">set_invoice_ref_in_moves.ready.form</field> |
2946 | + <field name="model">account_admin_tools.set_invoice_ref_in_moves</field> |
2947 | + <field name="type">form</field> |
2948 | + <field name="arch" type="xml"> |
2949 | + <form string="Set Invoice Reference in Moves"> |
2950 | + <label string="The reference will be set, for the selected account moves, to the reference/number of the (supplier/customer) invoice." colspan="4"/> |
2951 | + <label string="" colspan="4"/> |
2952 | + <separator string="Account moves to update" colspan="4"/> |
2953 | + <field name="move_ids" colspan="4" nolabel="1"/> |
2954 | + <label string="" colspan="4"/> |
2955 | + <group colspan="4"> |
2956 | + <button string="Cancel" special="cancel" icon="gtk-cancel"/> |
2957 | + <button string="Set invoice reference in moves" name="action_set_invoice_ref_in_moves" type="object" icon="gtk-apply"/> |
2958 | + </group> |
2959 | + </form> |
2960 | + </field> |
2961 | + </record> |
2962 | + |
2963 | + <record id="view_set_invoice_ref_in_moves_done_form" model="ir.ui.view"> |
2964 | + <field name="name">set_invoice_ref_in_moves.done.form</field> |
2965 | + <field name="model">account_admin_tools.set_invoice_ref_in_moves</field> |
2966 | + <field name="type">form</field> |
2967 | + <field name="arch" type="xml"> |
2968 | + <form string="Set Invoice Reference in Moves"> |
2969 | + <label string="The invoice references have been succesfully set on the account moves!" colspan="4"/> |
2970 | + <label string="" colspan="4"/> |
2971 | + <separator string="Updated account moves" colspan="4"/> |
2972 | + <field name="move_ids" colspan="4" nolabel="1" readonly="1"/> |
2973 | + <label string="" colspan="4"/> |
2974 | + <group colspan="4"> |
2975 | + <button string="Done" special="cancel" icon="gtk-ok"/> |
2976 | + </group> |
2977 | + </form> |
2978 | + </field> |
2979 | + </record> |
2980 | + |
2981 | + <record id="action_set_invoice_ref_in_moves" model="ir.actions.act_window"> |
2982 | + <field name="name">Set Invoice Reference in Moves</field> |
2983 | + <field name="res_model">account_admin_tools.set_invoice_ref_in_moves</field> |
2984 | + <field name="view_type">form</field> |
2985 | + <field name="view_mode">form</field> |
2986 | + <field name="view_id" ref="view_set_invoice_ref_in_moves_form"/> |
2987 | + <field name="target">new</field> |
2988 | + </record> |
2989 | + <menuitem id="menu_action_set_invoice_ref_in_moves" |
2990 | + parent="menu_action_account_admin_tools_repair" |
2991 | + action="action_set_invoice_ref_in_moves" |
2992 | + sequence="130"/> |
2993 | + |
2994 | + </data> |
2995 | +</openerp> |
2996 | |
2997 | === added file 'account_admin_tools/set_partner_in_moves.py' |
2998 | --- account_admin_tools/set_partner_in_moves.py 1970-01-01 00:00:00 +0000 |
2999 | +++ account_admin_tools/set_partner_in_moves.py 2012-12-07 10:00:37 +0000 |
3000 | @@ -0,0 +1,214 @@ |
3001 | +# -*- coding: utf-8 -*- |
3002 | +############################################################################## |
3003 | +# |
3004 | +# OpenERP, Open Source Management Solution |
3005 | +# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved |
3006 | +# $Id$ |
3007 | +# |
3008 | +# This program is free software: you can redistribute it and/or modify |
3009 | +# it under the terms of the GNU Affero General Public License as published |
3010 | +# by the Free Software Foundation, either version 3 of the License, or |
3011 | +# (at your option) any later version. |
3012 | +# |
3013 | +# This program is distributed in the hope that it will be useful, |
3014 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3015 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3016 | +# GNU Affero General Public License for more details. |
3017 | +# |
3018 | +# You should have received a copy of the GNU Affero General Public License |
3019 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
3020 | +# |
3021 | +############################################################################## |
3022 | +""" |
3023 | +Set Partner in Moves Wizard |
3024 | +""" |
3025 | +__author__ = "Borja López Soilán (Pexego)" |
3026 | + |
3027 | +import re |
3028 | +from osv import fields, osv |
3029 | +from tools.translate import _ |
3030 | + |
3031 | + |
3032 | +class set_partner_in_moves(osv.osv_memory): |
3033 | + """ |
3034 | + Set Partner in Moves Wizard |
3035 | + |
3036 | + Searchs for account move lines of that use the payable/receivable account |
3037 | + of a single partner, and have no partner reference in the line, |
3038 | + and sets the partner reference (partner_id). |
3039 | + This may fix cases where the receivable/payable amounts displayed in the |
3040 | + partner form does not match the balance of the receivable/payable accounts. |
3041 | + """ |
3042 | + _name = "account_admin_tools.set_partner_in_moves" |
3043 | + _description = "Set Partner in Moves Wizard" |
3044 | + |
3045 | + _columns = { |
3046 | + 'state': fields.selection([('new', 'New'), ('ready', 'Ready'), ('done', 'Done')], 'Status', readonly=True), |
3047 | + 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True), |
3048 | + 'period_ids': fields.many2many('account.period', 'set_partner_in_moves_period_rel', 'wizard_id', 'period_id', "Periods"), |
3049 | + 'move_line_ids': fields.many2many('account.move.line', 'set_partner_in_move_move_line_rel', 'wizard_id', 'line_id', 'Move Lines'), |
3050 | + } |
3051 | + |
3052 | + _defaults = { |
3053 | + 'company_id': lambda self, cr, uid, context: self.pool.get('res.users').browse(cr, uid, uid, context).company_id.id, |
3054 | + 'move_line_ids': lambda self, cr, uid, context: context and context.get('move_line_ids', None), |
3055 | + 'period_ids': lambda self, cr, uid, context: context and context.get('period_ids', None), |
3056 | + 'state': lambda self, cr, uid, context: context and context.get('state', 'new'), |
3057 | + } |
3058 | + |
3059 | + def _next_view(self, cr, uid, ids, view_name, args=None, context=None): |
3060 | + """ |
3061 | + Return the next wizard view |
3062 | + """ |
3063 | + if context is None: |
3064 | + context = {} |
3065 | + if args is None: |
3066 | + args = {} |
3067 | + ctx = context.copy() |
3068 | + ctx.update(args) |
3069 | + |
3070 | + model_data_ids = self.pool.get('ir.model.data').search(cr, uid, [ |
3071 | + ('model', '=', 'ir.ui.view'), |
3072 | + ('module', '=', 'account_admin_tools'), |
3073 | + ('name', '=', view_name) |
3074 | + ]) |
3075 | + resource_id = self.pool.get('ir.model.data').read(cr, uid, model_data_ids, fields=['res_id'], context=context)[0]['res_id'] |
3076 | + return { |
3077 | + 'name': _("Set Partner Reference in Moves"), |
3078 | + 'type': 'ir.actions.act_window', |
3079 | + 'res_model': 'account_admin_tools.set_partner_in_moves', |
3080 | + 'view_type': 'form', |
3081 | + 'view_mode': 'form', |
3082 | + 'views': [(resource_id, 'form')], |
3083 | + 'domain': "[('id', 'in', %s)]" % ids, |
3084 | + 'context': ctx, |
3085 | + 'target': 'new', |
3086 | + } |
3087 | + |
3088 | + def _get_accounts_map(self, cr, uid, context=None): |
3089 | + """ |
3090 | + Find the receivable/payable accounts that are associated with |
3091 | + a single partner and return a (account.id, partner.id) map |
3092 | + """ |
3093 | + partner_ids = self.pool.get( |
3094 | + 'res.partner').search(cr, uid, [], context=context) |
3095 | + accounts_map = {} |
3096 | + for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context): |
3097 | + # |
3098 | + # Add the receivable account to the map |
3099 | + # |
3100 | + if accounts_map.get(partner.property_account_receivable.id, None) is None: |
3101 | + accounts_map[ |
3102 | + partner.property_account_receivable.id] = partner.id |
3103 | + else: |
3104 | + # Two partners with the same receivable account: ignore |
3105 | + # this account! |
3106 | + accounts_map[partner.property_account_receivable.id] = 0 |
3107 | + # |
3108 | + # Add the payable account to the map |
3109 | + # |
3110 | + if accounts_map.get(partner.property_account_payable.id, None) is None: |
3111 | + accounts_map[partner.property_account_payable.id] = partner.id |
3112 | + else: |
3113 | + # Two partners with the same receivable account: ignore |
3114 | + # this account! |
3115 | + accounts_map[partner.property_account_payable.id] = 0 |
3116 | + return accounts_map |
3117 | + |
3118 | + def action_skip_new(self, cr, uid, ids, context=None): |
3119 | + """ |
3120 | + Action that just skips the to the ready state |
3121 | + """ |
3122 | + return self._next_view(cr, uid, ids, 'view_set_partner_in_moves_ready_form', {'state': 'ready'}, context) |
3123 | + |
3124 | + def action_find_moves_missing_partner(self, cr, uid, ids, context=None): |
3125 | + """ |
3126 | + Action that searchs for account move lines of payable/receivable |
3127 | + accounts (of just one partner) that don't have the partner reference. |
3128 | + """ |
3129 | + wiz = self.browse(cr, uid, ids[0], context) |
3130 | + |
3131 | + # FIXME: The next block of code is a workaround to the lp:586252 bug of the 6.0 client. |
3132 | + # (https://bugs.launchpad.net/openobject-client/+bug/586252) |
3133 | + if wiz.period_ids: |
3134 | + period_ids = [period.id for period in wiz.period_ids] |
3135 | + else: |
3136 | + period_ids = context and context.get('period_ids') |
3137 | + |
3138 | + move_line_ids = [] |
3139 | + accounts_map = self._get_accounts_map(cr, uid, context=context) |
3140 | + |
3141 | + # |
3142 | + # Find the account move lines, of each of the accounts in the map |
3143 | + # that don't have a partner set. |
3144 | + # |
3145 | + query = """ |
3146 | + SELECT id FROM account_move_line |
3147 | + WHERE account_id=%s |
3148 | + AND partner_id IS NULL |
3149 | + """ |
3150 | + if period_ids: |
3151 | + query += """ AND period_id IN %s""" |
3152 | + |
3153 | + for account_id in accounts_map.keys(): |
3154 | + if accounts_map[account_id] != 0: |
3155 | + if period_ids: |
3156 | + cr.execute(query, (account_id, tuple(period_ids))) |
3157 | + else: |
3158 | + cr.execute(query, (account_id,)) |
3159 | + new_move_line_ids = filter( |
3160 | + None, map(lambda x: x[0], cr.fetchall())) |
3161 | + if new_move_line_ids: |
3162 | + move_line_ids.extend(new_move_line_ids) |
3163 | + |
3164 | + # |
3165 | + # Return the next view: Show 'ready' view |
3166 | + # |
3167 | + args = { |
3168 | + 'move_line_ids': move_line_ids, |
3169 | + 'state': 'ready', |
3170 | + } |
3171 | + return self._next_view(cr, uid, ids, 'view_set_partner_in_moves_ready_form', args, context) |
3172 | + |
3173 | + def action_set_partner_in_moves(self, cr, uid, ids, context=None): |
3174 | + """ |
3175 | + Action that sets for each partner payable/receivable account, |
3176 | + that is used only on one partner, the parner reference on its move |
3177 | + lines. |
3178 | + """ |
3179 | + wiz = self.browse(cr, uid, ids[0], context) |
3180 | + |
3181 | + # FIXME: The next block of code is a workaround to the lp:586252 bug of the 6.0 client. |
3182 | + # (https://bugs.launchpad.net/openobject-client/+bug/586252) |
3183 | + if wiz.move_line_ids: |
3184 | + move_line_ids = [line.id for line in wiz.move_line_ids] |
3185 | + else: |
3186 | + move_line_ids = context and context.get('move_line_ids') |
3187 | + |
3188 | + accounts_map = self._get_accounts_map(cr, uid, context=context) |
3189 | + |
3190 | + # |
3191 | + # Update the account move lines, of each of the accounts in the map |
3192 | + # that don't have a partner set, with the associated partner. |
3193 | + # |
3194 | + query = """ |
3195 | + UPDATE account_move_line |
3196 | + SET partner_id=%s |
3197 | + WHERE id=%s |
3198 | + AND partner_id IS NULL |
3199 | + """ |
3200 | + for line in self.pool.get('account.move.line').browse(cr, uid, move_line_ids, context=context): |
3201 | + if accounts_map[line.account_id.id] != 0: |
3202 | + cr.execute(query, (accounts_map[line.account_id.id], line.id)) |
3203 | + |
3204 | + # |
3205 | + # Return the next view: Show 'done' view |
3206 | + # |
3207 | + args = { |
3208 | + 'move_line_ids': move_line_ids, |
3209 | + 'state': 'done', |
3210 | + } |
3211 | + return self._next_view(cr, uid, ids, 'view_set_partner_in_moves_done_form', args, context) |
3212 | + |
3213 | + |
3214 | +set_partner_in_moves() |
3215 | |
3216 | === added file 'account_admin_tools/set_partner_in_moves.xml' |
3217 | --- account_admin_tools/set_partner_in_moves.xml 1970-01-01 00:00:00 +0000 |
3218 | +++ account_admin_tools/set_partner_in_moves.xml 2012-12-07 10:00:37 +0000 |
3219 | @@ -0,0 +1,82 @@ |
3220 | +<?xml version="1.0" encoding="utf-8"?> |
3221 | +<openerp> |
3222 | + <data> |
3223 | + <record id="view_set_partner_in_moves_form" model="ir.ui.view"> |
3224 | + <field name="name">set_partner_in_moves.form</field> |
3225 | + <field name="model">account_admin_tools.set_partner_in_moves</field> |
3226 | + <field name="type">form</field> |
3227 | + <field name="arch" type="xml"> |
3228 | + <form string="Set Partner Reference in Moves"> |
3229 | + <label string="This wizard will set the partner reference in moves where the receivable/payable account (of a single partner) is used." colspan="4"/> |
3230 | + <label string="" colspan="4"/> |
3231 | + <group colspan="4"> |
3232 | + <separator string="Find moves with missing partner reference" colspan="4"/> |
3233 | + <label string="You may now search for move lines with missing partner reference on the given periods, or you may skip this step and select the move lines by hand." colspan="4"/> |
3234 | + <label string="" colspan="4"/> |
3235 | + <newline/> |
3236 | + <field name="period_ids" colspan="4"/> |
3237 | + <label string="" colspan="4"/> |
3238 | + <newline/> |
3239 | + <button string="Search for moves" name="action_find_moves_missing_partner" type="object" icon="gtk-ok" colspan="4"/> |
3240 | + </group> |
3241 | + <label string="" colspan="4"/> |
3242 | + <group colspan="4"> |
3243 | + <button string="Cancel" special="cancel" icon="gtk-cancel"/> |
3244 | + <button string="Skip" name="action_skip_new" type="object" icon="gtk-go-forward"/> |
3245 | + </group> |
3246 | + </form> |
3247 | + </field> |
3248 | + </record> |
3249 | + |
3250 | + <record id="view_set_partner_in_moves_ready_form" model="ir.ui.view"> |
3251 | + <field name="name">set_partner_in_moves.ready.form</field> |
3252 | + <field name="model">account_admin_tools.set_partner_in_moves</field> |
3253 | + <field name="type">form</field> |
3254 | + <field name="arch" type="xml"> |
3255 | + <form string="Set Partner Reference in Moves"> |
3256 | + <label string="The partner reference will be set, for the selected account move lines, to the one associated with the receivable/payable account." colspan="4"/> |
3257 | + <label string="" colspan="4"/> |
3258 | + <separator string="Account move lines to update" colspan="4"/> |
3259 | + <field name="move_line_ids" colspan="4" nolabel="1" domain="[('partner_id','=',False), ('account_id.type', 'in', ['receivable', 'payable'])]"/> |
3260 | + <label string="" colspan="4"/> |
3261 | + <group colspan="4"> |
3262 | + <button string="Cancel" special="cancel" icon="gtk-cancel"/> |
3263 | + <button string="Set partner in moves" name="action_set_partner_in_moves" type="object" icon="gtk-apply"/> |
3264 | + </group> |
3265 | + </form> |
3266 | + </field> |
3267 | + </record> |
3268 | + |
3269 | + <record id="view_set_partner_in_moves_done_form" model="ir.ui.view"> |
3270 | + <field name="name">set_partner_in_moves.done.form</field> |
3271 | + <field name="model">account_admin_tools.set_partner_in_moves</field> |
3272 | + <field name="type">form</field> |
3273 | + <field name="arch" type="xml"> |
3274 | + <form string="Set Partner Reference in Moves"> |
3275 | + <label string="The partner references have been succesfully set on the account moves!" colspan="4"/> |
3276 | + <label string="" colspan="4"/> |
3277 | + <separator string="Updated account move lines" colspan="4"/> |
3278 | + <field name="move_line_ids" colspan="4" nolabel="1" domain="[('partner_id','=',False), ('account_id.type', 'in', ['receivable', 'payable'])]" readonly="1"/> |
3279 | + <label string="" colspan="4"/> |
3280 | + <group colspan="4"> |
3281 | + <button string="Done" special="cancel" icon="gtk-ok"/> |
3282 | + </group> |
3283 | + </form> |
3284 | + </field> |
3285 | + </record> |
3286 | + |
3287 | + <record id="action_set_partner_in_moves" model="ir.actions.act_window"> |
3288 | + <field name="name">Set Partner Reference in Moves</field> |
3289 | + <field name="res_model">account_admin_tools.set_partner_in_moves</field> |
3290 | + <field name="view_type">form</field> |
3291 | + <field name="view_mode">form</field> |
3292 | + <field name="view_id" ref="view_set_partner_in_moves_form"/> |
3293 | + <field name="target">new</field> |
3294 | + </record> |
3295 | + <menuitem id="menu_action_set_partner_in_moves" |
3296 | + parent="menu_action_account_admin_tools_repair" |
3297 | + action="action_set_partner_in_moves" |
3298 | + sequence="120"/> |
3299 | + |
3300 | + </data> |
3301 | +</openerp> |
3302 | |
3303 | === added directory 'account_chart_update' |
3304 | === added file 'account_chart_update/__init__.py' |
3305 | --- account_chart_update/__init__.py 1970-01-01 00:00:00 +0000 |
3306 | +++ account_chart_update/__init__.py 2012-12-07 10:00:37 +0000 |
3307 | @@ -0,0 +1,28 @@ |
3308 | +# -*- coding: utf-8 -*- |
3309 | +############################################################################## |
3310 | +# |
3311 | +# Copyright (c) 2010 Zikzakmedia S.L. (http://www.zikzakmedia.com) |
3312 | +# Copyright (c) 2010 Pexego Sistemas Informáticos S.L. (http://www.pexego.es) |
3313 | +# @authors: Jordi Esteve (Zikzakmedia), Borja López Soilán (Pexego) |
3314 | +# |
3315 | +# This program is free software: you can redistribute it and/or modify |
3316 | +# it under the terms of the GNU Affero General Public License as published |
3317 | +# by the Free Software Foundation, either version 3 of the License, or |
3318 | +# (at your option) any later version. |
3319 | +# |
3320 | +# This program is distributed in the hope that it will be useful, |
3321 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3322 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3323 | +# GNU Affero General Public License for more details. |
3324 | +# |
3325 | +# You should have received a copy of the GNU Affero General Public License |
3326 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
3327 | +# |
3328 | +############################################################################## |
3329 | +""" |
3330 | +Account Chart Update Wizard |
3331 | +""" |
3332 | +__authors__ = ["Jordi Esteve <jesteve@zikzakmedia.com>", |
3333 | + "Borja López Soilán <borjals@pexego.es>"] |
3334 | + |
3335 | +import account |
3336 | |
3337 | === added file 'account_chart_update/__openerp__.py' |
3338 | --- account_chart_update/__openerp__.py 1970-01-01 00:00:00 +0000 |
3339 | +++ account_chart_update/__openerp__.py 2012-12-07 10:00:37 +0000 |
3340 | @@ -0,0 +1,64 @@ |
3341 | +# -*- coding: utf-8 -*- |
3342 | +############################################################################## |
3343 | +# |
3344 | +# Copyright (c) 2010 Zikzakmedia S.L. (http://www.zikzakmedia.com) |
3345 | +# Copyright (c) 2010 Pexego Sistemas Informáticos S.L. (http://www.pexego.es) |
3346 | +# @authors: Jordi Esteve (Zikzakmedia), Borja López Soilán (Pexego) |
3347 | +# |
3348 | +# This program is free software: you can redistribute it and/or modify |
3349 | +# it under the terms of the GNU Affero General Public License as published |
3350 | +# by the Free Software Foundation, either version 3 of the License, or |
3351 | +# (at your option) any later version. |
3352 | +# |
3353 | +# This program is distributed in the hope that it will be useful, |
3354 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3355 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3356 | +# GNU Affero General Public License for more details. |
3357 | +# |
3358 | +# You should have received a copy of the GNU Affero General Public License |
3359 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
3360 | +# |
3361 | +############################################################################## |
3362 | +{ |
3363 | + "name": "Detect changes and update the Account Chart from a template", |
3364 | + "version": "6.1", |
3365 | + "author": "Zikzakmedia SL", |
3366 | + "website": "www.zikzakmedia.com", |
3367 | + "license": "GPL-3", |
3368 | + "depends": ["account"], |
3369 | + "category": "Generic Modules/Accounting", |
3370 | + "description": """ |
3371 | +Adds a wizard to update a company account chart from a chart template. |
3372 | + |
3373 | +This is a pretty useful tool to update OpenERP instalations after tax reforms |
3374 | +on the oficial charts of accounts, or to apply fixes performed on the chart |
3375 | +template. |
3376 | + |
3377 | +The wizard: |
3378 | + |
3379 | +- Allows the user to compare a chart and a template showing differences |
3380 | + on accounts, taxes, tax codes and fiscal positions. |
3381 | +- It may create the new account, taxes, tax codes and fiscal positions detected |
3382 | + on the template. |
3383 | +- It can also update (overwrite) the accounts, taxes, tax codes and fiscal |
3384 | + positions that got modified on the template. |
3385 | + |
3386 | +The wizard lets the user select what kind of objects must be checked/updated, |
3387 | +and whether old records must be checked for changes and updated. |
3388 | +It will display all the accounts to be created / updated with some information |
3389 | +about the detected differences, and allow the user to exclude records |
3390 | +individually. |
3391 | +Any problem found while updating will be shown on the last step. |
3392 | + |
3393 | +Authors: |
3394 | + Jordi Esteve (Zikzakmedia) <jesteve@zikzakmedia.com> |
3395 | + Borja López Soilán (Pexego) <borjals@pexego.es> |
3396 | +""", |
3397 | + "init_xml": [], |
3398 | + "demo_xml": [], |
3399 | + "update_xml": [ |
3400 | + "account_view.xml", |
3401 | + ], |
3402 | + "active": False, |
3403 | + "installable": True |
3404 | +} |
3405 | |
3406 | === added file 'account_chart_update/account.py' |
3407 | --- account_chart_update/account.py 1970-01-01 00:00:00 +0000 |
3408 | +++ account_chart_update/account.py 2012-12-07 10:00:37 +0000 |
3409 | @@ -0,0 +1,1329 @@ |
3410 | +# -*- coding: utf-8 -*- |
3411 | +############################################################################## |
3412 | +# |
3413 | +# Copyright (c) 2010 Zikzakmedia S.L. (http://www.zikzakmedia.com) |
3414 | +# Copyright (c) 2010 Pexego Sistemas Informáticos S.L. (http://www.pexego.es) |
3415 | +# @authors: Jordi Esteve (Zikzakmedia), Borja López Soilán (Pexego) |
3416 | +# |
3417 | +# This program is free software: you can redistribute it and/or modify |
3418 | +# it under the terms of the GNU Affero General Public License as published |
3419 | +# by the Free Software Foundation, either version 3 of the License, or |
3420 | +# (at your option) any later version. |
3421 | +# |
3422 | +# This program is distributed in the hope that it will be useful, |
3423 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3424 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3425 | +# GNU Affero General Public License for more details. |
3426 | +# |
3427 | +# You should have received a copy of the GNU Affero General Public License |
3428 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
3429 | +# |
3430 | +############################################################################## |
3431 | +""" |
3432 | +Account Chart Update Wizard |
3433 | +""" |
3434 | +__authors__ = ["Jordi Esteve <jesteve@zikzakmedia.com>", |
3435 | + "Borja López Soilán <borjals@pexego.es>"] |
3436 | + |
3437 | +from osv import fields, osv |
3438 | +from tools.translate import _ |
3439 | +import logging |
3440 | + |
3441 | + |
3442 | +class WizardLog: |
3443 | + """ |
3444 | + Small helper class to store the messages and errors on the wizard. |
3445 | + """ |
3446 | + def __init__(self): |
3447 | + self.messages = [] |
3448 | + self.errors = [] |
3449 | + |
3450 | + def add(self, message, is_error=False): |
3451 | + """ |
3452 | + Adds a message to the log. |
3453 | + """ |
3454 | + logger = logging.getLogger("account_chart_update") |
3455 | + if is_error: |
3456 | + logger.warning(u"Log line: %s" % message) |
3457 | + self.errors.append(message) |
3458 | + else: |
3459 | + logger.debug(u"Log line: %s" % message) |
3460 | + self.messages.append(message) |
3461 | + |
3462 | + def has_errors(self): |
3463 | + """ |
3464 | + Returns whether errors where logged. |
3465 | + """ |
3466 | + return self.errors |
3467 | + |
3468 | + def __call__(self): |
3469 | + return "".join(self.messages) |
3470 | + |
3471 | + def __str__(self): |
3472 | + return "".join(self.messages) |
3473 | + |
3474 | + def get_errors_str(self): |
3475 | + return "".join(self.errors) |
3476 | + |
3477 | + |
3478 | +class wizard_update_charts_accounts(osv.osv_memory): |
3479 | + """ |
3480 | + Updates an existing account chart for a company. |
3481 | + Wizards ask for: |
3482 | + * a company |
3483 | + * an account chart template |
3484 | + * a number of digits for formatting code of non-view accounts |
3485 | + Then, the wizard: |
3486 | + * generates/updates all accounts from the template and assigns them to the right company |
3487 | + * generates/updates all taxes and tax codes, changing account assignations |
3488 | + * generates/updates all accounting properties and assigns them correctly |
3489 | + """ |
3490 | + _name = 'wizard.update.charts.accounts' |
3491 | + |
3492 | + def _get_lang_selection_options(self, cr, uid, context={}): |
3493 | + """ |
3494 | + Gets the available languages for the selection. |
3495 | + """ |
3496 | + obj = self.pool.get('res.lang') |
3497 | + ids = obj.search(cr, uid, [], context=context) |
3498 | + res = obj.read(cr, uid, ids, ['code', 'name'], context) |
3499 | + return [(r['code'], r['name']) for r in res] + [('', '')] |
3500 | + |
3501 | + _columns = { |
3502 | + 'state': fields.selection([ |
3503 | + ('init', 'Step 1'), |
3504 | + ('ready', 'Step 2'), |
3505 | + ('done', 'Wizard Complete') |
3506 | + ], 'Status', readonly=True), |
3507 | + 'company_id': fields.many2one('res.company', 'Company', required=True), |
3508 | + 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True), |
3509 | + 'code_digits': fields.integer('# of Digits', required=True, help="No. of Digits to use for account code. Make sure it is the same number as existing accounts."), |
3510 | + 'lang': fields.selection(_get_lang_selection_options, 'Language', size=5, help="For records searched by name (taxes, tax codes, fiscal positions), the template name will be matched against the record name on this language."), |
3511 | + 'update_tax_code': fields.boolean('Update tax codes', help="Existing tax codes are updated. Tax codes are searched by name."), |
3512 | + 'update_tax': fields.boolean('Update taxes', help="Existing taxes are updated. Taxes are searched by name."), |
3513 | + 'update_account': fields.boolean('Update accounts', help="Existing accounts are updated. Accounts are searched by code."), |
3514 | + 'update_fiscal_position': fields.boolean('Update fiscal positions', help="Existing fiscal positions are updated. Fiscal positions are searched by name."), |
3515 | + 'update_children_accounts_parent': fields.boolean("Update children accounts parent", |
3516 | + help="Update the parent of accounts that seem (based on the code) to be children of the newly created ones. If you had an account 430 with a child 4300000, and a 4300 account is created, the 4300000 parent will be set to 4300."), |
3517 | + 'continue_on_errors': fields.boolean("Continue on errors", help="If set, the wizard will continue to the next step even if there are minor errors (for example the parent account of a new account couldn't be set)."), |
3518 | + 'tax_code_ids': fields.one2many('wizard.update.charts.accounts.tax.code', 'update_chart_wizard_id', 'Tax codes'), |
3519 | + 'tax_ids': fields.one2many('wizard.update.charts.accounts.tax', 'update_chart_wizard_id', 'Taxes'), |
3520 | + 'account_ids': fields.one2many('wizard.update.charts.accounts.account', 'update_chart_wizard_id', 'Accounts'), |
3521 | + 'fiscal_position_ids': fields.one2many('wizard.update.charts.accounts.fiscal.position', 'update_chart_wizard_id', 'Fiscal positions'), |
3522 | + 'new_tax_codes': fields.integer('New tax codes', readonly=True), |
3523 | + 'new_taxes': fields.integer('New taxes', readonly=True), |
3524 | + 'new_accounts': fields.integer('New accounts', readonly=True), |
3525 | + 'new_fps': fields.integer('New fiscal positions', readonly=True), |
3526 | + 'updated_tax_codes': fields.integer('Updated tax codes', readonly=True), |
3527 | + 'updated_taxes': fields.integer('Updated taxes', readonly=True), |
3528 | + 'updated_accounts': fields.integer('Updated accounts', readonly=True), |
3529 | + 'updated_fps': fields.integer('Updated fiscal positions', readonly=True), |
3530 | + 'log': fields.text('Messages and Errors', readonly=True) |
3531 | + } |
3532 | + |
3533 | + def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80): |
3534 | + """ |
3535 | + Redefine the search to search by company name. |
3536 | + """ |
3537 | + if not name: |
3538 | + name = '%' |
3539 | + if not args: |
3540 | + args = [] |
3541 | + if not context: |
3542 | + context = {} |
3543 | + args = args[:] |
3544 | + ids = [] |
3545 | + ids = self.search( |
3546 | + cr, user, [('company_id', operator, name)] + args, limit=limit) |
3547 | + return self.name_get(cr, user, ids, context=context) |
3548 | + |
3549 | + def name_get(self, cr, uid, ids, context=None): |
3550 | + """ |
3551 | + Use the company name and template as name. |
3552 | + """ |
3553 | + if context is None: |
3554 | + context = {} |
3555 | + if not len(ids): |
3556 | + return [] |
3557 | + records = self.browse(cr, uid, ids, context) |
3558 | + res = [] |
3559 | + for record in records: |
3560 | + res.append((record.id, record.company_id.name + |
3561 | + ' - ' + record.chart_template_id.name)) |
3562 | + return res |
3563 | + |
3564 | + def _get_chart(self, cr, uid, context=None): |
3565 | + """ |
3566 | + Returns the default chart template. |
3567 | + """ |
3568 | + if context is None: |
3569 | + context = {} |
3570 | + ids = self.pool.get( |
3571 | + 'account.chart.template').search(cr, uid, [], context=context) |
3572 | + if ids: |
3573 | + return ids[0] |
3574 | + return False |
3575 | + |
3576 | + def _get_code_digits(self, cr, uid, context=None, company_id=None): |
3577 | + """ |
3578 | + Returns the default code size for the accounts. |
3579 | + |
3580 | + To figure out the number of digits of the accounts it look at the |
3581 | + code size of the default receivable account of the company |
3582 | + (or user's company if any company is given). |
3583 | + """ |
3584 | + if context is None: |
3585 | + context = {} |
3586 | + property_obj = self.pool.get('ir.property') |
3587 | + account_obj = self.pool.get('account.account') |
3588 | + if not company_id: |
3589 | + user = self.pool.get('res.users').browse(cr, uid, uid, context) |
3590 | + company_id = user.company_id.id |
3591 | + property_ids = property_obj.search(cr, uid, [('name', '=', 'property_account_receivable'), ('company_id', '=', company_id), ('res_id', '=', False), ('value_reference', '!=', False)]) |
3592 | + if not property_ids: |
3593 | + # Try to get a generic (no-company) property |
3594 | + property_ids = property_obj.search(cr, uid, [('name', '=', 'property_account_receivable'), ('res_id', '=', False), ('value_reference', '!=', False)]) |
3595 | + number_digits = 6 |
3596 | + if property_ids: |
3597 | + prop = property_obj.browse( |
3598 | + cr, uid, property_ids[0], context=context) |
3599 | + account_id = prop.value_reference.id |
3600 | + |
3601 | + if account_id: |
3602 | + code = account_obj.read( |
3603 | + cr, uid, account_id, ['code'], context)['code'] |
3604 | + number_digits = len(code) |
3605 | + return number_digits |
3606 | + |
3607 | + _defaults = { |
3608 | + 'state': lambda *a: 'init', |
3609 | + 'company_id': lambda self, cr, uid, context: self.pool.get('res.users').browse(cr, uid, [uid], context)[0].company_id.id, |
3610 | + 'chart_template_id': _get_chart, |
3611 | + 'update_tax_code': lambda *a: True, |
3612 | + 'update_tax': lambda *a: True, |
3613 | + 'update_account': lambda *a: True, |
3614 | + 'update_fiscal_position': lambda *a: True, |
3615 | + 'update_children_accounts_parent': lambda *a: True, |
3616 | + 'continue_on_errors': lambda *a: False, |
3617 | + 'lang': lambda self, cr, uid, context: context and context.get('lang') or None, |
3618 | + } |
3619 | + |
3620 | + def onchange_company_id(self, cr, uid, ids, company_id, context=None): |
3621 | + """ |
3622 | + Update the code size when the company changes |
3623 | + """ |
3624 | + res = { |
3625 | + 'value': { |
3626 | + 'code_digits': self._get_code_digits(cr, uid, context=context, company_id=company_id), |
3627 | + } |
3628 | + } |
3629 | + return res |
3630 | + |
3631 | + def action_init(self, cr, uid, ids, context=None): |
3632 | + """ |
3633 | + Initial action that sets the initial state. |
3634 | + """ |
3635 | + if context is None: |
3636 | + context = {} |
3637 | + self.write(cr, uid, ids, {'state': 'init'}, context) |
3638 | + return True |
3639 | + |
3640 | + ############################################################################ |
3641 | + # Helper methods |
3642 | + ########################################################################## |
3643 | + def _map_tax_template(self, cr, uid, wizard, tax_template_mapping, tax_template, context=None): |
3644 | + """ |
3645 | + Adds a tax template -> tax id to the mapping. |
3646 | + """ |
3647 | + if tax_template and not tax_template_mapping.get(tax_template.id): |
3648 | + tax_facade = self.pool.get('account.tax') |
3649 | + tax_ids = tax_facade.search(cr, uid, [ |
3650 | + ('name', '=', tax_template.name), |
3651 | + ('company_id', '=', wizard.company_id.id) |
3652 | + ], context=context) |
3653 | + if tax_ids: |
3654 | + tax_template_mapping[tax_template.id] = tax_ids[0] |
3655 | + |
3656 | + def _map_tax_code_template(self, cr, uid, wizard, tax_code_template_mapping, tax_code_template, context=None): |
3657 | + """ |
3658 | + Adds a tax code template -> tax code id to the mapping. |
3659 | + """ |
3660 | + if tax_code_template and not tax_code_template_mapping.get(tax_code_template.id): |
3661 | + tax_code_facade = self.pool.get('account.tax.code') |
3662 | + root_tax_code_id = wizard.chart_template_id.tax_code_root_id.id |
3663 | + tax_code_name = (tax_code_template.id == root_tax_code_id) and wizard.company_id.name or tax_code_template.name |
3664 | + tax_code_ids = tax_code_facade.search(cr, uid, [ |
3665 | + ('name', '=', tax_code_name), |
3666 | + ('company_id', '=', wizard.company_id.id) |
3667 | + ]) |
3668 | + if tax_code_ids: |
3669 | + tax_code_template_mapping[ |
3670 | + tax_code_template.id] = tax_code_ids[0] |
3671 | + |
3672 | + def _map_account_template(self, cr, uid, wizard, account_template_mapping, account_template, context=None): |
3673 | + """ |
3674 | + Adds an account template -> account id to the mapping |
3675 | + """ |
3676 | + if account_template and not account_template_mapping.get(account_template.id): |
3677 | + account_facade = self.pool.get('account.account') |
3678 | + code = account_template.code or '' |
3679 | + if account_template.type != 'view': |
3680 | + if len(code) > 0 and len(code) <= wizard.code_digits: |
3681 | + code = '%s%s' % ( |
3682 | + code, '0' * (wizard.code_digits - len(code))) |
3683 | + account_ids = account_facade.search(cr, uid, [ |
3684 | + ('code', '=', code), |
3685 | + ('company_id', '=', wizard.company_id.id) |
3686 | + ], context=context) |
3687 | + if account_ids: |
3688 | + account_template_mapping[account_template.id] = account_ids[0] |
3689 | + |
3690 | + def _map_fp_template(self, cr, uid, wizard, fp_template_mapping, fp_template, context=None): |
3691 | + """ |
3692 | + Adds a fiscal position template -> fiscal position id to the mapping. |
3693 | + """ |
3694 | + if fp_template and not fp_template_mapping.get(fp_template.id): |
3695 | + fp_facade = self.pool.get('account.fiscal.position') |
3696 | + fp_ids = fp_facade.search(cr, uid, [ |
3697 | + ('name', '=', fp_template.name), |
3698 | + ('company_id', '=', wizard.company_id.id) |
3699 | + ], context=context) |
3700 | + if fp_ids: |
3701 | + fp_template_mapping[fp_template.id] = fp_ids[0] |
3702 | + |
3703 | + ############################################################################ |
3704 | + # Find records methods |
3705 | + ########################################################################## |
3706 | + def _find_tax_codes(self, cr, uid, wizard, context=None): |
3707 | + """ |
3708 | + Search for, and load, tax code templates to create/update. |
3709 | + """ |
3710 | + new_tax_codes = 0 |
3711 | + updated_tax_codes = 0 |
3712 | + tax_code_template_mapping = {} |
3713 | + |
3714 | + tax_code_templ_facade = self.pool.get('account.tax.code.template') |
3715 | + tax_code_facade = self.pool.get('account.tax.code') |
3716 | + wiz_tax_code_facade = self.pool.get( |
3717 | + 'wizard.update.charts.accounts.tax.code') |
3718 | + |
3719 | + # Remove previous tax codes |
3720 | + wiz_tax_code_facade.unlink( |
3721 | + cr, uid, wiz_tax_code_facade.search(cr, uid, [])) |
3722 | + |
3723 | + # |
3724 | + # Search for new / updated tax codes |
3725 | + # |
3726 | + root_tax_code_id = wizard.chart_template_id.tax_code_root_id.id |
3727 | + children_tax_code_template = tax_code_templ_facade.search(cr, uid, [( |
3728 | + 'parent_id', 'child_of', [root_tax_code_id])], order='id') |
3729 | + for tax_code_template in tax_code_templ_facade.browse(cr, uid, children_tax_code_template): |
3730 | + # Ensure the tax code template is on the map (search for the mapped |
3731 | + # tax code id). |
3732 | + self._map_tax_code_template(cr, uid, wizard, tax_code_template_mapping, tax_code_template, context) |
3733 | + |
3734 | + tax_code_id = tax_code_template_mapping.get(tax_code_template.id) |
3735 | + if not tax_code_id: |
3736 | + new_tax_codes += 1 |
3737 | + wiz_tax_code_facade.create(cr, uid, { |
3738 | + 'tax_code_id': tax_code_template.id, |
3739 | + 'update_chart_wizard_id': wizard.id, |
3740 | + 'type': 'new', |
3741 | + }, context) |
3742 | + elif wizard.update_tax_code: |
3743 | + # |
3744 | + # Check the tax code for changes. |
3745 | + # |
3746 | + modified = False |
3747 | + notes = "" |
3748 | + tax_code = tax_code_facade.browse( |
3749 | + cr, uid, tax_code_id, context=context) |
3750 | + |
3751 | + if tax_code.code != tax_code_template.code: |
3752 | + notes += _("The code field is different.\n") |
3753 | + modified = True |
3754 | + if tax_code.info != tax_code_template.info: |
3755 | + notes += _("The info field is different.\n") |
3756 | + modified = True |
3757 | + if tax_code.sign != tax_code_template.sign: |
3758 | + notes += _("The sign field is different.\n") |
3759 | + modified = True |
3760 | + |
3761 | + # TODO: We could check other account fields for changes... |
3762 | + |
3763 | + if modified: |
3764 | + # |
3765 | + # Tax code to update. |
3766 | + # |
3767 | + updated_tax_codes += 1 |
3768 | + wiz_tax_code_facade.create(cr, uid, { |
3769 | + 'tax_code_id': tax_code_template.id, |
3770 | + 'update_chart_wizard_id': wizard.id, |
3771 | + 'type': 'updated', |
3772 | + 'update_tax_code_id': tax_code_id, |
3773 | + 'notes': notes, |
3774 | + }, context) |
3775 | + |
3776 | + return {'new': new_tax_codes, 'updated': updated_tax_codes, 'mapping': tax_code_template_mapping} |
3777 | + |
3778 | + def _find_taxes(self, cr, uid, wizard, context=None): |
3779 | + """ |
3780 | + Search for, and load, tax templates to create/update. |
3781 | + """ |
3782 | + new_taxes = 0 |
3783 | + updated_taxes = 0 |
3784 | + tax_template_mapping = {} |
3785 | + |
3786 | + tax_facade = self.pool.get('account.tax') |
3787 | + wiz_tax_facade = self.pool.get('wizard.update.charts.accounts.tax') |
3788 | + |
3789 | + delay_wiz_tax = [] |
3790 | + # Remove previous taxes |
3791 | + wiz_tax_facade.unlink(cr, uid, wiz_tax_facade.search(cr, uid, [])) |
3792 | + |
3793 | + # |
3794 | + # Search for new / updated taxes |
3795 | + # |
3796 | + for tax_template in wizard.chart_template_id.tax_template_ids: |
3797 | + # Ensure the tax template is on the map (search for the mapped tax |
3798 | + # id). |
3799 | + self._map_tax_template( |
3800 | + cr, uid, wizard, tax_template_mapping, tax_template, context) |
3801 | + |
3802 | + tax_id = tax_template_mapping.get(tax_template.id) |
3803 | + if not tax_id: |
3804 | + new_taxes += 1 |
3805 | + vals_wiz = { |
3806 | + 'tax_id': tax_template.id, |
3807 | + 'update_chart_wizard_id': wizard.id, |
3808 | + 'type': 'new', |
3809 | + } |
3810 | + if not tax_template.parent_id: |
3811 | + wiz_tax_facade.create(cr, uid, vals_wiz, context) |
3812 | + else: |
3813 | + delay_wiz_tax.append(vals_wiz) |
3814 | + elif wizard.update_tax: |
3815 | + # |
3816 | + # Check the tax for changes. |
3817 | + # |
3818 | + modified = False |
3819 | + notes = "" |
3820 | + tax = tax_facade.browse(cr, uid, tax_id, context=context) |
3821 | + |
3822 | + if tax.sequence != tax_template.sequence: |
3823 | + notes += _("The sequence field is different.\n") |
3824 | + modified = True |
3825 | + if tax.amount != tax_template.amount: |
3826 | + notes += _("The amount field is different.\n") |
3827 | + modified = True |
3828 | + if tax.type != tax_template.type: |
3829 | + notes += _("The type field is different.\n") |
3830 | + modified = True |
3831 | + if tax.applicable_type != tax_template.applicable_type: |
3832 | + notes += _("The applicable type field is different.\n") |
3833 | + modified = True |
3834 | + if tax.domain != tax_template.domain: |
3835 | + notes += _("The domain field is different.\n") |
3836 | + modified = True |
3837 | + if tax.child_depend != tax_template.child_depend: |
3838 | + notes += _("The child depend field is different.\n") |
3839 | + modified = True |
3840 | + if tax.python_compute != tax_template.python_compute: |
3841 | + notes += _("The python compute field is different.\n") |
3842 | + modified = True |
3843 | + # if tax.tax_group != tax_template.tax_group: |
3844 | + # notes += _("The tax group field is different.\n") |
3845 | + # modified = True |
3846 | + if tax.base_sign != tax_template.base_sign: |
3847 | + notes += _("The base sign field is different.\n") |
3848 | + modified = True |
3849 | + if tax.tax_sign != tax_template.tax_sign: |
3850 | + notes += _("The tax sign field is different.\n") |
3851 | + modified = True |
3852 | + if tax.include_base_amount != tax_template.include_base_amount: |
3853 | + notes += _("The include base amount field is different.\n") |
3854 | + modified = True |
3855 | + if tax.type_tax_use != tax_template.type_tax_use: |
3856 | + notes += _("The type tax use field is different.\n") |
3857 | + modified = True |
3858 | + # TODO: We could check other tax fields for changes... |
3859 | + |
3860 | + if modified: |
3861 | + # |
3862 | + # Tax code to update. |
3863 | + # |
3864 | + updated_taxes += 1 |
3865 | + wiz_tax_facade.create(cr, uid, { |
3866 | + 'tax_id': tax_template.id, |
3867 | + 'update_chart_wizard_id': wizard.id, |
3868 | + 'type': 'updated', |
3869 | + 'update_tax_id': tax_id, |
3870 | + 'notes': notes, |
3871 | + }, context) |
3872 | + |
3873 | + for delay_vals_wiz in delay_wiz_tax: |
3874 | + wiz_tax_facade.create(cr, uid, delay_vals_wiz, context) |
3875 | + |
3876 | + return {'new': new_taxes, 'updated': updated_taxes, 'mapping': tax_template_mapping} |
3877 | + |
3878 | + def _find_accounts(self, cr, uid, wizard, context=None): |
3879 | + """ |
3880 | + Search for, and load, account templates to create/update. |
3881 | + """ |
3882 | + new_accounts = 0 |
3883 | + updated_accounts = 0 |
3884 | + account_template_mapping = {} |
3885 | + |
3886 | + account_facade = self.pool.get('account.account') |
3887 | + account_template_facade = self.pool.get('account.account.template') |
3888 | + wiz_account_facade = self.pool.get( |
3889 | + 'wizard.update.charts.accounts.account') |
3890 | + |
3891 | + # Remove previous accounts |
3892 | + wiz_account_facade.unlink( |
3893 | + cr, uid, wiz_account_facade.search(cr, uid, [])) |
3894 | + |
3895 | + # |
3896 | + # Search for new / updated accounts |
3897 | + # |
3898 | + root_account_id = wizard.chart_template_id.account_root_id.id |
3899 | + children_acc_template = account_template_facade.search(cr, uid, [( |
3900 | + 'parent_id', 'child_of', [root_account_id])], context=context) |
3901 | + children_acc_template.sort() |
3902 | + for account_template in account_template_facade.browse(cr, uid, children_acc_template, context=context): |
3903 | + # Ensure the account template is on the map (search for the mapped |
3904 | + # account id). |
3905 | + self._map_account_template(cr, uid, wizard, account_template_mapping, account_template, context) |
3906 | + |
3907 | + account_id = account_template_mapping.get(account_template.id) |
3908 | + if not account_id: |
3909 | + new_accounts += 1 |
3910 | + wiz_account_facade.create(cr, uid, { |
3911 | + 'account_id': account_template.id, |
3912 | + 'update_chart_wizard_id': wizard.id, |
3913 | + 'type': 'new', |
3914 | + }, context) |
3915 | + elif wizard.update_account: |
3916 | + # |
3917 | + # Check the account for changes. |
3918 | + # |
3919 | + modified = False |
3920 | + notes = "" |
3921 | + account = account_facade.browse( |
3922 | + cr, uid, account_id, context=context) |
3923 | + |
3924 | + if account.name != account_template.name and account.name != wizard.company_id.name: |
3925 | + notes += _("The name is different.\n") |
3926 | + modified = True |
3927 | + if account.type != account_template.type: |
3928 | + notes += _("The type is different.\n") |
3929 | + modified = True |
3930 | + if account.user_type != account_template.user_type: |
3931 | + notes += _("The user type is different.\n") |
3932 | + modified = True |
3933 | + if account.reconcile != account_template.reconcile: |
3934 | + notes += _("The reconcile is different.\n") |
3935 | + modified = True |
3936 | + |
3937 | + # TODO: We could check other account fields for changes... |
3938 | + |
3939 | + if modified: |
3940 | + # |
3941 | + # Account to update. |
3942 | + # |
3943 | + updated_accounts += 1 |
3944 | + wiz_account_facade.create(cr, uid, { |
3945 | + 'account_id': account_template.id, |
3946 | + 'update_chart_wizard_id': wizard.id, |
3947 | + 'type': 'updated', |
3948 | + 'update_account_id': account_id, |
3949 | + 'notes': notes, |
3950 | + }, context) |
3951 | + |
3952 | + return {'new': new_accounts, 'updated': updated_accounts, 'mapping': account_template_mapping} |
3953 | + |
3954 | + def _find_fiscal_positions(self, cr, uid, wizard, context=None): |
3955 | + """ |
3956 | + Search for, and load, fiscal position templates to create/update. |
3957 | + """ |
3958 | + new_fps = 0 |
3959 | + updated_fps = 0 |
3960 | + fp_template_mapping = {} |
3961 | + |
3962 | + fp_template_facade = self.pool.get('account.fiscal.position.template') |
3963 | + fp_facade = self.pool.get('account.fiscal.position') |
3964 | + wiz_fp_facade = self.pool.get( |
3965 | + 'wizard.update.charts.accounts.fiscal.position') |
3966 | + |
3967 | + # Remove previous fiscal positions |
3968 | + wiz_fp_facade.unlink(cr, uid, wiz_fp_facade.search(cr, uid, [])) |
3969 | + |
3970 | + # |
3971 | + # Search for new / updated fiscal positions |
3972 | + # |
3973 | + fp_template_ids = fp_template_facade.search(cr, uid, [('chart_template_id', '=', wizard.chart_template_id.id)], context=context) |
3974 | + for fp_template in fp_template_facade.browse(cr, uid, fp_template_ids, context=context): |
3975 | + # Ensure the fiscal position template is on the map (search for the |
3976 | + # mapped fiscal position id). |
3977 | + self._map_fp_template( |
3978 | + cr, uid, wizard, fp_template_mapping, fp_template, context) |
3979 | + |
3980 | + fp_id = fp_template_mapping.get(fp_template.id) |
3981 | + if not fp_id: |
3982 | + # |
3983 | + # New fiscal position template. |
3984 | + # |
3985 | + new_fps += 1 |
3986 | + wiz_fp_facade.create(cr, uid, { |
3987 | + 'fiscal_position_id': fp_template.id, |
3988 | + 'update_chart_wizard_id': wizard.id, |
3989 | + 'type': 'new', |
3990 | + }, context) |
3991 | + elif wizard.update_fiscal_position: |
3992 | + # |
3993 | + # Check the fiscal position for changes. |
3994 | + # |
3995 | + modified = False |
3996 | + notes = "" |
3997 | + fp = fp_facade.browse(cr, uid, fp_id, context=context) |
3998 | + |
3999 | + # |
4000 | + # Check fiscal position taxes for changes. |
4001 | + # |
4002 | + if fp_template.tax_ids and fp.tax_ids: |
4003 | + for fp_tax_template in fp_template.tax_ids: |
4004 | + found = False |
4005 | + for fp_tax in fp.tax_ids: |
4006 | + if fp_tax.tax_src_id.name == fp_tax_template.tax_src_id.name: |
4007 | + if fp_tax_template.tax_dest_id and fp_tax.tax_dest_id: |
4008 | + if fp_tax.tax_dest_id.name == fp_tax_template.tax_dest_id.name: |
4009 | + found = True |
4010 | + break |
4011 | + elif not fp_tax_template.tax_dest_id and not fp_tax.tax_dest_id: |
4012 | + found = True |
4013 | + break |
4014 | + if not found: |
4015 | + if fp_tax_template.tax_dest_id: |
4016 | + notes += _("Tax mapping not found on the fiscal position instance: %s -> %s.\n") % (fp_tax_template.tax_src_id.name, fp_tax_template.tax_dest_id.name) |
4017 | + else: |
4018 | + notes += _("Tax mapping not found on the fiscal position instance: %s -> None.\n") % fp_tax_template.tax_src_id.name |
4019 | + modified = True |
4020 | + elif fp_template.tax_ids and not fp.tax_ids: |
4021 | + notes += _("The template has taxes the fiscal position instance does not.\n") |
4022 | + modified = True |
4023 | + |
4024 | + # |
4025 | + # Check fiscal position accounts for changes. |
4026 | + # |
4027 | + if fp_template.account_ids and fp.account_ids: |
4028 | + for fp_account_template in fp_template.account_ids: |
4029 | + found = False |
4030 | + for fp_account in fp.account_ids: |
4031 | + if fp_account.account_src_id.name == fp_account_template.account_src_id.name: |
4032 | + if fp_account.account_dest_id.name == fp_account_template.account_dest_id.name: |
4033 | + found = True |
4034 | + break |
4035 | + if not found: |
4036 | + notes += _("Account mapping not found on the fiscal position instance: %s -> %s.\n") % (fp_account_template.account_src_id.name, fp_account_template.account_dest_id.name) |
4037 | + modified = True |
4038 | + elif fp_template.account_ids and not fp.account_ids: |
4039 | + notes += _("The template has accounts the fiscal position instance does not.\n") |
4040 | + modified = True |
4041 | + |
4042 | + if modified: |
4043 | + # |
4044 | + # Fiscal position template to update. |
4045 | + # |
4046 | + updated_fps += 1 |
4047 | + wiz_fp_facade.create(cr, uid, { |
4048 | + 'fiscal_position_id': fp_template.id, |
4049 | + 'update_chart_wizard_id': wizard.id, |
4050 | + 'type': 'updated', |
4051 | + 'update_fiscal_position_id': fp_id, |
4052 | + 'notes': notes, |
4053 | + }, context) |
4054 | + |
4055 | + return {'new': new_fps, 'updated': updated_fps, 'mapping': fp_template_mapping} |
4056 | + |
4057 | + def action_find_records(self, cr, uid, ids, context=None): |
4058 | + """ |
4059 | + Searchs for records to update/create and shows them |
4060 | + """ |
4061 | + if context is None: |
4062 | + context = {} |
4063 | + wizard = self.browse(cr, uid, ids[0], context=context) |
4064 | + |
4065 | + if wizard.lang: |
4066 | + context['lang'] = wizard.lang |
4067 | + elif context.get('lang'): |
4068 | + del context['lang'] |
4069 | + |
4070 | + # |
4071 | + # Search for, and load, the records to create/update. |
4072 | + # |
4073 | + tax_codes_res = self._find_tax_codes(cr, uid, wizard, context=context) |
4074 | + taxes_res = self._find_taxes(cr, uid, wizard, context=context) |
4075 | + accounts_res = self._find_accounts(cr, uid, wizard, context=context) |
4076 | + fps_res = self._find_fiscal_positions(cr, uid, wizard, context=context) |
4077 | + |
4078 | + # |
4079 | + # Write the results, and go to the next step. |
4080 | + # |
4081 | + self.write(cr, uid, [wizard.id], { |
4082 | + 'state': 'ready', |
4083 | + 'new_tax_codes': tax_codes_res.get('new', 0), |
4084 | + 'new_taxes': taxes_res.get('new', 0), |
4085 | + 'new_accounts': accounts_res.get('new', 0), |
4086 | + 'new_fps': fps_res.get('new', 0), |
4087 | + 'updated_tax_codes': tax_codes_res.get('updated', 0), |
4088 | + 'updated_taxes': taxes_res.get('updated', 0), |
4089 | + 'updated_accounts': accounts_res.get('updated', 0), |
4090 | + 'updated_fps': fps_res.get('updated', 0), |
4091 | + }, context) |
4092 | + |
4093 | + return True |
4094 | + |
4095 | + ############################################################################ |
4096 | + # Update records methods |
4097 | + ########################################################################## |
4098 | + def _update_tax_codes(self, cr, uid, wizard, log, context=None): |
4099 | + """ |
4100 | + Search for, and load, tax code templates to create/update. |
4101 | + """ |
4102 | + tax_code_facade = self.pool.get('account.tax.code') |
4103 | + |
4104 | + root_tax_code_id = wizard.chart_template_id.tax_code_root_id.id |
4105 | + |
4106 | + new_tax_codes = 0 |
4107 | + updated_tax_codes = 0 |
4108 | + tax_code_template_mapping = {} |
4109 | + |
4110 | + for wiz_tax_code in wizard.tax_code_ids: |
4111 | + tax_code_template = wiz_tax_code.tax_code_id |
4112 | + tax_code_name = (root_tax_code_id == tax_code_template.id) and wizard.company_id.name or tax_code_template.name |
4113 | + |
4114 | + # Ensure the parent tax code template is on the map. |
4115 | + self._map_tax_code_template(cr, uid, wizard, tax_code_template_mapping, tax_code_template.parent_id, context) |
4116 | + |
4117 | + # |
4118 | + # Values |
4119 | + # |
4120 | + vals = { |
4121 | + 'name': tax_code_name, |
4122 | + 'code': tax_code_template.code, |
4123 | + 'info': tax_code_template.info, |
4124 | + 'parent_id': tax_code_template.parent_id and tax_code_template_mapping.get(tax_code_template.parent_id.id), |
4125 | + 'company_id': wizard.company_id.id, |
4126 | + 'sign': tax_code_template.sign, |
4127 | + } |
4128 | + |
4129 | + tax_code_id = None |
4130 | + modified = False |
4131 | + |
4132 | + if wiz_tax_code.type == 'new': |
4133 | + # |
4134 | + # Create the tax code |
4135 | + # |
4136 | + tax_code_id = tax_code_facade.create(cr, uid, vals) |
4137 | + log.add(_("Created tax code %s.\n") % tax_code_name) |
4138 | + new_tax_codes += 1 |
4139 | + modified = True |
4140 | + elif wizard.update_tax_code and wiz_tax_code.update_tax_code_id: |
4141 | + # |
4142 | + # Update the tax code |
4143 | + # |
4144 | + tax_code_id = wiz_tax_code.update_tax_code_id.id |
4145 | + tax_code_facade.write(cr, uid, [tax_code_id], vals) |
4146 | + log.add(_("Updated tax code %s.\n") % tax_code_name) |
4147 | + updated_tax_codes += 1 |
4148 | + modified = True |
4149 | + else: |
4150 | + tax_code_id = wiz_tax_code.update_tax_code_id and wiz_tax_code.update_tax_code_id.id |
4151 | + modified = False |
4152 | + |
4153 | + # Store the tax codes on the map |
4154 | + tax_code_template_mapping[tax_code_template.id] = tax_code_id |
4155 | + |
4156 | + if modified: |
4157 | + # |
4158 | + # Detect errors |
4159 | + # |
4160 | + if tax_code_template.parent_id and not tax_code_template_mapping.get(tax_code_template.parent_id.id): |
4161 | + log.add(_("Tax code %s: The parent tax code %s can not be set.\n") % (tax_code_name, tax_code_template.parent_id.name), True) |
4162 | + |
4163 | + return { |
4164 | + 'new': new_tax_codes, |
4165 | + 'updated': updated_tax_codes, |
4166 | + 'mapping': tax_code_template_mapping |
4167 | + } |
4168 | + |
4169 | + def _update_taxes(self, cr, uid, wizard, log, tax_code_template_mapping, context=None): |
4170 | + """ |
4171 | + Search for, and load, tax templates to create/update. |
4172 | + """ |
4173 | + tax_facade = self.pool.get('account.tax') |
4174 | + |
4175 | + new_taxes = 0 |
4176 | + updated_taxes = 0 |
4177 | + tax_template_mapping = {} |
4178 | + taxes_pending_for_accounts = {} |
4179 | + |
4180 | + for wiz_tax in wizard.tax_ids: |
4181 | + tax_template = wiz_tax.tax_id |
4182 | + |
4183 | + # Ensure the parent tax template is on the map. |
4184 | + self._map_tax_template(cr, uid, wizard, tax_template_mapping, |
4185 | + tax_template.parent_id, context) |
4186 | + |
4187 | + # |
4188 | + # Ensure the referenced tax codes are on the map. |
4189 | + # |
4190 | + tax_code_templates_to_find = [ |
4191 | + tax_template.base_code_id, |
4192 | + tax_template.tax_code_id, |
4193 | + tax_template.ref_base_code_id, |
4194 | + tax_template.ref_tax_code_id |
4195 | + ] |
4196 | + for tax_code_template in [tmpl for tmpl in tax_code_templates_to_find if tmpl]: |
4197 | + self._map_tax_code_template(cr, uid, wizard, tax_code_template_mapping, tax_code_template) |
4198 | + |
4199 | + # |
4200 | + # Values |
4201 | + # |
4202 | + vals_tax = { |
4203 | + 'name': tax_template.name, |
4204 | + 'sequence': tax_template.sequence, |
4205 | + 'amount': tax_template.amount, |
4206 | + 'type': tax_template.type, |
4207 | + 'applicable_type': tax_template.applicable_type, |
4208 | + 'domain': tax_template.domain, |
4209 | + 'parent_id': tax_template.parent_id and tax_template_mapping.get(tax_template.parent_id.id), |
4210 | + 'child_depend': tax_template.child_depend, |
4211 | + 'python_compute': tax_template.python_compute, |
4212 | + 'python_compute_inv': tax_template.python_compute_inv, |
4213 | + 'python_applicable': tax_template.python_applicable, |
4214 | + #'tax_group': tax_template.tax_group, |
4215 | + 'base_code_id': tax_template.base_code_id and tax_code_template_mapping.get(tax_template.base_code_id.id), |
4216 | + 'tax_code_id': tax_template.tax_code_id and tax_code_template_mapping.get(tax_template.tax_code_id.id), |
4217 | + 'base_sign': tax_template.base_sign, |
4218 | + 'tax_sign': tax_template.tax_sign, |
4219 | + 'ref_base_code_id': tax_template.ref_base_code_id and tax_code_template_mapping.get(tax_template.ref_base_code_id.id), |
4220 | + 'ref_tax_code_id': tax_template.ref_tax_code_id and tax_code_template_mapping.get(tax_template.ref_tax_code_id.id), |
4221 | + 'ref_base_sign': tax_template.ref_base_sign, |
4222 | + 'ref_tax_sign': tax_template.ref_tax_sign, |
4223 | + 'include_base_amount': tax_template.include_base_amount, |
4224 | + 'description': tax_template.description, |
4225 | + 'company_id': wizard.company_id.id, |
4226 | + 'type_tax_use': tax_template.type_tax_use |
4227 | + } |
4228 | + |
4229 | + tax_id = None |
4230 | + modified = False |
4231 | + |
4232 | + if wiz_tax.type == 'new': |
4233 | + # |
4234 | + # Create a new tax. |
4235 | + # |
4236 | + tax_id = tax_facade.create(cr, uid, vals_tax) |
4237 | + log.add(_("Created tax %s.\n") % tax_template.name) |
4238 | + new_taxes += 1 |
4239 | + modified = True |
4240 | + elif wizard.update_tax and wiz_tax.update_tax_id: |
4241 | + # |
4242 | + # Update a tax. |
4243 | + # |
4244 | + tax_id = wiz_tax.update_tax_id.id |
4245 | + tax_facade.write(cr, uid, [tax_id], vals_tax) |
4246 | + log.add(_("Updated tax %s.\n") % tax_template.name) |
4247 | + updated_taxes += 1 |
4248 | + modified = True |
4249 | + else: |
4250 | + tax_id = wiz_tax.update_tax_id and wiz_tax.update_tax_id.id |
4251 | + |
4252 | + # Update the tax template map |
4253 | + tax_template_mapping[tax_template.id] = tax_id |
4254 | + |
4255 | + if modified: |
4256 | + # |
4257 | + # Add to the dict of taxes waiting for accounts. |
4258 | + # |
4259 | + taxes_pending_for_accounts[tax_id] = { |
4260 | + 'account_collected_id': tax_template.account_collected_id and tax_template.account_collected_id.id or False, |
4261 | + 'account_paid_id': tax_template.account_paid_id and tax_template.account_paid_id.id or False, |
4262 | + } |
4263 | + |
4264 | + # |
4265 | + # Detect errors |
4266 | + # |
4267 | + if tax_template.parent_id and not tax_template_mapping.get(tax_template.parent_id.id): |
4268 | + log.add(_("Tax %s: The parent tax %s can not be set.\n") % (tax_template.name, tax_template.parent_id.name), True) |
4269 | + if tax_template.base_code_id and not tax_code_template_mapping.get(tax_template.base_code_id.id): |
4270 | + log.add(_("Tax %s: The tax code for the base %s can not be set.\n") % (tax_template.name, tax_template.base_code_id.name), True) |
4271 | + if tax_template.tax_code_id and not tax_code_template_mapping.get(tax_template.tax_code_id.id): |
4272 | + log.add(_("Tax %s: The tax code for the tax %s can not be set.\n") % (tax_template.name, tax_template.tax_code_id.name), True) |
4273 | + if tax_template.ref_base_code_id and not tax_code_template_mapping.get(tax_template.ref_base_code_id.id): |
4274 | + log.add(_("Tax %s: The tax code for the base refund %s can not be set.\n") % (tax_template.name, tax_template.ref_base_code_id.name), True) |
4275 | + if tax_template.ref_tax_code_id and not tax_code_template_mapping.get(tax_template.ref_tax_code_id.id): |
4276 | + log.add(_("Tax %s: The tax code for the tax refund %s can not be set.\n") % (tax_template.name, tax_template.ref_tax_code_id.name), True) |
4277 | + |
4278 | + return { |
4279 | + 'new': new_taxes, |
4280 | + 'updated': updated_taxes, |
4281 | + 'mapping': tax_template_mapping, |
4282 | + 'pending': taxes_pending_for_accounts |
4283 | + } |
4284 | + |
4285 | + def _update_children_accounts_parent(self, cr, uid, wizard, log, parent_account_id, context=None): |
4286 | + """ |
4287 | + Updates the parent_id of accounts that seem to be children of the |
4288 | + given account (accounts that start with the same code and are brothers |
4289 | + of the first account). |
4290 | + """ |
4291 | + account_facade = self.pool.get('account.account') |
4292 | + parent_account = account_facade.browse( |
4293 | + cr, uid, parent_account_id, context=context) |
4294 | + |
4295 | + if not parent_account.parent_id or not parent_account.code: |
4296 | + return False |
4297 | + |
4298 | + children_ids = account_facade.search(cr, uid, [ |
4299 | + ('company_id', '=', |
4300 | + parent_account.company_id and parent_account.company_id.id), |
4301 | + ('parent_id', '=', parent_account.parent_id.id), |
4302 | + ('code', '=like', "%s%%" % parent_account.code), |
4303 | + ('id', '!=', parent_account.id), |
4304 | + ], context=context) |
4305 | + |
4306 | + if children_ids: |
4307 | + try: |
4308 | + account_facade.write(cr, uid, children_ids, {'parent_id': |
4309 | + parent_account.id}, context=context) |
4310 | + except osv.except_osv, ex: |
4311 | + log.add(_("Exception setting the parent of account %s children: %s - %s.\n") % (parent_account.code, ex.name, ex.value), True) |
4312 | + |
4313 | + return True |
4314 | + |
4315 | + def _update_accounts(self, cr, uid, wizard, log, tax_template_mapping, context=None): |
4316 | + """ |
4317 | + Search for, and load, account templates to create/update. |
4318 | + """ |
4319 | + account_facade = self.pool.get('account.account') |
4320 | + |
4321 | + root_account_id = wizard.chart_template_id.account_root_id.id |
4322 | + |
4323 | + # Disable the parent_store computing on account_account during the batch |
4324 | + # processing, we will force _parent_store_compute afterwards. |
4325 | + self.pool._init = True |
4326 | + |
4327 | + new_accounts = 0 |
4328 | + updated_accounts = 0 |
4329 | + account_template_mapping = {} |
4330 | + |
4331 | + for wiz_account in wizard.account_ids: |
4332 | + account_template = wiz_account.account_id |
4333 | + |
4334 | + # Ensure the parent account template is on the map. |
4335 | + self._map_account_template(cr, uid, wizard, account_template_mapping, account_template.parent_id, context) |
4336 | + |
4337 | + # |
4338 | + # Ensure the related tax templates are on the map. |
4339 | + # |
4340 | + for tax_template in account_template.tax_ids: |
4341 | + self._map_tax_template(cr, uid, wizard, tax_template_mapping, |
4342 | + tax_template, context) |
4343 | + |
4344 | + # Get the tax ids |
4345 | + tax_ids = [tax_template_mapping[tax_template.id] for tax_template in account_template.tax_ids if tax_template_mapping[tax_template.id]] |
4346 | + |
4347 | + # |
4348 | + # Calculate the account code (we need to add zeros to non-view |
4349 | + # account codes) |
4350 | + # |
4351 | + code = account_template.code or '' |
4352 | + if account_template.type != 'view': |
4353 | + if len(code) > 0 and len(code) <= wizard.code_digits: |
4354 | + code = '%s%s' % ( |
4355 | + code, '0' * (wizard.code_digits - len(code))) |
4356 | + |
4357 | + # |
4358 | + # Values |
4359 | + # |
4360 | + vals = { |
4361 | + 'name': (root_account_id == account_template.id) and wizard.company_id.name or account_template.name, |
4362 | + #'sign': account_template.sign, |
4363 | + 'currency_id': account_template.currency_id and account_template.currency_id.id or False, |
4364 | + 'code': code, |
4365 | + 'type': account_template.type, |
4366 | + 'user_type': account_template.user_type and account_template.user_type.id or False, |
4367 | + 'reconcile': account_template.reconcile, |
4368 | + 'shortcut': account_template.shortcut, |
4369 | + 'note': account_template.note, |
4370 | + 'parent_id': account_template.parent_id and account_template_mapping.get(account_template.parent_id.id) or False, |
4371 | + 'tax_ids': [(6, 0, tax_ids)], |
4372 | + 'company_id': wizard.company_id.id, |
4373 | + } |
4374 | + |
4375 | + account_id = None |
4376 | + modified = False |
4377 | + |
4378 | + if wiz_account.type == 'new': |
4379 | + # |
4380 | + # Create the account |
4381 | + # |
4382 | + try: |
4383 | + account_id = account_facade.create(cr, uid, vals) |
4384 | + log.add(_("Created account %s.\n") % code) |
4385 | + new_accounts += 1 |
4386 | + modified = True |
4387 | + except osv.except_osv, ex: |
4388 | + log.add(_("Exception creating account %s: %s - %s.\n") |
4389 | + % (code, ex.name, ex.value), True) |
4390 | + elif wizard.update_account and wiz_account.update_account_id: |
4391 | + # |
4392 | + # Update the account |
4393 | + # |
4394 | + account_id = wiz_account.update_account_id.id |
4395 | + try: |
4396 | + account_facade.write(cr, uid, [account_id], vals) |
4397 | + log.add(_("Updated account %s.\n") % code) |
4398 | + updated_accounts += 1 |
4399 | + modified = True |
4400 | + except osv.except_osv, ex: |
4401 | + log.add(_("Exception writing account %s: %s - %s.\n") |
4402 | + % (code, ex.name, ex.value), True) |
4403 | + else: |
4404 | + account_id = wiz_account.update_account_id and wiz_account.update_account_id.id |
4405 | + |
4406 | + # Store the account on the map |
4407 | + account_template_mapping[account_template.id] = account_id |
4408 | + |
4409 | + if modified: |
4410 | + # |
4411 | + # Detect errors |
4412 | + # |
4413 | + if account_template.parent_id and not account_template_mapping.get(account_template.parent_id.id): |
4414 | + log.add(_("Account %s: The parent account %s can not be set.\n") % (code, account_template.parent_id.code), True) |
4415 | + |
4416 | + # |
4417 | + # Set this account as the parent of the accounts that seem to |
4418 | + # be its children (brothers starting with the same code). |
4419 | + # |
4420 | + if wizard.update_children_accounts_parent: |
4421 | + self._update_children_accounts_parent( |
4422 | + cr, uid, wizard, log, account_id, context=context) |
4423 | + |
4424 | + # |
4425 | + # Reenable the parent_store computing on account_account |
4426 | + # and force the recomputation. |
4427 | + # |
4428 | + self.pool._init = False |
4429 | + self.pool.get('account.account')._parent_store_compute(cr) |
4430 | + |
4431 | + return { |
4432 | + 'new': new_accounts, |
4433 | + 'updated': updated_accounts, |
4434 | + 'mapping': account_template_mapping |
4435 | + } |
4436 | + |
4437 | + def _update_taxes_pending_for_accounts(self, cr, uid, wizard, log, taxes_pending_for_accounts, account_template_mapping, context=None): |
4438 | + """ |
4439 | + Updates the taxes (created or updated on previous steps) to set |
4440 | + the references to the accounts (the taxes where created/updated first, |
4441 | + when the referenced accounts where still not available). |
4442 | + """ |
4443 | + tax_facade = self.pool.get('account.tax') |
4444 | + account_template_facade = self.pool.get('account.account.template') |
4445 | + |
4446 | + for key, value in taxes_pending_for_accounts.items(): |
4447 | + # |
4448 | + # Ensure the related account templates are on the map. |
4449 | + # |
4450 | + if value['account_collected_id']: |
4451 | + account_template = account_template_facade.browse( |
4452 | + cr, uid, value['account_collected_id'], context=context) |
4453 | + self._map_account_template(cr, uid, wizard, account_template_mapping, account_template, context) |
4454 | + if value['account_paid_id']: |
4455 | + account_template = account_template_facade.browse( |
4456 | + cr, uid, value['account_paid_id'], context=context) |
4457 | + self._map_account_template(cr, uid, wizard, account_template_mapping, account_template, context) |
4458 | + |
4459 | + if value['account_collected_id'] or value['account_paid_id']: |
4460 | + if account_template_mapping.get(value['account_collected_id']) and account_template_mapping.get(value['account_paid_id']): |
4461 | + vals = { |
4462 | + 'account_collected_id': account_template_mapping[value['account_collected_id']], |
4463 | + 'account_paid_id': account_template_mapping[value['account_paid_id']], |
4464 | + } |
4465 | + tax_facade.write(cr, uid, [key], vals) |
4466 | + else: |
4467 | + tax = tax_facade.browse(cr, uid, key) |
4468 | + if not account_template_mapping.get(value['account_collected_id']): |
4469 | + log.add(_("Tax %s: The collected account can not be set.\n") % (tax.name), True) |
4470 | + if not account_template_mapping.get(value['account_paid_id']): |
4471 | + log.add(_("Tax %s: The paid account can not be set.\n") |
4472 | + % (tax.name), True) |
4473 | + |
4474 | + def _update_fiscal_positions(self, cr, uid, wizard, log, tax_template_mapping, account_template_mapping, context=None): |
4475 | + """ |
4476 | + Search for, and load, fiscal position templates to create/update. |
4477 | + """ |
4478 | + fp_facade = self.pool.get('account.fiscal.position') |
4479 | + fp_tax_facade = self.pool.get('account.fiscal.position.tax') |
4480 | + fp_account_facade = self.pool.get('account.fiscal.position.account') |
4481 | + |
4482 | + new_fps = 0 |
4483 | + updated_fps = 0 |
4484 | + |
4485 | + for wiz_fp in wizard.fiscal_position_ids: |
4486 | + fp_template = wiz_fp.fiscal_position_id |
4487 | + |
4488 | + fp_id = None |
4489 | + modified = False |
4490 | + if wiz_fp.type == 'new': |
4491 | + # |
4492 | + # Create a new fiscal position |
4493 | + # |
4494 | + vals_fp = { |
4495 | + 'company_id': wizard.company_id.id, |
4496 | + 'name': fp_template.name, |
4497 | + } |
4498 | + fp_id = fp_facade.create(cr, uid, vals_fp) |
4499 | + new_fps += 1 |
4500 | + modified = True |
4501 | + elif wizard.update_fiscal_position and wiz_fp.update_fiscal_position_id: |
4502 | + # |
4503 | + # Update the given fiscal position (remove the tax and account |
4504 | + # mappings, that will be regenerated later) |
4505 | + # |
4506 | + fp_id = wiz_fp.update_fiscal_position_id.id |
4507 | + updated_fps += 1 |
4508 | + modified = True |
4509 | + # Remove the tax mappings |
4510 | + fp_tax_ids = fp_tax_facade.search( |
4511 | + cr, uid, [('position_id', '=', fp_id)]) |
4512 | + fp_tax_facade.unlink(cr, uid, fp_tax_ids) |
4513 | + # Remove the account mappings |
4514 | + fp_account_ids = fp_account_facade.search( |
4515 | + cr, uid, [('position_id', '=', fp_id)]) |
4516 | + fp_account_facade.unlink(cr, uid, fp_account_ids) |
4517 | + else: |
4518 | + fp_id = wiz_fp.update_fiscal_position_id and wiz_fp.update_fiscal_position_id.id |
4519 | + |
4520 | + if modified: |
4521 | + # |
4522 | + # (Re)create the tax mappings |
4523 | + # |
4524 | + for fp_tax in fp_template.tax_ids: |
4525 | + # |
4526 | + # Ensure the related tax templates are on the map. |
4527 | + # |
4528 | + self._map_tax_template(cr, uid, wizard, tax_template_mapping, fp_tax.tax_src_id, context) |
4529 | + if fp_tax.tax_dest_id: |
4530 | + self._map_tax_template(cr, uid, wizard, tax_template_mapping, fp_tax.tax_dest_id, context) |
4531 | + |
4532 | + # |
4533 | + # Create the fp tax mapping |
4534 | + # |
4535 | + vals_tax = { |
4536 | + 'tax_src_id': tax_template_mapping.get(fp_tax.tax_src_id.id), |
4537 | + 'tax_dest_id': fp_tax.tax_dest_id and tax_template_mapping.get(fp_tax.tax_dest_id.id), |
4538 | + 'position_id': fp_id, |
4539 | + } |
4540 | + fp_tax_facade.create(cr, uid, vals_tax) |
4541 | + |
4542 | + # |
4543 | + # Check for errors |
4544 | + # |
4545 | + if not tax_template_mapping.get(fp_tax.tax_src_id.id): |
4546 | + log.add(_("Fiscal position %s: The source tax %s can not be set.\n") % (fp_template.name, fp_tax.tax_src_id.code), True) |
4547 | + if fp_tax.tax_dest_id and not tax_template_mapping.get(fp_tax.tax_dest_id.id): |
4548 | + log.add(_("Fiscal position %s: The destination tax %s can not be set.\n") % (fp_template.name, fp_tax.tax_dest_id.name), True) |
4549 | + # |
4550 | + # (Re)create the account mappings |
4551 | + # |
4552 | + for fp_account in fp_template.account_ids: |
4553 | + # |
4554 | + # Ensure the related account templates are on the map. |
4555 | + # |
4556 | + self._map_account_template(cr, uid, wizard, account_template_mapping, fp_account.account_src_id, context) |
4557 | + if fp_account.account_dest_id: |
4558 | + self._map_account_template(cr, uid, wizard, account_template_mapping, fp_account.account_dest_id, context) |
4559 | + |
4560 | + # |
4561 | + # Create the fp account mapping |
4562 | + # |
4563 | + vals_account = { |
4564 | + 'account_src_id': account_template_mapping.get(fp_account.account_src_id.id), |
4565 | + 'account_dest_id': fp_account.account_dest_id and account_template_mapping.get(fp_account.account_dest_id.id), |
4566 | + 'position_id': fp_id, |
4567 | + } |
4568 | + fp_account_facade.create(cr, uid, vals_account) |
4569 | + |
4570 | + # |
4571 | + # Check for errors |
4572 | + # |
4573 | + if not account_template_mapping.get(fp_account.account_src_id.id): |
4574 | + log.add(_("Fiscal position %s: The source account %s can not be set.\n") % (fp_template.name, fp_account.account_src_id.code), True) |
4575 | + if fp_account.account_dest_id and not account_template_mapping.get(fp_account.account_dest_id.id): |
4576 | + log.add(_("Fiscal position %s: The destination account %s can not be set.\n") % (fp_template.name, fp_account.account_dest_id.code), True) |
4577 | + |
4578 | + log.add(_("Created or updated fiscal position %s.\n") |
4579 | + % fp_template.name) |
4580 | + return {'new': new_fps, 'updated': updated_fps} |
4581 | + |
4582 | + def action_update_records(self, cr, uid, ids, context=None): |
4583 | + """ |
4584 | + Action that creates/updates the selected elements. |
4585 | + """ |
4586 | + if context is None: |
4587 | + context = {} |
4588 | + wizard = self.browse(cr, uid, ids[0], context=context) |
4589 | + |
4590 | + if wizard.lang: |
4591 | + context['lang'] = wizard.lang |
4592 | + elif context.get('lang'): |
4593 | + del context['lang'] |
4594 | + |
4595 | + log = WizardLog() |
4596 | + |
4597 | + # |
4598 | + # Create or update the records. |
4599 | + # |
4600 | + tax_codes_res = self._update_tax_codes( |
4601 | + cr, uid, wizard, log, context=context) |
4602 | + taxes_res = self._update_taxes( |
4603 | + cr, uid, wizard, log, tax_codes_res['mapping'], context=context) |
4604 | + accounts_res = self._update_accounts( |
4605 | + cr, uid, wizard, log, taxes_res['pending'], context=context) |
4606 | + self._update_taxes_pending_for_accounts(cr, uid, wizard, log, taxes_res['pending'], accounts_res['mapping'], context=context) |
4607 | + fps_res = self._update_fiscal_positions(cr, uid, wizard, log, taxes_res['mapping'], accounts_res['mapping'], context=context) |
4608 | + |
4609 | + # |
4610 | + # Check if errors where detected and wether we should stop. |
4611 | + # |
4612 | + if log.has_errors() and not wizard.continue_on_errors: |
4613 | + raise osv.except_osv(_('Error'), _( |
4614 | + "One or more errors detected!\n\n%s") % log.get_errors_str()) |
4615 | + |
4616 | + # |
4617 | + # Store the data and go to the next step. |
4618 | + # |
4619 | + self.write(cr, uid, [wizard.id], { |
4620 | + 'state': 'done', |
4621 | + 'new_tax_codes': tax_codes_res.get('new', 0), |
4622 | + 'new_taxes': taxes_res.get('new', 0), |
4623 | + 'new_accounts': accounts_res .get('new', 0), |
4624 | + 'new_fps': fps_res.get('new', 0), |
4625 | + 'updated_tax_codes': tax_codes_res.get('updated', 0), |
4626 | + 'updated_taxes': taxes_res.get('updated', 0), |
4627 | + 'updated_accounts': accounts_res.get('updated', 0), |
4628 | + 'updated_fps': fps_res.get('updated', 0), |
4629 | + 'log': log(), |
4630 | + }, context) |
4631 | + |
4632 | + return True |
4633 | + |
4634 | +wizard_update_charts_accounts() |
4635 | + |
4636 | + |
4637 | +class wizard_update_charts_accounts_tax_code(osv.osv_memory): |
4638 | + """ |
4639 | + Tax code that needs to be updated (new or updated in the template). |
4640 | + """ |
4641 | + _name = 'wizard.update.charts.accounts.tax.code' |
4642 | + |
4643 | + _columns = { |
4644 | + 'tax_code_id': fields.many2one('account.tax.code.template', 'Tax code template', required=True), |
4645 | + 'update_chart_wizard_id': fields.many2one('wizard.update.charts.accounts', 'Update chart wizard', required=True), |
4646 | + 'type': fields.selection([ |
4647 | + ('new', 'New template'), |
4648 | + ('updated', 'Updated template'), |
4649 | + ], 'Type'), |
4650 | + 'update_tax_code_id': fields.many2one('account.tax.code', 'Tax code to update', required=False), |
4651 | + 'notes': fields.text('Notes', readonly=True), |
4652 | + } |
4653 | + |
4654 | + _defaults = { |
4655 | + #'update_tax_code_id': lambda *a: None, |
4656 | + } |
4657 | + |
4658 | +wizard_update_charts_accounts_tax_code() |
4659 | + |
4660 | + |
4661 | +class wizard_update_charts_accounts_tax(osv.osv_memory): |
4662 | + """ |
4663 | + Tax that needs to be updated (new or updated in the template). |
4664 | + """ |
4665 | + _name = 'wizard.update.charts.accounts.tax' |
4666 | + |
4667 | + _columns = { |
4668 | + 'tax_id': fields.many2one('account.tax.template', 'Tax template', required=True), |
4669 | + 'update_chart_wizard_id': fields.many2one('wizard.update.charts.accounts', 'Update chart wizard', required=True), |
4670 | + 'type': fields.selection([ |
4671 | + ('new', 'New template'), |
4672 | + ('updated', 'Updated template'), |
4673 | + ], 'Type'), |
4674 | + 'update_tax_id': fields.many2one('account.tax', 'Tax to update', required=False), |
4675 | + 'notes': fields.text('Notes', readonly=True), |
4676 | + } |
4677 | + |
4678 | + _defaults = { |
4679 | + #'update_tax_id': lambda *a: None, |
4680 | + } |
4681 | + |
4682 | +wizard_update_charts_accounts_tax() |
4683 | + |
4684 | + |
4685 | +class wizard_update_charts_accounts_account(osv.osv_memory): |
4686 | + """ |
4687 | + Account that needs to be updated (new or updated in the template). |
4688 | + """ |
4689 | + _name = 'wizard.update.charts.accounts.account' |
4690 | + |
4691 | + # The chart of accounts can have a lot of accounts, so we need an higher |
4692 | + # limit for the objects in memory to let the wizard create all the items |
4693 | + # at once. |
4694 | + _max_count = 4096 |
4695 | + |
4696 | + _columns = { |
4697 | + 'account_id': fields.many2one('account.account.template', 'Account template', required=True), |
4698 | + 'update_chart_wizard_id': fields.many2one('wizard.update.charts.accounts', 'Update chart wizard', required=True), |
4699 | + 'type': fields.selection([ |
4700 | + ('new', 'New template'), |
4701 | + ('updated', 'Updated template'), |
4702 | + ], 'Type'), |
4703 | + 'update_account_id': fields.many2one('account.account', 'Account to update', required=False), |
4704 | + 'notes': fields.text('Notes', readonly=True), |
4705 | + } |
4706 | + |
4707 | + _defaults = { |
4708 | + #'update_account_id': lambda *a: None, |
4709 | + } |
4710 | + |
4711 | +wizard_update_charts_accounts_account() |
4712 | + |
4713 | + |
4714 | +class wizard_update_charts_accounts_fiscal_position(osv.osv_memory): |
4715 | + """ |
4716 | + Fiscal position that needs to be updated (new or updated in the template). |
4717 | + """ |
4718 | + _name = 'wizard.update.charts.accounts.fiscal.position' |
4719 | + |
4720 | + _columns = { |
4721 | + 'fiscal_position_id': fields.many2one('account.fiscal.position.template', 'Fiscal position template', required=True), |
4722 | + 'update_chart_wizard_id': fields.many2one('wizard.update.charts.accounts', 'Update chart wizard', required=True), |
4723 | + 'type': fields.selection([ |
4724 | + ('new', 'New template'), |
4725 | + ('updated', 'Updated template'), |
4726 | + ], 'Type'), |
4727 | + 'update_fiscal_position_id': fields.many2one('account.fiscal.position', 'Fiscal position to update', required=False), |
4728 | + 'notes': fields.text('Notes', readonly=True), |
4729 | + } |
4730 | + |
4731 | + _defaults = { |
4732 | + #'update_fiscal_position_id': lambda *a: None, |
4733 | + } |
4734 | + |
4735 | + |
4736 | +wizard_update_charts_accounts_fiscal_position() |
4737 | + |
4738 | +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
4739 | |
4740 | === added file 'account_chart_update/account_view.xml' |
4741 | --- account_chart_update/account_view.xml 1970-01-01 00:00:00 +0000 |
4742 | +++ account_chart_update/account_view.xml 2012-12-07 10:00:37 +0000 |
4743 | @@ -0,0 +1,159 @@ |
4744 | +<?xml version="1.0" encoding="utf-8"?> |
4745 | +<openerp> |
4746 | + <data> |
4747 | + |
4748 | + <!-- Wizard for Multi Charts of Accounts --> |
4749 | + |
4750 | + <record id="view_update_multi_chart" model="ir.ui.view"> |
4751 | + <field name="name">Update Chart of Accounts from a Chart Template</field> |
4752 | + <field name="model">wizard.update.charts.accounts</field> |
4753 | + <field name="type">form</field> |
4754 | + <field name="arch" type="xml"> |
4755 | + <form string="Update Chart of Accounts from a Chart Template"> |
4756 | + <group col="4" colspan="4" attrs="{'invisible':[('state','!=','init')]}"> |
4757 | + <label string="This wizard will update your accounts, taxes and fiscal positions according to the selected chart template." colspan="4"/> |
4758 | + <label string="" colspan="4"/> |
4759 | + <group colspan="4"> |
4760 | + <separator col="4" colspan="4" string="Chart of Accounts"/> |
4761 | + <field name="company_id" on_change="onchange_company_id(company_id)"/> |
4762 | + <field name="code_digits"/> |
4763 | + <field name="chart_template_id"/> |
4764 | + <field name="lang"/> |
4765 | + </group> |
4766 | + <label string=""/> |
4767 | + <group colspan="4"> |
4768 | + <separator string="Update records?" colspan="4"/> |
4769 | + <group colspan="2" col="2"> |
4770 | + <field name="update_tax_code"/> |
4771 | + <field name="update_tax"/> |
4772 | + <field name="update_account"/> |
4773 | + <field name="update_fiscal_position"/> |
4774 | + </group> |
4775 | + <group colspan="2"> |
4776 | + <label string="If you leave these options set, the wizard will not just create new records, but also update records with changes (i.e. different tax amount)." colspan="4" align="0.0"/> |
4777 | + <label string="Note: Not all the fields are tested for changes, just the main ones." colspan="4" align="0.0"/> |
4778 | + </group> |
4779 | + </group> |
4780 | + <group colspan="4"> |
4781 | + <separator string="Other options" colspan="4"/> |
4782 | + <field name="update_children_accounts_parent"/> |
4783 | + <field name="continue_on_errors"/> |
4784 | + </group> |
4785 | + </group> |
4786 | + |
4787 | + <group col="4" colspan="4" attrs="{'invisible':[('state','!=','ready')]}"> |
4788 | + <separator colspan="4" string="Records to create/update"/> |
4789 | + <notebook colspan="4"> |
4790 | + <page string="Tax codes"> |
4791 | + <field name="tax_code_ids" colspan="4" nolabel="1" mode="tree,form" height="330"> |
4792 | + <tree string="Tax codes" colors="red:type=='updated'"> |
4793 | + <field name="tax_code_id"/> |
4794 | + <field name="update_tax_code_id"/> |
4795 | + <field name="type" invisible="1"/> |
4796 | + </tree> |
4797 | + <form string="Tax code"> |
4798 | + <field name="tax_code_id" colspan="4"/> |
4799 | + <field name="type"/> |
4800 | + <field name="update_tax_code_id"/> |
4801 | + <separator string="Notes" colspan="4"/> |
4802 | + <field name="notes" colspan="4" nolabel="1"/> |
4803 | + </form> |
4804 | + </field> |
4805 | + </page> |
4806 | + <page string="Taxes"> |
4807 | + <field name="tax_ids" colspan="4" nolabel="1" mode="tree,form" height="330"> |
4808 | + <tree string="Taxes" colors="red:type=='updated'"> |
4809 | + <field name="tax_id"/> |
4810 | + <field name="update_tax_id"/> |
4811 | + <field name="type" invisible="1"/> |
4812 | + </tree> |
4813 | + <form string="Tax"> |
4814 | + <field name="tax_id" colspan="4"/> |
4815 | + <field name="type"/> |
4816 | + <field name="update_tax_id"/> |
4817 | + <separator string="Notes" colspan="4"/> |
4818 | + <field name="notes" colspan="4" nolabel="1"/> |
4819 | + </form> |
4820 | + </field> |
4821 | + </page> |
4822 | + <page string="Accounts"> |
4823 | + <field name="account_ids" colspan="4" nolabel="1" mode="tree,form" height="330"> |
4824 | + <tree string="Accounts" colors="red:type=='updated'"> |
4825 | + <field name="account_id"/> |
4826 | + <field name="update_account_id"/> |
4827 | + <field name="type" invisible="1"/> |
4828 | + </tree> |
4829 | + <form string="Account"> |
4830 | + <field name="account_id" colspan="4"/> |
4831 | + <field name="type"/> |
4832 | + <field name="update_account_id"/> |
4833 | + <separator string="Notes" colspan="4"/> |
4834 | + <field name="notes" colspan="4" nolabel="1"/> |
4835 | + </form> |
4836 | + </field> |
4837 | + </page> |
4838 | + <page string="Fiscal positions"> |
4839 | + <field name="fiscal_position_ids" colspan="4" nolabel="1" mode="tree,form" height="330"> |
4840 | + <tree string="Fiscal positions" colors="red:type=='updated'"> |
4841 | + <field name="fiscal_position_id"/> |
4842 | + <field name="update_fiscal_position_id"/> |
4843 | + <field name="type" invisible="1"/> |
4844 | + </tree> |
4845 | + <form string="Fiscal position"> |
4846 | + <field name="fiscal_position_id" colspan="4"/> |
4847 | + <field name="type"/> |
4848 | + <field name="update_fiscal_position_id"/> |
4849 | + <separator string="Notes" colspan="4"/> |
4850 | + <field name="notes" colspan="4" nolabel="1"/> |
4851 | + </form> |
4852 | + </field> |
4853 | + </page> |
4854 | + </notebook> |
4855 | + </group> |
4856 | + |
4857 | + <group col="4" colspan="4" attrs="{'invisible':[('state','!=','done'),]}"> |
4858 | + <separator colspan="4" string="Log"/> |
4859 | + <field name="log" colspan="4" nolabel="1"/> |
4860 | + <group colspan="4"> |
4861 | + <separator colspan="4" string="Summary of created objects"/> |
4862 | + <field name="new_tax_codes"/> |
4863 | + <field name="new_taxes"/> |
4864 | + <field name="new_accounts"/> |
4865 | + <field name="new_fps"/> |
4866 | + </group> |
4867 | + <group colspan="4"> |
4868 | + <separator colspan="4" string="Summary of updated objects"/> |
4869 | + <field name="updated_tax_codes"/> |
4870 | + <field name="updated_taxes"/> |
4871 | + <field name="updated_accounts"/> |
4872 | + <field name="updated_fps"/> |
4873 | + </group> |
4874 | + </group> |
4875 | + |
4876 | + <separator string="" colspan="4"/> |
4877 | + <group col="8" colspan="4"> |
4878 | + <field name="state"/> |
4879 | + <button icon="gtk-cancel" special="cancel" string="Cancel" states="init,ready"/> |
4880 | + <button icon="gtk-go-forward" name="action_find_records" string="Next" type="object" states="init"/> |
4881 | + <button icon="gtk-go-back" name="action_init" string="Previous" type="object" states="ready"/> |
4882 | + <button icon="gtk-ok" name="action_update_records" string="Create/Update" type="object" states="ready"/> |
4883 | + <button icon="gtk-ok" special="cancel" string="Ok" type="object" states="done"/> |
4884 | + </group> |
4885 | + |
4886 | + </form> |
4887 | + </field> |
4888 | + </record> |
4889 | + |
4890 | + <record id="action_wizard_update_chart" model="ir.actions.act_window"> |
4891 | + <field name="name">Update Chart of Accounts from a Chart Template</field> |
4892 | + <field name="type">ir.actions.act_window</field> |
4893 | + <field name="res_model">wizard.update.charts.accounts</field> |
4894 | + <field name="view_type">form</field> |
4895 | + <field name="view_mode">form</field> |
4896 | + <field name="target">new</field> |
4897 | + </record> |
4898 | + |
4899 | + <menuitem parent="account.account_template_folder" action="action_wizard_update_chart" id="menu_wizard"/> |
4900 | + |
4901 | + </data> |
4902 | +</openerp> |
4903 | |
4904 | === added directory 'account_chart_update/i18n' |
4905 | === added file 'account_chart_update/i18n/account_chart_update.pot' |
4906 | --- account_chart_update/i18n/account_chart_update.pot 1970-01-01 00:00:00 +0000 |
4907 | +++ account_chart_update/i18n/account_chart_update.pot 2012-12-07 10:00:37 +0000 |
4908 | @@ -0,0 +1,744 @@ |
4909 | +# Translation of OpenERP Server. |
4910 | +# This file contains the translation of the following modules: |
4911 | +# * account_chart_update |
4912 | +# |
4913 | +msgid "" |
4914 | +msgstr "" |
4915 | +"Project-Id-Version: OpenERP Server 5.0.10\n" |
4916 | +"Report-Msgid-Bugs-To: support@openerp.com\n" |
4917 | +"POT-Creation-Date: 2010-06-10 15:41:54+0000\n" |
4918 | +"PO-Revision-Date: 2010-06-10 15:41:54+0000\n" |
4919 | +"Last-Translator: <>\n" |
4920 | +"Language-Team: \n" |
4921 | +"MIME-Version: 1.0\n" |
4922 | +"Content-Type: text/plain; charset=UTF-8\n" |
4923 | +"Content-Transfer-Encoding: \n" |
4924 | +"Plural-Forms: \n" |
4925 | + |
4926 | +#. module: account_chart_update |
4927 | +#: field:wizard.update.charts.accounts,lang:0 |
4928 | +msgid "Language" |
4929 | +msgstr "" |
4930 | + |
4931 | +#. module: account_chart_update |
4932 | +#: code:addons/account_chart_update/account.py:0 |
4933 | +#, python-format |
4934 | +msgid "Created or updated fiscal position %s.\n" |
4935 | +msgstr "" |
4936 | + |
4937 | +#. module: account_chart_update |
4938 | +#: code:addons/account_chart_update/account.py:0 |
4939 | +#, python-format |
4940 | +msgid "Exception creating account %s: %s - %s.\n" |
4941 | +msgstr "" |
4942 | + |
4943 | +#. module: account_chart_update |
4944 | +#: code:addons/account_chart_update/account.py:0 |
4945 | +#, python-format |
4946 | +msgid "Exception writing account %s: %s - %s.\n" |
4947 | +msgstr "" |
4948 | + |
4949 | +#. module: account_chart_update |
4950 | +#: code:addons/account_chart_update/account.py:0 |
4951 | +#, python-format |
4952 | +msgid "Exception setting the parent of account %s children: %s - %s.\n" |
4953 | +msgstr "" |
4954 | + |
4955 | +#. module: account_chart_update |
4956 | +#: code:addons/account_chart_update/account.py:0 |
4957 | +#, python-format |
4958 | +msgid "Updated account %s.\n" |
4959 | +msgstr "" |
4960 | + |
4961 | +#. module: account_chart_update |
4962 | +#: code:addons/account_chart_update/account.py:0 |
4963 | +#, python-format |
4964 | +msgid "Created account %s.\n" |
4965 | +msgstr "" |
4966 | + |
4967 | +#. module: account_chart_update |
4968 | +#: code:addons/account_chart_update/account.py:0 |
4969 | +#, python-format |
4970 | +msgid "Created tax %s.\n" |
4971 | +msgstr "" |
4972 | + |
4973 | +#. module: account_chart_update |
4974 | +#: code:addons/account_chart_update/account.py:0 |
4975 | +#, python-format |
4976 | +msgid "Updated tax %s.\n" |
4977 | +msgstr "" |
4978 | + |
4979 | +#. module: account_chart_update |
4980 | +#: code:addons/account_chart_update/account.py:0 |
4981 | +#, python-format |
4982 | +msgid "Created tax code %s.\n" |
4983 | +msgstr "" |
4984 | + |
4985 | +#. module: account_chart_update |
4986 | +#: code:addons/account_chart_update/account.py:0 |
4987 | +#, python-format |
4988 | +msgid "Updated tax code %s.\n" |
4989 | +msgstr "" |
4990 | + |
4991 | +#. module: account_chart_update |
4992 | +#: code:addons/account_chart_update/account.py:0 |
4993 | +#, python-format |
4994 | +msgid "The template has accounts the fiscal position instance does not.\n" |
4995 | +msgstr "" |
4996 | + |
4997 | +#. module: account_chart_update |
4998 | +#: code:addons/account_chart_update/account.py:0 |
4999 | +#, python-format |
5000 | +msgid "The base sign field is different.\n" |
Dear Omar,
First of all thank you for your contribution on this project ! Happy to see them landing here. I have analyzed your module here, more from a functional / features point of view, rather than technical, but still I have some remarks:
* account_ chart_update : Seems the right place to land. The menu where it stands is good for me as it'll be the same people that will use it than the one who will create the account chart.
* account_renumber : Seems useful in some cases for an administrator of the system, but it could definitely be dangerous for a lambda user. Move number are now related to invoice when they're issue by them. So using this by a non-administrator could lead to dangerous result. I would really prefer to have the menu entry in the administration menu for that purpose.
* account_admin_tools : On this one, my opinion is that the import feature (for both account and move) should be made in a new module. No need to mix accounting importation features with the "repair" tools. Then, for the repair tools, as far as I can see, it's more repair tools used on past version (I say version 5.0 probably, but I'm may be wrong). Most of the provided features (but may be not all) are a bit obsolete from my point of view.
What I would suggest to you if, you agree, is to:
* Make a merge proposal for both account_ chart_update and account_renumber and moving the menu entry
* Extract the importation tool form the account_admin_tools in a new module and make a MP for it in this project
* For the remaining "check and repair" tools, may be we need opinion from others as well. On my side, I don't think it's a good module to include here. Or at least not with the current included feature.
Other comments welcome here. We also said that serie 6.1 should include old modules, so if you think I'm to "hard" here, please let me know. For now, I put my vote on disapprove, but I'm open to discussion here.
In any case, thank you for your MP !
Regards,
Joël