Improved Security
[odoo/odoo.git] / addons / account_analytic_plans / account_analytic_plans.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 # Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
5 #
6 # $Id: account.py 1005 2005-07-25 08:41:42Z nicoe $
7 #
8 # WARNING: This program as such is intended to be used by professional
9 # programmers who take the whole responsability of assessing all potential
10 # consequences resulting from its eventual inadequacies and bugs
11 # End users who are looking for a ready-to-use solution with commercial
12 # garantees and support are strongly adviced to contract a Free Software
13 # Service Company
14 #
15 # This program is Free Software; you can redistribute it and/or
16 # modify it under the terms of the GNU General Public License
17 # as published by the Free Software Foundation; either version 2
18 # of the License, or (at your option) any later version.
19 #
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23 # GNU General Public License for more details.
24 #
25 # You should have received a copy of the GNU General Public License
26 # along with this program; if not, write to the Free Software
27 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
28 #
29 ##############################################################################
30 from xml import dom
31
32 from mx import DateTime
33 from mx.DateTime import now
34 import time
35
36 import netsvc
37 from osv import fields, osv,orm
38 import ir
39
40 import tools
41
42 class one2many_mod2(fields.one2many):
43     def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
44         if not context:
45             context = {}
46         res = {}
47         for id in ids:
48             res[id] = []
49         ids2 = None
50         if 'journal_id' in context:
51             journal = obj.pool.get('account.journal').browse(cr, user, context['journal_id'], context)
52             pnum = int(name[7]) -1
53             plan = journal.plan_id
54             if plan and len(plan.plan_ids)>pnum:
55                 acc_id = plan.plan_ids[pnum].root_analytic_id.id
56                 ids2 = obj.pool.get(self._obj).search(cr, user, [(self._fields_id,'in',ids),('analytic_account_id','child_of',[acc_id])], limit=self._limit)
57         if ids2 is None:
58             ids2 = obj.pool.get(self._obj).search(cr, user, [(self._fields_id,'in',ids)], limit=self._limit)
59         for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
60             res[r[self._fields_id]].append( r['id'] )
61         return res
62
63 class account_analytic_plan(osv.osv):
64     _name = "account.analytic.plan"
65     _description = "Analytic Plans"
66     _columns = {
67         'name': fields.char('Analytic Plan', size=64, required=True, select=True,),
68         'plan_ids': fields.one2many('account.analytic.plan.line','plan_id','Analytic Plans'),
69     }
70 account_analytic_plan()
71
72 class account_analytic_plan_line(osv.osv):
73     _name = "account.analytic.plan.line"
74     _description = "Analytic Plan Lines"
75     _columns = {
76         'plan_id':fields.many2one('account.analytic.plan','Analytic Plan'),
77         'name': fields.char('Plan Name', size=64, required=True, select=True),
78         'sequence':fields.integer('Sequence'),
79         'root_analytic_id': fields.many2one('account.analytic.account','Root Account',help="Root account of this plan.",required=True),
80         'min_required': fields.float('Minimum Allowed (%)'),
81         'max_required': fields.float('Maximum Allowed (%)'),
82     }
83     _defaults = {
84         'min_required': lambda *args: 100.0,
85         'max_required': lambda *args: 100.0,
86     }
87     _order = "sequence,id"
88 account_analytic_plan_line()
89
90 class account_analytic_plan_instance(osv.osv):
91     _name='account.analytic.plan.instance'
92     _description = 'Analytic Plan Instance'
93     _columns={
94         'name':fields.char('Analytic Distribution',size=64),
95         'code':fields.char('Distribution Code',size=16),
96         'journal_id': fields.many2one('account.analytic.journal', 'Analytic Journal', required=True),
97         'account_ids':fields.one2many('account.analytic.plan.instance.line','plan_id','Account Id'),
98         'account1_ids':one2many_mod2('account.analytic.plan.instance.line','plan_id','Account1 Id'),
99         'account2_ids':one2many_mod2('account.analytic.plan.instance.line','plan_id','Account2 Id'),
100         'account3_ids':one2many_mod2('account.analytic.plan.instance.line','plan_id','Account3 Id'),
101         'account4_ids':one2many_mod2('account.analytic.plan.instance.line','plan_id','Account4 Id'),
102         'account5_ids':one2many_mod2('account.analytic.plan.instance.line','plan_id','Account5 Id'),
103         'account6_ids':one2many_mod2('account.analytic.plan.instance.line','plan_id','Account6 Id'),
104         'plan_id':fields.many2one('account.analytic.plan', "Model's Plan"),
105     }
106     def copy(self, cr, uid, id, default=None, context=None):
107         if not default:
108             default = {}
109             default.update({'account1_ids':False, 'account2_ids':False, 'account3_ids':False,
110                 'account4_ids':False, 'account5_ids':False, 'account6_ids':False})
111         return super(account_analytic_plan_instance, self).copy(cr, uid, id, default, context)
112
113     _defaults = {
114         'plan_id': lambda *args: False,
115     }
116     def name_get(self, cr, uid, ids, context={}):
117         res = []
118         for inst in self.browse(cr, uid, ids, context):
119             name = inst.name or '/'
120             if name and inst.code:
121                 name=name+' ('+inst.code+')'
122             res.append((inst.id, name))
123         return res
124
125     def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=80):
126         args= args or []
127         if name:
128             ids = self.search(cr, uid, [('code', '=', name)] + args, limit=limit, context=context or {})
129             if not ids:
130                 ids = self.search(cr, uid, [('name', operator, name)] + args, limit=limit, context=context or {})
131         else:
132             ids = self.search(cr, uid, args, limit=limit, context=context or {})
133         return self.name_get(cr, uid, ids, context or {})
134
135     def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False):
136         wiz_id = self.pool.get('ir.actions.wizard').search(cr, uid, [("wiz_name","=","create.model")])
137         res = super(account_analytic_plan_instance,self).fields_view_get(cr, uid, view_id, view_type, context, toolbar)
138         if (res['type']=='form'):
139             plan_id = False
140             if context.get('journal_id',False):
141                 plan_id = self.pool.get('account.journal').browse(cr, uid, int(context['journal_id']), context).plan_id
142             elif context.get('plan_id',False):
143                 plan_id = self.pool.get('account.analytic.plan').browse(cr, uid, int(context['plan_id']), context).plan_id
144             if plan_id:
145                 i=1
146                 res['arch'] = """<form string="%s">
147     <field name="name"/>
148     <field name="code"/>
149     <field name="journal_id"/>
150     <button name="%d" string="Save This Distribution as a Model" type="action" colspan="2"/>
151     """% (tools.to_xml(plan_id.name), wiz_id[0])
152                 for line in plan_id.plan_ids:
153                     res['arch']+="""
154                     <field name="account%d_ids" string="%s" colspan="4">
155                     <tree string="%s" editable="bottom">
156                         <field name="rate"/>
157                         <field name="analytic_account_id" domain="[('parent_id','child_of',[%d])]"/>
158                     </tree>
159                 </field>
160                 <newline/>"""%(i,tools.to_xml(line.name),tools.to_xml(line.name),line.root_analytic_id and line.root_analytic_id.id or 0)
161                     i+=1
162                 res['arch'] += "</form>"
163                 doc = dom.minidom.parseString(res['arch'])
164                 xarch, xfields = self._view_look_dom_arch(cr, uid, doc, context=context)
165                 res['arch'] = xarch
166                 res['fields'] = xfields
167             return res
168         else:
169             return res
170
171     def create(self, cr, uid, vals, context=None):
172         if context and 'journal_id' in context:
173             journal= self.pool.get('account.journal').browse(cr,uid,context['journal_id'])
174
175             pids = self.pool.get('account.analytic.plan.instance').search(cr, uid, [('name','=',vals['name']),('code','=',vals['code']),('plan_id','<>',False)])
176             if pids:
177                 raise osv.except_osv('Error', 'A model having this name and code already exists !')
178
179             res = self.pool.get('account.analytic.plan.line').search(cr,uid,[('plan_id','=',journal.plan_id.id)])
180             for i in res:
181                 total_per_plan = 0
182                 item = self.pool.get('account.analytic.plan.line').browse(cr,uid,i)
183                 temp_list=['account1_ids','account2_ids','account3_ids','account4_ids','account5_ids','account6_ids']
184                 for l in temp_list:
185                     if vals.has_key(l):
186                         for tempo in vals[l]:
187                             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'])]):
188                                 total_per_plan += tempo[2]['rate']
189                 if total_per_plan < item.min_required or total_per_plan > item.max_required:
190                     raise osv.except_osv("Value Error" ,"The Total Should be Between " + str(item.min_required) + " and " + str(item.max_required))
191
192         return super(account_analytic_plan_instance, self).create(cr, uid, vals, context)
193
194     def write(self, cr, uid, ids, vals, context={}, check=True, update_check=True):
195         this = self.browse(cr,uid,ids[0])
196         if this.plan_id and not vals.has_key('plan_id'):
197             #this instance is a model, so we have to create a new plan instance instead of modifying it
198             #copy the existing model
199             temp_id = self.copy(cr, uid, this.id, None, context)
200             #get the list of the invoice line that were linked to the model
201             list = self.pool.get('account.invoice.line').search(cr,uid,[('analytics_id','=',this.id)])
202             #make them link to the copy
203             self.pool.get('account.invoice.line').write(cr, uid, list, {'analytics_id':temp_id}, context)
204
205             #and finally modify the old model to be not a model anymore
206             vals['plan_id'] = False 
207             if not vals.has_key['name']:
208                 vals['name'] = this.name+'*'
209             if not vals.has_key['code']:
210                 vals['code'] = this.code+'*'
211             return self.write(cr, uid, [this.id],vals, context)        
212         else:
213             #this plan instance isn't a model, so a simple write is fine
214             return super(account_analytic_plan_instance, self).write(cr, uid, ids, vals, context)
215
216 account_analytic_plan_instance()
217
218 class account_analytic_plan_instance_line(osv.osv):
219     _name='account.analytic.plan.instance.line'
220     _description = 'Analytic Instance Line'
221     _columns={
222         'plan_id':fields.many2one('account.analytic.plan.instance','Plan Id'),
223         'analytic_account_id':fields.many2one('account.analytic.account','Analytic Account', required=True),
224         'rate':fields.float('Rate (%)', required=True),
225     }
226     def name_get(self, cr, uid, ids, context={}):
227         if not len(ids):
228             return []
229         reads = self.read(cr, uid, ids, ['analytic_account_id'], context)
230         res = []
231         for record in reads:
232             res.append((record['id'], record['analytic_account_id']))
233         return res
234
235 account_analytic_plan_instance_line()
236
237 class account_journal(osv.osv):
238     _inherit='account.journal'
239     _name='account.journal'
240     _columns = {
241         'plan_id':fields.many2one('account.analytic.plan','Analytic Plans'),
242     }
243 account_journal()
244
245 class account_invoice_line(osv.osv):
246     _inherit='account.invoice.line'
247     _name='account.invoice.line'
248     _columns = {
249         'analytics_id':fields.many2one('account.analytic.plan.instance','Analytic Distribution'),
250     }
251
252     def create(self, cr, uid, vals, context=None):
253         if 'analytics_id' in vals and isinstance(vals['analytics_id'],tuple):
254             vals['analytics_id'] = vals['analytics_id'][0]
255         return super(account_invoice_line, self).create(cr, uid, vals, context)
256
257     def move_line_get_item(self, cr, uid, line, context={}):
258         res= super(account_invoice_line,self).move_line_get_item(cr, uid, line, context={})
259         res ['analytics_id']=line.analytics_id and line.analytics_id.id or False
260         return res
261
262     def product_id_change(self, cr, uid, ids, product, uom, qty=0, name='', type='out_invoice', partner_id=False, price_unit=False, address_invoice_id=False, context={}):
263         res_prod = super(account_invoice_line,self).product_id_change(cr, uid, ids, product, uom, qty, name, type, partner_id, price_unit, address_invoice_id, context)
264         rec = self.pool.get('account.analytic.default').account_get(cr, uid, product, partner_id, uid, time.strftime('%Y-%m-%d'), context)
265         if rec and rec.analytics_id:
266             res_prod['value'].update({'analytics_id':rec.analytics_id.id})
267         return res_prod
268 account_invoice_line()
269
270 class account_move_line(osv.osv):
271     _inherit='account.move.line'
272     _name='account.move.line'
273     _columns = {
274         'analytics_id':fields.many2one('account.analytic.plan.instance','Analytic Distribution'),
275     }
276 #   def _analytic_update(self, cr, uid, ids, context):
277 #       for line in self.browse(cr, uid, ids, context):
278 #           if line.analytics_id:
279 #               print "line.analytics_id",line,"now",line.analytics_id
280 #               toremove = self.pool.get('account.analytic.line').search(cr, uid, [('move_id','=',line.id)], context=context)
281 #               print "toremove",toremove
282 #               if toremove:
283 #                   obj_line=self.pool.get('account.analytic.line')
284 #                   self.pool.get('account.analytic.line').unlink(cr, uid, toremove, context=context)
285 #               for line2 in line.analytics_id.account_ids:
286 #                   val = (line.debit or 0.0) - (line.credit or  0.0)
287 #                   amt=val * (line2.rate/100)
288 #                   al_vals={
289 #                       'name': line.name,
290 #                       'date': line.date,
291 #                       'unit_amount':1,
292 #                       'product_id':12,
293 #                       'account_id': line2.analytic_account_id.id,
294 #                       'amount': amt,
295 #                       'general_account_id': line.account_id.id,
296 #                       'move_id': line.id,
297 #                       'journal_id': line.analytics_id.journal_id.id,
298 #                       'ref': line.ref,
299 #                   }
300 #                   ali_id=self.pool.get('account.analytic.line').create(cr,uid,al_vals)
301 #       return True
302 #
303 #   def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
304 #       result = super(account_move_line, self).write(cr, uid, ids, vals, context, check, update_check)
305 #       self._analytic_update(cr, uid, ids, context)
306 #       return result
307 #
308 #   def create(self, cr, uid, vals, context=None, check=True):
309 #       result = super(account_move_line, self).create(cr, uid, vals, context, check)
310 #       self._analytic_update(cr, uid, [result], context)
311 #       return result
312 account_move_line()
313
314 class account_invoice(osv.osv):
315     _name = "account.invoice"
316     _inherit="account.invoice"
317
318
319
320     def line_get_convert(self, cr, uid, x, part, date, context={}):
321         res=super(account_invoice,self).line_get_convert(cr, uid, x, part, date, context)
322         res['analytics_id']=x.get('analytics_id',False)
323         return res
324
325     def _get_analityc_lines(self, cr, uid, id):
326         inv = self.browse(cr, uid, [id])[0]
327         cur_obj = self.pool.get('res.currency')
328
329         company_currency = inv.company_id.currency_id.id
330         if inv.type in ('out_invoice', 'in_refund'):
331             sign = 1
332         else:
333             sign = -1
334
335         iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
336
337         for il in iml:
338             if il['analytics_id']:
339
340                 if inv.type in ('in_invoice', 'in_refund'):
341                     ref = inv.reference
342                 else:
343                     ref = self._convert_ref(cr, uid, inv.number)
344                 obj_move_line=self.pool.get('account.analytic.plan.instance').browse(cr,uid,il['analytics_id'])
345                 amount_calc=cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign
346                 qty=il['quantity']
347                 il['analytic_lines']=[]
348                 for line2 in obj_move_line.account_ids:
349                     amt=amount_calc * (line2.rate/100)
350                     qtty=qty* (line2.rate/100)
351                     al_vals={
352                         'name': il['name'],
353                         'date': inv['date_invoice'],
354                         'unit_amount':qtty,
355                         'product_id':il['product_id'],
356                         'account_id': line2.analytic_account_id.id,
357                         'amount': amt,
358                         'product_uom_id': il['uos_id'],
359                         'general_account_id': il['account_id'],
360                         'journal_id': self._get_journal_analytic(cr, uid, inv.type),
361                         'ref': ref,
362                     }
363                     il['analytic_lines'].append((0,0,al_vals))
364         return iml
365
366 account_invoice()
367
368 class account_analytic_plan(osv.osv):
369     _inherit = "account.analytic.plan"
370     _columns = {
371         'default_instance_id': fields.many2one('account.analytic.plan.instance', 'Default Entries'),
372     }
373 account_analytic_plan()
374
375 class analytic_default(osv.osv):
376     _inherit = 'account.analytic.default'
377     _columns = {
378         'analytics_id': fields.many2one('account.analytic.plan.instance', 'Analytic Distribution'),
379     }
380 analytic_default()
381