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