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 ##############################################################################
23 from lxml import etree
25 from osv import fields, osv
27 from tools.translate import _
29 class one2many_mod2(fields.one2many):
30 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
37 if 'journal_id' in context:
38 journal = obj.pool.get('account.journal').browse(cr, user, context['journal_id'], context=context)
39 pnum = int(name[7]) -1
40 plan = journal.plan_id
41 if plan and len(plan.plan_ids) > pnum:
42 acc_id = plan.plan_ids[pnum].root_analytic_id.id
43 ids2 = obj.pool.get(self._obj).search(cr, user, [(self._fields_id,'in',ids),('analytic_account_id','child_of',[acc_id])], limit=self._limit)
45 ids2 = obj.pool.get(self._obj).search(cr, user, [(self._fields_id,'in',ids)], limit=self._limit)
46 for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
47 res[r[self._fields_id]].append( r['id'] )
50 class account_analytic_plan(osv.osv):
51 _name = "account.analytic.plan"
52 _description = "Analytic Plan"
54 'name': fields.char('Analytic Plan', size=64, required=True, select=True),
55 'plan_ids': fields.one2many('account.analytic.plan.line', 'plan_id', 'Analytic Plans'),
58 account_analytic_plan()
60 class account_analytic_plan_line(osv.osv):
61 _name = "account.analytic.plan.line"
62 _description = "Analytic Plan Line"
63 _order = "sequence, id"
65 'plan_id': fields.many2one('account.analytic.plan','Analytic Plan'),
66 'name': fields.char('Plan Name', size=64, required=True, select=True),
67 'sequence': fields.integer('Sequence'),
68 'root_analytic_id': fields.many2one('account.analytic.account', 'Root Account', help="Root account of this plan.", required=False),
69 'min_required': fields.float('Minimum Allowed (%)'),
70 'max_required': fields.float('Maximum Allowed (%)'),
73 'min_required': 100.0,
74 'max_required': 100.0,
77 account_analytic_plan_line()
79 class account_analytic_plan_instance(osv.osv):
80 _name = "account.analytic.plan.instance"
81 _description = "Analytic Plan Instance"
83 'name': fields.char('Analytic Distribution', size=64),
84 'code': fields.char('Distribution Code', size=16),
85 'journal_id': fields.many2one('account.analytic.journal', 'Analytic Journal' ),
86 'account_ids': fields.one2many('account.analytic.plan.instance.line', 'plan_id', 'Account Id'),
87 'account1_ids': one2many_mod2('account.analytic.plan.instance.line', 'plan_id', 'Account1 Id'),
88 'account2_ids': one2many_mod2('account.analytic.plan.instance.line', 'plan_id', 'Account2 Id'),
89 'account3_ids': one2many_mod2('account.analytic.plan.instance.line', 'plan_id', 'Account3 Id'),
90 'account4_ids': one2many_mod2('account.analytic.plan.instance.line', 'plan_id', 'Account4 Id'),
91 'account5_ids': one2many_mod2('account.analytic.plan.instance.line', 'plan_id', 'Account5 Id'),
92 'account6_ids': one2many_mod2('account.analytic.plan.instance.line', 'plan_id', 'Account6 Id'),
93 'plan_id': fields.many2one('account.analytic.plan', "Model's Plan"),
96 def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
99 journal_obj = self.pool.get('account.journal')
100 if context.get('journal_id', False):
101 journal = journal_obj.browse(cr, user, [context['journal_id']], context=context)[0]
102 analytic_journal = journal.analytic_journal_id and journal.analytic_journal_id.id or False
104 args.append(('journal_id', '=', analytic_journal))
105 args.append(('journal_id', '=', False))
106 res = super(account_analytic_plan_instance, self).search(cr, user, args, offset=offset, limit=limit, order=order,
107 context=context, count=count)
110 def copy(self, cr, uid, id, default=None, context=None):
113 default.update({'account1_ids':False, 'account2_ids':False, 'account3_ids':False,
114 'account4_ids':False, 'account5_ids':False, 'account6_ids':False})
115 return super(account_analytic_plan_instance, self).copy(cr, uid, id, default, context=context)
117 def _default_journal(self, cr, uid, context=None):
120 journal_obj = self.pool.get('account.journal')
121 if context.has_key('journal_id') and context['journal_id']:
122 journal = journal_obj.browse(cr, uid, context['journal_id'], context=context)
123 if journal.analytic_journal_id:
124 return journal.analytic_journal_id.id
129 'journal_id': _default_journal,
131 def name_get(self, cr, uid, ids, context=None):
133 for inst in self.browse(cr, uid, ids, context=context):
134 name = inst.name or '/'
135 if name and inst.code:
136 name=name+' ('+inst.code+')'
137 res.append((inst.id, name))
140 def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
143 ids = self.search(cr, uid, [('code', '=', name)] + args, limit=limit, context=context or {})
145 ids = self.search(cr, uid, [('name', operator, name)] + args, limit=limit, context=context or {})
147 ids = self.search(cr, uid, args, limit=limit, context=context or {})
148 return self.name_get(cr, uid, ids, context or {})
150 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
153 wiz_id = self.pool.get('ir.actions.act_window').search(cr, uid, [("name","=","analytic.plan.create.model.action")], context=context)
154 res = super(account_analytic_plan_instance,self).fields_view_get(cr, uid, view_id, view_type, context, toolbar=toolbar, submenu=submenu)
155 journal_obj = self.pool.get('account.journal')
156 analytic_plan_obj = self.pool.get('account.analytic.plan')
157 if (res['type']=='form'):
159 if context.get('journal_id', False):
160 plan_id = journal_obj.browse(cr, uid, int(context['journal_id']), context=context).plan_id
161 elif context.get('plan_id', False):
162 plan_id = analytic_plan_obj.browse(cr, uid, int(context['plan_id']), context=context)
166 res['arch'] = """<form string="%s">
169 <field name="journal_id"/>
170 <button name="%d" string="Save This Distribution as a Model" type="action" colspan="2"/>
171 """% (tools.to_xml(plan_id.name), wiz_id[0])
172 for line in plan_id.plan_ids:
174 <field name="account%d_ids" string="%s" nolabel="1" colspan="4">
175 <tree string="%s" editable="bottom">
177 <field name="analytic_account_id" domain="[('parent_id','child_of',[%d])]" groups="base.group_extended"/>
180 <newline/>"""%(i,tools.to_xml(line.name),tools.to_xml(line.name),line.root_analytic_id and line.root_analytic_id.id or 0)
182 res['arch'] += "</form>"
183 doc = etree.fromstring(res['arch'].encode('utf8'))
184 xarch, xfields = self._view_look_dom_arch(cr, uid, doc, view_id, context=context)
186 res['fields'] = xfields
191 def create(self, cr, uid, vals, context=None):
192 journal_obj = self.pool.get('account.journal')
193 ana_plan_instance_obj = self.pool.get('account.analytic.plan.instance')
194 acct_anal_acct = self.pool.get('account.analytic.account')
195 acct_anal_plan_line_obj = self.pool.get('account.analytic.plan.line')
196 if context and 'journal_id' in context:
197 journal = journal_obj.browse(cr, uid, context['journal_id'], context=context)
199 pids = ana_plan_instance_obj.search(cr, uid, [('name','=',vals['name']), ('code','=',vals['code']), ('plan_id','<>',False)], context=context)
201 raise osv.except_osv(_('Error'), _('A model having this name and code already exists !'))
203 res = acct_anal_plan_line_obj.search(cr, uid, [('plan_id','=',journal.plan_id.id)], context=context)
206 item = acct_anal_plan_line_obj.browse(cr, uid, i, context=context)
207 temp_list = ['account1_ids','account2_ids','account3_ids','account4_ids','account5_ids','account6_ids']
210 for tempo in vals[l]:
211 if acct_anal_acct.search(cr, uid, [('parent_id', 'child_of', [item.root_analytic_id.id]), ('id', '=', tempo[2]['analytic_account_id'])], context=context):
212 total_per_plan += tempo[2]['rate']
213 if total_per_plan < item.min_required or total_per_plan > item.max_required:
214 raise osv.except_osv(_('Value Error'),_('The Total Should be Between %s and %s') % (str(item.min_required), str(item.max_required)))
216 return super(account_analytic_plan_instance, self).create(cr, uid, vals, context=context)
218 def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
221 this = self.browse(cr, uid, ids[0], context=context)
222 invoice_line_obj = self.pool.get('account.invoice.line')
223 if this.plan_id and not vals.has_key('plan_id'):
224 #this instance is a model, so we have to create a new plan instance instead of modifying it
225 #copy the existing model
226 temp_id = self.copy(cr, uid, this.id, None, context=context)
227 #get the list of the invoice line that were linked to the model
228 lists = invoice_line_obj.search(cr, uid, [('analytics_id','=',this.id)], context=context)
229 #make them link to the copy
230 invoice_line_obj.write(cr, uid, lists, {'analytics_id':temp_id}, context=context)
232 #and finally modify the old model to be not a model anymore
233 vals['plan_id'] = False
234 if not vals.has_key('name'):
235 vals['name'] = this.name and (str(this.name)+'*') or "*"
236 if not vals.has_key('code'):
237 vals['code'] = this.code and (str(this.code)+'*') or "*"
238 return super(account_analytic_plan_instance, self).write(cr, uid, ids, vals, context=context)
240 account_analytic_plan_instance()
242 class account_analytic_plan_instance_line(osv.osv):
243 _name = "account.analytic.plan.instance.line"
244 _description = "Analytic Instance Line"
246 'plan_id': fields.many2one('account.analytic.plan.instance', 'Plan Id'),
247 'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', required=True, domain=[('type','<>','view')]),
248 'rate': fields.float('Rate (%)', required=True),
253 def name_get(self, cr, uid, ids, context=None):
256 reads = self.read(cr, uid, ids, ['analytic_account_id'], context=context)
259 res.append((record['id'], record['analytic_account_id']))
262 account_analytic_plan_instance_line()
264 class account_journal(osv.osv):
265 _inherit = "account.journal"
266 _name = "account.journal"
268 'plan_id': fields.many2one('account.analytic.plan', 'Analytic Plans'),
273 class account_invoice_line(osv.osv):
274 _inherit = "account.invoice.line"
275 _name = "account.invoice.line"
277 'analytics_id': fields.many2one('account.analytic.plan.instance', 'Analytic Distribution'),
280 def create(self, cr, uid, vals, context=None):
281 if 'analytics_id' in vals and isinstance(vals['analytics_id'], tuple):
282 vals['analytics_id'] = vals['analytics_id'][0]
283 return super(account_invoice_line, self).create(cr, uid, vals, context=context)
285 def move_line_get_item(self, cr, uid, line, context=None):
286 res = super(account_invoice_line, self).move_line_get_item(cr, uid, line, context=context)
287 res ['analytics_id'] = line.analytics_id and line.analytics_id.id or False
290 def product_id_change(self, cr, uid, ids, product, uom, qty=0, name='', type='out_invoice', partner_id=False, fposition_id=False, price_unit=False, address_invoice_id=False, currency_id=False, context=None):
291 res_prod = super(account_invoice_line, self).product_id_change(cr, uid, ids, product, uom, qty, name, type, partner_id, fposition_id, price_unit, address_invoice_id, currency_id, context=context)
292 rec = self.pool.get('account.analytic.default').account_get(cr, uid, product, partner_id, uid, time.strftime('%Y-%m-%d'), context=context)
293 if rec and rec.analytics_id:
294 res_prod['value'].update({'analytics_id': rec.analytics_id.id})
297 account_invoice_line()
299 class account_move_line(osv.osv):
301 _inherit = "account.move.line"
302 _name = "account.move.line"
304 'analytics_id':fields.many2one('account.analytic.plan.instance', 'Analytic Distribution'),
307 def _default_get_move_form_hook(self, cursor, user, data):
308 data = super(account_move_line, self)._default_get_move_form_hook(cursor, user, data)
309 if data.has_key('analytics_id'):
310 del(data['analytics_id'])
313 def create_analytic_lines(self, cr, uid, ids, context=None):
316 super(account_move_line, self).create_analytic_lines(cr, uid, ids, context=context)
317 analytic_line_obj = self.pool.get('account.analytic.line')
318 for line in self.browse(cr, uid, ids, context=context):
319 if line.analytics_id:
320 if not line.journal_id.analytic_journal_id:
321 raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal on the '%s' journal!") % (line.journal_id.name,))
323 toremove = analytic_line_obj.search(cr, uid, [('move_id','=',line.id)], context=context)
325 analytic_line_obj.unlink(cr, uid, toremove, context=context)
326 for line2 in line.analytics_id.account_ids:
327 val = (line.credit or 0.0) - (line.debit or 0.0)
328 amt=val * (line2.rate/100)
332 'account_id': line2.analytic_account_id.id,
333 'unit_amount': line.quantity,
334 'product_id': line.product_id and line.product_id.id or False,
335 'product_uom_id': line.product_uom_id and line.product_uom_id.id or False,
337 'general_account_id': line.account_id.id,
339 'journal_id': line.journal_id.analytic_journal_id.id,
342 analytic_line_obj.create(cr, uid, al_vals, context=context)
345 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
348 result = super(osv.osv, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar=toolbar, submenu=submenu)
353 class account_invoice(osv.osv):
354 _name = "account.invoice"
355 _inherit = "account.invoice"
357 def line_get_convert(self, cr, uid, x, part, date, context=None):
358 res=super(account_invoice,self).line_get_convert(cr, uid, x, part, date, context=context)
359 res['analytics_id'] = x.get('analytics_id', False)
362 def _get_analytic_lines(self, cr, uid, id):
363 inv = self.browse(cr, uid, [id])[0]
364 cur_obj = self.pool.get('res.currency')
365 invoice_line_obj = self.pool.get('account.invoice.line')
366 acct_ins_obj = self.pool.get('account.analytic.plan.instance')
367 company_currency = inv.company_id.currency_id.id
368 if inv.type in ('out_invoice', 'in_refund'):
373 iml = invoice_line_obj.move_line_get(cr, uid, inv.id)
376 if il.get('analytics_id', False):
378 if inv.type in ('in_invoice', 'in_refund'):
381 ref = self._convert_ref(cr, uid, inv.number)
382 obj_move_line = acct_ins_obj.browse(cr, uid, il['analytics_id'])
383 amount_calc = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign
385 il['analytic_lines'] = []
386 for line2 in obj_move_line.account_ids:
387 amt = amount_calc * (line2.rate/100)
388 qtty = qty* (line2.rate/100)
391 'date': inv['date_invoice'],
393 'product_id': il['product_id'],
394 'account_id': line2.analytic_account_id.id,
396 'product_uom_id': il['uos_id'],
397 'general_account_id': il['account_id'],
398 'journal_id': self._get_journal_analytic(cr, uid, inv.type),
401 il['analytic_lines'].append((0, 0, al_vals))
406 class account_analytic_plan(osv.osv):
407 _inherit = "account.analytic.plan"
409 'default_instance_id': fields.many2one('account.analytic.plan.instance', 'Default Entries'),
411 account_analytic_plan()
413 class analytic_default(osv.osv):
414 _inherit = "account.analytic.default"
416 'analytics_id': fields.many2one('account.analytic.plan.instance', 'Analytic Distribution'),
421 class sale_order_line(osv.osv):
422 _inherit = "sale.order.line"
424 # Method overridden to set the analytic account by default on criterion match
425 def invoice_line_create(self, cr, uid, ids, context=None):
426 create_ids = super(sale_order_line,self).invoice_line_create(cr, uid, ids, context=context)
427 inv_line_obj = self.pool.get('account.invoice.line')
428 acct_anal_def_obj = self.pool.get('account.analytic.default')
430 sale_line = self.browse(cr, uid, ids[0], context=context)
431 for line in inv_line_obj.browse(cr, uid, create_ids, context=context):
432 rec = acct_anal_def_obj.account_get(cr, uid, line.product_id.id, sale_line.order_id.partner_id.id, uid, time.strftime('%Y-%m-%d'), context)
435 inv_line_obj.write(cr, uid, [line.id], {'analytics_id': rec.analytics_id.id}, context=context)
441 class account_bank_statement(osv.osv):
442 _inherit = "account.bank.statement"
443 _name = "account.bank.statement"
445 def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, st_line_number, context=None):
446 account_move_line_pool = self.pool.get('account.move.line')
447 account_bank_statement_line_pool = self.pool.get('account.bank.statement.line')
448 st_line = account_bank_statement_line_pool.browse(cr, uid, st_line_id, context=context)
449 result = super(account_bank_statement,self).create_move_from_st_line(cr, uid, st_line_id, company_currency_id, st_line_number, context=context)
450 move = st_line.move_ids and st_line.move_ids[0] or False
452 for line in move.line_id:
453 account_move_line_pool.write(cr, uid, [line.id], {'analytics_id':st_line.analytics_id.id}, context=context)
456 def button_confirm_bank(self, cr, uid, ids, context=None):
457 super(account_bank_statement,self).button_confirm_bank(cr, uid, ids, context=context)
458 for st in self.browse(cr, uid, ids, context=context):
459 for st_line in st.line_ids:
460 if st_line.analytics_id:
461 if not st.journal_id.analytic_journal_id:
462 raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal on the '%s' journal!") % (st.journal_id.name,))
463 if not st_line.amount:
467 account_bank_statement()
470 class account_bank_statement_line(osv.osv):
471 _inherit = "account.bank.statement.line"
472 _name = "account.bank.statement.line"
474 'analytics_id': fields.many2one('account.analytic.plan.instance', 'Analytic Distribution'),
476 account_bank_statement_line()
478 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: