[MERGE] merged trunk up to revision 9350
[odoo/odoo.git] / addons / account / account_cash_statement.py
1 # encoding: utf-8
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2008 PC Solutions (<http://pcsol.be>). All Rights Reserved
6 #    $Id$
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU General Public License as published by
10 #    the Free Software Foundation, either version 3 of the License, or
11 #    (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU General Public License for more details.
17 #
18 #    You should have received a copy of the GNU General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22
23 import time
24
25 from openerp.osv import fields, osv
26 from openerp.tools.translate import _
27 import openerp.addons.decimal_precision as dp
28
29 class account_cashbox_line(osv.osv):
30
31     """ Cash Box Details """
32
33     _name = 'account.cashbox.line'
34     _description = 'CashBox Line'
35     _rec_name = 'pieces'
36
37     def _sub_total(self, cr, uid, ids, name, arg, context=None):
38
39         """ Calculates Sub total
40         @param name: Names of fields.
41         @param arg: User defined arguments
42         @return: Dictionary of values.
43         """
44         res = {}
45         for obj in self.browse(cr, uid, ids, context=context):
46             res[obj.id] = {
47                 'subtotal_opening' : obj.pieces * obj.number_opening,
48                 'subtotal_closing' : obj.pieces * obj.number_closing,
49             }
50         return res
51
52     def on_change_sub_opening(self, cr, uid, ids, pieces, number, *a):
53         """ Compute the subtotal for the opening """
54         return {'value' : {'subtotal_opening' : (pieces * number) or 0.0 }}
55
56     def on_change_sub_closing(self, cr, uid, ids, pieces, number, *a):
57         """ Compute the subtotal for the closing """
58         return {'value' : {'subtotal_closing' : (pieces * number) or 0.0 }}
59
60     _columns = {
61         'pieces': fields.float('Unit of Currency', digits_compute=dp.get_precision('Account')),
62         'number_opening' : fields.integer('Number of Units', help='Opening Unit Numbers'),
63         'number_closing' : fields.integer('Number of Units', help='Closing Unit Numbers'),
64         'subtotal_opening': fields.function(_sub_total, string='Opening Subtotal', type='float', digits_compute=dp.get_precision('Account'), multi='subtotal'),
65         'subtotal_closing': fields.function(_sub_total, string='Closing Subtotal', type='float', digits_compute=dp.get_precision('Account'), multi='subtotal'),
66         'bank_statement_id' : fields.many2one('account.bank.statement', ondelete='cascade'),
67      }
68
69
70 class account_cash_statement(osv.osv):
71
72     _inherit = 'account.bank.statement'
73
74     def _update_balances(self, cr, uid, ids, context=None):
75         """
76             Set starting and ending balances according to pieces count
77         """
78         res = {}
79         for statement in self.browse(cr, uid, ids, context=context):
80             if (statement.journal_id.type not in ('cash',)) or (not statement.journal_id.cash_control):
81                 continue
82             start = end = 0
83             for line in statement.details_ids:
84                 start += line.subtotal_opening
85                 end += line.subtotal_closing
86             data = {
87                 'balance_start': start,
88                 'balance_end_real': end,
89             }
90             res[statement.id] = data
91             super(account_cash_statement, self).write(cr, uid, [statement.id], data, context=context)
92         return res
93
94     def _get_sum_entry_encoding(self, cr, uid, ids, name, arg, context=None):
95
96         """ Find encoding total of statements "
97         @param name: Names of fields.
98         @param arg: User defined arguments
99         @return: Dictionary of values.
100         """
101         res = {}
102         for statement in self.browse(cr, uid, ids, context=context):
103             res[statement.id] = sum((line.amount for line in statement.line_ids), 0.0)
104         return res
105
106     def _get_company(self, cr, uid, context=None):
107         user_pool = self.pool.get('res.users')
108         company_pool = self.pool.get('res.company')
109         user = user_pool.browse(cr, uid, uid, context=context)
110         company_id = user.company_id
111         if not company_id:
112             company_id = company_pool.search(cr, uid, [])
113         return company_id and company_id[0] or False
114
115     def _get_statement_from_line(self, cr, uid, ids, context=None):
116         result = {}
117         for line in self.pool.get('account.bank.statement.line').browse(cr, uid, ids, context=context):
118             result[line.statement_id.id] = True
119         return result.keys()
120
121     def _compute_difference(self, cr, uid, ids, fieldnames, args, context=None):
122         result =  dict.fromkeys(ids, 0.0)
123
124         for obj in self.browse(cr, uid, ids, context=context):
125             result[obj.id] = obj.balance_end_real - obj.balance_end
126
127         return result
128
129     def _compute_last_closing_balance(self, cr, uid, ids, fieldnames, args, context=None):
130         result = dict.fromkeys(ids, 0.0)
131
132         for obj in self.browse(cr, uid, ids, context=context):
133             if obj.state == 'draft':
134                 statement_ids = self.search(cr, uid,
135                     [('journal_id', '=', obj.journal_id.id),('state', '=', 'confirm')],
136                     order='create_date desc',
137                     limit=1,
138                     context=context
139                 )
140
141                 if not statement_ids:
142                     continue
143                 else:
144                     st = self.browse(cr, uid, statement_ids[0], context=context)
145                     result[obj.id] = st.balance_end_real
146
147         return result
148
149     def onchange_journal_id(self, cr, uid, ids, journal_id, context=None):
150         result = super(account_cash_statement, self).onchange_journal_id(cr, uid, ids, journal_id)
151
152         if not journal_id:
153             return result
154
155         statement_ids = self.search(cr, uid,
156                 [('journal_id', '=', journal_id),('state', '=', 'confirm')],
157                 order='create_date desc',
158                 limit=1,
159                 context=context
160         )
161
162         opening_details_ids = self._get_cash_open_box_lines(cr, uid, journal_id, context)
163         if opening_details_ids:
164             result['value']['opening_details_ids'] = opening_details_ids
165
166         if not statement_ids:
167             return result
168
169         st = self.browse(cr, uid, statement_ids[0], context=context)
170         result.setdefault('value', {}).update({'last_closing_balance' : st.balance_end_real})
171
172         return result
173
174     _columns = {
175         'total_entry_encoding': fields.function(_get_sum_entry_encoding, string="Total Transactions",
176             store = {
177                 'account.bank.statement': (lambda self, cr, uid, ids, context=None: ids, ['line_ids','move_line_ids'], 10),
178                 'account.bank.statement.line': (_get_statement_from_line, ['amount'], 10),
179             },
180             help="Total of cash transaction lines."),
181         'closing_date': fields.datetime("Closed On"),
182         'details_ids' : fields.one2many('account.cashbox.line', 'bank_statement_id', string='CashBox Lines'),
183         'opening_details_ids' : fields.one2many('account.cashbox.line', 'bank_statement_id', string='Opening Cashbox Lines'),
184         'closing_details_ids' : fields.one2many('account.cashbox.line', 'bank_statement_id', string='Closing Cashbox Lines'),
185         'user_id': fields.many2one('res.users', 'Responsible', required=False),
186         'difference' : fields.function(_compute_difference, method=True, string="Difference", type="float", help="Difference between the theoretical closing balance and the real closing balance."),
187         'last_closing_balance' : fields.function(_compute_last_closing_balance, method=True, string='Last Closing Balance', type='float'),
188     }
189     _defaults = {
190         'state': 'draft',
191         'date': lambda self, cr, uid, context={}: context.get('date', time.strftime("%Y-%m-%d %H:%M:%S")),
192         'user_id': lambda self, cr, uid, context=None: uid,
193     }
194
195     def _get_cash_open_box_lines(self, cr, uid, journal_id, context):
196         details_ids = []
197         if not journal_id:
198             return details_ids
199         journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
200         if journal and (journal.type == 'cash'):
201             last_pieces = None
202
203             if journal.with_last_closing_balance == True:
204                 domain = [('journal_id', '=', journal.id),
205                           ('state', '=', 'confirm')]
206                 last_bank_statement_ids = self.search(cr, uid, domain, limit=1, order='create_date desc', context=context)
207                 if last_bank_statement_ids:
208                     last_bank_statement = self.browse(cr, uid, last_bank_statement_ids[0], context=context)
209
210                     last_pieces = dict(
211                         (line.pieces, line.number_closing) for line in last_bank_statement.details_ids
212                     )
213             for value in journal.cashbox_line_ids:
214                 nested_values = {
215                     'number_closing' : 0,
216                     'number_opening' : last_pieces.get(value.pieces, 0) if isinstance(last_pieces, dict) else 0,
217                     'pieces' : value.pieces
218                 }
219                 details_ids.append([0, False, nested_values])
220         return details_ids
221
222     def create(self, cr, uid, vals, context=None):
223         journal_id = vals.get('journal_id')
224         if journal_id and not vals.get('opening_details_ids'):
225             vals['opening_details_ids'] = vals.get('opening_details_ids') or self._get_cash_open_box_lines(cr, uid, journal_id, context)
226         res_id = super(account_cash_statement, self).create(cr, uid, vals, context=context)
227         self._update_balances(cr, uid, [res_id], context)
228         return res_id
229
230     def write(self, cr, uid, ids, vals, context=None):
231         """
232         Update redord(s) comes in {ids}, with new value comes as {vals}
233         return True on success, False otherwise
234
235         @param cr: cursor to database
236         @param user: id of current user
237         @param ids: list of record ids to be update
238         @param vals: dict of new values to be set
239         @param context: context arguments, like lang, time zone
240
241         @return: True on success, False otherwise
242         """
243         if vals.get('journal_id', False):
244             cashbox_line_obj = self.pool.get('account.cashbox.line')
245             cashbox_ids = cashbox_line_obj.search(cr, uid, [('bank_statement_id', 'in', ids)], context=context)
246             cashbox_line_obj.unlink(cr, uid, cashbox_ids, context)
247         res = super(account_cash_statement, self).write(cr, uid, ids, vals, context=context)
248         self._update_balances(cr, uid, ids, context)
249         return res
250
251     def _user_allow(self, cr, uid, statement_id, context=None):
252         return True
253
254     def button_open(self, cr, uid, ids, context=None):
255         """ Changes statement state to Running.
256         @return: True
257         """
258         obj_seq = self.pool.get('ir.sequence')
259         if context is None:
260             context = {}
261         statement_pool = self.pool.get('account.bank.statement')
262         for statement in statement_pool.browse(cr, uid, ids, context=context):
263             vals = {}
264             if not self._user_allow(cr, uid, statement.id, context=context):
265                 raise osv.except_osv(_('Error!'), (_('You do not have rights to open this %s journal!') % (statement.journal_id.name, )))
266
267             if statement.name and statement.name == '/':
268                 c = {'fiscalyear_id': statement.period_id.fiscalyear_id.id}
269                 if statement.journal_id.sequence_id:
270                     st_number = obj_seq.next_by_id(cr, uid, statement.journal_id.sequence_id.id, context=c)
271                 else:
272                     st_number = obj_seq.next_by_code(cr, uid, 'account.cash.statement', context=c)
273                 vals.update({
274                     'name': st_number
275                 })
276
277             vals.update({
278                 'state': 'open',
279             })
280             self.write(cr, uid, [statement.id], vals, context=context)
281         return True
282
283     def statement_close(self, cr, uid, ids, journal_type='bank', context=None):
284         if journal_type == 'bank':
285             return super(account_cash_statement, self).statement_close(cr, uid, ids, journal_type, context)
286         vals = {
287             'state':'confirm',
288             'closing_date': time.strftime("%Y-%m-%d %H:%M:%S")
289         }
290         return self.write(cr, uid, ids, vals, context=context)
291
292     def check_status_condition(self, cr, uid, state, journal_type='bank'):
293         if journal_type == 'bank':
294             return super(account_cash_statement, self).check_status_condition(cr, uid, state, journal_type)
295         return state=='open'
296
297     def button_confirm_cash(self, cr, uid, ids, context=None):
298         super(account_cash_statement, self).button_confirm_bank(cr, uid, ids, context=context)
299         absl_proxy = self.pool.get('account.bank.statement.line')
300
301         TABLES = ((_('Profit'), 'profit_account_id'), (_('Loss'), 'loss_account_id'),)
302
303         for obj in self.browse(cr, uid, ids, context=context):
304             if obj.difference == 0.0:
305                 continue
306
307             for item_label, item_account in TABLES:
308                 if getattr(obj.journal_id, item_account):
309                     raise osv.except_osv(_('Error!'),
310                                          _('There is no %s Account on the journal %s.') % (item_label, obj.journal_id.name,))
311
312             is_profit = obj.difference < 0.0
313
314             account = getattr(obj.journal_id, TABLES[is_profit][1])
315
316             values = {
317                 'statement_id' : obj.id,
318                 'journal_id' : obj.journal_id.id,
319                 'account_id' : account.id,
320                 'amount' : obj.difference,
321                 'name' : 'Exceptional %s' % TABLES[is_profit][0],
322             }
323
324             absl_proxy.create(cr, uid, values, context=context)
325
326         return self.write(cr, uid, ids, {'closing_date': time.strftime("%Y-%m-%d %H:%M:%S")}, context=context)
327
328
329 class account_journal(osv.osv):
330     _inherit = 'account.journal'
331
332     def _default_cashbox_line_ids(self, cr, uid, context=None):
333         # Return a list of coins in Euros.
334         result = [
335             dict(pieces=value) for value in [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, 100, 200, 500]
336         ]
337         return result
338
339     _columns = {
340         'cashbox_line_ids' : fields.one2many('account.journal.cashbox.line', 'journal_id', 'CashBox'),
341     }
342
343     _defaults = {
344         'cashbox_line_ids' : _default_cashbox_line_ids,
345     }
346
347
348 class account_journal_cashbox_line(osv.osv):
349     _name = 'account.journal.cashbox.line'
350     _rec_name = 'pieces'
351     _columns = {
352         'pieces': fields.float('Values', digits_compute=dp.get_precision('Account')),
353         'journal_id' : fields.many2one('account.journal', 'Journal', required=True, select=1, ondelete="cascade"),
354     }
355
356     _order = 'pieces asc'
357
358
359 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: