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 ##############################################################################
21 from lxml import etree
23 from mx import DateTime
24 from mx.DateTime import now
28 from osv import fields, osv,orm
32 from tools.translate import _
34 class one2many_mod2(fields.one2many):
35 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
42 if 'journal_id' in context:
43 journal = obj.pool.get('account.journal').browse(cr, user, context['journal_id'], context)
44 pnum = int(name[7]) -1
45 plan = journal.plan_id
46 if plan and len(plan.plan_ids)>pnum:
47 acc_id = plan.plan_ids[pnum].root_analytic_id.id
48 ids2 = obj.pool.get(self._obj).search(cr, user, [(self._fields_id,'in',ids),('analytic_account_id','child_of',[acc_id])], limit=self._limit)
50 ids2 = obj.pool.get(self._obj).search(cr, user, [(self._fields_id,'in',ids)], limit=self._limit)
51 for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
52 res[r[self._fields_id]].append( r['id'] )
55 class account_analytic_plan(osv.osv):
56 _name = "account.analytic.plan"
57 _description = "Analytic Plans"
59 'name': fields.char('Analytic Plan', size=64, required=True, select=True,),
60 'plan_ids': fields.one2many('account.analytic.plan.line','plan_id','Analytic Plans'),
62 account_analytic_plan()
64 class account_analytic_plan_line(osv.osv):
65 _name = "account.analytic.plan.line"
66 _description = "Analytic Plan Lines"
68 'plan_id':fields.many2one('account.analytic.plan','Analytic Plan'),
69 'name': fields.char('Plan Name', size=64, required=True, select=True),
70 'sequence':fields.integer('Sequence'),
71 'root_analytic_id': fields.many2one('account.analytic.account','Root Account',help="Root account of this plan.",required=True),
72 'min_required': fields.float('Minimum Allowed (%)'),
73 'max_required': fields.float('Maximum Allowed (%)'),
76 'min_required': lambda *args: 100.0,
77 'max_required': lambda *args: 100.0,
79 _order = "sequence,id"
80 account_analytic_plan_line()
82 class account_analytic_plan_instance(osv.osv):
83 _name='account.analytic.plan.instance'
84 _description = 'Analytic Plan Instance'
86 'name':fields.char('Analytic Distribution',size=64),
87 'code':fields.char('Distribution Code',size=16),
88 'journal_id': fields.many2one('account.analytic.journal', 'Analytic Journal' ),
89 'account_ids':fields.one2many('account.analytic.plan.instance.line','plan_id','Account Id'),
90 'account1_ids':one2many_mod2('account.analytic.plan.instance.line','plan_id','Account1 Id'),
91 'account2_ids':one2many_mod2('account.analytic.plan.instance.line','plan_id','Account2 Id'),
92 'account3_ids':one2many_mod2('account.analytic.plan.instance.line','plan_id','Account3 Id'),
93 'account4_ids':one2many_mod2('account.analytic.plan.instance.line','plan_id','Account4 Id'),
94 'account5_ids':one2many_mod2('account.analytic.plan.instance.line','plan_id','Account5 Id'),
95 'account6_ids':one2many_mod2('account.analytic.plan.instance.line','plan_id','Account6 Id'),
96 'plan_id':fields.many2one('account.analytic.plan', "Model's Plan"),
99 def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
100 if context.get('journal_id', False):
101 journal = self.pool.get('account.journal').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)
117 def _default_journal(self, cr, uid, context={}):
118 if context.has_key('journal_id') and context['journal_id']:
119 journal = self.pool.get('account.journal').browse(cr, uid, context['journal_id'])
120 if journal.analytic_journal_id:
121 return journal.analytic_journal_id.id
125 'plan_id': lambda *args: False,
126 'journal_id': _default_journal,
128 def name_get(self, cr, uid, ids, context={}):
130 for inst in self.browse(cr, uid, ids, context):
131 name = inst.name or '/'
132 if name and inst.code:
133 name=name+' ('+inst.code+')'
134 res.append((inst.id, name))
137 def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
140 ids = self.search(cr, uid, [('code', '=', name)] + args, limit=limit, context=context or {})
142 ids = self.search(cr, uid, [('name', operator, name)] + args, limit=limit, context=context or {})
144 ids = self.search(cr, uid, args, limit=limit, context=context or {})
145 return self.name_get(cr, uid, ids, context or {})
147 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
148 wiz_id = self.pool.get('ir.actions.wizard').search(cr, uid, [("wiz_name","=","create.model")])
149 res = super(account_analytic_plan_instance,self).fields_view_get(cr, uid, view_id, view_type, context, toolbar=toolbar, submenu=submenu)
151 if (res['type']=='form'):
153 if context.get('journal_id',False):
154 plan_id = self.pool.get('account.journal').browse(cr, uid, int(context['journal_id']), context).plan_id
155 elif context.get('plan_id',False):
156 plan_id = self.pool.get('account.analytic.plan').browse(cr, uid, int(context['plan_id']), context)
160 res['arch'] = """<form string="%s">
163 <field name="journal_id"/>
164 <button name="%d" string="Save This Distribution as a Model" type="action" colspan="2"/>
165 """% (tools.to_xml(plan_id.name), wiz_id[0])
166 for line in plan_id.plan_ids:
168 <field name="account%d_ids" string="%s" colspan="4">
169 <tree string="%s" editable="bottom">
171 <field name="analytic_account_id" domain="[('parent_id','child_of',[%d])]"/>
174 <newline/>"""%(i,tools.to_xml(line.name),tools.to_xml(line.name),line.root_analytic_id and line.root_analytic_id.id or 0)
176 res['arch'] += "</form>"
177 doc = etree.fromstring(res['arch'].encode('utf8'))
178 xarch, xfields = self._view_look_dom_arch(cr, uid, doc, view_id, context=context)
180 res['fields'] = xfields
185 def create(self, cr, uid, vals, context=None):
186 if context and 'journal_id' in context:
187 journal= self.pool.get('account.journal').browse(cr,uid,context['journal_id'])
189 pids = self.pool.get('account.analytic.plan.instance').search(cr, uid, [('name','=',vals['name']),('code','=',vals['code']),('plan_id','<>',False)])
191 raise osv.except_osv(_('Error'), _('A model having this name and code already exists !'))
193 res = self.pool.get('account.analytic.plan.line').search(cr,uid,[('plan_id','=',journal.plan_id.id)])
196 item = self.pool.get('account.analytic.plan.line').browse(cr,uid,i)
197 temp_list=['account1_ids','account2_ids','account3_ids','account4_ids','account5_ids','account6_ids']
200 for tempo in vals[l]:
201 if self.pool.get('account.analytic.account').search(cr,uid,[('parent_id','child_of',[item.root_analytic_id.id]),('id','=',tempo[2]['analytic_account_id'])]):
202 total_per_plan += tempo[2]['rate']
203 if total_per_plan < item.min_required or total_per_plan > item.max_required:
204 raise osv.except_osv(_('Value Error') ,_('The Total Should be Between %s and %s') % (str(item.min_required), str(item.max_required)))
206 return super(account_analytic_plan_instance, self).create(cr, uid, vals, context)
208 def write(self, cr, uid, ids, vals, context={}, check=True, update_check=True):
209 this = self.browse(cr,uid,ids[0])
210 if this.plan_id and not vals.has_key('plan_id'):
211 #this instance is a model, so we have to create a new plan instance instead of modifying it
212 #copy the existing model
213 temp_id = self.copy(cr, uid, this.id, None, context)
214 #get the list of the invoice line that were linked to the model
215 list = self.pool.get('account.invoice.line').search(cr,uid,[('analytics_id','=',this.id)])
216 #make them link to the copy
217 self.pool.get('account.invoice.line').write(cr, uid, list, {'analytics_id':temp_id}, context)
219 #and finally modify the old model to be not a model anymore
220 vals['plan_id'] = False
221 if not vals.has_key('name'):
222 vals['name'] = this.name and (str(this.name)+'*') or "*"
223 if not vals.has_key('code'):
224 vals['code'] = this.code and (str(this.code)+'*') or "*"
225 return super(account_analytic_plan_instance, self).write(cr, uid, ids, vals, context)
227 account_analytic_plan_instance()
229 class account_analytic_plan_instance_line(osv.osv):
230 _name='account.analytic.plan.instance.line'
231 _description = 'Analytic Instance Line'
233 'plan_id':fields.many2one('account.analytic.plan.instance','Plan Id'),
234 'analytic_account_id':fields.many2one('account.analytic.account','Analytic Account', required=True),
235 'rate':fields.float('Rate (%)', required=True),
237 def name_get(self, cr, uid, ids, context={}):
240 reads = self.read(cr, uid, ids, ['analytic_account_id'], context)
243 res.append((record['id'], record['analytic_account_id']))
246 account_analytic_plan_instance_line()
248 class account_journal(osv.osv):
249 _inherit='account.journal'
250 _name='account.journal'
252 'plan_id':fields.many2one('account.analytic.plan','Analytic Plans'),
256 class account_invoice_line(osv.osv):
257 _inherit='account.invoice.line'
258 _name='account.invoice.line'
260 'analytics_id':fields.many2one('account.analytic.plan.instance','Analytic Distribution'),
263 def create(self, cr, uid, vals, context=None):
264 if 'analytics_id' in vals and isinstance(vals['analytics_id'],tuple):
265 vals['analytics_id'] = vals['analytics_id'][0]
266 return super(account_invoice_line, self).create(cr, uid, vals, context)
268 def move_line_get_item(self, cr, uid, line, context=None):
269 res= super(account_invoice_line,self).move_line_get_item(cr, uid, line, context={})
270 res ['analytics_id']=line.analytics_id and line.analytics_id.id or False
273 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):
274 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)
275 rec = self.pool.get('account.analytic.default').account_get(cr, uid, product, partner_id, uid, time.strftime('%Y-%m-%d'), context)
276 if rec and rec.analytics_id:
277 res_prod['value'].update({'analytics_id':rec.analytics_id.id})
279 account_invoice_line()
281 class account_move_line(osv.osv):
283 _inherit='account.move.line'
284 _name='account.move.line'
286 'analytics_id':fields.many2one('account.analytic.plan.instance','Analytic Distribution'),
289 def _default_get_move_form_hook(self, cursor, user, data):
290 data = super(account_move_line, self)._default_get_move_form_hook(cursor, user, data)
291 if data.has_key('analytics_id'):
292 del(data['analytics_id'])
295 def create_analytic_lines(self, cr, uid, ids, context={}):
296 super(account_move_line, self).create_analytic_lines(cr, uid, ids, context)
297 for line in self.browse(cr, uid, ids, context):
298 if line.analytics_id:
299 if not line.journal_id.analytic_journal_id:
300 raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal on the '%s' journal!") % (line.journal_id.name,))
302 toremove = self.pool.get('account.analytic.line').search(cr, uid, [('move_id','=',line.id)], context=context)
304 line.unlink(cr, uid, toremove, context=context)
305 for line2 in line.analytics_id.account_ids:
306 val = (line.credit or 0.0) - (line.debit or 0.0)
307 amt=val * (line2.rate/100)
311 'account_id': line2.analytic_account_id.id,
312 'unit_amount': line.quantity,
313 'product_id': line.product_id and line.product_id.id or False,
314 'product_uom_id': line.product_uom_id and line.product_uom_id.id or False,
316 'general_account_id': line.account_id.id,
318 'journal_id': line.journal_id.analytic_journal_id.id,
321 ali_id=self.pool.get('account.analytic.line').create(cr,uid,al_vals)
326 class account_invoice(osv.osv):
327 _name = "account.invoice"
328 _inherit="account.invoice"
330 def line_get_convert(self, cr, uid, x, part, date, context={}):
331 res=super(account_invoice,self).line_get_convert(cr, uid, x, part, date, context)
332 res['analytics_id']=x.get('analytics_id',False)
335 def _get_analytic_lines(self, cr, uid, id):
336 inv = self.browse(cr, uid, [id])[0]
337 cur_obj = self.pool.get('res.currency')
339 company_currency = inv.company_id.currency_id.id
340 if inv.type in ('out_invoice', 'in_refund'):
345 iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
348 if il['analytics_id']:
350 if inv.type in ('in_invoice', 'in_refund'):
353 ref = self._convert_ref(cr, uid, inv.number)
354 obj_move_line=self.pool.get('account.analytic.plan.instance').browse(cr,uid,il['analytics_id'])
355 amount_calc=cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign
357 il['analytic_lines']=[]
358 for line2 in obj_move_line.account_ids:
359 amt=amount_calc * (line2.rate/100)
360 qtty=qty* (line2.rate/100)
363 'date': inv['date_invoice'],
365 'product_id':il['product_id'],
366 'account_id': line2.analytic_account_id.id,
368 'product_uom_id': il['uos_id'],
369 'general_account_id': il['account_id'],
370 'journal_id': self._get_journal_analytic(cr, uid, inv.type),
373 il['analytic_lines'].append((0,0,al_vals))
378 class account_analytic_plan(osv.osv):
379 _inherit = "account.analytic.plan"
381 'default_instance_id': fields.many2one('account.analytic.plan.instance', 'Default Entries'),
383 account_analytic_plan()
385 class analytic_default(osv.osv):
386 _inherit = 'account.analytic.default'
388 'analytics_id': fields.many2one('account.analytic.plan.instance', 'Analytic Distribution'),
392 class sale_order_line(osv.osv):
393 _inherit = 'sale.order.line'
395 # Method overridden to set the analytic account by default on criterion match
396 def invoice_line_create(self, cr, uid, ids, context={}):
397 create_ids = super(sale_order_line,self).invoice_line_create(cr, uid, ids, context)
399 sale_line_obj = self.browse(cr, uid, ids[0], context)
400 pool_inv_line = self.pool.get('account.invoice.line')
402 for line in pool_inv_line.browse(cr, uid, create_ids, context):
403 rec = self.pool.get('account.analytic.default').account_get(cr, uid, line.product_id.id, sale_line_obj.order_id.partner_id.id, uid, time.strftime('%Y-%m-%d'), context)
406 pool_inv_line.write(cr, uid, [line.id], {'analytics_id':rec.analytics_id.id}, context=context)
412 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: