1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Business Applications
5 # Copyright (C) 2004-2012 OpenERP S.A. (<http://openerp.com>).
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.
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.
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/>.
20 ##############################################################################
24 from dateutil.relativedelta import relativedelta
27 from openerp import SUPERUSER_ID
28 from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF
29 from openerp.tools.translate import _
30 from openerp.osv import fields, osv
32 class account_config_settings(osv.osv_memory):
33 _name = 'account.config.settings'
34 _inherit = 'res.config.settings'
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"),
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'),
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),
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'),
82 'module_account_check_writing': fields.boolean('Pay your suppliers by check',
83 help='This allows you to check writing and printing.\n'
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.\n'
89 'It keeps track of the depreciation occurred on those assets, and creates account move for those depreciation lines.\n'
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.\n'
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 \n'
99 '* serve as base for an easy plug-in of various automated payment mechanisms, and \n'
100 '* provide a more efficient way to manage invoice payments.\n'
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.\n'
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.\n'
107 '-This installs the module account_followup.'),
108 'module_product_email_template': fields.boolean('Send products tools and information at the invoice confirmation',
109 help='With this module, link your products to a template to send complete information and tools to your customer.\n'
110 'For instance when invoicing a training, the training agenda and materials will automatically be send to your customers.'),
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 help="This sale tax will be assigned by default on new products."),
116 'default_purchase_tax': fields.many2one('account.tax', 'Default purchase tax',
117 help="This purchase tax will be assigned by default on new products."),
118 'decimal_precision': fields.integer('Decimal precision on journal entries',
119 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."""),
120 'group_multi_currency': fields.boolean('Allow multi currencies',
121 implied_group='base.group_multi_currency',
122 help="Allows you multi currency environment"),
123 'group_analytic_accounting': fields.boolean('Analytic accounting',
124 implied_group='analytic.group_analytic_accounting',
125 help="Allows you to use the analytic accounting."),
126 'group_check_supplier_invoice_total': fields.boolean('Check the total of supplier invoices',
127 implied_group="account.group_supplier_inv_check_total"),
130 def _default_company(self, cr, uid, context=None):
131 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
132 return user.company_id.id
134 def _default_has_default_company(self, cr, uid, context=None):
135 count = self.pool.get('res.company').search_count(cr, uid, [], context=context)
136 return bool(count == 1)
138 def _get_default_fiscalyear_data(self, cr, uid, company_id, context=None):
139 """Compute default period, starting and ending date for fiscalyear
140 - if in a fiscal year, use its period, starting and ending date
141 - if past fiscal year, use its period, and new dates [ending date of the latest +1 day ; ending date of the latest +1 year]
142 - if no fiscal year, use monthly, 1st jan, 31th dec of this year
143 :return: (date_start, date_stop, period) at format DEFAULT_SERVER_DATETIME_FORMAT
145 fiscalyear_ids = self.pool.get('account.fiscalyear').search(cr, uid,
146 [('date_start', '<=', time.strftime(DF)), ('date_stop', '>=', time.strftime(DF)),
147 ('company_id', '=', company_id)])
149 # is in a current fiscal year, use this one
150 fiscalyear = self.pool.get('account.fiscalyear').browse(cr, uid, fiscalyear_ids[0], context=context)
151 if len(fiscalyear.period_ids) == 5: # 4 periods of 3 months + opening period
155 return (fiscalyear.date_start, fiscalyear.date_stop, period)
157 past_fiscalyear_ids = self.pool.get('account.fiscalyear').search(cr, uid,
158 [('date_stop', '<=', time.strftime(DF)), ('company_id', '=', company_id)])
159 if past_fiscalyear_ids:
160 # use the latest fiscal, sorted by (start_date, id)
161 latest_year = self.pool.get('account.fiscalyear').browse(cr, uid, past_fiscalyear_ids[-1], context=context)
162 latest_stop = datetime.datetime.strptime(latest_year.date_stop, DF)
163 if len(latest_year.period_ids) == 5:
167 return ((latest_stop+datetime.timedelta(days=1)).strftime(DF), latest_stop.replace(year=latest_stop.year+1).strftime(DF), period)
169 return (time.strftime('%Y-01-01'), time.strftime('%Y-12-31'), 'month')
173 'company_id': _default_company,
174 'has_default_company': _default_has_default_company,
177 def create(self, cr, uid, values, context=None):
178 id = super(account_config_settings, self).create(cr, uid, values, context)
179 # Hack: to avoid some nasty bug, related fields are not written upon record creation.
180 # Hence we write on those fields here.
182 for fname, field in self._columns.iteritems():
183 if isinstance(field, fields.related) and fname in values:
184 vals[fname] = values[fname]
185 self.write(cr, uid, [id], vals, context)
188 def onchange_company_id(self, cr, uid, ids, company_id, context=None):
189 # update related fields
191 values['currency_id'] = False
193 company = self.pool.get('res.company').browse(cr, uid, company_id, context=context)
194 has_chart_of_accounts = company_id not in self.pool.get('account.installer').get_unconfigured_cmp(cr, uid)
195 fiscalyear_count = self.pool.get('account.fiscalyear').search_count(cr, uid,
196 [('date_start', '<=', time.strftime('%Y-%m-%d')), ('date_stop', '>=', time.strftime('%Y-%m-%d')),
197 ('company_id', '=', company_id)])
198 date_start, date_stop, period = self._get_default_fiscalyear_data(cr, uid, company_id, context=context)
200 'expects_chart_of_accounts': company.expects_chart_of_accounts,
201 'currency_id': company.currency_id.id,
202 'paypal_account': company.paypal_account,
203 'company_footer': company.rml_footer,
204 'has_chart_of_accounts': has_chart_of_accounts,
205 'has_fiscal_year': bool(fiscalyear_count),
206 'chart_template_id': False,
207 'tax_calculation_rounding_method': company.tax_calculation_rounding_method,
208 'date_start': date_start,
209 'date_stop': date_stop,
212 # update journals and sequences
213 for journal_type in ('sale', 'sale_refund', 'purchase', 'purchase_refund'):
214 for suffix in ('_journal_id', '_sequence_prefix', '_sequence_next'):
215 values[journal_type + suffix] = False
216 journal_obj = self.pool.get('account.journal')
217 journal_ids = journal_obj.search(cr, uid, [('company_id', '=', company_id)])
218 for journal in journal_obj.browse(cr, uid, journal_ids):
219 if journal.type in ('sale', 'sale_refund', 'purchase', 'purchase_refund'):
221 journal.type + '_journal_id': journal.id,
222 journal.type + '_sequence_prefix': journal.sequence_id.prefix,
223 journal.type + '_sequence_next': journal.sequence_id.number_next,
226 ir_values = self.pool.get('ir.values')
227 taxes_id = ir_values.get_default(cr, uid, 'product.product', 'taxes_id', company_id=company_id)
228 supplier_taxes_id = ir_values.get_default(cr, uid, 'product.product', 'supplier_taxes_id', company_id=company_id)
230 'default_sale_tax': isinstance(taxes_id, list) and taxes_id[0] or taxes_id,
231 'default_purchase_tax': isinstance(supplier_taxes_id, list) and supplier_taxes_id[0] or supplier_taxes_id,
233 return {'value': values}
235 def onchange_chart_template_id(self, cr, uid, ids, chart_template_id, context=None):
236 tax_templ_obj = self.pool.get('account.tax.template')
238 'complete_tax_set': False, 'sale_tax': False, 'purchase_tax': False,
239 'sale_tax_rate': 15, 'purchase_tax_rate': 15,
241 if chart_template_id:
242 # update complete_tax_set, sale_tax and purchase_tax
243 chart_template = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
244 res['value'].update({'complete_tax_set': chart_template.complete_tax_set})
245 if chart_template.complete_tax_set:
246 # 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
247 sale_tax_ids = tax_templ_obj.search(cr, uid,
248 [("chart_template_id", "=", chart_template_id), ('type_tax_use', 'in', ('sale','all'))],
249 order="sequence, id desc")
250 purchase_tax_ids = tax_templ_obj.search(cr, uid,
251 [("chart_template_id", "=", chart_template_id), ('type_tax_use', 'in', ('purchase','all'))],
252 order="sequence, id desc")
253 res['value']['sale_tax'] = sale_tax_ids and sale_tax_ids[0] or False
254 res['value']['purchase_tax'] = purchase_tax_ids and purchase_tax_ids[0] or False
255 if chart_template.code_digits:
256 res['value']['code_digits'] = chart_template.code_digits
259 def onchange_tax_rate(self, cr, uid, ids, rate, context=None):
260 return {'value': {'purchase_tax_rate': rate or False}}
262 def onchange_start_date(self, cr, uid, id, start_date):
264 start_date = datetime.datetime.strptime(start_date, "%Y-%m-%d")
265 end_date = (start_date + relativedelta(months=12)) - relativedelta(days=1)
266 return {'value': {'date_stop': end_date.strftime('%Y-%m-%d')}}
269 def open_company_form(self, cr, uid, ids, context=None):
270 config = self.browse(cr, uid, ids[0], context)
272 'type': 'ir.actions.act_window',
273 'name': 'Configure your Company',
274 'res_model': 'res.company',
275 'res_id': config.company_id.id,
279 def set_default_taxes(self, cr, uid, ids, context=None):
280 """ set default sale and purchase taxes for products """
281 if uid != SUPERUSER_ID and not self.pool['res.users'].has_group(cr, uid, 'base.group_erp_manager'):
282 raise openerp.exceptions.AccessError(_("Only administrators can change the settings"))
283 ir_values = self.pool.get('ir.values')
284 config = self.browse(cr, uid, ids[0], context)
285 ir_values.set_default(cr, SUPERUSER_ID, 'product.product', 'taxes_id',
286 config.default_sale_tax and [config.default_sale_tax.id] or False, company_id=config.company_id.id)
287 ir_values.set_default(cr, SUPERUSER_ID, 'product.product', 'supplier_taxes_id',
288 config.default_purchase_tax and [config.default_purchase_tax.id] or False, company_id=config.company_id.id)
290 def set_chart_of_accounts(self, cr, uid, ids, context=None):
291 """ install a chart of accounts for the given company (if required) """
292 config = self.browse(cr, uid, ids[0], context)
293 if config.chart_template_id:
294 assert config.expects_chart_of_accounts and not config.has_chart_of_accounts
295 wizard = self.pool.get('wizard.multi.charts.accounts')
296 wizard_id = wizard.create(cr, uid, {
297 'company_id': config.company_id.id,
298 'chart_template_id': config.chart_template_id.id,
299 'code_digits': config.code_digits or 6,
300 'sale_tax': config.sale_tax.id,
301 'purchase_tax': config.purchase_tax.id,
302 'sale_tax_rate': config.sale_tax_rate,
303 'purchase_tax_rate': config.purchase_tax_rate,
304 'complete_tax_set': config.complete_tax_set,
305 'currency_id': config.currency_id.id,
307 wizard.execute(cr, uid, [wizard_id], context)
309 def set_fiscalyear(self, cr, uid, ids, context=None):
310 """ create a fiscal year for the given company (if necessary) """
311 config = self.browse(cr, uid, ids[0], context)
312 if config.has_chart_of_accounts or config.chart_template_id:
313 fiscalyear = self.pool.get('account.fiscalyear')
314 fiscalyear_count = fiscalyear.search_count(cr, uid,
315 [('date_start', '<=', config.date_start), ('date_stop', '>=', config.date_stop),
316 ('company_id', '=', config.company_id.id)],
318 if not fiscalyear_count:
319 name = code = config.date_start[:4]
320 if int(name) != int(config.date_stop[:4]):
321 name = config.date_start[:4] +'-'+ config.date_stop[:4]
322 code = config.date_start[2:4] +'-'+ config.date_stop[2:4]
326 'date_start': config.date_start,
327 'date_stop': config.date_stop,
328 'company_id': config.company_id.id,
330 fiscalyear_id = fiscalyear.create(cr, uid, vals, context=context)
331 if config.period == 'month':
332 fiscalyear.create_period(cr, uid, [fiscalyear_id])
333 elif config.period == '3months':
334 fiscalyear.create_period3(cr, uid, [fiscalyear_id])
336 def get_default_dp(self, cr, uid, fields, context=None):
337 dp = self.pool.get('ir.model.data').get_object(cr, uid, 'product','decimal_account')
338 return {'decimal_precision': dp.digits}
340 def set_default_dp(self, cr, uid, ids, context=None):
341 config = self.browse(cr, uid, ids[0], context)
342 dp = self.pool.get('ir.model.data').get_object(cr, uid, 'product','decimal_account')
343 dp.write({'digits': config.decimal_precision})
345 def onchange_analytic_accounting(self, cr, uid, ids, analytic_accounting, context=None):
346 if analytic_accounting:
348 'module_account_accountant': True,
351 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: