[fix] account_payment: potential sql injection vectors in account_move_line
[odoo/odoo.git] / addons / account_payment / payment.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
6 #    $Id$
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU General Public License as published by
10 #    the Free Software Foundation, either version 3 of the License, or
11 #    (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU General Public License for more details.
17 #
18 #    You should have received a copy of the GNU General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22 from osv import fields
23 from osv import osv
24 import time
25 import netsvc
26 import pooler
27
28
29 class payment_type(osv.osv):
30     _name= 'payment.type'
31     _description= 'Payment type'
32     _columns= {
33         'name': fields.char('Name', size=64, required=True,help='Payment Type'),
34         'code': fields.char('Code', size=64, required=True,help='Specify the Code for Payment Type'),
35         'suitable_bank_types': fields.many2many('res.partner.bank.type',
36             'bank_type_payment_type_rel',
37             'pay_type_id','bank_type_id',
38             'Suitable bank types')
39     }
40
41 payment_type()
42
43
44 class payment_mode(osv.osv):
45     _name= 'payment.mode'
46     _description= 'Payment mode'
47     _columns= {
48         'name': fields.char('Name', size=64, required=True,help='Mode of Payment'),
49         'bank_id': fields.many2one('res.partner.bank', "Bank account",
50             required=True,help='Bank Account for the Payment Mode'),
51         'journal': fields.many2one('account.journal', 'Journal', required=True,
52             domain=[('type', '=', 'cash')],help='Cash Journal for the Payment Mode'),
53         'type': fields.many2one('payment.type','Payment type',required=True,help='Select the Payment Type for the Payment Mode.'),
54     }
55
56     def suitable_bank_types(self,cr,uid,payment_code=None,context={}):
57         """Return the codes of the bank type that are suitable
58         for the given payment type code"""
59         if not payment_code:
60             return []
61         cr.execute(""" select t.code
62             from res_partner_bank_type t
63             join bank_type_payment_type_rel r on (r.bank_type_id = t.id)
64             join payment_type pt on (r.pay_type_id = pt.id)
65             join payment_mode pm on (pm.type = pt.id)
66             where pm.id = %s """, [payment_code])
67         return [x[0] for x in cr.fetchall()]
68
69
70 payment_mode()
71
72
73 class payment_order(osv.osv):
74     _name = 'payment.order'
75     _description = 'Payment Order'
76     _rec_name = 'reference'
77
78     def get_wizard(self,type):
79         logger = netsvc.Logger()
80         logger.notifyChannel("warning", netsvc.LOG_WARNING,
81                 "No wizard found for the payment type '%s'." % type)
82         return None
83
84     def _total(self, cursor, user, ids, name, args, context=None):
85         if not ids:
86             return {}
87         res = {}
88         for order in self.browse(cursor, user, ids, context=context):
89             if order.line_ids:
90                 res[order.id] = reduce(lambda x, y: x + y.amount, order.line_ids, 0.0)
91             else:
92                 res[order.id] = 0.0
93         return res
94
95     _columns = {
96         'date_planned': fields.date('Scheduled date if fixed', states={'done':[('readonly',True)]}, help='Select a date if you have chosen Preferred Date to be fixed.'),
97         'reference': fields.char('Reference', size=128, required=1, states={'done':[('readonly',True)]}),
98         'mode': fields.many2one('payment.mode','Payment mode', select=True, required=1, states={'done':[('readonly',True)]}, help='Select the Payment Mode to be applied.'),
99         'state': fields.selection([
100             ('draft', 'Draft'),
101             ('open','Confirmed'),
102             ('cancel','Cancelled'),
103             ('done','Done')], 'State', select=True),
104         'line_ids': fields.one2many('payment.line','order_id','Payment lines',states={'done':[('readonly',True)]}),
105         'total': fields.function(_total, string="Total", method=True,
106             type='float'),
107         'user_id': fields.many2one('res.users','User', required=True, states={'done':[('readonly',True)]}),
108         'date_prefered': fields.selection([
109             ('now', 'Directly'),
110             ('due', 'Due date'),
111             ('fixed', 'Fixed date')
112             ], "Preferred date", change_default=True, required=True, states={'done':[('readonly',True)]}, help="Choose an option for the Payment Order:'Fixed' stands for a date specified by you.'Directly' stands for the direct execution.'Due date' stands for the scheduled date of execution."),
113         'date_created': fields.date('Creation date', readonly=True),
114         'date_done': fields.date('Execution date', readonly=True),
115     }
116
117     _defaults = {
118         'user_id': lambda self,cr,uid,context: uid,
119         'state': lambda *a: 'draft',
120         'date_prefered': lambda *a: 'due',
121         'date_created': lambda *a: time.strftime('%Y-%m-%d'),
122         'reference': lambda self,cr,uid,context: self.pool.get('ir.sequence').get(cr, uid, 'payment.order'),
123     }
124
125     def set_to_draft(self, cr, uid, ids, *args):
126         self.write(cr, uid, ids, {'state':'draft'})
127         wf_service = netsvc.LocalService("workflow")
128         for id in ids:
129             wf_service.trg_create(uid, 'payment.order', id, cr)
130         return True
131
132     def action_open(self, cr, uid, ids, *args):
133         for order in self.read(cr,uid,ids,['reference']):
134             if not order['reference']:
135                 reference = self.pool.get('ir.sequence').get(cr, uid, 'payment.order')
136                 self.write(cr,uid,order['id'],{'reference':reference})
137         return True
138
139     def set_done(self, cr, uid, id, *args):
140         self.write(cr,uid,id,{'date_done': time.strftime('%Y-%m-%d'),
141             'state': 'done',})
142         wf_service = netsvc.LocalService("workflow")
143         wf_service.trg_validate(uid, 'payment.order', id, 'done', cr)
144         return True
145
146 payment_order()
147
148
149 class payment_line(osv.osv):
150     _name = 'payment.line'
151     _description = 'Payment Line'
152
153     #~ def partner_payable(self, cr, uid, ids, name, args, context={}):
154         #~ if not ids: return {}
155         #~ partners= self.read(cr, uid, ids, ['partner_id'], context)
156         #~ partners= dict(map(lambda x: (x['id'], x['partner_id'][0]), partners))
157         #~ debit = self.pool.get('res.partner')._debit_get(cr, uid,
158                 #~ partners.values(), name, args, context)
159         #~ for i in partners:
160             #~ partners[i] = debit[partners[i]]
161         #~ return partners
162
163     def translate(self, orig):
164         return {
165 #               "to_pay": "credit",
166                 "due_date": "date_maturity",
167                 "reference": "ref"}.get(orig, orig)
168
169     def info_owner(self, cr, uid, ids, name=None, args=None, context=None):
170         if not ids: return {}
171         result = {}
172         info=''
173         for line in self.browse(cr, uid, ids, context=context):
174             owner=line.order_id.mode.bank_id.partner_id
175             result[line.id]=False
176             if owner.address:
177                 for ads in owner.address:
178                     if ads.type=='default':
179                         st=ads.street and ads.street or ''
180                         st1=ads.street2 and ads.street2 or ''
181                         if 'zip_id' in ads:
182                             zip_city= ads.zip_id and self.pool.get('res.partner.zip').name_get(cr,uid,[ads.zip_id.id])[0][1] or ''
183                         else:
184                             zip=ads.zip and ads.zip or ''
185                             city= ads.city and ads.city or  ''
186                             zip_city= zip + ' ' + city
187                         cntry= ads.country_id and ads.country_id.name or ''
188                         info=owner.name + "\n" + st + " " + st1 + "\n" + zip_city + "\n" +cntry
189                         result[line.id]=info
190                         break
191         return result
192
193     def info_partner(self, cr, uid, ids, name=None, args=None, context=None):
194         if not ids: return {}
195         result = {}
196         info=''
197         for line in self.browse(cr, uid, ids, context=context):
198             result[line.id]=False
199             if not line.partner_id:
200                 break
201             partner = line.partner_id.name or ''
202             if line.partner_id.address:
203                 for ads in line.partner_id.address:
204                     if ads.type=='default':
205                         st=ads.street and ads.street or ''
206                         st1=ads.street2 and ads.street2 or ''
207                         if 'zip_id' in ads:
208                             zip_city= ads.zip_id and self.pool.get('res.partner.zip').name_get(cr,uid,[ads.zip_id.id])[0][1] or ''
209                         else:
210                             zip=ads.zip and ads.zip or ''
211                             city= ads.city and ads.city or  ''
212                             zip_city= zip + ' ' + city
213                         cntry= ads.country_id and ads.country_id.name or ''
214                         info=partner + "\n" + st + " " + st1 + "\n" + zip_city + "\n" +cntry
215                         result[line.id]=info
216                         break
217         return result
218
219     def select_by_name(self, cr, uid, ids, name, args, context=None):
220         if not ids: return {}
221
222         partner_obj = self.pool.get('res.partner')
223         cr.execute("""SELECT pl.id, ml.%s
224             from account_move_line ml
225                 inner join payment_line pl
226                 on (ml.id = pl.move_line_id)
227             where pl.id in (%s)"""%
228             (self.translate(name), ','.join(map(str,ids))) )
229         res = dict(cr.fetchall())
230
231         if name == 'partner_id':
232             partner_name = {}
233             for p_id, p_name in partner_obj.name_get(cr,uid,
234                 filter(lambda x:x and x != 0,res.values()),context=context):
235                 partner_name[p_id] = p_name
236
237             for id in ids:
238                 if id in res and partner_name:
239                     res[id] = (res[id],partner_name[res[id]])
240                 else:
241                     res[id] = (False,False)
242         else:
243             for id in ids:
244                 res.setdefault(id, (False, ""))
245         return res
246
247 #   def _currency(self, cursor, user, ids, name, args, context=None):
248 #       if not ids:
249 #           return {}
250 #       res = {}
251 #
252 #       currency_obj = self.pool.get('res.currency')
253 #       account_obj = self.pool.get('account.account')
254 #       cursor.execute('''SELECT pl.id, ml.currency_id, ml.account_id
255 #       FROM account_move_line ml
256 #           INNER JOIN payment_line pl
257 #               ON (ml.id = pl.move_line_id)
258 #       WHERE pl.id in (''' + ','.join([str(x) for x in ids]) + ')')
259 #
260 #       res2 = {}
261 #       account_ids = []
262 #       for payment_line_id, currency_id, account_id in cursor.fetchall():
263 #           res2[payment_line_id] = [currency_id, account_id]
264 #           account_ids.append(account_id)
265 #
266 #       account2currency_id = {}
267 #       for account in account_obj.browse(cursor, user, account_ids,
268 #               context=context):
269 #           account2currency_id[account.id] = account.company_currency_id.id
270 #
271 #       for payment_line_id in ids:
272 #           if res2[payment_line_id][0]:
273 #               res[payment_line_id] = res2[payment_line_id][0]
274 #           else:
275 #               res[payment_line_id] = \
276 #                       account2currency_id[res2[payment_line_id][1]]
277 #
278 #       currency_names = {}
279 #       for currency_id, name in currency_obj.name_get(cursor, user, res.values(),
280 #               context=context):
281 #           currency_names[currency_id] = name
282 #       for payment_line_id in ids:
283 #           res[payment_line_id] = (res[payment_line_id],
284 #                   currency_names[res[payment_line_id]])
285 #       return res
286 #
287 #   def _to_pay_currency(self, cursor, user, ids, name , args, context=None):
288 #       if not ids:
289 #           return {}
290 #
291 #       cursor.execute('''SELECT pl.id,
292 #           CASE WHEN ml.amount_currency < 0
293 #               THEN - ml.amount_currency
294 #               ELSE ml.credit
295 #           END
296 #       FROM account_move_line ml
297 #           INNER JOIN payment_line pl
298 #               ON (ml.id = pl.move_line_id)
299 #       WHERE pl.id in (''' + ','.join([str(x) for x in ids]) + ')')
300 #       return dict(cursor.fetchall())
301
302     def _amount(self, cursor, user, ids, name, args, context=None):
303         if not ids:
304             return {}
305         currency_obj = self.pool.get('res.currency')
306         if context is None:
307             context = {}
308         res = {}
309         for line in self.browse(cursor, user, ids, context=context):
310             ctx = context.copy()
311             ctx['date'] = line.order_id.date_done or time.strftime('%Y-%m-%d')
312             res[line.id] = currency_obj.compute(cursor, user, line.currency.id,
313                     line.company_currency.id,
314                     line.amount_currency, context=ctx)
315         return res
316
317     def _value_date(self, cursor, user, ids, name, args, context=None):
318         if not ids:
319             return {}
320         res = {}
321         for line in self.browse(cursor, user, ids, context=context):
322             if line.order_id.date_prefered == 'fixed':
323                 res[line.id] = line.order_id.date_planned
324             elif line.order_id.date_prefered == 'due':
325                 res[line.id] = line.due_date or time.strftime('%Y-%m-%d')
326             else:
327                 res[line.id] = time.strftime('%Y-%m-%d')
328         return res
329
330     def _get_currency(self, cr, uid, context):
331         user = self.pool.get('res.users').browse(cr, uid, uid)
332         if user.company_id:
333             return user.company_id.currency_id.id
334         else:
335             return self.pool.get('res.currency').search(cr, uid, [('rate','=',1.0)])[0]
336
337 #   def select_move_lines(*a):
338 #       print a
339 #       return []
340
341 #   def create(self, cr, uid, vals, context):
342 #       print "created!!!"
343 #       vals['company_currency'] = self._get_currency(cr, uid, context)
344 #       return super(payment_line, self).create(cr, uid, vals, context)
345
346     def _get_ml_inv_ref(self, cr, uid, ids, *a):
347         res={}
348         for id in self.browse(cr, uid, ids):
349             res[id.id] = False
350             if id.move_line_id:
351                 if id.move_line_id.invoice:
352                     res[id.id] = id.move_line_id.invoice.id
353         return res
354
355     def _get_ml_maturity_date(self, cr, uid, ids, *a):
356         res={}
357         for id in self.browse(cr, uid, ids):
358             if id.move_line_id:
359                 res[id.id] = id.move_line_id.date_maturity
360             else:
361                 res[id.id] = ""
362         return res
363
364     def _get_ml_created_date(self, cr, uid, ids, *a):
365         res={}
366         for id in self.browse(cr, uid, ids):
367             if id.move_line_id:
368                 res[id.id] = id.move_line_id.date_created
369             else:
370                 res[id.id] = ""
371         return res
372
373     _columns = {
374         'name': fields.char('Your Reference', size=64, required=True),
375         'communication': fields.char('Communication', size=64, required=True,help="Used as the message between ordering customer and current company. Depicts 'What do you want to say to the recipient about this order ?'"),
376         'communication2': fields.char('Communication 2', size=64,help='The successor message of Communication.'),
377         'move_line_id': fields.many2one('account.move.line','Entry line', domain=[('reconcile_id','=', False), ('account_id.type', '=','payable')],help='This Entry Line will be referred for the information of the ordering customer.'),
378         'amount_currency': fields.float('Amount in Partner Currency', digits=(16,2),
379             required=True, help='Payment amount in the partner currency'),
380 #       'to_pay_currency': fields.function(_to_pay_currency, string='To Pay',
381 #           method=True, type='float',
382 #           help='Amount to pay in the partner currency'),
383 #       'currency': fields.function(_currency, string='Currency',
384 #           method=True, type='many2one', obj='res.currency'),
385         'currency': fields.many2one('res.currency','Partner Currency',required=True),
386         'company_currency': fields.many2one('res.currency','Company Currency',readonly=True),
387         'bank_id': fields.many2one('res.partner.bank', 'Destination Bank account'),
388         'order_id': fields.many2one('payment.order', 'Order', required=True,
389             ondelete='cascade', select=True),
390         'partner_id': fields.many2one('res.partner', string="Partner",required=True,help='The Ordering Customer'),
391         'amount': fields.function(_amount, string='Amount in Company Currency',
392             method=True, type='float',
393             help='Payment amount in the company currency'),
394 #       'to_pay': fields.function(select_by_name, string="To Pay", method=True,
395 #           type='float', help='Amount to pay in the company currency'),
396 #       'due_date': fields.function(select_by_name, string="Due date",
397 #           method=True, type='date'),
398         'ml_date_created': fields.function(_get_ml_created_date, string="Effective Date",
399             method=True, type='date',help="Invoice Effective Date"),
400 #       'reference': fields.function(select_by_name, string="Ref", method=True,
401 #           type='char'),
402         'ml_maturity_date': fields.function(_get_ml_maturity_date, method=True, type='date', string='Maturity Date'),
403         'ml_inv_ref': fields.function(_get_ml_inv_ref, method=True, type='many2one', relation='account.invoice', string='Invoice Ref.'),
404         'info_owner': fields.function(info_owner, string="Owner Account", method=True, type="text",help='Address of the Main Partner'),
405         'info_partner': fields.function(info_partner, string="Destination Account", method=True, type="text",help='Address of the Ordering Customer.'),
406 #        'partner_payable': fields.function(partner_payable, string="Partner payable", method=True, type='float'),
407 #       'value_date': fields.function(_value_date, string='Value Date',
408 #           method=True, type='date'),
409         'date': fields.date('Payment Date',help="If no payment date is specified, the bank will treat this payment line directly"),
410         'create_date': fields.datetime('Created' ,readonly=True),
411         'state': fields.selection([('normal','Free'), ('structured','Structured')], 'Communication Type', required=True)
412     }
413     _defaults = {
414         'name': lambda obj, cursor, user, context: obj.pool.get('ir.sequence'
415             ).get(cursor, user, 'payment.line'),
416         'state': lambda *args: 'normal',
417         'currency': _get_currency,
418         'company_currency': _get_currency,
419     }
420     _sql_constraints = [
421         ('name_uniq', 'UNIQUE(name)', 'The payment line name must be unique!'),
422     ]
423
424     def onchange_move_line(self,cr,uid,ids,move_line_id,payment_type,date_prefered,date_planned,currency=False,company_currency=False,context=None):
425         data={}
426
427         data['amount_currency']=data['communication']=data['partner_id']=data['reference']=data['date_created']=data['bank_id']=data['amount']=False
428
429         if move_line_id:
430             line = self.pool.get('account.move.line').browse(cr,uid,move_line_id)
431             data['amount_currency']=line.amount_to_pay
432
433             res = self.onchange_amount(cr, uid, ids, data['amount_currency'], currency,
434                                        company_currency, context)
435             if res:
436                 data['amount'] = res['value']['amount']
437             data['partner_id']=line.partner_id.id
438             temp = line.currency_id and line.currency_id.id or False
439             if not temp:
440                 if line.invoice:
441                     data['currency'] = line.invoice.currency_id.id
442             else:
443                 data['currency'] = temp
444
445             # calling onchange of partner and updating data dictionary
446             temp_dict=self.onchange_partner(cr,uid,ids,line.partner_id.id,payment_type)
447             data.update(temp_dict['value'])
448
449             data['reference']=line.ref
450             data['date_created'] = line.date_created
451             data['communication']=line.ref
452
453             if date_prefered == 'now':
454                 #no payment date => immediate payment
455                 data['date'] = False
456             elif date_prefered == 'due':
457                 data['date'] = line.date_maturity
458             elif date_prefered == 'fixed':
459                 data['date'] = date_planned
460
461         return {'value': data}
462
463     def onchange_amount(self, cr, uid, ids, amount, currency, cmpny_currency, context=None):
464         if (not amount) or (not cmpny_currency):
465             return {'value': {'amount':False}}
466         res = {}
467         currency_obj = self.pool.get('res.currency')
468         company_amount = currency_obj.compute(cr, uid, currency, cmpny_currency, amount)
469         res['amount'] = company_amount
470         return {'value': res}
471
472     def onchange_partner(self,cr,uid,ids,partner_id,payment_type,context=None):
473         data={}
474         data['info_partner']=data['bank_id']=False
475
476         if partner_id:
477             part_obj=self.pool.get('res.partner').browse(cr,uid,partner_id)
478             partner=part_obj.name or ''
479
480             if part_obj.address:
481                 for ads in part_obj.address:
482                     if ads.type=='default':
483                         st=ads.street and ads.street or ''
484                         st1=ads.street2 and ads.street2 or ''
485
486                         if 'zip_id' in ads:
487                             zip_city= ads.zip_id and self.pool.get('res.partner.zip').name_get(cr,uid,[ads.zip_id.id])[0][1] or ''
488                         else:
489                             zip=ads.zip and ads.zip or ''
490                             city= ads.city and ads.city or  ''
491                             zip_city= zip + ' ' + city
492
493                         cntry= ads.country_id and ads.country_id.name or ''
494                         info=partner + "\n" + st + " " + st1 + "\n" + zip_city + "\n" +cntry
495
496                         data['info_partner']=info
497
498             if part_obj.bank_ids and payment_type:
499                 bank_type = self.pool.get('payment.mode').suitable_bank_types(cr, uid, payment_type, context=context)
500                 for bank in part_obj.bank_ids:
501                     if bank.state in bank_type:
502                         data['bank_id'] = bank.id
503                         break
504
505         return {'value': data}
506
507     def fields_get(self, cr, uid, fields=None, context=None):
508         res = super(payment_line, self).fields_get(cr, uid, fields, context)
509         if 'communication2' in res:
510             res['communication2'].setdefault('states', {})
511             res['communication2']['states']['structured'] = [('readonly', True)]
512             res['communication2']['states']['normal'] = [('readonly', False)]
513
514         return res
515
516 payment_line()
517
518 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
519