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