~zaber/openobject-addons/zaber-custom

« back to all changes in this revision

Viewing changes to account_payment_extension/payment.py

  • Committer: Aki Mimoto
  • Date: 2013-08-27 12:25:06 UTC
  • Revision ID: aki+launchpad@zaber.com-20130827122506-e4hildop20zysli2
[IMP] Add modules from extras repo: account_payment_extension/ purchase_payment/

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- encoding: utf-8 -*-
 
2
##############################################################################
 
3
#
 
4
#    OpenERP, Open Source Management Solution
 
5
#    Copyright (c) 2008 Zikzakmedia S.L. (http://zikzakmedia.com) All Rights Reserved.
 
6
#                       Jordi Esteve <jesteve@zikzakmedia.com>
 
7
#    $Id$
 
8
#
 
9
#    This program is free software: you can redistribute it and/or modify
 
10
#    it under the terms of the GNU Affero General Public License as published by
 
11
#    the Free Software Foundation, either version 3 of the License, or
 
12
#    (at your option) any later version.
 
13
#
 
14
#    This program is distributed in the hope that it will be useful,
 
15
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
16
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
17
#    GNU Affero General Public License for more details.
 
18
#
 
19
#    You should have received a copy of the GNU Affero General Public License
 
20
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
21
#
 
22
##############################################################################
 
23
 
 
24
import time
 
25
import netsvc
 
26
from osv import fields, osv
 
27
from tools.translate import _
 
28
import decimal_precision as dp
 
29
 
 
30
class payment_type(osv.osv):
 
31
    _name= 'payment.type'
 
32
    _description= 'Payment type'
 
33
    _columns= {
 
34
        'name': fields.char('Name', size=64, required=True, help='Payment Type', translate=True),
 
35
        'code': fields.char('Code', size=64, required=True, help='Specify the Code for Payment Type'),
 
36
        'suitable_bank_types': fields.many2many('res.partner.bank.type',
 
37
            'bank_type_payment_type_rel',
 
38
            'pay_type_id','bank_type_id',
 
39
            'Suitable bank types'),
 
40
        'active': fields.boolean('Active', select=True),
 
41
        'note': fields.text('Description', translate=True, help="Description of the payment type that will be shown in the invoices"),
 
42
        'company_id': fields.many2one('res.company', 'Company', required=True),
 
43
    }
 
44
    _defaults = {
 
45
        'active': lambda *a: 1,
 
46
        'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id
 
47
    }
 
48
 
 
49
payment_type()
 
50
 
 
51
 
 
52
class payment_mode(osv.osv):
 
53
    _inherit = 'payment.mode'
 
54
    _columns = {
 
55
        'type': fields.many2one('payment.type', 'Payment type', required=True, help='Select the Payment Type for the Payment Mode.'),
 
56
        'require_bank_account': fields.boolean('Require Bank Account', help='Ensure all lines in the payment order have a bank account when proposing lines to be added in the payment order.'),
 
57
        'require_received_check': fields.boolean('Require Received Check', help='Ensure all lines in the payment order have the Received Check flag set.'),
 
58
        'require_same_bank_account': fields.boolean('Require the Same Bank Account', help='Ensure all lines in the payment order and the payment mode have the same account number.'),
 
59
    }
 
60
    _defaults = {
 
61
        'require_bank_account': lambda *a: False,
 
62
    }
 
63
payment_mode()
 
64
 
 
65
 
 
66
class res_partner(osv.osv):
 
67
    _inherit='res.partner'
 
68
    _columns={
 
69
        'payment_type_customer': fields.property(
 
70
            'payment.type',
 
71
            type='many2one',
 
72
            relation='payment.type',
 
73
            string ='Customer Payment Type',
 
74
            method=True,
 
75
            view_load=True,
 
76
            help="Payment type of the customer"),
 
77
        'payment_type_supplier': fields.property(
 
78
            'payment.type',
 
79
            type='many2one',
 
80
            relation='payment.type',
 
81
            string ='Supplier Payment Type',
 
82
            method=True,
 
83
            view_load=True,
 
84
            help="Payment type of the supplier"),
 
85
    }
 
86
res_partner()
 
87
 
 
88
 
 
89
class res_partner_bank(osv.osv):
 
90
 
 
91
    def create(self, cr, uid, vals, context=None):
 
92
        if vals.get('default_bank') and vals.get('partner_id') and vals.get('state'):
 
93
            sql = "UPDATE res_partner_bank SET default_bank='0' WHERE partner_id=%i AND default_bank='1' AND state='%s'" % (vals['partner_id'], vals['state'])
 
94
            cr.execute(sql)
 
95
        return super(res_partner_bank, self).create(cr, uid, vals, context=context)
 
96
 
 
97
    def write(self, cr, uid, ids, vals, context=None):
 
98
        if 'default_bank' in vals and vals['default_bank'] == True:
 
99
            partner_bank = self.pool.get('res.partner.bank').browse(cr, uid, ids)[0]
 
100
            partner_id = partner_bank.partner_id.id
 
101
            if 'state' in vals and vals['state']:
 
102
                state = vals['state']
 
103
            else:
 
104
                state = partner_bank.state
 
105
            sql = "UPDATE res_partner_bank SET default_bank='0' WHERE partner_id=%i AND default_bank='1' AND state='%s' AND id<>%i" % (partner_id, state, ids[0])
 
106
            cr.execute(sql)
 
107
        return super(res_partner_bank, self).write(cr, uid, ids, vals, context=context)
 
108
 
 
109
    _inherit="res.partner.bank"
 
110
    _columns = {
 
111
        'default_bank' : fields.boolean('Default'),
 
112
    }
 
113
 
 
114
res_partner_bank()
 
115
 
 
116
 
 
117
class payment_order(osv.osv):
 
118
    _name = 'payment.order'
 
119
    _inherit = 'payment.order'
 
120
 
 
121
    def _get_type(self, cr, uid, context=None):
 
122
        if context is None:
 
123
            context = {}
 
124
        return context.get('type', 'payable')
 
125
 
 
126
    def _get_reference(self, cr, uid, context=None):
 
127
        if context is None:
 
128
            context = {}
 
129
        type = context.get('type', 'payable')
 
130
        model = type == 'payable' and 'payment.order' or 'rec.payment.order'
 
131
        return self.pool.get('ir.sequence').get(cr, uid, model)
 
132
 
 
133
    def _get_period(self, cr, uid, context=None):
 
134
        try:
 
135
            # find() function will throw an exception if no period can be found for
 
136
            # current date. That should not be a problem because user would be notified
 
137
            # but as this model inherits an existing one, once installed it will create 
 
138
            # the new field and try to update existing records (even if there are no records yet)
 
139
            # So we must ensure no exception is thrown, otherwise the module can only be installed
 
140
            # once periods are created.
 
141
            periods = self.pool.get('account.period').find(cr, uid)
 
142
            return periods[0]
 
143
        except Exception, e:
 
144
            return False
 
145
 
 
146
    def _payment_type_name_get(self, cr, uid, ids, field_name, arg, context=None):
 
147
        result = {}
 
148
        for rec in self.browse(cr, uid, ids, context):
 
149
            result[rec.id] = rec.mode and rec.mode.type.name or ""
 
150
        return result
 
151
 
 
152
    def _name_get(self, cr, uid, ids, field_name, arg, context=None):
 
153
        result = {}
 
154
        for rec in self.browse(cr, uid, ids, context):
 
155
            result[rec.id] = rec.reference
 
156
        return result
 
157
 
 
158
    _columns = {
 
159
        'type': fields.selection([
 
160
            ('payable','Payable'),
 
161
            ('receivable','Receivable'),
 
162
            ],'Type', readonly=True, select=True),
 
163
        # invisible field to filter payment order lines by payment type
 
164
        'payment_type_name': fields.function(_payment_type_name_get, method=True, type="char", size=64, string="Payment type name"),
 
165
        # The field name is necessary to add attachement documents to payment orders
 
166
        'name': fields.function(_name_get, method=True, type="char", size=64, string="Name"),
 
167
        'create_account_moves': fields.selection([('bank-statement','Bank Statement'),('direct-payment','Direct Payment')],
 
168
                                                 'Create Account Moves',
 
169
                                                 required=True,
 
170
                                                 states={'done':[('readonly',True)]},
 
171
                                                 help='Indicates when account moves should be created for order payment lines. "Bank Statement" '\
 
172
                                                      'will wait until user introduces those payments in bank a bank statement. "Direct Payment" '\
 
173
                                                      'will mark all payment lines as payied once the order is done.'),
 
174
        'period_id': fields.many2one('account.period', 'Period', states={'done':[('readonly',True)]}),
 
175
    }
 
176
    _defaults = {
 
177
        'type': _get_type,
 
178
        'reference': _get_reference,
 
179
        'create_account_moves': lambda *a: 'bank-statement',
 
180
        'period_id': _get_period,
 
181
    }
 
182
 
 
183
    def cancel_from_done(self, cr, uid, ids, context=None):
 
184
        if context is None:
 
185
            context = {}
 
186
 
 
187
        #Search for account_moves
 
188
        remove = []
 
189
        for move in self.browse(cr,uid,ids,context):
 
190
            #Search for any line
 
191
            for line in move.line_ids:
 
192
                if line.payment_move_id:
 
193
                    remove += [ line.payment_move_id.id ]
 
194
        
 
195
        self.pool.get('account.move').button_cancel( cr, uid, remove, context=context)
 
196
        self.pool.get('account.move').unlink(cr, uid, remove, context)
 
197
        self.write( cr, uid, ids, {
 
198
            'state':'cancel'
 
199
        },context=context)
 
200
        return True
 
201
 
 
202
    def unlink(self, cr, uid, ids, context=None):
 
203
        pay_orders = self.read(cr, uid, ids, ['state'], context=context)
 
204
        unlink_ids = []
 
205
        for t in pay_orders:
 
206
            if t['state'] in ('draft', 'cancel'):
 
207
                unlink_ids.append(t['id'])
 
208
            else:
 
209
                raise osv.except_osv(_('Invalid action!'), _('You cannot delete payment order(s) which are already confirmed or done!'))
 
210
        result = super(payment_order, self).unlink(cr, uid, unlink_ids, context=context)
 
211
        return result
 
212
 
 
213
    def set_done(self, cr, uid, ids, context=None):
 
214
        result = super(payment_order, self).set_done(cr, uid, ids, context)
 
215
 
 
216
        company_currency_id = self.pool.get('res.users').browse(cr, uid, uid, context).company_id.currency_id.id
 
217
 
 
218
        for order in self.browse(cr, uid, ids, context):
 
219
            if order.create_account_moves != 'direct-payment':
 
220
                continue
 
221
 
 
222
            # This process creates a simple account move with bank and line accounts and line's amount. At the end
 
223
            # it will reconcile or partial reconcile both entries if that is possible.
 
224
 
 
225
            move_id = self.pool.get('account.move').create(cr, uid, {
 
226
                'name': '/',
 
227
                'journal_id': order.mode.journal.id,
 
228
                'period_id': order.period_id.id,
 
229
            }, context)
 
230
 
 
231
            for line in order.line_ids:
 
232
                if not line.amount:
 
233
                    continue
 
234
 
 
235
                if not line.account_id:
 
236
                    raise osv.except_osv(_('Error!'), _('Payment order should create account moves but line with amount %(amount).2f for partner "%(partner)s" has no account assigned.') % {'amount': line.amount, 'partner': line.partner_id.name} )
 
237
 
 
238
                currency_id = order.mode.journal.currency and order.mode.journal.currency.id or company_currency_id
 
239
 
 
240
                if line.type == 'payable':
 
241
                    line_amount = line.amount_currency or line.amount
 
242
                else:
 
243
                    line_amount = -line.amount_currency or -line.amount
 
244
                    
 
245
                if line_amount >= 0:
 
246
                    account_id = order.mode.journal.default_credit_account_id.id
 
247
                else:
 
248
                    account_id = order.mode.journal.default_debit_account_id.id
 
249
                acc_cur = ((line_amount<=0) and order.mode.journal.default_debit_account_id) or line.account_id
 
250
 
 
251
                ctx = context.copy()
 
252
                ctx['res.currency.compute.account'] = acc_cur
 
253
                amount = self.pool.get('res.currency').compute(cr, uid, currency_id, company_currency_id, line_amount, context=ctx)
 
254
 
 
255
                val = {
 
256
                    'name': line.move_line_id and line.move_line_id.name or '/',
 
257
                    'move_id': move_id,
 
258
                    'date': order.date_done,
 
259
                    'ref': line.move_line_id and line.move_line_id.ref or False,
 
260
                    'partner_id': line.partner_id and line.partner_id.id or False,
 
261
                    'account_id': line.account_id.id,
 
262
                    'debit': ((amount>0) and amount) or 0.0,
 
263
                    'credit': ((amount<0) and -amount) or 0.0,
 
264
                    'journal_id': order.mode.journal.id,
 
265
                    'period_id': order.period_id.id,
 
266
                    'currency_id': currency_id,
 
267
                }
 
268
                
 
269
                amount = self.pool.get('res.currency').compute(cr, uid, currency_id, company_currency_id, line_amount, context=ctx)
 
270
                if currency_id <> company_currency_id:
 
271
                    amount_cur = self.pool.get('res.currency').compute(cr, uid, company_currency_id, currency_id, amount, context=ctx)
 
272
                    val['amount_currency'] = -amount_cur
 
273
 
 
274
                if line.account_id and line.account_id.currency_id and line.account_id.currency_id.id <> company_currency_id:
 
275
                    val['currency_id'] = line.account_id.currency_id.id
 
276
                    if company_currency_id == line.account_id.currency_id.id:
 
277
                        amount_cur = line_amount
 
278
                    else:
 
279
                        amount_cur = self.pool.get('res.currency').compute(cr, uid, company_currency_id, line.account_id.currency_id.id, amount, context=ctx)
 
280
                    val['amount_currency'] = amount_cur
 
281
 
 
282
                partner_line_id = self.pool.get('account.move.line').create(cr, uid, val, context, check=False)
 
283
 
 
284
                # Fill the secondary amount/currency
 
285
                # if currency is not the same than the company
 
286
                if currency_id <> company_currency_id:
 
287
                    amount_currency = line_amount
 
288
                    move_currency_id = currency_id
 
289
                else:
 
290
                    amount_currency = False
 
291
                    move_currency_id = False
 
292
 
 
293
                self.pool.get('account.move.line').create(cr, uid, {
 
294
                    'name': line.move_line_id and line.move_line_id.name or '/',
 
295
                    'move_id': move_id,
 
296
                    'date': order.date_done,
 
297
                    'ref': line.move_line_id and line.move_line_id.ref or False,
 
298
                    'partner_id': line.partner_id and line.partner_id.id or False,
 
299
                    'account_id': account_id,
 
300
                    'debit': ((amount < 0) and -amount) or 0.0,
 
301
                    'credit': ((amount > 0) and amount) or 0.0,
 
302
                    'journal_id': order.mode.journal.id,
 
303
                    'period_id': order.period_id.id,
 
304
                    'amount_currency': amount_currency,
 
305
                    'currency_id': move_currency_id,
 
306
                }, context)
 
307
 
 
308
                aml_ids = [x.id for x in self.pool.get('account.move').browse(cr, uid, move_id, context).line_id]
 
309
                for x in self.pool.get('account.move.line').browse(cr, uid, aml_ids, context):
 
310
                    if x.state <> 'valid':
 
311
                        raise osv.except_osv(_('Error !'), _('Account move line "%s" is not valid') % x.name)
 
312
 
 
313
                if line.move_line_id and not line.move_line_id.reconcile_id:
 
314
                    # If payment line has a related move line, we try to reconcile it with the move we just created.
 
315
                    lines_to_reconcile = [
 
316
                        partner_line_id,
 
317
                    ]
 
318
 
 
319
                    # Check if payment line move is already partially reconciled and use those moves in that case.
 
320
                    if line.move_line_id.reconcile_partial_id:
 
321
                        for rline in line.move_line_id.reconcile_partial_id.line_partial_ids:
 
322
                            lines_to_reconcile.append( rline.id )
 
323
                    else:
 
324
                        lines_to_reconcile.append( line.move_line_id.id )
 
325
 
 
326
                    amount = 0.0
 
327
                    for rline in self.pool.get('account.move.line').browse(cr, uid, lines_to_reconcile, context):
 
328
                        amount += rline.debit - rline.credit
 
329
 
 
330
                    currency = self.pool.get('res.users').browse(cr, uid, uid, context).company_id.currency_id
 
331
 
 
332
                    if self.pool.get('res.currency').is_zero(cr, uid, currency, amount):
 
333
                        self.pool.get('account.move.line').reconcile(cr, uid, lines_to_reconcile, 'payment', context=context)
 
334
                    else:
 
335
                        self.pool.get('account.move.line').reconcile_partial(cr, uid, lines_to_reconcile, 'payment', context)
 
336
 
 
337
                if order.mode.journal.entry_posted:
 
338
                    self.pool.get('account.move').write(cr, uid, [move_id], {
 
339
                        'state':'posted',
 
340
                    }, context)
 
341
 
 
342
                self.pool.get('payment.line').write(cr, uid, [line.id], {
 
343
                    'payment_move_id': move_id,
 
344
                }, context)
 
345
 
 
346
        return result
 
347
 
 
348
payment_order()
 
349
 
 
350
 
 
351
class payment_line(osv.osv):
 
352
    _name = 'payment.line'
 
353
    _inherit = 'payment.line'
 
354
 
 
355
    def _auto_init(self, cr, context=None):
 
356
        cr.execute("SELECT column_name FROM information_schema.columns WHERE table_name = 'payment_line' and column_name='type'")
 
357
        if cr.fetchone():
 
358
            update_sign = False
 
359
        else:
 
360
            update_sign = True
 
361
        result = super(payment_line, self)._auto_init(cr, context=context)
 
362
 
 
363
        if update_sign:
 
364
            # Ensure related store value of field 'type' is updated in the database.
 
365
            # Note that by forcing the update here we also ensure everything is done in the same transaction.
 
366
            # Because addons/__init__.py will execute a commit just after creating table fields.
 
367
            result.sort()
 
368
            for item in result:
 
369
                item[1](cr, *item[2])
 
370
            # Change sign of 'receivable' payment lines
 
371
            cr.execute("UPDATE payment_line SET amount_currency = -amount_currency WHERE type='receivable'")
 
372
        return result
 
373
 
 
374
    _columns = {
 
375
        'move_line_id': fields.many2one('account.move.line', 'Entry line', domain="[('reconcile_id','=', False), ('amount_to_pay','<>',0), ('account_id.type','=',parent.type),('payment_type','ilike',parent.payment_type_name or '%')]", help='This Entry Line will be referred for the information of the ordering customer.'),
 
376
        'payment_move_id': fields.many2one('account.move', 'Payment Move', readonly=True, help='Account move that pays this debt.'),
 
377
        'account_id': fields.many2one('account.account', 'Account'),
 
378
        'type': fields.related('order_id','type', type='selection', selection=[('payable','Payable'),('receivable','Receivable')], readonly=True, store=True, string='Type'),
 
379
    }
 
380
 
 
381
    def onchange_move_line(self, cr, uid, ids, move_line_id, payment_type, date_prefered, date_scheduled, currency=False, company_currency=False, context=None):
 
382
        # Adds account.move.line name to the payment line communication
 
383
        res = super(payment_line, self).onchange_move_line(cr, uid, ids, move_line_id, payment_type, date_prefered, date_scheduled, currency, company_currency, context)
 
384
        if move_line_id:
 
385
            line = self.pool.get('account.move.line').browse(cr, uid, move_line_id)
 
386
            if line.name != '/':
 
387
                res['value']['communication'] = res['value']['communication'] + '. ' + line.name
 
388
            res['value']['account_id'] = line.account_id.id
 
389
        return res
 
390
 
 
391
payment_line()
 
392
 
 
393
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: