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