407 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			407 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Python
		
	
	
# -*- coding: utf-8 -*-
 | 
						|
##############################################################################
 | 
						|
#
 | 
						|
#    OpenERP, Open Source Management Solution
 | 
						|
#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
 | 
						|
#
 | 
						|
#    cam_invoice_skonto, Custom Module for OpenERP
 | 
						|
#    Copyright (C) 2014 Camadeus Consulting GmbH (<http://www.camadeus.at>).
 | 
						|
#
 | 
						|
#    This program is free software: you can redistribute it and/or modify
 | 
						|
#    it under the terms of the GNU Affero General Public License as
 | 
						|
#    published by the Free Software Foundation, either version 3 of the
 | 
						|
#    License, or (at your option) any later version.
 | 
						|
#
 | 
						|
#    This program is distributed in the hope that it will be useful,
 | 
						|
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
#    GNU Affero General Public License for more details.
 | 
						|
#
 | 
						|
#    You should have received a copy of the GNU Affero General Public License
 | 
						|
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
						|
#
 | 
						|
##############################################################################
 | 
						|
 | 
						|
from openerp.osv import fields, osv
 | 
						|
from openerp import models, fields as f, api
 | 
						|
import openerp.addons.decimal_precision as dp
 | 
						|
from datetime import date, timedelta, datetime
 | 
						|
from openerp.tools.translate import _
 | 
						|
 | 
						|
class account_payment_term(osv.osv):
 | 
						|
    _inherit = 'account.payment.term'
 | 
						|
 | 
						|
    _columns = {
 | 
						|
        'netto_tage': fields.integer('Netto Tage'),
 | 
						|
        'skonto_tage': fields.integer('Skonto Tage'),
 | 
						|
        'skonto_prozent': fields.float('Skonto Prozent'),
 | 
						|
    }
 | 
						|
    
 | 
						|
    def write(self, cr, uid, ids, values, context=None):
 | 
						|
        if context is None:
 | 
						|
            context = {}
 | 
						|
        
 | 
						|
        tl_obj = self.pool.get('account.payment.term.line')
 | 
						|
        for pay_term in self.browse(cr, uid, ids, context=context):
 | 
						|
            # Delete old account.payment.term.line
 | 
						|
            for tl_id in pay_term.line_ids:
 | 
						|
                tl_obj.unlink(cr, uid, tl_id.id, context=context)
 | 
						|
            # Create new account.payment.term.line based on netto_tage
 | 
						|
            days = 0
 | 
						|
            if('netto_tage' in values): 
 | 
						|
                days = values['netto_tage']
 | 
						|
            else: 
 | 
						|
                days = pay_term.netto_tage
 | 
						|
            values['line_ids'] = [(0,0, {'payment_id':pay_term.id, 'value': 'balance', 'days': days, 'days2':0})]
 | 
						|
            
 | 
						|
        return super(account_payment_term, self).write(cr, uid, ids, values, context=context)
 | 
						|
        
 | 
						|
    def create(self, cr, uid, vals, context=None):
 | 
						|
        if context is None:
 | 
						|
            context = {}
 | 
						|
            
 | 
						|
        new_id = super(account_payment_term, self).create(cr, uid, vals, context=context)
 | 
						|
        self.write(cr, uid, new_id, {'netto_tage': vals['netto_tage']}, context=context)
 | 
						|
        
 | 
						|
        return new_id
 | 
						|
            
 | 
						|
class account_invoice(models.Model):
 | 
						|
    _inherit = 'account.invoice'
 | 
						|
 | 
						|
    @api.one
 | 
						|
    def _skonto_betrag_inkl(self):
 | 
						|
        if self.payment_term and self.payment_term.skonto_prozent:
 | 
						|
            self.skonto_betrag_inkl = self.amount_total * (1 - self.payment_term.skonto_prozent/100.0)
 | 
						|
 | 
						|
    skonto_faelligkeit = f.Date(string=u'Skonto Fälligkeit', readonly=True)  
 | 
						|
    skonto_betrag_inkl = f.Float(string='Betrag inkl. Skonto', digits=dp.get_precision('Account'), readonly=True, compute='_skonto_betrag_inkl') 
 | 
						|
    
 | 
						|
    @api.multi
 | 
						|
    def action_skonto_faelligkeit_assign(self):
 | 
						|
        for inv in self:
 | 
						|
            if inv.payment_term and inv.payment_term.skonto_tage:
 | 
						|
                inv.write({'skonto_faelligkeit': datetime.strptime(inv.date_invoice, '%Y-%m-%d') + timedelta(days=inv.payment_term.skonto_tage)})
 | 
						|
        return True       
 | 
						|
 | 
						|
    # Add context 'click_register_payment'
 | 
						|
    def invoice_pay_customer(self, cr, uid, ids, context=None):
 | 
						|
        if not ids: return []
 | 
						|
        dummy, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_dialog_form')
 | 
						|
 | 
						|
        inv = self.browse(cr, uid, ids[0], context=context)
 | 
						|
        return {
 | 
						|
            'name':_("Pay Invoice"),
 | 
						|
            'view_mode': 'form',
 | 
						|
            'view_id': view_id,
 | 
						|
            'view_type': 'form',
 | 
						|
            'res_model': 'account.voucher',
 | 
						|
            'type': 'ir.actions.act_window',
 | 
						|
            'nodestroy': True,
 | 
						|
            'target': 'new',
 | 
						|
            'domain': '[]',
 | 
						|
            'context': {
 | 
						|
                'payment_expected_currency': inv.currency_id.id,
 | 
						|
                'default_partner_id': self.pool.get('res.partner')._find_accounting_partner(inv.partner_id).id,
 | 
						|
                'default_amount': inv.type in ('out_refund', 'in_refund') and -inv.residual or inv.residual,
 | 
						|
                'default_reference': inv.name,
 | 
						|
                'close_after_process': True,
 | 
						|
                'invoice_type': inv.type,
 | 
						|
                'invoice_id': inv.id,
 | 
						|
                'default_type': inv.type in ('out_invoice','out_refund') and 'receipt' or 'payment',
 | 
						|
                'type': inv.type in ('out_invoice','out_refund') and 'receipt' or 'payment',
 | 
						|
                'click_register_payment':True,
 | 
						|
            }
 | 
						|
        }
 | 
						|
    
 | 
						|
