~openobject-italia-core-devs/openobject-italia/italian-addons

« back to all changes in this revision

Viewing changes to account_fiscal_year_closing/wizard/wizard_run.py

  • Committer: eLBati
  • Date: 2011-09-28 07:31:37 UTC
  • mto: This revision was merged to the branch mainline in revision 133.
  • Revision ID: lorenzo.battistini@agilebg.com-20110928073137-0nxmw7f99won6p1f
[ADD] account_fiscal_year_closing

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
##############################################################################
 
3
#
 
4
#    OpenERP - Import operations model 347 engine
 
5
#    Copyright (C) 2009 Asr Oss. All Rights Reserved
 
6
#    $Id$
 
7
#
 
8
#    This program is free software: you can redistribute it and/or modify
 
9
#    it under the terms of the GNU Affero General Public License as published by
 
10
#    the Free Software Foundation, either version 3 of the License, or
 
11
#    (at your option) any later version.
 
12
#
 
13
#    This program is distributed in the hope that it will be useful,
 
14
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
15
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
16
#    GNU Affero General Public License for more details.
 
17
#
 
18
#    You should have received a copy of the GNU Affero General Public License
 
19
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
20
#
 
21
##############################################################################
 
22
 
 
23
"""
 
24
Create FYC entries wizards
 
25
"""
 
26
__author__ = """Borja López Soilán (Pexego) - borja@kami.es"""
 
27
 
 
28
from tools.translate import _
 
29
import wizard
 
30
import pooler
 
31
import time
 
32
import threading
 
33
import sql_db
 
34
import netsvc
 
35
import decimal_precision as dp
 
36
from tools import config
 
37
 
 
38
class wizard_run(wizard.interface):
 
39
    """
 
40
    Wizard to create the FYC entries.
 
41
    """
 
42
 
 
43
    ############################################################################
 
44
    # Forms
 
45
    ############################################################################
 
46
 
 
47
    _init_run_form = """<?xml version="1.0" encoding="utf-8"?>
 
48
    <form string="Fiscal Year Closing" colspan="4" width="400">
 
49
        <label string="This wizard will perform the selected operations." colspan="4"/>
 
50
        <label string="" colspan="4"/>
 
51
        <label string="It will create account moves for the operations you selected, skipping those already created." colspan="4"/>
 
52
        <label string="Non-selected operations will be canceled." colspan="4"/>
 
53
    </form>"""
 
54
 
 
55
    _init_cancel_form = """<?xml version="1.0" encoding="utf-8"?>
 
56
    <form string="Fiscal Year Closing" colspan="4" width="400">
 
57
        <label string="This wizard will cancel the selected operations." colspan="4"/>
 
58
        <label string="" colspan="4"/>
 
59
        <label string="It will remove the previously generated account moves." colspan="4"/>
 
60
        <label string="Closed periods, and the fiscal year, will be reopened." colspan="4"/>
 
61
    </form>"""
 
62
 
 
63
    _progress_form = '''<?xml version="1.0"?>
 
64
    <form string="Fiscal Year Closing - Working" colspan="4" width="400" auto_refresh="1">
 
65
        <label string="The process may take a while." colspan="4"/>
 
66
        <label string="" colspan="4"/>
 
67
        <field name="task_progress" widget="progressbar" colspan="4"/>
 
68
        <field name="progress" widget="progressbar" colspan="4"/>
 
69
    </form>'''
 
70
 
 
71
    _progress_fields = {
 
72
        'task_progress': { 'string': 'Task Progress', 'type':'float' },
 
73
        'progress': { 'string': 'Total Progress', 'type':'float' },
 
74
    }
 
75
 
 
76
 
 
77
    _done_form = """<?xml version="1.0" encoding="utf-8"?>
 
78
    <form string="Fiscal Year Closing - Done" colspan="4" width="400">
 
79
        <label string="The selected operations have been performed sucessfuly." colspan="4"/>
 
80
        <label string="" colspan="4"/>
 
81
    </form>"""
 
82
 
 
83
    _show_exception_form = """<?xml version="1.0" encoding="utf-8"?>
 
84
    <form string="Fiscal Year Closing - Error!" colspan="4" width="400">
 
85
        <label string="Error: One of the selected operations has failed!" colspan="4"/>
 
86
        <label string="" colspan="4"/>
 
87
        <separator string="Details"/>
 
88
        <field name="exception_text" colspan="4" nolabel="1"/>
 
89
    </form>"""
 
90
 
 
91
    _show_exception_fields = {
 
92
        'exception_text': {'string': 'Exception', 'type':'text' },
 
93
    }
 
94
 
 
95
    ############################################################################
 
96
    # CHECK OPERATIONS
 
97
    ############################################################################
 
98
 
 
99
    def _check_invalid_period_moves(self, cr, uid, fyc, data, context):
 
100
        """
 
101
        Checks for moves with invalid period on the fiscal year that is being closed
 
102
        """
 
103
        pool = pooler.get_pool(cr.dbname)
 
104
        data['process_task_progress'] = 0.0
 
105
 
 
106
        # Consider all the periods of the fiscal year.
 
107
        period_ids = [period.id for period in fyc.closing_fiscalyear_id.period_ids]
 
108
 
 
109
        # Find moves on the closing fiscal year with dates of previous years
 
110
        account_move_ids = pool.get('account.move').search(cr, uid, [
 
111
                                ('period_id', 'in', period_ids),
 
112
                                ('date', '<', fyc.closing_fiscalyear_id.date_start),
 
113
                            ], context=context)
 
114
 
 
115
        # Find moves on the closing fiscal year with dates of next years
 
116
        account_move_ids.extend(pool.get('account.move').search(cr, uid, [
 
117
                                ('period_id', 'in', period_ids),
 
118
                                ('date', '>', fyc.closing_fiscalyear_id.date_stop),
 
119
                            ], context=context))
 
120
 
 
121
        # Find moves not on the closing fiscal year with dates on its year
 
122
        account_move_ids.extend(pool.get('account.move').search(cr, uid, [
 
123
                                ('period_id', 'not in', period_ids),
 
124
                                ('date', '>=', fyc.closing_fiscalyear_id.date_start),
 
125
                                ('date', '<=', fyc.closing_fiscalyear_id.date_stop),
 
126
                            ], context=context))
 
127
 
 
128
        #
 
129
        # If one or more moves where found, raise an exception
 
130
        #
 
131
        if len(account_move_ids):
 
132
            invalid_period_moves = pool.get('account.move').browse(cr, uid, account_move_ids, context)
 
133
            str_invalid_period_moves = '\n'.join(['id: %s, date: %s, number: %s, ref: %s' % (move.id, move.date, move.name, move.ref) for move in invalid_period_moves])
 
134
            raise wizard.except_wizard(_('Error'), _('One or more moves with invalid period or date found on the fiscal year: \n%s') % str_invalid_period_moves)
 
135
 
 
136
        data['process_task_progress'] = 100.0
 
137
 
 
138
 
 
139
    def _check_draft_moves(self, cr, uid, fyc, data, context):
 
140
        """
 
141
        Checks for draft moves on the fiscal year that is being closed
 
142
        """
 
143
        pool = pooler.get_pool(cr.dbname)
 
144
        data['process_task_progress'] = 0.0
 
145
 
 
146
        #
 
147
        # Consider all the periods of the fiscal year *BUT* the L&P,
 
148
        # Net L&P and the Closing one.
 
149
        #
 
150
        period_ids = []
 
151
        for period in fyc.closing_fiscalyear_id.period_ids:
 
152
            if period.id != fyc.lp_period_id.id \
 
153
                    and period.id != fyc.nlp_period_id.id \
 
154
                    and period.id != fyc.c_period_id.id:
 
155
                period_ids.append(period.id)
 
156
 
 
157
        # Find the moves on the given periods
 
158
        account_move_ids = pool.get('account.move').search(cr, uid, [
 
159
                                ('period_id', 'in', period_ids),
 
160
                                ('state', '=', 'draft'),
 
161
                            ], context=context)
 
162
 
 
163
        #
 
164
        # If one or more draft moves where found, raise an exception
 
165
        #
 
166
        if len(account_move_ids):
 
167
            draft_moves = pool.get('account.move').browse(cr, uid, account_move_ids, context)
 
168
            str_draft_moves = '\n'.join(['id: %s, date: %s, number: %s, ref: %s' % (move.id, move.date, move.name, move.ref) for move in draft_moves])
 
169
            raise wizard.except_wizard(_('Error'), _('One or more draft moves found: \n%s') % str_draft_moves)
 
170
 
 
171
        data['process_task_progress'] = 100.0
 
172
 
 
173
 
 
174
    def _check_unbalanced_moves(self, cr, uid, fyc, data, context):
 
175
        """
 
176
        Checks for unbalanced moves on the fiscal year that is being closed
 
177
        """
 
178
        pool = pooler.get_pool(cr.dbname)
 
179
        data['process_task_progress'] = 0.0
 
180
 
 
181
        #
 
182
        # Consider all the periods of the fiscal year *BUT* the L&P,
 
183
        # Net L&P and the Closing one.
 
184
        #
 
185
        period_ids = []
 
186
        for period in fyc.closing_fiscalyear_id.period_ids:
 
187
            if period.id != fyc.lp_period_id.id \
 
188
                    and period.id != fyc.nlp_period_id.id \
 
189
                    and period.id != fyc.c_period_id.id:
 
190
                period_ids.append(period.id)
 
191
 
 
192
        # Find the moves on the given periods
 
193
        account_move_ids = pool.get('account.move').search(cr, uid, [
 
194
                                ('period_id', 'in', period_ids),
 
195
                                ('state', '!=', 'draft'),
 
196
                            ], context=context)
 
197
 
 
198
        #
 
199
        # For each found move, check it
 
200
        #
 
201
        unbalanced_moves = []
 
202
        total_accounts = len(account_move_ids)
 
203
        accounts_done = 0
 
204
        for move in pool.get('account.move').browse(cr, uid, account_move_ids, context):
 
205
            amount = 0
 
206
            for line in move.line_id:
 
207
                amount += (line.debit - line.credit)
 
208
 
 
209
            if round(abs(amount), pool.get('decimal.precision').precision_get(cr, uid, 'Account')) > 0:
 
210
                unbalanced_moves.append(move)
 
211
 
 
212
            accounts_done += 1
 
213
            data['process_task_progress'] = (accounts_done * 90.0) / total_accounts
 
214
 
 
215
        #
 
216
        # If one or more unbalanced moves where found, raise an exception
 
217
        #
 
218
        if len(unbalanced_moves):
 
219
            str_unbalanced_moves = '\n'.join(['id: %s, date: %s, number: %s, ref: %s' % (move.id, move.date, move.name, move.ref) for move in unbalanced_moves])
 
220
            raise wizard.except_wizard(_('Error'), _('One or more unbalanced moves found: \n%s') % str_unbalanced_moves)
 
221
 
 
222
        data['process_task_progress'] = 100.0
 
223
 
 
224
 
 
225
 
 
226
    ############################################################################
 
227
    # CLOSING/OPENING OPERATIONS
 
228
    ############################################################################
 
229
 
 
230
    def create_closing_move(self, cr, uid, operation, fyc, data, context):
 
231
        """
 
232
        Create a closing move (L&P, NL&P or Closing move).
 
233
        """
 
234
        pool = pooler.get_pool(cr.dbname)
 
235
 
 
236
        data['process_task_progress'] = 0.0
 
237
 
 
238
        move_lines = []
 
239
        dest_accounts_totals = {}
 
240
        period_ids = []
 
241
        account_mapping_ids = []
 
242
        description = None
 
243
        date = None
 
244
        period_id = None
 
245
        journal_id = None
 
246
        fiscalyear_id = fyc.closing_fiscalyear_id.id
 
247
 
 
248
        #
 
249
        # Depending on the operation we will use different data
 
250
        #
 
251
        if operation == 'loss_and_profit':
 
252
            #
 
253
            # Consider all the periods of the fiscal year *BUT* the L&P,
 
254
            # Net L&P and the Closing one.
 
255
            #
 
256
            for period in fyc.closing_fiscalyear_id.period_ids:
 
257
                if period.id != fyc.lp_period_id.id \
 
258
                        and period.id != fyc.nlp_period_id.id \
 
259
                        and period.id != fyc.c_period_id.id:
 
260
                    period_ids.append(period.id)
 
261
            #
 
262
            # Set the accounts to use
 
263
            #
 
264
            account_mapping_ids = fyc.lp_account_mapping_ids
 
265
            for account_map in account_mapping_ids:
 
266
                if not account_map.dest_account_id:
 
267
                    raise wizard.except_wizard(_('UserError'), _("The L&P account mappings are not properly configured: %s") % account_map.name)
 
268
 
 
269
            #
 
270
            # Get the values for the lines
 
271
            #
 
272
            if not fyc.lp_description:
 
273
                raise wizard.except_wizard(_('UserError'), _("The L&P description must be defined"))
 
274
            if not fyc.lp_date:
 
275
                raise wizard.except_wizard(_('UserError'), _("The L&P date must be defined"))
 
276
            if not (fyc.lp_period_id and fyc.lp_period_id.id):
 
277
                raise wizard.except_wizard(_('UserError'), _("The L&P period must be defined"))
 
278
            if not (fyc.lp_journal_id and fyc.lp_journal_id.id):
 
279
                raise wizard.except_wizard(_('UserError'), _("The L&P journal must be defined"))
 
280
            description = fyc.lp_description
 
281
            date = fyc.lp_date
 
282
            period_id = fyc.lp_period_id.id
 
283
            journal_id = fyc.lp_journal_id.id
 
284
        elif operation == 'net_loss_and_profit':
 
285
            #
 
286
            # Consider all the periods of the fiscal year *BUT* the 
 
287
            # Net L&P and the Closing one.
 
288
            #
 
289
            for period in fyc.closing_fiscalyear_id.period_ids:
 
290
                if period.id != fyc.nlp_period_id.id \
 
291
                        and period.id != fyc.c_period_id.id:
 
292
                    period_ids.append(period.id)
 
293
            #
 
294
            # Set the accounts to use
 
295
            #
 
296
            account_mapping_ids = fyc.nlp_account_mapping_ids
 
297
            for account_map in account_mapping_ids:
 
298
                if not account_map.dest_account_id:
 
299
                    raise wizard.except_wizard(_('UserError'), _("The Net L&P account mappings are not properly configured: %s") % account_map.name)
 
300
            #
 
301
            # Get the values for the lines
 
302
            #
 
303
            if not fyc.nlp_description:
 
304
                raise wizard.except_wizard(_('UserError'), _("The Net L&P description must be defined"))
 
305
            if not fyc.nlp_date:
 
306
                raise wizard.except_wizard(_('UserError'), _("The Net L&P date must be defined"))
 
307
            if not (fyc.nlp_period_id and fyc.nlp_period_id.id):
 
308
                raise wizard.except_wizard(_('UserError'), _("The Net L&P period must be defined"))
 
309
            if not (fyc.nlp_journal_id and fyc.nlp_journal_id.id):
 
310
                raise wizard.except_wizard(_('UserError'), _("The Net L&P journal must be defined"))
 
311
            description = fyc.nlp_description
 
312
            date = fyc.nlp_date
 
313
            period_id = fyc.nlp_period_id.id
 
314
            journal_id = fyc.nlp_journal_id.id
 
315
        elif operation == 'close':
 
316
            # Require the user to have performed the L&P operation
 
317
            if not (fyc.loss_and_profit_move_id and fyc.loss_and_profit_move_id.id):
 
318
                raise wizard.except_wizard(_('UserError'), _("The L&P move must exist before creating the closing one"))
 
319
            #
 
320
            # Consider all the periods of the fiscal year *BUT* the Closing one.
 
321
            #
 
322
            for period in fyc.closing_fiscalyear_id.period_ids:
 
323
                if period.id != fyc.c_period_id.id:
 
324
                    period_ids.append(period.id)
 
325
            # Set the accounts to use
 
326
            account_mapping_ids = fyc.c_account_mapping_ids
 
327
            #
 
328
            # Get the values for the lines
 
329
            #
 
330
            if not fyc.c_description:
 
331
                raise wizard.except_wizard(_('UserError'), _("The closing description must be defined"))
 
332
            if not fyc.c_date:
 
333
                raise wizard.except_wizard(_('UserError'), _("The closing date must be defined"))
 
334
            if not (fyc.c_period_id and fyc.c_period_id.id):
 
335
                raise wizard.except_wizard(_('UserError'), _("The closing period must be defined"))
 
336
            if not (fyc.c_journal_id and fyc.c_journal_id.id):
 
337
                raise wizard.except_wizard(_('UserError'), _("The closing journal must be defined"))
 
338
            description = fyc.c_description
 
339
            date = fyc.c_date
 
340
            period_id = fyc.c_period_id.id
 
341
            journal_id = fyc.c_journal_id.id
 
342
        else:
 
343
            assert operation in ('loss_and_profit', 'net_loss_and_profit', 'close'), "The operation must be a supported one"
 
344
 
 
345
 
 
346
        #
 
347
        # For each (parent) account in the mapping list
 
348
        #
 
349
        total_accounts = len(account_mapping_ids)
 
350
        accounts_done = 0
 
351
        for account_map in account_mapping_ids:
 
352
            # Init (if needed) the dictionary that will store the totals for the dest accounts
 
353
            if account_map.dest_account_id:
 
354
                dest_accounts_totals[account_map.dest_account_id.id] = dest_accounts_totals.get(account_map.dest_account_id.id, 0)
 
355
 
 
356
            context.update({'fiscalyear': fiscalyear_id, 'periods': period_ids})
 
357
            ctx = context.copy()
 
358
 
 
359
            # Find its children accounts (recursively)
 
360
            # FIXME: _get_children_and_consol is a protected member of account_account but the OpenERP code base uses it like this :(
 
361
            child_ids = pool.get('account.account')._get_children_and_consol(cr, uid, [account_map.source_account_id.id], ctx)
 
362
            
 
363
            # For each children account. (Notice the context filter! the computed balanced is based on this filter)
 
364
            for account in pool.get('account.account').browse(cr, uid, child_ids, ctx):
 
365
                # Check if the children account needs to (and can) be closed
 
366
                # Note: We currently ignore the close_method (account.user_type.close_method)
 
367
                #       and always do a balance close.
 
368
                if account.type != 'view': 
 
369
                    # Compute the balance for the account (uses the previous browse context filter)
 
370
                    balance = account.balance
 
371
                    # Check if the balance is greater than the limit
 
372
                    if round(abs(balance), pool.get('decimal.precision').precision_get(cr, uid, 'Account')) > 0:
 
373
                        #
 
374
                        # Add a new line to the move
 
375
                        #
 
376
                        move_lines.append({
 
377
                                'account_id': account.id,
 
378
                                'debit': balance<0 and -balance,
 
379
                                'credit': balance>0 and balance,
 
380
                                'name': description,
 
381
                                'date': date,
 
382
                                'period_id': period_id,
 
383
                                'journal_id': journal_id,
 
384
                            })
 
385
 
 
386
                        # Update the dest account total (with the inverse of the balance)
 
387
                        if account_map.dest_account_id:
 
388
                            dest_accounts_totals[account_map.dest_account_id.id] -= balance
 
389
            accounts_done += 1
 
390
            data['process_task_progress'] = (accounts_done * 90.0) / total_accounts
 
391
 
 
392
        #
 
393
        # Add the dest lines
 
394
        #
 
395
        for dest_account_id in dest_accounts_totals.keys():
 
396
            balance = dest_accounts_totals[dest_account_id]
 
397
            move_lines.append({
 
398
                    'account_id': dest_account_id,
 
399
                    'debit': balance<0 and -balance,
 
400
                    'credit': balance>0 and balance,
 
401
                    'name': description,
 
402
                    'date': date,
 
403
                    'period_id': period_id,
 
404
                    'journal_id': journal_id,
 
405
                })
 
406
        data['process_task_progress'] = 95.0
 
407
 
 
408
        #
 
409
        # Finally create the account move with all the lines (if needed)
 
410
        #
 
411
        if len(move_lines):
 
412
            move_id = pool.get('account.move').create(cr, uid, {
 
413
                            'ref': description,
 
414
                            'date': date,
 
415
                            'period_id': period_id,
 
416
                            'journal_id': journal_id,
 
417
                            'line_id': [(0,0,line) for line in move_lines],
 
418
                        }, context=context)
 
419
        else:
 
420
            move_id = None
 
421
        data['process_task_progress'] = 99.0
 
422
 
 
423
        #
 
424
        # Save the reference to the created account move into the fyc object
 
425
        #
 
426
        if operation == 'loss_and_profit':
 
427
            pool.get('account_fiscal_year_closing.fyc').write(cr, uid, [fyc.id], { 'loss_and_profit_move_id': move_id })
 
428
        elif operation == 'net_loss_and_profit':
 
429
            pool.get('account_fiscal_year_closing.fyc').write(cr, uid, [fyc.id], { 'net_loss_and_profit_move_id': move_id })
 
430
        elif operation == 'close':
 
431
            pool.get('account_fiscal_year_closing.fyc').write(cr, uid, [fyc.id], { 'closing_move_id': move_id })
 
432
        else:
 
433
            assert operation in ('loss_and_profit', 'net_loss_and_profit', 'close'), "The operation must be a supported one"
 
434
 
 
435
        data['process_task_progress'] = 100.0
 
436
        return move_id
 
437
 
 
438
 
 
439
    def create_opening_move(self, cr, uid, operation, fyc, data, context):
 
440
        """
 
441
        Create an opening move (based on the closing one)
 
442
        """
 
443
        pool = pooler.get_pool(cr.dbname)
 
444
        data['process_task_progress'] = 0.0
 
445
 
 
446
        move_lines = []
 
447
        description = None
 
448
        date = None
 
449
        period_id = None
 
450
        journal_id = None
 
451
        closing_move = None
 
452
 
 
453
        #
 
454
        # Depending on the operation we will use one or other closing move
 
455
        # as the base for the opening move.
 
456
        # Note: Yes, currently only one 'closing' move exists,
 
457
        #       but I want this to be extensible :)
 
458
        #
 
459
        if operation == 'open':
 
460
            closing_move = fyc.closing_move_id
 
461
            # Require the user to have performed the closing operation
 
462
            if not (closing_move and closing_move.id):
 
463
                raise wizard.except_wizard(_('UserError'), _("The closing move must exist to create the opening one"))
 
464
            if not closing_move.line_id:
 
465
                raise wizard.except_wizard(_('UserError'), _("The closing move shouldn't be empty"))
 
466
            #
 
467
            # Get the values for the lines
 
468
            #
 
469
            if not fyc.o_description:
 
470
                raise wizard.except_wizard(_('UserError'), _("The opening description must be defined"))
 
471
            if not fyc.o_date:
 
472
                raise wizard.except_wizard(_('UserError'), _("The opening date must be defined"))
 
473
            if not (fyc.o_period_id and fyc.o_period_id.id):
 
474
                raise wizard.except_wizard(_('UserError'), _("The opening period must be defined"))
 
475
            if not (fyc.o_journal_id and fyc.o_journal_id.id):
 
476
                raise wizard.except_wizard(_('UserError'), _("The opening journal must be defined"))
 
477
            description = fyc.o_description
 
478
            date = fyc.o_date
 
479
            period_id = fyc.o_period_id.id
 
480
            journal_id = fyc.o_journal_id.id
 
481
        else:
 
482
            assert operation in ('open'), "The operation must be a supported one"
 
483
 
 
484
        #
 
485
        # Read the lines from the closing move, and append the inverse lines
 
486
        # to the opening move lines.
 
487
        #
 
488
        total_accounts = len(closing_move.line_id)
 
489
        accounts_done = 0
 
490
        for line in closing_move.line_id:
 
491
            move_lines.append({
 
492
                    'account_id': line.account_id.id,
 
493
                    'debit': line.credit,
 
494
                    'credit': line.debit,
 
495
                    'name': description,
 
496
                    'date': date,
 
497
                    'period_id': period_id,
 
498
                    'journal_id': journal_id,
 
499
                })
 
500
            accounts_done += 1
 
501
            data['process_task_progress'] = (accounts_done * 90.0) / total_accounts
 
502
 
 
503
        #
 
504
        # Finally create the account move with all the lines (if needed)
 
505
        #
 
506
        if len(move_lines):
 
507
            move_id = pool.get('account.move').create(cr, uid, {
 
508
                            'ref': description,
 
509
                            'date': date,
 
510
                            'period_id': period_id,
 
511
                            'journal_id': journal_id,
 
512
                            'line_id': [(0,0,line) for line in move_lines],
 
513
                        }, context=context)
 
514
        else:
 
515
            move_id = None
 
516
        data['process_task_progress'] = 99.0
 
517
 
 
518
        #
 
519
        # Save the reference to the created account move into the fyc object
 
520
        #
 
521
        if operation == 'open':
 
522
            pool.get('account_fiscal_year_closing.fyc').write(cr, uid, [fyc.id], { 'opening_move_id': move_id })
 
523
        else:
 
524
            assert operation in ('open'), "The operation must be a supported one"
 
525
 
 
526
        data['process_task_progress'] = 100.0
 
527
        return move_id
 
528
 
 
529
 
 
530
    def remove_move(self, cr, uid, operation, fyc, data, context):
 
531
        """
 
532
        Remove a account move (L&P, NL&P, Closing or Opening move)
 
533
        """
 
534
        pool = pooler.get_pool(cr.dbname)
 
535
        data['process_task_progress'] = 0.0
 
536
 
 
537
        #
 
538
        # Depending on the operation we will delete one or other move
 
539
        #
 
540
        move = None
 
541
        if operation == 'loss_and_profit':
 
542
            move = fyc.loss_and_profit_move_id
 
543
            pool.get('account_fiscal_year_closing.fyc').write(cr, uid, fyc.id, { 'loss_and_profit_move_id': None })
 
544
        elif operation == 'net_loss_and_profit':
 
545
            move = fyc.net_loss_and_profit_move_id
 
546
            pool.get('account_fiscal_year_closing.fyc').write(cr, uid, fyc.id, { 'net_loss_and_profit_move_id': None })
 
547
        elif operation == 'close':
 
548
            move = fyc.closing_move_id
 
549
            pool.get('account_fiscal_year_closing.fyc').write(cr, uid, fyc.id, { 'closing_move_id': None })
 
550
        elif operation == 'open':
 
551
            move = fyc.opening_move_id
 
552
            pool.get('account_fiscal_year_closing.fyc').write(cr, uid, fyc.id, { 'opening_move_id': None })
 
553
        else:
 
554
            assert operation in ('loss_and_profit', 'net_loss_and_profit', 'close', 'open'), "The operation must be a supported one"
 
555
        data['process_task_progress'] = 15.0
 
556
 
 
557
        assert move and move.id, "The move to delete must be defined"
 
558
 
 
559
        #
 
560
        # Unreconcile the move if needed
 
561
        #
 
562
        reconcile_ids = []
 
563
        for line in move.line_id:
 
564
            if line.reconcile_id and (line.reconcile_id.id not in reconcile_ids):
 
565
                reconcile_ids.append(line.reconcile_id.id)
 
566
            if line.reconcile_partial_id and (line.reconcile_partial_id.id not in reconcile_ids):
 
567
                reconcile_ids.append(line.reconcile_partial_id.id)
 
568
        if reconcile_ids:
 
569
            pool.get('account.move.reconcile').unlink(cr, uid, reconcile_ids, context)
 
570
        data['process_task_progress'] = 30.0
 
571
 
 
572
        #
 
573
        # Remove the move after changing it's state to draft
 
574
        #
 
575
        pool.get('account.move').write(cr, uid, [move.id], {'state': 'draft'}, context)
 
576
        pool.get('account.move').unlink(cr, uid, [move.id], context)
 
577
 
 
578
        data['process_task_progress'] = 100.0
 
579
        return move.id
 
580
 
 
581
 
 
582
    ############################################################################
 
583
    # Wizard Actions
 
584
    ############################################################################
 
585
 
 
586
    def _init_choice(self, cr, uid, data, context):
 
587
        """
 
588
        Choice-like action that checks whether the operations must be run
 
589
        or canceled.
 
590
        """
 
591
        if context is None:
 
592
            context = {}
 
593
        if context.get('cancel_mode'):
 
594
            data['cancel_mode'] = True
 
595
            return 'init_cancel'
 
596
        else:
 
597
            data['cancel_mode'] = False
 
598
            return 'init_run'
 
599
        
 
600
 
 
601
    def _run(self, db_name, uid, data, context=None):
 
602
        """
 
603
        Creates / removes FYC entries
 
604
        """
 
605
        data['process_progress'] = 0
 
606
        data['process_task_progress'] = 0
 
607
        data['process_task'] = None
 
608
        try:
 
609
            conn = sql_db.db_connect(db_name)
 
610
            cr = conn.cursor()
 
611
            pool = pooler.get_pool(cr.dbname)
 
612
 
 
613
            #
 
614
            # If the wizard is in cancel mode, run the objects cancel action
 
615
            # to let it undo the confirmation action, before running the wizard.
 
616
            #
 
617
            if data.get('cancel_mode'):
 
618
                wf_service = netsvc.LocalService("workflow")
 
619
                wf_service.trg_validate(uid, 'account_fiscal_year_closing.fyc', data['id'], 'cancel', cr)
 
620
 
 
621
            # Read the object
 
622
            fyc = pool.get('account_fiscal_year_closing.fyc').browse(cr, uid, data['id'], context=context)
 
623
 
 
624
            #
 
625
            # Calculate the operations to perform (needed to calculate the progress)
 
626
            #
 
627
            total_operations = 0
 
628
            operations_done = 0
 
629
            if fyc.check_invalid_period_moves:
 
630
                total_operations += 1
 
631
            if fyc.check_draft_moves:
 
632
                total_operations += 1
 
633
            if fyc.check_unbalanced_moves:
 
634
                total_operations += 1
 
635
            if (fyc.create_loss_and_profit and not fyc.loss_and_profit_move_id) \
 
636
                or ((not fyc.create_loss_and_profit) and fyc.loss_and_profit_move_id):
 
637
                total_operations += 1
 
638
            if (fyc.create_net_loss_and_profit and not fyc.net_loss_and_profit_move_id) \
 
639
                or ((not fyc.create_net_loss_and_profit) and fyc.net_loss_and_profit_move_id):
 
640
                total_operations += 1
 
641
            if (fyc.create_closing and not fyc.closing_move_id) \
 
642
                or ((not fyc.create_closing) and fyc.closing_move_id):
 
643
                total_operations += 1
 
644
            if (fyc.create_opening and not fyc.opening_move_id) \
 
645
                or ((not fyc.create_opening) and fyc.opening_move_id):
 
646
                total_operations += 1
 
647
                
 
648
            if total_operations > 0:
 
649
 
 
650
                #
 
651
                # Check for invalid period moves if needed
 
652
                #
 
653
                if fyc.check_invalid_period_moves:
 
654
                    data['process_task'] = 'Check invalid period/date moves'
 
655
                    self._check_invalid_period_moves(cr, uid, fyc, data, context)
 
656
                    operations_done += 1
 
657
                    data['process_progress'] = (operations_done * 100.0) / total_operations
 
658
 
 
659
                #
 
660
                # Check for draft moves if needed
 
661
                #
 
662
                if fyc.check_draft_moves:
 
663
                    data['process_task'] = 'Check draft moves'
 
664
                    self._check_draft_moves(cr, uid, fyc, data, context)
 
665
                    operations_done += 1
 
666
                    data['process_progress'] = (operations_done * 100.0) / total_operations
 
667
 
 
668
                #
 
669
                # Check for unbalanced moves if needed
 
670
                #
 
671
                if fyc.check_unbalanced_moves:
 
672
                    data['process_task'] = 'Check unbalanced moves'
 
673
                    self._check_unbalanced_moves(cr, uid, fyc, data, context)
 
674
                    operations_done += 1
 
675
                    data['process_progress'] = (operations_done * 100.0) / total_operations
 
676
 
 
677
                #
 
678
                # Create L&P move if needed
 
679
                #
 
680
                if fyc.create_loss_and_profit and not fyc.loss_and_profit_move_id:
 
681
                    data['process_task'] = 'Create L&P move'
 
682
                    self.create_closing_move(cr, uid, 'loss_and_profit', fyc, data, context)
 
683
                    operations_done += 1
 
684
                    data['process_progress'] = (operations_done * 100.0) / total_operations
 
685
                #
 
686
                # Remove the L&P move if needed
 
687
                #
 
688
                if (not fyc.create_loss_and_profit) and fyc.loss_and_profit_move_id:
 
689
                    data['process_task'] = 'Remove L&P move'
 
690
                    self.remove_move(cr, uid, 'loss_and_profit', fyc, data, context)
 
691
                    operations_done += 1
 
692
                    data['process_progress'] = (operations_done * 100.0) / total_operations
 
693
 
 
694
                # Refresh the cached fyc object
 
695
                fyc = pool.get('account_fiscal_year_closing.fyc').browse(cr, uid, data['id'], context=context)
 
696
 
 
697
 
 
698
                #
 
699
                # Create the Net L&P move if needed
 
700
                #
 
701
                if fyc.create_net_loss_and_profit and not fyc.net_loss_and_profit_move_id:
 
702
                    data['process_task'] = 'Create NL&P move'
 
703
                    self.create_closing_move(cr, uid, 'net_loss_and_profit', fyc, data, context)
 
704
                    operations_done += 1
 
705
                    data['process_progress'] = (operations_done * 100.0) / total_operations
 
706
                #
 
707
                # Remove the Net L&P move if needed
 
708
                #
 
709
                if (not fyc.create_net_loss_and_profit) and fyc.net_loss_and_profit_move_id:
 
710
                    data['process_task'] = 'Remove NL&P move'
 
711
                    self.remove_move(cr, uid, 'net_loss_and_profit', fyc, data, context)
 
712
                    operations_done += 1
 
713
                    data['process_progress'] = (operations_done * 100.0) / total_operations
 
714
 
 
715
                # Refresh the cached fyc object
 
716
                fyc = pool.get('account_fiscal_year_closing.fyc').browse(cr, uid, data['id'], context=context)
 
717
 
 
718
 
 
719
                #
 
720
                # Create the closing move if needed
 
721
                #
 
722
                if fyc.create_closing and not fyc.closing_move_id:
 
723
                    data['process_task'] = 'Create closing move'
 
724
                    self.create_closing_move(cr, uid, 'close', fyc, data, context)
 
725
                    operations_done += 1
 
726
                    data['process_progress'] = (operations_done * 100.0) / total_operations
 
727
                #
 
728
                # Remove the closing move if needed
 
729
                #
 
730
                if (not fyc.create_closing) and fyc.closing_move_id:
 
731
                    data['process_task'] = 'Remove closing move'
 
732
                    self.remove_move(cr, uid, 'close', fyc, data, context)
 
733
                    operations_done += 1
 
734
                    data['process_progress'] = (operations_done * 100.0) / total_operations
 
735
 
 
736
                # Refresh the cached fyc object
 
737
                fyc = pool.get('account_fiscal_year_closing.fyc').browse(cr, uid, data['id'], context=context)
 
738
 
 
739
                
 
740
                #
 
741
                # Create the opening move if needed
 
742
                #
 
743
                if fyc.create_opening and not fyc.opening_move_id:
 
744
                    data['process_task'] = 'Create opening move'
 
745
                    self.create_opening_move(cr, uid, 'open', fyc, data, context)
 
746
                    operations_done += 1
 
747
                    data['process_progress'] = (operations_done * 100.0) / total_operations
 
748
                #
 
749
                # Remove the opening move if needed
 
750
                #
 
751
                if (not fyc.create_opening) and fyc.opening_move_id:
 
752
                    data['process_task'] = 'Remove opening move'
 
753
                    self.remove_move(cr, uid, 'open', fyc, data, context)
 
754
                    operations_done += 1
 
755
                    data['process_progress'] = (operations_done * 100.0) / total_operations
 
756
 
 
757
            #
 
758
            # Set the as done (if not in cancel_mode)
 
759
            #
 
760
            if not data.get('cancel_mode'):
 
761
                wf_service = netsvc.LocalService("workflow")
 
762
                wf_service.trg_validate(uid, 'account_fiscal_year_closing.fyc', fyc.id, 'run', cr)
 
763
 
 
764
            data['process_progress'] = 100
 
765
            cr.commit()
 
766
        except Exception, ex:
 
767
            data['process_exception'] = ex
 
768
            cr.rollback()
 
769
            raise
 
770
        finally:
 
771
            cr.close()
 
772
            data['process_done'] = True
 
773
        return {}
 
774
 
 
775
 
 
776
    def _run_in_background_choice(self, cr, uid, data, context):
 
777
        """
 
778
        Choice-like action that runs the process on background,
 
779
        waiting for it to end or timeout.
 
780
        """
 
781
        if not data.get('process_thread'):
 
782
            # Run the calculation in background
 
783
            data['process_done'] = False
 
784
            data['process_exception'] = None
 
785
            data['process_thread'] = threading.Thread(target=self._run, args=(cr.dbname, uid, data, context))
 
786
            data['process_thread'].start()
 
787
        #
 
788
        # Wait up some seconds seconds for the task to end.
 
789
        #
 
790
        time_left = 20
 
791
        while not data['process_done'] and time_left > 0:
 
792
            time_left = time_left - 1
 
793
            time.sleep(1)
 
794
            message = "Fiscal year closing progress: %s%% (%s: %s%%)" % (data.get('process_progress'), data.get('process_task'), data.get('process_task_progress'))
 
795
            netsvc.Logger().notifyChannel('fyc', netsvc.LOG_DEBUG, message)
 
796
        #
 
797
        # Check if we are done
 
798
        #
 
799
        if data['process_done']:
 
800
            if data['process_exception']:
 
801
                return 'show_exception'
 
802
            else:
 
803
                return 'done'
 
804
        else:
 
805
            return 'progress'
 
806
 
 
807
 
 
808
    def _progress_action(self, cr, uid, data, context):
 
809
        """
 
810
        Action that gets the current progress
 
811
        """
 
812
        return {
 
813
            'task_progress': data['process_task_progress'],
 
814
            'progress': data['process_progress']
 
815
        }
 
816
 
 
817
    def _show_exception_action(self, cr, uid, data, context):
 
818
        """
 
819
        Action that gets the calculation exception text
 
820
        """
 
821
        exception_text = ''
 
822
        if data.get('process_exception'):
 
823
            if isinstance(data['process_exception'], wizard.except_wizard):
 
824
                exception_text = data['process_exception'].value
 
825
            else:
 
826
                try:
 
827
                    exception_text = unicode(data['process_exception'])
 
828
                except:
 
829
                    exception_text = str(data['process_exception'])
 
830
        return { 'exception_text': exception_text }
 
831
 
 
832
    ############################################################################
 
833
    # States
 
834
    ############################################################################
 
835
 
 
836
    states = {
 
837
        'init': {
 
838
            'actions': [],
 
839
            'result': {'type': 'choice', 'next_state': _init_choice}
 
840
        },
 
841
        'init_run': {
 
842
            'actions': [],
 
843
            'result': {'type':'form', 'arch': _init_run_form, 'fields': {}, 'state':[('end', 'Cancel', 'gtk-cancel', True), ('run', 'Run', 'gtk-apply', True)]}
 
844
        },
 
845
        'init_cancel': {
 
846
            'actions': [],
 
847
            'result': {'type':'form', 'arch': _init_cancel_form, 'fields': {}, 'state':[('end', 'Cancel', 'gtk-cancel', True), ('run', 'Run', 'gtk-apply', True)]}
 
848
        },
 
849
        'run': {
 
850
            'actions': [],
 
851
            'result': {'type': 'choice', 'next_state': _run_in_background_choice}
 
852
        },
 
853
        'progress': {
 
854
            'actions': [_progress_action],
 
855
            'result': {'type': 'form', 'arch': _progress_form, 'fields': _progress_fields, 'state':[('end','Close (continues in background)', 'gtk-cancel', True),('run','Keep waiting', 'gtk-go-forward', True)]}
 
856
        },
 
857
        'done': {
 
858
            'actions': [],
 
859
            'result': {'type': 'form', 'arch': _done_form, 'fields': {}, 'state':[('end','Done', 'gtk-ok', True)]}
 
860
        },
 
861
        'show_exception': {
 
862
            'actions': [_show_exception_action],
 
863
            'result': {'type': 'form', 'arch': _show_exception_form, 'fields': _show_exception_fields, 'state':[('end','Done', 'gtk-ok', True)]}
 
864
        }
 
865
    }
 
866
 
 
867
 
 
868
wizard_run('account_fiscal_year_closing.wizard_run')
 
869
 
 
870
 
 
871
class wizard_cancel(wizard_run):
 
872
    """
 
873
    Wizard to remove the FYC entries.
 
874
    """
 
875
 
 
876
    def _init_choice(self, cr, uid, data, context):
 
877
        """
 
878
        Choice-like action that checks whether the operations must be run
 
879
        or canceled. => Always cancel on wizard_cancel.
 
880
        """
 
881
        data['cancel_mode'] = True
 
882
        return 'init_cancel'
 
883
 
 
884
    states = wizard_run.states.copy()
 
885
    
 
886
    states['init'] = {
 
887
            'actions': [],
 
888
            'result': {'type': 'choice', 'next_state': _init_choice}
 
889
        }
 
890
 
 
891
 
 
892
wizard_cancel('account_fiscal_year_closing.wizard_cancel')
 
893