[FIX] account: several fixes on the new bank statement reconciliation widget
[odoo/odoo.git] / addons / account / wizard / account_invoice_refund.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 import time
23
24 from openerp.osv import fields, osv
25 from openerp.tools.translate import _
26
27 class account_invoice_refund(osv.osv_memory):
28
29     """Refunds invoice"""
30
31     _name = "account.invoice.refund"
32     _description = "Invoice Refund"
33     _columns = {
34        'date': fields.date('Date', help='This date will be used as the invoice date for credit note and period will be chosen accordingly!'),
35        'period': fields.many2one('account.period', 'Force period'),
36        'journal_id': fields.many2one('account.journal', 'Refund Journal', help='You can select here the journal to use for the credit note that will be created. If you leave that field empty, it will use the same journal as the current invoice.'),
37        'description': fields.char('Reason', required=True),
38        'filter_refund': fields.selection([('refund', 'Create a draft refund'), ('cancel', 'Cancel: create refund and reconcile'),('modify', 'Modify: create refund, reconcile and create a new draft invoice')], "Refund Method", required=True, help='Refund base on this type. You can not Modify and Cancel if the invoice is already reconciled'),
39     }
40
41     def _get_journal(self, cr, uid, context=None):
42         obj_journal = self.pool.get('account.journal')
43         user_obj = self.pool.get('res.users')
44         if context is None:
45             context = {}
46         inv_type = context.get('type', 'out_invoice')
47         company_id = user_obj.browse(cr, uid, uid, context=context).company_id.id
48         type = (inv_type == 'out_invoice') and 'sale_refund' or \
49                (inv_type == 'out_refund') and 'sale' or \
50                (inv_type == 'in_invoice') and 'purchase_refund' or \
51                (inv_type == 'in_refund') and 'purchase'
52         journal = obj_journal.search(cr, uid, [('type', '=', type), ('company_id','=',company_id)], limit=1, context=context)
53         return journal and journal[0] or False
54
55     def _get_reason(self, cr, uid, context=None):
56         active_id = context and context.get('active_id', False)
57         if active_id:
58             inv = self.pool.get('account.invoice').browse(cr, uid, active_id, context=context)
59             return inv.name
60         else:
61             return ''
62
63     _defaults = {
64         'date': lambda *a: time.strftime('%Y-%m-%d'),
65         'journal_id': _get_journal,
66         'filter_refund': 'refund',
67         'description': _get_reason,
68     }
69
70     def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
71         journal_obj = self.pool.get('account.journal')
72         user_obj = self.pool.get('res.users')
73         # remove the entry with key 'form_view_ref', otherwise fields_view_get crashes
74         context = dict(context or {})
75         context.pop('form_view_ref', None)
76         res = super(account_invoice_refund,self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
77         type = context.get('type', 'out_invoice')
78         company_id = user_obj.browse(cr, uid, uid, context=context).company_id.id
79         journal_type = (type == 'out_invoice') and 'sale_refund' or \
80                        (type == 'out_refund') and 'sale' or \
81                        (type == 'in_invoice') and 'purchase_refund' or \
82                        (type == 'in_refund') and 'purchase'
83         for field in res['fields']:
84             if field == 'journal_id':
85                 journal_select = journal_obj._name_search(cr, uid, '', [('type', '=', journal_type), ('company_id','child_of',[company_id])], context=context)
86                 res['fields'][field]['selection'] = journal_select
87         return res
88
89     def compute_refund(self, cr, uid, ids, mode='refund', context=None):
90         """
91         @param cr: the current row, from the database cursor,
92         @param uid: the current user’s ID for security checks,
93         @param ids: the account invoice refund’s ID or list of IDs
94
95         """
96         inv_obj = self.pool.get('account.invoice')
97         reconcile_obj = self.pool.get('account.move.reconcile')
98         account_m_line_obj = self.pool.get('account.move.line')
99         mod_obj = self.pool.get('ir.model.data')
100         act_obj = self.pool.get('ir.actions.act_window')
101         inv_tax_obj = self.pool.get('account.invoice.tax')
102         inv_line_obj = self.pool.get('account.invoice.line')
103         res_users_obj = self.pool.get('res.users')
104         if context is None:
105             context = {}
106
107         for form in self.browse(cr, uid, ids, context=context):
108             created_inv = []
109             date = False
110             period = False
111             description = False
112             company = res_users_obj.browse(cr, uid, uid, context=context).company_id
113             journal_id = form.journal_id.id
114             for inv in inv_obj.browse(cr, uid, context.get('active_ids'), context=context):
115                 if inv.state in ['draft', 'proforma2', 'cancel']:
116                     raise osv.except_osv(_('Error!'), _('Cannot %s draft/proforma/cancel invoice.') % (mode))
117                 if inv.reconciled and mode in ('cancel', 'modify'):
118                     raise osv.except_osv(_('Error!'), _('Cannot %s invoice which is already reconciled, invoice should be unreconciled first. You can only refund this invoice.') % (mode))
119                 if form.period.id:
120                     period = form.period.id
121                 else:
122                     period = inv.period_id and inv.period_id.id or False
123
124                 if not journal_id:
125                     journal_id = inv.journal_id.id
126
127                 if form.date:
128                     date = form.date
129                     if not form.period.id:
130                             cr.execute("select name from ir_model_fields \
131                                             where model = 'account.period' \
132                                             and name = 'company_id'")
133                             result_query = cr.fetchone()
134                             if result_query:
135                                 cr.execute("""select p.id from account_fiscalyear y, account_period p where y.id=p.fiscalyear_id \
136                                     and date(%s) between p.date_start AND p.date_stop and y.company_id = %s limit 1""", (date, company.id,))
137                             else:
138                                 cr.execute("""SELECT id
139                                         from account_period where date(%s)
140                                         between date_start AND  date_stop  \
141                                         limit 1 """, (date,))
142                             res = cr.fetchone()
143                             if res:
144                                 period = res[0]
145                 else:
146                     date = inv.date_invoice
147                 if form.description:
148                     description = form.description
149                 else:
150                     description = inv.name
151
152                 if not period:
153                     raise osv.except_osv(_('Insufficient Data!'), \
154                                             _('No period found on the invoice.'))
155
156                 refund_id = inv_obj.refund(cr, uid, [inv.id], date, period, description, journal_id, context=context)
157                 refund = inv_obj.browse(cr, uid, refund_id[0], context=context)
158                 inv_obj.write(cr, uid, [refund.id], {'date_due': date,
159                                                 'check_total': inv.check_total})
160                 inv_obj.button_compute(cr, uid, refund_id)
161
162                 created_inv.append(refund_id[0])
163                 if mode in ('cancel', 'modify'):
164                     movelines = inv.move_id.line_id
165                     to_reconcile_ids = {}
166                     for line in movelines:
167                         if line.account_id.id == inv.account_id.id:
168                             to_reconcile_ids.setdefault(line.account_id.id, []).append(line.id)
169                         if line.reconcile_id:
170                             line.reconcile_id.unlink()
171                     refund.signal_workflow('invoice_open')
172                     refund = inv_obj.browse(cr, uid, refund_id[0], context=context)
173                     for tmpline in  refund.move_id.line_id:
174                         if tmpline.account_id.id == inv.account_id.id:
175                             to_reconcile_ids[tmpline.account_id.id].append(tmpline.id)
176                     for account in to_reconcile_ids:
177                         account_m_line_obj.reconcile(cr, uid, to_reconcile_ids[account],
178                                         writeoff_period_id=period,
179                                         writeoff_journal_id = inv.journal_id.id,
180                                         writeoff_acc_id=inv.account_id.id
181                                         )
182                     if mode == 'modify':
183                         invoice = inv_obj.read(cr, uid, [inv.id],
184                                     ['name', 'type', 'number', 'reference',
185                                     'comment', 'date_due', 'partner_id',
186                                     'partner_insite', 'partner_contact',
187                                     'partner_ref', 'payment_term', 'account_id',
188                                     'currency_id', 'invoice_line', 'tax_line',
189                                     'journal_id', 'period_id'], context=context)
190                         invoice = invoice[0]
191                         del invoice['id']
192                         invoice_lines = inv_line_obj.browse(cr, uid, invoice['invoice_line'], context=context)
193                         invoice_lines = inv_obj._refund_cleanup_lines(cr, uid, invoice_lines, context=context)
194                         tax_lines = inv_tax_obj.browse(cr, uid, invoice['tax_line'], context=context)
195                         tax_lines = inv_obj._refund_cleanup_lines(cr, uid, tax_lines, context=context)
196                         invoice.update({
197                             'type': inv.type,
198                             'date_invoice': date,
199                             'state': 'draft',
200                             'number': False,
201                             'invoice_line': invoice_lines,
202                             'tax_line': tax_lines,
203                             'period_id': period,
204                             'name': description
205                         })
206                         for field in ('partner_id', 'account_id', 'currency_id',
207                                          'payment_term', 'journal_id'):
208                                 invoice[field] = invoice[field] and invoice[field][0]
209                         inv_id = inv_obj.create(cr, uid, invoice, {})
210                         if inv.payment_term.id:
211                             data = inv_obj.onchange_payment_term_date_invoice(cr, uid, [inv_id], inv.payment_term.id, date)
212                             if 'value' in data and data['value']:
213                                 inv_obj.write(cr, uid, [inv_id], data['value'])
214                         created_inv.append(inv_id)
215
216             xml_id = (inv.type == 'out_refund') and 'action_invoice_tree1' or \
217                      (inv.type == 'in_refund') and 'action_invoice_tree2' or \
218                      (inv.type == 'out_invoice') and 'action_invoice_tree3' or \
219                      (inv.type == 'in_invoice') and 'action_invoice_tree4'
220             result = mod_obj.get_object_reference(cr, uid, 'account', xml_id)
221             id = result and result[1] or False
222
223             result = act_obj.read(cr, uid, [id], context=context)[0]
224             invoice_domain = eval(result['domain'])
225             invoice_domain.append(('id', 'in', created_inv))
226             result['domain'] = invoice_domain
227             return result
228
229     def invoice_refund(self, cr, uid, ids, context=None):
230         data_refund = self.read(cr, uid, ids, ['filter_refund'],context=context)[0]['filter_refund']
231         return self.compute_refund(cr, uid, ids, data_refund, context=context)
232
233
234
235 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: