~technofluid-team/openobject-addons/technofluid_multiple_installations

« back to all changes in this revision

Viewing changes to account/account.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
import time
 
31
import netsvc
 
32
from osv import fields, osv
 
33
 
 
34
from tools.misc import currency
 
35
 
 
36
import mx.DateTime
 
37
from mx.DateTime import RelativeDateTime, now, DateTime, localtime
 
38
 
 
39
class account_payment_term(osv.osv):
 
40
        _name = "account.payment.term"
 
41
        _description = "Payment Term"
 
42
        _columns = {
 
43
                'name': fields.char('Payment Term', size=32),
 
44
                'active': fields.boolean('Active'),
 
45
                'note': fields.text('Description'),
 
46
                'line_ids': fields.one2many('account.payment.term.line', 'payment_id', 'Terms')
 
47
        }
 
48
        _defaults = {
 
49
                'active': lambda *a: 1,
 
50
        }
 
51
        _order = "name"
 
52
        def compute(self, cr, uid, id, value, date_ref=False, context={}):
 
53
                if not date_ref:
 
54
                        date_ref = now().strftime('%Y-%m-%d')
 
55
                pt = self.browse(cr, uid, id, context)
 
56
                amount = value
 
57
                result = []
 
58
                for line in pt.line_ids:
 
59
                        if line.value=='fixed':
 
60
                                amt = line.value_amount
 
61
                        elif line.value=='procent':
 
62
                                amt = round(amount * line.value_amount,2)
 
63
                        elif line.value=='balance':
 
64
                                amt = amount
 
65
                        if amt:
 
66
                                next_date = mx.DateTime.strptime(date_ref, '%Y-%m-%d') + RelativeDateTime(days=line.days)
 
67
                                if line.condition == 'end of month':
 
68
                                        next_date += RelativeDateTime(day=-1)
 
69
                                result.append( (next_date.strftime('%Y-%m-%d'), amt) )
 
70
                                amount -= amt
 
71
                return result
 
72
account_payment_term()
 
73
 
 
74
class account_payment_term_line(osv.osv):
 
75
        _name = "account.payment.term.line"
 
76
        _description = "Payment Term Line"
 
77
        _columns = {
 
78
                'name': fields.char('Line Name', size=32,required=True),
 
79
                'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the payment term lines from the lowest sequences to the higher ones"),
 
80
                'value': fields.selection([('procent','Procent'),('balance','Balance'),('fixed','Fixed Amount')], 'Value',required=True),
 
81
                'value_amount': fields.float('Value Amount'),
 
82
                'days': fields.integer('Number of Days',required=True),
 
83
                'condition': fields.selection([('net days','Net Days'),('end of month','End of Month')], 'Condition', required=True, help="The payment delay condition id a number of days expressed in 2 ways: net days or end of the month. The 'net days' condition implies that the paiment arrive after 'Number of Days' calendar days. The 'end of the month' condition requires that the paiement arrives before the end of the month that is that is after 'Number of Days' calendar days."),
 
84
                'payment_id': fields.many2one('account.payment.term','Payment Term', required=True, select=True),
 
85
        }
 
86
        _defaults = {
 
87
                'value': lambda *a: 'balance',
 
88
                'sequence': lambda *a: 5,
 
89
                'condition': lambda *a: 'net days',
 
90
        }
 
91
        _order = "sequence"
 
92
account_payment_term_line()
 
93
 
 
94
 
 
95
class account_account_type(osv.osv):
 
96
        _name = "account.account.type"
 
97
        _description = "Account Type"
 
98
        _columns = {
 
99
                'name': fields.char('Acc. Type Name', size=64, required=True, translate=True),
 
100
                'code': fields.char('Code', size=32, required=True),
 
101
                'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of account types."),
 
102
                'code_from': fields.char('Code From', size=10, help="Gives the range of account code available for this type of account. These fields are given for information and are not used in any constraint."),
 
103
                'code_to': fields.char('Code To', size=10, help="Gives the range of account code available for this type of account. These fields are just given for information and are not used in any constraint."),
 
104
                'partner_account': fields.boolean('Partner account'),
 
105
                'close_method': fields.selection([('none','None'), ('balance','Balance'), ('detail','Detail'),('unreconciled','Unreconciled')], 'Deferral Method', required=True),
 
106
        }
 
107
        _defaults = {
 
108
                'close_method': lambda *a: 'none',
 
109
                'sequence': lambda *a: 5,
 
110
        }
 
111
        _order = "sequence"
 
112
account_account_type()
 
113
 
 
114
def _code_get(self, cr, uid, context={}):
 
115
        acc_type_obj = self.pool.get('account.account.type')
 
116
        ids = acc_type_obj.search(cr, uid, [])
 
117
        res = acc_type_obj.read(cr, uid, ids, ['code', 'name'], context)
 
118
        return [(r['code'], r['name']) for r in res]
 
119
 
 
120
#----------------------------------------------------------
 
121
# Accounts
 
122
#----------------------------------------------------------
 
123
class account_account(osv.osv):
 
124
        _order = "code"
 
125
        _name = "account.account"
 
126
        _description = "Account"
 
127
 
 
128
        def _credit(self, cr, uid, ids, field_name, arg, context={}):
 
129
                acc_set = ",".join(map(str, ids))
 
130
                cr.execute("SELECT a.id, COALESCE(SUM(l.credit*a.sign),0) FROM account_account a LEFT JOIN account_move_line l ON (a.id=l.account_id) WHERE a.type!='view' AND a.id IN (%s) AND l.active AND l.state<>'draft' GROUP BY a.id" % acc_set)
 
131
                res2 = cr.fetchall()
 
132
                res = {}
 
133
                for id in ids:
 
134
                        res[id] = 0.0
 
135
                for account_id, sum in res2:
 
136
                        res[account_id] += sum
 
137
                return res
 
138
 
 
139
        def _debit(self, cr, uid, ids, field_name, arg, context={}):
 
140
                acc_set = ",".join(map(str, ids))
 
141
                cr.execute("SELECT a.id, COALESCE(SUM(l.debit*a.sign),0) FROM account_account a LEFT JOIN account_move_line l ON (a.id=l.account_id) WHERE a.type!='view' AND a.id IN (%s) and l.active AND l.state<>'draft' GROUP BY a.id" % acc_set)
 
142
                res2 = cr.fetchall()
 
143
                res = {}
 
144
                for id in ids:
 
145
                        res[id] = 0.0
 
146
                for account_id, sum in res2:
 
147
                        res[account_id] += sum
 
148
                return res
 
149
 
 
150
        def _balance(self, cr, uid, ids, field_name, arg, context={}):
 
151
                ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)])
 
152
                acc_set = ",".join(map(str, ids2))
 
153
                cr.execute("SELECT a.id, COALESCE(SUM((l.debit-l.credit)),0) FROM account_account a LEFT JOIN account_move_line l ON (a.id=l.account_id) WHERE a.id IN (%s) and l.active AND l.state<>'draft' GROUP BY a.id" % acc_set)
 
154
                res = {}
 
155
                for account_id, sum in cr.fetchall():
 
156
                        res[account_id] = round(sum,2)
 
157
                for id in ids:
 
158
                        ids3 = self.search(cr, uid, [('parent_id', 'child_of', [id])])
 
159
                        for idx in ids3:
 
160
                                if idx <> id:
 
161
                                        res.setdefault(id, 0.0)
 
162
                                        res[id] += res.get(idx, 0.0)
 
163
                for id in ids:
 
164
                        res[id] = round(res.get(id,0.0), 2)
 
165
                return res
 
166
 
 
167
        _columns = {
 
168
                'name': fields.char('Name', size=128, required=True, translate=True, select=True),
 
169
                'sign': fields.selection([(-1, 'Negative'), (1, 'Positive')], 'Sign', required=True, help='Allows to change the displayed amount of the balance to see positive results instead of negative ones in expenses accounts'),
 
170
                'currency_id': fields.many2one('res.currency', 'Currency', required=True),
 
171
                'code': fields.char('Code', size=64),
 
172
                'type': fields.selection(_code_get, 'Account Type', required=True),
 
173
                'parent_id': fields.many2many('account.account', 'account_account_rel', 'child_id', 'parent_id', 'Parents'),
 
174
                'child_id': fields.many2many('account.account', 'account_account_rel', 'parent_id', 'child_id', 'Children'),
 
175
                'balance': fields.function(_balance, digits=(16,2), method=True, string='Balance'),
 
176
                'credit': fields.function(_credit, digits=(16,2), method=True, string='Credit'),
 
177
                'debit': fields.function(_debit, digits=(16,2), method=True, string='Debit'),
 
178
                'reconcile': fields.boolean('Reconcile', help="Check this account if the user can make a reconciliation of the entries in this account."),
 
179
                'shortcut': fields.char('Shortcut', size=12),
 
180
                'close_method': fields.selection([('none','None'), ('balance','Balance'), ('detail','Detail'),('unreconciled','Unreconciled')], 'Deferral Method', required=True, help="Tell Tiny ERP how to process the entries of this account when you close a fiscal year. None removes all entries to start with an empty account for the new fiscal year. Balance creates only one entry to keep the balance for the new fiscal year. Detail keeps the detail of all entries of the preceeding years. Unreconciled keeps the detail of unreconciled entries only."),
 
181
                'tax_ids': fields.many2many('account.tax', 'account_account_tax_default_rel', 'account_id','tax_id', 'Default Taxes'),
 
182
                'company_id': fields.many2one('res.company', 'Company'),
 
183
 
 
184
                'active': fields.boolean('Active'),
 
185
                'note': fields.text('Note')
 
186
        }
 
187
        _defaults = {
 
188
                'sign': lambda *a: 1,
 
189
                'type': lambda *a: 'view',
 
190
                'active': lambda *a: True,
 
191
                'reconcile': lambda *a: False,
 
192
                'close_method': lambda *a: 'balance',
 
193
        }
 
194
        def _check_recursion(self, cr, uid, ids):
 
195
                level = 100
 
196
                while len(ids):
 
197
                        cr.execute('select distinct parent_id from account_account_rel where child_id in ('+','.join(map(str,ids))+')')
 
198
                        ids = filter(None, map(lambda x:x[0], cr.fetchall()))
 
199
                        if not level:
 
200
                                return False
 
201
                        level -= 1
 
202
                return True
 
203
 
 
204
        _constraints = [
 
205
                (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
 
206
        ]
 
207
        def init(self, cr):
 
208
                cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname='account_tax'")
 
209
                if len(cr.dictfetchall())==0:
 
210
                        cr.execute("CREATE TABLE account_tax (id SERIAL NOT NULL, perm_id INTEGER, PRIMARY KEY(id))");
 
211
                        cr.commit()
 
212
 
 
213
        def name_search(self, cr, user, name, args=[], operator='ilike', context={}):
 
214
                ids = []
 
215
                if name:
 
216
                        ids = self.search(cr, user, [('code','=like',name+"%")]+ args)
 
217
                        if not ids:
 
218
                                ids = self.search(cr, user, [('shortcut','=',name)]+ args)
 
219
                        if not ids:
 
220
                                ids = self.search(cr, user, [('name',operator,name)]+ args)
 
221
                else:
 
222
                        ids = self.search(cr, user, args)
 
223
                return self.name_get(cr, user, ids, context=context)
 
224
 
 
225
        def name_get(self, cr, uid, ids, context={}):
 
226
                if not len(ids):
 
227
                        return []
 
228
                reads = self.read(cr, uid, ids, ['name','code'], context)
 
229
                res = []
 
230
                for record in reads:
 
231
                        name = record['name']
 
232
                        if record['code']:
 
233
                                name = record['code']+' - '+name
 
234
                        res.append((record['id'],name ))
 
235
                return res
 
236
account_account()
 
237
 
 
238
class account_journal_view(osv.osv):
 
239
        _name = "account.journal.view"
 
240
        _description = "Journal View"
 
241
        _columns = {
 
242
                'name': fields.char('Journal View', size=64, required=True),
 
243
                'columns_id': fields.one2many('account.journal.column', 'view_id', 'Columns')
 
244
        }
 
245
        _order = "name"
 
246
account_journal_view()
 
247
 
 
248
 
 
249
class account_journal_column(osv.osv):
 
250
        def _col_get(self, cr, user, context={}):
 
251
                result = []
 
252
                cols = self.pool.get('account.move.line')._columns
 
253
                for col in cols:
 
254
                        result.append( (col, cols[col].string) )
 
255
                result.sort()
 
256
                return result
 
257
        _name = "account.journal.column"
 
258
        _description = "Journal Column"
 
259
        _columns = {
 
260
                'name': fields.char('Column Name', size=64, required=True),
 
261
                'field': fields.selection(_col_get, 'Field Name', method=True, required=True, size=32),
 
262
                'view_id': fields.many2one('account.journal.view', 'Journal View', select=True),
 
263
                'sequence': fields.integer('Sequence'),
 
264
                'required': fields.boolean('Required'),
 
265
                'readonly': fields.boolean('Readonly'),
 
266
        }
 
267
        _order = "sequence"
 
268
account_journal_column()
 
269
 
 
270
 
 
271
class account_journal(osv.osv):
 
272
        _name = "account.journal"
 
273
        _description = "Journal"
 
274
        _columns = {
 
275
                'name': fields.char('Journal Name', size=64, required=True),
 
276
                'code': fields.char('Code', size=9),
 
277
                'type': fields.selection([('sale','Sale'), ('purchase','Purchase'), ('cash','Cash'), ('general','General'), ('situation','Situation')], 'Type', size=32, required=True),
 
278
                'type_control_ids': fields.many2many('account.account.type', 'account_journal_type_rel', 'journal_id','type_id', 'Type Controls', domain=[('code','<>','view')]),
 
279
                'active': fields.boolean('Active'),
 
280
                'view_id': fields.many2one('account.journal.view', 'View', required=True, help="Gives the view used when writing or browsing entries in this journal. The view tell Tiny ERP which fields should be visible, required or readonly and in which order. You can create your own view for a faster encoding in each journal."),
 
281
                'default_credit_account_id': fields.many2one('account.account', 'Default Credit Account'),
 
282
                'default_debit_account_id': fields.many2one('account.account', 'Default Debit Account'),
 
283
                'centralisation': fields.boolean('Centralisation', help="Use a centralisation journal if you want that each entry doesn't create a counterpart but share the same counterpart for each entry of this journal."),
 
284
                'update_posted': fields.boolean('Allow Cancelling Entries'),
 
285
                'sequence_id': fields.many2one('ir.sequence', 'Entry Sequence', help="The sequence gives the display order for a list of journals"),
 
286
                'user_id': fields.many2one('res.users', 'User', help="The responsible user of this journal"),
 
287
                'groups_id': fields.many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', 'Groups'),
 
288
        }
 
289
        _defaults = {
 
290
                'active': lambda *a: 1,
 
291
                'user_id': lambda self,cr,uid,context: uid,
 
292
        }
 
293
        def create(self, cr, uid, vals, context={}):
 
294
                journal_id = super(osv.osv, self).create(cr, uid, vals, context)
 
295
#               journal_name = self.browse(cr, uid, [journal_id])[0].code
 
296
#               periods = self.pool.get('account.period')
 
297
#               ids = periods.search(cr, uid, [('date_stop','>=',time.strftime('%Y-%m-%d'))])
 
298
#               for period in periods.browse(cr, uid, ids):
 
299
#                       self.pool.get('account.journal.period').create(cr, uid, {
 
300
#                               'name': (journal_name or '')+':'+(period.code or ''),
 
301
#                               'journal_id': journal_id,
 
302
#                               'period_id': period.id
 
303
#                       })
 
304
                return journal_id
 
305
        def name_search(self, cr, user, name, args=[], operator='ilike', context={}):
 
306
                ids = []
 
307
                if name:
 
308
                        ids = self.search(cr, user, [('code','ilike',name)]+ args)
 
309
                if not ids:
 
310
                        ids = self.search(cr, user, [('name',operator,name)]+ args)
 
311
                return self.name_get(cr, user, ids, context=context)
 
312
account_journal()
 
313
 
 
314
class account_bank(osv.osv):
 
315
        _name = "account.bank"
 
316
        _description = "Banks"
 
317
        _columns = {
 
318
                'name': fields.char('Bank Name', size=64, required=True),
 
319
                'code': fields.char('Code', size=6),
 
320
                'partner_id': fields.many2one('res.partner', 'Bank Partner', help="The link to the partner that represent this bank. The partner contains all information about contacts, phones, applied taxes, eso."),
 
321
                'bank_account_ids': fields.one2many('account.bank.account', 'bank_id', 'Bank Accounts'),
 
322
                'note': fields.text('Notes'),
 
323
        }
 
324
        _order = "code"
 
325
account_bank()
 
326
 
 
327
class account_bank_account(osv.osv):
 
328
        _name = "account.bank.account"
 
329
        _description = "Bank Accounts"
 
330
        _columns = {
 
331
                'name': fields.char('Bank Account', size=64, required=True),
 
332
                'code': fields.char('Code', size=6),
 
333
                'iban': fields.char('IBAN', size=24),
 
334
                'swift': fields.char('Swift Code', size=24),
 
335
                'currency_id': fields.many2one('res.currency', 'Currency', required=True),
 
336
                'journal_id': fields.many2one('account.journal', 'Journal', required=True),
 
337
                'account_id': fields.many2one('account.account', 'General Account', required=True, select=True),
 
338
                'bank_id': fields.many2one('account.bank', 'Bank'),
 
339
        }
 
340
        _order = "code"
 
341
account_bank_account()
 
342
 
 
343
 
 
344
class account_fiscalyear(osv.osv):
 
345
        _name = "account.fiscalyear"
 
346
        _description = "Fiscal Year"
 
347
        _columns = {
 
348
                'name': fields.char('Fiscal Year', size=64, required=True),
 
349
                'code': fields.char('Code', size=6, required=True),
 
350
                'company_id': fields.many2one('res.company', 'Company'),
 
351
                'date_start': fields.date('Start date', required=True),
 
352
                'date_stop': fields.date('End date', required=True),
 
353
                'period_ids': fields.one2many('account.period', 'fiscalyear_id', 'Periods'),
 
354
                'state': fields.selection([('draft','Draft'), ('done','Done')], 'State', redonly=True)
 
355
        }
 
356
        _defaults = {
 
357
                'state': lambda *a: 'draft',
 
358
        }
 
359
        _order = "code"
 
360
        def create_period3(self,cr, uid, ids, context={}):
 
361
                return self.create_period(cr, uid, ids, context, 3)
 
362
 
 
363
        def create_period(self,cr, uid, ids, context={}, interval=1):
 
364
                for fy in self.browse(cr, uid, ids, context):
 
365
                        dt = fy.date_start
 
366
                        ds = mx.DateTime.strptime(fy.date_start, '%Y-%m-%d')
 
367
                        while ds.strftime('%Y-%m-%d')<fy.date_stop:
 
368
                                de = ds + RelativeDateTime(months=interval, days=-1)
 
369
                                self.pool.get('account.period').create(cr, uid, {
 
370
                                        'name': ds.strftime('%d/%m') + ' - '+de.strftime('%d/%m'),
 
371
                                        'code': ds.strftime('%d/%m') + '-'+de.strftime('%d/%m'),
 
372
                                        'date_start': ds.strftime('%Y-%m-%d'),
 
373
                                        'date_stop': de.strftime('%Y-%m-%d'),
 
374
                                        'fiscalyear_id': fy.id,
 
375
                                })
 
376
                                ds = ds + RelativeDateTime(months=interval)
 
377
                return True
 
378
account_fiscalyear()
 
379
 
 
380
class account_period(osv.osv):
 
381
        _name = "account.period"
 
382
        _description = "Account period"
 
383
        _columns = {
 
384
                'name': fields.char('Period Name', size=64, required=True),
 
385
                'code': fields.char('Code', size=12),
 
386
                'date_start': fields.date('Start of period', required=True, states={'done':[('readonly',True)]}),
 
387
                'date_stop': fields.date('End of period', required=True, states={'done':[('readonly',True)]}),
 
388
                'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year', required=True, states={'done':[('readonly',True)]}, select=True),
 
389
                'state': fields.selection([('draft','Draft'), ('done','Done')], 'State', readonly=True)
 
390
        }
 
391
        _defaults = {
 
392
                'state': lambda *a: 'draft',
 
393
        }
 
394
        _order = "date_start"
 
395
 
 
396
        def find(self, cr, uid, dt=None, context={}):
 
397
                if not dt:
 
398
                        dt = time.strftime('%Y-%m-%d')
 
399
#CHECKME: shouldn't we check the state of the period?
 
400
                ids = self.search(cr, uid, [('date_start','<=',dt),('date_stop','>=',dt)])
 
401
                if not ids:
 
402
                        raise osv.except_osv('Error !', 'No period defined for this date !\nPlease create a fiscal year.')
 
403
                return ids
 
404
account_period()
 
405
 
 
406
class account_journal_period(osv.osv):
 
407
        _name = "account.journal.period"
 
408
        _description = "Journal - Period"
 
409
        def _icon_get(self, cr, uid, ids, field_name, arg=None, context={}):
 
410
                result = {}.fromkeys(ids, 'STOCK_NEW')
 
411
                for r in self.read(cr, uid, ids, ['state']):
 
412
                        result[r['id']] = {
 
413
                                'draft': 'STOCK_NEW',
 
414
                                'printed': 'STOCK_PRINT_PREVIEW',
 
415
                                'done': 'STOCK_DIALOG_AUTHENTICATION',
 
416
                        }.get(r['state'], 'STOCK_NEW')
 
417
                return result
 
418
        _columns = {
 
419
                'name': fields.char('Journal-Period Name', size=64, required=True),
 
420
                'journal_id': fields.many2one('account.journal', 'Journal', required=True, ondelete="cascade"),
 
421
                'period_id': fields.many2one('account.period', 'Period', required=True, ondelete="cascade"),
 
422
                'icon': fields.function(_icon_get, method=True, string='Icon'),
 
423
                'active': fields.boolean('Active', required=True),
 
424
                'state': fields.selection([('draft','Draft'), ('printed','Printed'), ('done','Done')], 'State', required=True, readonly=True)
 
425
        }
 
426
        def _check(self, cr, uid, ids, context={}):
 
427
                for obj in self.browse(cr, uid, ids, context):
 
428
                        cr.execute('select * from account_move_line where journal_id=%d and period_id=%d limit 1', (obj.journal_id.id, obj.period_id.id))
 
429
                        res = cr.fetchall()
 
430
                        if res:
 
431
                                raise osv.except_osv('Error !', 'You can not modify/delete a journal with entries for this period !')
 
432
                return True
 
433
 
 
434
        def write(self, cr, uid, ids, vals, context={}):
 
435
                self._check(cr, uid, ids, context)
 
436
                return super(account_journal_period, self).write(cr, uid, ids, vals, context)
 
437
 
 
438
        def unlink(self, cr, uid, ids, context={}):
 
439
                self._check(cr, uid, ids, context)
 
440
                return super(account_journal_period, self).unlink(cr, uid, ids, context)
 
441
 
 
442
        _defaults = {
 
443
                'state': lambda *a: 'draft',
 
444
                'active': lambda *a: True,
 
445
        }
 
446
        _order = "period_id"
 
447
account_journal_period()
 
448
 
 
449
#----------------------------------------------------------
 
450
# Entries
 
451
#----------------------------------------------------------
 
452
class account_move(osv.osv):
 
453
        _name = "account.move"
 
454
        _description = "Account Entry"
 
455
 
 
456
        def _get_period(self, cr, uid, context):
 
457
                periods = self.pool.get('account.period').find(cr, uid)
 
458
                if periods:
 
459
                        return periods[0]
 
460
                else:
 
461
                        return False
 
462
        _columns = {
 
463
                'name': fields.char('Entry Name', size=64, required=True),
 
464
                'ref': fields.char('Ref', size=64),
 
465
                'period_id': fields.many2one('account.period', 'Period', required=True, states={'posted':[('readonly',True)]}),
 
466
                'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'posted':[('readonly',True)]}, relate=True),
 
467
                'state': fields.selection([('draft','Draft'), ('posted','Posted')], 'State', required=True, readonly=True),
 
468
                'line_id': fields.one2many('account.move.line', 'move_id', 'Entries', states={'posted':[('readonly',True)]}),
 
469
        }
 
470
        _defaults = {
 
471
                'state': lambda *a: 'draft',
 
472
                'period_id': _get_period,
 
473
        }
 
474
        def button_validate(self, cr, uid, ids, context={}):
 
475
                if self.validate(cr, uid, ids, context) and len(ids):
 
476
                        cr.execute('update account_move set state=%s where id in ('+','.join(map(str,ids))+')', ('posted',))
 
477
                else:
 
478
                        cr.execute('update account_move set state=%s where id in ('+','.join(map(str,ids))+')', ('draft',))
 
479
                        cr.commit()
 
480
                        raise osv.except_osv('Integrity Error !', 'You can not validate a non balanced entry !')
 
481
                return True
 
482
 
 
483
        def button_cancel(self, cr, uid, ids, context={}):
 
484
                for line in self.browse(cr, uid, ids, context):
 
485
                        if not line.journal_id.update_posted:
 
486
                                raise osv.except_osv('Error !', 'You can not modify a posted entry of this journal !')
 
487
                if len(ids):
 
488
                        cr.execute('update account_move set state=%s where id in ('+','.join(map(str,ids))+')', ('draft',))
 
489
                return True
 
490
 
 
491
        def write(self, cr, uid, ids, vals, context={}):
 
492
                c = context.copy()
 
493
                c['novalidate'] = True
 
494
                result = super(osv.osv, self).write(cr, uid, ids, vals, c)
 
495
                self.validate(cr, uid, ids, context)
 
496
                return result
 
497
 
 
498
        #
 
499
        # TODO: Check if period is closed !
 
500
        #
 
501
        def create(self, cr, uid, vals, context={}):
 
502
                if 'line_id' in vals:
 
503
                        if 'journal_id' in vals:
 
504
                                for l in vals['line_id']:
 
505
                                        if not l[0]:
 
506
                                                l[2]['journal_id'] = vals['journal_id']
 
507
                                context['journal_id'] = vals['journal_id']
 
508
                        if 'period_id' in vals:
 
509
                                for l in vals['line_id']:
 
510
                                        if not l[0]:
 
511
                                                l[2]['period_id'] = vals['period_id']
 
512
                                context['period_id'] = vals['period_id']
 
513
                        else:
 
514
                                default_period = self._get_period(cr, uid, context)
 
515
                                for l in vals['line_id']:
 
516
                                        if not l[0]:
 
517
                                                l[2]['period_id'] = default_period
 
518
                                context['period_id'] = default_period
 
519
 
 
520
                if 'line_id' in vals:
 
521
                        c = context.copy()
 
522
                        c['novalidate'] = True
 
523
                        result = super(account_move, self).create(cr, uid, vals, c)
 
524
                        self.validate(cr, uid, [result], context)
 
525
                else:
 
526
                        result = super(account_move, self).create(cr, uid, vals, context)
 
527
                return result
 
528
 
 
529
        def unlink(self, cr, uid, ids, context={}, check=True):
 
530
                toremove = []
 
531
                for move in self.browse(cr, uid, ids, context):
 
532
                        line_ids = map(lambda x: x.id, move.line_id)
 
533
                        context['journal_id'] = move.journal_id.id
 
534
                        context['period_id'] = move.period_id.id
 
535
                        self.pool.get('account.move.line')._update_check(cr, uid, line_ids, context)
 
536
                        toremove.append(move.id)
 
537
                result = super(account_move, self).unlink(cr, uid, toremove, context)
 
538
                return result
 
539
 
 
540
        def _compute_balance(self, cr, uid, id, context={}):
 
541
                move = self.browse(cr, uid, [id])[0]
 
542
                amount = 0
 
543
                for line in move.line_id:
 
544
                        amount+= (line.debit - line.credit)
 
545
                return amount
 
546
 
 
547
        def _centralise(self, cr, uid, move, mode):
 
548
                if mode=='credit':
 
549
                        account_id = move.journal_id.default_debit_account_id.id
 
550
                        mode2 = 'debit'
 
551
                else:
 
552
                        account_id = move.journal_id.default_credit_account_id.id
 
553
                        mode2 = 'credit'
 
554
 
 
555
                # find the first line of this move with the current mode 
 
556
                # or create it if it doesn't exist
 
557
                cr.execute('select id from account_move_line where move_id=%d and centralisation=%s limit 1', (move.id, mode))
 
558
                res = cr.fetchone()
 
559
                if res:
 
560
                        line_id = res[0]
 
561
                else:
 
562
                        line_id = self.pool.get('account.move.line').create(cr, uid, {
 
563
                                'name': 'Centralisation '+mode,
 
564
                                'centralisation': mode,
 
565
                                'account_id': account_id,
 
566
                                'move_id': move.id,
 
567
                                'journal_id': move.journal_id.id,
 
568
                                'period_id': move.period_id.id,
 
569
                                'date': move.period_id.date_stop,
 
570
                                'debit': 0.0,
 
571
                                'credit': 0.0,
 
572
                        }, {'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
 
573
 
 
574
                # find the first line of this move with the other mode
 
575
                # so that we can exclude it from our calculation
 
576
                cr.execute('select id from account_move_line where move_id=%d and centralisation=%s limit 1', (move.id, mode2))
 
577
                res = cr.fetchone()
 
578
                if res:
 
579
                        line_id2 = res[0]
 
580
                else:
 
581
                        line_id2 = 0
 
582
 
 
583
                cr.execute('select sum('+mode+') from account_move_line where move_id=%d and id<>%d', (move.id, line_id2))
 
584
                result = cr.fetchone()[0] or 0.0
 
585
                cr.execute('update account_move_line set '+mode2+'=%f where id=%d', (result, line_id))
 
586
                return True
 
587
 
 
588
        #
 
589
        # Validate a balanced move. If it is a centralised journal, create a move.
 
590
        #
 
591
        def validate(self, cr, uid, ids, context={}):
 
592
                ok = True
 
593
                for move in self.browse(cr, uid, ids, context):
 
594
                        journal = move.journal_id
 
595
                        amount = 0
 
596
                        line_ids = []
 
597
                        line_draft_ids = []
 
598
                        for line in move.line_id:
 
599
                                amount += line.debit - line.credit
 
600
                                line_ids.append(line.id)
 
601
                                if line.state=='draft':
 
602
                                        line_draft_ids.append(line.id)
 
603
                        if abs(amount) < 0.0001:
 
604
                                if not len(line_draft_ids):
 
605
                                        continue
 
606
                                self.pool.get('account.move.line').write(cr, uid, line_draft_ids, {
 
607
                                        'journal_id': move.journal_id.id,
 
608
                                        'period_id': move.period_id.id,
 
609
                                        'state': 'valid'
 
610
                                }, context, check=False)
 
611
                                todo = []
 
612
                                account = {}
 
613
                                account2 = {}
 
614
                                field_base = ''
 
615
                                if journal.type not in ('purchase','sale'):
 
616
                                        continue
 
617
                                if journal.type=='purchase':
 
618
                                        field_base='ref_'
 
619
 
 
620
                                for line in move.line_id:
 
621
                                        if line.account_id.tax_ids:
 
622
                                                code = amount = False
 
623
                                                for tax in line.account_id.tax_ids:
 
624
                                                        if tax.tax_code_id:
 
625
                                                                acc = (line.debit >0) and tax.account_paid_id.id or tax.account_collected_id.id
 
626
                                                                account[acc] = (getattr(tax,field_base+'tax_code_id').id, getattr(tax,field_base+'tax_sign'))
 
627
                                                                account2[(acc,getattr(tax,field_base+'tax_code_id').id)] = (getattr(tax,field_base+'tax_code_id').id, getattr(tax,field_base+'tax_sign'))
 
628
                                                                code = getattr(tax,field_base+'base_code_id').id
 
629
                                                                amount = getattr(tax, field_base+'base_sign') * (line.debit + line.credit)
 
630
                                                                break
 
631
                                                if code: 
 
632
                                                        self.pool.get('account.move.line').write(cr, uid, [line.id], {
 
633
                                                                'tax_code_id': code,
 
634
                                                                'tax_amount': amount
 
635
                                                        }, context, check=False)
 
636
                                        else:
 
637
                                                todo.append(line)
 
638
                                for line in todo:
 
639
                                        code = amount = 0
 
640
                                        key = (line.account_id.id,line.tax_code_id.id)
 
641
                                        if key in account2:
 
642
                                                code = account2[key][0]
 
643
                                                amount = account2[key][1] * (line.debit + line.credit)
 
644
                                        elif line.account_id.id in account:
 
645
                                                code = account[line.account_id.id][0]
 
646
                                                amount = account[line.account_id.id][1] * (line.debit + line.credit)
 
647
                                        if code or amount:
 
648
                                                self.pool.get('account.move.line').write(cr, uid, [line.id], {
 
649
                                                        'tax_code_id': code,
 
650
                                                        'tax_amount': amount
 
651
                                                }, context, check=False)
 
652
                                #
 
653
                                # Compute VAT
 
654
                                #
 
655
                                continue
 
656
                        if journal.centralisation:
 
657
                                self._centralise(cr, uid, move, 'debit')
 
658
                                self._centralise(cr, uid, move, 'credit')
 
659
                                self.pool.get('account.move.line').write(cr, uid, line_draft_ids, {
 
660
                                        'state': 'valid'
 
661
                                }, context, check=False)
 
662
                                continue
 
663
                        else:
 
664
                                self.pool.get('account.move.line').write(cr, uid, line_ids, {
 
665
                                        'journal_id': move.journal_id.id,
 
666
                                        'period_id': move.period_id.id,
 
667
                                        #'tax_code_id': False,
 
668
                                        'tax_amount': False,
 
669
                                        'state': 'draft'
 
670
                                }, context, check=False)
 
671
                                ok = False
 
672
                return ok
 
673
account_move()
 
674
 
 
675
class account_move_reconcile(osv.osv):
 
676
        _name = "account.move.reconcile"
 
677
        _description = "Account Reconciliation"
 
678
        _columns = {
 
679
                'name': fields.char('Name', size=64, required=True),
 
680
                'type': fields.char('Type', size=16, required=True),
 
681
                'line_id': fields.one2many('account.move.line', 'reconcile_id', 'Entry lines'),
 
682
        }
 
683
        _defaults = {
 
684
                'name': lambda *a: 'reconcile '+time.strftime('%Y-%m-%d')
 
685
        }
 
686
account_move_reconcile()
 
687
 
 
688
#
 
689
# use a sequence for names ?
 
690
 
691
class account_bank_statement(osv.osv):
 
692
        def _default_journal_id(self, cr, uid, context={}):
 
693
                if context.get('journal_id', False):
 
694
                        return context['journal_id']
 
695
                if  context.get('journal_id', False):
 
696
                        # TODO: write this
 
697
                        return False
 
698
                return False
 
699
 
 
700
        def _default_balance_start(self, cr, uid, context={}):
 
701
                cr.execute('select id from account_bank_statement where journal_id=%d order by date desc limit 1', (1,))
 
702
                res = cr.fetchone()
 
703
                if res:
 
704
                        return self.browse(cr, uid, [res[0]], context)[0].balance_end
 
705
                return 0.0
 
706
 
 
707
        def _end_balance(self, cr, uid, ids, prop, unknow_none, unknow_dict):
 
708
                res = {}
 
709
                statements = self.browse(cr, uid, ids)
 
710
                for statement in statements:
 
711
                        res[statement.id] = statement.balance_start
 
712
                        for line in statement.line_ids:
 
713
                                res[statement.id] += line.amount
 
714
                for r in res:
 
715
                        res[r] = round(res[r], 2)
 
716
                return res
 
717
 
 
718
        def _get_period(self, cr, uid, context={}):
 
719
                periods = self.pool.get('account.period').find(cr, uid)
 
720
                if periods:
 
721
                        return periods[0]
 
722
                else:
 
723
                        return False
 
724
 
 
725
        _order = "date desc"
 
726
        _name = "account.bank.statement"
 
727
        _description = "Bank Statement"
 
728
        _columns = {
 
729
                'name': fields.char('Name', size=64, required=True),
 
730
                'date': fields.date('Date', required=True, states={'confirm':[('readonly',True)]}),
 
731
                'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'confirm':[('readonly',True)]}, domain=[('type','=','cash')], relate=True),
 
732
                'period_id': fields.many2one('account.period', 'Period', required=True, states={'confirm':[('readonly',True)]}),
 
733
                'balance_start': fields.float('Starting Balance', digits=(16,2), states={'confirm':[('readonly',True)]}),
 
734
                'balance_end_real': fields.float('Ending Balance', digits=(16,2), states={'confirm':[('readonly',True)]}),
 
735
                'balance_end': fields.function(_end_balance, method=True, string='Balance'),
 
736
                'line_ids': fields.one2many('account.bank.statement.line', 'statement_id', 'Statement lines', states={'confirm':[('readonly',True)]}),
 
737
                'move_line_ids': fields.one2many('account.move.line', 'statement_id', 'Entry lines', states={'confirm':[('readonly',True)]}),
 
738
                'state': fields.selection([('draft','Draft'),('confirm','Confirm')], 'State', required=True, states={'confirm':[('readonly',True)]}, readonly="1"),
 
739
        }
 
740
        _defaults = {
 
741
                'name': lambda self,cr,uid,context={}: self.pool.get('ir.sequence').get(cr, uid, 'account.bank.statement'),
 
742
                'date': lambda *a: time.strftime('%Y-%m-%d'),
 
743
                'state': lambda *a: 'draft',
 
744
                'balance_start': _default_balance_start,
 
745
                'journal_id': _default_journal_id,
 
746
                'period_id': _get_period,
 
747
        }
 
748
        def button_confirm(self, cr, uid, ids, context={}):
 
749
                done = []
 
750
                for st in self.browse(cr, uid, ids, context):
 
751
                        if not st.state=='draft':
 
752
                                continue
 
753
                        if not (abs(st.balance_end - st.balance_end_real) < 0.0001):
 
754
                                raise osv.except_osv('Error !', 'The statement balance is incorrect !\nCheck that the ending balance equals the computed one.')
 
755
                        if (not st.journal_id.default_credit_account_id) or (not st.journal_id.default_debit_account_id):
 
756
                                raise osv.except_osv('Configration Error !', 'Please verify that an account is defined in the journal.')
 
757
                        for move in st.line_ids:
 
758
                                if not move.amount:
 
759
                                        continue
 
760
                                self.pool.get('account.move.line').create(cr, uid, {
 
761
                                        'name': move.name,
 
762
                                        'date': move.date,
 
763
                                        'partner_id': ((move.partner_id) and move.partner_id.id) or False,
 
764
                                        'account_id': (move.account_id) and move.account_id.id,
 
765
                                        'credit': ((move.amount>0) and move.amount) or 0.0,
 
766
                                        'debit': ((move.amount<0) and -move.amount) or 0.0,
 
767
                                        'statement_id': st.id,
 
768
                                        'journal_id': st.journal_id.id,
 
769
                                        'period_id': st.period_id.id,
 
770
                                }, context=context)
 
771
                                if not st.journal_id.centralisation:
 
772
                                        c = context.copy()
 
773
                                        c['journal_id'] = st.journal_id.id
 
774
                                        c['period_id'] = st.period_id.id
 
775
                                        fields = ['move_id','name','date','partner_id','account_id','credit','debit']
 
776
                                        default = self.pool.get('account.move.line').default_get(cr, uid, fields, context=c)
 
777
                                        default.update({
 
778
                                                'statement_id': st.id,
 
779
                                                'journal_id': st.journal_id.id,
 
780
                                                'period_id': st.period_id.id,
 
781
                                        })
 
782
                                        self.pool.get('account.move.line').create(cr, uid, default, context=context)
 
783
                        done.append(st.id)
 
784
                self.write(cr, uid, done, {'state':'confirm'}, context=context)
 
785
                return True
 
786
        def button_cancel(self, cr, uid, ids, context={}):
 
787
                done = []
 
788
                for st in self.browse(cr, uid, ids, context):
 
789
                        if st.state=='draft':
 
790
                                continue
 
791
                        ids = [x.move_id.id for x in st.move_line_ids]
 
792
                        self.pool.get('account.move').unlink(cr, uid, ids, context)
 
793
                        done.append(st.id)
 
794
                self.write(cr, uid, done, {'state':'draft'}, context=context)
 
795
                return True
 
796
        def onchange_journal_id(self, cr, uid, id, journal_id, context={}):
 
797
                if not journal_id:
 
798
                        return {}
 
799
                cr.execute('select balance_end_real from account_bank_statement where journal_id=%d order by date desc limit 1', (journal_id,))
 
800
                res = cr.fetchone()
 
801
                if res:
 
802
                        return {'value': {'balance_start': res[0] or 0.0}}
 
803
                return {}
 
804
account_bank_statement()
 
805
 
 
806
class account_bank_statement_line(osv.osv):
 
807
        def onchange_partner_id(self, cr, uid, id, partner_id, type, context={}):
 
808
                if not partner_id:
 
809
                        return {}
 
810
                part = self.pool.get('res.partner').browse(cr, uid, partner_id, context)
 
811
                if type=='supplier':
 
812
                        account_id = part.property_account_payable[0]
 
813
                else:
 
814
                        account_id =  part.property_account_receivable[0]
 
815
                cr.execute('select sum(debit-credit) from account_move_line where (reconcile_id is null) and partner_id=%d and account_id=%d', (partner_id, account_id))
 
816
                balance = cr.fetchone()[0] or 0.0
 
817
                val = {'amount': balance, 'account_id':account_id}
 
818
                return {'value':val}
 
819
        _order = "date,name desc"
 
820
        _name = "account.bank.statement.line"
 
821
        _description = "Bank Statement Line"
 
822
        _columns = {
 
823
                'name': fields.char('Name', size=64, required=True),
 
824
                'date': fields.date('Date'),
 
825
                'amount': fields.float('Amount'),
 
826
                'type': fields.selection([('supplier','Supplier'),('customer','Customer'),('general','General')], 'Type', required=True),
 
827
                'partner_id': fields.many2one('res.partner', 'Partner'),
 
828
                'account_id': fields.many2one('account.account','Account', required=True),
 
829
                'statement_id': fields.many2one('account.bank.statement', 'Statement', select=True),
 
830
        }
 
831
        _defaults = {
 
832
                'name': lambda self,cr,uid,context={}: self.pool.get('ir.sequence').get(cr, uid, 'account.bank.statement.line'),
 
833
                'date': lambda *a: time.strftime('%Y-%m-%d'),
 
834
                'type': lambda *a: 'general',
 
835
        }
 
836
account_bank_statement_line()
 
837
 
 
838
 
 
839
#----------------------------------------------------------
 
840
# Tax
 
841
#----------------------------------------------------------
 
842
"""
 
843
a documenter 
 
844
child_depend: la taxe depend des taxes filles
 
845
"""
 
846
class account_tax_code(osv.osv):
 
847
        """
 
848
        A code for the tax object.
 
849
 
 
850
        This code is used for some tax declarations.
 
851
        """
 
852
        def _sum(self, cr, uid, ids, prop, unknow_none, unknow_dict, where =''):
 
853
                ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)])
 
854
                acc_set = ",".join(map(str, ids2))
 
855
                cr.execute('SELECT tax_code_id,sum(tax_amount) FROM account_move_line WHERE tax_code_id in ('+acc_set+') '+where+' GROUP BY tax_code_id')
 
856
                res=dict(cr.fetchall())
 
857
                for id in ids:
 
858
                        ids3 = self.search(cr, uid, [('parent_id', 'child_of', [id])])
 
859
                        for idx in ids3:
 
860
                                if idx <> id:
 
861
                                        res.setdefault(id, 0.0)
 
862
                                        res[id] += res.get(idx, 0.0)
 
863
                for id in ids:
 
864
                        res[id] = round(res.get(id,0.0), 2)
 
865
                return res
 
866
 
 
867
        def _sum_period(self, cr, uid, ids, prop, unknow_none, context={}):
 
868
                if not 'period_id' in context:
 
869
                        period_id = self.pool.get('account.period').find(cr, uid)
 
870
                        if not len(period_id):
 
871
                                return dict.fromkeys(ids, 0.0)
 
872
                        period_id = period_id[0]
 
873
                else:
 
874
                        period_id = context['period_id']
 
875
                return self._sum(cr, uid, ids, prop, unknow_none, context, where=' and period_id='+str(period_id))
 
876
 
 
877
        _name = 'account.tax.code'
 
878
        _description = 'Tax Code'
 
879
        _columns = {
 
880
                'name': fields.char('Tax Case Name', size=64, required=True),
 
881
                'code': fields.char('Case Code', size=16),
 
882
                'info': fields.text('Description'),
 
883
                'sum': fields.function(_sum, method=True, string="Year Sum"),
 
884
                'sum_period': fields.function(_sum_period, method=True, string="Period Sum"),
 
885
                'parent_id': fields.many2one('account.tax.code', 'Parent Code', select=True),
 
886
                'child_ids': fields.one2many('account.tax.code', 'parent_id', 'Childs Codes'),
 
887
                'line_ids': fields.one2many('account.move.line', 'tax_code_id', 'Lines')
 
888
        }
 
889
account_tax_code()
 
890
 
 
891
class account_move_line(osv.osv):
 
892
        _name = "account.move.line"
 
893
        _description = "Entry lines"
 
894
 
 
895
        def default_get(self, cr, uid, fields, context={}):
 
896
                data = self._default_get(cr, uid, fields, context)
 
897
                for f in data.keys():
 
898
                        if f not in fields:
 
899
                                del data[f]
 
900
                return data
 
901
 
 
902
        def _default_get(self, cr, uid, fields, context={}):
 
903
                # Compute simple values
 
904
                data = super(account_move_line, self).default_get(cr, uid, fields, context)
 
905
 
 
906
                # Compute the current move
 
907
                move_id = False
 
908
                partner_id = False
 
909
                statement_acc_id = False
 
910
                if context.get('journal_id',False) and context.get('period_id',False):
 
911
                        cr.execute('select move_id \
 
912
                                from \
 
913
                                        account_move_line \
 
914
                                where \
 
915
                                        journal_id=%d and period_id=%d and create_uid=%d and state=%s \
 
916
                                order by id desc limit 1', (context['journal_id'], context['period_id'], uid, 'draft'))
 
917
                        res = cr.fetchone()
 
918
                        move_id = (res and res[0]) or False
 
919
                        cr.execute('select date  \
 
920
                                from \
 
921
                                        account_move_line \
 
922
                                where \
 
923
                                        journal_id=%d and period_id=%d and create_uid=%d order by id desc', (context['journal_id'], context['period_id'], uid))
 
924
                        res = cr.fetchone()
 
925
                        data['date'] = res and res[0] or time.strftime('%Y-%m-%d')
 
926
                        cr.execute('select statement_id, account_id  \
 
927
                                from \
 
928
                                        account_move_line \
 
929
                                where \
 
930
                                        journal_id=%d and period_id=%d and statement_id is not null and create_uid=%d order by id desc', (context['journal_id'], context['period_id'], uid))
 
931
                        res = cr.fetchone()
 
932
                        statement_id = res and res[0] or False
 
933
                        statement_acc_id = res and res[1]
 
934
 
 
935
                if not move_id:
 
936
                        return data
 
937
 
 
938
                data['move_id'] = move_id
 
939
 
 
940
                total = 0
 
941
                taxes = {}
 
942
                move = self.pool.get('account.move').browse(cr, uid, move_id, context)
 
943
                for l in move.line_id:
 
944
                        partner_id = partner_id or l.partner_id.id
 
945
                        total += (l.debit - l.credit)
 
946
                        for tax in l.account_id.tax_ids:
 
947
                                acc = (l.debit >0) and tax.account_paid_id.id or tax.account_collected_id.id
 
948
                                taxes.setdefault((acc,tax.tax_code_id.id), False)
 
949
                        taxes[(l.account_id.id,l.tax_code_id.id)] = True
 
950
                        data.setdefault('name', l.name)
 
951
 
 
952
                data['partner_id'] = partner_id
 
953
 
 
954
                print taxes
 
955
                for t in taxes:
 
956
                        if not taxes[t] and t[0]:
 
957
                                s=0
 
958
                                for l in move.line_id:
 
959
                                        for tax in l.account_id.tax_ids:
 
960
                                                taxes = self.pool.get('account.tax').compute(cr, uid, [tax.id], l.debit or l.credit, 1, False)
 
961
                                                key = (l.debit and 'account_paid_id') or 'account_collected_id'
 
962
                                                for t2 in taxes:
 
963
                                                        if (t2[key] == t[0]) and (tax.tax_code_id.id==t[1]):
 
964
                                                                if l.debit:
 
965
                                                                        s += t2['amount']
 
966
                                                                else:
 
967
                                                                        s -= t2['amount']
 
968
                                data['debit'] = s>0  and s or 0.0
 
969
                                data['credit'] = s<0  and -s or 0.0
 
970
 
 
971
                                data['tax_code_id'] = t[1]
 
972
 
 
973
                                data['account_id'] = t[0]
 
974
 
 
975
                                #
 
976
                                # Compute line for tax T
 
977
                                #
 
978
                                return data
 
979
 
 
980
                #
 
981
                # Compute latest line
 
982
                #
 
983
                data['credit'] = total>0 and total
 
984
                data['debit'] = total<0 and -total
 
985
                if total>=0:
 
986
                        data['account_id'] = move.journal_id.default_credit_account_id.id or False
 
987
                else:
 
988
                        data['account_id'] = move.journal_id.default_debit_account_id.id or False
 
989
                if data['account_id']:
 
990
                        account = self.pool.get('account.account').browse(cr, uid, data['account_id'])
 
991
                        data['tax_code_id'] = self._default_get_tax(cr, uid, account )
 
992
                return data
 
993
 
 
994
        def _default_get_tax(self, cr, uid, account, debit=0, credit=0, context={}):
 
995
                if account.tax_ids:
 
996
                        return account.tax_ids[0].base_code_id.id
 
997
                return False
 
998
 
 
999
        def _on_create_write(self, cr, uid, id, context={}):
 
1000
                ml = self.browse(cr, uid, id, context)
 
1001
                return map(lambda x: x.id, ml.move_id.line_id)
 
1002
 
 
1003
        def _balance(self, cr, uid, ids, prop, unknow_none, unknow_dict):
 
1004
                res={}
 
1005
                # TODO group the foreach in sql
 
1006
                for id in ids:
 
1007
                        cr.execute('SELECT date,account_id FROM account_move_line WHERE id=%d', (id,))
 
1008
                        dt, acc = cr.fetchone()
 
1009
                        cr.execute('SELECT SUM(debit-credit) FROM account_move_line WHERE account_id=%d AND (date<%s OR (date=%s AND id<=%d)) and active', (acc,dt,dt,id))
 
1010
                        res[id] = cr.fetchone()[0]
 
1011
                return res
 
1012
 
 
1013
        _columns = {
 
1014
                'name': fields.char('Name', size=64, required=True),
 
1015
                'quantity': fields.float('Quantity', digits=(16,2), help="The optionnal quantity expressed by this line, eg: number of product sold. The quantity is not a legal requirement but is very usefull for some reports."),
 
1016
                'debit': fields.float('Debit', digits=(16,2), states={'reconciled':[('readonly',True)]}),
 
1017
                'credit': fields.float('Credit', digits=(16,2), states={'reconciled':[('readonly',True)]}),
 
1018
                'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade", states={'reconciled':[('readonly',True)]}, domain=[('type','<>','view')]),
 
1019
 
 
1020
                'move_id': fields.many2one('account.move', 'Entry', required=True, ondelete="cascade", states={'reconciled':[('readonly',True)]}, help="The entry of this entry line.", select=True),
 
1021
 
 
1022
                'ref': fields.char('Ref.', size=32),
 
1023
                'statement_id': fields.many2one('account.bank.statement', 'Statement', help="The bank statement used for bank reconciliation", select=True),
 
1024
                'reconcile_id': fields.many2one('account.move.reconcile', 'Reconcile', readonly=True, ondelete='set null', select=True),
 
1025
                'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optionnal other currency if it is a multi-currency entry."),
 
1026
                'currency_id': fields.many2one('res.currency', 'Currency', help="The optionnal other currency if it is a multi-currency entry."),
 
1027
 
 
1028
                'period_id': fields.many2one('account.period', 'Period', required=True),
 
1029
                'journal_id': fields.many2one('account.journal', 'Journal', required=True, relate=True),
 
1030
                'blocked': fields.boolean('Litigation', help="You can check this box to mark the entry line as a litigation with the associated partner"),
 
1031
 
 
1032
                'partner_id': fields.many2one('res.partner', 'Partner Ref.', states={'reconciled':[('readonly',True)]}),
 
1033
                'date_maturity': fields.date('Maturity date', states={'reconciled':[('readonly',True)]}, help="This field is used for payable and receivable entries. You can put the limit date for the payment of this entry line."),
 
1034
                'date': fields.date('Effective date', required=True),
 
1035
                'date_created': fields.date('Creation date'),
 
1036
                'analytic_lines': fields.one2many('account.analytic.line', 'move_id', 'Analytic lines'),
 
1037
                'centralisation': fields.selection([('normal','Normal'),('credit','Credit Centralisation'),('debit','Debit Centralisation')], 'Centralisation', size=6),
 
1038
                'balance': fields.function(_balance, method=True, string='Balance'),
 
1039
                'active': fields.boolean('Active'),
 
1040
                'state': fields.selection([('draft','Draft'), ('valid','Valid'), ('reconciled','Reconciled')], 'State', readonly=True),
 
1041
                'tax_code_id': fields.many2one('account.tax.code', 'Tax Account'),
 
1042
                'tax_amount': fields.float('Tax/Base Amount', digits=(16,2), select=True),
 
1043
        }
 
1044
        _defaults = {
 
1045
                'blocked': lambda *a: False,
 
1046
                'active': lambda *a: True,
 
1047
                'centralisation': lambda *a: 'normal',
 
1048
                'date_created': lambda *a: time.strftime('%Y-%m-%d'),
 
1049
                'state': lambda *a: 'draft',
 
1050
                'journal_id': lambda self, cr, uid, c: c.get('journal_id', False),
 
1051
                'period_id': lambda self, cr, uid, c: c.get('period_id', False),
 
1052
        }
 
1053
        _order = "date desc,id desc"
 
1054
        _sql_constraints = [
 
1055
                ('credit_debit1', 'CHECK (credit*debit=0)',  'Wrong credit or debit value in accounting entry !'),
 
1056
                ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in accounting entry !'),
 
1057
        ]
 
1058
        def onchange_partner_id(self, cr, uid, ids, move_id, partner_id, account_id=None, debit=0, credit=0, journal=False):
 
1059
                if (not partner_id) or account_id:
 
1060
                        return {}
 
1061
                part = self.pool.get('res.partner').browse(cr, uid, partner_id)
 
1062
                id1 = part.property_account_payable[0]
 
1063
                id2 =  part.property_account_receivable[0]
 
1064
                cr.execute('select sum(debit-credit) from account_move_line where (reconcile_id is null) and partner_id=%d and account_id=%d', (partner_id, id2))
 
1065
                balance = cr.fetchone()[0] or 0.0
 
1066
                val = {}
 
1067
                if (not debit) and (not credit):
 
1068
                        if abs(balance)>0.01:
 
1069
                                val['credit'] = ((balance>0) and balance) or 0
 
1070
                                val['debit'] = ((balance<0) and -balance) or 0
 
1071
                                val['account_id'] = id2
 
1072
                        else:
 
1073
                                cr.execute('select sum(debit-credit) from account_move_line where (reconcile_id is null) and partner_id=%d and account_id=%d', (partner_id, id1))
 
1074
                                balance = cr.fetchone()[0] or 0.0
 
1075
                                val['credit'] = ((balance>0) and balance) or 0
 
1076
                                val['debit'] = ((balance<0) and -balance) or 0
 
1077
                                val['account_id'] = id1
 
1078
                else:
 
1079
                        val['account_id'] =  (debit>0) and id2 or id1
 
1080
                if journal:
 
1081
                        jt = self.pool.get('account.journal').browse(cr, uid, journal).type
 
1082
                        if jt=='sale':
 
1083
                                val['account_id'] =  id2
 
1084
                        elif jt=='purchase':
 
1085
                                val['account_id'] =  id1
 
1086
                return {'value':val}
 
1087
 
 
1088
        def reconcile(self, cr, uid, ids, type='auto', writeoff_acc_id=False, writeoff_period_id=False, writeoff_journal_id=False, context={}):
 
1089
                id_set = ','.join(map(str, ids))
 
1090
                lines = self.read(cr, uid, ids, context=context)
 
1091
                unrec_lines = filter(lambda x: not x['reconcile_id'], lines)
 
1092
                credit = debit = 0
 
1093
                account_id = False
 
1094
                partner_id = False
 
1095
                for line in unrec_lines:
 
1096
                        credit += line['credit']
 
1097
                        debit += line['debit']
 
1098
                        account_id = line['account_id'][0]
 
1099
                        partner_id = (line['partner_id'] and line['partner_id'][0]) or False
 
1100
                writeoff = debit - credit
 
1101
                date = time.strftime('%Y-%m-%d')
 
1102
 
 
1103
                cr.execute('SELECT account_id,reconcile_id FROM account_move_line WHERE id IN ('+id_set+') GROUP BY account_id,reconcile_id')
 
1104
                r = cr.fetchall()
 
1105
#TODO: move this check to a constraint in the account_move_reconcile object
 
1106
                if len(r) != 1:
 
1107
                        raise 'Entries are not of the same account !'
 
1108
                if r[0][1] != None:
 
1109
                        raise 'Some entries are already reconciled !'
 
1110
                if writeoff != 0:
 
1111
                        if not writeoff_acc_id:
 
1112
                                raise osv.except_osv('Warning', 'You have to provide an account for the write off entry !')
 
1113
                        if writeoff > 0:
 
1114
                                debit = writeoff
 
1115
                                credit = 0.0
 
1116
                                self_credit = writeoff
 
1117
                                self_debit = 0.0
 
1118
                        else:
 
1119
                                debit = 0.0
 
1120
                                credit = -writeoff
 
1121
                                self_credit = 0.0
 
1122
                                self_debit = -writeoff
 
1123
 
 
1124
                        writeoff_lines = [
 
1125
                                (0, 0, {'name':'Write-Off', 'debit':self_debit, 'credit':self_credit, 'account_id':account_id, 'date':date, 'partner_id':partner_id}),
 
1126
                                (0, 0, {'name':'Write-Off', 'debit':debit, 'credit':credit, 'account_id':writeoff_acc_id, 'date':date, 'partner_id':partner_id})
 
1127
                        ]
 
1128
 
 
1129
                        name = 'Write-Off'
 
1130
                        if writeoff_journal_id:
 
1131
                                journal = self.pool.get('account.journal').browse(cr, uid, writeoff_journal_id)
 
1132
                                if journal.sequence_id:
 
1133
                                        name = self.pool.get('ir.sequence').get_id(cr, uid, journal.sequence_id.id)
 
1134
 
 
1135
                        writeoff_move_id = self.pool.get('account.move').create(cr, uid, {
 
1136
                                'name': name,
 
1137
                                'period_id': writeoff_period_id,
 
1138
                                'journal_id': writeoff_journal_id,
 
1139
 
 
1140
                                'state': 'draft',
 
1141
                                'line_id': writeoff_lines
 
1142
                        })
 
1143
 
 
1144
                        writeoff_line_ids = self.search(cr, uid, [('move_id', '=', writeoff_move_id), ('account_id', '=', account_id)])
 
1145
                        ids += writeoff_line_ids
 
1146
                        
 
1147
                self.write(cr, uid, ids, {'state': 'reconciled'}, update_check=False)
 
1148
                r_id = self.pool.get('account.move.reconcile').create(cr, uid, {
 
1149
                        'name': date, 
 
1150
                        'type': type, 
 
1151
                        'line_id': map(lambda x: (4,x,False), ids)
 
1152
                })
 
1153
                # the id of the move.reconcile is written in the move.line (self) by the create method above 
 
1154
                # because of the way the line_id are defined: (4, x, False)
 
1155
                wf_service = netsvc.LocalService("workflow")
 
1156
                for id in ids:
 
1157
                        wf_service.trg_trigger(uid, 'account.move.line', id, cr)
 
1158
                return r_id
 
1159
 
 
1160
        def view_header_get(self, cr, user, view_id, view_type, context):
 
1161
                if (not context.get('journal_id', False)) or (not context.get('period_id', False)):
 
1162
                        return False
 
1163
                cr.execute('select code from account_journal where id=%d', (context['journal_id'],))
 
1164
                j = cr.fetchone()[0] or ''
 
1165
                cr.execute('select code from account_period where id=%d', (context['period_id'],))
 
1166
                p = cr.fetchone()[0] or ''
 
1167
                if j or p:
 
1168
                        return j+':'+p
 
1169
                return 'Journal'
 
1170
 
 
1171
        def fields_view_get(self, cr, uid, view_id=None, view_type='form', context={}, toolbar=False):
 
1172
                result = super(osv.osv, self).fields_view_get(cr, uid, view_id,view_type,context)
 
1173
                if view_type=='tree' and 'journal_id' in context:
 
1174
                        title = self.view_header_get(cr, uid, view_id, view_type, context)
 
1175
                        journal = self.pool.get('account.journal').browse(cr, uid, context['journal_id'])
 
1176
 
 
1177
                        # if the journal view has a state field, color lines depending on
 
1178
                        # its value
 
1179
                        state = ''
 
1180
                        for field in journal.view_id.columns_id:
 
1181
                                if field.field=='state':
 
1182
                                        state = ' colors="red:state==\'draft\'"'
 
1183
 
 
1184
                        #xml = '''<?xml version="1.0"?>\n<tree string="%s" editable="top" refresh="5"%s>\n\t''' % (title, state)
 
1185
                        xml = '''<?xml version="1.0"?>\n<tree string="%s" editable="top" refresh="5" on_write="_on_create_write"%s>\n\t''' % (title, state)
 
1186
                        fields = []
 
1187
 
 
1188
                        widths = {
 
1189
                                'ref': 50,
 
1190
                                'statement_id': 50,
 
1191
                                'state': 60,
 
1192
                                'tax_code_id': 50,
 
1193
                                'move_id': 40,
 
1194
                        }
 
1195
                        for field in journal.view_id.columns_id:
 
1196
                                fields.append(field.field)
 
1197
                                attrs = []
 
1198
                                if field.readonly:
 
1199
                                        attrs.append('readonly="1"')
 
1200
                                if field.required:
 
1201
                                        attrs.append('required="1"')
 
1202
                                else:
 
1203
                                        attrs.append('required="0"')
 
1204
                                if field.field == 'partner_id':
 
1205
                                        attrs.append('on_change="onchange_partner_id(move_id,partner_id,account_id,debit,credit,((\'journal_id\' in context) and context[\'journal_id\']) or {})"')
 
1206
                                if field.field in widths:
 
1207
                                        attrs.append('width="'+str(widths[field.field])+'"')
 
1208
                                xml += '''<field name="%s" %s/>\n''' % (field.field,' '.join(attrs))
 
1209
 
 
1210
                        xml += '''</tree>'''
 
1211
                        result['arch'] = xml
 
1212
                        result['fields'] = self.fields_get(cr, uid, fields, context)
 
1213
                return result
 
1214
 
 
1215
        def unlink(self, cr, uid, ids, context={}, check=True):
 
1216
                self._update_check(cr, uid, ids, context)
 
1217
                for line in self.browse(cr, uid, ids, context):
 
1218
                        context['journal_id']=line.journal_id.id
 
1219
                        context['period_id']=line.period_id.id
 
1220
                        result = super(account_move_line, self).unlink(cr, uid, [line.id], context=context)
 
1221
                        if check:
 
1222
                                self.pool.get('account.move').validate(cr, uid, [line.move_id.id], context=context)
 
1223
                return result
 
1224
 
 
1225
        #
 
1226
        # TO VERIFY: check if try to write journal of only one line ???
 
1227
        #
 
1228
        def write(self, cr, uid, ids, vals, context={}, check=True, update_check=True):
 
1229
                if update_check:
 
1230
                        self._update_check(cr, uid, ids, context)
 
1231
                result = super(osv.osv, self).write(cr, uid, ids, vals, context)
 
1232
                if check:
 
1233
                        done = []
 
1234
                        for line in self.browse(cr, uid, ids):
 
1235
                                if line.move_id.id not in done:
 
1236
                                        done.append(line.move_id.id)
 
1237
                                        self.pool.get('account.move').validate(cr, uid, [line.move_id.id], context)
 
1238
                return result
 
1239
 
 
1240
        def _update_journal_check(self, cr, uid, journal_id, period_id, context={}):
 
1241
                cr.execute('select state from account_journal_period where journal_id=%d and period_id=%d', (journal_id, period_id))
 
1242
                result = cr.fetchall()
 
1243
                for (state,) in result:
 
1244
                        if state=='done':
 
1245
                                raise osv.except_osv('Error !', 'You can not add/modify entries in a closed journal.')
 
1246
                if not result:
 
1247
                        journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context)
 
1248
                        period = self.pool.get('account.period').browse(cr, uid, period_id, context)
 
1249
                        self.pool.get('account.journal.period').create(cr, uid, {
 
1250
                                'name': (journal.code or journal.name)+':'+(period.name or ''),
 
1251
                                'journal_id': journal.id,
 
1252
                                'period_id': period.id
 
1253
                        })
 
1254
                return True
 
1255
 
 
1256
        def _update_check(self, cr, uid, ids, context={}):
 
1257
                done = {}
 
1258
                for line in self.browse(cr, uid, ids, context):
 
1259
                        if line.move_id.state<>'draft':
 
1260
                                raise osv.except_osv('Error !', 'You can not modify or delete a confirmed entry !')
 
1261
                        if line.reconcile_id:
 
1262
                                raise osv.except_osv('Error !', 'You can not modify or delete a reconciled entry !')
 
1263
                        t = (line.journal_id.id, line.period_id.id)
 
1264
                        if t not in done:
 
1265
                                self._update_journal_check(cr, uid, line.journal_id.id, line.period_id.id, context)
 
1266
                                done[t] = True
 
1267
                return True
 
1268
 
 
1269
        def create(self, cr, uid, vals, context={}, check=True):
 
1270
                if 'journal_id' in vals and 'journal_id' not in context:
 
1271
                        context['journal_id'] = vals['journal_id']
 
1272
                if 'period_id' in vals and 'period_id' not in context:
 
1273
                        context['period_id'] = vals['period_id']
 
1274
                if 'journal_id' not in context and 'move_id' in vals:
 
1275
                        m = self.pool.get('account.move').browse(cr, uid, vals['move_id'])
 
1276
                        context['journal_id'] = m.journal_id.id
 
1277
                        context['period_id'] = m.period_id.id
 
1278
                self._update_journal_check(cr, uid, context['journal_id'], context['period_id'], context)
 
1279
                move_id = vals.get('move_id', False)
 
1280
                journal = self.pool.get('account.journal').browse(cr, uid, context['journal_id'])
 
1281
                if not move_id:
 
1282
                        if journal.centralisation:
 
1283
                                # use the first move ever created for this journal and period
 
1284
                                cr.execute('select id from account_move where journal_id=%d and period_id=%d order by id limit 1', (context['journal_id'],context['period_id']))
 
1285
                                res = cr.fetchone()
 
1286
                                if res:
 
1287
                                        vals['move_id'] = res[0]
 
1288
 
 
1289
                        if not vals.get('move_id', False):
 
1290
                                if journal.sequence_id:
 
1291
                                        name = self.pool.get('ir.sequence').get_id(cr, uid, journal.sequence_id.id)
 
1292
                                        v = {
 
1293
                                                'name': name,
 
1294
                                                'period_id': context['period_id'],
 
1295
                                                'journal_id': context['journal_id']
 
1296
                                        }
 
1297
                                        move_id = self.pool.get('account.move').create(cr, uid, v, context)
 
1298
                                        vals['move_id'] = move_id
 
1299
                                else:
 
1300
                                        raise osv.except_osv('No piece number !', 'Can not create an automatic sequence for this piece !\n\nPut a sequence in the journal definition for automatic numbering or create a sequence manually for this piece.')
 
1301
 
 
1302
                if ('account_id' in vals) and journal.type_control_ids:
 
1303
                        type = self.pool.get('account.account').browse(cr, uid, vals['account_id']).type
 
1304
                        ok = False
 
1305
                        for t in journal.type_control_ids:
 
1306
                                if type==t.code:
 
1307
                                        ok = True
 
1308
                                        break
 
1309
                        if not ok:
 
1310
                                raise osv.except_osv('Bad account !', 'You can not use this general account in this journal !')
 
1311
 
 
1312
                result = super(osv.osv, self).create(cr, uid, vals, context)
 
1313
                if check:
 
1314
                        self.pool.get('account.move').validate(cr, uid, [vals['move_id']], context)
 
1315
                return result
 
1316
account_move_line()
 
1317
 
 
1318
class account_tax(osv.osv):
 
1319
        """
 
1320
        A tax object.
 
1321
 
 
1322
        Type: percent, fixed, none, code
 
1323
                PERCENT: tax = price * amount
 
1324
                FIXED: tax = price + amount
 
1325
                NONE: no tax line
 
1326
                CODE: execute python code. localcontext = {'price_unit':pu, 'address':address_object}
 
1327
                        return result in the context
 
1328
                        Ex: result=round(price_unit*0.21,4)
 
1329
        """
 
1330
        _name = 'account.tax'
 
1331
        _description = 'Tax'
 
1332
        _columns = {
 
1333
                'name': fields.char('Tax Name', size=64, required=True),
 
1334
                'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the taxes lines from the lowest sequences to the higher ones. The order is important if you have a tax that have several tax childs. In this case, the evaluation order is important."),
 
1335
                'amount': fields.float('Amount', required=True, digits=(14,4)),
 
1336
                'active': fields.boolean('Active'),
 
1337
                'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code')], 'Tax Type', required=True),
 
1338
                'applicable_type': fields.selection( [('true','True'), ('code','Python Code')], 'Applicable Type', required=True),
 
1339
                'domain':fields.char('Domain', size=32, help="This field is only used if you develop your own module allowing developpers to create specific taxes in a custom domain."),
 
1340
                'account_collected_id':fields.many2one('account.account', 'Collected Tax Account'),
 
1341
                'account_paid_id':fields.many2one('account.account', 'Paid Tax Account'),
 
1342
                'parent_id':fields.many2one('account.tax', 'Parent Tax Account', select=True),
 
1343
                'child_ids':fields.one2many('account.tax', 'parent_id', 'Childs Tax Account'),
 
1344
                'child_depend':fields.boolean('Tax on Childs', help="Indicate if the tax computation is based on the value computed for the computation of child taxes or based on the total amount."),
 
1345
                'python_compute':fields.text('Python Code'),
 
1346
                'python_applicable':fields.text('Python Code'),
 
1347
                'company_id': fields.many2one('res.company', 'Company'),
 
1348
                'tax_group': fields.selection([('vat','VAT'),('other','Other')], 'Tax Group', help="If a default tax if given in the partner it only override taxes from account (or product) of the same group."),
 
1349
 
 
1350
                #
 
1351
                # Fields used for the VAT declaration
 
1352
                #
 
1353
                'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="Use this code for the VAT declaration."),
 
1354
                'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="Use this code for the VAT declaration."),
 
1355
                'base_sign': fields.float('Base Code Sign', help="Usualy 1 or -1."),
 
1356
                'tax_sign': fields.float('Tax Code Sign', help="Usualy 1 or -1."),
 
1357
 
 
1358
                # Same fields for refund invoices
 
1359
 
 
1360
                'ref_base_code_id': fields.many2one('account.tax.code', 'Base Code', help="Use this code for the VAT declaration."),
 
1361
                'ref_tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="Use this code for the VAT declaration."),
 
1362
                'ref_base_sign': fields.float('Base Code Sign', help="Usualy 1 or -1."),
 
1363
                'ref_tax_sign': fields.float('Tax Code Sign', help="Usualy 1 or -1."),
 
1364
        }
 
1365
        _defaults = {
 
1366
                'python_compute': lambda *a: '''# price_unit\n# address : res.partner.address object or False\n\nresult = price_unit * 0.10''',
 
1367
                'applicable_type': lambda *a: 'true',
 
1368
                'type': lambda *a: 'percent',
 
1369
                'amount': lambda *a: 0.196,
 
1370
                'active': lambda *a: 1,
 
1371
                'sequence': lambda *a: 1,
 
1372
                'tax_group': lambda *a: 'vat',
 
1373
                'ref_tax_sign': lambda *a: -1,
 
1374
                'ref_base_sign': lambda *a: -1,
 
1375
                'tax_sign': lambda *a: 1,
 
1376
                'base_sign': lambda *a: 1,
 
1377
        }
 
1378
        _order = 'sequence'
 
1379
        
 
1380
        def _applicable(self, cr, uid, taxes, price_unit, address_id=None):
 
1381
                res = []
 
1382
                for tax in taxes:
 
1383
                        if tax.applicable_type=='code':
 
1384
                                localdict = {'price_unit':price_unit, 'address':self.pool.get('res.partner.address').browse(cr, uid, address_id)}
 
1385
                                exec tax.python_applicable in localdict
 
1386
                                if localdict.get('result', False):
 
1387
                                        res.append(tax)
 
1388
                        else:
 
1389
                                res.append(tax)
 
1390
                return res
 
1391
 
 
1392
        def _unit_compute(self, cr, uid, ids, price_unit, address_id=None):
 
1393
                taxes = self.browse(cr, uid, ids)
 
1394
                return self._unit_compute_br(cr, uid, taxes, price_unit, address_id)
 
1395
 
 
1396
        def _unit_compute_br(self, cr, uid, taxes, price_unit, address_id=None):
 
1397
                taxes = self._applicable(cr, uid, taxes, price_unit, address_id)
 
1398
 
 
1399
                res = []
 
1400
                for tax in taxes:
 
1401
                        # we compute the amount for the current tax object and append it to the result
 
1402
                        if tax.type=='percent':
 
1403
                                amount = price_unit * tax.amount
 
1404
                                res.append({'id':tax.id, 'name':tax.name, 'amount':amount, 'account_collected_id':tax.account_collected_id.id, 'account_paid_id':tax.account_paid_id.id})
 
1405
                        elif tax.type=='fixed':
 
1406
                                res.append({'id':tax.id, 'name':tax.name, 'amount':tax.amount, 'account_collected_id':tax.account_collected_id.id, 'account_paid_id':tax.account_paid_id.id})
 
1407
                        elif tax.type=='code':
 
1408
                                address = address_id and self.pool.get('res.partner.address').browse(cr, uid, address_id) or None
 
1409
                                localdict = {'price_unit':price_unit, 'address':address}
 
1410
                                exec tax.python_compute in localdict
 
1411
                                amount = localdict['result']
 
1412
                                res.append({
 
1413
                                        'id': tax.id,
 
1414
                                        'name': tax.name,
 
1415
                                        'amount': amount,
 
1416
                                        'account_collected_id': tax.account_collected_id.id,
 
1417
                                        'account_paid_id': tax.account_paid_id.id
 
1418
                                })
 
1419
                        amount2 = res[-1]['amount']
 
1420
                        if len(tax.child_ids):
 
1421
                                if tax.child_depend:
 
1422
                                        del res[-1]
 
1423
                                        amount = amount2
 
1424
                                else:
 
1425
                                        amount = amount2
 
1426
                        for t in tax.child_ids:
 
1427
                                parent_tax = self._unit_compute_br(cr, uid, [t], amount, address_id)
 
1428
                                res.extend(parent_tax)
 
1429
                return res
 
1430
 
 
1431
        def compute(self, cr, uid, ids, price_unit, quantity, address_id=None):
 
1432
                """
 
1433
                Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
 
1434
 
 
1435
                RETURN:
 
1436
                        [ tax ]
 
1437
                        tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
 
1438
                        one tax for each tax id in IDS and their childs
 
1439
                """
 
1440
                res = self._unit_compute(cr, uid, ids, price_unit, address_id)
 
1441
                for r in res:
 
1442
                        r['amount'] = round(quantity * r['amount'],2)
 
1443
                return res
 
1444
account_tax()
 
1445
 
 
1446
# ---------------------------------------------------------
 
1447
# Budgets
 
1448
# ---------------------------------------------------------
 
1449
 
 
1450
class account_budget_post(osv.osv):
 
1451
        _name = 'account.budget.post'
 
1452
        _description = 'Budget item'
 
1453
        _columns = {
 
1454
                'code': fields.char('Code', size=64, required=True),
 
1455
                'name': fields.char('Name', size=256, required=True),
 
1456
                'sens': fields.selection( [('charge','Charge'), ('produit','Product')], 'Direction', required=True),
 
1457
                'dotation_ids': fields.one2many('account.budget.post.dotation', 'post_id', 'Expenses'),
 
1458
                'account_ids': fields.many2many('account.account', 'account_budget_rel', 'budget_id', 'account_id', 'Accounts'),
 
1459
        }
 
1460
        _defaults = {
 
1461
                'sens': lambda *a: 'produit',
 
1462
        }
 
1463
 
 
1464
        def spread(self, cr, uid, ids, fiscalyear_id=False, quantity=0.0, amount=0.0):
 
1465
                dobj = self.pool.get('account.budget.post.dotation')
 
1466
                for o in self.browse(cr, uid, ids):
 
1467
                        # delete dotations for this post
 
1468
                        dobj.unlink(cr, uid, dobj.search(cr, uid, [('post_id','=',o.id)]))
 
1469
 
 
1470
                        # create one dotation per period in the fiscal year, and spread the total amount/quantity over those dotations
 
1471
                        fy = self.pool.get('account.fiscalyear').browse(cr, uid, [fiscalyear_id])[0]
 
1472
                        num = len(fy.period_ids)
 
1473
                        for p in fy.period_ids:
 
1474
                                dobj.create(cr, uid, {'post_id': o.id, 'period_id': p.id, 'quantity': quantity/num, 'amount': amount/num})
 
1475
                return True
 
1476
account_budget_post()
 
1477
 
 
1478
class account_budget_post_dotation(osv.osv):
 
1479
        _name = 'account.budget.post.dotation'
 
1480
        _description = "Budget item endowment"
 
1481
        _columns = {
 
1482
                'name': fields.char('Name', size=64),
 
1483
                'post_id': fields.many2one('account.budget.post', 'Item', select=True),
 
1484
                'period_id': fields.many2one('account.period', 'Period'),
 
1485
                'quantity': fields.float('Quantity', digits=(16,2)),
 
1486
                'amount': fields.float('Amount', digits=(16,2)),
 
1487
        }
 
1488
account_budget_post_dotation()
 
1489
 
 
1490
 
 
1491
# ---------------------------------------------------------
 
1492
# Account Entries Models
 
1493
# ---------------------------------------------------------
 
1494
 
 
1495
class account_model(osv.osv):
 
1496
        _name = "account.model"
 
1497
        _description = "Account Model"
 
1498
        _columns = {
 
1499
                'name': fields.char('Model Name', size=64, required=True, help="This is a model for recurring accounting entries"),
 
1500
                'ref': fields.char('Ref', size=64),
 
1501
                'journal_id': fields.many2one('account.journal', 'Journal', required=True),
 
1502
                'lines_id': fields.one2many('account.model.line', 'model_id', 'Model Entries'),
 
1503
        }
 
1504
        def generate(self, cr, uid, ids, datas={}, context={}):
 
1505
                move_ids = []
 
1506
                for model in self.browse(cr, uid, ids, context):
 
1507
                        period_id = self.pool.get('account.period').find(cr,uid, context=context)
 
1508
                        if not period_id:
 
1509
                                raise osv.except_osv('No period found !', 'Unable to find a valid period !')
 
1510
                        period_id = period_id[0]
 
1511
                        name = model.name
 
1512
                        if model.journal_id.sequence_id:
 
1513
                                name = self.pool.get('ir.sequence').get_id(cr, uid, model.journal_id.sequence_id.id)
 
1514
                        move_id = self.pool.get('account.move').create(cr, uid, {
 
1515
                                'name': name,
 
1516
                                'ref': model.ref,
 
1517
                                'period_id': period_id,
 
1518
                                'journal_id': model.journal_id.id,
 
1519
                        })
 
1520
                        move_ids.append(move_id)
 
1521
                        for line in model.lines_id:
 
1522
                                val = {
 
1523
                                        'move_id': move_id,
 
1524
                                        'journal_id': model.journal_id.id,
 
1525
                                        'period_id': period_id
 
1526
                                }
 
1527
                                val.update({
 
1528
                                        'name': line.name,
 
1529
                                        'quantity': line.quantity,
 
1530
                                        'debit': line.debit,
 
1531
                                        'credit': line.credit,
 
1532
                                        'account_id': line.account_id.id,
 
1533
                                        'move_id': move_id,
 
1534
                                        'ref': line.ref,
 
1535
                                        'partner_id': line.partner_id.id,
 
1536
                                        'date': time.strftime('%Y-%m-%d'),
 
1537
                                        'date_maturity': time.strftime('%Y-%m-%d')
 
1538
                                })
 
1539
                                c = context.copy()
 
1540
                                c.update({'journal_id': model.journal_id.id,'period_id': period_id})
 
1541
                                self.pool.get('account.move.line').create(cr, uid, val, context=c)
 
1542
                return move_ids
 
1543
account_model()
 
1544
 
 
1545
class account_model_line(osv.osv):
 
1546
        _name = "account.model.line"
 
1547
        _description = "Account Model Entries"
 
1548
        _columns = {
 
1549
                'name': fields.char('Name', size=64, required=True),
 
1550
                'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the resources from the lowest sequences to the higher ones"),
 
1551
                'quantity': fields.float('Quantity', digits=(16,2), help="The optionnal quantity on entries"),
 
1552
                'debit': fields.float('Debit', digits=(16,2)),
 
1553
                'credit': fields.float('Credit', digits=(16,2)),
 
1554
 
 
1555
                'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade"),
 
1556
 
 
1557
                'model_id': fields.many2one('account.model', 'Model', required=True, ondelete="cascade", select=True),
 
1558
 
 
1559
                'ref': fields.char('Ref.', size=16),
 
1560
 
 
1561
                'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optionnal other currency."),
 
1562
                'currency_id': fields.many2one('res.currency', 'Currency'),
 
1563
 
 
1564
                'partner_id': fields.many2one('res.partner', 'Partner Ref.'),
 
1565
                'date_maturity': fields.selection([('today','Date of the day'), ('partner','Partner Payment Term')], 'Maturity date', help="The maturity date of the generated entries for this model. You can chosse between the date of the creation action or the the date of the creation of the entries plus the partner payment terms."),
 
1566
                'date': fields.selection([('today','Date of the day'), ('partner','Partner Payment Term')], 'Current Date', required=True, help="The date of the generated entries"),
 
1567
        }
 
1568
        _defaults = {
 
1569
                'date': lambda *a: 'today'
 
1570
        }
 
1571
        _order = 'sequence'
 
1572
        _sql_constraints = [
 
1573
                ('credit_debit1', 'CHECK (credit*debit=0)',  'Wrong credit or debit value in model !'),
 
1574
                ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model !'),
 
1575
        ]
 
1576
account_model_line()
 
1577
 
 
1578
# ---------------------------------------------------------
 
1579
# Account Subscription
 
1580
# ---------------------------------------------------------
 
1581
 
 
1582
 
 
1583
class account_subscription(osv.osv):
 
1584
        _name = "account.subscription"
 
1585
        _description = "Account Subscription"
 
1586
        _columns = {
 
1587
                'name': fields.char('Name', size=64, required=True),
 
1588
                'ref': fields.char('Ref.', size=16),
 
1589
                'model_id': fields.many2one('account.model', 'Model', required=True),
 
1590
 
 
1591
                'date_start': fields.date('Starting date', required=True),
 
1592
                'period_total': fields.integer('Number of period', required=True),
 
1593
                'period_nbr': fields.integer('Period', required=True),
 
1594
                'period_type': fields.selection([('day','days'),('month','month'),('year','year')], 'Period Type', required=True),
 
1595
                'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'State', required=True, readonly=True),
 
1596
 
 
1597
                'lines_id': fields.one2many('account.subscription.line', 'subscription_id', 'Subscription Lines')
 
1598
        }
 
1599
        _defaults = {
 
1600
                'date_start': lambda *a: time.strftime('%Y-%m-%d'),
 
1601
                'period_type': lambda *a: 'month',
 
1602
                'period_total': lambda *a: 12,
 
1603
                'period_nbr': lambda *a: 1,
 
1604
                'state': lambda *a: 'draft',
 
1605
        }
 
1606
        def state_draft(self, cr, uid, ids, context={}):
 
1607
                self.write(cr, uid, ids, {'state':'draft'})
 
1608
                return False
 
1609
 
 
1610
        def check(self, cr, uid, ids, context={}):
 
1611
                todone = []
 
1612
                for sub in self.browse(cr, uid, ids, context):
 
1613
                        ok = True
 
1614
                        for line in sub.lines_id:
 
1615
                                if not line.move_id.id:
 
1616
                                        ok = False
 
1617
                                        break
 
1618
                        if ok:
 
1619
                                todone.append(sub.id)
 
1620
                if len(todone):
 
1621
                        self.write(cr, uid, todone, {'state':'done'})
 
1622
                return False
 
1623
 
 
1624
        def remove_line(self, cr, uid, ids, context={}):
 
1625
                toremove = []
 
1626
                for sub in self.browse(cr, uid, ids, context):
 
1627
                        for line in sub.lines_id:
 
1628
                                if not line.move_id.id:
 
1629
                                        toremove.append(line.id)
 
1630
                if len(toremove):
 
1631
                        self.pool.get('account.subscription.line').unlink(cr, uid, toremove)
 
1632
                self.write(cr, uid, ids, {'state':'draft'})
 
1633
                return False
 
1634
 
 
1635
        def compute(self, cr, uid, ids, context={}):
 
1636
                for sub in self.browse(cr, uid, ids, context):
 
1637
                        ds = sub.date_start
 
1638
                        for i in range(sub.period_total):
 
1639
                                self.pool.get('account.subscription.line').create(cr, uid, {
 
1640
                                        'date': ds,
 
1641
                                        'subscription_id': sub.id,
 
1642
                                })
 
1643
                                if sub.period_type=='day':
 
1644
                                        ds = (mx.DateTime.strptime(ds, '%Y-%m-%d') + RelativeDateTime(days=sub.period_nbr)).strftime('%Y-%m-%d')
 
1645
                                if sub.period_type=='month':
 
1646
                                        ds = (mx.DateTime.strptime(ds, '%Y-%m-%d') + RelativeDateTime(months=sub.period_nbr)).strftime('%Y-%m-%d')
 
1647
                                if sub.period_type=='year':
 
1648
                                        ds = (mx.DateTime.strptime(ds, '%Y-%m-%d') + RelativeDateTime(years=sub.period_nbr)).strftime('%Y-%m-%d')
 
1649
                self.write(cr, uid, ids, {'state':'running'})
 
1650
                return True
 
1651
account_subscription()
 
1652
 
 
1653
class account_subscription_line(osv.osv):
 
1654
        _name = "account.subscription.line"
 
1655
        _description = "Account Subscription Line"
 
1656
        _columns = {
 
1657
                'subscription_id': fields.many2one('account.subscription', 'Subscription', required=True, select=True),
 
1658
                'date': fields.date('Date', required=True),
 
1659
                'move_id': fields.many2one('account.move', 'Entry'),
 
1660
        }
 
1661
        _defaults = {
 
1662
        }
 
1663
        def move_create(self, cr, uid, ids, context={}):
 
1664
                tocheck = {}
 
1665
                for line in self.browse(cr, uid, ids, context):
 
1666
                        datas = {
 
1667
                                'date': line.date,
 
1668
                        }
 
1669
                        ids = self.pool.get('account.model').generate(cr, uid, [line.subscription_id.model_id.id], datas, context)
 
1670
                        tocheck[line.subscription_id.id] = True
 
1671
                        self.write(cr, uid, [line.id], {'move_id':ids[0]})
 
1672
                if tocheck:
 
1673
                        self.pool.get('account.subscription').check(cr, uid, tocheck.keys(), context)
 
1674
                return True
 
1675
        _rec_name = 'date'
 
1676
account_subscription_line()
 
1677
 
 
1678