class account_voucher(osv.osv):
 | 
						|
    _inherit = 'account.voucher'
 | 
						|
 | 
						|
    #Workaround to send context in _compute_writeoff_amount()
 | 
						|
    def onchange_line_ids(self, cr, uid, ids, line_dr_ids, line_cr_ids, amount, voucher_currency, type, context=None):        
 | 
						|
        context = context or {}
 | 
						|
        if not line_dr_ids and not line_cr_ids:
 | 
						|
            return {'value':{'writeoff_amount': 0.0}}
 | 
						|
        line_osv = self.pool.get("account.voucher.line")
 | 
						|
        line_dr_ids = resolve_o2m_operations(cr, uid, line_osv, line_dr_ids, ['amount'], context)
 | 
						|
        line_cr_ids = resolve_o2m_operations(cr, uid, line_osv, line_cr_ids, ['amount'], context)
 | 
						|
        #compute the field is_multi_currency that is used to hide/display options linked to secondary currency on the voucher
 | 
						|
        is_multi_currency = False
 | 
						|
        #loop on the voucher lines to see if one of these has a secondary currency. If yes, we need to see the options
 | 
						|
        for voucher_line in line_dr_ids+line_cr_ids:
 | 
						|
            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')
 | 
						|
            if line_id and self.pool.get('account.move.line').browse(cr, uid, line_id, context=context).currency_id:
 | 
						|
                is_multi_currency = True
 | 
						|
                break
 | 
						|
        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}}
 | 
						|
 | 
						|
     
 | 
						|
    def get_default_acc_id(self, cr, uid, args):
 | 
						|
        konto = self.pool.get('account.account').search(cr,uid,[('code','=','99999999')],limit=1)
 | 
						|
        if konto:
 | 
						|
            return konto[0]
 | 
						|
        else:
 | 
						|
            raise osv.except_osv(_('Konfigurationsfehler!'),_("Es ist kein Konto für den Ausgleich der Rechnungen definiert (Skonto). Bitte wenden Sie sich an den Support."))
 | 
						|
         
 | 
						|
        return False
 | 
						|
     
 | 
						|
    _defaults = {
 | 
						|
                'comment': 'Skonto',
 | 
						|
                'writeoff_acc_id': get_default_acc_id,
 | 
						|
                }
 | 
						|
 | 
						|
    def _get_writeoff_amount(self, cr, uid, ids, name, args, context={}):
 | 
						|
        if not ids: return {}
 | 
						|
        currency_obj = self.pool.get('res.currency')
 | 
						|
        res = {}; diff_amount = 0.0
 | 
						|
        debit = credit = total_inv_residual = 0.0
 | 
						|
        for voucher in self.browse(cr, uid, ids, context=context):
 | 
						|
            sign = voucher.type == 'payment' and -1 or 1
 | 
						|
            for l in voucher.line_dr_ids:
 | 
						|
                debit += l.amount
 | 
						|
                if context.has_key('click_register_payment'):
 | 
						|
                    if voucher.payment_option == 'with_writeoff':
 | 
						|
                        # There can be several open lines => we only want to reconcile the line related to the invoice
 | 
						|
                        if l.move_line_id and l.move_line_id.invoice and l.move_line_id.invoice.id == context.get('invoice_id',-1):
 | 
						|
                            l.write({'reconcile': True, 'amount': l.amount_unreconciled})
 | 
						|
                    total_inv_residual += (l.amount > 0  and l.amount_unreconciled - l.amount)
 | 
						|
            for l in voucher.line_cr_ids:
 | 
						|
                credit += l.amount 
 | 
						|
                if context.has_key('click_register_payment'):
 | 
						|
                    if voucher.payment_option == 'with_writeoff':
 | 
						|
                        # There can be several open lines => we only want to reconcile the line related to the invoice
 | 
						|
                        if l.move_line_id and l.move_line_id.invoice and l.move_line_id.invoice.id == context.get('invoice_id',-1):
 | 
						|
                            l.write({'reconcile': True, 'amount': l.amount_unreconciled})
 | 
						|
                    total_inv_residual += (l.amount > 0  and l.amount_unreconciled - l.amount)
 | 
						|
            currency = voucher.currency_id or voucher.company_id.currency_id
 | 
						|
            write_off_amount = voucher.amount - sign * (credit - debit)
 | 
						|
            if context.has_key('click_register_payment'):
 | 
						|
                write_off_amount = total_inv_residual * sign
 | 
						|
                
 | 
						|
            res[voucher.id] =  currency_obj.round(cr, uid, currency, write_off_amount)
 | 
						|
        return res
 | 
						|
    
 | 
						|
    _columns = {
 | 
						|
        '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."),        
 | 
						|
        }
 | 
						|
 | 
						|
    #working on context to differentiate the button clicks without affecting the present code
 | 
						|
    #Workaround to send context in _compute_writeoff_amount()
 | 
						|
    def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount, type, context={}):
 | 
						|
        debit = credit = total_inv_residual = 0.0
 | 
						|
        sign = type == 'payment' and -1 or 1
 | 
						|
        for l in line_dr_ids:
 | 
						|
            debit += l['amount']
 | 
						|
            #total_inv_residual += (l['amount'] > 0 and l['amount_unreconciled'] - l['amount'])
 | 
						|
            total_inv_residual += l['amount_unreconciled']
 | 
						|
        for l in line_cr_ids:
 | 
						|
            credit += l['amount']
 | 
						|
            #total_inv_residual += (l['amount'] > 0 and  l['amount_unreconciled'] - l['amount'])
 | 
						|
            total_inv_residual += l['amount_unreconciled']
 | 
						|
        writeoff_amount = amount - sign * (credit - debit)
 | 
						|
        if context.has_key('click_register_payment'):
 | 
						|
            writeoff_amount = amount - (total_inv_residual)
 | 
						|
        return writeoff_amount
 | 
						|
 | 
						|
    #Workaround to send context in _compute_writeoff_amount()
 | 
						|
    def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
 | 
						|
        """
 | 
						|
        Returns a dict that contains new values and context
 | 
						|
 | 
						|
        @param partner_id: latest value from user input for field partner_id
 | 
						|
        @param args: other arguments
 | 
						|
        @param context: context arguments, like lang, time zone
 | 
						|
 | 
						|
        @return: Returns a dict which contains new values, and context
 | 
						|
        """
 | 
						|
        def _remove_noise_in_o2m():
 | 
						|
            """if the line is partially reconciled, then we must pay attention to display it only once and
 | 
						|
                in the good o2m.
 | 
						|
                This function returns True if the line is considered as noise and should not be displayed
 | 
						|
            """
 | 
						|
            if line.reconcile_partial_id:
 | 
						|
                if currency_id == line.currency_id.id:
 | 
						|
                    if line.amount_residual_currency <= 0:
 | 
						|
                        return True
 | 
						|
                else:
 | 
						|
                    if line.amount_residual <= 0:
 | 
						|
                        return True
 | 
						|
            return False
 | 
						|
 | 
						|
        if context is None:
 | 
						|
            context = {}
 | 
						|
        context_multi_currency = context.copy()
 | 
						|
 | 
						|
        currency_pool = self.pool.get('res.currency')
 | 
						|
        move_line_pool = self.pool.get('account.move.line')
 | 
						|
        partner_pool = self.pool.get('res.partner')
 | 
						|
        journal_pool = self.pool.get('account.journal')
 | 
						|
        line_pool = self.pool.get('account.voucher.line')
 | 
						|
 | 
						|
        #set default values
 | 
						|
        default = {
 | 
						|
            'value': {'line_dr_ids': [], 'line_cr_ids': [], 'pre_line': False},
 | 
						|
        }
 | 
						|
 | 
						|
        # drop existing lines
 | 
						|
        line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])])
 | 
						|
        for line in line_pool.browse(cr, uid, line_ids, context=context):
 | 
						|
            if line.type == 'cr':
 | 
						|
                default['value']['line_cr_ids'].append((2, line.id))
 | 
						|
            else:
 | 
						|
                default['value']['line_dr_ids'].append((2, line.id))
 | 
						|
 | 
						|
        if not partner_id or not journal_id:
 | 
						|
            return default
 | 
						|
 | 
						|
        journal = journal_pool.browse(cr, uid, journal_id, context=context)
 | 
						|
        partner = partner_pool.browse(cr, uid, partner_id, context=context)
 | 
						|
        currency_id = currency_id or journal.company_id.currency_id.id
 | 
						|
 | 
						|
        total_credit = 0.0
 | 
						|
        total_debit = 0.0
 | 
						|
        account_type = None
 | 
						|
        if context.get('account_id'):
 | 
						|
            account_type = self.pool['account.account'].browse(cr, uid, context['account_id'], context=context).type
 | 
						|
        if ttype == 'payment':
 | 
						|
            if not account_type:
 | 
						|
                account_type = 'payable'
 | 
						|
            total_debit = price or 0.0
 | 
						|
        else:
 | 
						|
            total_credit = price or 0.0
 | 
						|
            if not account_type:
 | 
						|
                account_type = 'receivable'
 | 
						|
 | 
						|
        if not context.get('move_line_ids', False):
 | 
						|
            if context.get('click_register_payment', False) and context.get('invoice_id', False):
 | 
						|
                ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id),('invoice','=',context.get('invoice_id'))], context=context)
 | 
						|
            else:
 | 
						|
                ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
 | 
						|
        else:
 | 
						|
            ids = context['move_line_ids']
 | 
						|
        invoice_id = context.get('invoice_id', False)
 | 
						|
        company_currency = journal.company_id.currency_id.id
 | 
						|
        move_lines_found = []
 | 
						|
 | 
						|
        #order the lines by most old first
 | 
						|
        ids.reverse()
 | 
						|
        account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
 | 
						|
 | 
						|
        #compute the total debit/credit and look for a matching open amount or invoice
 | 
						|
        for line in account_move_lines:
 | 
						|
            if _remove_noise_in_o2m():
 | 
						|
                continue
 | 
						|
 | 
						|
            if invoice_id:
 | 
						|
                if line.invoice.id == invoice_id:
 | 
						|
                    #if the invoice linked to the voucher line is equal to the invoice_id in context
 | 
						|
                    #then we assign the amount on that line, whatever the other voucher lines
 | 
						|
                    move_lines_found.append(line.id)
 | 
						|
            elif currency_id == company_currency:
 | 
						|
                #otherwise treatments is the same but with other field names
 | 
						|
                if line.amount_residual == price:
 | 
						|
                    #if the amount residual is equal the amount voucher, we assign it to that voucher
 | 
						|
                    #line, whatever the other voucher lines
 | 
						|
                    move_lines_found.append(line.id)
 | 
						|
                    break
 | 
						|
                #otherwise we will split the voucher amount on each line (by most old first)
 | 
						|
                total_credit += line.credit or 0.0
 | 
						|
                total_debit += line.debit or 0.0
 | 
						|
            elif currency_id == line.currency_id.id:
 | 
						|
                if line.amount_residual_currency == price:
 | 
						|
                    move_lines_found.append(line.id)
 | 
						|
                    break
 | 
						|
                total_credit += line.credit and line.amount_currency or 0.0
 | 
						|
                total_debit += line.debit and line.amount_currency or 0.0
 | 
						|
 | 
						|
        remaining_amount = price
 | 
						|
        #voucher line creation
 | 
						|
        for line in account_move_lines:
 | 
						|
 | 
						|
            if _remove_noise_in_o2m():
 | 
						|
                continue
 | 
						|
 | 
						|
            if line.currency_id and currency_id == line.currency_id.id:
 | 
						|
                amount_original = abs(line.amount_currency)
 | 
						|
                amount_unreconciled = abs(line.amount_residual_currency)
 | 
						|
            else:
 | 
						|
                #always use the amount booked in the company currency as the basis of the conversion into the voucher currency
 | 
						|
                amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0, context=context_multi_currency)
 | 
						|
                amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual), context=context_multi_currency)
 | 
						|
            line_currency_id = line.currency_id and line.currency_id.id or company_currency
 | 
						|
            rs = {
 | 
						|
                'name':line.move_id.name,
 | 
						|
                'type': line.credit and 'dr' or 'cr',
 | 
						|
                'move_line_id':line.id,
 | 
						|
                'account_id':line.account_id.id,
 | 
						|
                'amount_original': amount_original,
 | 
						|
                'amount': (line.id in move_lines_found) and min(abs(remaining_amount), amount_unreconciled) or 0.0,
 | 
						|
                'date_original':line.date,
 | 
						|
                'date_due':line.date_maturity,
 | 
						|
                'amount_unreconciled': amount_unreconciled,
 | 
						|
                'currency_id': line_currency_id,
 | 
						|
            }
 | 
						|
            remaining_amount -= rs['amount']
 | 
						|
            #in case a corresponding move_line hasn't been found, we now try to assign the voucher amount
 | 
						|
            #on existing invoices: we split voucher amount by most old first, but only for lines in the same currency
 | 
						|
            if not move_lines_found:
 | 
						|
                if currency_id == line_currency_id:
 | 
						|
                    if line.credit:
 | 
						|
                        amount = min(amount_unreconciled, abs(total_debit))
 | 
						|
                        rs['amount'] = amount
 | 
						|
                        total_debit -= amount
 | 
						|
                    else:
 | 
						|
                        amount = min(amount_unreconciled, abs(total_credit))
 | 
						|
                        rs['amount'] = amount
 | 
						|
                        total_credit -= amount
 | 
						|
 | 
						|
            if rs['amount_unreconciled'] == rs['amount']:
 | 
						|
                rs['reconcile'] = True
 | 
						|
 | 
						|
            if rs['type'] == 'cr':
 | 
						|
                default['value']['line_cr_ids'].append(rs)
 | 
						|
            else:
 | 
						|
                default['value']['line_dr_ids'].append(rs)
 | 
						|
 | 
						|
            if len(default['value']['line_cr_ids']) > 0:
 | 
						|
                default['value']['pre_line'] = 1
 | 
						|
            elif len(default['value']['line_dr_ids']) > 0:
 | 
						|
                default['value']['pre_line'] = 1
 | 
						|
            default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price, ttype, context=context)
 | 
						|
        return default
 | 
						|
 | 
						|
def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
 | 
						|
    results = []
 | 
						|
    for operation in operations:
 | 
						|
        result = None
 | 
						|
        if not isinstance(operation, (list, tuple)):
 | 
						|
            result = target_osv.read(cr, uid, operation, fields, context=context)
 | 
						|
        elif operation[0] == 0:
 | 
						|
            # may be necessary to check if all the fields are here and get the default values?
 | 
						|
            result = operation[2]
 | 
						|
        elif operation[0] == 1:
 | 
						|
            result = target_osv.read(cr, uid, operation[1], fields, context=context)
 | 
						|
            if not result: result = {}
 | 
						|
            result.update(operation[2])
 | 
						|
        elif operation[0] == 4:
 | 
						|
            result = target_osv.read(cr, uid, operation[1], fields, context=context)
 | 
						|
        if result != None:
 | 
						|
            results.append(result)
 | 
						|
    return results     
 | 
						|
 | 
						|
class sale_order(osv.osv):
 | 
						|
    _inherit = 'sale.order'
 | 
						|
    
 | 
						|
    def _skonto_betrag_inkl(self, cr, uid, ids, field_name, arg, context=None):
 | 
						|
        res = {}
 | 
						|
        sos = self.browse(cr, uid, ids, context=context)
 | 
						|
        for so in sos:
 | 
						|
            if so.payment_term and so.payment_term.skonto_prozent:
 | 
						|
                res[so.id] = so.amount_total * (1 - so.payment_term.skonto_prozent/100.0)
 | 
						|
        return res
 | 
						|
       
 | 
						|
    _columns = {
 | 
						|
        'skonto_betrag_inkl': fields.function(_skonto_betrag_inkl, string='Betrag inkl. Skonto', type='float'),
 | 
						|
    }
 | 
						|
    
 | 
						|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
 |