Merge lp:~pexego/account-financial-tools/adding_account_tools_from_extra_addons_to into lp:~account-core-editors/account-financial-tools/6.1

Proposed by Omar (Pexego)
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
Reviewer Review Type Date Requested Status
Joël Grand-Guillaume @ camptocamp Disapprove
Review via email: mp+138676@code.launchpad.net

Description of the change

Moving pxgo_account_admin_tools, account_renumber and account_chart_update from extra-addons with small improvements:

pxgo_account_admin_tools renamed to account_admin_tools
pxgo_account_admin_tools and account_renumber ported to 6.1
PEP8

To post a comment you must log in.
Revision history for this message
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote :

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

review: Disapprove
Revision history for this message
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_admin_tools, but for 6.1 i think is not a god idea split it into 2 modules. For sure it must be done for 7.0 and remove check and repair functionality

Revision history for this message
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_admin_tools, you still using those "repair and check" fixes on version 6.1 ? Having a look on LP show that those trouble was discussed in 2010 and I can't remember that we face it in v6.1.

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

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

Revision history for this message
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added directory 'account_admin_tools'
=== added file 'account_admin_tools/__init__.py'
--- account_admin_tools/__init__.py 1970-01-01 00:00:00 +0000
+++ account_admin_tools/__init__.py 2012-12-07 10:00:37 +0000
@@ -0,0 +1,35 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved
6# $Id$
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as published
10# by the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22
23"""
24Account Admin Tools
25"""
26
27__author__ = "Borja López Soilán (Pexego)"
28
29import account_importer
30import account_move_importer
31import account_chart_checker
32import revalidate_moves
33import move_partner_account
34import set_partner_in_moves
35import set_invoice_ref_in_moves
036
=== added file 'account_admin_tools/__openerp__.py'
--- account_admin_tools/__openerp__.py 1970-01-01 00:00:00 +0000
+++ account_admin_tools/__openerp__.py 2012-12-07 10:00:37 +0000
@@ -0,0 +1,86 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved
6# $Id$
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as published
10# by the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22
23{
24 "name": "Account Admin Tools",
25 "version": "6.1",
26 "author": "Pexego",
27 "website": "http://www.pexego.es",
28 "category": "Enterprise Specific Modules",
29 "description": """Account Tools for Administrators
30
31Import tools:
32
33- Import accounts from CSV files. This may be useful to import the initial
34 accounts into OpenERP.
35
36- Import account moves from CSV files. This may be useful to import the initial
37 balance into OpenERP.
38
39
40Check and Repair tools:
41
42- Check the Chart of Accounts for problems in its structure. This will allow
43 you to detect incoherences like the ones caused by bugs like
44 https://bugs.launchpad.net/openobject-server/+bug/581137
45 (the preordered tree [parent_left/parent_right] not matching the
46 parent-child structure [parent_id]).
47
48- Revalidate confirmed account moves so their analytic lines are regenerated.
49 This may be used to fix the data after bugs like
50 https://bugs.launchpad.net/openobject-addons/+bug/582988
51 The wizard also lets you find account moves missing their analytic lines.
52
53- Set the receivable/payable account of the partners, in moves and invoices
54 where a generic receivable/payable account was used instead.
55
56- Set the parent reference in account move lines where the receivable/payable
57 account associated with the partner was used, but a partner reference wasn't
58 set. This may fix cases where the receivable/payable amounts displayed in the
59 partner form does not match the balance of the receivable/payable accounts.
60
61- Set the reference in account moves, associated with invoices, that do not
62 have the right reference (the reference from the invoice if it was a supplier
63 invoice, or the number from the invoice if it was a customer invoice).
64 This is useful to fix the account moves after changing the invoice
65 references.
66 """,
67 "depends": [
68 'base',
69 'account',
70 ],
71 "init_xml": [],
72 "demo_xml": [],
73 "update_xml": [
74 'admin_tools_menu.xml',
75 'account_importer.xml',
76 'account_move_importer.xml',
77 'account_chart_checker.xml',
78 'revalidate_moves.xml',
79 'move_partner_account.xml',
80 'set_partner_in_moves.xml',
81 'set_invoice_ref_in_moves.xml',
82 ],
83 "installable": True,
84 'active': False
85
86}
087
=== added file 'account_admin_tools/account_chart_checker.py'
--- account_admin_tools/account_chart_checker.py 1970-01-01 00:00:00 +0000
+++ account_admin_tools/account_chart_checker.py 2012-12-07 10:00:37 +0000
@@ -0,0 +1,211 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved
6# $Id$
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as published
10# by the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22"""
23Account Chart Checker Wizard
24"""
25__author__ = "Borja López Soilán (Pexego)"
26
27import re
28from osv import fields, osv
29from tools.translate import _
30
31
32class account_chart_checker_problem(osv.osv_memory):
33 """
34 A problem found in the account chart
35 """
36 _name = "account_admin_tools.account_chart_checker_problem"
37 _description = "Account Chart Problem"
38
39 _columns = {
40 'wizard_id': fields.many2one('account_admin_tools.account_chart_checker',
41 'Wizard', required=True, readonly=True),
42 'account_id': fields.many2one('account.account', 'Account', required=True,
43 readonly=True),
44 'severity': fields.selection([('informative', 'Informative'),
45 ('low', 'Low'), ('medium', 'Medium'), ('high', 'High')],
46 'Severity', readonly=True),
47 'problem': fields.selection([
48 ('not_parent_of_children', 'Not parent of its children'),
49 ('not_children_of_parent', 'Not children of its parent'),
50 ], 'Problem', readonly=True),
51 'description': fields.text('Description')
52 }
53
54 def search(self, cr, uid, args, offset=0, limit=None, order=None,
55 context=None, count=False):
56 """
57 Redefinition of the search method (as osv_memory wizards currently
58 don't support domain filters by themselves.
59 """
60 problem_ids = super(account_chart_checker_problem, self).search(cr,
61 uid, args, offset=offset, limit=limit, order=order,
62 context=context, count=count)
63
64 for arg in args:
65 if arg[0] == 'wizard_id' and arg[1] == '=':
66 wizard_id = arg[2]
67 problems = self.browse(cr, uid, problem_ids, context=context)
68 problem_ids = [problem.id for problem in problems
69 if problem.wizard_id.id == wizard_id]
70
71 return problem_ids
72
73
74account_chart_checker_problem()
75
76
77class account_chart_checker(osv.osv_memory):
78 """
79 Account Chart Checker
80 """
81 _name = "account_admin_tools.account_chart_checker"
82 _description = "Account Chart Checker Wizard"
83
84 _columns = {
85 'company_id': fields.many2one('res.company', 'Company',
86 required=True, readonly=True),
87 'problem_ids': fields.one2many('account_admin_tools.account_chart_checker_problem',
88 'wizard_id', 'Problems'),
89 'state': fields.selection([('new', 'New'), ('done', 'Done')],
90 'Status', readonly=True),
91 }
92
93 _defaults = {
94 'state': lambda *a: 'new',
95 'company_id': lambda self, cr, uid, context:\
96 self.pool.get('res.users').browse(cr, uid, uid,\
97 context).company_id.id,
98 }
99
100 def action_check(self, cr, uid, ids, context=None):
101 """
102 Checks the account chart and reports the problems it finds.
103 """
104 for wiz in self.browse(cr, uid, ids, context):
105 problems = []
106
107 account_ids = self.pool.get('account.account').search(cr,
108 uid, [], context=context)
109
110 for account in self.pool.get('account.account').browse(cr,
111 uid, account_ids, context=context):
112 self._check_parent_of_children(cr, uid, account, problems)
113 self._check_child_of_parent(cr, uid, account, problems)
114
115 self.write(cr, uid, [wiz.id], {
116 'problem_ids': [(0, 0, problem)
117 for problem in problems] or None,
118 'state': 'done'
119 })
120
121 #
122 # Return the next view: Show the problems
123 #
124 model_data_ids = self.pool.get('ir.model.data').search(cr, uid, [
125 ('model', '=', 'ir.ui.view'),
126 ('module', '=', 'account_admin_tools'),
127 ('name', '=', 'account_chart_checker_problem_tree')
128 ])
129 resource_id = self.pool.get('ir.model.data').read(cr, uid,
130 model_data_ids, fields=['res_id'], context=context)[0]['res_id']
131
132 return {
133 'name': _("Problems Found in the Chart of Accounts"),
134 'type': 'ir.actions.act_window',
135 'res_model': 'account_admin_tools.account_chart_checker_problem',
136 'view_type': 'form',
137 'view_mode': 'tree',
138 'views': [(resource_id, 'tree')],
139 'domain': "[('wizard_id', '=', %s)]" % wiz.id,
140 'context': context,
141 }
142
143 def _check_parent_of_children(self, cr, uid, account, problems=[],
144 context=None):
145 """
146 Checks that for a parent account, every children has that account
147 as its parent.
148 """
149 query = """
150 SELECT id FROM account_account WHERE parent_left > %s
151 and parent_right < %s AND COALESCE(parent_id,0) NOT IN
152 (SELECT id FROM account_account WHERE parent_left > %s
153 and parent_right < %s) AND COALESCE(parent_id,0) != %s
154 """
155 cr.execute(query, (account.parent_left or 0, account.parent_right
156 or 0, account.parent_left or 0, account.parent_right or 0,
157 account.id))
158 problematic_ids = filter(None, map(lambda x: x[0], cr.fetchall()))
159
160 for child in self.pool.get('account.account').browse(cr, uid,
161 problematic_ids, context=context):
162 problems.append({
163 'problem': 'not_parent_of_children',
164 'severity': 'high',
165 'account_id': account.id,
166 'description': _('The account %d (%s) is listed as \
167 children of %d (%s) in the preordered tree, \
168 but its parent is %d (%s)')
169 % (child.id, child.code, account.id,
170 account.code, child.parent_id and
171 child.parent_id.id, child.parent_id
172 and child.parent_id.code)
173 })
174
175 def _check_child_of_parent(self, cr, uid, account, problems=[],
176 context=None):
177 """
178 Checks that for a child account, his parent has that account
179 in its children.
180 """
181 query = """
182 SELECT id FROM account_account WHERE parent_left < %s
183 and parent_right > %s
184 """
185 cr.execute(query, (account.parent_left or 0, account.parent_right
186 or 0))
187 parent_ids = filter(None, map(lambda x: x[0], cr.fetchall()))
188
189 if account.parent_id and (account.parent_id.id not in parent_ids):
190 problems.append({
191 'problem': 'not_children_of_parent',
192 'severity': 'high',
193 'account_id': account.id,
194 'description': _('The account %d (%s) is children of %d\
195 (%s), but is not listed as its children on\
196 the preordered tree')
197 % (account.id, account.code,
198 account.parent_id.id, account.parent_id.code)
199 })
200 elif parent_ids and not account.parent_id:
201 problems.append({
202 'problem': 'not_children_of_parent',
203 'severity': 'high',
204 'account_id': account.id,
205 'description': _('The account %d (%s) is a top level account,\
206 but is listed as a child on the preordered tree')
207 % (account.id, account.code)
208 })
209
210
211account_chart_checker()
0212
=== added file 'account_admin_tools/account_chart_checker.xml'
--- account_admin_tools/account_chart_checker.xml 1970-01-01 00:00:00 +0000
+++ account_admin_tools/account_chart_checker.xml 2012-12-07 10:00:37 +0000
@@ -0,0 +1,54 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4 <record id="view_account_chart_checker_form" model="ir.ui.view">
5 <field name="name">account_chart_checker.form</field>
6 <field name="model">account_admin_tools.account_chart_checker</field>
7 <field name="type">form</field>
8 <field name="arch" type="xml">
9 <form string="Check the Chart of Accounts">
10 <label string="This wizard will search for problems in the Chart of Accounts:" colspan="4"/>
11 <label string="" colspan="4"/>
12 <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"/>
13 <label string="" colspan="4"/>
14 <label string="A list with the problems found (if any) will be shown afterwards." colspan="4"/>
15 <label string="" colspan="4"/>
16 <group colspan="4">
17 <button string="Cancel" special="cancel" icon="gtk-cancel" states="new"/>
18 <button string="Check" name="action_check" type="object" icon="gtk-apply" states="new"/>
19 </group>
20
21 <field name="state" invisible="1"/>
22 </form>
23 </field>
24 </record>
25
26 <record id="account_chart_checker_problem_tree" model="ir.ui.view">
27 <field name="name">account_chart_checker_problem.tree</field>
28 <field name="model">account_admin_tools.account_chart_checker_problem</field>
29 <field name="type">tree</field>
30 <field name="arch" type="xml">
31 <tree string="Problems" colors="red:severity=='high';green:severity=='informative'">
32 <field name="problem"/>
33 <field name="severity"/>
34 <field name="account_id"/>
35 <field name="description"/>
36 </tree>
37 </field>
38 </record>
39
40 <record id="action_account_chart_checker" model="ir.actions.act_window">
41 <field name="name">Check the Chart of Accounts</field>
42 <field name="res_model">account_admin_tools.account_chart_checker</field>
43 <field name="view_type">form</field>
44 <field name="view_mode">form</field>
45 <field name="view_id" ref="view_account_chart_checker_form"/>
46 <field name="target">new</field>
47 </record>
48 <menuitem id="menu_action_account_chart_checker"
49 parent="menu_action_account_admin_tools_repair"
50 action="action_account_chart_checker"
51 sequence="10"/>
52
53 </data>
54</openerp>
055
=== added file 'account_admin_tools/account_importer.py'
--- account_admin_tools/account_importer.py 1970-01-01 00:00:00 +0000
+++ account_admin_tools/account_importer.py 2012-12-07 10:00:37 +0000
@@ -0,0 +1,261 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved
6# $Id$
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as published
10# by the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22"""
23Account Importer
24"""
25__author__ = "Borja López Soilán (Pexego)"
26
27import logging
28import time
29import csv
30import base64
31import StringIO
32import re
33from osv import fields, osv
34from tools.translate import _
35
36
37class account_importer(osv.osv_memory):
38 """
39 Account Importer
40
41 Creates accounts from a CSV file.
42
43 The CSV file lines are expected to have at least the code and name of the
44 account.
45
46 The wizard will find the account brothers (or parent) having the same
47 account code sufix, and will autocomplete the rest of the account
48 parameters (account type, reconcile, parent account...).
49
50 The CSV file lines are tested to be valid account lines using the regular
51 expresion options of the wizard.
52 """
53 _name = "account_admin_tools.account_importer"
54 _description = "Account importation wizard"
55
56 _columns = {
57 #
58 # Account move parameters
59 #
60 'company_id': fields.many2one('res.company', 'Company', required=True),
61 'overwrite': fields.boolean('Overwrite', help="If the account already\
62 exists, overwrite its name?"),
63 #
64 # Input file
65 #
66 'input_file': fields.binary('File', filters="*.csv", required=True),
67 'input_file_name': fields.char('File name', size=256),
68 'csv_delimiter': fields.char('Delimiter', size=1, required=True),
69 'csv_quotechar': fields.char('Quote', size=1, required=True),
70 'csv_code_index': fields.integer('Code field', required=True),
71 'csv_code_regexp': fields.char('Code regexp', size=32, required=True),
72 'csv_name_index': fields.integer('Name field', required=True),
73 'csv_name_regexp': fields.char('Name regexp', size=32, required=True),
74 }
75
76 _defaults = {
77 'company_id': lambda self, cr, uid, context:\
78 self.pool.get('res.users').browse(cr, uid, uid,\
79 context).company_id.id,
80 'csv_delimiter': lambda *a: ';',
81 'csv_quotechar': lambda *a: '"',
82 'csv_code_index': lambda *a: 0,
83 'csv_name_index': lambda *a: 1,
84 'csv_code_regexp': lambda *a: r'^[0-9]+$',
85 'csv_name_regexp': lambda *a: r'^.*$',
86 }
87
88 def _find_parent_account_id(self, cr, uid, wiz, account_code, context=None):
89 """
90 Finds the parent account given an account code.
91 It will remove the last digit of the code until it finds an
92 account that matches exactly the code.
93 """
94 if len(account_code) > 0:
95 parent_account_code = account_code[:-1]
96 while len(parent_account_code) > 0:
97 account_ids = self.pool.get('account.account').search(cr,\
98 uid, [('code', '=', parent_account_code),
99 ('company_id', '=', wiz.company_id.id)
100 ])
101 if account_ids and len(account_ids) > 0:
102 return account_ids[0]
103 parent_account_code = parent_account_code[:-1]
104 # No parent found
105 return None
106
107 def _find_brother_account_id(self, cr, uid, wiz, account_code, context=None):
108 """
109 Finds a brother account given an account code.
110 It will remove the last digit of the code until it finds an
111 account that matches the begin of the code.
112 """
113 if len(account_code) > 0:
114 brother_account_code = account_code[:-1]
115 while len(brother_account_code) > 0:
116 account_ids = self.pool.get('account.account').search(cr,\
117 uid, [
118 ('code', '=like', brother_account_code + '%%'),
119 ('company_id', '=', wiz.company_id.id)
120 ])
121 if account_ids and len(account_ids) > 0:
122 return account_ids[0]
123 brother_account_code = brother_account_code[:-1]
124 # No brother found
125 return None
126
127 def action_import(self, cr, uid, ids, context=None):
128 """
129 Imports the accounts from the CSV file using the options from the
130 wizard.
131 """
132 # List of the imported accounts
133 imported_account_ids = []
134
135 logger = logging.getLogger("account_importer")
136 for wiz in self.browse(cr, uid, ids, context):
137 if not wiz.input_file:
138 raise osv.except_osv(_('UserError'),\
139 _("You need to select a file!"))
140
141 # Decode the file data
142 data = base64.b64decode(wiz.input_file)
143
144 #
145 # Read the file
146 #
147 reader = csv.reader(StringIO.StringIO(data),
148 delimiter=str(wiz.csv_delimiter),
149 quotechar=str(wiz.csv_quotechar))
150
151 for record in reader:
152 # Ignore short records
153 if len(record) > wiz.csv_code_index \
154 and len(record) > wiz.csv_name_index:
155
156 record_code = record[wiz.csv_code_index]
157 record_name = record[wiz.csv_name_index]
158
159 #
160 # Ignore invalid records
161 #
162 if re.match(wiz.csv_code_regexp, record_code) \
163 and re.match(wiz.csv_name_regexp, record_name):
164
165 #
166 # Search for the account
167 #
168 account_ids = self.pool.get('account.account').search(cr,\
169 uid, [
170 ('code', '=', record_code),
171 ('company_id', '=', wiz.company_id.id)
172 ])
173 if account_ids:
174 if wiz.overwrite:
175 logger.debug("Overwriting \
176 account: %s %s" % (record_code,\
177 record_name))
178 self.pool.get('account.account').write(cr,\
179 uid, account_ids, {
180 'name': record_name
181 })
182 imported_account_ids.extend(account_ids)
183 else:
184 #
185 # Find the account's parent
186 #
187 parent_account_id = self._find_parent_account_id(cr,\
188 uid, wiz, record_code)
189
190 if not parent_account_id:
191 logger.warning("Couldn't find a\
192 parent account for: %s" % record_code)
193
194 #
195 # Find the account's brother
196 # (will be used as template)
197 #
198 brother_account_id = self._find_brother_account_id(cr,\
199 uid, wiz, record_code)
200
201 if not brother_account_id:
202 logger.warning("Couldn't find a\
203 brother account for: %s" % record_code)
204
205 brother_account = self.pool.get('account.account').browse(cr, \
206 uid, brother_account_id)
207
208 #
209 # Create the new account
210 #
211 logger.debug("Creating new account:\
212 %s %s" % (record_code, record_name))
213 account_id = self.pool.get('account.account').create(cr,\
214 uid, {
215 'code': record_code,
216 'name': record_name,
217 'parent_id': parent_account_id,
218 'type': brother_account.type,
219 'user_type': brother_account.user_type.id,
220 'reconcile': brother_account.reconcile,
221 'company_id': wiz.company_id.id,
222 'currency_id': brother_account.currency_id.id,
223 'currency_mode': brother_account.currency_mode,
224 'active': 1,
225 'tax_ids': [(6, 0, [tax.id for \
226 tax in brother_account.tax_ids])],
227 'note': False,
228 })
229
230 imported_account_ids.append(account_id)
231 else:
232 logger.warning("Invalid record format\
233 (ignoring line): %s" % repr(record))
234 else:
235 logger.warning("Too short record \
236 (ignoring line): %s" % repr(record))
237
238 #
239 # Show the accounts to the user
240 #
241 model_data_ids = self.pool.get('ir.model.data').search(cr, uid, [
242 ('model', '=', 'ir.ui.view'),
243 ('module', '=', 'account'),
244 ('name', '=', 'view_account_form')
245 ])
246 resource_id = self.pool.get('ir.model.data').read(cr, uid,\
247 model_data_ids, fields=['res_id'], context=context)[0]['res_id']
248
249 return {
250 'name': _("Imported accounts"),
251 'type': 'ir.actions.act_window',
252 'res_model': 'account.account',
253 'view_type': 'form',
254 'view_mode': 'tree,form',
255 'views': [(False, 'tree'), (resource_id, 'form')],
256 'domain': "[('id', 'in', %s)]" % imported_account_ids,
257 'context': context,
258 }
259
260
261account_importer()
0262
=== added file 'account_admin_tools/account_importer.xml'
--- account_admin_tools/account_importer.xml 1970-01-01 00:00:00 +0000
+++ account_admin_tools/account_importer.xml 2012-12-07 10:00:37 +0000
@@ -0,0 +1,63 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4
5 <record id="view_account_importer_form" model="ir.ui.view">
6 <field name="name">account_importer.form</field>
7 <field name="model">account_admin_tools.account_importer</field>
8 <field name="type">form</field>
9 <field name="arch" type="xml">
10 <form string="Account importer">
11 <label string="This wizard will import accounts from a CSV file." colspan="4"/>
12 <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"/>
13 <label string="" colspan="4"/>
14 <newline/>
15 <group string="Account parameters" colspan="4">
16 <label string="Select the parameters for the account"/>
17 <group colspan="4">
18 <field name="company_id"/>
19 <field name="overwrite"/>
20 </group>
21 </group>
22 <group string="Input file" colspan="4">
23 <label string="Select the CSV file with the lines for the account move"/>
24 <group colspan="4">
25 <field name="input_file_name" string="File"/>
26 <field name="input_file" filename="input_file_name" nolabel="1"/>
27 <group colspan="2">
28 <separator string="File format" colspan="4"/>
29 <field name="csv_delimiter"/>
30 <field name="csv_quotechar"/>
31 </group>
32 <group colspan="2">
33 <separator string="Record format" colspan="4"/>
34 <field name="csv_code_index"/>
35 <field name="csv_code_regexp"/>
36 <field name="csv_name_index"/>
37 <field name="csv_name_regexp"/>
38 </group>
39 </group>
40 </group>
41 <group colspan="4">
42 <button string="Cancel" special="cancel" icon="gtk-cancel"/>
43 <button string="Import" name="action_import" type="object" icon="gtk-ok"/>
44 </group>
45 </form>
46 </field>
47 </record>
48
49 <record id="action_account_importer" model="ir.actions.act_window">
50 <field name="name">Import Accounts from CSV</field>
51 <field name="res_model">account_admin_tools.account_importer</field>
52 <field name="view_type">form</field>
53 <field name="view_mode">form</field>
54 <field name="view_id" ref="view_account_importer_form"/>
55 <field name="target">new</field>
56 </record>
57 <menuitem id="menu_action_account_importer"
58 parent="menu_action_account_admin_tools_import"
59 action="action_account_importer"
60 sequence="10"/>
61
62 </data>
63</openerp>
064
=== added file 'account_admin_tools/account_importer_wizard.py'
--- account_admin_tools/account_importer_wizard.py 1970-01-01 00:00:00 +0000
+++ account_admin_tools/account_importer_wizard.py 2012-12-07 10:00:37 +0000
@@ -0,0 +1,268 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved
6# $Id$
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as published
10# by the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22"""
23Account Importer
24"""
25__author__ = "Borja López Soilán (Pexego)"
26
27import time
28import csv
29import base64
30import StringIO
31import logging
32import re
33from osv import fields, osv
34from tools.translate import _
35
36
37class account_importer_wizard(osv.osv_memory):
38 """
39 Account Importer
40
41 Creates accounts from a CSV file.
42
43 The CSV file lines are expected to have at least the code and name of the
44 account.
45
46 The wizard will find the account brothers (or parent) having the same
47 account code sufix, and will autocomplete the rest of the account
48 parameters (account type, reconcile, parent account...).
49
50 The CSV file lines are tested to be valid account lines using the regular
51 expresion options of the wizard.
52 """
53 _name = "account_importer_wizard"
54 _description = "Account importation wizard"
55
56 _columns = {
57 #
58 # Account move parameters
59 #
60 'company_id': fields.many2one('res.company', 'Company', required=True),
61 'overwrite': fields.boolean('Overwrite',
62 help="If the account already\ exists, overwrite its name?"),
63 #
64 # Input file
65 #
66 'input_file': fields.binary('File', filters="*.csv", required=True),
67 'input_file_name': fields.char('File name', size=256),
68 'csv_delimiter': fields.char('Delimiter', size=1, required=True),
69 'csv_quotechar': fields.char('Quote', size=1, required=True),
70 'csv_code_index': fields.integer('Code field', required=True),
71 'csv_code_regexp': fields.char('Code regexp', size=32, required=True),
72 'csv_name_index': fields.integer('Name field', required=True),
73 'csv_name_regexp': fields.char('Name regexp', size=32, required=True),
74
75 }
76
77 _defaults = {
78 'company_id': lambda self, cr, uid, context:
79 self.pool.get('res.users').browse(cr, uid,
80 uid, context).company_id.id,
81 'csv_delimiter': lambda *a: ';',
82 'csv_quotechar': lambda *a: '"',
83 'csv_code_index': lambda *a: 0,
84 'csv_name_index': lambda *a: 1,
85 'csv_code_regexp': lambda *a: r'^[0-9]+$',
86 'csv_name_regexp': lambda *a: r'^.*$',
87 }
88
89 def _find_parent_account_id(self, cr, uid, wiz, account_code, context=None):
90 """
91 Finds the parent account given an account code.
92 It will remove the last digit of the code until it finds an account that
93 matches exactly the code.
94 """
95 if len(account_code) > 0:
96 parent_account_code = account_code[:-1]
97 while len(parent_account_code) > 0:
98 account_ids = self.pool.get('account.account').search(cr, uid, [
99 ('code', '=', parent_account_code),
100 ('company_id', '=', wiz.company_id.id)
101 ])
102 if account_ids and len(account_ids) > 0:
103 return account_ids[0]
104 parent_account_code = parent_account_code[:-1]
105 # No parent found
106 return None
107
108 def _find_brother_account_id(self, cr, uid, wiz, account_code, context=None):
109 """
110 Finds a brother account given an account code.
111 It will remove the last digit of the code until it finds
112 an account that matches the begin of the code.
113 """
114 if len(account_code) > 0:
115 brother_account_code = account_code[:-1]
116 while len(brother_account_code) > 0:
117 account_ids = self.pool.get('account.account').search(cr,
118 uid, [
119 ('code', '=like', brother_account_code + '%%'),
120 ('company_id',
121 '=', wiz.company_id.id)
122 ])
123 if account_ids and len(account_ids) > 0:
124 return account_ids[0]
125 brother_account_code = brother_account_code[:-1]
126 # No brother found
127 return None
128
129 def action_import(self, cr, uid, ids, context=None):
130 """
131 Imports the accounts from the CSV file using the options from the
132 wizard.
133 """
134 # List of the imported accounts
135 imported_account_ids = []
136
137 logger = logging.getLogger("account_importer")
138 for wiz in self.browse(cr, uid, ids, context):
139 if not wiz.input_file:
140 raise osv.except_osv(_('UserError'),
141 _("You need to select a file!"))
142
143 # Decode the file data
144 data = base64.b64decode(wiz.input_file)
145
146 #
147 # Read the file
148 #
149 reader = csv.reader(StringIO.StringIO(data),
150 delimiter=str(wiz.csv_delimiter),
151 quotechar=str(wiz.csv_quotechar))
152
153 for record in reader:
154 # Ignore short records
155 if len(record) > wiz.csv_code_index \
156 and len(record) > wiz.csv_name_index:
157
158 record_code = record[wiz.csv_code_index]
159 record_name = record[wiz.csv_name_index]
160
161 #
162 # Ignore invalid records
163 #
164 if re.match(wiz.csv_code_regexp, record_code) \
165 and re.match(wiz.csv_name_regexp, record_name):
166
167 #
168 # Search for the account
169 #
170 account_ids = self.pool.get(
171 'account.account').search(cr,
172 uid, [
173 ('code',
174 '=', record_code),
175 ('company_id',
176 '=', wiz.company_id.id)
177 ])
178 if account_ids:
179 if wiz.overwrite:
180 logger.debug("Overwriting account: %s %s"
181 % (record_code, record_name))
182 self.pool.get('account.account').write(cr,
183 uid, account_ids, {
184 'name': record_name
185 })
186 imported_account_ids.extend(account_ids)
187 else:
188 #
189 # Find the account's parent
190 #
191 parent_account_id = self._find_parent_account_id(
192 cr,
193 uid, wiz, record_code)
194
195 if not parent_account_id:
196 logger.warning("Couldn't find a parent\
197 account for: %s" % record_code)
198
199 #
200 # Find the account's brother (will be used as template)
201 #
202 brother_account_id = self._find_brother_account_id(cr,
203 uid, wiz, record_code)
204
205 if not brother_account_id:
206 logger.warning("Couldn't find a\
207 brother account for: %s" % record_code)
208
209 brother_account = self.pool.get(
210 'account.account').browse(cr,
211 uid, brother_account_id)
212
213 #
214 # Create the new account
215 #
216 logger.debug("Creating new account:\
217 %s %s" % (record_code, record_name))
218 account_id = self.pool.get(
219 'account.account').create(cr,
220 uid, {
221 'code': record_code,
222 'name': record_name,
223 'parent_id': parent_account_id,
224 'type': brother_account.type,
225 'user_type': brother_account.user_type.id,
226 'reconcile': brother_account.reconcile,
227 'company_id': wiz.company_id.id,
228 'currency_id': brother_account.currency_id.id,
229 'currency_mode': brother_account.currency_mode,
230 'check_history': brother_account.check_history,
231 'active': 1,
232 'tax_ids': [(6, 0, [tax.id for tax
233 in brother_account.tax_ids])],
234 'note': False,
235 })
236
237 imported_account_ids.append(account_id)
238 else:
239 logger.warning("Invalid record format \
240 (ignoring line): %s" % repr(record))
241 else:
242 logger.warning("Too short record \
243 (ignoring line): %s" % repr(record))
244
245 #
246 # Show the accounts to the user
247 #
248 model_data_ids = self.pool.get('ir.model.data').search(cr, uid,
249 [
250 ('model', '=', 'ir.ui.view'),
251 ('module', '=', 'account'),
252 ('name', '=', 'view_account_form')
253 ])
254 resource_id = self.pool.get('ir.model.data').read(cr, uid,
255 model_data_ids, fields=['res_id'])[0]['res_id']
256
257 return {
258 'name': _("Imported accounts"),
259 'type': 'ir.actions.act_window',
260 'res_model': 'account.account',
261 'view_type': 'form',
262 'view_mode': 'tree, form',
263 'views': [(False, 'tree'), (resource_id, 'form')],
264 'domain': "[('id', 'in', %s)]" % imported_account_ids,
265 }
266
267
268account_importer_wizard()
0269
=== added file 'account_admin_tools/account_importer_wizard.xml'
--- account_admin_tools/account_importer_wizard.xml 1970-01-01 00:00:00 +0000
+++ account_admin_tools/account_importer_wizard.xml 2012-12-07 10:00:37 +0000
@@ -0,0 +1,62 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4
5 <record id="view_account_importer_wizard_form" model="ir.ui.view">
6 <field name="name">account_importer_wizard.form</field>
7 <field name="model">account_importer_wizard</field>
8 <field name="type">form</field>
9 <field name="arch" type="xml">
10 <form string="Account importer">
11 <label string="This wizard will import accounts from a CSV file." colspan="4"/>
12 <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"/>
13 <label string=""/>
14 <newline/>
15 <group string="Account parameters" colspan="4">
16 <label string="Select the parameters for the account"/>
17 <group colspan="4">
18 <field name="company_id"/>
19 <field name="overwrite"/>
20 </group>
21 </group>
22 <group string="Input file" colspan="4">
23 <label string="Select the CSV file with the lines for the account move"/>
24 <group colspan="4">
25 <field name="input_file_name" string="File"/>
26 <field name="input_file" filename="input_file_name" nolabel="1"/>
27 <group colspan="2">
28 <separator string="File format" colspan="4"/>
29 <field name="csv_delimiter"/>
30 <field name="csv_quotechar"/>
31 </group>
32 <group colspan="2">
33 <separator string="Record format" colspan="4"/>
34 <field name="csv_code_index"/>
35 <field name="csv_code_regexp"/>
36 <field name="csv_name_index"/>
37 <field name="csv_name_regexp"/>
38 </group>
39 </group>
40 </group>
41 <group colspan="4">
42 <button string="Cancel" special="cancel" icon="gtk-cancel"/>
43 <button string="Import" name="action_import" type="object" icon="gtk-ok"/>
44 </group>
45 </form>
46 </field>
47 </record>
48
49 <record id="action_account_importer_wizard" model="ir.actions.act_window">
50 <field name="name">Import Accounts from CSV</field>
51 <field name="res_model">account_importer_wizard</field>
52 <field name="view_type">form</field>
53 <field name="view_mode">form</field>
54 <field name="target">new</field>
55 </record>
56 <menuitem id="menu_action_account_importer_wizard"
57 parent="menu_action_account_admin_tools"
58 action="action_account_importer_wizard"
59 sequence="10"/>
60
61 </data>
62</openerp>
063
=== added file 'account_admin_tools/account_move_importer.py'
--- account_admin_tools/account_move_importer.py 1970-01-01 00:00:00 +0000
+++ account_admin_tools/account_move_importer.py 2012-12-07 10:00:37 +0000
@@ -0,0 +1,300 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved
6# $Id$
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as published
10# by the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22"""
23Account Move Importer
24"""
25__author__ = "Borja López Soilán (Pexego)"
26
27import time
28import logging
29import csv
30import base64
31import StringIO
32import re
33from osv import fields, osv
34from tools.translate import _
35
36
37class account_move_importer(osv.osv_memory):
38 """
39 Account Move Importer
40
41 Wizard that imports a CSV file into a new account move.
42
43 The CSV file is expected to have at least the account code, a reference
44 (description of the move line), the debit and the credit.
45
46 The lines of the CSV file are tested to be valid account move lines
47 using the regular expresions set on the wizard.
48 """
49 _name = "account_admin_tools.account_move_importer"
50 _description = "Account move importation wizard"
51
52 _columns = {
53 #
54 # Account move parameters
55 #
56 'company_id': fields.many2one('res.company', 'Company',
57 required=True),
58 'ref': fields.char('Ref', size=64, required=True),
59 'period_id': fields.many2one('account.period', 'Period',
60 required=True),
61 'journal_id': fields.many2one('account.journal', 'Journal',
62 required=True),
63 'date': fields.date('Date', required=True),
64 'type': fields.selection([
65 ('pay_voucher', 'Cash Payment'),
66 ('bank_pay_voucher', 'Bank Payment'),
67 ('rec_voucher', 'Cash Receipt'),
68 ('bank_rec_voucher', 'Bank Receipt'),
69 ('cont_voucher', 'Contra'),
70 ('journal_sale_vou', 'Journal Sale'),
71 ('journal_pur_voucher', 'Journal Purchase'),
72 ('journal_voucher', 'Journal Voucher'),
73 ], 'Type', select=True, required=True),
74 #
75 # Input file
76 #
77 'input_file': fields.binary('File', filters="*.csv", required=True),
78 'input_file_name': fields.char('File name', size=256),
79 'csv_delimiter': fields.char('Delimiter', size=1, required=True),
80 'csv_quotechar': fields.char('Quote', size=1, required=True),
81 'csv_decimal_separator': fields.char('Decimal sep.', size=1,
82 required=True),
83 'csv_thousands_separator': fields.char('Thousands sep.', size=1,
84 required=True),
85 'csv_code_index': fields.integer('Code field', required=True),
86 'csv_code_regexp': fields.char('Code regexp', size=32, required=True),
87 'csv_ref_index': fields.integer('Ref field', required=True),
88 'csv_ref_regexp': fields.char('Ref regexp', size=32, required=True),
89 'csv_debit_index': fields.integer('Debit field', required=True),
90 'csv_debit_regexp': fields.char('Debit regexp', size=32,
91 required=True),
92 'csv_credit_index': fields.integer('Credit field', required=True),
93 'csv_credit_regexp': fields.char('Credit regexp', size=32,
94 required=True),
95 }
96
97 def _get_default_period_id(self, cr, uid, context=None):
98 """
99 Returns the default period to use (based on account.move)
100 """
101 period_ids = self.pool.get('account.period').find(cr, uid)
102 return period_ids and period_ids[0] or False
103
104 _defaults = {
105 'company_id': lambda self, cr, uid, context:
106 self.pool.get('res.users').browse(cr, uid, uid,
107 context).company_id.id,
108 'period_id': _get_default_period_id,
109 'date': lambda *a: time.strftime('%Y-%m-%d'),
110 'type': lambda *a: 'journal_voucher', # Based on account move
111 'csv_delimiter': lambda *a: ';',
112 'csv_quotechar': lambda *a: '"',
113 'csv_decimal_separator': lambda *a: '.',
114 'csv_thousands_separator': lambda *a: ',',
115 'csv_code_index': lambda *a: 0,
116 'csv_ref_index': lambda *a: 1,
117 'csv_debit_index': lambda *a: 2,
118 'csv_credit_index': lambda *a: 3,
119 'csv_code_regexp': lambda *a: r'^[0-9]+$',
120 'csv_ref_regexp': lambda *a: r'^.*$',
121 'csv_debit_regexp': lambda *a: r'^[0-9\-\.\,]*$',
122 'csv_credit_regexp': lambda *a: r'^[0-9\-\.\,]*$',
123 }
124
125 def _get_accounts_map(self, cr, uid, context=None):
126 """
127 Find the receivable/payable accounts that are associated with
128 a single partner and return a (account.id, partner.id) map
129 """
130 partner_ids = self.pool.get('res.partner').search(cr, uid, [],
131 context=context)
132 accounts_map = {}
133 for partner in self.pool.get('res.partner').browse(cr, uid,
134 partner_ids, context=context):
135 #
136 # Add the receivable account to the map
137 #
138 if accounts_map.get(partner.property_account_receivable.id,
139 None) is None:
140 accounts_map[
141 partner.property_account_receivable.id] = partner.id
142 else:
143 # Two partners with the same receivable account: ignore
144 # this account!
145 accounts_map[partner.property_account_receivable.id] = 0
146 #
147 # Add the payable account to the map
148 #
149 if accounts_map.get(partner.property_account_payable.id,
150 None) is None:
151 accounts_map[partner.property_account_payable.id] = partner.id
152 else:
153 # Two partners with the same receivable account: ignore
154 # this account!
155 accounts_map[partner.property_account_payable.id] = 0
156 return accounts_map
157
158 def action_import(self, cr, uid, ids, context=None):
159 """
160 Imports a CSV file into a new account move using the options from
161 the wizard.
162 """
163 accounts_map = self._get_accounts_map(cr, uid, context=context)
164 logger = logging.getLogger("account_move_importer")
165
166 for wiz in self.browse(cr, uid, ids, context=context):
167 if not wiz.input_file:
168 raise osv.except_osv(_('UserError'),
169 _("You need to select a file!"))
170
171 account_move_data = self.pool.get('account.move').default_get(cr,
172 uid, ['state', 'name'])
173 account_move_data.update({
174 'ref': wiz.ref,
175 'journal_id': wiz.journal_id.id,
176 'period_id': wiz.period_id.id,
177 'date': wiz.date,
178 'type': wiz.type,
179 'line_id': [],
180 'partner_id': False,
181 'to_check': 0
182 })
183
184 lines_data = account_move_data['line_id']
185
186 # Decode the file data
187 data = base64.b64decode(wiz.input_file)
188
189 #
190 # Read the file
191 #
192 reader = csv.reader(StringIO.StringIO(data),
193 delimiter=str(wiz.csv_delimiter),
194 quotechar=str(wiz.csv_quotechar))
195
196 for record in reader:
197 # Ignore short records
198 if len(record) > wiz.csv_code_index \
199 and len(record) > wiz.csv_ref_index \
200 and len(record) > wiz.csv_debit_index \
201 and len(record) > wiz.csv_credit_index:
202
203 record_code = record[wiz.csv_code_index]
204 record_ref = record[wiz.csv_ref_index]
205 record_debit = record[wiz.csv_debit_index]
206 record_credit = record[wiz.csv_credit_index]
207
208 #
209 # Ignore invalid records
210 #
211 if re.match(wiz.csv_code_regexp, record_code) \
212 and re.match(wiz.csv_ref_regexp, record_ref) \
213 and re.match(wiz.csv_debit_regexp, record_debit) \
214 and re.match(wiz.csv_credit_regexp, record_credit):
215 #
216 # Clean the input amounts
217 #
218 record_debit = float(record_debit.replace(wiz.csv_thousands_separator, '').replace(wiz.csv_decimal_separator, '.'))
219 record_credit = float(record_credit.replace(wiz.csv_thousands_separator, '').replace(wiz.csv_decimal_separator, '.'))
220
221 #
222 # Find the account (or fail!)
223 #
224 account_ids = self.pool.get(
225 'account.account').search(cr,
226 uid, [
227 ('code',
228 '=', record_code),
229 ('company_id',
230 '=', wiz.company_id.id)
231 ])
232 if not account_ids:
233 raise osv.except_osv(_('Error'), _("Account\
234 not found: %s!") % record_code)
235
236 #
237 # Prepare the line data
238 #
239 line_data = {
240 'account_id': account_ids[0],
241 'debit': 0.0,
242 'credit': 0.0,
243 'name': record_ref,
244 'ref': False,
245 'currency_id': False,
246 'tax_amount': False,
247 'partner_id': accounts_map.get(account_ids[0])
248 or False,
249 'tax_code_id': False,
250 'date_maturity': False,
251 'amount_currency': False,
252 'analytic_account_id': False,
253 }
254
255 #
256 # Create a debit line + a credit line if needed
257 #
258 line_data_debit = line_data.copy()
259 line_data_credit = line_data
260 if record_debit != 0.0:
261 line_data_debit['debit'] = record_debit
262 lines_data.append((0, 0, line_data_debit))
263 if record_credit != 0.0:
264 line_data_credit['credit'] = record_credit
265 lines_data.append((0, 0, line_data_credit))
266 else:
267 logger.warning("Invalid record format\
268 (ignoring line): %s" % repr(record))
269 else:
270 logger.warning("Too short record\
271 (ignoring line): %s" % repr(record))
272
273 # Finally create the move
274 move_id = self.pool.get('account.move').create(cr, uid,
275 account_move_data)
276
277 #
278 # Show the move to the user
279 #
280 model_data_ids = self.pool.get('ir.model.data').search(cr, uid, [
281 ('model', '=', 'ir.ui.view'),
282 ('module', '=', 'account'),
283 ('name', '=', 'view_move_form')
284 ])
285 resource_id = self.pool.get('ir.model.data').read(cr, uid,
286 model_data_ids, fields=['res_id'], context=context)[0]['res_id']
287
288 return {
289 'name': _("Imported account moves"),
290 'type': 'ir.actions.act_window',
291 'res_model': 'account.move',
292 'view_type': 'form',
293 'view_mode': 'form, tree',
294 #'view_id': (resource_id, 'View'),
295 'views': [(False, 'tree'), (resource_id, 'form')],
296 'domain': "[('id', '=', %s)]" % move_id,
297 'context': context,
298 }
299
300account_move_importer()
0301
=== added file 'account_admin_tools/account_move_importer.xml'
--- account_admin_tools/account_move_importer.xml 1970-01-01 00:00:00 +0000
+++ account_admin_tools/account_move_importer.xml 2012-12-07 10:00:37 +0000
@@ -0,0 +1,75 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4
5 <record id="view_account_move_importer_form" model="ir.ui.view">
6 <field name="name">account_move_importer.form</field>
7 <field name="model">account_admin_tools.account_move_importer</field>
8 <field name="type">form</field>
9 <field name="arch" type="xml">
10 <form string="Account move importer">
11 <label string="This wizard will import one account move from a CSV file." colspan="4"/>
12 <label string="Note: It will fail if any of the accounts do not exist in OpenERP." colspan="4"/>
13 <label string="" colspan="4"/>
14 <newline/>
15 <group string="Account move parameters" colspan="4">
16 <label string="Select the parameters for the account move"/>
17 <group colspan="4">
18 <field name="ref"/>
19 <field name="company_id"/>
20 <newline/>
21 <field name="journal_id"/>
22 <field name="type"/>
23 <newline/>
24 <field name="period_id"/>
25 <field name="date"/>
26 </group>
27 </group>
28 <group string="Input file" colspan="4">
29 <label string="Select the CSV file with the lines for the account move"/>
30 <group colspan="4">
31 <field name="input_file_name" string="File"/>
32 <field name="input_file" filename="input_file_name" nolabel="1"/>
33 <group colspan="2">
34 <separator string="File format" colspan="4"/>
35 <field name="csv_delimiter"/>
36 <field name="csv_quotechar"/>
37 <field name="csv_thousands_separator"/>
38 <field name="csv_decimal_separator"/>
39 </group>
40 <group colspan="2">
41 <separator string="Record format" colspan="4"/>
42 <field name="csv_code_index"/>
43 <field name="csv_code_regexp"/>
44 <field name="csv_ref_index"/>
45 <field name="csv_ref_regexp"/>
46 <field name="csv_debit_index"/>
47 <field name="csv_debit_regexp"/>
48 <field name="csv_credit_index"/>
49 <field name="csv_credit_regexp"/>
50 </group>
51 </group>
52 </group>
53 <group colspan="4">
54 <button string="Cancel" special="cancel" icon="gtk-cancel"/>
55 <button string="Import" name="action_import" type="object" icon="gtk-ok"/>
56 </group>
57 </form>
58 </field>
59 </record>
60
61 <record id="action_account_move_importer" model="ir.actions.act_window">
62 <field name="name">Import Account Move from CSV</field>
63 <field name="res_model">account_admin_tools.account_move_importer</field>
64 <field name="view_type">form</field>
65 <field name="view_mode">form</field>
66 <field name="view_id" ref="view_account_move_importer_form"/>
67 <field name="target">new</field>
68 </record>
69 <menuitem id="menu_action_account_move_importer"
70 parent="menu_action_account_admin_tools_import"
71 action="action_account_move_importer"
72 sequence="20"/>
73
74 </data>
75</openerp>
076
=== added file 'account_admin_tools/account_move_importer_wizard.py'
--- account_admin_tools/account_move_importer_wizard.py 1970-01-01 00:00:00 +0000
+++ account_admin_tools/account_move_importer_wizard.py 2012-12-07 10:00:37 +0000
@@ -0,0 +1,266 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved
6# $Id$
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as published
10# by the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22"""
23Account Move Importer
24"""
25__author__ = "Borja López Soilán (Pexego)"
26
27import time
28import csv
29import base64
30import logging
31import StringIO
32import re
33from osv import fields, osv
34from tools.translate import _
35
36
37class account_move_importer_wizard(osv.osv_memory):
38 """
39 Account Move Importer
40
41 Wizard that imports a CSV file into a new account move.
42
43 The CSV file is expected to have at least the account code, a reference
44 (description of the move line), the debit and the credit.
45
46 The lines of the CSV file are tested to be valid account move lines
47 using the regular expresions set on the wizard.
48 """
49 _name = "account_move_importer_wizard"
50 _description = "Account move importation wizard"
51
52 _columns = {
53 #
54 # Account move parameters
55 #
56 'company_id': fields.many2one('res.company', 'Company',
57 required=True),
58 'ref': fields.char('Ref', size=64, required=True),
59 'period_id': fields.many2one('account.period', 'Period',
60 required=True),
61 'journal_id': fields.many2one('account.journal', 'Journal',
62 required=True),
63 'date': fields.date('Date', required=True),
64
65 'type': fields.selection([
66 ('pay_voucher', 'Cash Payment'),
67 ('bank_pay_voucher', 'Bank Payment'),
68 ('rec_voucher', 'Cash Receipt'),
69 ('bank_rec_voucher', 'Bank Receipt'),
70 ('cont_voucher', 'Contra'),
71 ('journal_sale_vou', 'Journal Sale'),
72 ('journal_pur_voucher', 'Journal Purchase'),
73 ('journal_voucher', 'Journal Voucher'),
74 ], 'Type', select=True, required=True),
75 #
76 # Input file
77 #
78 'input_file': fields.binary('File', filters="*.csv",
79 required=True),
80 'input_file_name': fields.char('File name', size=256),
81 'csv_delimiter': fields.char('Delimiter', size=1,
82 required=True),
83 'csv_quotechar': fields.char('Quote', size=1, required=True),
84 'csv_decimal_separator': fields.char('Decimal sep.', size=1,
85 required=True),
86 'csv_thousands_separator': fields.char('Thousands sep.', size=1,
87 required=True),
88 'csv_code_index': fields.integer('Code field', required=True),
89 'csv_code_regexp': fields.char('Code regexp', size=32, required=True),
90 'csv_ref_index': fields.integer('Ref field', required=True),
91 'csv_ref_regexp': fields.char('Ref regexp', size=32, required=True),
92 'csv_debit_index': fields.integer('Debit field', required=True),
93 'csv_debit_regexp': fields.char('Debit regexp', size=32,
94 required=True),
95 'csv_credit_index': fields.integer('Credit field', required=True),
96 'csv_credit_regexp': fields.char('Credit regexp', size=32,
97 required=True),
98 }
99
100 def _get_default_period_id(self, cr, uid, context=None):
101 """
102 Returns the default period to use (based on account.move)
103 """
104 period_ids = self.pool.get('account.period').find(cr, uid)
105 return period_ids and period_ids[0] or False
106
107 _defaults = {
108 'company_id': lambda self, cr, uid, context:
109 self.pool.get('res.users').browse(cr, uid, uid,
110 context).company_id.id,
111 'period_id': _get_default_period_id,
112 'date': lambda *a: time.strftime('%Y-%m-%d'),
113 'type': lambda *a: 'journal_voucher', # Based on account move
114 'csv_delimiter': lambda *a: ';',
115 'csv_quotechar': lambda *a: '"',
116 'csv_decimal_separator': lambda *a: '.',
117 'csv_thousands_separator': lambda *a: ',',
118 'csv_code_index': lambda *a: 0,
119 'csv_ref_index': lambda *a: 1,
120 'csv_debit_index': lambda *a: 2,
121 'csv_credit_index': lambda *a: 3,
122 'csv_code_regexp': lambda *a: r'^[0-9]+$',
123 'csv_ref_regexp': lambda *a: r'^.*$',
124 'csv_debit_regexp': lambda *a: r'^[0-9\-\.\,]*$',
125 'csv_credit_regexp': lambda *a: r'^[0-9\-\.\,]*$',
126 }
127
128 def action_import(self, cr, uid, ids, context=None):
129 """
130 Imports a CSV file into a new account move using the options from
131 the wizard.
132 """
133 logger = logging.getLogger("account_move_importer")
134 for wiz in self.browse(cr, uid, ids, context):
135 if not wiz.input_file:
136 raise osv.except_osv(_('UserError'),
137 _("You need to select a file!"))
138
139 account_move_data = self.pool.get('account.move').default_get(cr,
140 uid, ['state', 'name'])
141 account_move_data.update({
142 'ref': wiz.ref,
143 'journal_id': wiz.journal_id.id,
144 'period_id': wiz.period_id.id,
145 'date': wiz.date,
146 'type': wiz.type,
147 'line_id': [],
148 'partner_id': False,
149 'to_check': 0
150 })
151
152 lines_data = account_move_data['line_id']
153
154 # Decode the file data
155 data = base64.b64decode(wiz.input_file)
156
157 #
158 # Read the file
159 #
160 reader = csv.reader(StringIO.StringIO(data),
161 delimiter=str(wiz.csv_delimiter),
162 quotechar=str(wiz.csv_quotechar))
163
164 for record in reader:
165 # Ignore short records
166 if len(record) > wiz.csv_code_index \
167 and len(record) > wiz.csv_ref_index \
168 and len(record) > wiz.csv_debit_index \
169 and len(record) > wiz.csv_credit_index:
170
171 record_code = record[wiz.csv_code_index]
172 record_ref = record[wiz.csv_ref_index]
173 record_debit = record[wiz.csv_debit_index]
174 record_credit = record[wiz.csv_credit_index]
175
176 #
177 # Ignore invalid records
178 #
179 if re.match(wiz.csv_code_regexp, record_code) \
180 and re.match(wiz.csv_ref_regexp, record_ref) \
181 and re.match(wiz.csv_debit_regexp, record_debit) \
182 and re.match(wiz.csv_credit_regexp, record_credit):
183 #
184 # Clean the input amounts
185 #
186 record_debit = float(record_debit.replace(wiz.csv_thousands_separator, '').replace(wiz.csv_decimal_separator, '.'))
187 record_credit = float(record_credit.replace(wiz.csv_thousands_separator, '').replace(wiz.csv_decimal_separator, '.'))
188
189 #
190 # Find the account (or fail!)
191 #
192 account_ids = self.pool.get(
193 'account.account').search(cr,
194 uid, [
195 ('code',
196 '=', record_code),
197 ('company_id',
198 '=', wiz.company_id.id)
199 ])
200 if not account_ids:
201 raise osv.except_osv(_('Error'),
202 _("Account not found: %s!") % record_code)
203
204 #
205 # Prepare the line data
206 #
207 line_data = {
208 'account_id': account_ids[0],
209 'debit': 0.0,
210 'credit': 0.0,
211 'name': record_ref,
212 'ref': False,
213 'currency_id': False,
214 'tax_amount': False,
215 'partner_id': False,
216 'tax_code_id': False,
217 'date_maturity': False,
218 'amount_currency': False,
219 'analytic_account_id': False,
220 }
221
222 #
223 # Create a debit line + a credit line if needed
224 #
225 line_data_debit = line_data.copy()
226 line_data_credit = line_data
227 if record_debit != 0.0:
228 line_data_debit['debit'] = record_debit
229 lines_data.append((0, 0, line_data_debit))
230 if record_credit != 0.0:
231 line_data_credit['credit'] = record_credit
232 lines_data.append((0, 0, line_data_credit))
233 else:
234 logger.warning("Invalid record format\
235 (ignoring line): %s" % repr(record))
236 else:
237 logger.warning("Too short record\
238 (ignoring line): %s" % repr(record))
239
240 # Finally create the move
241 move_id = self.pool.get('account.move').create(cr, uid,
242 account_move_data)
243
244 #
245 # Show the move to the user
246 #
247 model_data_ids = self.pool.get('ir.model.data').search(cr, uid, [
248 ('model', '=', 'ir.ui.view'),
249 ('module', '=', 'account'),
250 ('name', '=', 'view_move_form')
251 ])
252 resource_id = self.pool.get('ir.model.data').read(cr, uid,
253 model_data_ids, fields=['res_id'])[0]['res_id']
254
255 return {
256 'name': _("Imported account moves"),
257 'type': 'ir.actions.act_window',
258 'res_model': 'account.move',
259 'view_type': 'form',
260 'view_mode': 'form,tree',
261 #'view_id': (resource_id, 'View'),
262 'views': [(False, 'tree'), (resource_id, 'form')],
263 'domain': "[('id', '=', %s)]" % move_id,
264 }
265
266account_move_importer_wizard()
0267
=== added file 'account_admin_tools/account_move_importer_wizard.xml'
--- account_admin_tools/account_move_importer_wizard.xml 1970-01-01 00:00:00 +0000
+++ account_admin_tools/account_move_importer_wizard.xml 2012-12-07 10:00:37 +0000
@@ -0,0 +1,74 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4
5 <record id="view_account_move_importer_wizard_form" model="ir.ui.view">
6 <field name="name">account_move_importer_wizard.form</field>
7 <field name="model">account_move_importer_wizard</field>
8 <field name="type">form</field>
9 <field name="arch" type="xml">
10 <form string="Account move importer">
11 <label string="This wizard will import one account move from a CSV file." colspan="4"/>
12 <label string="Note: It will fail if any of the accounts do not exist in OpenERP." colspan="4"/>
13 <label string=""/>
14 <newline/>
15 <group string="Account move parameters" colspan="4">
16 <label string="Select the parameters for the account move"/>
17 <group colspan="4">
18 <field name="ref"/>
19 <field name="company_id"/>
20 <newline/>
21 <field name="journal_id"/>
22 <field name="type"/>
23 <newline/>
24 <field name="period_id"/>
25 <field name="date"/>
26 </group>
27 </group>
28 <group string="Input file" colspan="4">
29 <label string="Select the CSV file with the lines for the account move"/>
30 <group colspan="4">
31 <field name="input_file_name" string="File"/>
32 <field name="input_file" filename="input_file_name" nolabel="1"/>
33 <group colspan="2">
34 <separator string="File format" colspan="4"/>
35 <field name="csv_delimiter"/>
36 <field name="csv_quotechar"/>
37 <field name="csv_thousands_separator"/>
38 <field name="csv_decimal_separator"/>
39 </group>
40 <group colspan="2">
41 <separator string="Record format" colspan="4"/>
42 <field name="csv_code_index"/>
43 <field name="csv_code_regexp"/>
44 <field name="csv_ref_index"/>
45 <field name="csv_ref_regexp"/>
46 <field name="csv_debit_index"/>
47 <field name="csv_debit_regexp"/>
48 <field name="csv_credit_index"/>
49 <field name="csv_credit_regexp"/>
50 </group>
51 </group>
52 </group>
53 <group colspan="4">
54 <button string="Cancel" special="cancel" icon="gtk-cancel"/>
55 <button string="Import" name="action_import" type="object" icon="gtk-ok"/>
56 </group>
57 </form>
58 </field>
59 </record>
60
61 <record id="action_account_move_importer_wizard" model="ir.actions.act_window">
62 <field name="name">Import Account Move from CSV</field>
63 <field name="res_model">account_move_importer_wizard</field>
64 <field name="view_type">form</field>
65 <field name="view_mode">form</field>
66 <field name="target">new</field>
67 </record>
68 <menuitem id="menu_action_account_move_importer_wizard"
69 parent="menu_action_account_admin_tools"
70 action="action_account_move_importer_wizard"
71 sequence="20"/>
72
73 </data>
74</openerp>
075
=== added file 'account_admin_tools/admin_tools_menu.xml'
--- account_admin_tools/admin_tools_menu.xml 1970-01-01 00:00:00 +0000
+++ account_admin_tools/admin_tools_menu.xml 2012-12-07 10:00:37 +0000
@@ -0,0 +1,20 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4
5 <menuitem id="menu_action_account_admin_tools"
6 name="Admin Tools"
7 parent="account.menu_finance"
8 sequence="999"/>
9
10 <menuitem id="menu_action_account_admin_tools_import"
11 name="Import"
12 parent="menu_action_account_admin_tools"
13 sequence="10"/>
14
15 <menuitem id="menu_action_account_admin_tools_repair"
16 name="Check and Repair"
17 parent="menu_action_account_admin_tools"
18 sequence="20"/>
19 </data>
20</openerp>
021
=== added directory 'account_admin_tools/i18n'
=== added file 'account_admin_tools/move_partner_account.py'
--- account_admin_tools/move_partner_account.py 1970-01-01 00:00:00 +0000
+++ account_admin_tools/move_partner_account.py 2012-12-07 10:00:37 +0000
@@ -0,0 +1,188 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved
6# $Id$
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as published
10# by the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22"""
23Move Partner Account Wizard
24
25Checks that the account moves use the partner account instead of a
26generic account.
27"""
28__author__ = "Borja López Soilán (Pexego)"
29
30import re
31from osv import fields, osv
32from tools.translate import _
33
34
35class move_partner_account(osv.osv_memory):
36 """
37 Move Partner Account Wizard
38
39 Checks that the account moves use the partner account instead of a
40 generic account.
41 """
42 _name = "account_admin_tools.move_partner_account"
43 _description = "Move Partner Account Wizard"
44
45 _columns = {
46 'state': fields.selection([('new', 'New'), ('done', 'Done')],
47 'Status', readonly=True),
48 #
49 # Account move parameters
50 #
51 'company_id': fields.many2one('res.company', 'Company',
52 required=True, readonly=True),
53 'period_ids': fields.many2many('account.period',
54 'move_partner_account_period_rel', 'wizard_id',
55 'period_id', "Periods"),
56 'account_payable_id': fields.many2one('account.account',
57 'Account Payable', required=True),
58 'account_receivable_id': fields.many2one('account.account',
59 'Account Receivable', required=True),
60 }
61
62 def _get_payable_account_id(self, cr, uid, context=None):
63 """
64 Gets the default payable (supplier) account property value for the
65 current (user's) company.
66 """
67 if context is None:
68 context = {}
69 company_id = self.pool.get('res.users').browse(cr, uid, uid,
70 context).company_id.id
71 res = None
72 property_ids = self.pool.get('ir.property').search(cr, uid, [
73 '|',
74 ('company_id', '=', company_id),
75 ('company_id', '=', False),
76 ('name', '=', 'property_account_payable'),
77 ('res_id', '=', False)
78 ])
79 if property_ids:
80 property = self.pool.get(
81 'ir.property').browse(cr, uid, property_ids[0])
82 if property:
83 try:
84 # OpenERP 5.0 and 5.2/6.0 revno <= 2236
85 res = int(property.value.split(',')[1])
86 except AttributeError:
87 # OpenERP 6.0 revno >= 2236
88 res = property.value_reference.id
89 return res
90
91 def _get_receivable_account_id(self, cr, uid, context=None):
92 """
93 Gets the default receivable (customer) account property value for the
94 current (user's) company.
95 """
96 company_id = self.pool.get('res.users').browse(cr, uid, uid,
97 context).company_id.id
98 res = None
99 property_ids = self.pool.get('ir.property').search(cr, uid, [
100 '|',
101 ('company_id', '=', company_id),
102 ('company_id', '=', False),
103 ('name', '=', 'property_account_receivable'),
104 ('res_id', '=', False)
105 ])
106 if property_ids:
107 property = self.pool.get('ir.property').browse(cr, uid,
108 property_ids[0])
109 if property:
110 try:
111 # OpenERP 5.0 and 5.2/6.0 revno <= 2236
112 res = int(property.value.split(',')[1])
113 except AttributeError:
114 # OpenERP 6.0 revno >= 2236
115 res = property.value_reference.id
116 return res
117
118 _defaults = {
119 'company_id': lambda self, cr, uid, context:
120 self.pool.get('res.users').browse(cr, uid, uid,
121 context).company_id.id,
122 'account_payable_id': _get_payable_account_id,
123 'account_receivable_id': _get_receivable_account_id,
124 }
125
126 def action_set_partner_accounts_in_moves(self, cr, uid, ids, context=None):
127 """
128 Action that searchs for the account moves that do not use the partner
129 account, but the generic payable/receivable one, and sets the partner
130 account instead.
131 """
132 for wiz in self.browse(cr, uid, ids, context):
133 period_ids = [period.id for period in wiz.period_ids]
134 query_acc = """
135 UPDATE account_move_line
136 SET account_id=%s
137 WHERE partner_id=%s
138 AND account_id=%s
139 """
140 query_inv = """
141 UPDATE account_invoice
142 SET account_id=%s
143 WHERE partner_id=%s
144 AND account_id=%s
145 """
146 periods_str = ','.join(map(str, period_ids))
147 if period_ids:
148 query_acc += """ AND period_id IN (%s)""" % periods_str
149 query_inv += """ AND period_id IN (%s)""" % periods_str
150
151 partner_ids = self.pool.get('res.partner').search(cr, uid, [])
152 for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids):
153 # Receivable account
154 if partner.property_account_receivable.id != wiz.account_receivable_id.id:
155 cr.execute(query_acc % (partner.property_account_receivable.id, partner.id, wiz.account_receivable_id.id))
156 cr.execute(query_inv % (partner.property_account_receivable.id, partner.id, wiz.account_receivable_id.id))
157 # Payable account
158 if partner.property_account_payable.id != wiz.account_payable_id.id:
159 cr.execute(query_acc % (partner.property_account_payable.id, partner.id, wiz.account_payable_id.id))
160 cr.execute(query_inv % (partner.property_account_payable.id, partner.id, wiz.account_payable_id.id))
161
162 # Update the wizard status
163 self.write(cr, uid, [wiz.id], {'state': 'done'})
164
165 #
166 # Return the next view: Show 'done' view
167 #
168 model_data_ids = self.pool.get('ir.model.data').search(cr, uid, [
169 ('model', '=', 'ir.ui.view'),
170 ('module', '=', 'account_admin_tools'),
171 ('name', '=', 'view_move_partner_account_done_form')
172 ])
173 resource_id = self.pool.get('ir.model.data').read(cr, uid, model_data_ids, fields=['res_id'], context=context)[0]['res_id']
174
175 return {
176 'name': _("Set Partner Accounts in Moves"),
177 'type': 'ir.actions.act_window',
178 'res_model': 'account_admin_tools.move_partner_account',
179 'view_type': 'form',
180 'view_mode': 'form',
181 'views': [(resource_id, 'form')],
182 'domain': "[('id', 'in', %s)]" % ids,
183 'context': context,
184 'target': 'new',
185 }
186
187
188move_partner_account()
0189
=== added file 'account_admin_tools/move_partner_account.xml'
--- account_admin_tools/move_partner_account.xml 1970-01-01 00:00:00 +0000
+++ account_admin_tools/move_partner_account.xml 2012-12-07 10:00:37 +0000
@@ -0,0 +1,58 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4 <record id="view_move_partner_account_form" model="ir.ui.view">
5 <field name="name">move_partner_account.form</field>
6 <field name="model">account_admin_tools.move_partner_account</field>
7 <field name="type">form</field>
8 <field name="arch" type="xml">
9 <form string="Set Partner Accounts in Moves">
10 <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"/>
11 <label string="" colspan="4"/>
12 <newline/>
13 <group string="Parameters" colspan="4">
14 <separator string="Accounts to replace with partner accounts" colspan="4"/>
15 <field name="account_receivable_id"/>
16 <field name="account_payable_id"/>
17 <newline/>
18 <separator string="Filter for the moves to update" colspan="4"/>
19 <field name="period_ids" colspan="4"/>
20 </group>
21 <group colspan="4">
22 <button string="Cancel" special="cancel" icon="gtk-cancel"/>
23 <button string="Set partner accounts" name="action_set_partner_accounts_in_moves" type="object" icon="gtk-apply"/>
24 </group>
25 </form>
26 </field>
27 </record>
28
29 <record id="view_move_partner_account_done_form" model="ir.ui.view">
30 <field name="name">move_partner_account.done.form</field>
31 <field name="model">account_admin_tools.move_partner_account</field>
32 <field name="type">form</field>
33 <field name="arch" type="xml">
34 <form string="Set Partner Accounts in Moves">
35 <label string="The partner's receivable/payable accounts have been set succesfuly on the account moves!" colspan="4"/>
36 <label string="" colspan="4"/>
37 <group colspan="4">
38 <button string="Done" special="cancel" icon="gtk-ok"/>
39 </group>
40 </form>
41 </field>
42 </record>
43
44 <record id="action_move_partner_account" model="ir.actions.act_window">
45 <field name="name">Set Partner Accounts in Moves</field>
46 <field name="res_model">account_admin_tools.move_partner_account</field>
47 <field name="view_type">form</field>
48 <field name="view_mode">form</field>
49 <field name="view_id" ref="view_move_partner_account_form"/>
50 <field name="target">new</field>
51 </record>
52 <menuitem id="menu_action_move_partner_account"
53 parent="menu_action_account_admin_tools_repair"
54 action="action_move_partner_account"
55 sequence="110"/>
56
57 </data>
58</openerp>
059
=== added file 'account_admin_tools/move_partner_account_wizard.py'
--- account_admin_tools/move_partner_account_wizard.py 1970-01-01 00:00:00 +0000
+++ account_admin_tools/move_partner_account_wizard.py 2012-12-07 10:00:37 +0000
@@ -0,0 +1,145 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved
6# $Id$
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as published
10# by the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22"""
23Move Partner Account Wizard
24
25Checks that the account moves use the partner account instead of a
26generic account.
27"""
28__author__ = "Borja López Soilán (Pexego)"
29
30import re
31from osv import fields, osv
32from tools.translate import _
33
34
35class move_partner_account_wizard(osv.osv_memory):
36 """
37 Move Partner Account Wizard
38
39 Checks that the account moves use the partner account instead of a
40 generic account.
41 """
42 _name = "move_partner_account_wizard"
43 _description = "Move Partner Account Wizard"
44
45 _columns = {
46 #
47 # Account move parameters
48 #
49 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True),
50
51 'period_ids': fields.many2many('account.period', 'move_partner_account_wizard_period_rel', 'wizard_id', 'period_id', "Periods"),
52
53 'account_payable_id': fields.many2one('account.account', 'Account Payable', required=True),
54 'account_receivable_id': fields.many2one('account.account', 'Account Receivable', required=True),
55 }
56
57 def _get_payable_account_id(self, cr, uid, context=None):
58 if context is None:
59 context = {}
60 company_id = self.pool.get(
61 'res.users').browse(cr, uid, uid, context).company_id.id
62 res = None
63 property_ids = self.pool.get('ir.property').search(cr, uid, [
64 '|',
65 ('company_id', '=', company_id),
66 ('company_id', '=', False),
67 ('name', '=', 'property_account_payable'),
68 ('res_id', '=', False)
69 ])
70 if property_ids:
71 property = self.pool.get(
72 'ir.property').browse(cr, uid, property_ids[0])
73 if property:
74 try:
75 # OpenERP 5.0 and 5.2/6.0 revno <= 2236
76 res = int(property.value.split(',')[1])
77 except AttributeError:
78 # OpenERP 6.0 revno >= 2236
79 res = property.value_reference.id
80 return res
81
82 def _get_receivable_account_id(self, cr, uid, context=None):
83 company_id = self.pool.get(
84 'res.users').browse(cr, uid, uid, context).company_id.id
85 res = None
86 property_ids = self.pool.get('ir.property').search(cr, uid, [
87 '|',
88 ('company_id', '=', company_id),
89 ('company_id', '=', False),
90 ('name', '=', 'property_account_receivable'),
91 ('res_id', '=', False)
92 ])
93 if property_ids:
94 property = self.pool.get(
95 'ir.property').browse(cr, uid, property_ids[0])
96 if property:
97 try:
98 # OpenERP 5.0 and 5.2/6.0 revno <= 2236
99 res = int(property.value.split(',')[1])
100 except AttributeError:
101 # OpenERP 6.0 revno >= 2236
102 res = property.value_reference.id
103 return res
104
105 _defaults = {
106 'company_id': lambda self, cr, uid, context: self.pool.get('res.users').browse(cr, uid, uid, context).company_id.id,
107 'account_payable_id': _get_payable_account_id,
108 'account_receivable_id': _get_receivable_account_id,
109 }
110
111 def action_set_partner_accounts_in_moves(self, cr, uid, ids, context=None):
112 for wiz in self.browse(cr, uid, ids, context):
113 period_ids = [period.id for period in wiz.period_ids]
114 query_acc = """
115 UPDATE account_move_line
116 SET account_id=%s
117 WHERE partner_id=%s
118 AND account_id=%s
119 """
120 query_inv = """
121 UPDATE account_invoice
122 SET account_id=%s
123 WHERE partner_id=%s
124 AND account_id=%s
125 """
126 periods_str = ','.join(map(str, period_ids))
127 if period_ids:
128 query_acc += """ AND period_id IN (%s)""" % periods_str
129 query_inv += """ AND period_id IN (%s)""" % periods_str
130
131 partner_ids = self.pool.get('res.partner').search(cr, uid, [])
132 for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids):
133 # Receivable account
134 if partner.property_account_receivable.id != wiz.account_receivable_id.id:
135 cr.execute(query_acc % (partner.property_account_receivable.id, partner.id, wiz.account_receivable_id.id))
136 cr.execute(query_inv % (partner.property_account_receivable.id, partner.id, wiz.account_receivable_id.id))
137 # Payable account
138 if partner.property_account_payable.id != wiz.account_payable_id.id:
139 cr.execute(query_acc % (partner.property_account_payable.id, partner.id, wiz.account_payable_id.id))
140 cr.execute(query_inv % (partner.property_account_payable.id, partner.id, wiz.account_payable_id.id))
141
142 return {}
143
144
145move_partner_account_wizard()
0146
=== added file 'account_admin_tools/move_partner_account_wizard.xml'
--- account_admin_tools/move_partner_account_wizard.xml 1970-01-01 00:00:00 +0000
+++ account_admin_tools/move_partner_account_wizard.xml 2012-12-07 10:00:37 +0000
@@ -0,0 +1,42 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4 <record id="view_move_partner_account_wizard_form" model="ir.ui.view">
5 <field name="name">move_partner_account_wizard.form</field>
6 <field name="model">move_partner_account_wizard</field>
7 <field name="type">form</field>
8 <field name="arch" type="xml">
9 <form string="Set partner account in moves">
10 <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"/>
11 <label string=""/>
12 <newline/>
13 <group string="Parameters" colspan="4">
14 <separator string="Accounts to replace with partner accounts" colspan="4"/>
15 <field name="account_receivable_id"/>
16 <field name="account_payable_id"/>
17 <newline/>
18 <separator string="Filter for the moves to update" colspan="4"/>
19 <field name="period_ids" colspan="4"/>
20 </group>
21 <group colspan="4">
22 <button string="Cancel" special="cancel" icon="gtk-cancel"/>
23 <button string="Set partner accounts" name="action_set_partner_accounts_in_moves" type="object" icon="gtk-ok"/>
24 </group>
25 </form>
26 </field>
27 </record>
28
29 <record id="action_move_partner_account_wizard" model="ir.actions.act_window">
30 <field name="name">Set Partner Account in Moves</field>
31 <field name="res_model">move_partner_account_wizard</field>
32 <field name="view_type">form</field>
33 <field name="view_mode">form</field>
34 <field name="target">new</field>
35 </record>
36 <menuitem id="menu_action_move_partner_account_wizard"
37 parent="menu_action_account_admin_tools"
38 action="action_move_partner_account_wizard"
39 sequence="20"/>
40
41 </data>
42</openerp>
043
=== added file 'account_admin_tools/revalidate_moves.py'
--- account_admin_tools/revalidate_moves.py 1970-01-01 00:00:00 +0000
+++ account_admin_tools/revalidate_moves.py 2012-12-07 10:00:37 +0000
@@ -0,0 +1,157 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved
6# $Id$
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as published
10# by the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22"""
23Revalidate Account Moves Wizard
24"""
25__author__ = "Borja López Soilán (Pexego)"
26
27import re
28from osv import fields, osv
29from tools.translate import _
30
31
32class revalidate_moves(osv.osv_memory):
33 """
34 Revalidate Account Moves Wizard
35
36 Revalidates all the (already confirmed) moves, so their analytic lines
37 are recomputed (to fix the data after problems like this:
38 https://bugs.launchpad.net/openobject-addons/+bug/582988).
39 """
40 _name = "account_admin_tools.revalidate_moves"
41 _description = "Revalidate Account Moves Wizard"
42
43 _columns = {
44 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True),
45 'period_ids': fields.many2many('account.period', 'revalidate_moves_period_rel', 'wizard_id', 'period_id', "Periods"),
46 'move_ids': fields.many2many('account.move', 'revalidate_moves_moves_rel', 'wizard_id', 'move_id', 'Moves'),
47 'state': fields.selection([('new', 'New'), ('ready', 'Ready'), ('done', 'Done')], 'Status', readonly=True),
48 }
49
50 _defaults = {
51 'company_id': lambda self, cr, uid, context: self.pool.get('res.users').browse(cr, uid, uid, context).company_id.id,
52 'move_ids': lambda self, cr, uid, context: context and context.get('move_ids', None),
53 'period_ids': lambda self, cr, uid, context: context and context.get('period_ids', None),
54 'state': lambda self, cr, uid, context: context and context.get('state', 'new'),
55 }
56
57 def _next_view(self, cr, uid, ids, view_name, args=None, context=None):
58 """
59 Return the next view
60 """
61 if context is None:
62 context = {}
63 if args is None:
64 args = {}
65 ctx = context.copy()
66 ctx.update(args)
67
68 model_data_ids = self.pool.get('ir.model.data').search(cr, uid, [
69 ('model', '=', 'ir.ui.view'),
70 ('module', '=', 'account_admin_tools'),
71 ('name', '=', view_name)
72 ])
73 resource_id = self.pool.get('ir.model.data').read(cr, uid, model_data_ids, fields=['res_id'], context=context)[0]['res_id']
74 return {
75 'name': _("Revalidate Moves"),
76 'type': 'ir.actions.act_window',
77 'res_model': 'account_admin_tools.revalidate_moves',
78 'view_type': 'form',
79 'view_mode': 'form',
80 'views': [(resource_id, 'form')],
81 'domain': "[('id', 'in', %s)]" % ids,
82 'context': ctx,
83 'target': 'new',
84 }
85
86 def action_skip_new(self, cr, uid, ids, context=None):
87 """
88 Action that just skips the to the ready state
89 """
90 return self._next_view(cr, uid, ids, 'view_revalidate_moves_ready_form', {'state': 'ready'}, context)
91
92 def action_find_moves_missing_analytic_lines(self, cr, uid, ids, context=None):
93 """
94 Finds account moves with missing analytic lines and adds them
95 to the move_ids many2many field.
96 """
97 wiz = self.browse(cr, uid, ids[0], context)
98
99 # FIXME: The next block of code is a workaround to the lp:586252 bug of the 6.0 client.
100 # (https://bugs.launchpad.net/openobject-client/+bug/586252)
101 if wiz.period_ids:
102 period_ids = [period.id for period in wiz.period_ids]
103 else:
104 period_ids = context and context.get('period_ids')
105
106 query = """
107 SELECT account_move_line.move_id FROM account_move_line
108 LEFT JOIN account_analytic_line
109 ON account_analytic_line.move_id = account_move_line.id
110 WHERE account_move_line.analytic_account_id IS NOT NULL AND account_analytic_line.id IS NULL
111 """
112 if period_ids:
113 query += """ AND period_id IN %s"""
114 cr.execute(query, (tuple(period_ids),))
115 else:
116 cr.execute(query)
117
118 move_ids = filter(None, map(lambda x: x[0], cr.fetchall()))
119
120 #
121 # Return the next view: Show 'ready' view
122 #
123 args = {
124 'move_ids': move_ids,
125 'state': 'ready',
126 }
127 return self._next_view(cr, uid, ids, 'view_revalidate_moves_ready_form', args, context)
128
129 def action_revalidate_moves(self, cr, uid, ids, context=None):
130 """
131 Calls the validate method of the account moves for each move in the
132 move_ids many2many.
133 """
134 wiz = self.browse(cr, uid, ids[0], context)
135
136 # FIXME: The next block of code is a workaround to the lp:586252 bug of the 6.0 client.
137 # (https://bugs.launchpad.net/openobject-client/+bug/586252)
138 if wiz.move_ids:
139 move_ids = [line.id for line in wiz.move_ids]
140 else:
141 move_ids = context and context.get('move_ids')
142
143 for move in self.pool.get('account.move').browse(cr, uid, move_ids, context=context):
144 # We validate the moves one by one to prevent problems
145 self.pool.get('account.move').validate(cr, uid, [move.id], context)
146
147 #
148 # Return the next view: Show 'done' view
149 #
150 args = {
151 'move_ids': move_ids,
152 'state': 'done',
153 }
154 return self._next_view(cr, uid, ids, 'view_revalidate_moves_done_form', args, context)
155
156
157revalidate_moves()
0158
=== added file 'account_admin_tools/revalidate_moves.xml'
--- account_admin_tools/revalidate_moves.xml 1970-01-01 00:00:00 +0000
+++ account_admin_tools/revalidate_moves.xml 2012-12-07 10:00:37 +0000
@@ -0,0 +1,82 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4 <record id="view_revalidate_moves_form" model="ir.ui.view">
5 <field name="name">revalidate_moves.form</field>
6 <field name="model">account_admin_tools.revalidate_moves</field>
7 <field name="type">form</field>
8 <field name="arch" type="xml">
9 <form string="Revalidate Account Moves">
10 <label string="This wizard will revalidate the account moves, so their analytic lines are regenerated." colspan="4"/>
11 <label string="" colspan="4"/>
12 <group colspan="4">
13 <separator string="Find moves with missing analytic lines" colspan="4"/>
14 <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"/>
15 <label string="" colspan="4"/>
16 <newline/>
17 <field name="period_ids" colspan="4"/>
18 <label string="" colspan="4"/>
19 <newline/>
20 <button string="Search for moves" name="action_find_moves_missing_analytic_lines" type="object" icon="gtk-ok" colspan="4"/>
21 </group>
22 <label string="" colspan="4"/>
23 <group colspan="4">
24 <button string="Cancel" special="cancel" icon="gtk-cancel"/>
25 <button string="Skip" name="action_skip_new" type="object" icon="gtk-go-forward"/>
26 </group>
27 </form>
28 </field>
29 </record>
30
31 <record id="view_revalidate_moves_ready_form" model="ir.ui.view">
32 <field name="name">revalidate_moves.ready.form</field>
33 <field name="model">account_admin_tools.revalidate_moves</field>
34 <field name="type">form</field>
35 <field name="arch" type="xml">
36 <form string="Revalidate Account Moves">
37 <label string="The selected moves will be revalidated, that will regenerate their analytic lines." colspan="4"/>
38 <label string="" colspan="4"/>
39 <separator string="Account moves to revalidate" colspan="4"/>
40 <field name="move_ids" colspan="4" nolabel="1"/>
41 <label string="" colspan="4"/>
42 <group colspan="4">
43 <button string="Cancel" special="cancel" icon="gtk-cancel"/>
44 <button string="Revalidate selected moves" name="action_revalidate_moves" type="object" icon="gtk-apply"/>
45 </group>
46 </form>
47 </field>
48 </record>
49
50 <record id="view_revalidate_moves_done_form" model="ir.ui.view">
51 <field name="name">revalidate_moves.done.form</field>
52 <field name="model">account_admin_tools.revalidate_moves</field>
53 <field name="type">form</field>
54 <field name="arch" type="xml">
55 <form string="Revalidate Account Moves">
56 <label string="The moves have been revalidated sucessfully!" colspan="4"/>
57 <label string="" colspan="4"/>
58 <separator string="Revalidated account moves" colspan="4"/>
59 <field name="move_ids" colspan="4" nolabel="1" readonly="1"/>
60 <label string="" colspan="4"/>
61 <group colspan="4">
62 <button string="Done" special="cancel" icon="gtk-ok"/>
63 </group>
64 </form>
65 </field>
66 </record>
67
68 <record id="action_revalidate_moves" model="ir.actions.act_window">
69 <field name="name">Revalidate Account Moves (Regenerate Analytic Lines)</field>
70 <field name="res_model">account_admin_tools.revalidate_moves</field>
71 <field name="view_type">form</field>
72 <field name="view_mode">form</field>
73 <field name="view_id" ref="view_revalidate_moves_form"/>
74 <field name="target">new</field>
75 </record>
76 <menuitem id="menu_action_revalidate_moves"
77 parent="menu_action_account_admin_tools_repair"
78 action="action_revalidate_moves"
79 sequence="20"/>
80
81 </data>
82</openerp>
083
=== added file 'account_admin_tools/revalidate_moves_wizard.py'
--- account_admin_tools/revalidate_moves_wizard.py 1970-01-01 00:00:00 +0000
+++ account_admin_tools/revalidate_moves_wizard.py 2012-12-07 10:00:37 +0000
@@ -0,0 +1,100 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved
6# $Id$
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as published
10# by the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22"""
23Revalidate Account Moves Wizard
24"""
25__author__ = "Borja López Soilán (Pexego)"
26
27import re
28from osv import fields, osv
29from tools.translate import _
30
31
32class revalidate_moves_wizard(osv.osv_memory):
33 """
34 Revalidate Account Moves Wizard
35
36 Revalidates all the (already confirmed) moves, so their analytic lines
37 are recomputed (to fix the data after problems like this:
38 https://bugs.launchpad.net/openobject-addons/+bug/582988).
39 """
40 _name = "revalidate_moves_wizard"
41 _description = "Revalidate Account Moves Wizard"
42
43 _columns = {
44 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True),
45 'period_ids': fields.many2many('account.period', 'revalidate_moves_wizard_period_rel', 'wizard_id', 'period_id', "Periods"),
46 'move_ids': fields.many2many('account.move', 'revalidate_moves_wizard_moves_rel', 'wizard_id', 'move_id', 'Moves'),
47 'state': fields.selection([('new', 'New'), ('ready', 'Ready'), ('done', 'Done')], 'Status', readonly=True),
48 }
49
50 _defaults = {
51 'state': lambda *a: 'new',
52 }
53
54 def action_skip_new(self, cr, uid, ids, context=None):
55 """
56 Skips to the ready state.
57 """
58 for wiz in self.browse(cr, uid, ids, context):
59 self.write(cr, uid, [wiz.id], {'state': 'ready'})
60 return True
61
62 def action_find_moves_missing_analytic_lines(self, cr, uid, ids, context=None):
63 """
64 Finds account moves with missing analytic lines and adds them
65 to the move_ids many2many field.
66 """
67 for wiz in self.browse(cr, uid, ids, context):
68 period_ids = [period.id for period in wiz.period_ids]
69 query = """
70 SELECT account_move_line.move_id FROM account_move_line
71 LEFT JOIN account_analytic_line
72 ON account_analytic_line.move_id = account_move_line.id
73 WHERE account_move_line.analytic_account_id IS NOT NULL AND account_analytic_line.id IS NULL
74 """
75 periods_str = ','.join(map(str, period_ids))
76 if period_ids:
77 query += """ AND period_id IN (%s)""" % periods_str
78
79 cr.execute(query)
80 move_ids = filter(None, map(lambda x: x[0], cr.fetchall()))
81 self.write(cr, uid, [wiz.id], {
82 'move_ids': [(6, 0, move_ids)],
83 'state': 'ready'
84 })
85 return True
86
87 def action_revalidate_moves(self, cr, uid, ids, context=None):
88 """
89 Calls the validate method of the account moves for each move in the
90 move_ids many2many.
91 """
92 for wiz in self.browse(cr, uid, ids, context):
93 for move in wiz.move_ids:
94 # We validate the moves one by one to prevent problems
95 self.pool.get(
96 'account.move').validate(cr, uid, [move.id], context)
97 self.write(cr, uid, [wiz.id], {'state': 'done'})
98 return True
99
100revalidate_moves_wizard()
0101
=== added file 'account_admin_tools/revalidate_moves_wizard.xml'
--- account_admin_tools/revalidate_moves_wizard.xml 1970-01-01 00:00:00 +0000
+++ account_admin_tools/revalidate_moves_wizard.xml 2012-12-07 10:00:37 +0000
@@ -0,0 +1,45 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4 <record id="view_revalidate_moves_wizard_form" model="ir.ui.view">
5 <field name="name">revalidate_moves_wizard.form</field>
6 <field name="model">revalidate_moves_wizard</field>
7 <field name="type">form</field>
8 <field name="arch" type="xml">
9 <form string="Revalidate account moves">
10 <label string="This wizard will revalidate the given account moves, so their analytic lines are regenerated." colspan="4"/>
11 <label string=""/>
12 <newline/>
13 <group string="Moves to revalidate" attrs="{'invisible': [('state','!=','ready')]}">
14 <field name="move_ids" colspan="4" nolabel="1"/>
15 </group>
16 <group string="Find moves with missing analytic lines" colspan="4" attrs="{'invisible': [('state','!=','new')]}">
17 <button string="Search for moves" name="action_find_moves_missing_analytic_lines" type="object" icon="gtk-ok" states="new" colspan="4"/>
18 <separator string="Filter for the moves" colspan="4"/>
19 <field name="period_ids" colspan="4"/>
20 </group>
21 <group colspan="4">
22 <button string="Cancel" special="cancel" icon="gtk-cancel" states="new,ready"/>
23 <button string="Skip" name="action_skip_new" type="object" icon="gtk-go-forward" states="new"/>
24 <button string="Revalidate selected moves" name="action_revalidate_moves" type="object" icon="gtk-apply" states="ready"/>
25 <button string="Done" special="cancel" icon="gtk-ok" states="done"/>
26 </group>
27 <field name="state" invisible="1"/>
28 </form>
29 </field>
30 </record>
31
32 <record id="action_revalidate_moves_wizard" model="ir.actions.act_window">
33 <field name="name">Revalidate Account Moves (Regenerate Analytic Lines)</field>
34 <field name="res_model">revalidate_moves_wizard</field>
35 <field name="view_type">form</field>
36 <field name="view_mode">form</field>
37 <field name="target">new</field>
38 </record>
39 <menuitem id="menu_action_revalidate_moves_wizard"
40 parent="menu_action_account_admin_tools"
41 action="action_revalidate_moves_wizard"
42 sequence="50"/>
43
44 </data>
45</openerp>
046
=== added file 'account_admin_tools/set_invoice_ref_in_moves.py'
--- account_admin_tools/set_invoice_ref_in_moves.py 1970-01-01 00:00:00 +0000
+++ account_admin_tools/set_invoice_ref_in_moves.py 2012-12-07 10:00:37 +0000
@@ -0,0 +1,205 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved
6# $Id$
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as published
10# by the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22"""
23Set Invoice Reference in Moves
24"""
25__author__ = "Borja López Soilán (Pexego)"
26
27import re
28from osv import fields, osv
29from tools.translate import _
30
31
32class set_invoice_ref_in_moves(osv.osv_memory):
33 """
34 Set Invoice Reference in Moves
35
36 Searchs for account moves associated with invoices that do not have the
37 right reference (the reference from a supplier invoice or the number from
38 a customer invoice) and lets the user fix them.
39 """
40 _name = "account_admin_tools.set_invoice_ref_in_moves"
41 _description = "Set Invoice Reference in Moves"
42
43 _columns = {
44 'state': fields.selection([('new', 'New'), ('ready', 'Ready'), ('done', 'Done')], 'Status', readonly=True),
45 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True),
46 'period_ids': fields.many2many('account.period', 'set_invoice_ref_in_moves_period_rel', 'wizard_id', 'period_id', "Periods"),
47 'move_ids': fields.many2many('account.move', 'set_invoice_ref_in_move_move_rel', 'wizard_id', 'move_id', 'Moves'),
48 }
49
50 _defaults = {
51 'company_id': lambda self, cr, uid, context: self.pool.get('res.users').browse(cr, uid, uid, context).company_id.id,
52 'move_ids': lambda self, cr, uid, context: context and context.get('move_ids', None),
53 'period_ids': lambda self, cr, uid, context: context and context.get('period_ids', None),
54 'state': lambda self, cr, uid, context: context and context.get('state', 'new'),
55 }
56
57 def _next_view(self, cr, uid, ids, view_name, args=None, context=None):
58 """
59 Return the next wizard view
60 """
61 if context is None:
62 context = {}
63 if args is None:
64 args = {}
65 ctx = context.copy()
66 ctx.update(args)
67
68 model_data_ids = self.pool.get('ir.model.data').search(cr, uid, [
69 ('model', '=', 'ir.ui.view'),
70 ('module', '=', 'account_admin_tools'),
71 ('name', '=', view_name)
72 ])
73 resource_id = self.pool.get('ir.model.data').read(cr, uid, model_data_ids, fields=['res_id'], context=context)[0]['res_id']
74 return {
75 'name': _("Set Invoice Reference in Moves"),
76 'type': 'ir.actions.act_window',
77 'res_model': 'account_admin_tools.set_invoice_ref_in_moves',
78 'view_type': 'form',
79 'view_mode': 'form',
80 'views': [(resource_id, 'form')],
81 'domain': "[('id', 'in', %s)]" % ids,
82 'context': ctx,
83 'target': 'new',
84 }
85
86 def _get_reference(self, cr, uid, invoice, context=None):
87 """
88 Get's the reference for an account move given the related invoice.
89 """
90 if invoice.type in ('in_invoice', 'in_refund'):
91 return invoice.reference
92 else:
93 return self.pool.get('account.invoice')._convert_ref(cr, uid, invoice.number)
94
95 def _is_valid_reference(self, cr, uid, reference, invoice, context=None):
96 """
97 Checks that the given reference matches the invoice reference or number.
98 """
99 if reference == invoice.reference:
100 return True
101 elif reference == self.pool.get('account.invoice')._convert_ref(cr, uid, invoice.number):
102 return True
103 else:
104 return False
105
106 def action_skip_new(self, cr, uid, ids, context=None):
107 """
108 Action that just skips the to the ready state
109 """
110 return self._next_view(cr, uid, ids, 'view_set_invoice_ref_in_moves_ready_form', {'state': 'ready'}, context)
111
112 def action_find_moves_with_wrong_invoice_ref(self, cr, uid, ids, context=None):
113 """
114 Action that searchs for account moves associated with invoices,
115 that do not have the right reference.
116 """
117 wiz = self.browse(cr, uid, ids[0], context)
118
119 # FIXME: The next block of code is a workaround to the lp:586252 bug of the 6.0 client.
120 # (https://bugs.launchpad.net/openobject-client/+bug/586252)
121 if wiz.period_ids:
122 period_ids = [period.id for period in wiz.period_ids]
123 else:
124 period_ids = context and context.get('period_ids')
125
126 #
127 # Find the invoices (on the given periods)
128 #
129 args = []
130 if period_ids:
131 args = [('period_id', 'in', period_ids)]
132 invoice_ids = self.pool.get(
133 'account.invoice').search(cr, uid, args, context=context)
134
135 #
136 # Get the moves with references not matching the desired ones
137 #
138 move_ids = []
139 for invoice in self.pool.get('account.invoice').browse(cr, uid, invoice_ids, context=context):
140 if invoice.move_id:
141 if not self._is_valid_reference(cr, uid, invoice.move_id.ref, invoice, context=context):
142 reference = self._get_reference(
143 cr, uid, invoice, context=context)
144 if reference and len(reference):
145 move_ids.append(invoice.move_id.id)
146
147 #
148 # Return the next view: Show 'ready' view
149 #
150 args = {
151 'move_ids': move_ids,
152 'state': 'ready',
153 }
154 return self._next_view(cr, uid, ids, 'view_set_invoice_ref_in_moves_ready_form', args, context)
155
156 def action_set_invoice_ref_in_moves(self, cr, uid, ids, context=None):
157 """
158 Action that sets the invoice reference or number as the account move
159 reference for the selected moves.
160 """
161 wiz = self.browse(cr, uid, ids[0], context)
162
163 # FIXME: The next block of code is a workaround to the lp:586252 bug of the 6.0 client.
164 # (https://bugs.launchpad.net/openobject-client/+bug/586252)
165 if wiz.move_ids:
166 move_ids = [line.id for line in wiz.move_ids]
167 else:
168 move_ids = context and context.get('move_ids')
169
170 #
171 # Find the invoices of the moves
172 #
173 args = [('move_id', 'in', move_ids)]
174 invoice_ids = self.pool.get(
175 'account.invoice').search(cr, uid, args, context=context)
176
177 #
178 # Update the moves with the reference of the invoice.
179 #
180 for invoice in self.pool.get('account.invoice').browse(cr, uid, invoice_ids, context=context):
181 if invoice.move_id:
182 reference = self._get_reference(
183 cr, uid, invoice, context=context)
184 if invoice.move_id.ref != reference:
185 self.pool.get('account.move').write(cr, uid, [invoice.move_id.id], {'ref': reference}, context=context)
186
187 #
188 # Update the move line references too
189 # (if they where equal to the move reference)
190 #
191 for line in invoice.move_id.line_id:
192 if line.ref == invoice.move_id.ref:
193 self.pool.get('account.move.line').write(cr, uid, [line.id], {'ref': reference}, context=context)
194
195 #
196 # Return the next view: Show 'done' view
197 #
198 args = {
199 'move_ids': move_ids,
200 'state': 'done',
201 }
202 return self._next_view(cr, uid, ids, 'view_set_invoice_ref_in_moves_done_form', args, context)
203
204
205set_invoice_ref_in_moves()
0206
=== added file 'account_admin_tools/set_invoice_ref_in_moves.xml'
--- account_admin_tools/set_invoice_ref_in_moves.xml 1970-01-01 00:00:00 +0000
+++ account_admin_tools/set_invoice_ref_in_moves.xml 2012-12-07 10:00:37 +0000
@@ -0,0 +1,82 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4 <record id="view_set_invoice_ref_in_moves_form" model="ir.ui.view">
5 <field name="name">set_invoice_ref_in_moves.form</field>
6 <field name="model">account_admin_tools.set_invoice_ref_in_moves</field>
7 <field name="type">form</field>
8 <field name="arch" type="xml">
9 <form string="Set Invoice Reference in Moves">
10 <label string="This wizard will set the reference in account moves associated with invoices, that don't match the invoice reference/number." colspan="4"/>
11 <label string="" colspan="4"/>
12 <group colspan="4">
13 <separator string="Find moves not matching the invoice reference/number" colspan="4"/>
14 <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"/>
15 <label string="" colspan="4"/>
16 <newline/>
17 <field name="period_ids" colspan="4"/>
18 <label string="" colspan="4"/>
19 <newline/>
20 <button string="Search for moves" name="action_find_moves_with_wrong_invoice_ref" type="object" icon="gtk-ok" colspan="4"/>
21 </group>
22 <label string="" colspan="4"/>
23 <group colspan="4">
24 <button string="Cancel" special="cancel" icon="gtk-cancel"/>
25 <button string="Skip" name="action_skip_new" type="object" icon="gtk-go-forward"/>
26 </group>
27 </form>
28 </field>
29 </record>
30
31 <record id="view_set_invoice_ref_in_moves_ready_form" model="ir.ui.view">
32 <field name="name">set_invoice_ref_in_moves.ready.form</field>
33 <field name="model">account_admin_tools.set_invoice_ref_in_moves</field>
34 <field name="type">form</field>
35 <field name="arch" type="xml">
36 <form string="Set Invoice Reference in Moves">
37 <label string="The reference will be set, for the selected account moves, to the reference/number of the (supplier/customer) invoice." colspan="4"/>
38 <label string="" colspan="4"/>
39 <separator string="Account moves to update" colspan="4"/>
40 <field name="move_ids" colspan="4" nolabel="1"/>
41 <label string="" colspan="4"/>
42 <group colspan="4">
43 <button string="Cancel" special="cancel" icon="gtk-cancel"/>
44 <button string="Set invoice reference in moves" name="action_set_invoice_ref_in_moves" type="object" icon="gtk-apply"/>
45 </group>
46 </form>
47 </field>
48 </record>
49
50 <record id="view_set_invoice_ref_in_moves_done_form" model="ir.ui.view">
51 <field name="name">set_invoice_ref_in_moves.done.form</field>
52 <field name="model">account_admin_tools.set_invoice_ref_in_moves</field>
53 <field name="type">form</field>
54 <field name="arch" type="xml">
55 <form string="Set Invoice Reference in Moves">
56 <label string="The invoice references have been succesfully set on the account moves!" colspan="4"/>
57 <label string="" colspan="4"/>
58 <separator string="Updated account moves" colspan="4"/>
59 <field name="move_ids" colspan="4" nolabel="1" readonly="1"/>
60 <label string="" colspan="4"/>
61 <group colspan="4">
62 <button string="Done" special="cancel" icon="gtk-ok"/>
63 </group>
64 </form>
65 </field>
66 </record>
67
68 <record id="action_set_invoice_ref_in_moves" model="ir.actions.act_window">
69 <field name="name">Set Invoice Reference in Moves</field>
70 <field name="res_model">account_admin_tools.set_invoice_ref_in_moves</field>
71 <field name="view_type">form</field>
72 <field name="view_mode">form</field>
73 <field name="view_id" ref="view_set_invoice_ref_in_moves_form"/>
74 <field name="target">new</field>
75 </record>
76 <menuitem id="menu_action_set_invoice_ref_in_moves"
77 parent="menu_action_account_admin_tools_repair"
78 action="action_set_invoice_ref_in_moves"
79 sequence="130"/>
80
81 </data>
82</openerp>
083
=== added file 'account_admin_tools/set_partner_in_moves.py'
--- account_admin_tools/set_partner_in_moves.py 1970-01-01 00:00:00 +0000
+++ account_admin_tools/set_partner_in_moves.py 2012-12-07 10:00:37 +0000
@@ -0,0 +1,214 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2004-2009 Pexego Sistemas Informáticos. All Rights Reserved
6# $Id$
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as published
10# by the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22"""
23Set Partner in Moves Wizard
24"""
25__author__ = "Borja López Soilán (Pexego)"
26
27import re
28from osv import fields, osv
29from tools.translate import _
30
31
32class set_partner_in_moves(osv.osv_memory):
33 """
34 Set Partner in Moves Wizard
35
36 Searchs for account move lines of that use the payable/receivable account
37 of a single partner, and have no partner reference in the line,
38 and sets the partner reference (partner_id).
39 This may fix cases where the receivable/payable amounts displayed in the
40 partner form does not match the balance of the receivable/payable accounts.
41 """
42 _name = "account_admin_tools.set_partner_in_moves"
43 _description = "Set Partner in Moves Wizard"
44
45 _columns = {
46 'state': fields.selection([('new', 'New'), ('ready', 'Ready'), ('done', 'Done')], 'Status', readonly=True),
47 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True),
48 'period_ids': fields.many2many('account.period', 'set_partner_in_moves_period_rel', 'wizard_id', 'period_id', "Periods"),
49 'move_line_ids': fields.many2many('account.move.line', 'set_partner_in_move_move_line_rel', 'wizard_id', 'line_id', 'Move Lines'),
50 }
51
52 _defaults = {
53 'company_id': lambda self, cr, uid, context: self.pool.get('res.users').browse(cr, uid, uid, context).company_id.id,
54 'move_line_ids': lambda self, cr, uid, context: context and context.get('move_line_ids', None),
55 'period_ids': lambda self, cr, uid, context: context and context.get('period_ids', None),
56 'state': lambda self, cr, uid, context: context and context.get('state', 'new'),
57 }
58
59 def _next_view(self, cr, uid, ids, view_name, args=None, context=None):
60 """
61 Return the next wizard view
62 """
63 if context is None:
64 context = {}
65 if args is None:
66 args = {}
67 ctx = context.copy()
68 ctx.update(args)
69
70 model_data_ids = self.pool.get('ir.model.data').search(cr, uid, [
71 ('model', '=', 'ir.ui.view'),
72 ('module', '=', 'account_admin_tools'),
73 ('name', '=', view_name)
74 ])
75 resource_id = self.pool.get('ir.model.data').read(cr, uid, model_data_ids, fields=['res_id'], context=context)[0]['res_id']
76 return {
77 'name': _("Set Partner Reference in Moves"),
78 'type': 'ir.actions.act_window',
79 'res_model': 'account_admin_tools.set_partner_in_moves',
80 'view_type': 'form',
81 'view_mode': 'form',
82 'views': [(resource_id, 'form')],
83 'domain': "[('id', 'in', %s)]" % ids,
84 'context': ctx,
85 'target': 'new',
86 }
87
88 def _get_accounts_map(self, cr, uid, context=None):
89 """
90 Find the receivable/payable accounts that are associated with
91 a single partner and return a (account.id, partner.id) map
92 """
93 partner_ids = self.pool.get(
94 'res.partner').search(cr, uid, [], context=context)
95 accounts_map = {}
96 for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context):
97 #
98 # Add the receivable account to the map
99 #
100 if accounts_map.get(partner.property_account_receivable.id, None) is None:
101 accounts_map[
102 partner.property_account_receivable.id] = partner.id
103 else:
104 # Two partners with the same receivable account: ignore
105 # this account!
106 accounts_map[partner.property_account_receivable.id] = 0
107 #
108 # Add the payable account to the map
109 #
110 if accounts_map.get(partner.property_account_payable.id, None) is None:
111 accounts_map[partner.property_account_payable.id] = partner.id
112 else:
113 # Two partners with the same receivable account: ignore
114 # this account!
115 accounts_map[partner.property_account_payable.id] = 0
116 return accounts_map
117
118 def action_skip_new(self, cr, uid, ids, context=None):
119 """
120 Action that just skips the to the ready state
121 """
122 return self._next_view(cr, uid, ids, 'view_set_partner_in_moves_ready_form', {'state': 'ready'}, context)
123
124 def action_find_moves_missing_partner(self, cr, uid, ids, context=None):
125 """
126 Action that searchs for account move lines of payable/receivable
127 accounts (of just one partner) that don't have the partner reference.
128 """
129 wiz = self.browse(cr, uid, ids[0], context)
130
131 # FIXME: The next block of code is a workaround to the lp:586252 bug of the 6.0 client.
132 # (https://bugs.launchpad.net/openobject-client/+bug/586252)
133 if wiz.period_ids:
134 period_ids = [period.id for period in wiz.period_ids]
135 else:
136 period_ids = context and context.get('period_ids')
137
138 move_line_ids = []
139 accounts_map = self._get_accounts_map(cr, uid, context=context)
140
141 #
142 # Find the account move lines, of each of the accounts in the map
143 # that don't have a partner set.
144 #
145 query = """
146 SELECT id FROM account_move_line
147 WHERE account_id=%s
148 AND partner_id IS NULL
149 """
150 if period_ids:
151 query += """ AND period_id IN %s"""
152
153 for account_id in accounts_map.keys():
154 if accounts_map[account_id] != 0:
155 if period_ids:
156 cr.execute(query, (account_id, tuple(period_ids)))
157 else:
158 cr.execute(query, (account_id,))
159 new_move_line_ids = filter(
160 None, map(lambda x: x[0], cr.fetchall()))
161 if new_move_line_ids:
162 move_line_ids.extend(new_move_line_ids)
163
164 #
165 # Return the next view: Show 'ready' view
166 #
167 args = {
168 'move_line_ids': move_line_ids,
169 'state': 'ready',
170 }
171 return self._next_view(cr, uid, ids, 'view_set_partner_in_moves_ready_form', args, context)
172
173 def action_set_partner_in_moves(self, cr, uid, ids, context=None):
174 """
175 Action that sets for each partner payable/receivable account,
176 that is used only on one partner, the parner reference on its move
177 lines.
178 """
179 wiz = self.browse(cr, uid, ids[0], context)
180
181 # FIXME: The next block of code is a workaround to the lp:586252 bug of the 6.0 client.
182 # (https://bugs.launchpad.net/openobject-client/+bug/586252)
183 if wiz.move_line_ids:
184 move_line_ids = [line.id for line in wiz.move_line_ids]
185 else:
186 move_line_ids = context and context.get('move_line_ids')
187
188 accounts_map = self._get_accounts_map(cr, uid, context=context)
189
190 #
191 # Update the account move lines, of each of the accounts in the map
192 # that don't have a partner set, with the associated partner.
193 #
194 query = """
195 UPDATE account_move_line
196 SET partner_id=%s
197 WHERE id=%s
198 AND partner_id IS NULL
199 """
200 for line in self.pool.get('account.move.line').browse(cr, uid, move_line_ids, context=context):
201 if accounts_map[line.account_id.id] != 0:
202 cr.execute(query, (accounts_map[line.account_id.id], line.id))
203
204 #
205 # Return the next view: Show 'done' view
206 #
207 args = {
208 'move_line_ids': move_line_ids,
209 'state': 'done',
210 }
211 return self._next_view(cr, uid, ids, 'view_set_partner_in_moves_done_form', args, context)
212
213
214set_partner_in_moves()
0215
=== added file 'account_admin_tools/set_partner_in_moves.xml'
--- account_admin_tools/set_partner_in_moves.xml 1970-01-01 00:00:00 +0000
+++ account_admin_tools/set_partner_in_moves.xml 2012-12-07 10:00:37 +0000
@@ -0,0 +1,82 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4 <record id="view_set_partner_in_moves_form" model="ir.ui.view">
5 <field name="name">set_partner_in_moves.form</field>
6 <field name="model">account_admin_tools.set_partner_in_moves</field>
7 <field name="type">form</field>
8 <field name="arch" type="xml">
9 <form string="Set Partner Reference in Moves">
10 <label string="This wizard will set the partner reference in moves where the receivable/payable account (of a single partner) is used." colspan="4"/>
11 <label string="" colspan="4"/>
12 <group colspan="4">
13 <separator string="Find moves with missing partner reference" colspan="4"/>
14 <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"/>
15 <label string="" colspan="4"/>
16 <newline/>
17 <field name="period_ids" colspan="4"/>
18 <label string="" colspan="4"/>
19 <newline/>
20 <button string="Search for moves" name="action_find_moves_missing_partner" type="object" icon="gtk-ok" colspan="4"/>
21 </group>
22 <label string="" colspan="4"/>
23 <group colspan="4">
24 <button string="Cancel" special="cancel" icon="gtk-cancel"/>
25 <button string="Skip" name="action_skip_new" type="object" icon="gtk-go-forward"/>
26 </group>
27 </form>
28 </field>
29 </record>
30
31 <record id="view_set_partner_in_moves_ready_form" model="ir.ui.view">
32 <field name="name">set_partner_in_moves.ready.form</field>
33 <field name="model">account_admin_tools.set_partner_in_moves</field>
34 <field name="type">form</field>
35 <field name="arch" type="xml">
36 <form string="Set Partner Reference in Moves">
37 <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"/>
38 <label string="" colspan="4"/>
39 <separator string="Account move lines to update" colspan="4"/>
40 <field name="move_line_ids" colspan="4" nolabel="1" domain="[('partner_id','=',False), ('account_id.type', 'in', ['receivable', 'payable'])]"/>
41 <label string="" colspan="4"/>
42 <group colspan="4">
43 <button string="Cancel" special="cancel" icon="gtk-cancel"/>
44 <button string="Set partner in moves" name="action_set_partner_in_moves" type="object" icon="gtk-apply"/>
45 </group>
46 </form>
47 </field>
48 </record>
49
50 <record id="view_set_partner_in_moves_done_form" model="ir.ui.view">
51 <field name="name">set_partner_in_moves.done.form</field>
52 <field name="model">account_admin_tools.set_partner_in_moves</field>
53 <field name="type">form</field>
54 <field name="arch" type="xml">
55 <form string="Set Partner Reference in Moves">
56 <label string="The partner references have been succesfully set on the account moves!" colspan="4"/>
57 <label string="" colspan="4"/>
58 <separator string="Updated account move lines" colspan="4"/>
59 <field name="move_line_ids" colspan="4" nolabel="1" domain="[('partner_id','=',False), ('account_id.type', 'in', ['receivable', 'payable'])]" readonly="1"/>
60 <label string="" colspan="4"/>
61 <group colspan="4">
62 <button string="Done" special="cancel" icon="gtk-ok"/>
63 </group>
64 </form>
65 </field>
66 </record>
67
68 <record id="action_set_partner_in_moves" model="ir.actions.act_window">
69 <field name="name">Set Partner Reference in Moves</field>
70 <field name="res_model">account_admin_tools.set_partner_in_moves</field>
71 <field name="view_type">form</field>
72 <field name="view_mode">form</field>
73 <field name="view_id" ref="view_set_partner_in_moves_form"/>
74 <field name="target">new</field>
75 </record>
76 <menuitem id="menu_action_set_partner_in_moves"
77 parent="menu_action_account_admin_tools_repair"
78 action="action_set_partner_in_moves"
79 sequence="120"/>
80
81 </data>
82</openerp>
083
=== added directory 'account_chart_update'
=== added file 'account_chart_update/__init__.py'
--- account_chart_update/__init__.py 1970-01-01 00:00:00 +0000
+++ account_chart_update/__init__.py 2012-12-07 10:00:37 +0000
@@ -0,0 +1,28 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Copyright (c) 2010 Zikzakmedia S.L. (http://www.zikzakmedia.com)
5# Copyright (c) 2010 Pexego Sistemas Informáticos S.L. (http://www.pexego.es)
6# @authors: Jordi Esteve (Zikzakmedia), Borja López Soilán (Pexego)
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as published
10# by the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22"""
23Account Chart Update Wizard
24"""
25__authors__ = ["Jordi Esteve <jesteve@zikzakmedia.com>",
26 "Borja López Soilán <borjals@pexego.es>"]
27
28import account
029
=== added file 'account_chart_update/__openerp__.py'
--- account_chart_update/__openerp__.py 1970-01-01 00:00:00 +0000
+++ account_chart_update/__openerp__.py 2012-12-07 10:00:37 +0000
@@ -0,0 +1,64 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Copyright (c) 2010 Zikzakmedia S.L. (http://www.zikzakmedia.com)
5# Copyright (c) 2010 Pexego Sistemas Informáticos S.L. (http://www.pexego.es)
6# @authors: Jordi Esteve (Zikzakmedia), Borja López Soilán (Pexego)
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as published
10# by the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22{
23 "name": "Detect changes and update the Account Chart from a template",
24 "version": "6.1",
25 "author": "Zikzakmedia SL",
26 "website": "www.zikzakmedia.com",
27 "license": "GPL-3",
28 "depends": ["account"],
29 "category": "Generic Modules/Accounting",
30 "description": """
31Adds a wizard to update a company account chart from a chart template.
32
33This is a pretty useful tool to update OpenERP instalations after tax reforms
34on the oficial charts of accounts, or to apply fixes performed on the chart
35template.
36
37The wizard:
38
39- Allows the user to compare a chart and a template showing differences
40 on accounts, taxes, tax codes and fiscal positions.
41- It may create the new account, taxes, tax codes and fiscal positions detected
42 on the template.
43- It can also update (overwrite) the accounts, taxes, tax codes and fiscal
44 positions that got modified on the template.
45
46The wizard lets the user select what kind of objects must be checked/updated,
47and whether old records must be checked for changes and updated.
48It will display all the accounts to be created / updated with some information
49about the detected differences, and allow the user to exclude records
50individually.
51Any problem found while updating will be shown on the last step.
52
53Authors:
54 Jordi Esteve (Zikzakmedia) <jesteve@zikzakmedia.com>
55 Borja López Soilán (Pexego) <borjals@pexego.es>
56""",
57 "init_xml": [],
58 "demo_xml": [],
59 "update_xml": [
60 "account_view.xml",
61 ],
62 "active": False,
63 "installable": True
64}
065
=== added file 'account_chart_update/account.py'
--- account_chart_update/account.py 1970-01-01 00:00:00 +0000
+++ account_chart_update/account.py 2012-12-07 10:00:37 +0000
@@ -0,0 +1,1329 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Copyright (c) 2010 Zikzakmedia S.L. (http://www.zikzakmedia.com)
5# Copyright (c) 2010 Pexego Sistemas Informáticos S.L. (http://www.pexego.es)
6# @authors: Jordi Esteve (Zikzakmedia), Borja López Soilán (Pexego)
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as published
10# by the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22"""
23Account Chart Update Wizard
24"""
25__authors__ = ["Jordi Esteve <jesteve@zikzakmedia.com>",
26 "Borja López Soilán <borjals@pexego.es>"]
27
28from osv import fields, osv
29from tools.translate import _
30import logging
31
32
33class WizardLog:
34 """
35 Small helper class to store the messages and errors on the wizard.
36 """
37 def __init__(self):
38 self.messages = []
39 self.errors = []
40
41 def add(self, message, is_error=False):
42 """
43 Adds a message to the log.
44 """
45 logger = logging.getLogger("account_chart_update")
46 if is_error:
47 logger.warning(u"Log line: %s" % message)
48 self.errors.append(message)
49 else:
50 logger.debug(u"Log line: %s" % message)
51 self.messages.append(message)
52
53 def has_errors(self):
54 """
55 Returns whether errors where logged.
56 """
57 return self.errors
58
59 def __call__(self):
60 return "".join(self.messages)
61
62 def __str__(self):
63 return "".join(self.messages)
64
65 def get_errors_str(self):
66 return "".join(self.errors)
67
68
69class wizard_update_charts_accounts(osv.osv_memory):
70 """
71 Updates an existing account chart for a company.
72 Wizards ask for:
73 * a company
74 * an account chart template
75 * a number of digits for formatting code of non-view accounts
76 Then, the wizard:
77 * generates/updates all accounts from the template and assigns them to the right company
78 * generates/updates all taxes and tax codes, changing account assignations
79 * generates/updates all accounting properties and assigns them correctly
80 """
81 _name = 'wizard.update.charts.accounts'
82
83 def _get_lang_selection_options(self, cr, uid, context={}):
84 """
85 Gets the available languages for the selection.
86 """
87 obj = self.pool.get('res.lang')
88 ids = obj.search(cr, uid, [], context=context)
89 res = obj.read(cr, uid, ids, ['code', 'name'], context)
90 return [(r['code'], r['name']) for r in res] + [('', '')]
91
92 _columns = {
93 'state': fields.selection([
94 ('init', 'Step 1'),
95 ('ready', 'Step 2'),
96 ('done', 'Wizard Complete')
97 ], 'Status', readonly=True),
98 'company_id': fields.many2one('res.company', 'Company', required=True),
99 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
100 '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."),
101 '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."),
102 'update_tax_code': fields.boolean('Update tax codes', help="Existing tax codes are updated. Tax codes are searched by name."),
103 'update_tax': fields.boolean('Update taxes', help="Existing taxes are updated. Taxes are searched by name."),
104 'update_account': fields.boolean('Update accounts', help="Existing accounts are updated. Accounts are searched by code."),
105 'update_fiscal_position': fields.boolean('Update fiscal positions', help="Existing fiscal positions are updated. Fiscal positions are searched by name."),
106 'update_children_accounts_parent': fields.boolean("Update children accounts parent",
107 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."),
108 '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)."),
109 'tax_code_ids': fields.one2many('wizard.update.charts.accounts.tax.code', 'update_chart_wizard_id', 'Tax codes'),
110 'tax_ids': fields.one2many('wizard.update.charts.accounts.tax', 'update_chart_wizard_id', 'Taxes'),
111 'account_ids': fields.one2many('wizard.update.charts.accounts.account', 'update_chart_wizard_id', 'Accounts'),
112 'fiscal_position_ids': fields.one2many('wizard.update.charts.accounts.fiscal.position', 'update_chart_wizard_id', 'Fiscal positions'),
113 'new_tax_codes': fields.integer('New tax codes', readonly=True),
114 'new_taxes': fields.integer('New taxes', readonly=True),
115 'new_accounts': fields.integer('New accounts', readonly=True),
116 'new_fps': fields.integer('New fiscal positions', readonly=True),
117 'updated_tax_codes': fields.integer('Updated tax codes', readonly=True),
118 'updated_taxes': fields.integer('Updated taxes', readonly=True),
119 'updated_accounts': fields.integer('Updated accounts', readonly=True),
120 'updated_fps': fields.integer('Updated fiscal positions', readonly=True),
121 'log': fields.text('Messages and Errors', readonly=True)
122 }
123
124 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
125 """
126 Redefine the search to search by company name.
127 """
128 if not name:
129 name = '%'
130 if not args:
131 args = []
132 if not context:
133 context = {}
134 args = args[:]
135 ids = []
136 ids = self.search(
137 cr, user, [('company_id', operator, name)] + args, limit=limit)
138 return self.name_get(cr, user, ids, context=context)
139
140 def name_get(self, cr, uid, ids, context=None):
141 """
142 Use the company name and template as name.
143 """
144 if context is None:
145 context = {}
146 if not len(ids):
147 return []
148 records = self.browse(cr, uid, ids, context)
149 res = []
150 for record in records:
151 res.append((record.id, record.company_id.name +
152 ' - ' + record.chart_template_id.name))
153 return res
154
155 def _get_chart(self, cr, uid, context=None):
156 """
157 Returns the default chart template.
158 """
159 if context is None:
160 context = {}
161 ids = self.pool.get(
162 'account.chart.template').search(cr, uid, [], context=context)
163 if ids:
164 return ids[0]
165 return False
166
167 def _get_code_digits(self, cr, uid, context=None, company_id=None):
168 """
169 Returns the default code size for the accounts.
170
171 To figure out the number of digits of the accounts it look at the
172 code size of the default receivable account of the company
173 (or user's company if any company is given).
174 """
175 if context is None:
176 context = {}
177 property_obj = self.pool.get('ir.property')
178 account_obj = self.pool.get('account.account')
179 if not company_id:
180 user = self.pool.get('res.users').browse(cr, uid, uid, context)
181 company_id = user.company_id.id
182 property_ids = property_obj.search(cr, uid, [('name', '=', 'property_account_receivable'), ('company_id', '=', company_id), ('res_id', '=', False), ('value_reference', '!=', False)])
183 if not property_ids:
184 # Try to get a generic (no-company) property
185 property_ids = property_obj.search(cr, uid, [('name', '=', 'property_account_receivable'), ('res_id', '=', False), ('value_reference', '!=', False)])
186 number_digits = 6
187 if property_ids:
188 prop = property_obj.browse(
189 cr, uid, property_ids[0], context=context)
190 account_id = prop.value_reference.id
191
192 if account_id:
193 code = account_obj.read(
194 cr, uid, account_id, ['code'], context)['code']
195 number_digits = len(code)
196 return number_digits
197
198 _defaults = {
199 'state': lambda *a: 'init',
200 'company_id': lambda self, cr, uid, context: self.pool.get('res.users').browse(cr, uid, [uid], context)[0].company_id.id,
201 'chart_template_id': _get_chart,
202 'update_tax_code': lambda *a: True,
203 'update_tax': lambda *a: True,
204 'update_account': lambda *a: True,
205 'update_fiscal_position': lambda *a: True,
206 'update_children_accounts_parent': lambda *a: True,
207 'continue_on_errors': lambda *a: False,
208 'lang': lambda self, cr, uid, context: context and context.get('lang') or None,
209 }
210
211 def onchange_company_id(self, cr, uid, ids, company_id, context=None):
212 """
213 Update the code size when the company changes
214 """
215 res = {
216 'value': {
217 'code_digits': self._get_code_digits(cr, uid, context=context, company_id=company_id),
218 }
219 }
220 return res
221
222 def action_init(self, cr, uid, ids, context=None):
223 """
224 Initial action that sets the initial state.
225 """
226 if context is None:
227 context = {}
228 self.write(cr, uid, ids, {'state': 'init'}, context)
229 return True
230
231 ############################################################################
232 # Helper methods
233 ##########################################################################
234 def _map_tax_template(self, cr, uid, wizard, tax_template_mapping, tax_template, context=None):
235 """
236 Adds a tax template -> tax id to the mapping.
237 """
238 if tax_template and not tax_template_mapping.get(tax_template.id):
239 tax_facade = self.pool.get('account.tax')
240 tax_ids = tax_facade.search(cr, uid, [
241 ('name', '=', tax_template.name),
242 ('company_id', '=', wizard.company_id.id)
243 ], context=context)
244 if tax_ids:
245 tax_template_mapping[tax_template.id] = tax_ids[0]
246
247 def _map_tax_code_template(self, cr, uid, wizard, tax_code_template_mapping, tax_code_template, context=None):
248 """
249 Adds a tax code template -> tax code id to the mapping.
250 """
251 if tax_code_template and not tax_code_template_mapping.get(tax_code_template.id):
252 tax_code_facade = self.pool.get('account.tax.code')
253 root_tax_code_id = wizard.chart_template_id.tax_code_root_id.id
254 tax_code_name = (tax_code_template.id == root_tax_code_id) and wizard.company_id.name or tax_code_template.name
255 tax_code_ids = tax_code_facade.search(cr, uid, [
256 ('name', '=', tax_code_name),
257 ('company_id', '=', wizard.company_id.id)
258 ])
259 if tax_code_ids:
260 tax_code_template_mapping[
261 tax_code_template.id] = tax_code_ids[0]
262
263 def _map_account_template(self, cr, uid, wizard, account_template_mapping, account_template, context=None):
264 """
265 Adds an account template -> account id to the mapping
266 """
267 if account_template and not account_template_mapping.get(account_template.id):
268 account_facade = self.pool.get('account.account')
269 code = account_template.code or ''
270 if account_template.type != 'view':
271 if len(code) > 0 and len(code) <= wizard.code_digits:
272 code = '%s%s' % (
273 code, '0' * (wizard.code_digits - len(code)))
274 account_ids = account_facade.search(cr, uid, [
275 ('code', '=', code),
276 ('company_id', '=', wizard.company_id.id)
277 ], context=context)
278 if account_ids:
279 account_template_mapping[account_template.id] = account_ids[0]
280
281 def _map_fp_template(self, cr, uid, wizard, fp_template_mapping, fp_template, context=None):
282 """
283 Adds a fiscal position template -> fiscal position id to the mapping.
284 """
285 if fp_template and not fp_template_mapping.get(fp_template.id):
286 fp_facade = self.pool.get('account.fiscal.position')
287 fp_ids = fp_facade.search(cr, uid, [
288 ('name', '=', fp_template.name),
289 ('company_id', '=', wizard.company_id.id)
290 ], context=context)
291 if fp_ids:
292 fp_template_mapping[fp_template.id] = fp_ids[0]
293
294 ############################################################################
295 # Find records methods
296 ##########################################################################
297 def _find_tax_codes(self, cr, uid, wizard, context=None):
298 """
299 Search for, and load, tax code templates to create/update.
300 """
301 new_tax_codes = 0
302 updated_tax_codes = 0
303 tax_code_template_mapping = {}
304
305 tax_code_templ_facade = self.pool.get('account.tax.code.template')
306 tax_code_facade = self.pool.get('account.tax.code')
307 wiz_tax_code_facade = self.pool.get(
308 'wizard.update.charts.accounts.tax.code')
309
310 # Remove previous tax codes
311 wiz_tax_code_facade.unlink(
312 cr, uid, wiz_tax_code_facade.search(cr, uid, []))
313
314 #
315 # Search for new / updated tax codes
316 #
317 root_tax_code_id = wizard.chart_template_id.tax_code_root_id.id
318 children_tax_code_template = tax_code_templ_facade.search(cr, uid, [(
319 'parent_id', 'child_of', [root_tax_code_id])], order='id')
320 for tax_code_template in tax_code_templ_facade.browse(cr, uid, children_tax_code_template):
321 # Ensure the tax code template is on the map (search for the mapped
322 # tax code id).
323 self._map_tax_code_template(cr, uid, wizard, tax_code_template_mapping, tax_code_template, context)
324
325 tax_code_id = tax_code_template_mapping.get(tax_code_template.id)
326 if not tax_code_id:
327 new_tax_codes += 1
328 wiz_tax_code_facade.create(cr, uid, {
329 'tax_code_id': tax_code_template.id,
330 'update_chart_wizard_id': wizard.id,
331 'type': 'new',
332 }, context)
333 elif wizard.update_tax_code:
334 #
335 # Check the tax code for changes.
336 #
337 modified = False
338 notes = ""
339 tax_code = tax_code_facade.browse(
340 cr, uid, tax_code_id, context=context)
341
342 if tax_code.code != tax_code_template.code:
343 notes += _("The code field is different.\n")
344 modified = True
345 if tax_code.info != tax_code_template.info:
346 notes += _("The info field is different.\n")
347 modified = True
348 if tax_code.sign != tax_code_template.sign:
349 notes += _("The sign field is different.\n")
350 modified = True
351
352 # TODO: We could check other account fields for changes...
353
354 if modified:
355 #
356 # Tax code to update.
357 #
358 updated_tax_codes += 1
359 wiz_tax_code_facade.create(cr, uid, {
360 'tax_code_id': tax_code_template.id,
361 'update_chart_wizard_id': wizard.id,
362 'type': 'updated',
363 'update_tax_code_id': tax_code_id,
364 'notes': notes,
365 }, context)
366
367 return {'new': new_tax_codes, 'updated': updated_tax_codes, 'mapping': tax_code_template_mapping}
368
369 def _find_taxes(self, cr, uid, wizard, context=None):
370 """
371 Search for, and load, tax templates to create/update.
372 """
373 new_taxes = 0
374 updated_taxes = 0
375 tax_template_mapping = {}
376
377 tax_facade = self.pool.get('account.tax')
378 wiz_tax_facade = self.pool.get('wizard.update.charts.accounts.tax')
379
380 delay_wiz_tax = []
381 # Remove previous taxes
382 wiz_tax_facade.unlink(cr, uid, wiz_tax_facade.search(cr, uid, []))
383
384 #
385 # Search for new / updated taxes
386 #
387 for tax_template in wizard.chart_template_id.tax_template_ids:
388 # Ensure the tax template is on the map (search for the mapped tax
389 # id).
390 self._map_tax_template(
391 cr, uid, wizard, tax_template_mapping, tax_template, context)
392
393 tax_id = tax_template_mapping.get(tax_template.id)
394 if not tax_id:
395 new_taxes += 1
396 vals_wiz = {
397 'tax_id': tax_template.id,
398 'update_chart_wizard_id': wizard.id,
399 'type': 'new',
400 }
401 if not tax_template.parent_id:
402 wiz_tax_facade.create(cr, uid, vals_wiz, context)
403 else:
404 delay_wiz_tax.append(vals_wiz)
405 elif wizard.update_tax:
406 #
407 # Check the tax for changes.
408 #
409 modified = False
410 notes = ""
411 tax = tax_facade.browse(cr, uid, tax_id, context=context)
412
413 if tax.sequence != tax_template.sequence:
414 notes += _("The sequence field is different.\n")
415 modified = True
416 if tax.amount != tax_template.amount:
417 notes += _("The amount field is different.\n")
418 modified = True
419 if tax.type != tax_template.type:
420 notes += _("The type field is different.\n")
421 modified = True
422 if tax.applicable_type != tax_template.applicable_type:
423 notes += _("The applicable type field is different.\n")
424 modified = True
425 if tax.domain != tax_template.domain:
426 notes += _("The domain field is different.\n")
427 modified = True
428 if tax.child_depend != tax_template.child_depend:
429 notes += _("The child depend field is different.\n")
430 modified = True
431 if tax.python_compute != tax_template.python_compute:
432 notes += _("The python compute field is different.\n")
433 modified = True
434 # if tax.tax_group != tax_template.tax_group:
435 # notes += _("The tax group field is different.\n")
436 # modified = True
437 if tax.base_sign != tax_template.base_sign:
438 notes += _("The base sign field is different.\n")
439 modified = True
440 if tax.tax_sign != tax_template.tax_sign:
441 notes += _("The tax sign field is different.\n")
442 modified = True
443 if tax.include_base_amount != tax_template.include_base_amount:
444 notes += _("The include base amount field is different.\n")
445 modified = True
446 if tax.type_tax_use != tax_template.type_tax_use:
447 notes += _("The type tax use field is different.\n")
448 modified = True
449 # TODO: We could check other tax fields for changes...
450
451 if modified:
452 #
453 # Tax code to update.
454 #
455 updated_taxes += 1
456 wiz_tax_facade.create(cr, uid, {
457 'tax_id': tax_template.id,
458 'update_chart_wizard_id': wizard.id,
459 'type': 'updated',
460 'update_tax_id': tax_id,
461 'notes': notes,
462 }, context)
463
464 for delay_vals_wiz in delay_wiz_tax:
465 wiz_tax_facade.create(cr, uid, delay_vals_wiz, context)
466
467 return {'new': new_taxes, 'updated': updated_taxes, 'mapping': tax_template_mapping}
468
469 def _find_accounts(self, cr, uid, wizard, context=None):
470 """
471 Search for, and load, account templates to create/update.
472 """
473 new_accounts = 0
474 updated_accounts = 0
475 account_template_mapping = {}
476
477 account_facade = self.pool.get('account.account')
478 account_template_facade = self.pool.get('account.account.template')
479 wiz_account_facade = self.pool.get(
480 'wizard.update.charts.accounts.account')
481
482 # Remove previous accounts
483 wiz_account_facade.unlink(
484 cr, uid, wiz_account_facade.search(cr, uid, []))
485
486 #
487 # Search for new / updated accounts
488 #
489 root_account_id = wizard.chart_template_id.account_root_id.id
490 children_acc_template = account_template_facade.search(cr, uid, [(
491 'parent_id', 'child_of', [root_account_id])], context=context)
492 children_acc_template.sort()
493 for account_template in account_template_facade.browse(cr, uid, children_acc_template, context=context):
494 # Ensure the account template is on the map (search for the mapped
495 # account id).
496 self._map_account_template(cr, uid, wizard, account_template_mapping, account_template, context)
497
498 account_id = account_template_mapping.get(account_template.id)
499 if not account_id:
500 new_accounts += 1
501 wiz_account_facade.create(cr, uid, {
502 'account_id': account_template.id,
503 'update_chart_wizard_id': wizard.id,
504 'type': 'new',
505 }, context)
506 elif wizard.update_account:
507 #
508 # Check the account for changes.
509 #
510 modified = False
511 notes = ""
512 account = account_facade.browse(
513 cr, uid, account_id, context=context)
514
515 if account.name != account_template.name and account.name != wizard.company_id.name:
516 notes += _("The name is different.\n")
517 modified = True
518 if account.type != account_template.type:
519 notes += _("The type is different.\n")
520 modified = True
521 if account.user_type != account_template.user_type:
522 notes += _("The user type is different.\n")
523 modified = True
524 if account.reconcile != account_template.reconcile:
525 notes += _("The reconcile is different.\n")
526 modified = True
527
528 # TODO: We could check other account fields for changes...
529
530 if modified:
531 #
532 # Account to update.
533 #
534 updated_accounts += 1
535 wiz_account_facade.create(cr, uid, {
536 'account_id': account_template.id,
537 'update_chart_wizard_id': wizard.id,
538 'type': 'updated',
539 'update_account_id': account_id,
540 'notes': notes,
541 }, context)
542
543 return {'new': new_accounts, 'updated': updated_accounts, 'mapping': account_template_mapping}
544
545 def _find_fiscal_positions(self, cr, uid, wizard, context=None):
546 """
547 Search for, and load, fiscal position templates to create/update.
548 """
549 new_fps = 0
550 updated_fps = 0
551 fp_template_mapping = {}
552
553 fp_template_facade = self.pool.get('account.fiscal.position.template')
554 fp_facade = self.pool.get('account.fiscal.position')
555 wiz_fp_facade = self.pool.get(
556 'wizard.update.charts.accounts.fiscal.position')
557
558 # Remove previous fiscal positions
559 wiz_fp_facade.unlink(cr, uid, wiz_fp_facade.search(cr, uid, []))
560
561 #
562 # Search for new / updated fiscal positions
563 #
564 fp_template_ids = fp_template_facade.search(cr, uid, [('chart_template_id', '=', wizard.chart_template_id.id)], context=context)
565 for fp_template in fp_template_facade.browse(cr, uid, fp_template_ids, context=context):
566 # Ensure the fiscal position template is on the map (search for the
567 # mapped fiscal position id).
568 self._map_fp_template(
569 cr, uid, wizard, fp_template_mapping, fp_template, context)
570
571 fp_id = fp_template_mapping.get(fp_template.id)
572 if not fp_id:
573 #
574 # New fiscal position template.
575 #
576 new_fps += 1
577 wiz_fp_facade.create(cr, uid, {
578 'fiscal_position_id': fp_template.id,
579 'update_chart_wizard_id': wizard.id,
580 'type': 'new',
581 }, context)
582 elif wizard.update_fiscal_position:
583 #
584 # Check the fiscal position for changes.
585 #
586 modified = False
587 notes = ""
588 fp = fp_facade.browse(cr, uid, fp_id, context=context)
589
590 #
591 # Check fiscal position taxes for changes.
592 #
593 if fp_template.tax_ids and fp.tax_ids:
594 for fp_tax_template in fp_template.tax_ids:
595 found = False
596 for fp_tax in fp.tax_ids:
597 if fp_tax.tax_src_id.name == fp_tax_template.tax_src_id.name:
598 if fp_tax_template.tax_dest_id and fp_tax.tax_dest_id:
599 if fp_tax.tax_dest_id.name == fp_tax_template.tax_dest_id.name:
600 found = True
601 break
602 elif not fp_tax_template.tax_dest_id and not fp_tax.tax_dest_id:
603 found = True
604 break
605 if not found:
606 if fp_tax_template.tax_dest_id:
607 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)
608 else:
609 notes += _("Tax mapping not found on the fiscal position instance: %s -> None.\n") % fp_tax_template.tax_src_id.name
610 modified = True
611 elif fp_template.tax_ids and not fp.tax_ids:
612 notes += _("The template has taxes the fiscal position instance does not.\n")
613 modified = True
614
615 #
616 # Check fiscal position accounts for changes.
617 #
618 if fp_template.account_ids and fp.account_ids:
619 for fp_account_template in fp_template.account_ids:
620 found = False
621 for fp_account in fp.account_ids:
622 if fp_account.account_src_id.name == fp_account_template.account_src_id.name:
623 if fp_account.account_dest_id.name == fp_account_template.account_dest_id.name:
624 found = True
625 break
626 if not found:
627 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)
628 modified = True
629 elif fp_template.account_ids and not fp.account_ids:
630 notes += _("The template has accounts the fiscal position instance does not.\n")
631 modified = True
632
633 if modified:
634 #
635 # Fiscal position template to update.
636 #
637 updated_fps += 1
638 wiz_fp_facade.create(cr, uid, {
639 'fiscal_position_id': fp_template.id,
640 'update_chart_wizard_id': wizard.id,
641 'type': 'updated',
642 'update_fiscal_position_id': fp_id,
643 'notes': notes,
644 }, context)
645
646 return {'new': new_fps, 'updated': updated_fps, 'mapping': fp_template_mapping}
647
648 def action_find_records(self, cr, uid, ids, context=None):
649 """
650 Searchs for records to update/create and shows them
651 """
652 if context is None:
653 context = {}
654 wizard = self.browse(cr, uid, ids[0], context=context)
655
656 if wizard.lang:
657 context['lang'] = wizard.lang
658 elif context.get('lang'):
659 del context['lang']
660
661 #
662 # Search for, and load, the records to create/update.
663 #
664 tax_codes_res = self._find_tax_codes(cr, uid, wizard, context=context)
665 taxes_res = self._find_taxes(cr, uid, wizard, context=context)
666 accounts_res = self._find_accounts(cr, uid, wizard, context=context)
667 fps_res = self._find_fiscal_positions(cr, uid, wizard, context=context)
668
669 #
670 # Write the results, and go to the next step.
671 #
672 self.write(cr, uid, [wizard.id], {
673 'state': 'ready',
674 'new_tax_codes': tax_codes_res.get('new', 0),
675 'new_taxes': taxes_res.get('new', 0),
676 'new_accounts': accounts_res.get('new', 0),
677 'new_fps': fps_res.get('new', 0),
678 'updated_tax_codes': tax_codes_res.get('updated', 0),
679 'updated_taxes': taxes_res.get('updated', 0),
680 'updated_accounts': accounts_res.get('updated', 0),
681 'updated_fps': fps_res.get('updated', 0),
682 }, context)
683
684 return True
685
686 ############################################################################
687 # Update records methods
688 ##########################################################################
689 def _update_tax_codes(self, cr, uid, wizard, log, context=None):
690 """
691 Search for, and load, tax code templates to create/update.
692 """
693 tax_code_facade = self.pool.get('account.tax.code')
694
695 root_tax_code_id = wizard.chart_template_id.tax_code_root_id.id
696
697 new_tax_codes = 0
698 updated_tax_codes = 0
699 tax_code_template_mapping = {}
700
701 for wiz_tax_code in wizard.tax_code_ids:
702 tax_code_template = wiz_tax_code.tax_code_id
703 tax_code_name = (root_tax_code_id == tax_code_template.id) and wizard.company_id.name or tax_code_template.name
704
705 # Ensure the parent tax code template is on the map.
706 self._map_tax_code_template(cr, uid, wizard, tax_code_template_mapping, tax_code_template.parent_id, context)
707
708 #
709 # Values
710 #
711 vals = {
712 'name': tax_code_name,
713 'code': tax_code_template.code,
714 'info': tax_code_template.info,
715 'parent_id': tax_code_template.parent_id and tax_code_template_mapping.get(tax_code_template.parent_id.id),
716 'company_id': wizard.company_id.id,
717 'sign': tax_code_template.sign,
718 }
719
720 tax_code_id = None
721 modified = False
722
723 if wiz_tax_code.type == 'new':
724 #
725 # Create the tax code
726 #
727 tax_code_id = tax_code_facade.create(cr, uid, vals)
728 log.add(_("Created tax code %s.\n") % tax_code_name)
729 new_tax_codes += 1
730 modified = True
731 elif wizard.update_tax_code and wiz_tax_code.update_tax_code_id:
732 #
733 # Update the tax code
734 #
735 tax_code_id = wiz_tax_code.update_tax_code_id.id
736 tax_code_facade.write(cr, uid, [tax_code_id], vals)
737 log.add(_("Updated tax code %s.\n") % tax_code_name)
738 updated_tax_codes += 1
739 modified = True
740 else:
741 tax_code_id = wiz_tax_code.update_tax_code_id and wiz_tax_code.update_tax_code_id.id
742 modified = False
743
744 # Store the tax codes on the map
745 tax_code_template_mapping[tax_code_template.id] = tax_code_id
746
747 if modified:
748 #
749 # Detect errors
750 #
751 if tax_code_template.parent_id and not tax_code_template_mapping.get(tax_code_template.parent_id.id):
752 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)
753
754 return {
755 'new': new_tax_codes,
756 'updated': updated_tax_codes,
757 'mapping': tax_code_template_mapping
758 }
759
760 def _update_taxes(self, cr, uid, wizard, log, tax_code_template_mapping, context=None):
761 """
762 Search for, and load, tax templates to create/update.
763 """
764 tax_facade = self.pool.get('account.tax')
765
766 new_taxes = 0
767 updated_taxes = 0
768 tax_template_mapping = {}
769 taxes_pending_for_accounts = {}
770
771 for wiz_tax in wizard.tax_ids:
772 tax_template = wiz_tax.tax_id
773
774 # Ensure the parent tax template is on the map.
775 self._map_tax_template(cr, uid, wizard, tax_template_mapping,
776 tax_template.parent_id, context)
777
778 #
779 # Ensure the referenced tax codes are on the map.
780 #
781 tax_code_templates_to_find = [
782 tax_template.base_code_id,
783 tax_template.tax_code_id,
784 tax_template.ref_base_code_id,
785 tax_template.ref_tax_code_id
786 ]
787 for tax_code_template in [tmpl for tmpl in tax_code_templates_to_find if tmpl]:
788 self._map_tax_code_template(cr, uid, wizard, tax_code_template_mapping, tax_code_template)
789
790 #
791 # Values
792 #
793 vals_tax = {
794 'name': tax_template.name,
795 'sequence': tax_template.sequence,
796 'amount': tax_template.amount,
797 'type': tax_template.type,
798 'applicable_type': tax_template.applicable_type,
799 'domain': tax_template.domain,
800 'parent_id': tax_template.parent_id and tax_template_mapping.get(tax_template.parent_id.id),
801 'child_depend': tax_template.child_depend,
802 'python_compute': tax_template.python_compute,
803 'python_compute_inv': tax_template.python_compute_inv,
804 'python_applicable': tax_template.python_applicable,
805 #'tax_group': tax_template.tax_group,
806 'base_code_id': tax_template.base_code_id and tax_code_template_mapping.get(tax_template.base_code_id.id),
807 'tax_code_id': tax_template.tax_code_id and tax_code_template_mapping.get(tax_template.tax_code_id.id),
808 'base_sign': tax_template.base_sign,
809 'tax_sign': tax_template.tax_sign,
810 'ref_base_code_id': tax_template.ref_base_code_id and tax_code_template_mapping.get(tax_template.ref_base_code_id.id),
811 'ref_tax_code_id': tax_template.ref_tax_code_id and tax_code_template_mapping.get(tax_template.ref_tax_code_id.id),
812 'ref_base_sign': tax_template.ref_base_sign,
813 'ref_tax_sign': tax_template.ref_tax_sign,
814 'include_base_amount': tax_template.include_base_amount,
815 'description': tax_template.description,
816 'company_id': wizard.company_id.id,
817 'type_tax_use': tax_template.type_tax_use
818 }
819
820 tax_id = None
821 modified = False
822
823 if wiz_tax.type == 'new':
824 #
825 # Create a new tax.
826 #
827 tax_id = tax_facade.create(cr, uid, vals_tax)
828 log.add(_("Created tax %s.\n") % tax_template.name)
829 new_taxes += 1
830 modified = True
831 elif wizard.update_tax and wiz_tax.update_tax_id:
832 #
833 # Update a tax.
834 #
835 tax_id = wiz_tax.update_tax_id.id
836 tax_facade.write(cr, uid, [tax_id], vals_tax)
837 log.add(_("Updated tax %s.\n") % tax_template.name)
838 updated_taxes += 1
839 modified = True
840 else:
841 tax_id = wiz_tax.update_tax_id and wiz_tax.update_tax_id.id
842
843 # Update the tax template map
844 tax_template_mapping[tax_template.id] = tax_id
845
846 if modified:
847 #
848 # Add to the dict of taxes waiting for accounts.
849 #
850 taxes_pending_for_accounts[tax_id] = {
851 'account_collected_id': tax_template.account_collected_id and tax_template.account_collected_id.id or False,
852 'account_paid_id': tax_template.account_paid_id and tax_template.account_paid_id.id or False,
853 }
854
855 #
856 # Detect errors
857 #
858 if tax_template.parent_id and not tax_template_mapping.get(tax_template.parent_id.id):
859 log.add(_("Tax %s: The parent tax %s can not be set.\n") % (tax_template.name, tax_template.parent_id.name), True)
860 if tax_template.base_code_id and not tax_code_template_mapping.get(tax_template.base_code_id.id):
861 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)
862 if tax_template.tax_code_id and not tax_code_template_mapping.get(tax_template.tax_code_id.id):
863 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)
864 if tax_template.ref_base_code_id and not tax_code_template_mapping.get(tax_template.ref_base_code_id.id):
865 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)
866 if tax_template.ref_tax_code_id and not tax_code_template_mapping.get(tax_template.ref_tax_code_id.id):
867 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)
868
869 return {
870 'new': new_taxes,
871 'updated': updated_taxes,
872 'mapping': tax_template_mapping,
873 'pending': taxes_pending_for_accounts
874 }
875
876 def _update_children_accounts_parent(self, cr, uid, wizard, log, parent_account_id, context=None):
877 """
878 Updates the parent_id of accounts that seem to be children of the
879 given account (accounts that start with the same code and are brothers
880 of the first account).
881 """
882 account_facade = self.pool.get('account.account')
883 parent_account = account_facade.browse(
884 cr, uid, parent_account_id, context=context)
885
886 if not parent_account.parent_id or not parent_account.code:
887 return False
888
889 children_ids = account_facade.search(cr, uid, [
890 ('company_id', '=',
891 parent_account.company_id and parent_account.company_id.id),
892 ('parent_id', '=', parent_account.parent_id.id),
893 ('code', '=like', "%s%%" % parent_account.code),
894 ('id', '!=', parent_account.id),
895 ], context=context)
896
897 if children_ids:
898 try:
899 account_facade.write(cr, uid, children_ids, {'parent_id':
900 parent_account.id}, context=context)
901 except osv.except_osv, ex:
902 log.add(_("Exception setting the parent of account %s children: %s - %s.\n") % (parent_account.code, ex.name, ex.value), True)
903
904 return True
905
906 def _update_accounts(self, cr, uid, wizard, log, tax_template_mapping, context=None):
907 """
908 Search for, and load, account templates to create/update.
909 """
910 account_facade = self.pool.get('account.account')
911
912 root_account_id = wizard.chart_template_id.account_root_id.id
913
914 # Disable the parent_store computing on account_account during the batch
915 # processing, we will force _parent_store_compute afterwards.
916 self.pool._init = True
917
918 new_accounts = 0
919 updated_accounts = 0
920 account_template_mapping = {}
921
922 for wiz_account in wizard.account_ids:
923 account_template = wiz_account.account_id
924
925 # Ensure the parent account template is on the map.
926 self._map_account_template(cr, uid, wizard, account_template_mapping, account_template.parent_id, context)
927
928 #
929 # Ensure the related tax templates are on the map.
930 #
931 for tax_template in account_template.tax_ids:
932 self._map_tax_template(cr, uid, wizard, tax_template_mapping,
933 tax_template, context)
934
935 # Get the tax ids
936 tax_ids = [tax_template_mapping[tax_template.id] for tax_template in account_template.tax_ids if tax_template_mapping[tax_template.id]]
937
938 #
939 # Calculate the account code (we need to add zeros to non-view
940 # account codes)
941 #
942 code = account_template.code or ''
943 if account_template.type != 'view':
944 if len(code) > 0 and len(code) <= wizard.code_digits:
945 code = '%s%s' % (
946 code, '0' * (wizard.code_digits - len(code)))
947
948 #
949 # Values
950 #
951 vals = {
952 'name': (root_account_id == account_template.id) and wizard.company_id.name or account_template.name,
953 #'sign': account_template.sign,
954 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
955 'code': code,
956 'type': account_template.type,
957 'user_type': account_template.user_type and account_template.user_type.id or False,
958 'reconcile': account_template.reconcile,
959 'shortcut': account_template.shortcut,
960 'note': account_template.note,
961 'parent_id': account_template.parent_id and account_template_mapping.get(account_template.parent_id.id) or False,
962 'tax_ids': [(6, 0, tax_ids)],
963 'company_id': wizard.company_id.id,
964 }
965
966 account_id = None
967 modified = False
968
969 if wiz_account.type == 'new':
970 #
971 # Create the account
972 #
973 try:
974 account_id = account_facade.create(cr, uid, vals)
975 log.add(_("Created account %s.\n") % code)
976 new_accounts += 1
977 modified = True
978 except osv.except_osv, ex:
979 log.add(_("Exception creating account %s: %s - %s.\n")
980 % (code, ex.name, ex.value), True)
981 elif wizard.update_account and wiz_account.update_account_id:
982 #
983 # Update the account
984 #
985 account_id = wiz_account.update_account_id.id
986 try:
987 account_facade.write(cr, uid, [account_id], vals)
988 log.add(_("Updated account %s.\n") % code)
989 updated_accounts += 1
990 modified = True
991 except osv.except_osv, ex:
992 log.add(_("Exception writing account %s: %s - %s.\n")
993 % (code, ex.name, ex.value), True)
994 else:
995 account_id = wiz_account.update_account_id and wiz_account.update_account_id.id
996
997 # Store the account on the map
998 account_template_mapping[account_template.id] = account_id
999
1000 if modified:
1001 #
1002 # Detect errors
1003 #
1004 if account_template.parent_id and not account_template_mapping.get(account_template.parent_id.id):
1005 log.add(_("Account %s: The parent account %s can not be set.\n") % (code, account_template.parent_id.code), True)
1006
1007 #
1008 # Set this account as the parent of the accounts that seem to
1009 # be its children (brothers starting with the same code).
1010 #
1011 if wizard.update_children_accounts_parent:
1012 self._update_children_accounts_parent(
1013 cr, uid, wizard, log, account_id, context=context)
1014
1015 #
1016 # Reenable the parent_store computing on account_account
1017 # and force the recomputation.
1018 #
1019 self.pool._init = False
1020 self.pool.get('account.account')._parent_store_compute(cr)
1021
1022 return {
1023 'new': new_accounts,
1024 'updated': updated_accounts,
1025 'mapping': account_template_mapping
1026 }
1027
1028 def _update_taxes_pending_for_accounts(self, cr, uid, wizard, log, taxes_pending_for_accounts, account_template_mapping, context=None):
1029 """
1030 Updates the taxes (created or updated on previous steps) to set
1031 the references to the accounts (the taxes where created/updated first,
1032 when the referenced accounts where still not available).
1033 """
1034 tax_facade = self.pool.get('account.tax')
1035 account_template_facade = self.pool.get('account.account.template')
1036
1037 for key, value in taxes_pending_for_accounts.items():
1038 #
1039 # Ensure the related account templates are on the map.
1040 #
1041 if value['account_collected_id']:
1042 account_template = account_template_facade.browse(
1043 cr, uid, value['account_collected_id'], context=context)
1044 self._map_account_template(cr, uid, wizard, account_template_mapping, account_template, context)
1045 if value['account_paid_id']:
1046 account_template = account_template_facade.browse(
1047 cr, uid, value['account_paid_id'], context=context)
1048 self._map_account_template(cr, uid, wizard, account_template_mapping, account_template, context)
1049
1050 if value['account_collected_id'] or value['account_paid_id']:
1051 if account_template_mapping.get(value['account_collected_id']) and account_template_mapping.get(value['account_paid_id']):
1052 vals = {
1053 'account_collected_id': account_template_mapping[value['account_collected_id']],
1054 'account_paid_id': account_template_mapping[value['account_paid_id']],
1055 }
1056 tax_facade.write(cr, uid, [key], vals)
1057 else:
1058 tax = tax_facade.browse(cr, uid, key)
1059 if not account_template_mapping.get(value['account_collected_id']):
1060 log.add(_("Tax %s: The collected account can not be set.\n") % (tax.name), True)
1061 if not account_template_mapping.get(value['account_paid_id']):
1062 log.add(_("Tax %s: The paid account can not be set.\n")
1063 % (tax.name), True)
1064
1065 def _update_fiscal_positions(self, cr, uid, wizard, log, tax_template_mapping, account_template_mapping, context=None):
1066 """
1067 Search for, and load, fiscal position templates to create/update.
1068 """
1069 fp_facade = self.pool.get('account.fiscal.position')
1070 fp_tax_facade = self.pool.get('account.fiscal.position.tax')
1071 fp_account_facade = self.pool.get('account.fiscal.position.account')
1072
1073 new_fps = 0
1074 updated_fps = 0
1075
1076 for wiz_fp in wizard.fiscal_position_ids:
1077 fp_template = wiz_fp.fiscal_position_id
1078
1079 fp_id = None
1080 modified = False
1081 if wiz_fp.type == 'new':
1082 #
1083 # Create a new fiscal position
1084 #
1085 vals_fp = {
1086 'company_id': wizard.company_id.id,
1087 'name': fp_template.name,
1088 }
1089 fp_id = fp_facade.create(cr, uid, vals_fp)
1090 new_fps += 1
1091 modified = True
1092 elif wizard.update_fiscal_position and wiz_fp.update_fiscal_position_id:
1093 #
1094 # Update the given fiscal position (remove the tax and account
1095 # mappings, that will be regenerated later)
1096 #
1097 fp_id = wiz_fp.update_fiscal_position_id.id
1098 updated_fps += 1
1099 modified = True
1100 # Remove the tax mappings
1101 fp_tax_ids = fp_tax_facade.search(
1102 cr, uid, [('position_id', '=', fp_id)])
1103 fp_tax_facade.unlink(cr, uid, fp_tax_ids)
1104 # Remove the account mappings
1105 fp_account_ids = fp_account_facade.search(
1106 cr, uid, [('position_id', '=', fp_id)])
1107 fp_account_facade.unlink(cr, uid, fp_account_ids)
1108 else:
1109 fp_id = wiz_fp.update_fiscal_position_id and wiz_fp.update_fiscal_position_id.id
1110
1111 if modified:
1112 #
1113 # (Re)create the tax mappings
1114 #
1115 for fp_tax in fp_template.tax_ids:
1116 #
1117 # Ensure the related tax templates are on the map.
1118 #
1119 self._map_tax_template(cr, uid, wizard, tax_template_mapping, fp_tax.tax_src_id, context)
1120 if fp_tax.tax_dest_id:
1121 self._map_tax_template(cr, uid, wizard, tax_template_mapping, fp_tax.tax_dest_id, context)
1122
1123 #
1124 # Create the fp tax mapping
1125 #
1126 vals_tax = {
1127 'tax_src_id': tax_template_mapping.get(fp_tax.tax_src_id.id),
1128 'tax_dest_id': fp_tax.tax_dest_id and tax_template_mapping.get(fp_tax.tax_dest_id.id),
1129 'position_id': fp_id,
1130 }
1131 fp_tax_facade.create(cr, uid, vals_tax)
1132
1133 #
1134 # Check for errors
1135 #
1136 if not tax_template_mapping.get(fp_tax.tax_src_id.id):
1137 log.add(_("Fiscal position %s: The source tax %s can not be set.\n") % (fp_template.name, fp_tax.tax_src_id.code), True)
1138 if fp_tax.tax_dest_id and not tax_template_mapping.get(fp_tax.tax_dest_id.id):
1139 log.add(_("Fiscal position %s: The destination tax %s can not be set.\n") % (fp_template.name, fp_tax.tax_dest_id.name), True)
1140 #
1141 # (Re)create the account mappings
1142 #
1143 for fp_account in fp_template.account_ids:
1144 #
1145 # Ensure the related account templates are on the map.
1146 #
1147 self._map_account_template(cr, uid, wizard, account_template_mapping, fp_account.account_src_id, context)
1148 if fp_account.account_dest_id:
1149 self._map_account_template(cr, uid, wizard, account_template_mapping, fp_account.account_dest_id, context)
1150
1151 #
1152 # Create the fp account mapping
1153 #
1154 vals_account = {
1155 'account_src_id': account_template_mapping.get(fp_account.account_src_id.id),
1156 'account_dest_id': fp_account.account_dest_id and account_template_mapping.get(fp_account.account_dest_id.id),
1157 'position_id': fp_id,
1158 }
1159 fp_account_facade.create(cr, uid, vals_account)
1160
1161 #
1162 # Check for errors
1163 #
1164 if not account_template_mapping.get(fp_account.account_src_id.id):
1165 log.add(_("Fiscal position %s: The source account %s can not be set.\n") % (fp_template.name, fp_account.account_src_id.code), True)
1166 if fp_account.account_dest_id and not account_template_mapping.get(fp_account.account_dest_id.id):
1167 log.add(_("Fiscal position %s: The destination account %s can not be set.\n") % (fp_template.name, fp_account.account_dest_id.code), True)
1168
1169 log.add(_("Created or updated fiscal position %s.\n")
1170 % fp_template.name)
1171 return {'new': new_fps, 'updated': updated_fps}
1172
1173 def action_update_records(self, cr, uid, ids, context=None):
1174 """
1175 Action that creates/updates the selected elements.
1176 """
1177 if context is None:
1178 context = {}
1179 wizard = self.browse(cr, uid, ids[0], context=context)
1180
1181 if wizard.lang:
1182 context['lang'] = wizard.lang
1183 elif context.get('lang'):
1184 del context['lang']
1185
1186 log = WizardLog()
1187
1188 #
1189 # Create or update the records.
1190 #
1191 tax_codes_res = self._update_tax_codes(
1192 cr, uid, wizard, log, context=context)
1193 taxes_res = self._update_taxes(
1194 cr, uid, wizard, log, tax_codes_res['mapping'], context=context)
1195 accounts_res = self._update_accounts(
1196 cr, uid, wizard, log, taxes_res['pending'], context=context)
1197 self._update_taxes_pending_for_accounts(cr, uid, wizard, log, taxes_res['pending'], accounts_res['mapping'], context=context)
1198 fps_res = self._update_fiscal_positions(cr, uid, wizard, log, taxes_res['mapping'], accounts_res['mapping'], context=context)
1199
1200 #
1201 # Check if errors where detected and wether we should stop.
1202 #
1203 if log.has_errors() and not wizard.continue_on_errors:
1204 raise osv.except_osv(_('Error'), _(
1205 "One or more errors detected!\n\n%s") % log.get_errors_str())
1206
1207 #
1208 # Store the data and go to the next step.
1209 #
1210 self.write(cr, uid, [wizard.id], {
1211 'state': 'done',
1212 'new_tax_codes': tax_codes_res.get('new', 0),
1213 'new_taxes': taxes_res.get('new', 0),
1214 'new_accounts': accounts_res .get('new', 0),
1215 'new_fps': fps_res.get('new', 0),
1216 'updated_tax_codes': tax_codes_res.get('updated', 0),
1217 'updated_taxes': taxes_res.get('updated', 0),
1218 'updated_accounts': accounts_res.get('updated', 0),
1219 'updated_fps': fps_res.get('updated', 0),
1220 'log': log(),
1221 }, context)
1222
1223 return True
1224
1225wizard_update_charts_accounts()
1226
1227
1228class wizard_update_charts_accounts_tax_code(osv.osv_memory):
1229 """
1230 Tax code that needs to be updated (new or updated in the template).
1231 """
1232 _name = 'wizard.update.charts.accounts.tax.code'
1233
1234 _columns = {
1235 'tax_code_id': fields.many2one('account.tax.code.template', 'Tax code template', required=True),
1236 'update_chart_wizard_id': fields.many2one('wizard.update.charts.accounts', 'Update chart wizard', required=True),
1237 'type': fields.selection([
1238 ('new', 'New template'),
1239 ('updated', 'Updated template'),
1240 ], 'Type'),
1241 'update_tax_code_id': fields.many2one('account.tax.code', 'Tax code to update', required=False),
1242 'notes': fields.text('Notes', readonly=True),
1243 }
1244
1245 _defaults = {
1246 #'update_tax_code_id': lambda *a: None,
1247 }
1248
1249wizard_update_charts_accounts_tax_code()
1250
1251
1252class wizard_update_charts_accounts_tax(osv.osv_memory):
1253 """
1254 Tax that needs to be updated (new or updated in the template).
1255 """
1256 _name = 'wizard.update.charts.accounts.tax'
1257
1258 _columns = {
1259 'tax_id': fields.many2one('account.tax.template', 'Tax template', required=True),
1260 'update_chart_wizard_id': fields.many2one('wizard.update.charts.accounts', 'Update chart wizard', required=True),
1261 'type': fields.selection([
1262 ('new', 'New template'),
1263 ('updated', 'Updated template'),
1264 ], 'Type'),
1265 'update_tax_id': fields.many2one('account.tax', 'Tax to update', required=False),
1266 'notes': fields.text('Notes', readonly=True),
1267 }
1268
1269 _defaults = {
1270 #'update_tax_id': lambda *a: None,
1271 }
1272
1273wizard_update_charts_accounts_tax()
1274
1275
1276class wizard_update_charts_accounts_account(osv.osv_memory):
1277 """
1278 Account that needs to be updated (new or updated in the template).
1279 """
1280 _name = 'wizard.update.charts.accounts.account'
1281
1282 # The chart of accounts can have a lot of accounts, so we need an higher
1283 # limit for the objects in memory to let the wizard create all the items
1284 # at once.
1285 _max_count = 4096
1286
1287 _columns = {
1288 'account_id': fields.many2one('account.account.template', 'Account template', required=True),
1289 'update_chart_wizard_id': fields.many2one('wizard.update.charts.accounts', 'Update chart wizard', required=True),
1290 'type': fields.selection([
1291 ('new', 'New template'),
1292 ('updated', 'Updated template'),
1293 ], 'Type'),
1294 'update_account_id': fields.many2one('account.account', 'Account to update', required=False),
1295 'notes': fields.text('Notes', readonly=True),
1296 }
1297
1298 _defaults = {
1299 #'update_account_id': lambda *a: None,
1300 }
1301
1302wizard_update_charts_accounts_account()
1303
1304
1305class wizard_update_charts_accounts_fiscal_position(osv.osv_memory):
1306 """
1307 Fiscal position that needs to be updated (new or updated in the template).
1308 """
1309 _name = 'wizard.update.charts.accounts.fiscal.position'
1310
1311 _columns = {
1312 'fiscal_position_id': fields.many2one('account.fiscal.position.template', 'Fiscal position template', required=True),
1313 'update_chart_wizard_id': fields.many2one('wizard.update.charts.accounts', 'Update chart wizard', required=True),
1314 'type': fields.selection([
1315 ('new', 'New template'),
1316 ('updated', 'Updated template'),
1317 ], 'Type'),
1318 'update_fiscal_position_id': fields.many2one('account.fiscal.position', 'Fiscal position to update', required=False),
1319 'notes': fields.text('Notes', readonly=True),
1320 }
1321
1322 _defaults = {
1323 #'update_fiscal_position_id': lambda *a: None,
1324 }
1325
1326
1327wizard_update_charts_accounts_fiscal_position()
1328
1329# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
01330
=== added file 'account_chart_update/account_view.xml'
--- account_chart_update/account_view.xml 1970-01-01 00:00:00 +0000
+++ account_chart_update/account_view.xml 2012-12-07 10:00:37 +0000
@@ -0,0 +1,159 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4
5 <!-- Wizard for Multi Charts of Accounts -->
6
7 <record id="view_update_multi_chart" model="ir.ui.view">
8 <field name="name">Update Chart of Accounts from a Chart Template</field>
9 <field name="model">wizard.update.charts.accounts</field>
10 <field name="type">form</field>
11 <field name="arch" type="xml">
12 <form string="Update Chart of Accounts from a Chart Template">
13 <group col="4" colspan="4" attrs="{'invisible':[('state','!=','init')]}">
14 <label string="This wizard will update your accounts, taxes and fiscal positions according to the selected chart template." colspan="4"/>
15 <label string="" colspan="4"/>
16 <group colspan="4">
17 <separator col="4" colspan="4" string="Chart of Accounts"/>
18 <field name="company_id" on_change="onchange_company_id(company_id)"/>
19 <field name="code_digits"/>
20 <field name="chart_template_id"/>
21 <field name="lang"/>
22 </group>
23 <label string=""/>
24 <group colspan="4">
25 <separator string="Update records?" colspan="4"/>
26 <group colspan="2" col="2">
27 <field name="update_tax_code"/>
28 <field name="update_tax"/>
29 <field name="update_account"/>
30 <field name="update_fiscal_position"/>
31 </group>
32 <group colspan="2">
33 <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"/>
34 <label string="Note: Not all the fields are tested for changes, just the main ones." colspan="4" align="0.0"/>
35 </group>
36 </group>
37 <group colspan="4">
38 <separator string="Other options" colspan="4"/>
39 <field name="update_children_accounts_parent"/>
40 <field name="continue_on_errors"/>
41 </group>
42 </group>
43
44 <group col="4" colspan="4" attrs="{'invisible':[('state','!=','ready')]}">
45 <separator colspan="4" string="Records to create/update"/>
46 <notebook colspan="4">
47 <page string="Tax codes">
48 <field name="tax_code_ids" colspan="4" nolabel="1" mode="tree,form" height="330">
49 <tree string="Tax codes" colors="red:type=='updated'">
50 <field name="tax_code_id"/>
51 <field name="update_tax_code_id"/>
52 <field name="type" invisible="1"/>
53 </tree>
54 <form string="Tax code">
55 <field name="tax_code_id" colspan="4"/>
56 <field name="type"/>
57 <field name="update_tax_code_id"/>
58 <separator string="Notes" colspan="4"/>
59 <field name="notes" colspan="4" nolabel="1"/>
60 </form>
61 </field>
62 </page>
63 <page string="Taxes">
64 <field name="tax_ids" colspan="4" nolabel="1" mode="tree,form" height="330">
65 <tree string="Taxes" colors="red:type=='updated'">
66 <field name="tax_id"/>
67 <field name="update_tax_id"/>
68 <field name="type" invisible="1"/>
69 </tree>
70 <form string="Tax">
71 <field name="tax_id" colspan="4"/>
72 <field name="type"/>
73 <field name="update_tax_id"/>
74 <separator string="Notes" colspan="4"/>
75 <field name="notes" colspan="4" nolabel="1"/>
76 </form>
77 </field>
78 </page>
79 <page string="Accounts">
80 <field name="account_ids" colspan="4" nolabel="1" mode="tree,form" height="330">
81 <tree string="Accounts" colors="red:type=='updated'">
82 <field name="account_id"/>
83 <field name="update_account_id"/>
84 <field name="type" invisible="1"/>
85 </tree>
86 <form string="Account">
87 <field name="account_id" colspan="4"/>
88 <field name="type"/>
89 <field name="update_account_id"/>
90 <separator string="Notes" colspan="4"/>
91 <field name="notes" colspan="4" nolabel="1"/>
92 </form>
93 </field>
94 </page>
95 <page string="Fiscal positions">
96 <field name="fiscal_position_ids" colspan="4" nolabel="1" mode="tree,form" height="330">
97 <tree string="Fiscal positions" colors="red:type=='updated'">
98 <field name="fiscal_position_id"/>
99 <field name="update_fiscal_position_id"/>
100 <field name="type" invisible="1"/>
101 </tree>
102 <form string="Fiscal position">
103 <field name="fiscal_position_id" colspan="4"/>
104 <field name="type"/>
105 <field name="update_fiscal_position_id"/>
106 <separator string="Notes" colspan="4"/>
107 <field name="notes" colspan="4" nolabel="1"/>
108 </form>
109 </field>
110 </page>
111 </notebook>
112 </group>
113
114 <group col="4" colspan="4" attrs="{'invisible':[('state','!=','done'),]}">
115 <separator colspan="4" string="Log"/>
116 <field name="log" colspan="4" nolabel="1"/>
117 <group colspan="4">
118 <separator colspan="4" string="Summary of created objects"/>
119 <field name="new_tax_codes"/>
120 <field name="new_taxes"/>
121 <field name="new_accounts"/>
122 <field name="new_fps"/>
123 </group>
124 <group colspan="4">
125 <separator colspan="4" string="Summary of updated objects"/>
126 <field name="updated_tax_codes"/>
127 <field name="updated_taxes"/>
128 <field name="updated_accounts"/>
129 <field name="updated_fps"/>
130 </group>
131 </group>
132
133 <separator string="" colspan="4"/>
134 <group col="8" colspan="4">
135 <field name="state"/>
136 <button icon="gtk-cancel" special="cancel" string="Cancel" states="init,ready"/>
137 <button icon="gtk-go-forward" name="action_find_records" string="Next" type="object" states="init"/>
138 <button icon="gtk-go-back" name="action_init" string="Previous" type="object" states="ready"/>
139 <button icon="gtk-ok" name="action_update_records" string="Create/Update" type="object" states="ready"/>
140 <button icon="gtk-ok" special="cancel" string="Ok" type="object" states="done"/>
141 </group>
142
143 </form>
144 </field>
145 </record>
146
147 <record id="action_wizard_update_chart" model="ir.actions.act_window">
148 <field name="name">Update Chart of Accounts from a Chart Template</field>
149 <field name="type">ir.actions.act_window</field>
150 <field name="res_model">wizard.update.charts.accounts</field>
151 <field name="view_type">form</field>
152 <field name="view_mode">form</field>
153 <field name="target">new</field>
154 </record>
155
156 <menuitem parent="account.account_template_folder" action="action_wizard_update_chart" id="menu_wizard"/>
157
158 </data>
159</openerp>
0160
=== added directory 'account_chart_update/i18n'
=== added file 'account_chart_update/i18n/account_chart_update.pot'
--- account_chart_update/i18n/account_chart_update.pot 1970-01-01 00:00:00 +0000
+++ account_chart_update/i18n/account_chart_update.pot 2012-12-07 10:00:37 +0000
@@ -0,0 +1,744 @@
1# Translation of OpenERP Server.
2# This file contains the translation of the following modules:
3# * account_chart_update
4#
5msgid ""
6msgstr ""
7"Project-Id-Version: OpenERP Server 5.0.10\n"
8"Report-Msgid-Bugs-To: support@openerp.com\n"
9"POT-Creation-Date: 2010-06-10 15:41:54+0000\n"
10"PO-Revision-Date: 2010-06-10 15:41:54+0000\n"
11"Last-Translator: <>\n"
12"Language-Team: \n"
13"MIME-Version: 1.0\n"
14"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Transfer-Encoding: \n"
16"Plural-Forms: \n"
17
18#. module: account_chart_update
19#: field:wizard.update.charts.accounts,lang:0
20msgid "Language"
21msgstr ""
22
23#. module: account_chart_update
24#: code:addons/account_chart_update/account.py:0
25#, python-format
26msgid "Created or updated fiscal position %s.\n"
27msgstr ""
28
29#. module: account_chart_update
30#: code:addons/account_chart_update/account.py:0
31#, python-format
32msgid "Exception creating account %s: %s - %s.\n"
33msgstr ""
34
35#. module: account_chart_update
36#: code:addons/account_chart_update/account.py:0
37#, python-format
38msgid "Exception writing account %s: %s - %s.\n"
39msgstr ""
40
41#. module: account_chart_update
42#: code:addons/account_chart_update/account.py:0
43#, python-format
44msgid "Exception setting the parent of account %s children: %s - %s.\n"
45msgstr ""
46
47#. module: account_chart_update
48#: code:addons/account_chart_update/account.py:0
49#, python-format
50msgid "Updated account %s.\n"
51msgstr ""
52
53#. module: account_chart_update
54#: code:addons/account_chart_update/account.py:0
55#, python-format
56msgid "Created account %s.\n"
57msgstr ""
58
59#. module: account_chart_update
60#: code:addons/account_chart_update/account.py:0
61#, python-format
62msgid "Created tax %s.\n"
63msgstr ""
64
65#. module: account_chart_update
66#: code:addons/account_chart_update/account.py:0
67#, python-format
68msgid "Updated tax %s.\n"
69msgstr ""
70
71#. module: account_chart_update
72#: code:addons/account_chart_update/account.py:0
73#, python-format
74msgid "Created tax code %s.\n"
75msgstr ""
76
77#. module: account_chart_update
78#: code:addons/account_chart_update/account.py:0
79#, python-format
80msgid "Updated tax code %s.\n"
81msgstr ""
82
83#. module: account_chart_update
84#: code:addons/account_chart_update/account.py:0
85#, python-format
86msgid "The template has accounts the fiscal position instance does not.\n"
87msgstr ""
88
89#. module: account_chart_update
90#: code:addons/account_chart_update/account.py:0
91#, python-format
92msgid "The base sign field is different.\n"
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches