~openerp-spain-team/openerp-spain/6.0-git

« back to all changes in this revision

Viewing changes to l10n_es_cierre_ejercicio/wizard/wizard_run.py

  • Committer: Borja L.S.
  • Date: 2010-10-18 10:04:25 UTC
  • Revision ID: git-v1:271c47a993616dbba60585d48b8b98d603199d93
[REF] *: Refactorización para portar a 6.0 - Paso 1.

- Se han renombrado los módulos para usar la nomenclatura propuesta
  por OpenERP: l10n_es para el módulo base de localización (plan de 
  cuentas), l10n_es_* para el resto de módulos.

- Se eliminan los módulos extra_addons/* que deberían moverse a 
  los extra-addons genéricos (no son específicos de España).

- Se renombran los __terp__.py por __openerp__.py

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
# -*- encoding: utf-8 -*-
 
3
##############################################################################
 
4
#
 
5
#    OpenERP - Import operations model 347 engine
 
6
#    Copyright (C) 2009 Asr Oss. All Rights Reserved
 
7
#    $Id$
 
8
#
 
9
#    This program is free software: you can redistribute it and/or modify
 
10
#    it under the terms of the GNU General Public License as published by
 
11
#    the Free Software Foundation, either version 3 of the License, or
 
12
#    (at your option) any later version.
 
13
#
 
14
#    This program is distributed in the hope that it will be useful,
 
15
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
16
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
17
#    GNU General Public License for more details.
 
18
#
 
19
#    You should have received a copy of the GNU General Public License
 
20
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
21
#
 
22
##############################################################################
 
23
 
 
24
"""
 
25
Create FYC entries wizards
 
26
"""
 
27
__author__ = """Borja López Soilán (Pexego) - borjals@pexego.es"""
 
28
 
 
29
from tools.translate import _
 
30
import wizard
 
31
import pooler
 
32
import time
 
33
import threading
 
34
import sql_db
 
35
import netsvc
 
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">
 
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 abs(amount) > 0.5 * 10 ** -int(config['price_accuracy']):
 
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
            # Find its children accounts (recursively)
 
357
            # FIXME: _get_children_and_consol is a protected member of account_account but the OpenERP code base uses it like this :(
 
358
            child_ids = pool.get('account.account')._get_children_and_consol(cr, uid, [account_map.source_account_id.id], context)
 
359
 
 
360
            # For each children account. (Notice the context filter! the computed balanced is based on this filter)
 
361
            for account in pool.get('account.account').browse(cr, uid, child_ids, context={'fiscalyear': fiscalyear_id, 'periods': period_ids}):
 
362
                # Check if the children account needs to (and can) be closed
 
363
                # Note: We currently ignore the close_method (account.user_type.close_method)
 
364
                #       and always do a balance close.
 
365
                if account.type != 'view': 
 
366
                    # Compute the balance for the account (uses the previous browse context filter)
 
367
                    balance = account.balance
 
368
                    # Check if the balance is greater than the limit
 
369
                    if abs(balance) >= 0.5 * 10 ** -int(config['price_accuracy']):
 
370
                        #
 
371
                        # Add a new line to the move
 
372
                        #
 
373
                        move_lines.append({
 
374
                                'account_id': account.id,
 
375
                                'debit': balance<0 and -balance,
 
376
                                'credit': balance>0 and balance,
 
377
                                'name': description,
 
378
                                'date': date,
 
379
                                'period_id': period_id,
 
380
                                'journal_id': journal_id,
 
381
                            })
 
382
 
 
383
                        # Update the dest account total (with the inverse of the balance)
 
384
                        if account_map.dest_account_id:
 
385
                            dest_accounts_totals[account_map.dest_account_id.id] -= balance
 
386
            accounts_done += 1
 
387
            data['process_task_progress'] = (accounts_done * 90.0) / total_accounts
 
388
 
 
389
        #
 
390
        # Add the dest lines
 
391
        #
 
392
        for dest_account_id in dest_accounts_totals.keys():
 
393
            balance = dest_accounts_totals[dest_account_id]
 
394
            move_lines.append({
 
395
                    'account_id': dest_account_id,
 
396
                    'debit': balance<0 and -balance,
 
397
                    'credit': balance>0 and balance,
 
398
                    'name': description,
 
399
                    'date': date,
 
400
                    'period_id': period_id,
 
401
                    'journal_id': journal_id,
 
402
                })
 
403
        data['process_task_progress'] = 95.0
 
404
 
 
405
        #
 
406
        # Finally create the account move with all the lines (if needed)
 
407
        #
 
408
        if len(move_lines):
 
409
            move_id = pool.get('account.move').create(cr, uid, {
 
410
                            'ref': description,
 
411
                            'date': date,
 
412
                            'period_id': period_id,
 
413
                            'journal_id': journal_id,
 
414
                            'line_id': [(0,0,line) for line in move_lines],
 
415
                        }, context=context)
 
416
        else:
 
417
            move_id = None
 
418
        data['process_task_progress'] = 99.0
 
419
 
 
420
        #
 
421
        # Save the reference to the created account move into the fyc object
 
422
        #
 
423
        if operation == 'loss_and_profit':
 
424
            pool.get('l10n_es_cierre_ejercicio.fyc').write(cr, uid, [fyc.id], { 'loss_and_profit_move_id': move_id })
 
425
        elif operation == 'net_loss_and_profit':
 
426
            pool.get('l10n_es_cierre_ejercicio.fyc').write(cr, uid, [fyc.id], { 'net_loss_and_profit_move_id': move_id })
 
427
        elif operation == 'close':
 
428
            pool.get('l10n_es_cierre_ejercicio.fyc').write(cr, uid, [fyc.id], { 'closing_move_id': move_id })
 
429
        else:
 
430
            assert operation in ('loss_and_profit', 'net_loss_and_profit', 'close'), "The operation must be a supported one"
 
431
 
 
432
        data['process_task_progress'] = 100.0
 
433
        return move_id
 
434
 
 
435
 
 
436
    def create_opening_move(self, cr, uid, operation, fyc, data, context):
 
437
        """
 
438
        Create an opening move (based on the closing one)
 
439
        """
 
440
        pool = pooler.get_pool(cr.dbname)
 
441
        data['process_task_progress'] = 0.0
 
442
 
 
443
        move_lines = []
 
444
        description = None
 
445
        date = None
 
446
        period_id = None
 
447
        journal_id = None
 
448
        closing_move = None
 
449
 
 
450
        #
 
451
        # Depending on the operation we will use one or other closing move
 
452
        # as the base for the opening move.
 
453
        # Note: Yes, currently only one 'closing' move exists,
 
454
        #       but I want this to be extensible :)
 
455
        #
 
456
        if operation == 'open':
 
457
            closing_move = fyc.closing_move_id
 
458
            # Require the user to have performed the closing operation
 
459
            if not (closing_move and closing_move.id):
 
460
                raise wizard.except_wizard(_('UserError'), _("The closing move must exist to create the opening one"))
 
461
            if not closing_move.line_id:
 
462
                raise wizard.except_wizard(_('UserError'), _("The closing move shouldn't be empty"))
 
463
            #
 
464
            # Get the values for the lines
 
465
            #
 
466
            if not fyc.o_description:
 
467
                raise wizard.except_wizard(_('UserError'), _("The opening description must be defined"))
 
468
            if not fyc.o_date:
 
469
                raise wizard.except_wizard(_('UserError'), _("The opening date must be defined"))
 
470
            if not (fyc.o_period_id and fyc.o_period_id.id):
 
471
                raise wizard.except_wizard(_('UserError'), _("The opening period must be defined"))
 
472
            if not (fyc.o_journal_id and fyc.o_journal_id.id):
 
473
                raise wizard.except_wizard(_('UserError'), _("The opening journal must be defined"))
 
474
            description = fyc.o_description
 
475
            date = fyc.o_date
 
476
            period_id = fyc.o_period_id.id
 
477
            journal_id = fyc.o_journal_id.id
 
478
        else:
 
479
            assert operation in ('open'), "The operation must be a supported one"
 
480
 
 
481
        #
 
482
        # Read the lines from the closing move, and append the inverse lines
 
483
        # to the opening move lines.
 
484
        #
 
485
        total_accounts = len(closing_move.line_id)
 
486
        accounts_done = 0
 
487
        for line in closing_move.line_id:
 
488
            move_lines.append({
 
489
                    'account_id': line.account_id.id,
 
490
                    'debit': line.credit,
 
491
                    'credit': line.debit,
 
492
                    'name': description,
 
493
                    'date': date,
 
494
                    'period_id': period_id,
 
495
                    'journal_id': journal_id,
 
496
                })
 
497
            accounts_done += 1
 
498
            data['process_task_progress'] = (accounts_done * 90.0) / total_accounts
 
499
 
 
500
        #
 
501
        # Finally create the account move with all the lines (if needed)
 
502
        #
 
503
        if len(move_lines):
 
504
            move_id = pool.get('account.move').create(cr, uid, {
 
505
                            'ref': description,
 
506
                            'date': date,
 
507
                            'period_id': period_id,
 
508
                            'journal_id': journal_id,
 
509
                            'line_id': [(0,0,line) for line in move_lines],
 
510
                        }, context=context)
 
511
        else:
 
512
            move_id = None
 
513
        data['process_task_progress'] = 99.0
 
514
 
 
515
        #
 
516
        # Save the reference to the created account move into the fyc object
 
517
        #
 
518
        if operation == 'open':
 
519
            pool.get('l10n_es_cierre_ejercicio.fyc').write(cr, uid, [fyc.id], { 'opening_move_id': move_id })
 
520
        else:
 
521
            assert operation in ('open'), "The operation must be a supported one"
 
522
 
 
523
        data['process_task_progress'] = 100.0
 
524
        return move_id
 
525
 
 
526
 
 
527
    def remove_move(self, cr, uid, operation, fyc, data, context):
 
528
        """
 
529
        Remove a account move (L&P, NL&P, Closing or Opening move)
 
530
        """
 
531
        pool = pooler.get_pool(cr.dbname)
 
532
        data['process_task_progress'] = 0.0
 
533
 
 
534
        #
 
535
        # Depending on the operation we will delete one or other move
 
536
        #
 
537
        move = None
 
538
        if operation == 'loss_and_profit':
 
539
            move = fyc.loss_and_profit_move_id
 
540
            pool.get('l10n_es_cierre_ejercicio.fyc').write(cr, uid, fyc.id, { 'loss_and_profit_move_id': None })
 
541
        elif operation == 'net_loss_and_profit':
 
542
            move = fyc.net_loss_and_profit_move_id
 
543
            pool.get('l10n_es_cierre_ejercicio.fyc').write(cr, uid, fyc.id, { 'net_loss_and_profit_move_id': None })
 
544
        elif operation == 'close':
 
545
            move = fyc.closing_move_id
 
546
            pool.get('l10n_es_cierre_ejercicio.fyc').write(cr, uid, fyc.id, { 'closing_move_id': None })
 
547
        elif operation == 'open':
 
548
            move = fyc.opening_move_id
 
549
            pool.get('l10n_es_cierre_ejercicio.fyc').write(cr, uid, fyc.id, { 'opening_move_id': None })
 
550
        else:
 
551
            assert operation in ('loss_and_profit', 'net_loss_and_profit', 'close', 'open'), "The operation must be a supported one"
 
552
        data['process_task_progress'] = 15.0
 
553
 
 
554
        assert move and move.id, "The move to delete must be defined"
 
555
 
 
556
        #
 
557
        # Unreconcile the move if needed
 
558
        #
 
559
        reconcile_ids = []
 
560
        for line in move.line_id:
 
561
            if line.reconcile_id and (line.reconcile_id.id not in reconcile_ids):
 
562
                reconcile_ids.append(line.reconcile_id.id)
 
563
            if line.reconcile_partial_id and (line.reconcile_partial_id.id not in reconcile_ids):
 
564
                reconcile_ids.append(line.reconcile_partial_id.id)
 
565
        if reconcile_ids:
 
566
            pool.get('account.move.reconcile').unlink(cr, uid, reconcile_ids, context)
 
567
        data['process_task_progress'] = 30.0
 
568
 
 
569
        #
 
570
        # Remove the move after changing it's state to draft
 
571
        #
 
572
        pool.get('account.move').write(cr, uid, [move.id], {'state': 'draft'}, context)
 
573
        pool.get('account.move').unlink(cr, uid, [move.id], context)
 
574
 
 
575
        data['process_task_progress'] = 100.0
 
576
        return move.id
 
577
 
 
578
 
 
579
    ############################################################################
 
580
    # Wizard Actions
 
581
    ############################################################################
 
582
 
 
583
    def _init_choice(self, cr, uid, data, context):
 
584
        """
 
585
        Choice-like action that checks whether the operations must be run
 
586
        or canceled.
 
587
        """
 
588
        if context is None:
 
589
            context = {}
 
590
        if context.get('cancel_mode'):
 
591
            data['cancel_mode'] = True
 
592
            return 'init_cancel'
 
593
        else:
 
594
            data['cancel_mode'] = False
 
595
            return 'init_run'
 
596
        
 
597
 
 
598
    def _run(self, db_name, uid, data, context=None):
 
599
        """
 
600
        Creates / removes FYC entries
 
601
        """
 
602
        data['process_progress'] = 0
 
603
        data['process_task_progress'] = 0
 
604
        data['process_task'] = None
 
605
        try:
 
606
            conn = sql_db.db_connect(db_name)
 
607
            cr = conn.cursor()
 
608
            pool = pooler.get_pool(cr.dbname)
 
609
 
 
610
            #
 
611
            # If the wizard is in cancel mode, run the objects cancel action
 
612
            # to let it undo the confirmation action, before running the wizard.
 
613
            #
 
614
            if data.get('cancel_mode'):
 
615
                wf_service = netsvc.LocalService("workflow")
 
616
                wf_service.trg_validate(uid, 'l10n_es_cierre_ejercicio.fyc', data['id'], 'cancel', cr)
 
617
 
 
618
            # Read the object
 
619
            fyc = pool.get('l10n_es_cierre_ejercicio.fyc').browse(cr, uid, data['id'], context=context)
 
620
 
 
621
            #
 
622
            # Calculate the operations to perform (needed to calculate the progress)
 
623
            #
 
624
            total_operations = 0
 
625
            operations_done = 0
 
626
            if fyc.check_invalid_period_moves:
 
627
                total_operations += 1
 
628
            if fyc.check_draft_moves:
 
629
                total_operations += 1
 
630
            if fyc.check_unbalanced_moves:
 
631
                total_operations += 1
 
632
            if (fyc.create_loss_and_profit and not fyc.loss_and_profit_move_id) \
 
633
                or ((not fyc.create_loss_and_profit) and fyc.loss_and_profit_move_id):
 
634
                total_operations += 1
 
635
            if (fyc.create_net_loss_and_profit and not fyc.net_loss_and_profit_move_id) \
 
636
                or ((not fyc.create_net_loss_and_profit) and fyc.net_loss_and_profit_move_id):
 
637
                total_operations += 1
 
638
            if (fyc.create_closing and not fyc.closing_move_id) \
 
639
                or ((not fyc.create_closing) and fyc.closing_move_id):
 
640
                total_operations += 1
 
641
            if (fyc.create_opening and not fyc.opening_move_id) \
 
642
                or ((not fyc.create_opening) and fyc.opening_move_id):
 
643
                total_operations += 1
 
644
                
 
645
            if total_operations > 0:
 
646
                #
 
647
                # Check for invalid period moves if needed
 
648
                #
 
649
                if fyc.check_invalid_period_moves:
 
650
                    data['process_task'] = 'Check invalid period/date moves'
 
651
                    self._check_invalid_period_moves(cr, uid, fyc, data, context)
 
652
                    operations_done += 1
 
653
                    data['process_progress'] = (operations_done * 100.0) / total_operations
 
654
 
 
655
                #
 
656
                # Check for draft moves if needed
 
657
                #
 
658
                if fyc.check_draft_moves:
 
659
                    data['process_task'] = 'Check draft moves'
 
660
                    self._check_draft_moves(cr, uid, fyc, data, context)
 
661
                    operations_done += 1
 
662
                    data['process_progress'] = (operations_done * 100.0) / total_operations
 
663
 
 
664
                #
 
665
                # Check for unbalanced moves if needed
 
666
                #
 
667
                if fyc.check_unbalanced_moves:
 
668
                    data['process_task'] = 'Check unbalanced moves'
 
669
                    self._check_unbalanced_moves(cr, uid, fyc, data, context)
 
670
                    operations_done += 1
 
671
                    data['process_progress'] = (operations_done * 100.0) / total_operations
 
672
 
 
673
                #
 
674
                # Create L&P move if needed
 
675
                #
 
676
                if fyc.create_loss_and_profit and not fyc.loss_and_profit_move_id:
 
677
                    data['process_task'] = 'Create L&P move'
 
678
                    self.create_closing_move(cr, uid, 'loss_and_profit', fyc, data, context)
 
679
                    operations_done += 1
 
680
                    data['process_progress'] = (operations_done * 100.0) / total_operations
 
681
                #
 
682
                # Remove the L&P move if needed
 
683
                #
 
684
                if (not fyc.create_loss_and_profit) and fyc.loss_and_profit_move_id:
 
685
                    data['process_task'] = 'Remove L&P move'
 
686
                    self.remove_move(cr, uid, 'loss_and_profit', fyc, data, context)
 
687
                    operations_done += 1
 
688
                    data['process_progress'] = (operations_done * 100.0) / total_operations
 
689
 
 
690
                # Refresh the cached fyc object
 
691
                fyc = pool.get('l10n_es_cierre_ejercicio.fyc').browse(cr, uid, data['id'], context=context)
 
692
 
 
693
 
 
694
                #
 
695
                # Create the Net L&P move if needed
 
696
                #
 
697
                if fyc.create_net_loss_and_profit and not fyc.net_loss_and_profit_move_id:
 
698
                    data['process_task'] = 'Create NL&P move'
 
699
                    self.create_closing_move(cr, uid, 'net_loss_and_profit', fyc, data, context)
 
700
                    operations_done += 1
 
701
                    data['process_progress'] = (operations_done * 100.0) / total_operations
 
702
                #
 
703
                # Remove the Net L&P move if needed
 
704
                #
 
705
                if (not fyc.create_net_loss_and_profit) and fyc.net_loss_and_profit_move_id:
 
706
                    data['process_task'] = 'Remove NL&P move'
 
707
                    self.remove_move(cr, uid, 'net_loss_and_profit', fyc, data, context)
 
708
                    operations_done += 1
 
709
                    data['process_progress'] = (operations_done * 100.0) / total_operations
 
710
 
 
711
                # Refresh the cached fyc object
 
712
                fyc = pool.get('l10n_es_cierre_ejercicio.fyc').browse(cr, uid, data['id'], context=context)
 
713
 
 
714
 
 
715
                #
 
716
                # Create the closing move if needed
 
717
                #
 
718
                if fyc.create_closing and not fyc.closing_move_id:
 
719
                    data['process_task'] = 'Create closing move'
 
720
                    self.create_closing_move(cr, uid, 'close', fyc, data, context)
 
721
                    operations_done += 1
 
722
                    data['process_progress'] = (operations_done * 100.0) / total_operations
 
723
                #
 
724
                # Remove the closing move if needed
 
725
                #
 
726
                if (not fyc.create_closing) and fyc.closing_move_id:
 
727
                    data['process_task'] = 'Remove closing move'
 
728
                    self.remove_move(cr, uid, 'close', fyc, data, context)
 
729
                    operations_done += 1
 
730
                    data['process_progress'] = (operations_done * 100.0) / total_operations
 
731
 
 
732
                # Refresh the cached fyc object
 
733
                fyc = pool.get('l10n_es_cierre_ejercicio.fyc').browse(cr, uid, data['id'], context=context)
 
734
 
 
735
                
 
736
                #
 
737
                # Create the opening move if needed
 
738
                #
 
739
                if fyc.create_opening and not fyc.opening_move_id:
 
740
                    data['process_task'] = 'Create opening move'
 
741
                    self.create_opening_move(cr, uid, 'open', fyc, data, context)
 
742
                    operations_done += 1
 
743
                    data['process_progress'] = (operations_done * 100.0) / total_operations
 
744
                #
 
745
                # Remove the opening move if needed
 
746
                #
 
747
                if (not fyc.create_opening) and fyc.opening_move_id:
 
748
                    data['process_task'] = 'Remove opening move'
 
749
                    self.remove_move(cr, uid, 'open', fyc, data, context)
 
750
                    operations_done += 1
 
751
                    data['process_progress'] = (operations_done * 100.0) / total_operations
 
752
 
 
753
            #
 
754
            # Set the as done (if not in cancel_mode)
 
755
            #
 
756
            if not data.get('cancel_mode'):
 
757
                wf_service = netsvc.LocalService("workflow")
 
758
                wf_service.trg_validate(uid, 'l10n_es_cierre_ejercicio.fyc', fyc.id, 'run', cr)
 
759
 
 
760
            data['process_progress'] = 100
 
761
            cr.commit()
 
762
        except Exception, ex:
 
763
            data['process_exception'] = ex
 
764
            cr.rollback()
 
765
            raise
 
766
        finally:
 
767
            cr.close()
 
768
            data['process_done'] = True
 
769
        return {}
 
770
 
 
771
 
 
772
    def _run_in_background_choice(self, cr, uid, data, context):
 
773
        """
 
774
        Choice-like action that runs the process on background,
 
775
        waiting for it to end or timeout.
 
776
        """
 
777
        if not data.get('process_thread'):
 
778
            # Run the calculation in background
 
779
            data['process_done'] = False
 
780
            data['process_exception'] = None
 
781
            data['process_thread'] = threading.Thread(target=self._run, args=(cr.dbname, uid, data, context))
 
782
            data['process_thread'].start()
 
783
        #
 
784
        # Wait up some seconds seconds for the task to end.
 
785
        #
 
786
        time_left = 20
 
787
        while not data['process_done'] and time_left > 0:
 
788
            time_left = time_left - 1
 
789
            time.sleep(1)
 
790
            message = "Fiscal year closing progress: %s%% (%s: %s%%)" % (data.get('process_progress'), data.get('process_task'), data.get('process_task_progress'))
 
791
            netsvc.Logger().notifyChannel('fyc', netsvc.LOG_DEBUG, message)
 
792
        #
 
793
        # Check if we are done
 
794
        #
 
795
        if data['process_done']:
 
796
            if data['process_exception']:
 
797
                return 'show_exception'
 
798
            else:
 
799
                return 'done'
 
800
        else:
 
801
            return 'progress'
 
802
 
 
803
 
 
804
    def _progress_action(self, cr, uid, data, context):
 
805
        """
 
806
        Action that gets the current progress
 
807
        """
 
808
        return {
 
809
            'task_progress': data['process_task_progress'],
 
810
            'progress': data['process_progress']
 
811
        }
 
812
 
 
813
    def _show_exception_action(self, cr, uid, data, context):
 
814
        """
 
815
        Action that gets the calculation exception text
 
816
        """
 
817
        exception_text = ''
 
818
        if data.get('process_exception'):
 
819
            if isinstance(data['process_exception'], wizard.except_wizard):
 
820
                exception_text = data['process_exception'].value
 
821
            else:
 
822
                try:
 
823
                    exception_text = unicode(data['process_exception'])
 
824
                except:
 
825
                    exception_text = str(data['process_exception'])
 
826
        return { 'exception_text': exception_text }
 
827
 
 
828
    ############################################################################
 
829
    # States
 
830
    ############################################################################
 
831
 
 
832
    states = {
 
833
        'init': {
 
834
            'actions': [],
 
835
            'result': {'type': 'choice', 'next_state': _init_choice}
 
836
        },
 
837
        'init_run': {
 
838
            'actions': [],
 
839
            'result': {'type':'form', 'arch': _init_run_form, 'fields': {}, 'state':[('end', 'Cancel', 'gtk-cancel', True), ('run', 'Run', 'gtk-apply', True)]}
 
840
        },
 
841
        'init_cancel': {
 
842
            'actions': [],
 
843
            'result': {'type':'form', 'arch': _init_cancel_form, 'fields': {}, 'state':[('end', 'Cancel', 'gtk-cancel', True), ('run', 'Run', 'gtk-apply', True)]}
 
844
        },
 
845
        'run': {
 
846
            'actions': [],
 
847
            'result': {'type': 'choice', 'next_state': _run_in_background_choice}
 
848
        },
 
849
        'progress': {
 
850
            'actions': [_progress_action],
 
851
            '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)]}
 
852
        },
 
853
        'done': {
 
854
            'actions': [],
 
855
            'result': {'type': 'form', 'arch': _done_form, 'fields': {}, 'state':[('end','Done', 'gtk-ok', True)]}
 
856
        },
 
857
        'show_exception': {
 
858
            'actions': [_show_exception_action],
 
859
            'result': {'type': 'form', 'arch': _show_exception_form, 'fields': _show_exception_fields, 'state':[('end','Done', 'gtk-ok', True)]}
 
860
        }
 
861
    }
 
862
 
 
863
 
 
864
wizard_run('l10n_es_cierre_ejercicio.wizard_run')
 
865
 
 
866
 
 
867
class wizard_cancel(wizard_run):
 
868
    """
 
869
    Wizard to remove the FYC entries.
 
870
    """
 
871
 
 
872
    def _init_choice(self, cr, uid, data, context):
 
873
        """
 
874
        Choice-like action that checks whether the operations must be run
 
875
        or canceled. => Always cancel on wizard_cancel.
 
876
        """
 
877
        data['cancel_mode'] = True
 
878
        return 'init_cancel'
 
879
 
 
880
    states = wizard_run.states.copy()
 
881
    
 
882
    states['init'] = {
 
883
            'actions': [],
 
884
            'result': {'type': 'choice', 'next_state': _init_choice}
 
885
        }
 
886
 
 
887
 
 
888
wizard_cancel('l10n_es_cierre_ejercicio.wizard_cancel')
 
889