1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
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 ##############################################################################
25 from dateutil.relativedelta import relativedelta
26 from operator import itemgetter
27 from os.path import join as opj
29 from tools.translate import _
30 from osv import fields, osv
34 class account_configuration(osv.osv_memory):
35 _name = 'account.installer'
36 _inherit = 'res.config.settings'
37 __logger = logging.getLogger(_name)
39 def _get_charts(self, cr, uid, context=None):
40 modules = self.pool.get('ir.module.module')
41 # Looking for the module with the 'Account Charts' category
42 category_name, category_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'module_category_localization_account_charts')
43 ids = modules.search(cr, uid, [('category_id', '=', category_id)], context=context)
45 sorted(((m.name, m.shortdesc)
46 for m in modules.browse(cr, uid, ids, context=context)),
48 charts.insert(0, ('configurable', 'Generic Chart Of Accounts'))
52 'company_id': fields.many2one('res.company', 'Company',help="Your company."),
53 'currency_id': fields.related('company_id', 'currency_id', type='many2one', relation='res.currency', string='Currency', store=True, help="Currency of your company."),
54 'charts': fields.selection(_get_charts, 'Chart of Accounts',
56 help="Installs localized accounting charts to match as closely as "
57 "possible the accounting needs of your company based on your "
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 'has_default_company' : fields.boolean('Has Default Company', readonly=True),
63 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template'),
64 'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year'),
65 'default_paypal_account': fields.char("Your Paypal Account", size=128, help="Paypal username (usually email) for receiving online payments.", default_model='res.company'),
66 'company_footer': fields.char("Footer of Reports", size=128, readonly=True, help="Footer of reports based on your bank accounts."),
67 'sale_journal_id': fields.many2one('account.journal','Sale Journal'),
68 'customer_invoice_sequence_prefix': fields.related('sale_journal_id', 'sequence_id', 'prefix', type='char', relation='ir.sequence', string='Invoice Sequence'),
69 'customer_invoice_sequence_next': fields.related('sale_journal_id', 'sequence_id', 'number_next', type='integer', relation='ir.sequence', string='Invoice Sequence Next Number'),
70 'sale_refund_journal_id': fields.many2one('account.journal','Sale Refund Journal'),
71 'customer_refund_sequence_prefix': fields.related('sale_refund_journal_id', 'sequence_id', 'prefix', type='char', relation='ir.sequence', string='Refund Sequence'),
72 'customer_refund_sequence_next': fields.related('sale_refund_journal_id', 'sequence_id', 'number_next', type='integer', relation='ir.sequence', string='Refund Sequence Next Number'),
73 'purchase_journal_id': fields.many2one('account.journal','Purchase Journal'),
74 'supplier_invoice_sequence_prefix': fields.related('purchase_journal_id', 'sequence_id', 'prefix', type='char', relation='ir.sequence', string='Supplier Invoice Sequence'),
75 'supplier_invoice_sequence_next': fields.related('purchase_journal_id', 'sequence_id', 'number_next', type='integer', relation='ir.sequence', string='Supplier Invoice Sequence Next Number'),
76 'purchase_refund_journal_id': fields.many2one('account.journal','Purchase Refund Journal'),
77 'supplier_refund_sequence_prefix': fields.related('purchase_refund_journal_id', 'sequence_id', 'prefix', type='char', relation='ir.sequence', string='Supplier Refund Sequence'),
78 'supplier_refund_sequence_next': fields.related('purchase_refund_journal_id', 'sequence_id', 'number_next', type='integer', relation='ir.sequence', string='Supplier Refund Sequence Next Number'),
80 'module_account_check_writing': fields.boolean('Support check writings',
81 help=""" This allows you to check writing and printing.
82 It installs the account_check_writing module."""),
83 'module_account_accountant': fields.boolean('Accountant Features',
84 help="""This allows you to access all the accounting features like the journal items and the chart of accounts.
85 It installs the account_accountant module."""),
86 'module_account_asset': fields.boolean('Assets Management',
87 help="""This allows you to manages the assets owned by a company or an individual. It will keep track of depreciation's occurred on
88 those assets. And it allows to create Move's of the depreciation lines.
89 It installs the account_asset module."""),
90 'module_account_budget': fields.boolean('Budgets Management',
91 help="""This allows accountants to manage analytic and crossovered budgets.
92 Once the Master Budgets and the Budgets are defined (in Accounting/Budgets/),
93 the Project Managers can set the planned amount on each Analytic Account.
94 It installs the account_budget module."""),
95 'module_account_payment': fields.boolean('Supplier Payment Orders',
96 help="""This allows you to create and manage your payment orders, with purposes to
97 * serve as base for an easy plug-in of various automated payment mechanisms.
98 * provide a more efficient way to manage invoice payment.
99 It installs the account_payment module."""),
100 'module_account_voucher': fields.boolean('Manage Customer Payments',
101 help="""This includes all the basic requirements of Voucher Entries for Bank, Cash, Sales, Purchase, Expanse, Contra, etc.
102 It installs the account_voucher module."""),
103 'module_account_followup': fields.boolean('Customer Follow-Ups',
104 help="""This allows to automate letters for unpaid invoices, with multi-level recalls.
105 It installs the account_followup module."""),
106 'module_account_analytic_plans': fields.boolean('Support Multiple Analytic Plans',
107 help="""This allows to use several analytic plans, according to the general journal.
108 It installs the account_analytic_plans module."""),
109 'module_account_analytic_default': fields.boolean('Rules for Analytic Assignation',
110 help="""Set default values for your analytic accounts
111 Allows to automatically select analytic accounts based on criterias:
117 It installs the account_analytic_default module."""),
118 'module_account_invoice_layout': fields.boolean('Allow notes and subtotals',
119 help="""This provides some features to improve the layout of the invoices.
120 It gives you the possibility to:
121 * order all the lines of an invoice
122 * add titles, comment lines, sub total lines
123 * draw horizontal lines and put page breaks.
124 It installs the account_invoice_layout module."""),
126 'group_analytic_account_for_sales': fields.boolean('Analytic Accounting for Sales', group='base.group_user', implied_group='base.group_analytic_account_for_sales',
127 help="Allows you to set analytic account for sale order. It assigns 'Analytic Accounting for Sales' group to all employees."),
128 'group_analytic_account_for_purchase': fields.boolean('Analytic Accounting for Purchase', group='base.group_user', implied_group='base.group_analytic_account_for_purchase',
129 help="Allows you to set analytic account for purchase order. It assigns 'Analytic Accounting for Purchase' group to all employees."),
130 'group_dates_periods': fields.boolean('Allow dates/periods', group='base.group_user', implied_group='base.group_dates_periods',
131 help="Allows you to keep the period same as your invoice date when you validate the invoice."\
132 "It will add the group 'Allow dates and periods' for all users."),
133 'group_proforma_invoices': fields.boolean('Allow Pro-forma Invoices', group='base.group_user', implied_group='base.group_proforma_invoices',
134 help="Allows you to put invoice in pro-forma state. It assigns 'Allow Pro-forma Invoices' group to all employees."),
136 'multi_charts_id':fields.many2one('wizard.multi.charts.accounts', 'Multi charts accounts'),
137 'taxes_id':fields.many2one('account.tax.template', 'Default Sale Tax', domain="[('type_tax_use','=','sale')]"),
138 'supplier_taxes_id':fields.many2one('account.tax.template', 'Default Purchase Tax', domain="[('type_tax_use','=','purchase')]"),
139 'sale_tax_rate': fields.float('Sales Tax(%)'),
140 'purchase_tax_rate': fields.float('Purchase Tax(%)'),
141 'complete_tax_set': fields.boolean('Complete Set of Taxes'),
143 def _default_company(self, cr, uid, context=None):
144 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
145 return user.company_id and user.company_id.id or False
147 def _default_has_default_company(self, cr, uid, context=None):
148 count = self.pool.get('res.company').search_count(cr, uid, [], context=context)
149 return bool(count == 1)
152 'date_start': lambda *a: time.strftime('%Y-01-01'),
153 'date_stop': lambda *a: time.strftime('%Y-12-31'),
155 'company_id': _default_company,
156 'has_default_company': _default_has_default_company,
157 'charts': 'configurable',
160 def _check_default_tax(self, cr, uid, context=None):
161 ir_values_obj = self.pool.get('ir.values')
163 for tax in ir_values_obj.get(cr, uid, 'default', False, ['product.template']):
164 if tax[1] == 'taxes_id':
165 taxes.update({'taxes_id': tax[2]})
166 if tax[1] == 'supplier_taxes_id':
167 taxes.update({'supplier_taxes_id': tax[2]})
170 def set_tax_defaults(self, cr, uid, ids, context=None):
171 ir_values_obj = self.pool.get('ir.values')
174 wizard = self.browse(cr, uid, ids)[0]
175 if wizard.taxes_id.id:
176 ir_values_obj.set_default(cr, uid, 'product.template', 'taxes_id', wizard.taxes_id.id )
178 if wizard.supplier_taxes_id.id:
179 ir_values_obj.set_default(cr, uid, 'product.template', 'supplier_taxes_id', wizard.supplier_taxes_id.id )
183 def default_get(self, cr, uid, fields_list, context=None):
184 ir_values_obj = self.pool.get('ir.values')
185 chart_template_obj = self.pool.get('account.chart.template')
186 fiscalyear_obj = self.pool.get('account.fiscalyear')
187 journal_obj = self.pool.get('account.journal')
188 res = super(account_configuration, self).default_get(cr, uid, fields_list, context=context)
189 taxes = self._check_default_tax(cr, uid, context)
190 chart_template_ids = chart_template_obj.search(cr, uid, [('visible', '=', True)], context=context)
191 fiscalyear_ids = fiscalyear_obj.search(cr, uid, [('date_start','=',time.strftime('%Y-01-01')),('date_stop','=',time.strftime('%Y-12-31'))])
193 cmp_id = self.pool.get('ir.model.data').get_object(cr, uid, 'base', 'main_company').id
194 company_data = self.pool.get('res.company').browse(cr, uid, cmp_id)
195 res.update({'company_footer': company_data.rml_footer2})
197 journal_ids = journal_obj.search(cr, uid, [('company_id', '=', res.get('company_id'))])
199 for journal in journal_obj.browse(cr, uid, journal_ids, context=context):
200 if journal.type == 'sale':
201 res.update({'sale_journal_id': journal.id})
202 if journal.type == 'sale_refund':
203 res.update({'sale_refund_journal_id': journal.id})
204 if journal.type == 'purchase':
205 res.update({'purchase_journal_id': journal.id})
206 if journal.type == 'purchase_refund':
207 res.update({'purchase_refund_journal_id': journal.id})
209 if chart_template_ids:
210 res.update({'chart_template_id': chart_template_ids[0]})
211 data = chart_template_obj.browse(cr, uid, chart_template_ids[0], context=context)
212 res.update({'complete_tax_set': data.complete_tax_set})
213 supplier_taxes_id = ir_values_obj.get_default(cr, uid, 'product.template', 'supplier_taxes_id')
214 res.update({'supplier_taxes_id': supplier_taxes_id})
215 taxes_id = ir_values_obj.get_default(cr, uid, 'product.template', 'taxes_id')
216 res.update({'taxes_id': taxes_id})
219 res.update({'fiscalyear_id': fiscalyear_ids[0]})
221 if chart_template_ids:
222 sale_tax_id = taxes.get('taxes_id')
223 res.update({'taxes_id': isinstance(sale_tax_id,list) and sale_tax_id[0] or sale_tax_id})
224 purchase_tax_id = taxes.get('supplier_taxes_id')
225 res.update({'supplier_taxes_id': isinstance(purchase_tax_id,list) and purchase_tax_id[0] or purchase_tax_id})
227 res.update({'sale_tax_rate': 15.0, 'purchase_tax_rate': 15.0})
230 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
231 ir_values_obj = self.pool.get('ir.values')
232 res = super(account_configuration, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)
234 # display in the widget selection only the companies that haven't been configured yet
235 unconfigured_cmp = self.get_unconfigured_cmp(cr, uid, context=context)
236 for field in res['fields']:
237 if field == 'company_id':
238 res['fields'][field]['domain'] = [('id','in',unconfigured_cmp)]
239 res['fields'][field]['selection'] = [('', '')]
241 cmp_select = [(line.id, line.name) for line in self.pool.get('res.company').browse(cr, uid, unconfigured_cmp)]
242 res['fields'][field]['selection'] = cmp_select
245 def get_unconfigured_cmp(self, cr, uid, context=None):
246 """ get the list of companies that have not been configured yet
247 but don't care about the demo chart of accounts """
249 company_ids = self.pool.get('res.company').search(cr, uid, [], context=context)
250 cr.execute("SELECT company_id FROM account_account WHERE active = 't' AND account_account.parent_id IS NULL AND name != %s", ("Chart For Automated Tests",))
251 configured_cmp = [r[0] for r in cr.fetchall()]
252 return list(set(company_ids)-set(configured_cmp))
254 def check_unconfigured_cmp(self, cr, uid, context=None):
255 """ check if there are still unconfigured companies """
256 if not self.get_unconfigured_cmp(cr, uid, context=context):
257 raise osv.except_osv(_('No unconfigured company !'), _("There are currently no company without chart of account. The wizard will therefore not be executed."))
259 def on_change_start_date(self, cr, uid, id, start_date=False):
261 start_date = datetime.datetime.strptime(start_date, "%Y-%m-%d")
262 end_date = (start_date + relativedelta(months=12)) - relativedelta(days=1)
263 return {'value': {'date_stop': end_date.strftime('%Y-%m-%d')}}
266 def on_change_company_id(self, cr, uid, id, company_id=False):
267 company_obj = self.pool.get('res.company')
268 currency_id = company_obj.browse(cr, uid, company_id).currency_id
269 return {'value': {'currency_id': currency_id.id}}
271 def install_chartofaccounts(self, cr, uid, ids, context=None):
272 ir_module = self.pool.get('ir.module.module')
273 multi_chart_obj = self.pool.get('wizard.multi.charts.accounts')
274 chart_template_obj = self.pool.get('account.chart.template')
275 tax_templ_obj = self.pool.get('account.tax.template')
279 for res in self.read(cr, uid, ids, context=context):
280 chart = res.get('charts')
281 if chart == 'configurable':
282 #load generic chart of account
283 fp = tools.file_open(opj('account', 'configurable_account_chart.xml'))
284 tools.convert_xml_import(cr, 'account', fp, {}, 'init', True, None)
286 elif chart.startswith('l10n_'):
287 mod_ids = ir_module.search(cr, uid, [('name','=',chart)])
288 if mod_ids and ir_module.browse(cr, uid, mod_ids[0], context).state == 'uninstalled':
289 ir_module.button_immediate_install(cr, uid, mod_ids, context)
291 chart_template_ids = chart_template_obj.search(cr, uid, [('visible', '=', True)], context=context)
292 complete_tax_set = chart_template_obj.browse(cr, uid, chart_template_ids[0]).complete_tax_set
293 if not complete_tax_set:
294 code_digits = multi_chart_obj.onchange_chart_template_id(cr, uid, [], chart_template_ids[0], context=context)['value']['code_digits']
295 object_id = multi_chart_obj.create(cr, uid, {'code_digits': code_digits , 'sale_tax_rate': res.get('sale_tax_rate'), 'purchase_tax_rate': res.get('purchase_tax_rate')}, context=context)
296 multi_chart_obj.execute(cr, uid, [object_id], context=context)
298 def configure_fiscalyear(self, cr, uid, ids, context=None):
301 fy_obj = self.pool.get('account.fiscalyear')
302 for res in self.read(cr, uid, ids, context=context):
303 if 'date_start' in res and 'date_stop' in res:
304 f_ids = fy_obj.search(cr, uid, [('date_start', '<=', res['date_start']), ('date_stop', '>=', res['date_stop']), ('company_id', '=', res['company_id'][0])], context=context)
306 name = code = res['date_start'][:4]
307 if int(name) != int(res['date_stop'][:4]):
308 name = res['date_start'][:4] +'-'+ res['date_stop'][:4]
309 code = res['date_start'][2:4] +'-'+ res['date_stop'][2:4]
313 'date_start': res['date_start'],
314 'date_stop': res['date_stop'],
315 'company_id': res['company_id'][0]
317 fiscal_id = fy_obj.create(cr, uid, vals, context=context)
318 if res['period'] == 'month':
319 fy_obj.create_period(cr, uid, [fiscal_id])
320 elif res['period'] == '3months':
321 fy_obj.create_period3(cr, uid, [fiscal_id])
323 account_configuration()
325 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: