~technofluid-team/openobject-addons/technofluid_multiple_installations

« back to all changes in this revision

Viewing changes to account/transfer.py

  • Committer: pinky
  • Date: 2006-12-07 13:41:40 UTC
  • Revision ID: pinky-dedd7f8a42bd4557112a0513082691b8590ad6cc
New trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- encoding: utf-8 -*-
 
2
##############################################################################
 
3
#
 
4
# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
 
5
#
 
6
# $Id: account.py 1005 2005-07-25 08:41:42Z nicoe $
 
7
#
 
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
 
13
# Service Company
 
14
#
 
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.
 
19
#
 
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.
 
24
#
 
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.
 
28
#
 
29
##############################################################################
 
30
 
 
31
import time
 
32
import netsvc
 
33
from osv import fields, osv
 
34
import ir
 
35
 
 
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)
 
41
                if periods:
 
42
                        return periods[0]
 
43
                else:
 
44
                        return False
 
45
        _columns = {
 
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)]}),
 
50
 
 
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)]}),
 
53
 
 
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."),
 
75
        }
 
76
        _defaults = {
 
77
                'date': lambda *a: time.strftime('%Y-%m-%d'),
 
78
                'state': lambda *a: 'draft',
 
79
                'period_id' : _get_period,
 
80
        }
 
81
 
 
82
        def unlink(self, cr, uid, ids):
 
83
                transfers = self.read(cr, uid, ids, ['state'])
 
84
                unlink_ids = []
 
85
                for t in transfers:
 
86
                        if t['state']=='draft':
 
87
                                unlink_ids.append(t['id'])
 
88
                        else:
 
89
                                raise osv.except_osv('Invalid action !', 'Cannot delete transfer(s) which are already posted !')
 
90
                osv.osv.unlink(self, cr, uid, unlink_ids)
 
91
                return True
 
92
 
 
93
        def _onchange_type_domain(self,cr,uid,ids,type):
 
94
                type_acc={
 
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'),
 
102
                }
 
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]))
 
107
                return d
 
108
 
 
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}
 
115
 
 
116
        def _onchange_account_domain(self,cr,uid,ids,type, account_src, account_dest):
 
117
                d=self._onchange_type_domain(cr,uid,ids,type)
 
118
                if type != 'change':
 
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]))
 
125
                return d
 
126
 
 
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)
 
129
                return {'domain': d}
 
130
 
 
131
        def onchange_partner(self, cr, uid, ids, type, partner_id):
 
132
                if partner_id:
 
133
                        value={}
 
134
                        
 
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
 
139
                                
 
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]
 
143
                                
 
144
                                d = self._onchange_account_domain(cr,uid,ids,type, value['account_src_id'], value['account_dest_id'])
 
145
                                return {'domain': d, 'value': value}
 
146
                                
 
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
 
151
                                
 
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]
 
155
 
 
156
                                # get the new domain
 
157
                                d = self._onchange_account_domain(cr,uid,ids,type, value['account_src_id'], value['account_dest_id'])
 
158
                                return {'domain': d, 'value': value}
 
159
                                
 
160
                return self.onchange_type(cr,uid,ids, type)
 
161
 
 
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':
 
166
                                continue
 
167
                        name = pay['name']
 
168
 
 
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]
 
171
 
 
172
                        # create two move lines (one for the source account and one for the destination account)
 
173
                        l = {
 
174
                                'name':name,
 
175
                                'journal_id': pay['journal_id'][0],
 
176
                                'period_id': pay['period_id'][0],
 
177
                        }
 
178
#CHECKME: why don't these two lines have period_id and journal_id defined?
 
179
                        l2 = {
 
180
                                'name':name,
 
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
 
185
                        }
 
186
                        l1 = {
 
187
                                'name':name,
 
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
 
192
                        }
 
193
                        line_id = [l1, l2]
 
194
 
 
195
                        # possibly create two more lines if there is an adjustment
 
196
                        if False and pay['adjust_amount']:
 
197
                                if pay['adjust_account_id']:
 
198
                                        la = l.copy()
 
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],})
 
200
                                        line_id.append(la)
 
201
                                        la = l.copy()
 
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],})
 
203
                                        line_id.append(la)
 
204
                                else:
 
205
                                        raise Exception('No Adjust Account !', 'missing adjust account')
 
206
                                        
 
207
                        # create the new move and its 2 (or 4) lines
 
208
                        move = l.copy()
 
209
                        move['line_id'] = [(0, 0, l) for l in line_id]
 
210
                        move_id = self.pool.get('account.move').create(cr, uid, move)
 
211
                        
 
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)
 
215
                        
 
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()]
 
225
                                if not l_ids:
 
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'])
 
227
                                        continue
 
228
                                
 
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 + ")")
 
232
                                s = cr.fetchone()[0]
 
233
                                
 
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])
 
242
                                else:
 
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))
 
244
                                        
 
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))
 
248
                                s = cr.fetchone()[0]
 
249
                                
 
250
                                # if that amount is 0, we match those move lines together
 
251
                                if s==0.0:
 
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()]
 
254
                                        if len(ids2):
 
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])
 
256
 
 
257
                        # change transfer state and assign it its move
 
258
                        self.write(cr, uid, [pay['id']], {'state':'posted', 'move_id': move_id})
 
259
                return True
 
260
 
 
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'})
 
265
                return True
 
266
account_transfer()