[IMP] kept periods on single line
[odoo/odoo.git] / addons / account / wizard / account_fiscalyear_close.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
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.
11 #
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.
16 #
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/>.
19 #
20 ##############################################################################
21
22 from osv import fields, osv
23 from tools.translate import _
24
25 class account_fiscalyear_close(osv.osv_memory):
26     """
27     Closes Account Fiscalyear and Generate Opening entries for New Fiscalyear
28     """
29     _name = "account.fiscalyear.close"
30     _description = "Fiscalyear Close"
31     _columns = {
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"),
39     }
40     _defaults = {
41         'report_name': _('End of Fiscal Year Entry'),
42     }
43
44     def data_save(self, cr, uid, ids, context=None):
45         """
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
50
51         """
52         def _reconcile_fy_closing(cr, uid, ids, context=None):
53             """
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.
58             """
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),))
65             return r_id
66
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')
75
76         data = self.browse(cr, uid, ids, context=context)
77
78         if context is None:
79             context = {}
80         fy_id = data[0].fy_id.id
81
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()))
86
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'))
89
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)
93
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
97
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!'))
104
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)])
108         if move_ids:
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)
113
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
120         vals = {
121             'name': '/',
122             'ref': '',
123             'period_id': period.id,
124             'date': period.date_start,
125             'journal_id': new_journal.id,
126         }
127         move_id = obj_acc_move.create(cr, uid, vals, context=context)
128
129         #1. report of the accounts with defferal method == 'unreconciled'
130         cr.execute('''
131             SELECT a.id
132             FROM account_account a
133             LEFT JOIN account_account_type t ON (a.user_type = t.id)
134             WHERE a.active
135               AND a.type != 'view'
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())
139         if account_ids:
140             cr.execute('''
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),))
155
156             #We have also to consider all move_lines that were reconciled
157             #on another fiscal year, and report them too
158             cr.execute('''
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)
165                   (SELECT
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),))
178
179         #2. report of the accounts with defferal method == 'detail'
180         cr.execute('''
181             SELECT a.id
182             FROM account_account a
183             LEFT JOIN account_account_type t ON (a.user_type = t.id)
184             WHERE a.active
185               AND a.type != 'view'
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())
189
190         if account_ids:
191             cr.execute('''
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),))
206
207
208         #3. report of the accounts with defferal method == 'balance'
209         cr.execute('''
210             SELECT a.id
211             FROM account_account a
212             LEFT JOIN account_account_type t ON (a.user_type = t.id)
213             WHERE a.active
214               AND a.type != 'view'
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())
218
219         query_1st_part = """
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
223         """
224         query_2nd_part = ""
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']
234
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)):
237                 if query_2nd_part:
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,
242                        data[0].report_name,
243                        period.date_start,
244                        move_id,
245                        new_journal.id,
246                        period.id,
247                        account.id,
248                        account.currency_id and account.currency_id.id or None,
249                        balance_in_currency,
250                        account.company_id.id,
251                        'draft')
252         if query_2nd_part:
253             cr.execute(query_1st_part + query_2nd_part, tuple(query_2nd_part_args))
254
255         #validate and centralize the opening move
256         obj_acc_move.validate(cr, uid, [move_id], context=context)
257
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)])
261         if ids:
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)
265
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)])
269         if not ids:
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
274                })]
275         cr.execute('UPDATE account_fiscalyear ' \
276                     'SET end_journal_period_id = %s ' \
277                     'WHERE id = %s', (ids[0], old_fyear.id))
278
279         return {'type': 'ir.actions.act_window_close'}
280
281 account_fiscalyear_close()
282
283 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: