[FIX] Fixed sidebar padding problem
[odoo/odoo.git] / addons / account_payment / account_payment.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
22 import time
23
24 from osv import osv, fields
25 import netsvc
26
27 class payment_mode(osv.osv):
28     _name= 'payment.mode'
29     _description= 'Payment Mode'
30     _columns = {
31         'name': fields.char('Name', size=64, required=True, help='Mode of Payment'),
32         'bank_id': fields.many2one('res.partner.bank', "Bank account",
33             required=True,help='Bank Account for the Payment Mode'),
34         'journal': fields.many2one('account.journal', 'Journal', required=True,
35             domain=[('type', 'in', ('bank','cash'))], help='Bank or Cash Journal for the Payment Mode'),
36         'company_id': fields.many2one('res.company', 'Company',required=True),
37         'partner_id':fields.related('company_id','partner_id',type='many2one',relation='res.partner',string='Partner',store=True,),
38         
39     }
40     _defaults = {
41         'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id
42     }
43
44     def suitable_bank_types(self, cr, uid, payment_code=None, context=None):
45         """Return the codes of the bank type that are suitable
46         for the given payment type code"""
47         if not payment_code:
48             return []
49         cr.execute(""" SELECT pb.state
50             FROM res_partner_bank pb
51             JOIN payment_mode pm ON (pm.bank_id = pb.id)
52             WHERE pm.id = %s """, [payment_code])
53         return [x[0] for x in cr.fetchall()]
54     
55     def onchange_company_id (self, cr, uid, ids, company_id=False, context=None):
56         result = {}
57         if company_id:
58             partner_id = self.pool.get('res.company').browse(cr, uid, company_id, context=context).partner_id.id
59             result['partner_id'] = partner_id
60         return {'value': result}
61                 
62
63 payment_mode()
64
65 class payment_order(osv.osv):
66     _name = 'payment.order'
67     _description = 'Payment Order'
68     _rec_name = 'reference'
69     _order = 'id desc'
70
71     def get_wizard(self, type):
72         logger = netsvc.Logger()
73         logger.notifyChannel("warning", netsvc.LOG_WARNING,
74                 "No wizard found for the payment type '%s'." % type)
75         return None
76
77     def _total(self, cursor, user, ids, name, args, context=None):
78         if not ids:
79             return {}
80         res = {}
81         for order in self.browse(cursor, user, ids, context=context):
82             if order.line_ids:
83                 res[order.id] = reduce(lambda x, y: x + y.amount, order.line_ids, 0.0)
84             else:
85                 res[order.id] = 0.0
86         return res
87
88     _columns = {
89         'date_scheduled': fields.date('Scheduled date if fixed', states={'done':[('readonly', True)]}, help='Select a date if you have chosen Preferred Date to be fixed.'),
90         'reference': fields.char('Reference', size=128, required=1, states={'done': [('readonly', True)]}),
91         'mode': fields.many2one('payment.mode', 'Payment mode', select=True, required=1, states={'done': [('readonly', True)]}, help='Select the Payment Mode to be applied.'),
92         'state': fields.selection([
93             ('draft', 'Draft'),
94             ('open', 'Confirmed'),
95             ('cancel', 'Cancelled'),
96             ('done', 'Done')], 'State', select=True,
97             help='When an order is placed the state is \'Draft\'.\n Once the bank is confirmed the state is set to \'Confirmed\'.\n Then the order is paid the state is \'Done\'.'),
98         'line_ids': fields.one2many('payment.line', 'order_id', 'Payment lines', states={'done': [('readonly', True)]}),
99         'total': fields.function(_total, string="Total", type='float'),
100         'user_id': fields.many2one('res.users', 'User', required=True, states={'done': [('readonly', True)]}),
101         'date_prefered': fields.selection([
102             ('now', 'Directly'),
103             ('due', 'Due date'),
104             ('fixed', 'Fixed date')
105             ], "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."),
106         'date_created': fields.date('Creation date', readonly=True),
107         'date_done': fields.date('Execution date', readonly=True),
108         'company_id': fields.related('mode', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
109     }
110
111     _defaults = {
112         'user_id': lambda self,cr,uid,context: uid,
113         'state': 'draft',
114         'date_prefered': 'due',
115         'date_created': lambda *a: time.strftime('%Y-%m-%d'),
116         'reference': lambda self,cr,uid,context: self.pool.get('ir.sequence').get(cr, uid, 'payment.order'),
117     }
118
119     def set_to_draft(self, cr, uid, ids, *args):
120         self.write(cr, uid, ids, {'state': 'draft'})
121         wf_service = netsvc.LocalService("workflow")
122         for id in ids:
123             wf_service.trg_create(uid, 'payment.order', id, cr)
124         return True
125
126     def action_open(self, cr, uid, ids, *args):
127         ir_seq_obj = self.pool.get('ir.sequence')
128
129         for order in self.read(cr, uid, ids, ['reference']):
130             if not order['reference']:
131                 reference = ir_seq_obj.get(cr, uid, 'payment.order')
132                 self.write(cr, uid, order['id'], {'reference':reference})
133         return True
134
135     def set_done(self, cr, uid, ids, *args):
136         wf_service = netsvc.LocalService("workflow")
137         self.write(cr, uid, ids, {'date_done': time.strftime('%Y-%m-%d')})
138         wf_service.trg_validate(uid, 'payment.order', ids[0], 'done', cr)
139         return True
140
141     def copy(self, cr, uid, id, default={}, context=None):
142         default.update({
143             'state': 'draft',
144             'line_ids': [],
145             'reference': self.pool.get('ir.sequence').get(cr, uid, 'payment.order')
146         })
147         return super(payment_order, self).copy(cr, uid, id, default, context=context)
148
149     def write(self, cr, uid, ids, vals, context=None):
150         if context is None:
151             context = {}
152         payment_line_obj = self.pool.get('payment.line')
153         payment_line_ids = []
154
155         if (vals.get('date_prefered', False) == 'fixed' and not vals.get('date_scheduled', False)) or vals.get('date_scheduled', False):
156             for order in self.browse(cr, uid, ids, context=context):
157                 for line in order.line_ids:
158                     payment_line_ids.append(line.id)
159             payment_line_obj.write(cr, uid, payment_line_ids, {'date': vals.get('date_scheduled', False)}, context=context)
160         elif vals.get('date_prefered', False) == 'due':
161             vals.update({'date_scheduled': False})
162             for order in self.browse(cr, uid, ids, context=context):
163                 for line in order.line_ids:
164                     payment_line_obj.write(cr, uid, [line.id], {'date': line.ml_maturity_date}, context=context)
165         elif vals.get('date_prefered', False) == 'now':
166             vals.update({'date_scheduled': False})
167             for order in self.browse(cr, uid, ids, context=context):
168                 for line in order.line_ids:
169                     payment_line_ids.append(line.id)
170             payment_line_obj.write(cr, uid, payment_line_ids, {'date': False}, context=context)
171         return super(payment_order, self).write(cr, uid, ids, vals, context=context)
172
173 payment_order()
174
175 class payment_line(osv.osv):
176     _name = 'payment.line'
177     _description = 'Payment Line'
178
179     def translate(self, orig):
180         return {
181                 "due_date": "date_maturity",
182                 "reference": "ref"}.get(orig, orig)
183
184     def info_owner(self, cr, uid, ids, name=None, args=None, context=None):
185         if not ids: return {}
186         partner_zip_obj = self.pool.get('res.partner.zip')
187
188         result = {}
189         info=''
190         for line in self.browse(cr, uid, ids, context=context):
191             owner = line.order_id.mode.bank_id.partner_id
192             result[line.id] = False
193             if owner.address:
194                 for ads in owner.address:
195                     if ads.type == 'default':
196                         st = ads.street and ads.street or ''
197                         st1 = ads.street2 and ads.street2 or ''
198                         if 'zip_id' in ads:
199                             zip_city = ads.zip_id and partner_zip_obj.name_get(cr, uid, [ads.zip_id.id])[0][1] or ''
200                         else:
201                             zip = ads.zip and ads.zip or ''
202                             city = ads.city and ads.city or  ''
203                             zip_city = zip + ' ' + city
204                         cntry = ads.country_id and ads.country_id.name or ''
205                         info = owner.name + "\n" + st + " " + st1 + "\n" + zip_city + "\n" +cntry
206                         result[line.id] = info
207                         break
208         return result
209
210     def info_partner(self, cr, uid, ids, name=None, args=None, context=None):
211         if not ids: return {}
212         partner_zip_obj = self.pool.get('res.partner.zip')
213         result = {}
214         info = ''
215
216         for line in self.browse(cr, uid, ids, context=context):
217             result[line.id] = False
218             if not line.partner_id:
219                 break
220             partner = line.partner_id.name or ''
221             if line.partner_id.address:
222                 for ads in line.partner_id.address:
223                     if ads.type == 'default':
224                         st = ads.street and ads.street or ''
225                         st1 = ads.street2 and ads.street2 or ''
226                         if 'zip_id' in ads:
227                             zip_city = ads.zip_id and partner_zip_obj.name_get(cr, uid, [ads.zip_id.id])[0][1] or ''
228                         else:
229                             zip = ads.zip and ads.zip or ''
230                             city = ads.city and ads.city or  ''
231                             zip_city = zip + ' ' + city
232                         cntry = ads.country_id and ads.country_id.name or ''
233                         info = partner + "\n" + st + " " + st1 + "\n" + zip_city + "\n" +cntry
234                         result[line.id] = info
235                         break
236         return result
237
238     def select_by_name(self, cr, uid, ids, name, args, context=None):
239         if not ids: return {}
240         partner_obj = self.pool.get('res.partner')
241
242         cr.execute("""SELECT pl.id, ml.%s
243             FROM account_move_line ml
244                 INNER JOIN payment_line pl
245                 ON (ml.id = pl.move_line_id)
246                 WHERE pl.id IN %%s"""% self.translate(name),
247                    (tuple(ids),))
248         res = dict(cr.fetchall())
249
250         if name == 'partner_id':
251             partner_name = {}
252             for p_id, p_name in partner_obj.name_get(cr, uid,
253                 filter(lambda x:x and x != 0,res.values()), context=context):
254                 partner_name[p_id] = p_name
255
256             for id in ids:
257                 if id in res and partner_name:
258                     res[id] = (res[id],partner_name[res[id]])
259                 else:
260                     res[id] = (False,False)
261         else:
262             for id in ids:
263                 res.setdefault(id, (False, ""))
264         return res
265
266     def _amount(self, cursor, user, ids, name, args, context=None):
267         if not ids:
268             return {}
269         currency_obj = self.pool.get('res.currency')
270         if context is None:
271             context = {}
272         res = {}
273
274         for line in self.browse(cursor, user, ids, context=context):
275             ctx = context.copy()
276             ctx['date'] = line.order_id.date_done or time.strftime('%Y-%m-%d')
277             res[line.id] = currency_obj.compute(cursor, user, line.currency.id,
278                     line.company_currency.id,
279                     line.amount_currency, context=ctx)
280         return res
281
282     def _get_currency(self, cr, uid, context=None):
283         user_obj = self.pool.get('res.users')
284         currency_obj = self.pool.get('res.currency')
285         user = user_obj.browse(cr, uid, uid, context=context)
286
287         if user.company_id:
288             return user.company_id.currency_id.id
289         else:
290             return currency_obj.search(cr, uid, [('rate', '=', 1.0)])[0]
291
292     def _get_date(self, cr, uid, context=None):
293         if context is None:
294             context = {}
295         payment_order_obj = self.pool.get('payment.order')
296         date = False
297
298         if context.get('order_id') and context['order_id']:
299             order = payment_order_obj.browse(cr, uid, context['order_id'], context=context)
300             if order.date_prefered == 'fixed':
301                 date = order.date_scheduled
302             else:
303                 date = time.strftime('%Y-%m-%d')
304         return date
305
306     def _get_ml_inv_ref(self, cr, uid, ids, *a):
307         res = {}
308         for id in self.browse(cr, uid, ids):
309             res[id.id] = False
310             if id.move_line_id:
311                 if id.move_line_id.invoice:
312                     res[id.id] = id.move_line_id.invoice.id
313         return res
314
315     def _get_ml_maturity_date(self, cr, uid, ids, *a):
316         res = {}
317         for id in self.browse(cr, uid, ids):
318             if id.move_line_id:
319                 res[id.id] = id.move_line_id.date_maturity
320             else:
321                 res[id.id] = False
322         return res
323
324     def _get_ml_created_date(self, cr, uid, ids, *a):
325         res = {}
326         for id in self.browse(cr, uid, ids):
327             if id.move_line_id:
328                 res[id.id] = id.move_line_id.date_created
329             else:
330                 res[id.id] = False
331         return res
332
333     _columns = {
334         'name': fields.char('Your Reference', size=64, required=True),
335         '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 ?'"),
336         'communication2': fields.char('Communication 2', size=64, help='The successor message of Communication.'),
337         '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.'),
338         'amount_currency': fields.float('Amount in Partner Currency', digits=(16, 2),
339             required=True, help='Payment amount in the partner currency'),
340         'currency': fields.many2one('res.currency','Partner Currency', required=True),
341         'company_currency': fields.many2one('res.currency', 'Company Currency', readonly=True),
342         'bank_id': fields.many2one('res.partner.bank', 'Destination Bank Account'),
343         'order_id': fields.many2one('payment.order', 'Order', required=True,
344             ondelete='cascade', select=True),
345         'partner_id': fields.many2one('res.partner', string="Partner", required=True, help='The Ordering Customer'),
346         'amount': fields.function(_amount, string='Amount in Company Currency',
347             type='float',
348             help='Payment amount in the company currency'),
349         'ml_date_created': fields.function(_get_ml_created_date, string="Effective Date",
350             type='date', help="Invoice Effective Date"),
351         'ml_maturity_date': fields.function(_get_ml_maturity_date, type='date', string='Due Date'),
352         'ml_inv_ref': fields.function(_get_ml_inv_ref, type='many2one', relation='account.invoice', string='Invoice Ref.'),
353         'info_owner': fields.function(info_owner, string="Owner Account", type="text", help='Address of the Main Partner'),
354         'info_partner': fields.function(info_partner, string="Destination Account", type="text", help='Address of the Ordering Customer.'),
355         'date': fields.date('Payment Date', help="If no payment date is specified, the bank will treat this payment line directly"),
356         'create_date': fields.datetime('Created', readonly=True),
357         'state': fields.selection([('normal','Free'), ('structured','Structured')], 'Communication Type', required=True),
358         'bank_statement_line_id': fields.many2one('account.bank.statement.line', 'Bank statement line'),
359         'company_id': fields.related('order_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
360     }
361     _defaults = {
362         'name': lambda obj, cursor, user, context: obj.pool.get('ir.sequence'
363             ).get(cursor, user, 'payment.line'),
364         'state': 'normal',
365         'currency': _get_currency,
366         'company_currency': _get_currency,
367         'date': _get_date,
368     }
369     _sql_constraints = [
370         ('name_uniq', 'UNIQUE(name)', 'The payment line name must be unique!'),
371     ]
372
373     def onchange_move_line(self, cr, uid, ids, move_line_id, payment_type, date_prefered, date_scheduled, currency=False, company_currency=False, context=None):
374         data = {}
375         move_line_obj = self.pool.get('account.move.line')
376
377         data['amount_currency'] = data['communication'] = data['partner_id'] = data['bank_id'] = data['amount'] = False
378
379         if move_line_id:
380             line = move_line_obj.browse(cr, uid, move_line_id, context=context)
381             data['amount_currency'] = line.amount_to_pay
382
383             res = self.onchange_amount(cr, uid, ids, data['amount_currency'], currency,
384                                        company_currency, context)
385             if res:
386                 data['amount'] = res['value']['amount']
387             data['partner_id'] = line.partner_id.id
388             temp = line.currency_id and line.currency_id.id or False
389             if not temp:
390                 if line.invoice:
391                     data['currency'] = line.invoice.currency_id.id
392             else:
393                 data['currency'] = temp
394
395             # calling onchange of partner and updating data dictionary
396             temp_dict = self.onchange_partner(cr, uid, ids, line.partner_id.id, payment_type)
397             data.update(temp_dict['value'])
398
399             data['communication'] = line.ref
400
401             if date_prefered == 'now':
402                 #no payment date => immediate payment
403                 data['date'] = False
404             elif date_prefered == 'due':
405                 data['date'] = line.date_maturity
406             elif date_prefered == 'fixed':
407                 data['date'] = date_scheduled
408         return {'value': data}
409
410     def onchange_amount(self, cr, uid, ids, amount, currency, cmpny_currency, context=None):
411         if (not amount) or (not cmpny_currency):
412             return {'value': {'amount': False}}
413         res = {}
414         currency_obj = self.pool.get('res.currency')
415         company_amount = currency_obj.compute(cr, uid, currency, cmpny_currency, amount)
416         res['amount'] = company_amount
417         return {'value': res}
418
419     def onchange_partner(self, cr, uid, ids, partner_id, payment_type, context=None):
420         data = {}
421         partner_zip_obj = self.pool.get('res.partner.zip')
422         partner_obj = self.pool.get('res.partner')
423         payment_mode_obj = self.pool.get('payment.mode')
424         data['info_partner'] = data['bank_id'] = False
425
426         if partner_id:
427             part_obj = partner_obj.browse(cr, uid, partner_id, context=context)
428             partner = part_obj.name or ''
429
430             if part_obj.address:
431                 for ads in part_obj.address:
432                     if ads.type == 'default':
433                         st = ads.street and ads.street or ''
434                         st1 = ads.street2 and ads.street2 or ''
435
436                         if 'zip_id' in ads:
437                             zip_city = ads.zip_id and partner_zip_obj.name_get(cr, uid, [ads.zip_id.id])[0][1] or ''
438                         else:
439                             zip = ads.zip and ads.zip or ''
440                             city = ads.city and ads.city or  ''
441                             zip_city = zip + ' ' + city
442
443                         cntry = ads.country_id and ads.country_id.name or ''
444                         info = partner + "\n" + st + " " + st1 + "\n" + zip_city + "\n" +cntry
445
446                         data['info_partner'] = info
447
448             if part_obj.bank_ids and payment_type:
449                 bank_type = payment_mode_obj.suitable_bank_types(cr, uid, payment_type, context=context)
450                 for bank in part_obj.bank_ids:
451                     if bank.state in bank_type:
452                         data['bank_id'] = bank.id
453                         break
454         return {'value': data}
455
456     def fields_get(self, cr, uid, fields=None, context=None):
457         res = super(payment_line, self).fields_get(cr, uid, fields, context)
458         if 'communication2' in res:
459             res['communication2'].setdefault('states', {})
460             res['communication2']['states']['structured'] = [('readonly', True)]
461             res['communication2']['states']['normal'] = [('readonly', False)]
462         return res
463
464 payment_line()
465
466 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: