[IMP] im: implemented group chat
[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 openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF
29 from openerp.tools.translate import _
30 from openerp.osv import fields, osv
31 from openerp import tools
32
33 class account_config_settings(osv.osv_memory):
34     _name = 'account.config.settings'
35     _inherit = 'res.config.settings'
36
37     _columns = {
38         'company_id': fields.many2one('res.company', 'Company', required=True),
39         'has_default_company': fields.boolean('Has default company', readonly=True),
40         'expects_chart_of_accounts': fields.related('company_id', 'expects_chart_of_accounts', type='boolean',
41             string='This company has its own chart of accounts',
42             help="""Check this box if this company is a legal entity."""),
43         'currency_id': fields.related('company_id', 'currency_id', type='many2one', relation='res.currency', required=True,
44             string='Default company currency', help="Main currency of the company."),
45         'paypal_account': fields.related('company_id', 'paypal_account', type='char', size=128,
46             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."),
47         'company_footer': fields.related('company_id', 'rml_footer', type='text', readonly=True,
48             string='Bank accounts footer preview', help="Bank accounts as printed in the footer of each printed document"),
49
50         'has_chart_of_accounts': fields.boolean('Company has a chart of accounts'),
51         'chart_template_id': fields.many2one('account.chart.template', 'Template', domain="[('visible','=', True)]"),
52         'code_digits': fields.integer('# of Digits', help="No. of digits to use for account code"),
53         'tax_calculation_rounding_method': fields.related('company_id',
54             'tax_calculation_rounding_method', type='selection', selection=[
55             ('round_per_line', 'Round per line'),
56             ('round_globally', 'Round globally'),
57             ], string='Tax calculation rounding method',
58             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."),
59         'sale_tax': fields.many2one("account.tax.template", "Default sale tax"),
60         'purchase_tax': fields.many2one("account.tax.template", "Default purchase tax"),
61         'sale_tax_rate': fields.float('Sales tax (%)'),
62         'purchase_tax_rate': fields.float('Purchase tax (%)'),
63         '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'),
64
65         'has_fiscal_year': fields.boolean('Company has a fiscal year'),
66         'date_start': fields.date('Start date', required=True),
67         'date_stop': fields.date('End date', required=True),
68         'period': fields.selection([('month', 'Monthly'), ('3months','3 Monthly')], 'Periods', required=True),
69
70         'sale_journal_id': fields.many2one('account.journal', 'Sale journal'),
71         'sale_sequence_prefix': fields.related('sale_journal_id', 'sequence_id', 'prefix', type='char', string='Invoice sequence'),
72         'sale_sequence_next': fields.related('sale_journal_id', 'sequence_id', 'number_next', type='integer', string='Next invoice number'),
73         'sale_refund_journal_id': fields.many2one('account.journal', 'Sale refund journal'),
74         'sale_refund_sequence_prefix': fields.related('sale_refund_journal_id', 'sequence_id', 'prefix', type='char', string='Credit note sequence'),
75         'sale_refund_sequence_next': fields.related('sale_refund_journal_id', 'sequence_id', 'number_next', type='integer', string='Next credit note number'),
76         'purchase_journal_id': fields.many2one('account.journal', 'Purchase journal'),
77         'purchase_sequence_prefix': fields.related('purchase_journal_id', 'sequence_id', 'prefix', type='char', string='Supplier invoice sequence'),
78         'purchase_sequence_next': fields.related('purchase_journal_id', 'sequence_id', 'number_next', type='integer', string='Next supplier invoice number'),
79         'purchase_refund_journal_id': fields.many2one('account.journal', 'Purchase refund journal'),
80         'purchase_refund_sequence_prefix': fields.related('purchase_refund_journal_id', 'sequence_id', 'prefix', type='char', string='Supplier credit note sequence'),
81         'purchase_refund_sequence_next': fields.related('purchase_refund_journal_id', 'sequence_id', 'number_next', type='integer', string='Next supplier credit note number'),
82
83         'module_account_check_writing': fields.boolean('Pay your suppliers by check',
84             help="""This allows you to check writing and printing.
85                 This installs the module account_check_writing."""),
86         'module_account_accountant': fields.boolean('Full accounting features: journals, legal statements, chart of accounts, etc.',
87             help="""If you do not check this box, you will be able to do invoicing & payments, but not accounting (Journal Items, Chart of  Accounts, ...)"""),
88         'module_account_asset': fields.boolean('Assets management',
89             help="""This allows you to manage the assets owned by a company or a person.
90                 It keeps track of the depreciation occurred on those assets, and creates account move for those depreciation lines.
91                 This installs the module account_asset. If you do not check this box, you will be able to do invoicing & payments,
92                 but not accounting (Journal Items, Chart of Accounts, ...)"""),
93         'module_account_budget': fields.boolean('Budget management',
94             help="""This allows accountants to manage analytic and crossovered budgets.
95                 Once the master budgets and the budgets are defined,
96                 the project managers can set the planned amount on each analytic account.
97                 This installs the module account_budget."""),
98         'module_account_payment': fields.boolean('Manage 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, and
101                     * provide a more efficient way to manage invoice payments.
102                 This installs the module account_payment."""),
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, expense, contra, etc.
105                 This installs the module account_voucher."""),
106         'module_account_followup': fields.boolean('Manage customer payment follow-ups',
107             help="""This allows to automate letters for unpaid invoices, with multi-level recalls.
108                 This installs the module account_followup."""),
109         'group_proforma_invoices': fields.boolean('Allow pro-forma invoices',
110             implied_group='account.group_proforma_invoices',
111             help="Allows you to put invoices in pro-forma state."),
112         'default_sale_tax': fields.many2one('account.tax', 'Default sale tax',
113             help="This sale tax will be assigned by default on new products."),
114         'default_purchase_tax': fields.many2one('account.tax', 'Default purchase tax',
115             help="This purchase tax will be assigned by default on new products."),
116         'decimal_precision': fields.integer('Decimal precision on journal entries',
117             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."""),
118         'group_multi_currency': fields.boolean('Allow multi currencies',
119             implied_group='base.group_multi_currency',
120             help="Allows you multi currency environment"),
121         'group_analytic_accounting': fields.boolean('Analytic accounting',
122             implied_group='analytic.group_analytic_accounting',
123             help="Allows you to use the analytic accounting."),
124         'group_check_supplier_invoice_total': fields.boolean('Check the total of supplier invoices', 
125             implied_group="account.group_supplier_inv_check_total"),
126     }
127
128     def _default_company(self, cr, uid, context=None):
129         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
130         return user.company_id.id
131
132     def _default_has_default_company(self, cr, uid, context=None):
133         count = self.pool.get('res.company').search_count(cr, uid, [], context=context)
134         return bool(count == 1)
135
136     def _get_default_fiscalyear_data(self, cr, uid, company_id, context=None):
137         """Compute default period, starting and ending date for fiscalyear
138         - if in a fiscal year, use its period, starting and ending date
139         - if past fiscal year, use its period, and new dates [ending date of the latest +1 day ; ending date of the latest +1 year]
140         - if no fiscal year, use monthly, 1st jan, 31th dec of this year
141         :return: (date_start, date_stop, period) at format DEFAULT_SERVER_DATETIME_FORMAT
142         """
143         fiscalyear_ids = self.pool.get('account.fiscalyear').search(cr, uid,
144                 [('date_start', '<=', time.strftime(DF)), ('date_stop', '>=', time.strftime(DF)),
145                  ('company_id', '=', company_id)])
146         if fiscalyear_ids:
147             # is in a current fiscal year, use this one
148             fiscalyear = self.pool.get('account.fiscalyear').browse(cr, uid, fiscalyear_ids[0], context=context)
149             if len(fiscalyear.period_ids) == 5:  # 4 periods of 3 months + opening period
150                 period = '3months'
151             else:
152                 period = 'month'
153             return (fiscalyear.date_start, fiscalyear.date_stop, period)
154         else:
155             past_fiscalyear_ids = self.pool.get('account.fiscalyear').search(cr, uid,
156                 [('date_stop', '<=', time.strftime(DF)), ('company_id', '=', company_id)])
157             if past_fiscalyear_ids:
158                 # use the latest fiscal, sorted by (start_date, id)
159                 latest_year = self.pool.get('account.fiscalyear').browse(cr, uid, past_fiscalyear_ids[-1], context=context)
160                 latest_stop = datetime.datetime.strptime(latest_year.date_stop, DF)
161                 if len(latest_year.period_ids) == 5:
162                     period = '3months'
163                 else:
164                     period = 'month'
165                 return ((latest_stop+datetime.timedelta(days=1)).strftime(DF), latest_stop.replace(year=latest_stop.year+1).strftime(DF), period)
166             else:
167                 return (time.strftime('%Y-01-01'), time.strftime('%Y-12-31'), 'month')
168
169
170     _defaults = {
171         'company_id': _default_company,
172         'has_default_company': _default_has_default_company,
173     }
174
175     def create(self, cr, uid, values, context=None):
176         id = super(account_config_settings, self).create(cr, uid, values, context)
177         # Hack: to avoid some nasty bug, related fields are not written upon record creation.
178         # Hence we write on those fields here.
179         vals = {}
180         for fname, field in self._columns.iteritems():
181             if isinstance(field, fields.related) and fname in values:
182                 vals[fname] = values[fname]
183         self.write(cr, uid, [id], vals, context)
184         return id
185
186     def onchange_company_id(self, cr, uid, ids, company_id, context=None):
187         # update related fields
188         values = {}
189         values['currency_id'] = False
190         if company_id:
191             company = self.pool.get('res.company').browse(cr, uid, company_id, context=context)
192             has_chart_of_accounts = company_id not in self.pool.get('account.installer').get_unconfigured_cmp(cr, uid)
193             fiscalyear_count = self.pool.get('account.fiscalyear').search_count(cr, uid,
194                 [('date_start', '<=', time.strftime('%Y-%m-%d')), ('date_stop', '>=', time.strftime('%Y-%m-%d')),
195                  ('company_id', '=', company_id)])
196             date_start, date_stop, period = self._get_default_fiscalyear_data(cr, uid, company_id, context=context)
197             values = {
198                 'expects_chart_of_accounts': company.expects_chart_of_accounts,
199                 'currency_id': company.currency_id.id,
200                 'paypal_account': company.paypal_account,
201                 'company_footer': company.rml_footer,
202                 'has_chart_of_accounts': has_chart_of_accounts,
203                 'has_fiscal_year': bool(fiscalyear_count),
204                 'chart_template_id': False,
205                 'tax_calculation_rounding_method': company.tax_calculation_rounding_method,
206                 'date_start': date_start,
207                 'date_stop': date_stop,
208                 'period': period,
209             }
210             # update journals and sequences
211             for journal_type in ('sale', 'sale_refund', 'purchase', 'purchase_refund'):
212                 for suffix in ('_journal_id', '_sequence_prefix', '_sequence_next'):
213                     values[journal_type + suffix] = False
214             journal_obj = self.pool.get('account.journal')
215             journal_ids = journal_obj.search(cr, uid, [('company_id', '=', company_id)])
216             for journal in journal_obj.browse(cr, uid, journal_ids):
217                 if journal.type in ('sale', 'sale_refund', 'purchase', 'purchase_refund'):
218                     values.update({
219                         journal.type + '_journal_id': journal.id,
220                         journal.type + '_sequence_prefix': journal.sequence_id.prefix,
221                         journal.type + '_sequence_next': journal.sequence_id.number_next,
222                     })
223             # update taxes
224             ir_values = self.pool.get('ir.values')
225             taxes_id = ir_values.get_default(cr, uid, 'product.product', 'taxes_id', company_id=company_id)
226             supplier_taxes_id = ir_values.get_default(cr, uid, 'product.product', 'supplier_taxes_id', company_id=company_id)
227             values.update({
228                 'default_sale_tax': isinstance(taxes_id, list) and taxes_id[0] or taxes_id,
229                 'default_purchase_tax': isinstance(supplier_taxes_id, list) and supplier_taxes_id[0] or supplier_taxes_id,
230             })
231         return {'value': values}
232
233     def onchange_chart_template_id(self, cr, uid, ids, chart_template_id, context=None):
234         tax_templ_obj = self.pool.get('account.tax.template')
235         res = {'value': {
236             'complete_tax_set': False, 'sale_tax': False, 'purchase_tax': False,
237             'sale_tax_rate': 15, 'purchase_tax_rate': 15,
238         }}
239         if chart_template_id:
240             # update complete_tax_set, sale_tax and purchase_tax
241             chart_template = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
242             res['value'].update({'complete_tax_set': chart_template.complete_tax_set})
243             if chart_template.complete_tax_set:
244                 # 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
245                 sale_tax_ids = tax_templ_obj.search(cr, uid,
246                     [("chart_template_id", "=", chart_template_id), ('type_tax_use', 'in', ('sale','all'))],
247                     order="sequence, id desc")
248                 purchase_tax_ids = tax_templ_obj.search(cr, uid,
249                     [("chart_template_id", "=", chart_template_id), ('type_tax_use', 'in', ('purchase','all'))],
250                     order="sequence, id desc")
251                 res['value']['sale_tax'] = sale_tax_ids and sale_tax_ids[0] or False
252                 res['value']['purchase_tax'] = purchase_tax_ids and purchase_tax_ids[0] or False
253             if chart_template.code_digits:
254                 res['value']['code_digits'] = chart_template.code_digits
255         return res
256
257     def onchange_tax_rate(self, cr, uid, ids, rate, context=None):
258         return {'value': {'purchase_tax_rate': rate or False}}
259
260     def onchange_start_date(self, cr, uid, id, start_date):
261         if start_date:
262             start_date = datetime.datetime.strptime(start_date, "%Y-%m-%d")
263             end_date = (start_date + relativedelta(months=12)) - relativedelta(days=1)
264             return {'value': {'date_stop': end_date.strftime('%Y-%m-%d')}}
265         return {}
266
267     def open_company_form(self, cr, uid, ids, context=None):
268         config = self.browse(cr, uid, ids[0], context)
269         return {
270             'type': 'ir.actions.act_window',
271             'name': 'Configure your Company',
272             'res_model': 'res.company',
273             'res_id': config.company_id.id,
274             'view_mode': 'form',
275         }
276
277     def set_default_taxes(self, cr, uid, ids, context=None):
278         """ set default sale and purchase taxes for products """
279         ir_values = self.pool.get('ir.values')
280         config = self.browse(cr, uid, ids[0], context)
281         ir_values.set_default(cr, uid, 'product.product', 'taxes_id',
282             config.default_sale_tax and [config.default_sale_tax.id] or False, company_id=config.company_id.id)
283         ir_values.set_default(cr, uid, 'product.product', 'supplier_taxes_id',
284             config.default_purchase_tax and [config.default_purchase_tax.id] or False, company_id=config.company_id.id)
285
286     def set_chart_of_accounts(self, cr, uid, ids, context=None):
287         """ install a chart of accounts for the given company (if required) """
288         config = self.browse(cr, uid, ids[0], context)
289         if config.chart_template_id:
290             assert config.expects_chart_of_accounts and not config.has_chart_of_accounts
291             wizard = self.pool.get('wizard.multi.charts.accounts')
292             wizard_id = wizard.create(cr, uid, {
293                 'company_id': config.company_id.id,
294                 'chart_template_id': config.chart_template_id.id,
295                 'code_digits': config.code_digits or 6,
296                 'sale_tax': config.sale_tax.id,
297                 'purchase_tax': config.purchase_tax.id,
298                 'sale_tax_rate': config.sale_tax_rate,
299                 'purchase_tax_rate': config.purchase_tax_rate,
300                 'complete_tax_set': config.complete_tax_set,
301                 'currency_id': config.currency_id.id,
302             }, context)
303             wizard.execute(cr, uid, [wizard_id], context)
304
305     def set_fiscalyear(self, cr, uid, ids, context=None):
306         """ create a fiscal year for the given company (if necessary) """
307         config = self.browse(cr, uid, ids[0], context)
308         if config.has_chart_of_accounts or config.chart_template_id:
309             fiscalyear = self.pool.get('account.fiscalyear')
310             fiscalyear_count = fiscalyear.search_count(cr, uid,
311                 [('date_start', '<=', config.date_start), ('date_stop', '>=', config.date_stop),
312                  ('company_id', '=', config.company_id.id)],
313                 context=context)
314             if not fiscalyear_count:
315                 name = code = config.date_start[:4]
316                 if int(name) != int(config.date_stop[:4]):
317                     name = config.date_start[:4] +'-'+ config.date_stop[:4]
318                     code = config.date_start[2:4] +'-'+ config.date_stop[2:4]
319                 vals = {
320                     'name': name,
321                     'code': code,
322                     'date_start': config.date_start,
323                     'date_stop': config.date_stop,
324                     'company_id': config.company_id.id,
325                 }
326                 fiscalyear_id = fiscalyear.create(cr, uid, vals, context=context)
327                 if config.period == 'month':
328                     fiscalyear.create_period(cr, uid, [fiscalyear_id])
329                 elif config.period == '3months':
330                     fiscalyear.create_period3(cr, uid, [fiscalyear_id])
331
332     def get_default_dp(self, cr, uid, fields, context=None):
333         dp = self.pool.get('ir.model.data').get_object(cr, uid, 'product','decimal_account')
334         return {'decimal_precision': dp.digits}
335
336     def set_default_dp(self, cr, uid, ids, context=None):
337         config = self.browse(cr, uid, ids[0], context)
338         dp = self.pool.get('ir.model.data').get_object(cr, uid, 'product','decimal_account')
339         dp.write({'digits': config.decimal_precision})
340
341     def onchange_analytic_accounting(self, cr, uid, ids, analytic_accounting, context=None):
342         if analytic_accounting:
343             return {'value': {
344                 'module_account_accountant': True,
345                 }}
346         return {}
347 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: