1
# -*- coding: utf-8 -*-
2
# -*- encoding: utf-8 -*-
3
##############################################################################
5
# OpenERP, Open Source Management Solution
6
# Copyright (C) 2010 Pexego Sistemas Informáticos. All Rights Reserved
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.
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.
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/>.
22
##############################################################################
25
Spanish Fiscal Year Closing
27
__author__ = "Borja López Soilán (Pexego)"
30
from osv import fields, osv
31
from tools.translate import _
32
from datetime import datetime
34
from tools import config
36
#-------------------------------------------------------------------------------
37
# Default Spanish Account Mappings
38
#-------------------------------------------------------------------------------
40
# Format for the mappings:
41
# (<source account code>, <dest account code>, <description>)
44
_LP_ACCOUNT_MAPPING = [
50
_NLP_ACCOUNT_MAPPING = [
51
('800', '133%', False),
52
('802', '133%', False),
53
('810', '1340%0', False),
54
('811', '1341%0', False),
55
('812', '1340%0', False),
56
('813', '1341%0', False),
57
('820', '135%0', False),
58
('821', '135%0', False),
59
('830', '13', False), # Can be any 13? account, like 130 or 133
60
('833', '13', False), # Can be any 13? account, like 130 or 133
61
('834', '137%0', False),
62
('835', '137%0', False),
63
('835', '137%0', False),
64
('838', '133%0', False),
65
('840', '130%0', False),
66
('841', '131%0', False),
67
('842', '132%0', False),
68
('850', '115%0', False),
69
('851', '115%0', False),
70
('860', '136%0', False),
71
('862', '136%0', False),
72
('890', '133%0', False),
73
('892', '133%0', False),
74
('900', '133%', False),
75
('902', '133%', False),
76
('910', '1340%0', False),
77
('911', '1341%0', False),
78
('912', '1340%0', False),
79
('913', '1341%0', False),
80
('920', '135%0', False),
81
('921', '135%0', False),
82
('940', '130%0', False),
83
('941', '131%0', False),
84
('942', '132%0', False),
85
('950', '115%0', False),
86
('951', '115%0', False),
87
('960', '136%0', False),
88
('962', '136%0', False),
89
('990', '133%0', False),
90
('991', '133%0', False),
91
('992', '133%0', False),
92
('993', '133%0', False),
96
_C_ACCOUNT_MAPPING = [
106
#-------------------------------------------------------------------------------
107
# Predeclaration of the FYC object
108
#-------------------------------------------------------------------------------
109
class fiscal_year_closing_init(osv.osv):
111
Spanish Fiscal Year Closing Wizard
114
_name = "l10n_es_cierre_ejercicio.fyc"
115
_description = "Spanish Fiscal Year Closing Wizard"
118
'name': fields.char('Description', size=60, required=True),
121
fiscal_year_closing_init()
124
#-------------------------------------------------------------------------------
125
# Account mapping objects (to be used on the fyc configuration)
126
#-------------------------------------------------------------------------------
128
class fiscal_year_closing_lp_account_mapping(osv.osv):
130
Loss & Profit Account Mapping
133
_name = "l10n_es_cierre_ejercicio.fyc_lp_account_map"
134
_description = "SFYC Loss & Profit Account Mapping"
137
'name': fields.char('Description', size=60, required=False),
140
'fyc_id': fields.many2one('l10n_es_cierre_ejercicio.fyc', 'Fiscal Year Closing', ondelete='cascade', required=True, select=1),
143
'source_account_id':fields.many2one('account.account', 'Source account', required=True, ondelete='cascade'),
144
'dest_account_id':fields.many2one('account.account', 'Dest account', required=False, ondelete='cascade'),
146
fiscal_year_closing_lp_account_mapping()
149
class fiscal_year_closing_nlp_account_mapping(osv.osv):
151
Net Loss & Profit Account Mapping
154
_name = "l10n_es_cierre_ejercicio.fyc_nlp_account_map"
155
_description = "SFYC Net Loss & Profit Account Mapping"
158
'name': fields.char('Description', size=60, required=False),
161
'fyc_id': fields.many2one('l10n_es_cierre_ejercicio.fyc', 'Fiscal Year Closing', ondelete='cascade', required=True, select=1),
164
'source_account_id':fields.many2one('account.account', 'Source account', required=True, ondelete='cascade'),
165
'dest_account_id':fields.many2one('account.account', 'Dest account', required=False, ondelete='cascade'),
167
fiscal_year_closing_nlp_account_mapping()
170
class fiscal_year_closing_c_account_mapping(osv.osv):
172
Closing Account Mapping
175
_name = "l10n_es_cierre_ejercicio.fyc_c_account_map"
176
_description = "SFYC Closing Account Mapping"
179
'name': fields.char('Description', size=60, required=False),
182
'fyc_id': fields.many2one('l10n_es_cierre_ejercicio.fyc', 'Fiscal Year Closing', ondelete='cascade', required=True, select=1),
185
'source_account_id':fields.many2one('account.account', 'Account', required=True, ondelete='cascade'),
186
'dest_account_id':fields.many2one('account.account', 'Dest account', ondelete='cascade'),
188
fiscal_year_closing_c_account_mapping()
190
#-------------------------------------------------------------------------------
191
# Spanish Fiscal Year Closing Wizard
192
#-------------------------------------------------------------------------------
193
class fiscal_year_closing(osv.osv):
195
Spanish Fiscal Year Closing Wizard
198
_inherit = "l10n_es_cierre_ejercicio.fyc"
201
# Fields -------------------------------------------------------------------
206
'company_id': fields.many2one('res.company', 'Company', ondelete='cascade', readonly=True, required=True),
209
'closing_fiscalyear_id':fields.many2one('account.fiscalyear', 'Fiscal year to close', required=True, ondelete='cascade', select=1),
210
'opening_fiscalyear_id':fields.many2one('account.fiscalyear', 'Fiscal year to open', required=True, ondelete='cascade', select=2),
213
# Operations (to do), and their account moves (when done)
215
'create_loss_and_profit': fields.boolean('Create Loss & Profit move'),
216
'loss_and_profit_move_id': fields.many2one('account.move', 'L&P Move', ondelete='set null', readonly=True),
217
'create_net_loss_and_profit': fields.boolean('Create Net Loss & Profit'),
218
'net_loss_and_profit_move_id': fields.many2one('account.move', 'Net L&P Move', ondelete='set null', readonly=True),
219
'create_closing': fields.boolean('Close fiscal year'),
220
'closing_move_id': fields.many2one('account.move', 'Closing Move', ondelete='set null', readonly=True),
221
'create_opening': fields.boolean('Open next fiscal year'),
222
'opening_move_id': fields.many2one('account.move', 'Opening Move', ondelete='set null', readonly=True),
227
'check_invalid_period_moves': fields.boolean('Check invalid period or date moves', help="Checks that there are no moves, on the fiscal year that is being closed, with dates or periods outside that fiscal year."),
228
'check_draft_moves': fields.boolean('Check draft moves', help="Checks that there are no draft moves on the fiscal year that is being closed. Non-confirmed moves won't be taken in account on the closing operations."),
229
'check_unbalanced_moves': fields.boolean('Check unbalanced moves', help="Checks that there are no unbalanced moves on the fiscal year that is being closed."),
232
'state': fields.selection([
235
('in_progress', 'In Progress'),
237
('canceled', 'Canceled'),
241
# Loss and Profit options
243
'lp_description': fields.char('Description', size=60),
244
'lp_journal_id': fields.many2one('account.journal', 'Journal'),
245
'lp_period_id': fields.many2one('account.period', 'Period'),
246
'lp_date': fields.date('Date'),
247
'lp_account_mapping_ids': fields.one2many('l10n_es_cierre_ejercicio.fyc_lp_account_map', 'fyc_id', 'Account mappings'),
250
# Net Loss and Profit options
252
'nlp_description': fields.char('Description', size=60),
253
'nlp_journal_id': fields.many2one('account.journal', 'Journal'),
254
'nlp_period_id': fields.many2one('account.period', 'Period'),
255
'nlp_date': fields.date('Date'),
256
'nlp_account_mapping_ids': fields.one2many('l10n_es_cierre_ejercicio.fyc_nlp_account_map', 'fyc_id', 'Account mappings'),
261
'c_description': fields.char('Description', size=60),
262
'c_journal_id': fields.many2one('account.journal', 'Journal'),
263
'c_period_id': fields.many2one('account.period', 'Period'),
264
'c_date': fields.date('Date'),
265
'c_account_mapping_ids': fields.one2many('l10n_es_cierre_ejercicio.fyc_c_account_map', 'fyc_id', 'Accounts'),
270
'o_description': fields.char('Description', size=60),
271
'o_journal_id': fields.many2one('account.journal', 'Journal'),
272
'o_period_id': fields.many2one('account.period', 'Period'),
273
'o_date': fields.date('Date'),
277
# Default values -----------------------------------------------------------
280
def _get_closing_fiscalyear_id(self, cr, uid, context):
282
Gets the last (previous) fiscal year
284
company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
285
str_date = '%s-06-01' % (datetime.now().year - 1)
286
fiscalyear_ids = self.pool.get('account.fiscalyear').search(cr, uid, [
287
('company_id', '=', company.id),
288
('date_start', '<=', str_date),
289
('date_stop', '>=', str_date),
291
if not fiscalyear_ids:
292
fiscalyear_ids = self.pool.get('account.fiscalyear').search(cr, uid, [
293
('company_id', '=', False),
294
('date_start', '<=', str_date),
295
('date_stop', '>=', str_date),
297
return fiscalyear_ids and fiscalyear_ids[0]
299
def _get_opening_fiscalyear_id(self, cr, uid, context):
301
Gets the current fiscal year
303
company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
304
str_date = '%s-06-01' % datetime.now().year
305
fiscalyear_ids = self.pool.get('account.fiscalyear').search(cr, uid, [
306
('company_id', '=', company.id),
307
('date_start', '<=', str_date),
308
('date_stop', '>=', str_date),
310
if not fiscalyear_ids:
311
fiscalyear_ids = self.pool.get('account.fiscalyear').search(cr, uid, [
312
('company_id', '=', False),
313
('date_start', '<=', str_date),
314
('date_stop', '>=', str_date),
316
return fiscalyear_ids and fiscalyear_ids[0]
319
# Current company by default:
320
'company_id': lambda self, cr, uid, context: self.pool.get('res.users').browse(cr, uid, uid, context).company_id.id,
322
# Draft state by default:
323
'state': lambda *a: 'new',
326
'name': lambda self, cr, uid, context: _("%s Fiscal Year Closing") % (datetime.now().year - 1),
329
'closing_fiscalyear_id': _get_closing_fiscalyear_id,
330
'opening_fiscalyear_id': _get_opening_fiscalyear_id,
334
# Workflow actions ---------------------------------------------------------
337
def _get_journal_id(self, cr, uid, fyc, context):
339
Gets the journal to use.
340
(It will search for a 'GRAL' or 'General' journal)
342
assert fyc.company_id, "A company should have been selected"
343
journal_ids = self.pool.get('account.journal').search(cr, uid, [
344
('company_id', '=', fyc.company_id.id),
345
('code', '=', 'GRAL'),
348
journal_ids = self.pool.get('account.journal').search(cr, uid, [
349
('company_id', '=', False),
350
('code', '=', 'GRAL'),
353
journal_ids = self.pool.get('account.journal').search(cr, uid, [
354
('company_id', '=', fyc.company_id.id),
355
('name', 'ilike', 'General'),
358
journal_ids = self.pool.get('account.journal').search(cr, uid, [
359
('company_id', '=', False),
360
('name', 'ilike', 'General'),
362
return journal_ids and journal_ids[0]
364
def _get_lp_period_id(self, cr, uid, fyc, context):
366
Gets the period for the L&P entry
367
(It searches for a "PG%" special period on the previous fiscal year)
369
period_ids = self.pool.get('account.period').search(cr, uid, [
370
('fiscalyear_id', '=', fyc.closing_fiscalyear_id.id),
371
('special', '=', True),
372
('date_start', '=', fyc.closing_fiscalyear_id.date_stop),
373
('code', 'ilike', 'PG'),
375
return period_ids and period_ids[0]
377
def _get_c_period_id(self, cr, uid, fyc, context):
379
Gets the period for the Closing entry
380
(It searches for a "C%" special period on the previous fiscal year)
382
period_ids = self.pool.get('account.period').search(cr, uid, [
383
('fiscalyear_id', '=', fyc.closing_fiscalyear_id.id),
384
('special', '=', True),
385
('date_start', '=', fyc.closing_fiscalyear_id.date_stop),
386
('code', 'ilike', 'C'),
388
return period_ids and period_ids[0]
390
def _get_o_period_id(self, cr, uid, fyc, context):
392
Gets the period for the Opening entry
393
(It searches for a "A%" special period on the previous fiscal year)
395
period_ids = self.pool.get('account.period').search(cr, uid, [
396
('fiscalyear_id', '=', fyc.opening_fiscalyear_id.id),
397
('special', '=', True),
398
('date_stop', '=', fyc.opening_fiscalyear_id.date_start),
399
('code', 'ilike', 'A'),
401
return period_ids and period_ids[0]
404
def _get_account_mappings(self, cr, uid, fyc, mapping, context):
406
Transforms the mapping dictionary on a list of mapping lines.
408
account_mappings = []
409
for source, dest, description in mapping:
411
# Find the source account
413
account_ids = self.pool.get('account.account').search(cr, uid, [
414
('company_id', '=', fyc.company_id.id),
415
('code', '=like', source),
417
source_account_id = account_ids and account_ids[0] or None
420
# Find the dest account
422
account_ids = self.pool.get('account.account').search(cr, uid, [
423
('company_id', '=', fyc.company_id.id),
424
('code', '=like', dest),
425
('type', '!=', 'view'),
427
dest_account_id = account_ids and account_ids[0] or None
430
# Use a default description if not provided
433
if source_account_id:
434
description = self.pool.get('account.account').read(cr, uid, source_account_id, ['name'])['name']
437
# If the mapping is valid for this chart of accounts
439
if source_account_id:
441
# Make sure that the dest account is valid
444
# Add the line to the result
445
account_mappings.append({
447
'source_account_id': source_account_id,
448
'dest_account_id': dest_account_id,
451
# Add the line to the result
452
account_mappings.append({
453
'name': _('No destination account %s found for account %s.') % (dest, source),
454
'source_account_id': source_account_id,
455
'dest_account_id': None,
458
return [(0, 0, acc_map) for acc_map in account_mappings]
462
def action_draft(self, cr, uid, ids, context=None):
464
Called when the user clicks the confirm button.
469
# Make sure the lang is defined on the context
471
user = self.pool.get('res.users').browse(cr, uid, uid, context)
472
context['lang'] = context.get('lang') or user.context_lang
474
for fyc in self.browse(cr, uid, ids, context):
476
# Check for duplicated entries
478
fyc_ids = self.search(cr, uid, [('name', '=', fyc.name)])
480
raise osv.except_osv(_('Error'), _('There is already a fiscal year closing with this name.'))
482
assert fyc.closing_fiscalyear_id and fyc.closing_fiscalyear_id.id
483
fyc_ids = self.search(cr, uid, [('closing_fiscalyear_id', '=', fyc.closing_fiscalyear_id.id)])
485
raise osv.except_osv(_('Error'), _('There is already a fiscal year closing for the fiscal year to close.'))
487
assert fyc.opening_fiscalyear_id and fyc.opening_fiscalyear_id.id
488
fyc_ids = self.search(cr, uid, [('opening_fiscalyear_id', '=', fyc.opening_fiscalyear_id.id)])
490
raise osv.except_osv(_('Error'), _('There is already a fiscal year closing for the fiscal year to open.'))
493
# Check whether the default values of the fyc object have to be computed
494
# or they have already been computed (restarted workflow)
496
if fyc.c_account_mapping_ids:
497
# Fyc wizard reverted to 'new' after canceled
499
self.write(cr, uid, [fyc.id], { 'state': 'draft' })
501
# New fyc wizard object
505
# Perform all the operations by default
507
'create_loss_and_profit': True,
508
'create_net_loss_and_profit': True,
509
'create_closing': True,
510
'create_opening': True,
512
'check_invalid_period_moves': True,
513
'check_draft_moves': True,
514
'check_unbalanced_moves': True,
519
'lp_description': _("Loss & Profit"),
520
'lp_journal_id': self._get_journal_id(cr, uid, fyc, context),
521
'lp_period_id': self._get_lp_period_id(cr, uid, fyc, context),
522
'lp_date': fyc.closing_fiscalyear_id.date_stop,
523
'lp_account_mapping_ids': self._get_account_mappings(cr, uid, fyc, _LP_ACCOUNT_MAPPING, context),
528
'nlp_description': _("Net Loss & Profit"),
529
'nlp_journal_id': self._get_journal_id(cr, uid, fyc, context),
530
'nlp_period_id': self._get_lp_period_id(cr, uid, fyc, context),
531
'nlp_date': fyc.closing_fiscalyear_id.date_stop,
532
'nlp_account_mapping_ids': self._get_account_mappings(cr, uid, fyc, _NLP_ACCOUNT_MAPPING, context),
537
'c_description': _("Fiscal Year Closing"),
538
'c_journal_id': self._get_journal_id(cr, uid, fyc, context),
539
'c_period_id': self._get_c_period_id(cr, uid, fyc, context),
540
'c_date': fyc.closing_fiscalyear_id.date_stop,
541
'c_account_mapping_ids': self._get_account_mappings(cr, uid, fyc, _C_ACCOUNT_MAPPING, context),
546
'o_description': _("Fiscal Year Opening"),
547
'o_journal_id': self._get_journal_id(cr, uid, fyc, context),
548
'o_period_id': self._get_o_period_id(cr, uid, fyc, context),
549
'o_date': fyc.opening_fiscalyear_id.date_start,
554
self.write(cr, uid, [fyc.id], vals)
558
def action_run(self, cr, uid, ids, context=None):
560
Called when the create entries button is used.
562
# Note: Just change the state, everything else is done on the run wizard
563
# *before* this action is called.
564
self.write(cr, uid, ids, {'state': 'in_progress'})
568
def action_confirm(self, cr, uid, ids, context=None):
570
Called when the user clicks the confirm button.
575
# Make sure the lang is defined on the context
577
user = self.pool.get('res.users').browse(cr, uid, uid, context)
578
context['lang'] = context.get('lang') or user.context_lang
580
for fyc in self.browse(cr, uid, ids, context):
582
# Require the L&P, closing, and opening moves to exist (NL&P is optional)
584
if not fyc.loss_and_profit_move_id:
585
raise osv.except_osv(_("Not all the operations have been performed!"), _("The Loss & Profit move is required"))
586
if not fyc.closing_move_id:
587
raise osv.except_osv(_("Not all the operations have been performed!"), _("The Closing move is required"))
588
if not fyc.opening_move_id:
589
raise osv.except_osv(_("Not all the operations have been performed!"), _("The Opening move is required"))
592
# Calculate the moves to check
595
moves.append(fyc.loss_and_profit_move_id)
596
if fyc.net_loss_and_profit_move_id:
597
moves.append(fyc.net_loss_and_profit_move_id)
598
moves.append(fyc.closing_move_id)
599
moves.append(fyc.opening_move_id)
602
# Check and reconcile each of the moves
605
netsvc.Logger().notifyChannel('fyc', netsvc.LOG_DEBUG, "Checking %s" % move.ref)
607
# Check if it has been confirmed
609
if move.state == 'draft':
610
raise osv.except_osv(_("Some moves are in draft state!"), _("You have to review and confirm each of the moves before continuing"))
615
for line in move.line_id:
616
amount += (line.debit - line.credit)
617
if abs(amount) > 0.5 * 10 ** -int(config['price_accuracy']):
618
raise osv.except_osv(_("Some moves are unbalanced!"), _("All the moves should be balanced before continuing"))
623
# Note: We will reconcile all the lines, even the 'not reconcile' ones,
624
# to prevent future problems (the user may change the
625
# reconcile option of an account in the future)
627
netsvc.Logger().notifyChannel('fyc', netsvc.LOG_DEBUG, "Reconcile %s" % move.ref)
628
tmp_context = context.copy()
629
tmp_context['fy_closing'] = True # Fiscal year closing = reconcile everything
630
line_ids = [line.id for line in move.line_id]
631
self.pool.get('account.move.line').reconcile(cr, uid, line_ids, context=tmp_context)
634
# Close the fiscal year and it's periods
636
# Note: We can not just do a write, cause it would raise a
637
# "You can not modify/delete a journal with entries for this period!"
638
# so we have to do it on SQL level :(
639
# This is based on the "account.fiscalyear.close.state" wizard.
641
netsvc.Logger().notifyChannel('fyc', netsvc.LOG_DEBUG, "Closing fiscal year")
643
UPDATE account_journal_period
645
WHERE period_id IN (SELECT id FROM account_period WHERE fiscalyear_id = %d)
647
cr.execute(query % fyc.closing_fiscalyear_id.id)
649
UPDATE account_period
651
WHERE fiscalyear_id = %d
653
cr.execute(query % fyc.closing_fiscalyear_id.id)
655
UPDATE account_fiscalyear
659
cr.execute(query % fyc.closing_fiscalyear_id.id)
662
self.write(cr, uid, ids, {'state': 'done'})
666
def action_cancel(self, cr, uid, ids, context=None):
668
Called when the user clicks the cancel button.
673
# Make sure the lang is defined on the context
675
user = self.pool.get('res.users').browse(cr, uid, uid, context)
676
context['lang'] = context.get('lang') or user.context_lang
679
# Uncheck all the operations
681
self.pool.get('l10n_es_cierre_ejercicio.fyc').write(cr, uid, ids, {
682
'create_loss_and_profit': False,
683
'create_net_loss_and_profit': False,
684
'create_closing': False,
685
'create_opening': False,
686
'check_invalid_period_moves': False,
687
'check_draft_moves': False,
688
'check_unbalanced_moves': False,
692
# Open the fiscal year and it's periods
694
# Note: We can not just do a write, cause it would raise a
695
# "You can not modify/delete a journal with entries for this period!"
696
# so we have to do it on SQL level :(
697
# This is based on the "account.fiscalyear.close.state" wizard.
699
for fyc in self.browse(cr, uid, ids, context):
701
UPDATE account_journal_period
703
WHERE period_id IN (SELECT id FROM account_period WHERE fiscalyear_id = %d)
705
cr.execute(query % fyc.closing_fiscalyear_id.id)
707
UPDATE account_period
709
WHERE fiscalyear_id = %d
711
cr.execute(query % fyc.closing_fiscalyear_id.id)
713
UPDATE account_fiscalyear
717
cr.execute(query % fyc.closing_fiscalyear_id.id)
721
self.write(cr, uid, ids, {'state': 'canceled'})
723
# Note: Everything else (removing the account moves) is done on the
724
# cancel wizard *after* this action returns.
728
def action_recover(self, cr, uid, ids, context=None):
730
Called when the user clicks the draft button to create
731
a new workflow instance.
733
self.write(cr, uid, ids, {'state': 'new'})
734
wf_service = netsvc.LocalService("workflow")
736
wf_service.trg_create(uid, 'l10n_es_cierre_ejercicio.fyc', item_id, cr)
740
fiscal_year_closing()