1
# -*- encoding: utf-8 -*-
2
##############################################################################
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>
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.
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.
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/>.
22
##############################################################################
26
from osv import fields, osv
27
from tools.translate import _
28
import decimal_precision as dp
30
class payment_type(osv.osv):
32
_description= 'Payment type'
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),
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
52
class payment_mode(osv.osv):
53
_inherit = 'payment.mode'
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.'),
61
'require_bank_account': lambda *a: False,
66
class res_partner(osv.osv):
67
_inherit='res.partner'
69
'payment_type_customer': fields.property(
72
relation='payment.type',
73
string ='Customer Payment Type',
76
help="Payment type of the customer"),
77
'payment_type_supplier': fields.property(
80
relation='payment.type',
81
string ='Supplier Payment Type',
84
help="Payment type of the supplier"),
89
class res_partner_bank(osv.osv):
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'])
95
return super(res_partner_bank, self).create(cr, uid, vals, context=context)
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']
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])
107
return super(res_partner_bank, self).write(cr, uid, ids, vals, context=context)
109
_inherit="res.partner.bank"
111
'default_bank' : fields.boolean('Default'),
117
class payment_order(osv.osv):
118
_name = 'payment.order'
119
_inherit = 'payment.order'
121
def _get_type(self, cr, uid, context=None):
124
return context.get('type', 'payable')
126
def _get_reference(self, cr, uid, context=None):
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)
133
def _get_period(self, cr, uid, context=None):
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)
146
def _payment_type_name_get(self, cr, uid, ids, field_name, arg, context=None):
148
for rec in self.browse(cr, uid, ids, context):
149
result[rec.id] = rec.mode and rec.mode.type.name or ""
152
def _name_get(self, cr, uid, ids, field_name, arg, context=None):
154
for rec in self.browse(cr, uid, ids, context):
155
result[rec.id] = rec.reference
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',
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)]}),
178
'reference': _get_reference,
179
'create_account_moves': lambda *a: 'bank-statement',
180
'period_id': _get_period,
183
def cancel_from_done(self, cr, uid, ids, context=None):
187
#Search for account_moves
189
for move in self.browse(cr,uid,ids,context):
191
for line in move.line_ids:
192
if line.payment_move_id:
193
remove += [ line.payment_move_id.id ]
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, {
202
def unlink(self, cr, uid, ids, context=None):
203
pay_orders = self.read(cr, uid, ids, ['state'], context=context)
206
if t['state'] in ('draft', 'cancel'):
207
unlink_ids.append(t['id'])
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)
213
def set_done(self, cr, uid, ids, context=None):
214
result = super(payment_order, self).set_done(cr, uid, ids, context)
216
company_currency_id = self.pool.get('res.users').browse(cr, uid, uid, context).company_id.currency_id.id
218
for order in self.browse(cr, uid, ids, context):
219
if order.create_account_moves != 'direct-payment':
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.
225
move_id = self.pool.get('account.move').create(cr, uid, {
227
'journal_id': order.mode.journal.id,
228
'period_id': order.period_id.id,
231
for line in order.line_ids:
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} )
238
currency_id = order.mode.journal.currency and order.mode.journal.currency.id or company_currency_id
240
if line.type == 'payable':
241
line_amount = line.amount_currency or line.amount
243
line_amount = -line.amount_currency or -line.amount
246
account_id = order.mode.journal.default_credit_account_id.id
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
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)
256
'name': line.move_line_id and line.move_line_id.name or '/',
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,
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
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
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
282
partner_line_id = self.pool.get('account.move.line').create(cr, uid, val, context, check=False)
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
290
amount_currency = False
291
move_currency_id = False
293
self.pool.get('account.move.line').create(cr, uid, {
294
'name': line.move_line_id and line.move_line_id.name or '/',
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,
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)
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 = [
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 )
324
lines_to_reconcile.append( line.move_line_id.id )
327
for rline in self.pool.get('account.move.line').browse(cr, uid, lines_to_reconcile, context):
328
amount += rline.debit - rline.credit
330
currency = self.pool.get('res.users').browse(cr, uid, uid, context).company_id.currency_id
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)
335
self.pool.get('account.move.line').reconcile_partial(cr, uid, lines_to_reconcile, 'payment', context)
337
if order.mode.journal.entry_posted:
338
self.pool.get('account.move').write(cr, uid, [move_id], {
342
self.pool.get('payment.line').write(cr, uid, [line.id], {
343
'payment_move_id': move_id,
351
class payment_line(osv.osv):
352
_name = 'payment.line'
353
_inherit = 'payment.line'
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'")
361
result = super(payment_line, self)._auto_init(cr, context=context)
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.
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'")
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'),
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)
385
line = self.pool.get('account.move.line').browse(cr, uid, move_line_id)
387
res['value']['communication'] = res['value']['communication'] + '. ' + line.name
388
res['value']['account_id'] = line.account_id.id
393
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: