1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
22 from osv import fields, osv
23 from tools.translate import _
25 class account_fiscalyear_close(osv.osv_memory):
27 Closes Account Fiscalyear and Generate Opening entries for New Fiscalyear
29 _name = "account.fiscalyear.close"
30 _description = "Fiscalyear Close"
32 'fy_id': fields.many2one('account.fiscalyear', \
33 'Fiscal Year to close', required=True, help="Select a Fiscal year to close"),
34 'fy2_id': fields.many2one('account.fiscalyear', \
35 'New Fiscal Year', required=True),
36 'journal_id': fields.many2one('account.journal', 'Opening Entries Journal', domain="[('type','=','situation')]", required=True, help='The best practice here is to use a journal dedicated to contain the opening entries of all fiscal years. Note that you should define it with default debit/credit accounts, of type \'situation\' and with a centralized counterpart.'),
37 'period_id': fields.many2one('account.period', 'Opening Entries Period', required=True),
38 'report_name': fields.char('Name of new entries',size=64, required=True, help="Give name of the new entries"),
41 'report_name': _('End of Fiscal Year Entry'),
44 def data_save(self, cr, uid, ids, context=None):
46 This function close account fiscalyear and create entries in new fiscalyear
47 @param cr: the current row, from the database cursor,
48 @param uid: the current user’s ID for security checks,
49 @param ids: List of Account fiscalyear close state’s IDs
52 def _reconcile_fy_closing(cr, uid, ids, context=None):
54 This private function manually do the reconciliation on the account_move_line given as `ids´, and directly
55 through psql. It's necessary to do it this way because the usual `reconcile()´ function on account.move.line
56 object is really resource greedy (not supposed to work on reconciliation between thousands of records) and
57 it does a lot of different computation that are useless in this particular case.
59 #check that the reconcilation concern journal entries from only one company
60 cr.execute('select distinct(company_id) from account_move_line where id in %s',(tuple(ids),))
61 if len(cr.fetchall()) > 1:
62 raise osv.except_osv(_('Warning !'), _('The entries to reconcile should belong to the same company'))
63 r_id = self.pool.get('account.move.reconcile').create(cr, uid, {'type': 'auto'})
64 cr.execute('update account_move_line set reconcile_id = %s where id in %s',(r_id, tuple(ids),))
67 obj_acc_period = self.pool.get('account.period')
68 obj_acc_fiscalyear = self.pool.get('account.fiscalyear')
69 obj_acc_journal = self.pool.get('account.journal')
70 obj_acc_move = self.pool.get('account.move')
71 obj_acc_move_line = self.pool.get('account.move.line')
72 obj_acc_account = self.pool.get('account.account')
73 obj_acc_journal_period = self.pool.get('account.journal.period')
74 currency_obj = self.pool.get('res.currency')
76 data = self.browse(cr, uid, ids, context=context)
80 fy_id = data[0].fy_id.id
82 cr.execute("SELECT id FROM account_period WHERE date_stop < (SELECT date_start FROM account_fiscalyear WHERE id = %s)", (str(data[0].fy2_id.id),))
83 fy_period_set = ','.join(map(lambda id: str(id[0]), cr.fetchall()))
84 cr.execute("SELECT id FROM account_period WHERE date_start > (SELECT date_stop FROM account_fiscalyear WHERE id = %s)", (str(fy_id),))
85 fy2_period_set = ','.join(map(lambda id: str(id[0]), cr.fetchall()))
87 if not fy_period_set or not fy2_period_set:
88 raise osv.except_osv(_('UserError'), _('The periods to generate opening entries were not found'))
90 period = obj_acc_period.browse(cr, uid, data[0].period_id.id, context=context)
91 new_fyear = obj_acc_fiscalyear.browse(cr, uid, data[0].fy2_id.id, context=context)
92 old_fyear = obj_acc_fiscalyear.browse(cr, uid, fy_id, context=context)
94 new_journal = data[0].journal_id.id
95 new_journal = obj_acc_journal.browse(cr, uid, new_journal, context=context)
96 company_id = new_journal.company_id.id
98 if not new_journal.default_credit_account_id or not new_journal.default_debit_account_id:
99 raise osv.except_osv(_('UserError'),
100 _('The journal must have default credit and debit account'))
101 if (not new_journal.centralisation) or new_journal.entry_posted:
102 raise osv.except_osv(_('UserError'),
103 _('The journal must have centralised counterpart without the Skipping draft state option checked!'))
105 #delete existing move and move lines if any
106 move_ids = obj_acc_move.search(cr, uid, [
107 ('journal_id', '=', new_journal.id), ('period_id', '=', period.id)])
109 move_line_ids = obj_acc_move_line.search(cr, uid, [('move_id', 'in', move_ids)])
110 obj_acc_move_line._remove_move_reconcile(cr, uid, move_line_ids, context=context)
111 obj_acc_move_line.unlink(cr, uid, move_line_ids, context=context)
112 obj_acc_move.unlink(cr, uid, move_ids, context=context)
114 cr.execute("SELECT id FROM account_fiscalyear WHERE date_stop < %s", (str(new_fyear.date_start),))
115 result = cr.dictfetchall()
116 fy_ids = ','.join([str(x['id']) for x in result])
117 query_line = obj_acc_move_line._query_get(cr, uid,
118 obj='account_move_line', context={'fiscalyear': fy_ids})
119 #create the opening move
123 'period_id': period.id,
124 'date': period.date_start,
125 'journal_id': new_journal.id,
127 move_id = obj_acc_move.create(cr, uid, vals, context=context)
129 #1. report of the accounts with defferal method == 'unreconciled'
132 FROM account_account a
133 LEFT JOIN account_account_type t ON (a.user_type = t.id)
136 AND a.company_id = %s
137 AND t.close_method = %s''', (company_id, 'unreconciled', ))
138 account_ids = map(lambda x: x[0], cr.fetchall())
141 INSERT INTO account_move_line (
142 name, create_uid, create_date, write_uid, write_date,
143 statement_id, journal_id, currency_id, date_maturity,
144 partner_id, blocked, credit, state, debit,
145 ref, account_id, period_id, date, move_id, amount_currency,
146 quantity, product_id, company_id)
147 (SELECT name, create_uid, create_date, write_uid, write_date,
148 statement_id, %s,currency_id, date_maturity, partner_id,
149 blocked, credit, 'draft', debit, ref, account_id,
150 %s, (%s) AS date, %s, amount_currency, quantity, product_id, company_id
151 FROM account_move_line
152 WHERE account_id IN %s
153 AND ''' + query_line + '''
154 AND reconcile_id IS NULL)''', (new_journal.id, period.id, period.date_start, move_id, tuple(account_ids),))
156 #We have also to consider all move_lines that were reconciled
157 #on another fiscal year, and report them too
159 INSERT INTO account_move_line (
160 name, create_uid, create_date, write_uid, write_date,
161 statement_id, journal_id, currency_id, date_maturity,
162 partner_id, blocked, credit, state, debit,
163 ref, account_id, period_id, date, move_id, amount_currency,
164 quantity, product_id, company_id)
166 b.name, b.create_uid, b.create_date, b.write_uid, b.write_date,
167 b.statement_id, %s, b.currency_id, b.date_maturity,
168 b.partner_id, b.blocked, b.credit, 'draft', b.debit,
169 b.ref, b.account_id, %s, (%s) AS date, %s, b.amount_currency,
170 b.quantity, b.product_id, b.company_id
171 FROM account_move_line b
172 WHERE b.account_id IN %s
173 AND b.reconcile_id IS NOT NULL
174 AND b.period_id IN ('''+fy_period_set+''')
175 AND b.reconcile_id IN (SELECT DISTINCT(reconcile_id)
176 FROM account_move_line a
177 WHERE a.period_id IN ('''+fy2_period_set+''')))''', (new_journal.id, period.id, period.date_start, move_id, tuple(account_ids),))
179 #2. report of the accounts with defferal method == 'detail'
182 FROM account_account a
183 LEFT JOIN account_account_type t ON (a.user_type = t.id)
186 AND a.company_id = %s
187 AND t.close_method = %s''', (company_id, 'detail', ))
188 account_ids = map(lambda x: x[0], cr.fetchall())
192 INSERT INTO account_move_line (
193 name, create_uid, create_date, write_uid, write_date,
194 statement_id, journal_id, currency_id, date_maturity,
195 partner_id, blocked, credit, state, debit,
196 ref, account_id, period_id, date, move_id, amount_currency,
197 quantity, product_id, company_id)
198 (SELECT name, create_uid, create_date, write_uid, write_date,
199 statement_id, %s,currency_id, date_maturity, partner_id,
200 blocked, credit, 'draft', debit, ref, account_id,
201 %s, (%s) AS date, %s, amount_currency, quantity, product_id, company_id
202 FROM account_move_line
203 WHERE account_id IN %s
204 AND ''' + query_line + ''')
205 ''', (new_journal.id, period.id, period.date_start, move_id, tuple(account_ids),))
208 #3. report of the accounts with defferal method == 'balance'
211 FROM account_account a
212 LEFT JOIN account_account_type t ON (a.user_type = t.id)
215 AND a.company_id = %s
216 AND t.close_method = %s''', (company_id, 'balance', ))
217 account_ids = map(lambda x: x[0], cr.fetchall())
220 INSERT INTO account_move_line (
221 debit, credit, name, date, move_id, journal_id, period_id,
222 account_id, currency_id, amount_currency, company_id, state) VALUES
225 query_2nd_part_args = []
226 for account in obj_acc_account.browse(cr, uid, account_ids, context={'fiscalyear': fy_id}):
227 balance_in_currency = 0.0
228 if account.currency_id:
229 cr.execute('SELECT sum(amount_currency) as balance_in_currency FROM account_move_line ' \
230 'WHERE account_id = %s ' \
231 'AND ' + query_line + ' ' \
232 'AND currency_id = %s', (account.id, account.currency_id.id))
233 balance_in_currency = cr.dictfetchone()['balance_in_currency']
235 company_currency_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.currency_id
236 if not currency_obj.is_zero(cr, uid, company_currency_id, abs(account.balance)):
238 query_2nd_part += ','
239 query_2nd_part += "(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)"
240 query_2nd_part_args += (account.balance > 0 and account.balance or 0.0,
241 account.balance < 0 and -account.balance or 0.0,
248 account.currency_id and account.currency_id.id or None,
250 account.company_id.id,
253 cr.execute(query_1st_part + query_2nd_part, tuple(query_2nd_part_args))
255 #validate and centralize the opening move
256 obj_acc_move.validate(cr, uid, [move_id], context=context)
258 #reconcile all the move.line of the opening move
259 ids = obj_acc_move_line.search(cr, uid, [('journal_id', '=', new_journal.id),
260 ('period_id.fiscalyear_id','=',new_fyear.id)])
262 reconcile_id = _reconcile_fy_closing(cr, uid, ids, context=context)
263 #set the creation date of the reconcilation at the first day of the new fiscalyear, in order to have good figures in the aged trial balance
264 self.pool.get('account.move.reconcile').write(cr, uid, [reconcile_id], {'create_date': new_fyear.date_start}, context=context)
266 #create the journal.period object and link it to the old fiscalyear
267 new_period = data[0].period_id.id
268 ids = obj_acc_journal_period.search(cr, uid, [('journal_id', '=', new_journal.id), ('period_id', '=', new_period)])
270 ids = [obj_acc_journal_period.create(cr, uid, {
271 'name': (new_journal.name or '') + ':' + (period.code or ''),
272 'journal_id': new_journal.id,
273 'period_id': period.id
275 cr.execute('UPDATE account_fiscalyear ' \
276 'SET end_journal_period_id = %s ' \
277 'WHERE id = %s', (ids[0], old_fyear.id))
279 return {'type': 'ir.actions.act_window_close'}
281 account_fiscalyear_close()
283 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: