1
# -*- encoding: utf-8 -*-
2
##############################################################################
4
# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
6
# $Id: account.py 1005 2005-07-25 08:41:42Z nicoe $
8
# WARNING: This program as such is intended to be used by professional
9
# programmers who take the whole responsability of assessing all potential
10
# consequences resulting from its eventual inadequacies and bugs
11
# End users who are looking for a ready-to-use solution with commercial
12
# garantees and support are strongly adviced to contract a Free Software
15
# This program is Free Software; you can redistribute it and/or
16
# modify it under the terms of the GNU General Public License
17
# as published by the Free Software Foundation; either version 2
18
# of the License, or (at your option) any later version.
20
# This program is distributed in the hope that it will be useful,
21
# but WITHOUT ANY WARRANTY; without even the implied warranty of
22
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
# GNU General Public License for more details.
25
# You should have received a copy of the GNU General Public License
26
# along with this program; if not, write to the Free Software
27
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29
##############################################################################
33
from osv import fields, osv
36
class account_transfer(osv.osv):
37
_name = "account.transfer"
38
_description = "Money Transfer"
39
def _get_period(self, cr, uid, context):
40
periods = self.pool.get('account.period').find(cr, uid)
46
'name': fields.char('Description', size=64, required=True),
47
'state': fields.selection( (('draft','draft'),('posted','posted')),'State', readonly=True),
48
'partner_id': fields.many2one('res.partner', 'Partner', states={'posted':[('readonly',True)]}),
49
'project_id': fields.many2one('account.analytic.account', 'Analytic Account', states={'posted':[('readonly',True)]}),
51
'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'posted':[('readonly',True)]}),
52
'period_id': fields.many2one('account.period', 'Period', required=True, states={'posted':[('readonly',True)]}),
54
'date': fields.date('Payment Date', required=True, states={'posted':[('readonly',True)]}),
55
'type': fields.selection([
56
('undefined','Undefined'),
57
('in_payment','Incoming Customer Payment'),
58
('out_payment','Outgoing Supplier Payment'),
59
('expense', 'Direct Expense'),
60
('transfer','Money Transfer'),
61
('change','Currency Change'),
62
('refund','Customer Refund'),
63
('sale','Manual Sale'),
64
# ('expense reimburse','Remboursement Depense')
65
], 'Transfer Type', required=True, states={'posted':[('readonly',True)]} ),
66
'reference': fields.char('Reference',size=64),
67
'account_src_id': fields.many2one('account.account', 'Source Account', required=True, states={'posted':[('readonly',True)]}),
68
'account_dest_id': fields.many2one('account.account', 'Destination Account', required=True, states={'posted':[('readonly',True)]}),
69
'amount': fields.float('Amount', digits=(16,2), required=True, states={'posted':[('readonly',True)]}),
70
'change': fields.float('Amount Changed', digits=(16,2), states={'posted':[('readonly',True)]}, readonly=True),
71
'move_id': fields.many2one('account.move', 'Entry', readonly=True),
72
'adjust_amount': fields.float('Adjustement amount', states={'posted':[('readonly',True)]}),
73
'adjust_account_id': fields.many2one('account.account', 'Adjustement Account', states={'posted':[('readonly',True)]}),
74
'invoice_id': fields.many2many('account.invoice','account_transfer_invoice','transfer_id','invoice_id','Invoices', states={'posted':[('readonly',True)]}, help="You can select customer or supplier invoice that are related to this payment. This is optionnal but if you specify the invoices, Tiny ERP will automatically reconcile the entries and mark invoices as paid."),
77
'date': lambda *a: time.strftime('%Y-%m-%d'),
78
'state': lambda *a: 'draft',
79
'period_id' : _get_period,
82
def unlink(self, cr, uid, ids):
83
transfers = self.read(cr, uid, ids, ['state'])
86
if t['state']=='draft':
87
unlink_ids.append(t['id'])
89
raise osv.except_osv('Invalid action !', 'Cannot delete transfer(s) which are already posted !')
90
osv.osv.unlink(self, cr, uid, unlink_ids)
93
def _onchange_type_domain(self,cr,uid,ids,type):
95
'in_payment': ('receivable','cash'),
96
'out_payment': ('cash','payable'),
97
'expense': ('cash','expense'),
98
'transfer': ('cash','cash'),
99
'change': ('cash','cash'),
100
'refund': ('receivable','income'),
101
'sale': ('income','cash'),
103
d = {'account_src_id': [('type','<>','view')], 'account_dest_id': [('type','<>','view')]}
104
if type_acc.has_key(type):
105
d['account_src_id'].append(('type','=',type_acc[type][0]))
106
d['account_dest_id'].append(('type','=',type_acc[type][1]))
109
def onchange_type(self, cr, uid, ids, type):
110
ro = {'change':type!='change',
111
'adjust_amount': type not in ('in_payment','out_payment'),
112
'adjust_account_id': type not in ('in_payment','out_payment')}
113
d=self._onchange_type_domain(cr,uid,ids,type)
114
return {'domain': d, 'value': {'account_src_id': False, 'account_dest_id': False}, 'readonly':ro}
116
def _onchange_account_domain(self,cr,uid,ids,type, account_src, account_dest):
117
d=self._onchange_type_domain(cr,uid,ids,type)
119
if account_src and not account_dest:
120
cr.execute("SELECT currency_id FROM account_account WHERE id=%d",(account_src,))
121
d['account_dest_id'].append(('currency_id','=',cr.fetchall()[0][0]))
122
if account_dest and not account_src:
123
cr.execute("SELECT currency_id FROM account_account WHERE id=%d",(account_dest,))
124
d['account_src_id'].append(('currency_id','=',cr.fetchall()[0][0]))
127
def onchange_account(self, cr, uid, ids, type, account_src, account_dest):
128
d=self._onchange_account_domain(cr,uid,ids,type,account_src,account_dest)
131
def onchange_partner(self, cr, uid, ids, type, partner_id):
135
if type=='in_payment':
136
a = self.pool.get('res.partner').browse(cr, uid, partner_id).property_account_receivable[0]
137
value['account_src_id'] = a
138
value['account_dest_id'] = False
140
# compute the amount this partner owe us (the sum of all move lines which have not been matched)
141
cr.execute("SELECT COALESCE(SUM(debit-credit),0) from account_move_line where account_id=%d and partner_id=%d and reconcile_id is null and state<>'draft'", (a, partner_id))
142
value['amount'] = cr.fetchone()[0]
144
d = self._onchange_account_domain(cr,uid,ids,type, value['account_src_id'], value['account_dest_id'])
145
return {'domain': d, 'value': value}
147
elif type=='out_payment':
148
a = self.pool.get('res.partner').browse(cr, uid, partner_id).property_account_payable[0]
149
value['account_src_id'] = False
150
value['account_dest_id'] = a
152
# compute the amount we owe this partner (the sum of all move lines which have not been matched)
153
cr.execute("SELECT COALESCE(SUM(debit-credit),0) from account_move_line where account_id=%d and partner_id=%d and reconcile_id is null and state<>'draft'", (a,partner_id))
154
value['amount'] = -cr.fetchone()[0]
157
d = self._onchange_account_domain(cr,uid,ids,type, value['account_src_id'], value['account_dest_id'])
158
return {'domain': d, 'value': value}
160
return self.onchange_type(cr,uid,ids, type)
162
def pay_validate(self, cr, uid, ids, *args):
163
transfers = self.read(cr, uid, ids)
164
for pay in transfers:
165
if pay['state']!='draft':
169
partner_id = (pay['partner_id'] or None) and pay['partner_id'][0]
170
project_id = (pay['project_id'] or None) and pay['project_id'][0]
172
# create two move lines (one for the source account and one for the destination account)
175
'journal_id': pay['journal_id'][0],
176
'period_id': pay['period_id'][0],
178
#CHECKME: why don't these two lines have period_id and journal_id defined?
181
'credit':pay['amount']<0 and -pay['amount'],
182
'debit':pay['amount']>0 and pay['amount'],
183
'account_id': pay['account_dest_id'][0],
184
'partner_id': partner_id
188
'debit':pay['amount']<0 and -pay['amount'],
189
'credit':pay['amount']>0 and pay['amount'],
190
'account_id': pay['account_src_id'][0],
191
'partner_id': partner_id
195
# possibly create two more lines if there is an adjustment
196
if False and pay['adjust_amount']:
197
if pay['adjust_account_id']:
199
la.update({'name': name+' adjustment', 'credit':pay['adjust_amount']<0 and -pay['adjust_amount'], 'debit':pay['adjust_amount']>0 and pay['adjust_amount'], 'account_id': pay['adjust_account_id'][0],})
202
la.update({'name': name+' adjustment', 'debit':pay['adjust_amount']<0 and -pay['adjust_amount'], 'credit':pay['adjust_amount']>0 and pay['adjust_amount'], 'account_id': pay['account_src_id'][0],})
205
raise Exception('No Adjust Account !', 'missing adjust account')
207
# create the new move and its 2 (or 4) lines
209
move['line_id'] = [(0, 0, l) for l in line_id]
210
move_id = self.pool.get('account.move').create(cr, uid, move)
212
# get account_id depending on the type of transfer
213
tmp = {'in_payment':pay['account_src_id'][0], 'out_payment':pay['account_dest_id'][0]}
214
account_id = tmp.get(pay['type'], None)
216
if account_id and len(pay['invoice_id']):
217
# get the ids of all moves lines which 1) use account_id 2) are not matched 3) correspond to the selected invoices
218
inv_set = ",".join(map(str, pay["invoice_id"]))
219
query = "SELECT id FROM account_move_line "+\
220
"WHERE account_id=%d "+\
221
"AND reconcile_id IS NULL "+\
222
"AND move_id IN (SELECT move_id FROM account_invoice WHERE id IN ("+inv_set+"))"
223
cr.execute(query, (account_id,))
224
l_ids = [i[0] for i in cr.fetchall()]
226
print 'Error: no unmatched move line found for account %d and invoices %s while confirming transfer %d' % (account_id, pay['invoice_id'], pay['id'])
229
# compute the sum of those lines
230
l_set = ",".join(map(str, l_ids))
231
cr.execute("SELECT SUM(debit-credit) FROM account_move_line WHERE id IN (" + l_set + ")")
234
# if that amount = the amount paid, match those lines (from the selected invoices) with the current transfer
235
types = {'out_payment': -1, 'in_payment': 1}
236
sign = types.get(pay['type'], 1)
237
if (s-(pay['adjust_amount'] or 0.0))==sign*pay['amount']:
238
# get the id of the move_line for the current transfer
239
cr.execute("SELECT id FROM account_move_line WHERE account_id=%d AND move_id=%d", (account_id,move_id))
240
l_ids.append(cr.fetchall()[0][0])
241
self.pool.get('account.move.line').reconcile(cr, uid, l_ids, writeoff_period_id=pay['period_id'][0], writeoff_journal_id=pay['journal_id'][0], writeoff_acc_id=pay['adjust_account_id'] and pay['adjust_account_id'][0])
243
raise osv.except_osv('Warning !', 'Could not confirm payment because its amount (%.2f) is different from the selected invoice(s) amount (%.2f) !' % (pay['amount']+pay['adjust_amount'],s))
245
elif account_id and partner_id:
246
# compute the sum of all move lines for this account and partner which were not matched
247
cr.execute("SELECT SUM(debit-credit) FROM account_move_line WHERE account_id=%d AND partner_id=%d AND reconcile_id is null", (account_id, partner_id))
250
# if that amount is 0, we match those move lines together
252
cr.execute("select id from account_move_line where account_id=%d and partner_id=%d and reconcile_id is null", (account_id, partner_id))
253
ids2 = [id for (id,) in cr.fetchall()]
255
self.pool.get('account.move.line').reconcile(cr, uid, ids2, writeoff_period_id=pay['period_id'][0], writeoff_journal_id=pay['journal_id'][0], writeoff_acc_id=pay['adjust_account_id'] and pay['adjust_account_id'][0])
257
# change transfer state and assign it its move
258
self.write(cr, uid, [pay['id']], {'state':'posted', 'move_id': move_id})
261
def pay_cancel(self, cr, uid, ids, *args):
262
pays = self.read(cr, uid, ids, ['move_id'])
263
self.pool.get('account.move').unlink(cr, uid, [ x['move_id'][0] for x in pays if x['move_id']] )
264
self.write(cr, uid, ids, {'state':'draft'})