[FIX] account: Fixed installing chart of account problem.
[odoo/odoo.git] / addons / account / res_config.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 import logging
23 import time
24 import datetime
25 from dateutil.relativedelta import relativedelta
26 from operator import itemgetter
27 from os.path import join as opj
28
29 from tools.translate import _
30 from osv import fields, osv
31 import netsvc
32 import tools
33
34 class account_configuration(osv.osv_memory):
35     _name = 'account.installer'
36     _inherit = 'res.config.settings'
37     __logger = logging.getLogger(_name)
38     
39     def _get_charts(self, cr, uid, context=None):
40         modules = self.pool.get('ir.module.module')
41         # Looking for the module with the 'Account Charts' category
42         category_name, category_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'module_category_localization_account_charts')
43         ids = modules.search(cr, uid, [('category_id', '=', category_id)], context=context)
44         charts = list(
45             sorted(((m.name, m.shortdesc)
46                     for m in modules.browse(cr, uid, ids, context=context)),
47                    key=itemgetter(1)))
48         charts.insert(0, ('configurable', 'Generic Chart Of Accounts'))
49         return charts
50
51     _columns = {
52             'company_id': fields.many2one('res.company', 'Company',help="Your company."),
53             'currency_id': fields.related('company_id', 'currency_id', type='many2one', relation='res.currency', string='Currency', store=True, help="Currency of your company."),
54             'sale_tax': fields.float('Default Sale Tax'),
55             'purchase_tax': fields.float('Default Purchase Tax'),
56             'charts': fields.selection(_get_charts, 'Chart of Accounts',
57                                         required=True,
58                                         help="Installs localized accounting charts to match as closely as "
59                                              "possible the accounting needs of your company based on your "
60                                              "country."),
61             'date_start': fields.date('Start Date', required=True),
62             'date_stop': fields.date('End Date', required=True),
63             'period': fields.selection([('month', 'Monthly'), ('3months','3 Monthly')], 'Periods', required=True),
64             'has_default_company' : fields.boolean('Has Default Company', readonly=True),
65             'chart_template_id': fields.many2one('account.chart.template', 'Chart Template'),
66             'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year'),
67             'default_paypal_account': fields.char("Your Paypal Account", size=128, help="Paypal username (usually email) for receiving online payments.", default_model='res.company'),
68             'company_footer': fields.char("Footer of Reports", size=128, readonly=True, help="Footer of reports based on your bank accounts."),
69             'sale_journal_id': fields.many2one('account.journal','Sale Journal'),
70             'customer_invoice_sequence_prefix': fields.related('sale_journal_id', 'sequence_id', 'prefix', type='char', relation='ir.sequence', string='Invoice Sequence'),
71             'customer_invoice_sequence_next': fields.related('sale_journal_id', 'sequence_id', 'number_next', type='integer', relation='ir.sequence', string='Invoice Sequence Next Number'),
72             'sale_refund_journal_id': fields.many2one('account.journal','Sale Refund Journal'),
73             'customer_refund_sequence_prefix': fields.related('sale_refund_journal_id', 'sequence_id', 'prefix', type='char', relation='ir.sequence', string='Refund Sequence'),
74             'customer_refund_sequence_next': fields.related('sale_refund_journal_id', 'sequence_id', 'number_next', type='integer', relation='ir.sequence', string='Refund Sequence Next Number'),
75
76             'purchase_journal_id': fields.many2one('account.journal','Purchase Journal'),
77             'supplier_invoice_sequence_prefix': fields.related('purchase_journal_id', 'sequence_id', 'prefix', type='char', relation='ir.sequence', string='Supplier Invoice Sequence'),
78             'supplier_invoice_sequence_next': fields.related('purchase_journal_id', 'sequence_id', 'number_next', type='integer', relation='ir.sequence', string='Supplier Invoice Sequence Next Number'),
79             'purchase_refund_journal_id': fields.many2one('account.journal','Purchase Refund Journal'),
80             'supplier_refund_sequence_prefix': fields.related('purchase_refund_journal_id', 'sequence_id', 'prefix', type='char', relation='ir.sequence', string='Supplier Refund Sequence'),
81             'supplier_refund_sequence_next': fields.related('purchase_refund_journal_id', 'sequence_id', 'number_next', type='integer', relation='ir.sequence', string='Supplier Refund Sequence Next Number'),
82
83             'module_account_check_writing': fields.boolean('Support check writings',
84                                             help="""  This allows you to check writing and printing.
85                                             It installs the account_check_writing module."""),
86             'module_account_accountant': fields.boolean('Accountant Features',
87                                         help="""This allows you to access all the accounting features like the journal items and the chart of accounts.
88                                         It installs the account_accountant module."""),
89             'module_account_asset': fields.boolean('Assets Management',
90                                     help="""This allows you to manages the assets owned by a company or an individual. It will keep track of depreciation's occurred on
91                                     those assets. And it allows to create Move's of the depreciation lines.
92                                     It installs the account_asset module."""),
93             'module_account_budget': fields.boolean('Budgets Management',
94                                     help="""This allows accountants to manage analytic and crossovered budgets.
95                                     Once the Master Budgets and the Budgets are defined (in Accounting/Budgets/),
96                                     the Project Managers can set the planned amount on each Analytic Account.
97                                     It installs the account_budget module."""),
98             'module_account_payment': fields.boolean('Supplier Payment Orders',
99                                     help="""This allows you to create and manage your payment orders, with purposes to
100                                     * serve as base for an easy plug-in of various automated payment mechanisms.
101                                     * provide a more efficient way to manage invoice payment.
102                                     It installs the account_payment module."""),
103             'module_account_voucher': fields.boolean('Manage Customer Payments',
104                                     help="""This includes all the basic requirements of Voucher Entries for Bank, Cash, Sales, Purchase, Expanse, Contra, etc.
105                                     It installs the account_voucher module."""),
106             'module_account_followup': fields.boolean('Customer Follow-Ups',
107                                     help="""This allows to automate letters for unpaid invoices, with multi-level recalls.
108                                     It installs the account_followup module."""),
109             'module_account_analytic_plans': fields.boolean('Support Multiple Analytic Plans',
110                                         help="""This allows to use several analytic plans, according to the general journal.
111                                         It installs the account_analytic_plans module."""),
112             'module_account_analytic_default': fields.boolean('Rules for Analytic Assignation',
113                                             help="""Set default values for your analytic accounts
114                                             Allows to automatically select analytic accounts based on criterias:
115                                             * Product
116                                             * Partner
117                                             * User
118                                             * Company
119                                             * Date.
120                                         It installs the account_analytic_default module."""),
121             'module_account_invoice_layout': fields.boolean('Allow notes and subtotals',
122                                             help="""This provides some features to improve the layout of the invoices.
123                                             It gives you the possibility to:
124                                             * order all the lines of an invoice
125                                             * add titles, comment lines, sub total lines
126                                             * draw horizontal lines and put page breaks.
127                                             It installs the account_invoice_layout module."""),
128
129             'group_analytic_account_for_sales': fields.boolean('Analytic Accounting for Sales', group='base.group_user', implied_group='base.group_analytic_account_for_sales',
130                                                                help="Allows you to set analytic account for sale order. It assigns 'Analytic Accounting for Sales' group to all employees."),
131             'group_analytic_account_for_purchase': fields.boolean('Analytic Accounting for Purchase', group='base.group_user', implied_group='base.group_analytic_account_for_purchase',
132                                                                   help="Allows you to set analytic account for purchase order. It assigns 'Analytic Accounting for Purchase' group to all employees."),
133             'group_dates_periods': fields.boolean('Allow dates/periods', group='base.group_user', implied_group='base.group_dates_periods',
134                                                   help="Allows you to keep the period same as your invoice date when you validate the invoice."\
135                                                        "It will add the group 'Allow dates and periods' for all users."),
136             'group_proforma_invoices': fields.boolean('Allow Pro-forma Invoices', group='base.group_user', implied_group='base.group_proforma_invoices',
137                                                       help="Allows you to put invoice in pro-forma state. It assigns 'Allow Pro-forma Invoices' group to all employees."),
138     }
139
140     def _default_company(self, cr, uid, context=None):
141         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
142         return user.company_id and user.company_id.id or False
143
144     def _default_has_default_company(self, cr, uid, context=None):
145         count = self.pool.get('res.company').search_count(cr, uid, [], context=context)
146         return bool(count == 1)
147
148     _defaults = {
149             'date_start': lambda *a: time.strftime('%Y-01-01'),
150             'date_stop': lambda *a: time.strftime('%Y-12-31'),
151             'period': 'month',
152             'company_id': _default_company,
153             'has_default_company': _default_has_default_company,
154             'charts': 'configurable',
155     }
156
157     def _check_default_tax(self, cr, uid, context=None):
158         ir_values_obj = self.pool.get('ir.values')
159         taxes = {}
160         for tax in ir_values_obj.get(cr, uid, 'default', False, ['product.product']):
161             if tax[1] == 'taxes_id':
162                 taxes.update({'taxes_id': tax[2]})
163             if tax[1] == 'supplier_taxes_id':
164                 taxes.update({'supplier_taxes_id': tax[2]})
165             return taxes
166         return False
167
168     def default_get(self, cr, uid, fields_list, context=None):
169         ir_values_obj = self.pool.get('ir.values')
170         chart_template_obj = self.pool.get('account.chart.template')
171         fiscalyear_obj = self.pool.get('account.fiscalyear')
172         journal_obj = self.pool.get('account.journal')
173         res = super(account_configuration, self).default_get(cr, uid, fields_list, context=context)
174         res.update({'sale_tax': 15.0, 'purchase_tax': 15.0})
175         taxes = self._check_default_tax(cr, uid, context)
176         chart_template_ids = chart_template_obj.search(cr, uid, [('visible', '=', True)], context=context)
177         fiscalyear_ids = fiscalyear_obj.search(cr, uid, [('date_start','=',time.strftime('%Y-01-01')),('date_stop','=',time.strftime('%Y-12-31'))])
178
179         cmp_id = self.pool.get('ir.model.data').get_object(cr, uid, 'base', 'main_company').id
180         company_data = self.pool.get('res.company').browse(cr, uid, cmp_id)
181         res.update({'company_footer': company_data.rml_footer2})
182
183         journal_ids = journal_obj.search(cr, uid, [('company_id', '=', res.get('company_id'))])
184         if journal_ids:
185             for journal in journal_obj.browse(cr, uid, journal_ids, context=context):
186                 if journal.type == 'sale':
187                     res.update({'sale_journal_id': journal.id})
188                 if journal.type == 'sale_refund':
189                     res.update({'sale_refund_journal_id': journal.id})
190                 if journal.type == 'purchase':
191                     res.update({'purchase_journal_id': journal.id})
192                 if journal.type == 'purchase_refund':
193                     res.update({'purchase_refund_journal_id': journal.id})
194
195         if chart_template_ids:
196             res.update({'chart_template_id': chart_template_ids[0]})
197         if fiscalyear_ids:
198             res.update({'fiscalyear_id': fiscalyear_ids[0]})
199         if taxes:
200             sale_tax_id = taxes.get('taxes_id')
201             res.update({'sale_tax': isinstance(sale_tax_id,list) and sale_tax_id[0] or False}) 
202             purchase_tax_id = taxes.get('supplier_taxes_id')
203             res.update({'purchase_tax': isinstance(purchase_tax_id,list) and purchase_tax_id[0] or False})
204         return res
205     
206     def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
207         ir_values_obj = self.pool.get('ir.values')
208         res = super(account_configuration, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)
209         cmp_select = []
210         if self._check_default_tax(cr, uid, context):
211             if res['fields'].get('sale_tax') and res['fields'].get('purchase_tax'):
212                 res['fields']['sale_tax'] = {'domain': [], 'views': {}, 'context': {}, 'selectable': True, 'type': 'many2one', 'relation': 'account.tax', 'string': 'Default Sale Tax'}
213                 res['fields']['purchase_tax'] = {'domain': [], 'views': {}, 'context': {}, 'selectable': True, 'type': 'many2one', 'relation': 'account.tax', 'string': 'Default Purchase Tax'}
214         # display in the widget selection only the companies that haven't been configured yet
215         unconfigured_cmp = self.get_unconfigured_cmp(cr, uid, context=context)
216         for field in res['fields']:
217             if field == 'company_id':
218                 res['fields'][field]['domain'] = [('id','in',unconfigured_cmp)]
219                 res['fields'][field]['selection'] = [('', '')]
220                 if unconfigured_cmp:
221                     cmp_select = [(line.id, line.name) for line in self.pool.get('res.company').browse(cr, uid, unconfigured_cmp)]
222                     res['fields'][field]['selection'] = cmp_select
223         return res
224
225     def get_unconfigured_cmp(self, cr, uid, context=None):
226         """ get the list of companies that have not been configured yet
227         but don't care about the demo chart of accounts """
228         cmp_select = []
229         company_ids = self.pool.get('res.company').search(cr, uid, [], context=context)
230         cr.execute("SELECT company_id FROM account_account WHERE active = 't' AND account_account.parent_id IS NULL AND name != %s", ("Chart For Automated Tests",))
231         configured_cmp = [r[0] for r in cr.fetchall()]
232         return list(set(company_ids)-set(configured_cmp))
233
234     def check_unconfigured_cmp(self, cr, uid, context=None):
235         """ check if there are still unconfigured companies """
236         if not self.get_unconfigured_cmp(cr, uid, context=context):
237             raise osv.except_osv(_('No unconfigured company !'), _("There are currently no company without chart of account. The wizard will therefore not be executed."))
238
239     def on_change_start_date(self, cr, uid, id, start_date=False):
240         if start_date:
241             start_date = datetime.datetime.strptime(start_date, "%Y-%m-%d")
242             end_date = (start_date + relativedelta(months=12)) - relativedelta(days=1)
243             return {'value': {'date_stop': end_date.strftime('%Y-%m-%d')}}
244         return {}
245
246     def install_chartofaccounts(self, cr, uid, ids, context=None):
247         ir_module = self.pool.get('ir.module.module')
248         if context is None:
249             context = {}
250         for res in self.read(cr, uid, ids, context=context):
251             chart = res.get('charts')
252             if chart == 'configurable':
253                 #load generic chart of account
254                 fp = tools.file_open(opj('account', 'configurable_account_chart.xml'))
255                 tools.convert_xml_import(cr, 'account', fp, {}, 'init', True, None)
256                 fp.close()
257             elif chart.startswith('l10n_'):
258                 mod_ids = ir_module.search(cr, uid, [('name','=',chart)])
259                 if mod_ids and ir_module.browse(cr, uid, mod_ids[0], context).state == 'uninstalled':
260                     ir_module.button_immediate_install(cr, uid, mod_ids, context)
261
262     def configure_fiscalyear(self, cr, uid, ids, context=None):
263         if context is None:
264             context = {}
265         fy_obj = self.pool.get('account.fiscalyear')
266         for res in self.read(cr, uid, ids, context=context):
267             if 'date_start' in res and 'date_stop' in res:
268                 f_ids = fy_obj.search(cr, uid, [('date_start', '<=', res['date_start']), ('date_stop', '>=', res['date_stop']), ('company_id', '=', res['company_id'][0])], context=context)
269                 if not f_ids:
270                     name = code = res['date_start'][:4]
271                     if int(name) != int(res['date_stop'][:4]):
272                         name = res['date_start'][:4] +'-'+ res['date_stop'][:4]
273                         code = res['date_start'][2:4] +'-'+ res['date_stop'][2:4]
274                     vals = {
275                         'name': name,
276                         'code': code,
277                         'date_start': res['date_start'],
278                         'date_stop': res['date_stop'],
279                         'company_id': res['company_id'][0]
280                     }
281                     fiscal_id = fy_obj.create(cr, uid, vals, context=context)
282                     if res['period'] == 'month':
283                         fy_obj.create_period(cr, uid, [fiscal_id])
284                     elif res['period'] == '3months':
285                         fy_obj.create_period3(cr, uid, [fiscal_id])
286
287 account_configuration()
288
289 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: