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