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 'sale_tax': fields.float('Default Sale Tax'),
55 'purchase_tax': fields.float('Default Purchase Tax'),
56 'charts': fields.selection(_get_charts, 'Chart of Accounts',
58 help="Installs localized accounting charts to match as closely as "
59 "possible the accounting needs of your company based on your "
61 'date_start': fields.date('Start Date', required=True),
62 'date_stop': fields.date('End Date', required=True),
63 'period': fields.selection([('month', 'Monthly'), ('3months','3 Monthly')], 'Periods', required=True),
64 'has_default_company' : fields.boolean('Has Default Company', readonly=True),
65 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template'),
66 'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year'),
67 'default_paypal_account': fields.char("Your Paypal Account", size=128, help="Paypal username (usually email) for receiving online payments.", default_model='res.company'),
68 'company_footer': fields.char("Footer of Reports", size=128, readonly=True, help="Footer of reports based on your bank accounts."),
69 'sale_journal_id': fields.many2one('account.journal','Sale Journal'),
70 'customer_invoice_sequence_prefix': fields.related('sale_journal_id', 'sequence_id', 'prefix', type='char', relation='ir.sequence', string='Invoice Sequence'),
71 'customer_invoice_sequence_next': fields.related('sale_journal_id', 'sequence_id', 'number_next', type='integer', relation='ir.sequence', string='Invoice Sequence Next Number'),
72 'sale_refund_journal_id': fields.many2one('account.journal','Sale Refund Journal'),
73 'customer_refund_sequence_prefix': fields.related('sale_refund_journal_id', 'sequence_id', 'prefix', type='char', relation='ir.sequence', string='Refund Sequence'),
74 'customer_refund_sequence_next': fields.related('sale_refund_journal_id', 'sequence_id', 'number_next', type='integer', relation='ir.sequence', string='Refund Sequence Next Number'),
76 'purchase_journal_id': fields.many2one('account.journal','Purchase Journal'),
77 'supplier_invoice_sequence_prefix': fields.related('purchase_journal_id', 'sequence_id', 'prefix', type='char', relation='ir.sequence', string='Supplier Invoice Sequence'),
78 'supplier_invoice_sequence_next': fields.related('purchase_journal_id', 'sequence_id', 'number_next', type='integer', relation='ir.sequence', string='Supplier Invoice Sequence Next Number'),
79 'purchase_refund_journal_id': fields.many2one('account.journal','Purchase Refund Journal'),
80 'supplier_refund_sequence_prefix': fields.related('purchase_refund_journal_id', 'sequence_id', 'prefix', type='char', relation='ir.sequence', string='Supplier Refund Sequence'),
81 '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'),
83 'module_account_check_writing': fields.boolean('Support check writings',
84 help=""" This allows you to check writing and printing.
85 It installs the account_check_writing module."""),
86 'module_account_accountant': fields.boolean('Accountant Features',
87 help="""This allows you to access all the accounting features like the journal items and the chart of accounts.
88 It installs the account_accountant module."""),
89 'module_account_asset': fields.boolean('Assets Management',
90 help="""This allows you to manages the assets owned by a company or an individual. It will keep track of depreciation's occurred on
91 those assets. And it allows to create Move's of the depreciation lines.
92 It installs the account_asset module."""),
93 'module_account_budget': fields.boolean('Budgets Management',
94 help="""This allows accountants to manage analytic and crossovered budgets.
95 Once the Master Budgets and the Budgets are defined (in Accounting/Budgets/),
96 the Project Managers can set the planned amount on each Analytic Account.
97 It installs the account_budget module."""),
98 'module_account_payment': fields.boolean('Supplier 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.
101 * provide a more efficient way to manage invoice payment.
102 It installs the account_payment module."""),
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, Expanse, Contra, etc.
105 It installs the account_voucher module."""),
106 'module_account_followup': fields.boolean('Customer Follow-Ups',
107 help="""This allows to automate letters for unpaid invoices, with multi-level recalls.
108 It installs the account_followup module."""),
109 'module_account_analytic_plans': fields.boolean('Support Multiple Analytic Plans',
110 help="""This allows to use several analytic plans, according to the general journal.
111 It installs the account_analytic_plans module."""),
112 'module_account_analytic_default': fields.boolean('Rules for Analytic Assignation',
113 help="""Set default values for your analytic accounts
114 Allows to automatically select analytic accounts based on criterias:
120 It installs the account_analytic_default module."""),
121 'module_account_invoice_layout': fields.boolean('Allow notes and subtotals',
122 help="""This provides some features to improve the layout of the invoices.
123 It gives you the possibility to:
124 * order all the lines of an invoice
125 * add titles, comment lines, sub total lines
126 * draw horizontal lines and put page breaks.
127 It installs the account_invoice_layout module."""),
129 'group_analytic_account_for_sales': fields.boolean('Analytic Accounting for Sales', group='base.group_user', implied_group='base.group_analytic_account_for_sales',
130 help="Allows you to set analytic account for sale order. It assigns 'Analytic Accounting for Sales' group to all employees."),
131 'group_analytic_account_for_purchase': fields.boolean('Analytic Accounting for Purchase', group='base.group_user', implied_group='base.group_analytic_account_for_purchase',
132 help="Allows you to set analytic account for purchase order. It assigns 'Analytic Accounting for Purchase' group to all employees."),
133 'group_dates_periods': fields.boolean('Allow dates/periods', group='base.group_user', implied_group='base.group_dates_periods',
134 help="Allows you to keep the period same as your invoice date when you validate the invoice."\
135 "It will add the group 'Allow dates and periods' for all users."),
136 'group_proforma_invoices': fields.boolean('Allow Pro-forma Invoices', group='base.group_user', implied_group='base.group_proforma_invoices',
137 help="Allows you to put invoice in pro-forma state. It assigns 'Allow Pro-forma Invoices' group to all employees."),
140 def _default_company(self, cr, uid, context=None):
141 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
142 return user.company_id and user.company_id.id or False
144 def _default_has_default_company(self, cr, uid, context=None):
145 count = self.pool.get('res.company').search_count(cr, uid, [], context=context)
146 return bool(count == 1)
149 'date_start': lambda *a: time.strftime('%Y-01-01'),
150 'date_stop': lambda *a: time.strftime('%Y-12-31'),
152 'company_id': _default_company,
153 'has_default_company': _default_has_default_company,
154 'charts': 'configurable',
157 def _check_default_tax(self, cr, uid, context=None):
158 ir_values_obj = self.pool.get('ir.values')
160 for tax in ir_values_obj.get(cr, uid, 'default', False, ['product.product']):
161 if tax[1] == 'taxes_id':
162 taxes.update({'taxes_id': tax[2]})
163 if tax[1] == 'supplier_taxes_id':
164 taxes.update({'supplier_taxes_id': tax[2]})
168 def default_get(self, cr, uid, fields_list, context=None):
169 ir_values_obj = self.pool.get('ir.values')
170 chart_template_obj = self.pool.get('account.chart.template')
171 fiscalyear_obj = self.pool.get('account.fiscalyear')
172 journal_obj = self.pool.get('account.journal')
173 res = super(account_configuration, self).default_get(cr, uid, fields_list, context=context)
174 res.update({'sale_tax': 15.0, 'purchase_tax': 15.0})
175 taxes = self._check_default_tax(cr, uid, context)
176 chart_template_ids = chart_template_obj.search(cr, uid, [('visible', '=', True)], context=context)
177 fiscalyear_ids = fiscalyear_obj.search(cr, uid, [('date_start','=',time.strftime('%Y-01-01')),('date_stop','=',time.strftime('%Y-12-31'))])
179 cmp_id = self.pool.get('ir.model.data').get_object(cr, uid, 'base', 'main_company').id
180 company_data = self.pool.get('res.company').browse(cr, uid, cmp_id)
181 res.update({'company_footer': company_data.rml_footer2})
183 journal_ids = journal_obj.search(cr, uid, [('company_id', '=', res.get('company_id'))])
185 for journal in journal_obj.browse(cr, uid, journal_ids, context=context):
186 if journal.type == 'sale':
187 res.update({'sale_journal_id': journal.id})
188 if journal.type == 'sale_refund':
189 res.update({'sale_refund_journal_id': journal.id})
190 if journal.type == 'purchase':
191 res.update({'purchase_journal_id': journal.id})
192 if journal.type == 'purchase_refund':
193 res.update({'purchase_refund_journal_id': journal.id})
195 if chart_template_ids:
196 res.update({'chart_template_id': chart_template_ids[0]})
198 res.update({'fiscalyear_id': fiscalyear_ids[0]})
200 sale_tax_id = taxes.get('taxes_id')
201 res.update({'sale_tax': isinstance(sale_tax_id,list) and sale_tax_id[0] or False})
202 purchase_tax_id = taxes.get('supplier_taxes_id')
203 res.update({'purchase_tax': isinstance(purchase_tax_id,list) and purchase_tax_id[0] or False})
206 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
207 ir_values_obj = self.pool.get('ir.values')
208 res = super(account_configuration, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)
210 if self._check_default_tax(cr, uid, context):
211 if res['fields'].get('sale_tax') and res['fields'].get('purchase_tax'):
212 res['fields']['sale_tax'] = {'domain': [], 'views': {}, 'context': {}, 'selectable': True, 'type': 'many2one', 'relation': 'account.tax', 'string': 'Default Sale Tax'}
213 res['fields']['purchase_tax'] = {'domain': [], 'views': {}, 'context': {}, 'selectable': True, 'type': 'many2one', 'relation': 'account.tax', 'string': 'Default Purchase Tax'}
214 # display in the widget selection only the companies that haven't been configured yet
215 unconfigured_cmp = self.get_unconfigured_cmp(cr, uid, context=context)
216 for field in res['fields']:
217 if field == 'company_id':
218 res['fields'][field]['domain'] = [('id','in',unconfigured_cmp)]
219 res['fields'][field]['selection'] = [('', '')]
221 cmp_select = [(line.id, line.name) for line in self.pool.get('res.company').browse(cr, uid, unconfigured_cmp)]
222 res['fields'][field]['selection'] = cmp_select
225 def get_unconfigured_cmp(self, cr, uid, context=None):
226 """ get the list of companies that have not been configured yet
227 but don't care about the demo chart of accounts """
229 company_ids = self.pool.get('res.company').search(cr, uid, [], context=context)
230 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",))
231 configured_cmp = [r[0] for r in cr.fetchall()]
232 return list(set(company_ids)-set(configured_cmp))
234 def check_unconfigured_cmp(self, cr, uid, context=None):
235 """ check if there are still unconfigured companies """
236 if not self.get_unconfigured_cmp(cr, uid, context=context):
237 raise osv.except_osv(_('No unconfigured company !'), _("There are currently no company without chart of account. The wizard will therefore not be executed."))
239 def on_change_start_date(self, cr, uid, id, start_date=False):
241 start_date = datetime.datetime.strptime(start_date, "%Y-%m-%d")
242 end_date = (start_date + relativedelta(months=12)) - relativedelta(days=1)
243 return {'value': {'date_stop': end_date.strftime('%Y-%m-%d')}}
246 def install_chartofaccounts(self, cr, uid, ids, context=None):
247 ir_module = self.pool.get('ir.module.module')
250 for res in self.read(cr, uid, ids, context=context):
251 chart = res.get('charts')
252 if chart == 'configurable':
253 #load generic chart of account
254 fp = tools.file_open(opj('account', 'configurable_account_chart.xml'))
255 tools.convert_xml_import(cr, 'account', fp, {}, 'init', True, None)
257 elif chart.startswith('l10n_'):
258 mod_ids = ir_module.search(cr, uid, [('name','=',chart)])
259 if mod_ids and ir_module.browse(cr, uid, mod_ids[0], context).state == 'uninstalled':
260 ir_module.button_immediate_install(cr, uid, mod_ids, context)
262 def configure_fiscalyear(self, cr, uid, ids, context=None):
265 fy_obj = self.pool.get('account.fiscalyear')
266 for res in self.read(cr, uid, ids, context=context):
267 if 'date_start' in res and 'date_stop' in res:
268 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)
270 name = code = res['date_start'][:4]
271 if int(name) != int(res['date_stop'][:4]):
272 name = res['date_start'][:4] +'-'+ res['date_stop'][:4]
273 code = res['date_start'][2:4] +'-'+ res['date_stop'][2:4]
277 'date_start': res['date_start'],
278 'date_stop': res['date_stop'],
279 'company_id': res['company_id'][0]
281 fiscal_id = fy_obj.create(cr, uid, vals, context=context)
282 if res['period'] == 'month':
283 fy_obj.create_period(cr, uid, [fiscal_id])
284 elif res['period'] == '3months':
285 fy_obj.create_period3(cr, uid, [fiscal_id])
287 account_configuration()
289 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: