8ca35426427799945eeb81e4229e0c9107c65228
[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_footer2', type='char', size=250, readonly=True,
47             string='Bank Accounts on Reports', help="Bank accounts as printed in the footer of each customer  document. This is for information purpose only, you should configure  these bank accounts through the above button \"Configure Bank Accounts\"."),
48
49         'has_chart_of_accounts': fields.boolean('Company has a chart of accounts'),
50         'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', domain="[('visible','=', True)]"),
51         'code_digits': fields.integer('# of Digits', help="No. of Digits to use for account code"),
52         'sale_tax': fields.many2one("account.tax.template", "Default Sale Tax"),
53         'purchase_tax': fields.many2one("account.tax.template", "Default Purchase Tax"),
54         'sale_tax_rate': fields.float('Sales Tax (%)'),
55         'purchase_tax_rate': fields.float('Purchase Tax (%)'),
56         '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'),
57
58         'has_fiscal_year': fields.boolean('Company has a fiscal year'),
59         'date_start': fields.date('Start Date', required=True),
60         'date_stop': fields.date('End Date', required=True),
61         'period': fields.selection([('month', 'Monthly'), ('3months','3 Monthly')], 'Periods', required=True),
62
63         'sale_journal_id': fields.many2one('account.journal', 'Sale Journal'),
64         'sale_sequence_prefix': fields.related('sale_journal_id', 'sequence_id', 'prefix', type='char', string='Invoice Sequence'),
65         'sale_sequence_next': fields.related('sale_journal_id', 'sequence_id', 'number_next', type='integer', string='Next Invoice Number'),
66         'sale_refund_journal_id': fields.many2one('account.journal', 'Sale Refund Journal'),
67         'sale_refund_sequence_prefix': fields.related('sale_refund_journal_id', 'sequence_id', 'prefix', type='char', string='Credit Note Sequence'),
68         'sale_refund_sequence_next': fields.related('sale_refund_journal_id', 'sequence_id', 'number_next', type='integer', string='Next Credit Note Number'),
69         'purchase_journal_id': fields.many2one('account.journal', 'Purchase Journal'),
70         'purchase_sequence_prefix': fields.related('purchase_journal_id', 'sequence_id', 'prefix', type='char', string='Supplier Invoice Sequence'),
71         'purchase_sequence_next': fields.related('purchase_journal_id', 'sequence_id', 'number_next', type='integer', string='Next Supplier Invoice Number'),
72         'purchase_refund_journal_id': fields.many2one('account.journal', 'Purchase Refund Journal'),
73         'purchase_refund_sequence_prefix': fields.related('purchase_refund_journal_id', 'sequence_id', 'prefix', type='char', string='Supplier Credit Note Sequence'),
74         'purchase_refund_sequence_next': fields.related('purchase_refund_journal_id', 'sequence_id', 'number_next', type='integer', string='Next Supplier Credit Note Number'),
75
76         'module_account_check_writing': fields.boolean('Check Writing',
77             help="""This allows you to check writing and printing.
78                 This installs the module account_check_writing."""),
79         'module_account_accountant': fields.boolean('Accountant Features',
80             help="""If you do not check this box, you will be able to do invoicing & payments, but not accounting (Journal Items, Chart of  Accounts, ...)"""),
81         'module_account_asset': fields.boolean('Assets Management',
82             help="""This allows you to manage the assets owned by a company or a person.
83                 It keeps track of the depreciation occurred on those assets, and creates account move for those depreciation lines.
84                 This installs the module account_asset. If you do not check this box, you will be able to do invoicing & payments,
85                 but not accounting (Journal Items, Chart of Accounts, ...)"""),
86         'module_account_budget': fields.boolean('Budget Management',
87             help="""This allows accountants to manage analytic and crossovered budgets.
88                 Once the master budgets and the budgets are defined,
89                 the project managers can set the planned amount on each analytic account.
90                 This installs the module account_budget."""),
91         'module_account_payment': fields.boolean('Manage Payment Orders',
92             help="""This allows you to create and manage your payment orders, with purposes to
93                     * serve as base for an easy plug-in of various automated payment mechanisms, and
94                     * provide a more efficient way to manage invoice payments.
95                 This installs the module account_payment."""),
96         'module_account_voucher': fields.boolean('Manage Customer Payments',
97             help="""This includes all the basic requirements of voucher entries for bank, cash, sales, purchase, expense, contra, etc.
98                 This installs the module account_voucher."""),
99         'module_account_followup': fields.boolean('Manage Customer Payment Follow-ups',
100             help="""This allows to automate letters for unpaid invoices, with multi-level recalls.
101                 This installs the module account_followup."""),
102         'group_proforma_invoices': fields.boolean('Allow Pro-forma Invoices',
103             implied_group='account.group_proforma_invoices',
104             help="Allows you to put invoices in pro-forma state."),
105         'default_sale_tax': fields.many2one('account.tax', 'Default Sale Tax',
106             help="This sale tax will be assigned by default on new products."),
107         'default_purchase_tax': fields.many2one('account.tax', 'Default Purchase Tax',
108             help="This purchase tax will be assigned by default on new products."),
109         'decimal_precision': fields.integer('Decimal Precision on Journal Entries',
110             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."""),
111     }
112
113     def _default_company(self, cr, uid, context=None):
114         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
115         return user.company_id.id
116
117     def _default_has_default_company(self, cr, uid, context=None):
118         count = self.pool.get('res.company').search_count(cr, uid, [], context=context)
119         return bool(count == 1)
120
121     _defaults = {
122         'company_id': _default_company,
123         'has_default_company': _default_has_default_company,
124         'date_start': lambda *a: time.strftime('%Y-01-01'),
125         'date_stop': lambda *a: time.strftime('%Y-12-31'),
126         'period': 'month',
127     }
128
129     def create(self, cr, uid, values, context=None):
130         id = super(account_config_settings, self).create(cr, uid, values, context)
131         # Hack: to avoid some nasty bug, related fields are not written upon record creation.
132         # Hence we write on those fields here.
133         vals = {}
134         for fname, field in self._columns.iteritems():
135             if isinstance(field, fields.related) and fname in values:
136                 vals[fname] = values[fname]
137         self.write(cr, uid, [id], vals, context)
138         return id
139
140     def onchange_company_id(self, cr, uid, ids, company_id):
141         # update related fields
142         company = self.pool.get('res.company').browse(cr, uid, company_id)
143         has_chart_of_accounts = company_id not in self.pool.get('account.installer').get_unconfigured_cmp(cr, uid)
144         fiscalyear_count = self.pool.get('account.fiscalyear').search_count(cr, uid,
145             [('date_start', '<=', time.strftime('%Y-%m-%d')), ('date_stop', '>=', time.strftime('%Y-%m-%d')),
146              ('company_id', '=', company_id)])
147         values = {
148             'expects_chart_of_accounts': company.expects_chart_of_accounts,
149             'currency_id': company.currency_id.id,
150             'paypal_account': company.paypal_account,
151             'company_footer': company.rml_footer2,
152             'has_chart_of_accounts': has_chart_of_accounts,
153             'has_fiscal_year': bool(fiscalyear_count),
154             'chart_template_id': False,
155         }
156         # update journals and sequences
157         for journal_type in ('sale', 'sale_refund', 'purchase', 'purchase_refund'):
158             for suffix in ('_journal_id', '_sequence_prefix', '_sequence_next'):
159                 values[journal_type + suffix] = False
160         journal_obj = self.pool.get('account.journal')
161         journal_ids = journal_obj.search(cr, uid, [('company_id', '=', company_id)])
162         for journal in journal_obj.browse(cr, uid, journal_ids):
163             if journal.type in ('sale', 'sale_refund', 'purchase', 'purchase_refund'):
164                 values.update({
165                     journal.type + '_journal_id': journal.id,
166                     journal.type + '_sequence_prefix': journal.sequence_id.prefix,
167                     journal.type + '_sequence_next': journal.sequence_id.number_next,
168                 })
169         # update taxes
170         ir_values = self.pool.get('ir.values')
171         taxes_id = ir_values.get_default(cr, uid, 'product.product', 'taxes_id', company_id=company_id)
172         supplier_taxes_id = ir_values.get_default(cr, uid, 'product.product', 'supplier_taxes_id', company_id=company_id)
173         values.update({
174             'default_sale_tax': isinstance(taxes_id, list) and taxes_id[0] or taxes_id,
175             'default_purchase_tax': isinstance(supplier_taxes_id, list) and supplier_taxes_id[0] or supplier_taxes_id,
176         })
177         return {'value': values}
178
179     def onchange_chart_template_id(self, cr, uid, ids, chart_template_id, context=None):
180         tax_templ_obj = self.pool.get('account.tax.template')
181         res = {'value': {
182             'complete_tax_set': False, 'sale_tax': False, 'purchase_tax': False,
183             'sale_tax_rate': 15, 'purchase_tax_rate': 15,
184         }}
185         if chart_template_id:
186             # update complete_tax_set, sale_tax and purchase_tax
187             chart_template = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
188             res['value'].update({'complete_tax_set': chart_template.complete_tax_set})
189             if chart_template.complete_tax_set:
190                 # 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
191                 sale_tax_ids = tax_templ_obj.search(cr, uid,
192                     [("chart_template_id", "=", chart_template_id), ('type_tax_use', 'in', ('sale','all'))],
193                     order="sequence, id desc")
194                 purchase_tax_ids = tax_templ_obj.search(cr, uid,
195                     [("chart_template_id", "=", chart_template_id), ('type_tax_use', 'in', ('purchase','all'))],
196                     order="sequence, id desc")
197                 res['value']['sale_tax'] = sale_tax_ids and sale_tax_ids[0] or False
198                 res['value']['purchase_tax'] = purchase_tax_ids and purchase_tax_ids[0] or False
199             if chart_template.code_digits:
200                 res['value']['code_digits'] = chart_template.code_digits
201         return res
202
203     def onchange_tax_rate(self, cr, uid, ids, rate, context=None):
204         return {'value': {'purchase_tax_rate': rate or False}}
205
206     def onchange_start_date(self, cr, uid, id, start_date):
207         if start_date:
208             start_date = datetime.datetime.strptime(start_date, "%Y-%m-%d")
209             end_date = (start_date + relativedelta(months=12)) - relativedelta(days=1)
210             return {'value': {'date_stop': end_date.strftime('%Y-%m-%d')}}
211         return {}
212
213     def set_default_taxes(self, cr, uid, ids, context=None):
214         """ set default sale and purchase taxes for products """
215         ir_values = self.pool.get('ir.values')
216         config = self.browse(cr, uid, ids[0], context)
217         ir_values.set_default(cr, uid, 'product.product', 'taxes_id',
218             config.default_sale_tax and [config.default_sale_tax.id] or False, company_id=config.company_id.id)
219         ir_values.set_default(cr, uid, 'product.product', 'supplier_taxes_id',
220             config.default_purchase_tax and [config.default_purchase_tax.id] or False, company_id=config.company_id.id)
221
222     def set_chart_of_accounts(self, cr, uid, ids, context=None):
223         """ install a chart of accounts for the given company (if required) """
224         config = self.browse(cr, uid, ids[0], context)
225         if config.chart_template_id:
226             assert config.expects_chart_of_accounts and not config.has_chart_of_accounts
227             wizard = self.pool.get('wizard.multi.charts.accounts')
228             wizard_id = wizard.create(cr, uid, {
229                 'company_id': config.company_id.id,
230                 'chart_template_id': config.chart_template_id.id,
231                 'code_digits': config.code_digits or 6,
232                 'sale_tax': config.sale_tax.id,
233                 'purchase_tax': config.purchase_tax.id,
234                 'sale_tax_rate': config.sale_tax_rate,
235                 'purchase_tax_rate': config.purchase_tax_rate,
236                 'complete_tax_set': config.complete_tax_set,
237             }, context)
238             wizard.execute(cr, uid, [wizard_id], context)
239
240     def set_fiscalyear(self, cr, uid, ids, context=None):
241         """ create a fiscal year for the given company (if necessary) """
242         config = self.browse(cr, uid, ids[0], context)
243         if config.has_chart_of_accounts or config.chart_template_id:
244             fiscalyear = self.pool.get('account.fiscalyear')
245             fiscalyear_count = fiscalyear.search_count(cr, uid,
246                 [('date_start', '<=', config.date_start), ('date_stop', '>=', config.date_stop),
247                  ('company_id', '=', config.company_id.id)],
248                 context=context)
249             if not fiscalyear_count:
250                 name = code = config.date_start[:4]
251                 if int(name) != int(config.date_stop[:4]):
252                     name = config.date_start[:4] +'-'+ config.date_stop[:4]
253                     code = config.date_start[2:4] +'-'+ config.date_stop[2:4]
254                 vals = {
255                     'name': name,
256                     'code': code,
257                     'date_start': config.date_start,
258                     'date_stop': config.date_stop,
259                     'company_id': config.company_id.id,
260                 }
261                 fiscalyear_id = fiscalyear.create(cr, uid, vals, context=context)
262                 if config.period == 'month':
263                     fiscalyear.create_period(cr, uid, [fiscalyear_id])
264                 elif config.period == '3months':
265                     fiscalyear.create_period3(cr, uid, [fiscalyear_id])
266
267     def get_default_dp(self, cr, uid, fields, context=None):
268         dp = self.pool.get('ir.model.data').get_object(cr, uid, 'product','decimal_account')
269         return {'decimal_precision': dp.digits}
270
271     def set_default_dp(self, cr, uid, ids, context=None):
272         config = self.browse(cr, uid, ids[0], context)
273         dp = self.pool.get('ir.model.data').get_object(cr, uid, 'product','decimal_account')
274         dp.write({'digits': config.decimal_precision})
275
276 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: