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