[MERGE] typo
[odoo/odoo.git] / addons / account / res_config.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Business Applications
5 #    Copyright (C) 2004-2012 OpenERP S.A. (<http://openerp.com>).
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 time
23 import datetime
24 from dateutil.relativedelta import relativedelta
25 from operator import itemgetter
26 from os.path import join as opj
27
28 from tools.translate import _
29 from osv import osv, fields
30 import tools
31
32 class account_config_settings(osv.osv_memory):
33     _name = 'account.config.settings'
34     _inherit = 'res.config.settings'
35
36     _columns = {
37         'company_id': fields.many2one('res.company', 'Company', required=True),
38         'has_default_company': fields.boolean('Has default company', readonly=True),
39         'expects_chart_of_accounts': fields.related('company_id', 'expects_chart_of_accounts', type='boolean',
40             string='This company has its own chart of accounts',
41             help="""Check this box if this company is a legal entity."""),
42         'currency_id': fields.related('company_id', 'currency_id', type='many2one', relation='res.currency', required=True,
43             string='Default company currency', help="Main currency of the company."),
44         'paypal_account': fields.related('company_id', 'paypal_account', type='char', size=128,
45             string='Paypal account', help="Paypal account (email) for receiving online payments (credit card, etc.) If you set a paypal account, the customer  will be able to pay your invoices or quotations with a button \"Pay with  Paypal\" in automated emails or through the OpenERP portal."),
46         'company_footer': fields.related('company_id', 'rml_footer', type='text', readonly=True,
47             string='Bank accounts footer preview', help="Bank accounts as printed in the footer of each printed document"),
48
49         'has_chart_of_accounts': fields.boolean('Company has a chart of accounts'),
50         'chart_template_id': fields.many2one('account.chart.template', 'Template', domain="[('visible','=', True)]"),
51         'code_digits': fields.integer('# of Digits', help="No. of digits to use for account code"),
52         'tax_calculation_rounding_method': fields.related('company_id',
53             'tax_calculation_rounding_method', type='selection', selection=[
54             ('round_per_line', 'Round per line'),
55             ('round_globally', 'Round globally'),
56             ], string='Tax calculation rounding method',
57             help="If you select 'Round per line' : for each tax, the tax amount will first be computed and rounded for each PO/SO/invoice line and then these rounded amounts will be summed, leading to the total amount for that tax. If you select 'Round globally': for each tax, the tax amount will be computed for each PO/SO/invoice line, then these amounts will be summed and eventually this total tax amount will be rounded. If you sell with tax included, you should choose 'Round per line' because you certainly want the sum of your tax-included line subtotals to be equal to the total amount with taxes."),
58         'sale_tax': fields.many2one("account.tax.template", "Default sale tax"),
59         'purchase_tax': fields.many2one("account.tax.template", "Default purchase tax"),
60         'sale_tax_rate': fields.float('Sales tax (%)'),
61         'purchase_tax_rate': fields.float('Purchase tax (%)'),
62         'complete_tax_set': fields.boolean('Complete set of taxes', help='This boolean helps you to choose if you want to propose to the user to encode the sales and purchase rates or use the usual m2o fields. This last choice assumes that the set of tax defined for the chosen template is complete'),
63
64         'has_fiscal_year': fields.boolean('Company has a fiscal year'),
65         'date_start': fields.date('Start date', required=True),
66         'date_stop': fields.date('End date', required=True),
67         'period': fields.selection([('month', 'Monthly'), ('3months','3 Monthly')], 'Periods', required=True),
68
69         'sale_journal_id': fields.many2one('account.journal', 'Sale journal'),
70         'sale_sequence_prefix': fields.related('sale_journal_id', 'sequence_id', 'prefix', type='char', string='Invoice sequence'),
71         'sale_sequence_next': fields.related('sale_journal_id', 'sequence_id', 'number_next', type='integer', string='Next invoice number'),
72         'sale_refund_journal_id': fields.many2one('account.journal', 'Sale refund journal'),
73         'sale_refund_sequence_prefix': fields.related('sale_refund_journal_id', 'sequence_id', 'prefix', type='char', string='Credit note sequence'),
74         'sale_refund_sequence_next': fields.related('sale_refund_journal_id', 'sequence_id', 'number_next', type='integer', string='Next credit note number'),
75         'purchase_journal_id': fields.many2one('account.journal', 'Purchase journal'),
76         'purchase_sequence_prefix': fields.related('purchase_journal_id', 'sequence_id', 'prefix', type='char', string='Supplier invoice sequence'),
77         'purchase_sequence_next': fields.related('purchase_journal_id', 'sequence_id', 'number_next', type='integer', string='Next supplier invoice number'),
78         'purchase_refund_journal_id': fields.many2one('account.journal', 'Purchase refund journal'),
79         'purchase_refund_sequence_prefix': fields.related('purchase_refund_journal_id', 'sequence_id', 'prefix', type='char', string='Supplier credit note sequence'),
80         'purchase_refund_sequence_next': fields.related('purchase_refund_journal_id', 'sequence_id', 'number_next', type='integer', string='Next supplier credit note number'),
81
82         'module_account_check_writing': fields.boolean('Pay your suppliers by check',
83             help="""This allows you to check writing and printing.
84                 This installs the module account_check_writing."""),
85         'module_account_accountant': fields.boolean('Full accounting features: journals, legal statements, chart of accounts, etc.',
86             help="""If you do not check this box, you will be able to do invoicing & payments, but not accounting (Journal Items, Chart of  Accounts, ...)"""),
87         'module_account_asset': fields.boolean('Assets management',
88             help="""This allows you to manage the assets owned by a company or a person.
89                 It keeps track of the depreciation occurred on those assets, and creates account move for those depreciation lines.
90                 This installs the module account_asset. If you do not check this box, you will be able to do invoicing & payments,
91                 but not accounting (Journal Items, Chart of Accounts, ...)"""),
92         'module_account_budget': fields.boolean('Budget management',
93             help="""This allows accountants to manage analytic and crossovered budgets.
94                 Once the master budgets and the budgets are defined,
95                 the project managers can set the planned amount on each analytic account.
96                 This installs the module account_budget."""),
97         'module_account_payment': fields.boolean('Manage payment orders',
98             help="""This allows you to create and manage your payment orders, with purposes to
99                     * serve as base for an easy plug-in of various automated payment mechanisms, and
100                     * provide a more efficient way to manage invoice payments.
101                 This installs the module account_payment."""),
102         'module_account_voucher': fields.boolean('Manage customer payments',
103             help="""This includes all the basic requirements of voucher entries for bank, cash, sales, purchase, expense, contra, etc.
104                 This installs the module account_voucher."""),
105         'module_account_followup': fields.boolean('Manage customer payment follow-ups',
106             help="""This allows to automate letters for unpaid invoices, with multi-level recalls.
107                 This installs the module account_followup."""),
108         'group_proforma_invoices': fields.boolean('Allow pro-forma invoices',
109             implied_group='account.group_proforma_invoices',
110             help="Allows you to put invoices in pro-forma state."),
111         'default_sale_tax': fields.many2one('account.tax', 'Default sale tax',
112             help="This sale tax will be assigned by default on new products."),
113         'default_purchase_tax': fields.many2one('account.tax', 'Default purchase tax',
114             help="This purchase tax will be assigned by default on new products."),
115         'decimal_precision': fields.integer('Decimal precision on journal entries',
116             help="""As an example, a decimal precision of 2 will allow journal entries  like: 9.99 EUR, whereas a decimal precision of 4 will allow journal  entries like: 0.0231 EUR."""),
117         'group_multi_currency': fields.boolean('Allow multi currencies',
118             implied_group='base.group_multi_currency',
119             help="Allows you multi currency environment"),
120         'group_analytic_accounting': fields.boolean('Analytic accounting',
121             implied_group='analytic.group_analytic_accounting',
122             help="Allows you to use the analytic accounting."),
123     }
124
125     def _default_company(self, cr, uid, context=None):
126         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
127         return user.company_id.id
128
129     def _default_has_default_company(self, cr, uid, context=None):
130         count = self.pool.get('res.company').search_count(cr, uid, [], context=context)
131         return bool(count == 1)
132
133     _defaults = {
134         'company_id': _default_company,
135         'has_default_company': _default_has_default_company,
136         'date_start': lambda *a: time.strftime('%Y-01-01'),
137         'date_stop': lambda *a: time.strftime('%Y-12-31'),
138         'period': 'month',
139     }
140
141     def create(self, cr, uid, values, context=None):
142         id = super(account_config_settings, self).create(cr, uid, values, context)
143         # Hack: to avoid some nasty bug, related fields are not written upon record creation.
144         # Hence we write on those fields here.
145         vals = {}
146         for fname, field in self._columns.iteritems():
147             if isinstance(field, fields.related) and fname in values:
148                 vals[fname] = values[fname]
149         self.write(cr, uid, [id], vals, context)
150         return id
151
152     def onchange_company_id(self, cr, uid, ids, company_id):
153         # update related fields
154         values = {}
155         values['currency_id'] = False
156         if company_id:
157             company = self.pool.get('res.company').browse(cr, uid, company_id)
158             has_chart_of_accounts = company_id not in self.pool.get('account.installer').get_unconfigured_cmp(cr, uid)
159             fiscalyear_count = self.pool.get('account.fiscalyear').search_count(cr, uid,
160                 [('date_start', '<=', time.strftime('%Y-%m-%d')), ('date_stop', '>=', time.strftime('%Y-%m-%d')),
161                  ('company_id', '=', company_id)])
162             values = {
163                 'expects_chart_of_accounts': company.expects_chart_of_accounts,
164                 'currency_id': company.currency_id.id,
165                 'paypal_account': company.paypal_account,
166                 'company_footer': company.rml_footer,
167                 'has_chart_of_accounts': has_chart_of_accounts,
168                 'has_fiscal_year': bool(fiscalyear_count),
169                 'chart_template_id': False,
170                 'tax_calculation_rounding_method': company.tax_calculation_rounding_method,
171             }
172             # update journals and sequences
173             for journal_type in ('sale', 'sale_refund', 'purchase', 'purchase_refund'):
174                 for suffix in ('_journal_id', '_sequence_prefix', '_sequence_next'):
175                     values[journal_type + suffix] = False
176             journal_obj = self.pool.get('account.journal')
177             journal_ids = journal_obj.search(cr, uid, [('company_id', '=', company_id)])
178             for journal in journal_obj.browse(cr, uid, journal_ids):
179                 if journal.type in ('sale', 'sale_refund', 'purchase', 'purchase_refund'):
180                     values.update({
181                         journal.type + '_journal_id': journal.id,
182                         journal.type + '_sequence_prefix': journal.sequence_id.prefix,
183                         journal.type + '_sequence_next': journal.sequence_id.number_next,
184                     })
185             # update taxes
186             ir_values = self.pool.get('ir.values')
187             taxes_id = ir_values.get_default(cr, uid, 'product.product', 'taxes_id', company_id=company_id)
188             supplier_taxes_id = ir_values.get_default(cr, uid, 'product.product', 'supplier_taxes_id', company_id=company_id)
189             values.update({
190                 'default_sale_tax': isinstance(taxes_id, list) and taxes_id[0] or taxes_id,
191                 'default_purchase_tax': isinstance(supplier_taxes_id, list) and supplier_taxes_id[0] or supplier_taxes_id,
192             })
193         return {'value': values}
194
195     def onchange_chart_template_id(self, cr, uid, ids, chart_template_id, context=None):
196         tax_templ_obj = self.pool.get('account.tax.template')
197         res = {'value': {
198             'complete_tax_set': False, 'sale_tax': False, 'purchase_tax': False,
199             'sale_tax_rate': 15, 'purchase_tax_rate': 15,
200         }}
201         if chart_template_id:
202             # update complete_tax_set, sale_tax and purchase_tax
203             chart_template = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
204             res['value'].update({'complete_tax_set': chart_template.complete_tax_set})
205             if chart_template.complete_tax_set:
206                 # default tax is given by the lowest sequence. For same sequence we will take the latest created as it will be the case for tax created while isntalling the generic chart of account
207                 sale_tax_ids = tax_templ_obj.search(cr, uid,
208                     [("chart_template_id", "=", chart_template_id), ('type_tax_use', 'in', ('sale','all'))],
209                     order="sequence, id desc")
210                 purchase_tax_ids = tax_templ_obj.search(cr, uid,
211                     [("chart_template_id", "=", chart_template_id), ('type_tax_use', 'in', ('purchase','all'))],
212                     order="sequence, id desc")
213                 res['value']['sale_tax'] = sale_tax_ids and sale_tax_ids[0] or False
214                 res['value']['purchase_tax'] = purchase_tax_ids and purchase_tax_ids[0] or False
215             if chart_template.code_digits:
216                 res['value']['code_digits'] = chart_template.code_digits
217         return res
218
219     def onchange_tax_rate(self, cr, uid, ids, rate, context=None):
220         return {'value': {'purchase_tax_rate': rate or False}}
221
222     def onchange_start_date(self, cr, uid, id, start_date):
223         if start_date:
224             start_date = datetime.datetime.strptime(start_date, "%Y-%m-%d")
225             end_date = (start_date + relativedelta(months=12)) - relativedelta(days=1)
226             return {'value': {'date_stop': end_date.strftime('%Y-%m-%d')}}
227         return {}
228
229     def open_company_form(self, cr, uid, ids, context=None):
230         config = self.browse(cr, uid, ids[0], context)
231         return {
232             'type': 'ir.actions.act_window',
233             'name': 'Configure your Company',
234             'res_model': 'res.company',
235             'res_id': config.company_id.id,
236             'view_mode': 'form',
237         }
238
239     def set_default_taxes(self, cr, uid, ids, context=None):
240         """ set default sale and purchase taxes for products """
241         ir_values = self.pool.get('ir.values')
242         config = self.browse(cr, uid, ids[0], context)
243         ir_values.set_default(cr, uid, 'product.product', 'taxes_id',
244             config.default_sale_tax and [config.default_sale_tax.id] or False, company_id=config.company_id.id)
245         ir_values.set_default(cr, uid, 'product.product', 'supplier_taxes_id',
246             config.default_purchase_tax and [config.default_purchase_tax.id] or False, company_id=config.company_id.id)
247
248     def set_chart_of_accounts(self, cr, uid, ids, context=None):
249         """ install a chart of accounts for the given company (if required) """
250         config = self.browse(cr, uid, ids[0], context)
251         if config.chart_template_id:
252             assert config.expects_chart_of_accounts and not config.has_chart_of_accounts
253             wizard = self.pool.get('wizard.multi.charts.accounts')
254             wizard_id = wizard.create(cr, uid, {
255                 'company_id': config.company_id.id,
256                 'chart_template_id': config.chart_template_id.id,
257                 'code_digits': config.code_digits or 6,
258                 'sale_tax': config.sale_tax.id,
259                 'purchase_tax': config.purchase_tax.id,
260                 'sale_tax_rate': config.sale_tax_rate,
261                 'purchase_tax_rate': config.purchase_tax_rate,
262                 'complete_tax_set': config.complete_tax_set,
263             }, context)
264             wizard.execute(cr, uid, [wizard_id], context)
265
266     def set_fiscalyear(self, cr, uid, ids, context=None):
267         """ create a fiscal year for the given company (if necessary) """
268         config = self.browse(cr, uid, ids[0], context)
269         if config.has_chart_of_accounts or config.chart_template_id:
270             fiscalyear = self.pool.get('account.fiscalyear')
271             fiscalyear_count = fiscalyear.search_count(cr, uid,
272                 [('date_start', '<=', config.date_start), ('date_stop', '>=', config.date_stop),
273                  ('company_id', '=', config.company_id.id)],
274                 context=context)
275             if not fiscalyear_count:
276                 name = code = config.date_start[:4]
277                 if int(name) != int(config.date_stop[:4]):
278                     name = config.date_start[:4] +'-'+ config.date_stop[:4]
279                     code = config.date_start[2:4] +'-'+ config.date_stop[2:4]
280                 vals = {
281                     'name': name,
282                     'code': code,
283                     'date_start': config.date_start,
284                     'date_stop': config.date_stop,
285                     'company_id': config.company_id.id,
286                 }
287                 fiscalyear_id = fiscalyear.create(cr, uid, vals, context=context)
288                 if config.period == 'month':
289                     fiscalyear.create_period(cr, uid, [fiscalyear_id])
290                 elif config.period == '3months':
291                     fiscalyear.create_period3(cr, uid, [fiscalyear_id])
292
293     def get_default_dp(self, cr, uid, fields, context=None):
294         dp = self.pool.get('ir.model.data').get_object(cr, uid, 'product','decimal_account')
295         return {'decimal_precision': dp.digits}
296
297     def set_default_dp(self, cr, uid, ids, context=None):
298         config = self.browse(cr, uid, ids[0], context)
299         dp = self.pool.get('ir.model.data').get_object(cr, uid, 'product','decimal_account')
300         dp.write({'digits': config.decimal_precision})
301
302 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: