~eduardo-bayardo-bias/openobject-addons/bias_trunk_v8

« back to all changes in this revision

Viewing changes to account_voucher_payment_fix/account_voucher.py

  • Committer: Eduardo Bayardo
  • Date: 2016-10-06 21:28:13 UTC
  • Revision ID: eduardo.bayardo@bias.com.mx-20161006212813-z4utlenj8qtt3dlg
update crm protocol module

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- encoding: utf-8 -*-
 
2
##############################################################################
 
3
#
 
4
#    Copyright (C) 2010-TODAY Tech Receptives (<http://www.techreceptives.com>).
 
5
#   
 
6
#    Authors : Tech Receptives & Win-Soft - Web Solution (http://www.win-soft.ch)
 
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
from openerp.osv import fields, osv
 
24
from openerp.tools.translate import _
 
25
import time
 
26
import openerp.addons.decimal_precision as dp
 
27
from openerp import SUPERUSER_ID
 
28
 
 
29
 
 
30
class account_voucher(osv.osv):
 
31
    
 
32
    _inherit = "account.voucher"
 
33
    
 
34
    def default_get(self, cr, user, fields_list, context=None):
 
35
        """
 
36
        Fix field journal_id on voucher: Unknown journal entry
 
37
        
 
38
        Returns default values for fields
 
39
        @param fields_list: list of fields, for which default values are required to be read
 
40
        @param context: context arguments, like lang, time zone
 
41
 
 
42
        @return: Returns a dict that contains default values for fields
 
43
        """
 
44
        if context is None:
 
45
            context = {}
 
46
        values = super(account_voucher, self).default_get(cr, user, fields_list, context=context)
 
47
        if user != SUPERUSER_ID:
 
48
            values.update({'journal_id': False})
 
49
        return values
 
50
 
 
51
    def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
 
52
        '''
 
53
        Fix onchange journal id: set period for used date
 
54
        '''
 
55
        res = super(account_voucher,self).onchange_journal(cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=context)
 
56
        if date and res:
 
57
            period_ids = self.pool['account.period'].find(cr, uid, date,context=dict(context, company_id=company_id,account_period_prefer_normal=True))
 
58
            res['value']['period_id'] =period_ids and period_ids[0] or False
 
59
        return res
 
60
    
 
61
    def action_move_line_create(self, cr, uid, ids, context=None):
 
62
        '''
 
63
        Confirm the vouchers given in ids and create the journal entries for each of them
 
64
        '''
 
65
        if context is None:
 
66
            context = {}
 
67
        move_pool = self.pool.get('account.move')
 
68
        move_line_pool = self.pool.get('account.move.line')
 
69
        for voucher in self.browse(cr, uid, ids, context=context):
 
70
            if voucher.tax_id:
 
71
                self.compute_tax(cr, uid, [voucher.id], context)
 
72
                voucher = self.browse(cr, uid, voucher.id)
 
73
            local_context = dict(context, force_company=voucher.journal_id.company_id.id)
 
74
            if voucher.move_id:
 
75
                continue
 
76
            company_currency = self._get_company_currency(cr, uid, voucher.id, context)
 
77
            current_currency = self._get_current_currency(cr, uid, voucher.id, context)
 
78
            # we select the context to use accordingly if it's a multicurrency case or not
 
79
            context = self._sel_context(cr, uid, voucher.id, context)
 
80
            # But for the operations made by _convert_amount, we always need to give the date in the context
 
81
            ctx = context.copy()
 
82
            ctx.update({'date': voucher.date})
 
83
            # Create the account move record.
 
84
            move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
 
85
            # Get the name of the account_move just created
 
86
            name = move_pool.browse(cr, uid, move_id, context=context).name
 
87
            # Create the first line of the voucher
 
88
            move_line_id = move_line_pool.create(cr, uid, self.first_move_line_get(cr,uid,voucher.id, move_id, company_currency, current_currency, local_context), local_context)
 
89
            move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
 
90
            line_total = move_line_brw.debit - move_line_brw.credit
 
91
            rec_list_ids = []
 
92
            if voucher.tax_id and not voucher.tax_id.price_include:
 
93
                if voucher.type == 'sale':
 
94
                    line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
 
95
                elif voucher.type == 'purchase':
 
96
                    line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
 
97
            # Create one move line per voucher line where amount is not 0.0
 
98
            line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
 
99
            # Create the writeoff line if needed
 
100
            ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, local_context)
 
101
            if ml_writeoff:
 
102
                move_line_pool.create(cr, uid, ml_writeoff, local_context)
 
103
            # We post the voucher.
 
104
            self.write(cr, uid, [voucher.id], {
 
105
                'move_id': move_id,
 
106
                'state': 'posted',
 
107
                'number': name,
 
108
            })
 
109
            if voucher.journal_id.entry_posted:
 
110
                move_pool.post(cr, uid, [move_id], context={})
 
111
            # We automatically reconcile the account move lines.
 
112
            reconcile = False
 
113
            for rec_ids in rec_list_ids:
 
114
                if len(rec_ids) >= 2:
 
115
                    reconcile = move_line_pool.reconcile_partial(cr, uid, rec_ids, writeoff_acc_id=voucher.writeoff_acc_id.id, writeoff_period_id=voucher.period_id.id, writeoff_journal_id=voucher.journal_id.id)
 
116
        return True
 
117
 
 
118
    def _get_writeoff_amount(self, cr, uid, ids, name, args, context={}):
 
119
        if not ids: return {}
 
120
        currency_obj = self.pool.get('res.currency')
 
121
        res = {}; diff_amount = 0.0
 
122
        debit = credit = total_inv_residual = 0.0
 
123
        for voucher in self.browse(cr, uid, ids, context=context):
 
124
            sign = voucher.type == 'payment' and -1 or 1
 
125
            for l in voucher.line_dr_ids:
 
126
                debit += l.amount
 
127
                if context.has_key('click_register_payment'):
 
128
                    if voucher.payment_option == 'with_writeoff' and (l.move_line_id.invoice.id == context.get('invoice_id') or l.reconcile):
 
129
                        l.write({'reconcile': True, 'amount': l.amount_unreconciled})
 
130
                        total_inv_residual += (l.amount > 0  and l.amount_unreconciled - l.amount)
 
131
            for l in voucher.line_cr_ids:
 
132
                credit += l.amount 
 
133
                if context.has_key('click_register_payment'):
 
134
                    if voucher.payment_option == 'with_writeoff' and (l.move_line_id.invoice.id == context.get('invoice_id') or l.reconcile):
 
135
                        l.write({'reconcile': True, 'amount': l.amount_unreconciled})
 
136
                        total_inv_residual += (l.amount > 0  and l.amount_unreconciled - l.amount)
 
137
            currency = voucher.currency_id or voucher.company_id.currency_id
 
138
            write_off_amount = voucher.amount - sign * (credit - debit)
 
139
            if context.has_key('click_register_payment'):
 
140
                write_off_amount = total_inv_residual * sign
 
141
                
 
142
            res[voucher.id] =  currency_obj.round(cr, uid, currency, write_off_amount)
 
143
        return res
 
144
    
 
145
    _columns = {
 
146
        'writeoff_amount': fields.function(_get_writeoff_amount, string='Difference Amount', type='float', readonly=True, help="Computed as the difference between the amount stated in the voucher and the sum of allocation on the voucher lines."),        
 
147
        }
 
148
    
 
149
    def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount, type, context={}):
 
150
        #working on context to differentiate the button clicks without affecting the present code
 
151
        #Workaround to send context in _compute_writeoff_amount()
 
152
        debit = credit = total_inv_residual = 0.0
 
153
        sign = type == 'payment' and -1 or 1
 
154
        for l in line_dr_ids:
 
155
            debit += l['amount']
 
156
            total_inv_residual += (l['amount'] > 0 and l['amount_unreconciled'] - l['amount'])
 
157
        for l in line_cr_ids:
 
158
            credit += l['amount']
 
159
            total_inv_residual += (l['amount'] > 0 and  l['amount_unreconciled'] - l['amount'])
 
160
        writeoff_amount = amount - sign * (credit - debit)
 
161
        if context.has_key('click_register_payment'):
 
162
            writeoff_amount = (total_inv_residual * sign)
 
163
        return writeoff_amount
 
164
    
 
165
    def onchange_line_ids(self, cr, uid, ids, line_dr_ids, line_cr_ids, amount, voucher_currency, type, context=None):
 
166
        #Workaround to send context in _compute_writeoff_amount()
 
167
        context = context or {}
 
168
        if not line_dr_ids and not line_cr_ids:
 
169
            return {'value':{'writeoff_amount': 0.0}}
 
170
        line_osv = self.pool.get("account.voucher.line")
 
171
         
 
172
        line_dr_ids = resolve_o2m_operations(cr, uid, line_osv, line_dr_ids, ['amount'], context)
 
173
        line_cr_ids = resolve_o2m_operations(cr, uid, line_osv, line_cr_ids, ['amount'], context)
 
174
         
 
175
        #compute the field is_multi_currency that is used to hide/display options linked to secondary currency on the voucher
 
176
        is_multi_currency = False
 
177
        #loop on the voucher lines to see if one of these has a secondary currency. If yes, we need to see the options
 
178
        for voucher_line in line_dr_ids+line_cr_ids:
 
179
            line_id = voucher_line.get('id') and self.pool.get('account.voucher.line').browse(cr, uid, voucher_line['id'], context=context).move_line_id.id or voucher_line.get('move_line_id')
 
180
            if line_id and self.pool.get('account.move.line').browse(cr, uid, line_id, context=context).currency_id:
 
181
                is_multi_currency = True
 
182
                break
 
183
        return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount, type, context=context), 'is_multi_currency': is_multi_currency}}
 
184
 
 
185
    def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
 
186
        #Workaround to send context in _compute_writeoff_amount()
 
187
        """
 
188
        Returns a dict that contains new values and context
 
189
 
 
190
        @param partner_id: latest value from user input for field partner_id
 
191
        @param args: other arguments
 
192
        @param context: context arguments, like lang, time zone
 
193
 
 
194
        @return: Returns a dict which contains new values, and context
 
195
        """
 
196
        def _remove_noise_in_o2m():
 
197
            """if the line is partially reconciled, then we must pay attention to display it only once and
 
198
                in the good o2m.
 
199
                This function returns True if the line is considered as noise and should not be displayed
 
200
            """
 
201
            if line.reconcile_partial_id:
 
202
                if currency_id == line.currency_id.id:
 
203
                    if line.amount_residual_currency <= 0:
 
204
                        return True
 
205
                else:
 
206
                    if line.amount_residual <= 0:
 
207
                        return True
 
208
            return False
 
209
 
 
210
        if context is None:
 
211
            context = {}
 
212
        context_multi_currency = context.copy()
 
213
 
 
214
        currency_pool = self.pool.get('res.currency')
 
215
        move_line_pool = self.pool.get('account.move.line')
 
216
        partner_pool = self.pool.get('res.partner')
 
217
        journal_pool = self.pool.get('account.journal')
 
218
        line_pool = self.pool.get('account.voucher.line')
 
219
 
 
220
        #set default values
 
221
        default = {
 
222
            'value': {'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
 
223
        }
 
224
 
 
225
        #drop existing lines
 
226
        line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
 
227
        if line_ids:
 
228
            line_pool.unlink(cr, uid, line_ids)
 
229
 
 
230
        if not partner_id or not journal_id:
 
231
            return default
 
232
 
 
233
        journal = journal_pool.browse(cr, uid, journal_id, context=context)
 
234
        partner = partner_pool.browse(cr, uid, partner_id, context=context)
 
235
        currency_id = currency_id or journal.company_id.currency_id.id
 
236
 
 
237
        total_credit = 0.0
 
238
        total_debit = 0.0
 
239
        account_type = 'receivable'
 
240
        if ttype == 'payment':
 
241
            account_type = 'payable'
 
242
            total_debit = price or 0.0
 
243
        else:
 
244
            total_credit = price or 0.0
 
245
            account_type = 'receivable'
 
246
 
 
247
        if not context.get('move_line_ids', False):
 
248
            ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
 
249
        else:
 
250
            ids = context['move_line_ids']
 
251
        invoice_id = context.get('invoice_id', False)
 
252
        company_currency = journal.company_id.currency_id.id
 
253
        move_lines_found = []
 
254
 
 
255
        #order the lines by most old first
 
256
        ids.reverse()
 
257
        account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
 
258
 
 
259
        #compute the total debit/credit and look for a matching open amount or invoice
 
260
        for line in account_move_lines:
 
261
            if _remove_noise_in_o2m():
 
262
                continue
 
263
 
 
264
            if invoice_id:
 
265
                if line.invoice.id == invoice_id:
 
266
                    #if the invoice linked to the voucher line is equal to the invoice_id in context
 
267
                    #then we assign the amount on that line, whatever the other voucher lines
 
268
                    move_lines_found.append(line.id)
 
269
            elif currency_id == company_currency:
 
270
                #otherwise treatments is the same but with other field names
 
271
                if line.amount_residual == price:
 
272
                    #if the amount residual is equal the amount voucher, we assign it to that voucher
 
273
                    #line, whatever the other voucher lines
 
274
                    move_lines_found.append(line.id)
 
275
                    break
 
276
                #otherwise we will split the voucher amount on each line (by most old first)
 
277
                total_credit += line.credit or 0.0
 
278
                total_debit += line.debit or 0.0
 
279
            elif currency_id == line.currency_id.id:
 
280
                if line.amount_residual_currency == price:
 
281
                    move_lines_found.append(line.id)
 
282
                    break
 
283
                total_credit += line.credit and line.amount_currency or 0.0
 
284
                total_debit += line.debit and line.amount_currency or 0.0
 
285
 
 
286
        #voucher line creation
 
287
        for line in account_move_lines:
 
288
 
 
289
            if _remove_noise_in_o2m():
 
290
                continue
 
291
 
 
292
            if line.currency_id and currency_id == line.currency_id.id:
 
293
                amount_original = abs(line.amount_currency)
 
294
                amount_unreconciled = abs(line.amount_residual_currency)
 
295
            else:
 
296
                #always use the amount booked in the company currency as the basis of the conversion into the voucher currency
 
297
                amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0, context=context_multi_currency)
 
298
                amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual), context=context_multi_currency)
 
299
            line_currency_id = line.currency_id and line.currency_id.id or company_currency
 
300
            rs = {
 
301
                'name':line.move_id.name,
 
302
                'type': line.credit and 'dr' or 'cr',
 
303
                'move_line_id':line.id,
 
304
                'account_id':line.account_id.id,
 
305
                'amount_original': amount_original,
 
306
                'amount': (line.id in move_lines_found) and min(abs(price), amount_unreconciled) or 0.0,
 
307
                'date_original':line.date,
 
308
                'date_due':line.date_maturity,
 
309
                'amount_unreconciled': amount_unreconciled,
 
310
                'currency_id': line_currency_id,
 
311
            }
 
312
            price -= rs['amount']
 
313
            #in case a corresponding move_line hasn't been found, we now try to assign the voucher amount
 
314
            #on existing invoices: we split voucher amount by most old first, but only for lines in the same currency
 
315
            if not move_lines_found:
 
316
                if currency_id == line_currency_id:
 
317
                    if line.credit:
 
318
                        amount = min(amount_unreconciled, abs(total_debit))
 
319
                        rs['amount'] = amount
 
320
                        total_debit -= amount
 
321
                    else:
 
322
                        amount = min(amount_unreconciled, abs(total_credit))
 
323
                        rs['amount'] = amount
 
324
                        total_credit -= amount
 
325
 
 
326
            if rs['amount_unreconciled'] == rs['amount']:
 
327
                rs['reconcile'] = True
 
328
 
 
329
            if rs['type'] == 'cr':
 
330
                default['value']['line_cr_ids'].append(rs)
 
331
            else:
 
332
                default['value']['line_dr_ids'].append(rs)
 
333
 
 
334
            if ttype == 'payment' and len(default['value']['line_cr_ids']) > 0:
 
335
                default['value']['pre_line'] = 1
 
336
            elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0:
 
337
                default['value']['pre_line'] = 1
 
338
            default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price, ttype, context=context)
 
339
        return default
 
340
 
 
341
account_voucher()
 
342
 
 
343
class invoice(osv.osv):
 
344
    _inherit = 'account.invoice'
 
345
 
 
346
    def invoice_pay_customer(self, cr, uid, ids, context=None):
 
347
        #overriding function to send a special context...
 
348
        if not ids: return []
 
349
        dummy, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_dialog_form')
 
350
 
 
351
        inv = self.browse(cr, uid, ids[0], context=context)
 
352
        return {
 
353
            'name':_("Pay Invoice"),
 
354
            'view_mode': 'form',
 
355
            'view_id': view_id,
 
356
            'view_type': 'form',
 
357
            'res_model': 'account.voucher',
 
358
            'type': 'ir.actions.act_window',
 
359
            'nodestroy': True,
 
360
            'target': 'new',
 
361
            'domain': '[]',
 
362
            'context': {
 
363
                'payment_expected_currency': inv.currency_id.id,
 
364
                'default_partner_id': self.pool.get('res.partner')._find_accounting_partner(inv.partner_id).id,
 
365
                'default_amount': inv.type in ('out_refund', 'in_refund') and -inv.residual or inv.residual,
 
366
                'default_reference': inv.name,
 
367
                'close_after_process': True,
 
368
                'invoice_type': inv.type,
 
369
                'invoice_id': inv.id,
 
370
                'default_type': inv.type in ('out_invoice','out_refund') and 'receipt' or 'payment',
 
371
                'type': inv.type in ('out_invoice','out_refund') and 'receipt' or 'payment',
 
372
                'click_register_payment':True,
 
373
            }
 
374
        }
 
375
 
 
376
invoice()
 
377
 
 
378
def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
 
379
    results = []
 
380
    for operation in operations:
 
381
        result = None
 
382
        if not isinstance(operation, (list, tuple)):
 
383
            result = target_osv.read(cr, uid, operation, fields, context=context)
 
384
        elif operation[0] == 0:
 
385
            # may be necessary to check if all the fields are here and get the default values?
 
386
            result = operation[2]
 
387
        elif operation[0] == 1:
 
388
            result = target_osv.read(cr, uid, operation[1], fields, context=context)
 
389
            if not result: result = {}
 
390
            result.update(operation[2])
 
391
        elif operation[0] == 4:
 
392
            result = target_osv.read(cr, uid, operation[1], fields, context=context)
 
393
        if result != None:
 
394
            results.append(result)
 
395
    return results
 
396
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
 
 
b'\\ No newline at end of file'