~therp-nl/banking-addons/ba61-lp1098699-fix_clieop_rounding_issue

« back to all changes in this revision

Viewing changes to account_banking/account_banking.py

  • Committer: Pieter J. Kersten
  • Date: 2010-01-26 20:55:24 UTC
  • Revision ID: p.j.kersten@edusense.nl-20100126205524-cc4jsho1p49acnn7
[META] Set structure

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- encoding: utf-8 -*-
 
2
##############################################################################
 
3
#
 
4
#    Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
 
5
#    All Rights Reserved
 
6
#
 
7
#    This program is free software: you can redistribute it and/or modify
 
8
#    it under the terms of the GNU General Public License as published by
 
9
#    the Free Software Foundation, either version 3 of the License, or
 
10
#    (at your option) any later version.
 
11
#
 
12
#    This program is distributed in the hope that it will be useful,
 
13
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
14
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
15
#    GNU General Public License for more details.
 
16
#
 
17
#    You should have received a copy of the GNU General Public License
 
18
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
19
#
 
20
##############################################################################
 
21
 
 
22
'''
 
23
This module shows resemblance to both account_bankimport/bankimport.py,
 
24
account/account_bank_statement.py and account_payment(_export). All hail to
 
25
the makers. account_bankimport is only referenced for their ideas and the
 
26
framework of the filters, which they in their turn seem to have derived
 
27
from account_coda.
 
28
 
 
29
Modifications are extensive:
 
30
 
 
31
1. In relation to account/account_bank_statement.py:
 
32
    account.bank.statement is effectively stripped from its account.period
 
33
    association, while account.bank.statement.line is extended with the same
 
34
    association, thereby reflecting real world usage of bank.statement as a
 
35
    list of bank transactions and bank.statement.line as a bank transaction.
 
36
 
 
37
2. In relation to account/account_bankimport:
 
38
    All filter objects and extensions to res.company are removed. Instead a
 
39
    flexible auto-loading and auto-browsing plugin structure is created,
 
40
    whereby business logic and encoding logic are strictly separated.
 
41
    Both parsers and business logic are rewritten from scratch.
 
42
 
 
43
    The association of account.journal with res.company is replaced by an
 
44
    association of account.journal with res.partner.bank, thereby allowing
 
45
    multiple bank accounts per company and one journal per bank account.
 
46
 
 
47
    The imported bank statement file does not result in a single 'bank
 
48
    statement', but in a list of bank statements by definition of whatever the
 
49
    bank sees as a statement. Every imported bank statement contains at least
 
50
    one bank transaction, which is a modded account.bank.statement.line.
 
51
 
 
52
3. In relation to account_payment:
 
53
    An additional state was inserted between 'open' and 'done', to reflect a
 
54
    exported bank orders file which was not reported back through statements.
 
55
    The import of statements matches the payments and reconciles them when
 
56
    needed, flagging them 'done'. When no export wizards are found, the
 
57
    default behavior is to flag the orders as 'sent', not as 'done'.
 
58
'''
 
59
import time
 
60
from osv import osv, fields
 
61
from tools.translate import _
 
62
 
 
63
class account_banking_account_settings(osv.osv):
 
64
    '''Default Journal for Bank Account'''
 
65
    _name = 'account.banking.account.settings'
 
66
    _description = __doc__
 
67
    _columns = {
 
68
        'company_id': fields.many2one('res.company', 'Company', select=True,
 
69
                                      required=True),
 
70
        'partner_bank_id': fields.many2one('res.partner.bank', 'Bank Account',
 
71
                                           select=True, required=True),
 
72
        'journal_id': fields.many2one('account.journal', 'Journal',
 
73
                                      required=True),
 
74
        'default_credit_account_id': fields.many2one(
 
75
            'account.account', 'Default credit account', select=True,
 
76
            help=('The account to use when an unexpected payment was signaled. '
 
77
                  'This can happen when a direct debit payment is cancelled '
 
78
                  'by a customer, or when no matching payment can be found. '
 
79
                  ' Mind that you can correct movements before confirming them.'
 
80
                 ),
 
81
            required=True
 
82
        ),
 
83
        'default_debit_account_id': fields.many2one(
 
84
            'account.account', 'Default debit account',
 
85
            select=True, required=True,
 
86
            help=('The account to use when an unexpected payment is received. '
 
87
                  'This can be needed when a customer pays in advance or when '
 
88
                  'no matching invoice can be found. Mind that you can correct '
 
89
                  'movements before confirming them.'
 
90
                 ),
 
91
        ),
 
92
    }
 
93
 
 
94
    def _default_company(self, cursor, uid, context={}):
 
95
        user = self.pool.get('res.users').browse(cursor, uid, uid, context=context)
 
96
        if user.company_id:
 
97
            return user.company_id.id
 
98
        return self.pool.get('res.company').search(cursor, uid,
 
99
                                                   [('parent_id', '=', False)]
 
100
                                                  )[0]
 
101
 
 
102
    _defaults = {
 
103
        'company_id': _default_company,
 
104
    }
 
105
account_banking_account_settings()
 
106
 
 
107
class account_banking_imported_file(osv.osv):
 
108
    '''Imported Bank Statements File'''
 
109
    _name = 'account.banking.imported.file'
 
110
    _description = __doc__
 
111
    _columns = {
 
112
        'company_id': fields.many2one('res.company', 'Company',
 
113
                                      select=True, readonly=True
 
114
                                     ),
 
115
        'date': fields.datetime('Import Date', readonly=False, select=True),
 
116
        'format': fields.char('File Format', size=20, readonly=False),
 
117
        'file': fields.binary('Raw Data', readonly=False),
 
118
        'log': fields.text('Import Log', readonly=False),
 
119
        'user_id': fields.many2one('res.users', 'Responsible User',
 
120
                                   readonly=False, select=True
 
121
                                  ),
 
122
        'state': fields.selection(
 
123
            [('unfinished', 'Unfinished'),
 
124
             ('error', 'Error'),
 
125
             ('ready', 'Finished'),
 
126
            ], 'State', select=True, readonly=True
 
127
        ),
 
128
        'statement_ids': fields.one2many('account.bank.statement',
 
129
                                         'banking_id', 'Statements',
 
130
                                         readonly=False,
 
131
                                  ),
 
132
    }
 
133
    _defaults = {
 
134
        'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
 
135
        'user_id': lambda self, cursor, uid, context: uid,
 
136
    }
 
137
account_banking_imported_file()
 
138
 
 
139
class account_bank_statement(osv.osv):
 
140
    '''
 
141
    Extensions from account_bank_statement:
 
142
        1. Removed period_id (transformed to optional boolean) - as it is no
 
143
           longer needed.
 
144
        2. Extended 'button_confirm' trigger to cope with the period per
 
145
           statement_line situation.
 
146
        3. Added optional relation with imported statements file
 
147
    '''
 
148
    _inherit = 'account.bank.statement'
 
149
    _columns = {
 
150
        'period_id': fields.many2one('account.period', 'Period',
 
151
                                     required=False, readonly=True),
 
152
        'banking_id': fields.many2one('account.banking.imported.file',
 
153
                                     'Imported File', readonly=True,
 
154
                                     ),
 
155
    }
 
156
    _defaults = {
 
157
        'period_id': lambda *a: False,
 
158
    }
 
159
 
 
160
    def _get_period(self, cursor, uid, date, context={}):
 
161
        '''
 
162
        Find matching period for date, not meant for _defaults.
 
163
        '''
 
164
        period_obj = self.pool.get('account.period')
 
165
        periods = period_obj.find(cursor, uid, dt=date, context=context)
 
166
        return periods and periods[0] or False
 
167
 
 
168
    def button_confirm(self, cursor, uid, ids, context=None):
 
169
        # This is largely a copy of the original code in account
 
170
        # As there is no valid inheritance mechanism for large actions, this
 
171
        # is the only option to add functionality to existing actions.
 
172
        # WARNING: when the original code changes, this trigger has to be
 
173
        # updated in sync.
 
174
        done = []
 
175
        res_currency_obj = self.pool.get('res.currency')
 
176
        res_users_obj = self.pool.get('res.users')
 
177
        account_move_obj = self.pool.get('account.move')
 
178
        account_move_line_obj = self.pool.get('account.move.line')
 
179
        account_bank_statement_line_obj = \
 
180
                self.pool.get('account.bank.statement.line')
 
181
 
 
182
        company_currency_id = res_users_obj.browse(cursor, uid, uid,
 
183
                context=context).company_id.currency_id.id
 
184
 
 
185
        for st in self.browse(cursor, uid, ids, context):
 
186
            if not st.state=='draft':
 
187
                continue
 
188
            end_bal = st.balance_end or 0.0
 
189
            if not (abs(end_bal - st.balance_end_real) < 0.0001):
 
190
                raise osv.except_osv(_('Error !'),
 
191
                    _('The statement balance is incorrect !\n') +
 
192
                    _('The expected balance (%.2f) is different '
 
193
                      'than the computed one. (%.2f)') % (
 
194
                          st.balance_end_real, st.balance_end
 
195
                      ))
 
196
            if (not st.journal_id.default_credit_account_id) \
 
197
                    or (not st.journal_id.default_debit_account_id):
 
198
                raise osv.except_osv(_('Configration Error !'),
 
199
                    _('Please verify that an account is defined in the journal.'))
 
200
 
 
201
            for line in st.move_line_ids:
 
202
                if line.state != 'valid':
 
203
                    raise osv.except_osv(_('Error !'),
 
204
                        _('The account entries lines are not in valid state.'))
 
205
 
 
206
            for move in st.line_ids:
 
207
                period_id = self._get_period(cursor, uid, move.date, context=context)
 
208
                move_id = account_move_obj.create(cursor, uid, {
 
209
                    'journal_id': st.journal_id.id,
 
210
                    'period_id': period_id,
 
211
                }, context=context)
 
212
                account_bank_statement_line_obj.write(cursor, uid, [move.id], {
 
213
                    'move_ids': [(4, move_id, False)]
 
214
                })
 
215
                if not move.amount:
 
216
                    continue
 
217
 
 
218
                torec = []
 
219
                if move.amount >= 0:
 
220
                    account_id = st.journal_id.default_credit_account_id.id
 
221
                else:
 
222
                    account_id = st.journal_id.default_debit_account_id.id
 
223
                acc_cur = ((move.amount<=0) and st.journal_id.default_debit_account_id) \
 
224
                          or move.account_id
 
225
                amount = res_currency_obj.compute(cursor, uid, st.currency.id,
 
226
                        company_currency_id, move.amount, context=context,
 
227
                        account=acc_cur)
 
228
                if move.reconcile_id and move.reconcile_id.line_new_ids:
 
229
                    for newline in move.reconcile_id.line_new_ids:
 
230
                        amount += newline.amount
 
231
 
 
232
                val = {
 
233
                    'name': move.name,
 
234
                    'date': move.date,
 
235
                    'ref': move.ref,
 
236
                    'move_id': move_id,
 
237
                    'partner_id': ((move.partner_id) and move.partner_id.id) or False,
 
238
                    'account_id': (move.account_id) and move.account_id.id,
 
239
                    'credit': ((amount>0) and amount) or 0.0,
 
240
                    'debit': ((amount<0) and -amount) or 0.0,
 
241
                    'statement_id': st.id,
 
242
                    'journal_id': st.journal_id.id,
 
243
                    'period_id': period_id,
 
244
                    'currency_id': st.currency.id,
 
245
                }
 
246
 
 
247
                amount = res_currency_obj.compute(cursor, uid, st.currency.id,
 
248
                        company_currency_id, move.amount, context=context,
 
249
                        account=acc_cur)
 
250
 
 
251
                if move.account_id and move.account_id.currency_id:
 
252
                    val['currency_id'] = move.account_id.currency_id.id
 
253
                    if company_currency_id==move.account_id.currency_id.id:
 
254
                        amount_cur = move.amount
 
255
                    else:
 
256
                        amount_cur = res_currency_obj.compute(cursor, uid, company_currency_id,
 
257
                                move.account_id.currency_id.id, amount, context=context,
 
258
                                account=acc_cur)
 
259
                    val['amount_currency'] = amount_cur
 
260
 
 
261
                torec.append(account_move_line_obj.create(cursor, uid, val , context=context))
 
262
 
 
263
                if move.reconcile_id and move.reconcile_id.line_new_ids:
 
264
                    for newline in move.reconcile_id.line_new_ids:
 
265
                        account_move_line_obj.create(cursor, uid, {
 
266
                            'name': newline.name or move.name,
 
267
                            'date': move.date,
 
268
                            'ref': move.ref,
 
269
                            'move_id': move_id,
 
270
                            'partner_id': ((move.partner_id) and move.partner_id.id) or False,
 
271
                            'account_id': (newline.account_id) and newline.account_id.id,
 
272
                            'debit': newline.amount>0 and newline.amount or 0.0,
 
273
                            'credit': newline.amount<0 and -newline.amount or 0.0,
 
274
                            'statement_id': st.id,
 
275
                            'journal_id': st.journal_id.id,
 
276
                            'period_id': period_id,
 
277
                        }, context=context)
 
278
 
 
279
                # Fill the secondary amount/currency
 
280
                # if currency is not the same than the company
 
281
                amount_currency = False
 
282
                currency_id = False
 
283
                if st.currency.id <> company_currency_id:
 
284
                    amount_currency = move.amount
 
285
                    currency_id = st.currency.id
 
286
 
 
287
                account_move_line_obj.create(cursor, uid, {
 
288
                    'name': move.name,
 
289
                    'date': move.date,
 
290
                    'ref': move.ref,
 
291
                    'move_id': move_id,
 
292
                    'partner_id': ((move.partner_id) and move.partner_id.id) or False,
 
293
                    'account_id': account_id,
 
294
                    'credit': ((amount < 0) and -amount) or 0.0,
 
295
                    'debit': ((amount > 0) and amount) or 0.0,
 
296
                    'statement_id': st.id,
 
297
                    'journal_id': st.journal_id.id,
 
298
                    'period_id': period_id,
 
299
                    'amount_currency': amount_currency,
 
300
                    'currency_id': currency_id,
 
301
                    }, context=context)
 
302
 
 
303
                for line in account_move_line_obj.browse(cursor, uid, [x.id for x in 
 
304
                        account_move_obj.browse(cursor, uid, move_id, context=context).line_id
 
305
                        ], context=context):
 
306
                    if line.state != 'valid':
 
307
                        raise osv.except_osv(
 
308
                            _('Error !'),
 
309
                            _('Account move line "%s" is not valid')
 
310
                            % line.name
 
311
                        )
 
312
 
 
313
                if move.reconcile_id and move.reconcile_id.line_ids:
 
314
                    torec += map(lambda x: x.id, move.reconcile_id.line_ids)
 
315
                    #try:
 
316
                    if abs(move.reconcile_amount-move.amount)<0.0001:
 
317
                        account_move_line_obj.reconcile(
 
318
                            cursor, uid, torec, 'statement', context
 
319
                        )
 
320
                    else:
 
321
                        account_move_line_obj.reconcile_partial(
 
322
                            cursor, uid, torec, 'statement', context
 
323
                        )
 
324
                    #except:
 
325
                    #    raise osv.except_osv(
 
326
                    #        _('Error !'),
 
327
                    #        _('Unable to reconcile entry "%s": %.2f') %
 
328
                    #        (move.name, move.amount)
 
329
                    #    )
 
330
 
 
331
                if st.journal_id.entry_posted:
 
332
                    account_move_obj.write(cursor, uid, [move_id], {'state':'posted'})
 
333
            done.append(st.id)
 
334
        self.write(cursor, uid, done, {'state':'confirm'}, context=context)
 
335
        return True
 
336
 
 
337
account_bank_statement()
 
338
 
 
339
class account_bank_statement_line(osv.osv):
 
340
    '''
 
341
    Extension on basic class:
 
342
        1. Extra links to account.period and res.partner.bank for tracing and
 
343
           matching.
 
344
        2. Extra 'trans' field to carry the transaction id of the bank.
 
345
        3. Extra 'international' flag to indicate the missing of a remote
 
346
           account number. Some banks use seperate international banking
 
347
           modules that do not integrate with the standard transaction files.
 
348
        4. Readonly states for most fields except when in draft.
 
349
    '''
 
350
    _inherit = 'account.bank.statement.line'
 
351
    _description = 'Bank Transaction'
 
352
 
 
353
    def _get_period(self, cursor, uid, context={}):
 
354
        date = context.get('date') and context['date'] or None
 
355
        periods = self.pool.get('account.period').find(cursor, uid, dt=date)
 
356
        return periods and periods[0] or False
 
357
 
 
358
    def _seems_international(self, cursor, uid, context={}):
 
359
        '''
 
360
        Some banks have seperate international banking modules which do not
 
361
        translate correctly into the national formats. Instead, they
 
362
        leave key fields blank and signal this anomaly with a special
 
363
        transfer type.
 
364
        With the introduction of SEPA, this may worsen greatly, as SEPA
 
365
        payments are considered to be analogous to international payments
 
366
        by most local formats.
 
367
        '''
 
368
        # Quick and dirty check: if remote bank account is missing, assume
 
369
        # international transfer
 
370
        return not (
 
371
            context.get('partner_bank_id') and context['partner_bank_id']
 
372
        )
 
373
        # Not so dirty check: check if partner_id is set. If it is, check the
 
374
        # default/invoice addresses country. If it is the same as our
 
375
        # company's, its local, else international.
 
376
        # TODO: to be done
 
377
 
 
378
    _columns = {
 
379
        # Redefines
 
380
        'amount': fields.float('Amount', readonly=True,
 
381
                            states={'draft': [('readonly', False)]}),
 
382
        'ref': fields.char('Ref.', size=32, readonly=True,
 
383
                            states={'draft': [('readonly', False)]}),
 
384
        'name': fields.char('Name', size=64, required=True, readonly=True,
 
385
                            states={'draft': [('readonly', False)]}),
 
386
        'date': fields.date('Date', required=True, readonly=True,
 
387
                            states={'draft': [('readonly', False)]}),
 
388
        # New columns
 
389
        'trans': fields.char('Bank Transaction ID', size=15, required=False,
 
390
                            readonly=True,
 
391
                            states={'draft':[('readonly', False)]},
 
392
                            ),
 
393
        'partner_bank_id': fields.many2one('res.partner.bank', 'Bank Account',
 
394
                            required=False, readonly=True,
 
395
                            states={'draft':[('readonly', False)]},
 
396
                            ),
 
397
        'period_id': fields.many2one('account.period', 'Period', required=True,
 
398
                            states={'confirm': [('readonly', True)]}),
 
399
        # Not used yet, but usefull in the future.
 
400
        'international': fields.boolean('International Transaction',
 
401
                            required=False,
 
402
                            states={'confirm': [('readonly', True)]},
 
403
                            ),
 
404
    }
 
405
 
 
406
    _defaults = {
 
407
        'period_id': _get_period,
 
408
        'international': _seems_international,
 
409
    }
 
410
 
 
411
    def onchange_partner_id(self, cursor, uid, line_id, partner_id, type,
 
412
                            currency_id, context={}
 
413
                           ):
 
414
        if not partner_id:
 
415
            return {}
 
416
        users_obj = self.pool.get('res.users')
 
417
        partner_obj = self.pool.get('res.partner')
 
418
        
 
419
        company_currency_id = users_obj.browse(
 
420
                cursor, uid, uid, context=context
 
421
            ).company_id.currency_id.id
 
422
            
 
423
        if not currency_id:
 
424
            currency_id = company_currency_id
 
425
        
 
426
        partner = partner_obj.browse(cursor, uid, partner_id, context=context)
 
427
        if partner.supplier and not part.customer:
 
428
            account_id = part.property_account_payable.id
 
429
            type = 'supplier'
 
430
        elif partner.supplier and not part.customer:
 
431
            account_id =  part.property_account_receivable.id
 
432
            type = 'customer'
 
433
        else:
 
434
            account_id = 0
 
435
            type = 'general'
 
436
 
 
437
        return {'value': {'type': type, 'account_id': account_id}}
 
438
 
 
439
    def write(self, cursor, uid, ids, values, context={}):
 
440
        # TODO: Not sure what to do with this, as it seems that most of
 
441
        # this code is related to res_partner_bank and not to this class.
 
442
        account_numbers = []
 
443
        bank_obj = self.pool.get('res.partner.bank')
 
444
        statement_line_obj = self.pool.get('account.bank.statement.line')
 
445
 
 
446
        if 'partner_id' in values:
 
447
            bank_account_ids = bank_obj.search(cursor, uid,
 
448
                                               [('partner_id','=', values['partner_id'])]
 
449
                                              )
 
450
            bank_accounts = bank_obj.browse(cursor, uid, bank_account_ids)
 
451
            import_account_numbers = statement_line_obj.browse(cursor, uid, ids)
 
452
 
 
453
            for accno in bank_accounts:
 
454
                # Allow acc_number and iban to co-exist (SEPA will unite the
 
455
                # two, but - as seen now - in an uneven pace per country)
 
456
                if accno.acc_number:
 
457
                    account_numbers.append(accno.acc_number)
 
458
                if accno.iban:
 
459
                    account_numbers.append(accno.iban)
 
460
 
 
461
            if any([x for x in import_account_numbers if x.bank_accnumber in
 
462
                    account_numbers]):
 
463
                for accno in import_account_numbers:
 
464
                    account_data = _get_account_data(accno.bank_accnumber)
 
465
                    if account_data:
 
466
                        bank_id = bank_obj.search(cursor, uid, [
 
467
                            ('name', '=', account_data['bank_name'])
 
468
                        ])
 
469
                        if not bank_id:
 
470
                            bank_id = bank_obj.create(cursor, uid, {
 
471
                                'name': account_data['bank_name'],
 
472
                                'bic': account_data['bic'],
 
473
                                'active': 1,
 
474
                            })
 
475
                        else:
 
476
                            bank_id = bank_id[0]
 
477
 
 
478
                        bank_acc = bank_obj.create(cursor, uid, {
 
479
                            'state': 'bank',
 
480
                            'partner_id': values['partner_id'],
 
481
                            'bank': bank_id,
 
482
                            'acc_number': accno.bank_accnumber,
 
483
                        })
 
484
 
 
485
                        bank_iban = bank_obj.create(cursor, uid, {
 
486
                            'state': 'iban',
 
487
                            'partner_id': values['partner_id'],
 
488
                            'bank': bank_id,
 
489
                            'iban': account_data['iban'],
 
490
                        })
 
491
 
 
492
                    else:
 
493
                        bank_acc = bank_obj.create(cursor, uid, {
 
494
                            'state': 'bank',
 
495
                            'partner_id': values['partner_id'],
 
496
                            'acc_number': accno.bank_accnumber,
 
497
                        })
 
498
 
 
499
        return super(account_bank_statement_line, self).write(
 
500
            cursor, uid, ids, values, context
 
501
        )
 
502
 
 
503
account_bank_statement_line()
 
504
 
 
505
class payment_type(osv.osv):
 
506
    '''
 
507
    Make description field translatable #, add country context
 
508
    '''
 
509
    _inherit = 'payment.type'
 
510
    _columns = {
 
511
        'name': fields.char('Name', size=64, required=True, translate=True,
 
512
                            help='Payment Type'
 
513
                           ),
 
514
        #'country_id': fields.many2one('res.country', 'Country',
 
515
        #                    required=False,
 
516
        #                    help='Use this to limit this type to a specific country'
 
517
        #                   ),
 
518
    }
 
519
    #_defaults = {
 
520
    #    'country_id': lambda *a: False,
 
521
    #}
 
522
payment_type()
 
523
 
 
524
class payment_line(osv.osv):
 
525
    '''
 
526
    Add extra export_state and date_done fields; make destination bank account
 
527
    mandatory, as it makes no sense to send payments into thin air.
 
528
    '''
 
529
    _inherit = 'payment.line'
 
530
    _columns = {
 
531
        # New fields
 
532
        'bank_id': fields.many2one('res.partner.bank',
 
533
                                   'Destination Bank account',
 
534
                                   required=True
 
535
                                  ),
 
536
        'export_state': fields.selection([
 
537
            ('draft', 'Draft'),
 
538
            ('open','Confirmed'),
 
539
            ('cancel','Cancelled'),
 
540
            ('sent', 'Sent'),
 
541
            ('done','Done'),
 
542
            ], 'State', select=True
 
543
        ),
 
544
        # Redefined fields: added states
 
545
        'date_done': fields.datetime('Date Confirmed', select=True,
 
546
                                     readonly=True),
 
547
        'name': fields.char(
 
548
            'Your Reference', size=64, required=True,
 
549
            states={
 
550
                'sent': [('readonly', True)],
 
551
                'done': [('readonly', True)]
 
552
            },
 
553
        ),
 
554
        'communication': fields.char(
 
555
            'Communication', size=64, required=True, 
 
556
            help=("Used as the message between ordering customer and current "
 
557
                  "company. Depicts 'What do you want to say to the recipient"
 
558
                  " about this order ?'"
 
559
                 ),
 
560
            states={
 
561
                'sent': [('readonly', True)],
 
562
                'done': [('readonly', True)]
 
563
            },
 
564
        ),
 
565
        'communication2': fields.char(
 
566
            'Communication 2', size=64,
 
567
            help='The successor message of Communication.',
 
568
            states={
 
569
                'sent': [('readonly', True)],
 
570
                'done': [('readonly', True)]
 
571
            },
 
572
        ),
 
573
        'move_line_id': fields.many2one(
 
574
            'account.move.line', 'Entry line',
 
575
            domain=[('reconcile_id','=', False),
 
576
                    ('account_id.type', '=','payable')
 
577
                   ],
 
578
            help=('This Entry Line will be referred for the information of '
 
579
                  'the ordering customer.'
 
580
                 ),
 
581
            states={
 
582
                'sent': [('readonly', True)],
 
583
                'done': [('readonly', True)]
 
584
            },
 
585
        ),
 
586
        'amount_currency': fields.float(
 
587
            'Amount in Partner Currency', digits=(16,2),
 
588
            required=True,
 
589
            help='Payment amount in the partner currency',
 
590
            states={
 
591
                'sent': [('readonly', True)],
 
592
                'done': [('readonly', True)]
 
593
            },
 
594
        ),
 
595
        'currency': fields.many2one(
 
596
            'res.currency', 'Partner Currency', required=True,
 
597
            states={
 
598
                'sent': [('readonly', True)],
 
599
                'done': [('readonly', True)]
 
600
            },
 
601
        ),
 
602
        'bank_id': fields.many2one(
 
603
            'res.partner.bank', 'Destination Bank account',
 
604
            states={
 
605
                'sent': [('readonly', True)],
 
606
                'done': [('readonly', True)]
 
607
            },
 
608
        ),
 
609
        'order_id': fields.many2one(
 
610
            'payment.order', 'Order', required=True,
 
611
            ondelete='cascade', select=True,
 
612
            states={
 
613
                'sent': [('readonly', True)],
 
614
                'done': [('readonly', True)]
 
615
            },
 
616
        ),
 
617
        'partner_id': fields.many2one(
 
618
            'res.partner', string="Partner", required=True,
 
619
            help='The Ordering Customer',
 
620
            states={
 
621
                'sent': [('readonly', True)],
 
622
                'done': [('readonly', True)]
 
623
            },
 
624
        ),
 
625
        'date': fields.date(
 
626
            'Payment Date',
 
627
            help=("If no payment date is specified, the bank will treat this "
 
628
                  "payment line directly"
 
629
                 ),
 
630
            states={
 
631
                'sent': [('readonly', True)],
 
632
                'done': [('readonly', True)]
 
633
            },
 
634
        ),
 
635
        'state': fields.selection([
 
636
            ('normal','Free'),
 
637
            ('structured','Structured')
 
638
            ], 'Communication Type', required=True,
 
639
            states={
 
640
                'sent': [('readonly', True)],
 
641
                'done': [('readonly', True)]
 
642
            },
 
643
        ),
 
644
    }
 
645
    _defaults = {
 
646
        'export_state': lambda *a: 'draft',
 
647
        'date_done': lambda *a: False,
 
648
    }
 
649
payment_line()
 
650
 
 
651
class payment_order(osv.osv):
 
652
    '''
 
653
    Enable extra state for payment exports
 
654
    '''
 
655
    _inherit = 'payment.order'
 
656
    _columns = {
 
657
        'date_planned': fields.date(
 
658
            'Scheduled date if fixed',
 
659
            states={
 
660
                'sent': [('readonly', True)],
 
661
                'done': [('readonly', True)]
 
662
            },
 
663
            help='Select a date if you have chosen Preferred Date to be fixed.'
 
664
        ),
 
665
        'reference': fields.char(
 
666
            'Reference', size=128, required=True,
 
667
            states={
 
668
                'sent': [('readonly', True)],
 
669
                'done': [('readonly', True)]
 
670
            },
 
671
        ),
 
672
        'mode': fields.many2one(
 
673
            'payment.mode', 'Payment mode', select=True, required=True,
 
674
            states={
 
675
                'sent': [('readonly', True)],
 
676
                'done': [('readonly', True)]
 
677
            },
 
678
            help='Select the Payment Mode to be applied.'
 
679
        ),
 
680
        'state': fields.selection([
 
681
            ('draft', 'Draft'),
 
682
            ('open','Confirmed'),
 
683
            ('cancel','Cancelled'),
 
684
            ('sent', 'Sent'),
 
685
            ('done','Done'),
 
686
            ], 'State', select=True
 
687
        ),
 
688
        'line_ids': fields.one2many(
 
689
            'payment.line', 'order_id', 'Payment lines',
 
690
            states={
 
691
                'sent': [('readonly', True)],
 
692
                'done': [('readonly', True)]
 
693
            },
 
694
        ),
 
695
        'user_id': fields.many2one(
 
696
            'res.users','User', required=True,
 
697
            states={
 
698
                'sent': [('readonly', True)],
 
699
                'done': [('readonly', True)]
 
700
            },
 
701
        ),
 
702
        'date_prefered': fields.selection([
 
703
            ('now', 'Directly'),
 
704
            ('due', 'Due date'),
 
705
            ('fixed', 'Fixed date')
 
706
            ], "Preferred date", change_default=True, required=True,
 
707
            states={
 
708
                'sent': [('readonly', True)],
 
709
                'done': [('readonly', True)]
 
710
            },
 
711
            help=("Choose an option for the Payment Order:'Fixed' stands for a "
 
712
                  "date specified by you.'Directly' stands for the direct "
 
713
                  "execution.'Due date' stands for the scheduled date of "
 
714
                  "execution."
 
715
                 )
 
716
            ),
 
717
    }
 
718
 
 
719
    def set_to_draft(self, cr, uid, ids, *args):
 
720
        cr.execute("UPDATE payment_line "
 
721
                   "SET export_state = 'draft' "
 
722
                   "WHERE order_id in (%s)" % (
 
723
                       ','.join(map(str, ids))
 
724
                   ))
 
725
        return super(payment_order, self).set_to_draft(
 
726
            cr, uid, ids, *args
 
727
        )
 
728
 
 
729
    def action_sent(self, cr, uid, ids, *args):
 
730
        cr.execute("UPDATE payment_line "
 
731
                   "SET export_state = 'sent' "
 
732
                   "WHERE order_id in (%s)" % (
 
733
                       ','.join(map(str, ids))
 
734
                   ))
 
735
        return True
 
736
 
 
737
    def set_done(self, cr, uid, id, *args):
 
738
        '''
 
739
        Extend standard transition to update childs as well.
 
740
        '''
 
741
        cr.execute("UPDATE payment_line "
 
742
                   "SET export_state = 'done', date_done = '%s' "
 
743
                   "WHERE order_id = %s" % (
 
744
                       time.strftime('%Y-%m-%d'),
 
745
                       self.id
 
746
                   ))
 
747
        return super(payment_order, self).set_done(
 
748
            cr, uid, id, *args
 
749
        )
 
750
 
 
751
payment_order()
 
752
 
 
753
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: