odoo/ext/custom-addons/cam_invoice_skonto/cam_invoice_skonto.py

404 lines
20 KiB
Python

# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 20014-2016 Camadeus 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: