404 lines
20 KiB
Python
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:
|