af197076c23bbb6001e995598f9906bd5e682d2c
[odoo/odoo.git] / addons / account_analytic_plans / account_analytic_plans.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
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.
11 #
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.
16 #
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/>.
19 #
20 ##############################################################################
21 from lxml import etree
22
23 from mx import DateTime
24 from mx.DateTime import now
25 import time
26
27 import netsvc
28 from osv import fields, osv,orm
29 import ir
30
31 import tools
32 from tools.translate import _
33
34 class one2many_mod2(fields.one2many):
35     def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
36         if not context:
37             context = {}
38         res = {}
39         for id in ids:
40             res[id] = []
41         ids2 = 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)
49         if ids2 is None:
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'] )
53         return res
54
55 class account_analytic_plan(osv.osv):
56     _name = "account.analytic.plan"
57     _description = "Analytic Plans"
58     _columns = {
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'),
61     }
62 account_analytic_plan()
63
64 class account_analytic_plan_line(osv.osv):
65     _name = "account.analytic.plan.line"
66     _description = "Analytic Plan Lines"
67     _columns = {
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 (%)'),
74     }
75     _defaults = {
76         'min_required': lambda *args: 100.0,
77         'max_required': lambda *args: 100.0,
78     }
79     _order = "sequence,id"
80 account_analytic_plan_line()
81
82 class account_analytic_plan_instance(osv.osv):
83     _name='account.analytic.plan.instance'
84     _description = 'Analytic Plan Instance'
85     _columns={
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"),
97     }
98
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
103             args.append('|')
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)
108         return res
109
110     def copy(self, cr, uid, id, default=None, context=None):
111         if not default:
112             default = {}
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)
116
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
122         return False
123
124     _defaults = {
125         'plan_id': lambda *args: False,
126         'journal_id': _default_journal,
127     }
128     def name_get(self, cr, uid, ids, context={}):
129         res = []
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))
135         return res
136
137     def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
138         args= args or []
139         if name:
140             ids = self.search(cr, uid, [('code', '=', name)] + args, limit=limit, context=context or {})
141             if not ids:
142                 ids = self.search(cr, uid, [('name', operator, name)] + args, limit=limit, context=context or {})
143         else:
144             ids = self.search(cr, uid, args, limit=limit, context=context or {})
145         return self.name_get(cr, uid, ids, context or {})
146
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)
150
151         if (res['type']=='form'):
152             plan_id = False
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)
157
158             if plan_id:
159                 i=1
160                 res['arch'] = """<form string="%s">
161     <field name="name"/>
162     <field name="code"/>
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:
167                     res['arch']+="""
168                     <field name="account%d_ids" string="%s" colspan="4">
169                     <tree string="%s" editable="bottom">
170                         <field name="rate"/>
171                         <field name="analytic_account_id" domain="[('parent_id','child_of',[%d])]"/>
172                     </tree>
173                 </field>
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)
175                     i+=1
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)
179                 res['arch'] = xarch
180                 res['fields'] = xfields
181             return res
182         else:
183             return res
184
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'])
188
189             pids = self.pool.get('account.analytic.plan.instance').search(cr, uid, [('name','=',vals['name']),('code','=',vals['code']),('plan_id','<>',False)])
190             if pids:
191                 raise osv.except_osv(_('Error'), _('A model having this name and code already exists !'))
192
193             res = self.pool.get('account.analytic.plan.line').search(cr,uid,[('plan_id','=',journal.plan_id.id)])
194             for i in res:
195                 total_per_plan = 0
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']
198                 for l in temp_list:
199                     if vals.has_key(l):
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)))
205
206         return super(account_analytic_plan_instance, self).create(cr, uid, vals, context)
207
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)
218
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)
226
227 account_analytic_plan_instance()
228
229 class account_analytic_plan_instance_line(osv.osv):
230     _name='account.analytic.plan.instance.line'
231     _description = 'Analytic Instance Line'
232     _columns={
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),
236     }
237     def name_get(self, cr, uid, ids, context={}):
238         if not len(ids):
239             return []
240         reads = self.read(cr, uid, ids, ['analytic_account_id'], context)
241         res = []
242         for record in reads:
243             res.append((record['id'], record['analytic_account_id']))
244         return res
245
246 account_analytic_plan_instance_line()
247
248 class account_journal(osv.osv):
249     _inherit='account.journal'
250     _name='account.journal'
251     _columns = {
252         'plan_id':fields.many2one('account.analytic.plan','Analytic Plans'),
253     }
254 account_journal()
255
256 class account_invoice_line(osv.osv):
257     _inherit='account.invoice.line'
258     _name='account.invoice.line'
259     _columns = {
260         'analytics_id':fields.many2one('account.analytic.plan.instance','Analytic Distribution'),
261     }
262
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)
267
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
271         return res
272
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})
278         return res_prod
279 account_invoice_line()
280
281 class account_move_line(osv.osv):
282
283     _inherit='account.move.line'
284     _name='account.move.line'
285     _columns = {
286         'analytics_id':fields.many2one('account.analytic.plan.instance','Analytic Distribution'),
287     }
288
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'])
293         return data
294
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,))
301
302                toremove = self.pool.get('account.analytic.line').search(cr, uid, [('move_id','=',line.id)], context=context)
303                if toremove:
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)
308                    al_vals={
309                        'name': line.name,
310                        'date': line.date,
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,
315                        'amount': amt,
316                        'general_account_id': line.account_id.id,
317                        'move_id': line.id,
318                        'journal_id': line.journal_id.analytic_journal_id.id,
319                        'ref': line.ref,
320                    }
321                    ali_id=self.pool.get('account.analytic.line').create(cr,uid,al_vals)
322         return True
323
324 account_move_line()
325
326 class account_invoice(osv.osv):
327     _name = "account.invoice"
328     _inherit="account.invoice"
329
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)
333         return res
334
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')
338
339         company_currency = inv.company_id.currency_id.id
340         if inv.type in ('out_invoice', 'in_refund'):
341             sign = 1
342         else:
343             sign = -1
344
345         iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
346
347         for il in iml:
348             if il['analytics_id']:
349
350                 if inv.type in ('in_invoice', 'in_refund'):
351                     ref = inv.reference
352                 else:
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
356                 qty=il['quantity']
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)
361                     al_vals={
362                         'name': il['name'],
363                         'date': inv['date_invoice'],
364                         'unit_amount':qtty,
365                         'product_id':il['product_id'],
366                         'account_id': line2.analytic_account_id.id,
367                         'amount': amt,
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),
371                         'ref': ref,
372                     }
373                     il['analytic_lines'].append((0,0,al_vals))
374         return iml
375
376 account_invoice()
377
378 class account_analytic_plan(osv.osv):
379     _inherit = "account.analytic.plan"
380     _columns = {
381         'default_instance_id': fields.many2one('account.analytic.plan.instance', 'Default Entries'),
382     }
383 account_analytic_plan()
384
385 class analytic_default(osv.osv):
386     _inherit = 'account.analytic.default'
387     _columns = {
388         'analytics_id': fields.many2one('account.analytic.plan.instance', 'Analytic Distribution'),
389     }
390 analytic_default()
391
392 class sale_order_line(osv.osv):
393     _inherit = 'sale.order.line'
394
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)
398         if ids:
399             sale_line_obj = self.browse(cr, uid, ids[0], context)
400             pool_inv_line = self.pool.get('account.invoice.line')
401
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)
404
405                 if rec:
406                     pool_inv_line.write(cr, uid, [line.id], {'analytics_id':rec.analytics_id.id}, context=context)
407                     cr.commit()
408         return create_ids
409
410 sale_order_line()
411
412
413 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: