1
# -*- coding: utf-8 -*-
2
##############################################################################
4
# OpenERP - Import operations model 347 engine
5
# Copyright (C) 2009 Asr Oss. All Rights Reserved
6
# Copyright (C) 2012 Agile Business Group sagl (<http://www.agilebg.com>)
7
# Copyright (C) 2012 Domsense srl (<http://www.domsense.com>)
9
# This program is free software: you can redistribute it and/or modify
10
# it under the terms of the GNU Affero 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.
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 Affero General Public License for more details.
19
# You should have received a copy of the GNU Affero General Public License
20
# along with this program. If not, see <http://www.gnu.org/licenses/>.
22
##############################################################################
25
Create FYC entries wizards
28
from tools.translate import _
30
import decimal_precision as dp
31
from osv import fields, osv
33
class wizard_run(osv.osv_memory):
35
Wizard to create the FYC entries.
40
def run(self, cr, uid, ids, context=None):
42
Creates / removes FYC entries
46
active_id = context and context.get('active_id', False) or False
48
raise osv.except_osv(_('Error'), _('No active ID found'))
50
fyc = pool.get('account_fiscal_year_closing.fyc').browse(cr, uid, active_id, context=context)
53
# Check for invalid period moves if needed
55
if fyc.check_invalid_period_moves:
56
self._check_invalid_period_moves(cr, uid, fyc, context)
59
# Check for draft moves if needed
61
if fyc.check_draft_moves:
62
self._check_draft_moves(cr, uid, fyc, context)
65
# Check for unbalanced moves if needed
67
if fyc.check_unbalanced_moves:
68
self._check_unbalanced_moves(cr, uid, fyc, context)
71
# Create L&P move if needed
73
if fyc.create_loss_and_profit and not fyc.loss_and_profit_move_id:
74
self.create_closing_move(cr, uid, 'loss_and_profit', fyc, context)
76
# Remove the L&P move if needed
78
if (not fyc.create_loss_and_profit) and fyc.loss_and_profit_move_id:
79
self.remove_move(cr, uid, 'loss_and_profit', fyc, context)
81
# Refresh the cached fyc object
82
fyc = pool.get('account_fiscal_year_closing.fyc').browse(cr, uid, active_id, context=context)
86
# Create the Net L&P move if needed
88
if fyc.create_net_loss_and_profit and not fyc.net_loss_and_profit_move_id:
89
self.create_closing_move(cr, uid, 'net_loss_and_profit', fyc, context)
91
# Remove the Net L&P move if needed
93
if (not fyc.create_net_loss_and_profit) and fyc.net_loss_and_profit_move_id:
94
self.remove_move(cr, uid, 'net_loss_and_profit', fyc, context)
96
# Refresh the cached fyc object
97
fyc = pool.get('account_fiscal_year_closing.fyc').browse(cr, uid, active_id, context=context)
101
# Create the closing move if needed
103
if fyc.create_closing and not fyc.closing_move_id:
104
self.create_closing_move(cr, uid, 'close', fyc, context)
106
# Remove the closing move if needed
108
if (not fyc.create_closing) and fyc.closing_move_id:
109
self.remove_move(cr, uid, 'close', fyc, context)
111
# Refresh the cached fyc object
112
fyc = pool.get('account_fiscal_year_closing.fyc').browse(cr, uid, active_id, context=context)
116
# Create the opening move if needed
118
if fyc.create_opening and not fyc.opening_move_id:
119
self.create_opening_move(cr, uid, 'open', fyc, context)
121
# Remove the opening move if needed
123
if (not fyc.create_opening) and fyc.opening_move_id:
124
self.remove_move(cr, uid, 'open', fyc, context)
127
# Set the fyc as done (if not in cancel_mode)
129
if not fyc.create_opening and not fyc.create_closing and not not fyc.create_net_loss_and_profit and not fyc.create_loss_and_profit:
130
wf_service = netsvc.LocalService("workflow")
131
wf_service.trg_validate(uid, 'account_fiscal_year_closing.fyc', fyc.id, 'cancel', cr)
133
wf_service = netsvc.LocalService("workflow")
134
wf_service.trg_validate(uid, 'account_fiscal_year_closing.fyc', fyc.id, 'run', cr)
136
return {'type': 'ir.actions.act_window_close'}
140
############################################################################
142
############################################################################
144
def _check_invalid_period_moves(self, cr, uid, fyc, context):
146
Checks for moves with invalid period on the fiscal year that is being closed
150
# Consider all the periods of the fiscal year.
151
period_ids = [period.id for period in fyc.closing_fiscalyear_id.period_ids]
153
# Find moves on the closing fiscal year with dates of previous years
154
account_move_ids = pool.get('account.move').search(cr, uid, [
155
('period_id', 'in', period_ids),
156
('date', '<', fyc.closing_fiscalyear_id.date_start),
159
# Find moves on the closing fiscal year with dates of next years
160
account_move_ids.extend(pool.get('account.move').search(cr, uid, [
161
('period_id', 'in', period_ids),
162
('date', '>', fyc.closing_fiscalyear_id.date_stop),
165
# Find moves not on the closing fiscal year with dates on its year
166
account_move_ids.extend(pool.get('account.move').search(cr, uid, [
167
('period_id', 'not in', period_ids),
168
('date', '>=', fyc.closing_fiscalyear_id.date_start),
169
('date', '<=', fyc.closing_fiscalyear_id.date_stop),
173
# If one or more moves where found, raise an exception
175
if len(account_move_ids):
176
invalid_period_moves = pool.get('account.move').browse(cr, uid, account_move_ids, context)
177
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])
178
raise osv.except_osv(_('Error'), _('One or more moves with invalid period or date found on the fiscal year: \n%s') % str_invalid_period_moves)
181
def _check_draft_moves(self, cr, uid, fyc, context):
183
Checks for draft moves on the fiscal year that is being closed
188
# Consider all the periods of the fiscal year *BUT* the L&P,
189
# Net L&P and the Closing one.
192
for period in fyc.closing_fiscalyear_id.period_ids:
193
if period.id != fyc.lp_period_id.id \
194
and period.id != fyc.nlp_period_id.id \
195
and period.id != fyc.c_period_id.id:
196
period_ids.append(period.id)
198
# Find the moves on the given periods
199
account_move_ids = pool.get('account.move').search(cr, uid, [
200
('period_id', 'in', period_ids),
201
('state', '=', 'draft'),
205
# If one or more draft moves where found, raise an exception
207
if len(account_move_ids):
208
draft_moves = pool.get('account.move').browse(cr, uid, account_move_ids, context)
209
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])
210
raise osv.except_osv(_('Error'), _('One or more draft moves found: \n%s') % str_draft_moves)
212
def _check_unbalanced_moves(self, cr, uid, fyc, context):
214
Checks for unbalanced moves on the fiscal year that is being closed
219
# Consider all the periods of the fiscal year *BUT* the L&P,
220
# Net L&P and the Closing one.
223
for period in fyc.closing_fiscalyear_id.period_ids:
224
if period.id != fyc.lp_period_id.id \
225
and period.id != fyc.nlp_period_id.id \
226
and period.id != fyc.c_period_id.id:
227
period_ids.append(period.id)
229
# Find the moves on the given periods
230
account_move_ids = pool.get('account.move').search(cr, uid, [
231
('period_id', 'in', period_ids),
232
('state', '!=', 'draft'),
236
# For each found move, check it
238
unbalanced_moves = []
239
total_accounts = len(account_move_ids)
240
for move in pool.get('account.move').browse(cr, uid, account_move_ids, context):
242
for line in move.line_id:
243
amount += (line.debit - line.credit)
245
if round(abs(amount), pool.get('decimal.precision').precision_get(cr, uid, 'Account')) > 0:
246
unbalanced_moves.append(move)
249
# If one or more unbalanced moves where found, raise an exception
251
if len(unbalanced_moves):
252
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])
253
raise osv.except_osv(_('Error'), _('One or more unbalanced moves found: \n%s') % str_unbalanced_moves)
256
############################################################################
257
# CLOSING/OPENING OPERATIONS
258
############################################################################
260
def create_closing_move(self, cr, uid, operation, fyc, context):
262
Create a closing move (L&P, NL&P or Closing move).
268
dest_accounts_totals = {}
270
for period in fyc.closing_fiscalyear_id.period_ids:
271
period_ids.append(period.id)
272
account_mapping_ids = []
277
fiscalyear_id = fyc.closing_fiscalyear_id.id
280
# Depending on the operation we will use different data
282
if operation == 'loss_and_profit':
285
# Consider all the periods of the fiscal year *BUT* the L&P,
286
# Net L&P and the Closing one.
288
for period in fyc.closing_fiscalyear_id.period_ids:
289
if period.id != fyc.lp_period_id.id \
290
and period.id != fyc.nlp_period_id.id \
291
and period.id != fyc.c_period_id.id:
292
period_ids.append(period.id)
295
# Set the accounts to use
297
account_mapping_ids = fyc.lp_account_mapping_ids
298
for account_map in account_mapping_ids:
299
if not account_map.dest_account_id:
300
raise osv.except_osv(_('UserError'), _("The L&P account mappings are not properly configured: %s") % account_map.name)
303
# Get the values for the lines
305
if not fyc.lp_description:
306
raise osv.except_osv(_('UserError'), _("The L&P description must be defined"))
308
raise osv.except_osv(_('UserError'), _("The L&P date must be defined"))
309
if not (fyc.lp_period_id and fyc.lp_period_id.id):
310
raise osv.except_osv(_('UserError'), _("The L&P period must be defined"))
311
if not (fyc.lp_journal_id and fyc.lp_journal_id.id):
312
raise osv.except_osv(_('UserError'), _("The L&P journal must be defined"))
313
description = fyc.lp_description
315
period_id = fyc.lp_period_id.id
316
journal_id = fyc.lp_journal_id.id
317
elif operation == 'net_loss_and_profit':
320
# Consider all the periods of the fiscal year *BUT* the
321
# Net L&P and the Closing one.
323
for period in fyc.closing_fiscalyear_id.period_ids:
324
if period.id != fyc.nlp_period_id.id \
325
and period.id != fyc.c_period_id.id:
326
period_ids.append(period.id)
329
# Set the accounts to use
331
account_mapping_ids = fyc.nlp_account_mapping_ids
333
for account_map in account_mapping_ids:
334
if not account_map.dest_account_id:
335
raise osv.except_osv(_('UserError'), _("The Net L&P account mappings are not properly configured: %s") % account_map.name)
338
# Get the values for the lines
340
if not fyc.nlp_description:
341
raise osv.except_osv(_('UserError'), _("The Net L&P description must be defined"))
343
raise osv.except_osv(_('UserError'), _("The Net L&P date must be defined"))
344
if not (fyc.nlp_period_id and fyc.nlp_period_id.id):
345
raise osv.except_osv(_('UserError'), _("The Net L&P period must be defined"))
346
if not (fyc.nlp_journal_id and fyc.nlp_journal_id.id):
347
raise osv.except_osv(_('UserError'), _("The Net L&P journal must be defined"))
348
description = fyc.nlp_description
350
period_id = fyc.nlp_period_id.id
351
journal_id = fyc.nlp_journal_id.id
352
elif operation == 'close':
353
# Require the user to have performed the L&P operation
354
if not (fyc.loss_and_profit_move_id and fyc.loss_and_profit_move_id.id):
355
raise osv.except_osv(_('UserError'), _("The L&P move must exist before creating the closing one"))
358
# Consider all the periods of the fiscal year *BUT* the Closing one.
360
for period in fyc.closing_fiscalyear_id.period_ids:
361
if period.id != fyc.c_period_id.id:
362
period_ids.append(period.id)
364
# Set the accounts to use
365
account_mapping_ids = fyc.c_account_mapping_ids
367
# Get the values for the lines
369
if not fyc.c_description:
370
raise osv.except_osv(_('UserError'), _("The closing description must be defined"))
372
raise osv.except_osv(_('UserError'), _("The closing date must be defined"))
373
if not (fyc.c_period_id and fyc.c_period_id.id):
374
raise osv.except_osv(_('UserError'), _("The closing period must be defined"))
375
if not (fyc.c_journal_id and fyc.c_journal_id.id):
376
raise osv.except_osv(_('UserError'), _("The closing journal must be defined"))
377
description = fyc.c_description
379
period_id = fyc.c_period_id.id
380
journal_id = fyc.c_journal_id.id
382
assert operation in ('loss_and_profit', 'net_loss_and_profit', 'close'), "The operation must be a supported one"
386
# For each (parent) account in the mapping list
388
total_accounts = len(account_mapping_ids)
390
for account_map in account_mapping_ids:
391
# Init (if needed) the dictionary that will store the totals for the dest accounts
392
if account_map.dest_account_id:
393
dest_accounts_totals[account_map.dest_account_id.id] = dest_accounts_totals.get(account_map.dest_account_id.id, 0)
395
context.update({'periods': period_ids})
398
# Find its children accounts (recursively)
399
# FIXME: _get_children_and_consol is a protected member of account_account but the OpenERP code base uses it like this :(
400
child_ids = pool.get('account.account')._get_children_and_consol(cr, uid, [account_map.source_account_id.id], ctx)
402
# For each children account. (Notice the context filter! the computed balanced is based on this filter)
403
for account in pool.get('account.account').browse(cr, uid, child_ids, ctx):
404
# Check if the children account needs to (and can) be closed
405
# Note: We currently ignore the close_method (account.user_type.close_method)
406
# and always do a balance close.
407
if account.type != 'view':
408
# Compute the balance for the account (uses the previous browse context filter)
409
balance = account.balance
410
# Check if the balance is greater than the limit
411
if round(abs(balance), pool.get('decimal.precision').precision_get(cr, uid, 'Account')) > 0:
413
# Add a new line to the move
416
'account_id': account.id,
417
'debit': balance<0 and -balance,
418
'credit': balance>0 and balance,
421
'period_id': period_id,
422
'journal_id': journal_id,
425
# Update the dest account total (with the inverse of the balance)
426
if account_map.dest_account_id:
427
dest_accounts_totals[account_map.dest_account_id.id] -= balance
433
for dest_account_id in dest_accounts_totals.keys():
434
balance = dest_accounts_totals[dest_account_id]
436
'account_id': dest_account_id,
437
'debit': balance<0 and -balance,
438
'credit': balance>0 and balance,
441
'period_id': period_id,
442
'journal_id': journal_id,
446
# Finally create the account move with all the lines (if needed)
449
move_id = pool.get('account.move').create(cr, uid, {
452
'period_id': period_id,
453
'journal_id': journal_id,
454
'line_id': [(0,0,line) for line in move_lines],
456
# pool.get('account.move').button_validate(cr, uid, [move_id], context)
461
# Save the reference to the created account move into the fyc object
463
if operation == 'loss_and_profit':
464
pool.get('account_fiscal_year_closing.fyc').write(cr, uid, [fyc.id], { 'loss_and_profit_move_id': move_id })
465
elif operation == 'net_loss_and_profit':
466
pool.get('account_fiscal_year_closing.fyc').write(cr, uid, [fyc.id], { 'net_loss_and_profit_move_id': move_id })
467
elif operation == 'close':
468
pool.get('account_fiscal_year_closing.fyc').write(cr, uid, [fyc.id], { 'closing_move_id': move_id })
470
assert operation in ('loss_and_profit', 'net_loss_and_profit', 'close'), "The operation must be a supported one"
475
def create_opening_move(self, cr, uid, operation, fyc, context):
477
Create an opening move (based on the closing one)
489
# Depending on the operation we will use one or other closing move
490
# as the base for the opening move.
491
# Note: Yes, currently only one 'closing' move exists,
492
# but I want this to be extensible :)
494
if operation == 'open':
495
closing_move = fyc.closing_move_id
496
# Require the user to have performed the closing operation
497
if not (closing_move and closing_move.id):
498
raise osv.except_osv(_('UserError'), _("The closing move must exist to create the opening one"))
499
if not closing_move.line_id:
500
raise osv.except_osv(_('UserError'), _("The closing move shouldn't be empty"))
502
# Get the values for the lines
504
if not fyc.o_description:
505
raise osv.except_osv(_('UserError'), _("The opening description must be defined"))
507
raise osv.except_osv(_('UserError'), _("The opening date must be defined"))
508
if not (fyc.o_period_id and fyc.o_period_id.id):
509
raise osv.except_osv(_('UserError'), _("The opening period must be defined"))
510
if not (fyc.o_journal_id and fyc.o_journal_id.id):
511
raise osv.except_osv(_('UserError'), _("The opening journal must be defined"))
512
description = fyc.o_description
514
period_id = fyc.o_period_id.id
515
journal_id = fyc.o_journal_id.id
517
assert operation in ('open'), "The operation must be a supported one"
520
# Read the lines from the closing move, and append the inverse lines
521
# to the opening move lines.
523
total_accounts = len(closing_move.line_id)
525
for line in closing_move.line_id:
527
'account_id': line.account_id.id,
528
'debit': line.credit,
529
'credit': line.debit,
532
'period_id': period_id,
533
'journal_id': journal_id,
538
# Finally create the account move with all the lines (if needed)
541
move_id = pool.get('account.move').create(cr, uid, {
544
'period_id': period_id,
545
'journal_id': journal_id,
546
'line_id': [(0,0,line) for line in move_lines],
548
# pool.get('account.move').button_validate(cr, uid, [move_id], context)
553
# Save the reference to the created account move into the fyc object
555
if operation == 'open':
556
pool.get('account_fiscal_year_closing.fyc').write(cr, uid, [fyc.id], { 'opening_move_id': move_id })
558
assert operation in ('open'), "The operation must be a supported one"
563
def remove_move(self, cr, uid, operation, fyc, context):
565
Remove a account move (L&P, NL&P, Closing or Opening move)
570
# Depending on the operation we will delete one or other move
573
if operation == 'loss_and_profit':
574
move = fyc.loss_and_profit_move_id
575
pool.get('account_fiscal_year_closing.fyc').write(cr, uid, fyc.id, { 'loss_and_profit_move_id': None })
576
elif operation == 'net_loss_and_profit':
577
move = fyc.net_loss_and_profit_move_id
578
pool.get('account_fiscal_year_closing.fyc').write(cr, uid, fyc.id, { 'net_loss_and_profit_move_id': None })
579
elif operation == 'close':
580
move = fyc.closing_move_id
581
pool.get('account_fiscal_year_closing.fyc').write(cr, uid, fyc.id, { 'closing_move_id': None })
582
elif operation == 'open':
583
move = fyc.opening_move_id
584
pool.get('account_fiscal_year_closing.fyc').write(cr, uid, fyc.id, { 'opening_move_id': None })
586
assert operation in ('loss_and_profit', 'net_loss_and_profit', 'close', 'open'), "The operation must be a supported one"
588
assert move and move.id, "The move to delete must be defined"
590
pool.get('account.move').unlink(cr, uid, [move.id], context)