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:
 |