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