<commit_before># -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
#
# $Id: account.py 1005 2005-07-25 08:41:42Z nicoe $
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################

import time
import netsvc
from osv import fields, osv
import ir
import pooler
import mx.DateTime
from mx.DateTime import RelativeDateTime

from tools import config

class account_invoice(osv.osv):
	def _amount_untaxed(self, cr, uid, ids, name, args, context={}):
		id_set=",".join(map(str,ids))
		cr.execute("SELECT s.id,COALESCE(SUM(l.price_unit*l.quantity*(100-l.discount))/100.0,0)::decimal(16,2) AS amount FROM account_invoice s LEFT OUTER JOIN account_invoice_line l ON (s.id=l.invoice_id) WHERE s.id IN ("+id_set+") GROUP BY s.id ")
		res=dict(cr.fetchall())
		return res

	def _amount_tax(self, cr, uid, ids, name, args, context={}):
		id_set=",".join(map(str,ids))
		cr.execute("SELECT s.id,COALESCE(SUM(l.amount),0)::decimal(16,2) AS amount FROM account_invoice s LEFT OUTER JOIN account_invoice_tax l ON (s.id=l.invoice_id) WHERE s.id IN ("+id_set+") GROUP BY s.id ")
		res=dict(cr.fetchall())
		return res

	def _amount_total(self, cr, uid, ids, name, args, context={}):
		untax = self._amount_untaxed(cr, uid, ids, name, args, context)
		tax = self._amount_tax(cr, uid, ids, name, args, context)
		res = {}
		for id in ids:
			res[id] = untax.get(id,0.0) + tax.get(id,0.0)
		return res

	def _get_journal(self, cr, uid, context):
		type_inv = context.get('type', 'out_invoice')
		type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
		journal_obj = self.pool.get('account.journal')
		res = journal_obj.search(cr, uid, [('type', '=', type2journal.get(type_inv, 'sale'))], limit=1)
		if res:
			return res[0]
		else:
			return False
	
	def _get_currency(self, cr, uid, context):
		user = pooler.get_pool(cr.dbname).get('res.users').browse(cr, uid, [uid])[0]
		if user.company_id:
			return user.company_id.currency_id.id
		else:
			return pooler.get_pool(cr.dbname).get('res.currency').search(cr, uid, [('rate','=',1.0)])[0]

	def _get_journal_analytic(self, cr, uid, type_inv, context={}):
		type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
		tt = type2journal.get(type_inv, 'sale')
		cr.execute("select id from account_analytic_journal where type=%s limit 1", (tt,))
		result = cr.fetchone()
		if not result:
			raise osv.except_osv('No Analytic Journal !', "You have to define an analytic journal of type '%s' !" % (tt,))
		return result[0]

	def _get_type(self, cr, uid, context={}):
		type = context.get('type', 'out_invoice')
		return type

	def _reconciled(self, cr, uid, ids, name, args, context):
		res = {}
		for id in ids:
			res[id] = self.test_paid(cr, uid, [id])
		return res

	def _get_reference_type(self, cursor, user, context=None):
		return [('none', 'None')]

	_name = "account.invoice"
	_description = 'Invoice'
	_order = "number"
	_columns = {
		'name': fields.char('Description', size=64, select=True,readonly=True, states={'draft':[('readonly',False)]}),
		'origin': fields.char('Origin', size=64),
		'type': fields.selection([
			('out_invoice','Customer Invoice'),
			('in_invoice','Supplier Invoice'),
			('out_refund','Customer Refund'),
			('in_refund','Supplier Refund'),
			],'Type', readonly=True, select=True),

		'number': fields.char('Invoice Number', size=32, readonly=True),
		'reference': fields.char('Invoice Reference', size=64),
		'reference_type': fields.selection(_get_reference_type, 'Reference Type',
			required=True),
		'comment': fields.text('Additionnal Information'),

		'state': fields.selection([
			('draft','Draft'),
			('proforma','Pro-forma'),
			('open','Open'),
			('paid','Paid'),
			('cancel','Canceled')
		],'State', select=True, readonly=True),

		'date_invoice': fields.date('Date Invoiced', required=True, states={'open':[('readonly',True)],'close':[('readonly',True)]}),
		'date_due': fields.date('Due Date', states={'open':[('readonly',True)],'close':[('readonly',True)]}),

		'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}),
		'partner_bank_id': fields.many2one('res.partner.bank', 'Partner bank'),
		'address_contact_id': fields.many2one('res.partner.address', 'Contact Address', readonly=True, states={'draft':[('readonly',False)]}),
		'address_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft':[('readonly',False)]}),

		'payment_term': fields.many2one('account.payment.term', 'Payment Term',readonly=True, states={'draft':[('readonly',False)]} ),

		'period_id': fields.many2one('account.period', 'Force Period', help="Keep empty to use the period of the validation date."),

		'account_id': fields.many2one('account.account', 'Dest Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
		'invoice_line': fields.one2many('account.invoice.line', 'invoice_id', 'Invoice Lines', readonly=True, states={'draft':[('readonly',False)]}),
		'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),

		'move_id': fields.many2one('account.move', 'Invoice Movement', readonly=True),
		'amount_untaxed': fields.function(_amount_untaxed, method=True, digits=(16,2),string='Untaxed'),
		'amount_tax': fields.function(_amount_tax, method=True, string='Tax', store=True),
		'amount_total': fields.function(_amount_total, method=True, string='Total', store=True),
		'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
		'journal_id': fields.many2one('account.journal', 'Journal', required=True,readonly=True, states={'draft':[('readonly',False)]}),
		'company_id': fields.many2one('res.company', 'Company', required=True),
		'check_total': fields.float('Total', digits=(16,2), states={'open':[('readonly',True)],'close':[('readonly',True)]}),
		'reconciled': fields.function(_reconciled, method=True, string='Reconciled', type='boolean'),
	}
	_defaults = {
		'type': _get_type,
		'date_invoice': lambda *a: time.strftime('%Y-%m-%d'),
		'state': lambda *a: 'draft',
		'journal_id': _get_journal,
		'currency_id': _get_currency,
		'company_id': lambda self, cr, uid, context: \
				self.pool.get('res.users').browse(cr, uid, uid,
					context=context).company_id.id,
		'reference_type': lambda *a: 'none',
	}

	def unlink(self, cr, uid, ids):
		invoices = self.read(cr, uid, ids, ['state'])
		unlink_ids = []
		for t in invoices:
			if t['state'] in ('draft', 'cancel'):
				unlink_ids.append(t['id'])
			else:
				raise osv.except_osv('Invalid action !', 'Cannot delete invoice(s) which are already opened or paid !')
		osv.osv.unlink(self, cr, uid, unlink_ids)
		return True

#	def get_invoice_address(self, cr, uid, ids):
#		res = self.pool.get('res.partner').address_get(cr, uid, [part], ['invoice'])
#		return [{}]

	def onchange_partner_id(self, cr, uid, ids, type, partner_id):
		invoice_addr_id = False
		contact_addr_id = False
		partner_bank_id = False
		payment_term = False
		acc_id = False
		
		opt = [('uid', str(uid))]
		if partner_id:
			
			opt.insert(0, ('id', partner_id))
			res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
			contact_addr_id = res['contact']
			invoice_addr_id = res['invoice']
			p = self.pool.get('res.partner').browse(cr, uid, partner_id)
			if type in ('out_invoice', 'out_refund'):
				acc_id = p.property_account_receivable.id
			else:
				acc_id = p.property_account_payable.id

			partner_bank_id = p.bank_ids and p.bank_ids[0] and p.bank_ids[0].id or False
			payment_term = p.property_payment_term and p.property_payment_term or False

		result = {'value': {'address_contact_id': contact_addr_id, 'address_invoice_id': invoice_addr_id,
							'account_id': acc_id,'partner_bank_id':partner_bank_id,
							'payment_term':payment_term}}

		if partner_id and p.property_payment_term.id:
			result['value']['payment_term'] = p.property_payment_term.id
		return result

	def onchange_currency_id(self, cr, uid, ids, curr_id):
		return {}
	
	def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
		if not payment_term_id:
			return {}
		res={}
		pt_obj= self.pool.get('account.payment.term')

		if not date_invoice :
			date_invoice= time.strftime('%Y-%m-%d')

		pterm_list= pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)

		if pterm_list:
			pterm_list = [line[0] for line in pterm_list]
			pterm_list.sort()
			res= {'value':{'date_due': pterm_list[-1]}}

		return res

	def onchange_invoice_line(self, cr, uid, ids, lines):
		return {}

	# go from canceled state to draft state
	def action_cancel_draft(self, cr, uid, ids, *args):
		self.write(cr, uid, ids, {'state':'draft'})
		wf_service = netsvc.LocalService("workflow")
		for inv_id in ids:
			wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
		return True

	# Workflow stuff
	#################

	# return the ids of the move lines which has the same account than the invoice
	# whose id is in ids
	def move_line_id_payment_get(self, cr, uid, ids, *args):
		ml = self.pool.get('account.move.line')
		res = []
		for inv in self.read(cr, uid, ids, ['move_id','account_id']):
			if inv['move_id']:
				move_line_ids = ml.search(cr, uid, [('move_id', '=', inv['move_id'][0])])
				for line in ml.read(cr, uid, move_line_ids, ['account_id']):
					if line['account_id']==inv['account_id']:
						res.append(line['id'])
		return res

	def copy(self, cr, uid, id, default=None, context={}):
		if not default: default = {}
		default.update({'state':'draft', 'number':False, 'move_id':False})
		return super(account_invoice, self).copy(cr, uid, id, default, context)

	def test_paid(self, cr, uid, ids, *args):
		res = self.move_line_id_payment_get(cr, uid, ids)
		if not res:
			return False
		ok = True
		for id in res:
			cr.execute('select reconcile_id from account_move_line where id=%d', (id,))
			ok = ok and  bool(cr.fetchone()[0])
		return ok

	def button_reset_taxes(self, cr, uid, ids, context={}):
		ait_obj = self.pool.get('account.invoice.tax')
		for id in ids:
			cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%d", (id,))
			for taxe in ait_obj.compute(cr, uid, id).values():
				ait_obj.create(cr, uid, taxe)
		return True

	def button_compute(self, cr, uid, ids, context={}, set_total=False):
		ait_obj = self.pool.get('account.invoice.tax')
		cur_obj = self.pool.get('res.currency')
		for inv in self.browse(cr, uid, ids):
			company_currency = inv.company_id.currency_id.id
			compute_taxes = ait_obj.compute(cr, uid, inv.id)
			if not inv.tax_line:
				for tax in compute_taxes.values():
					ait_obj.create(cr, uid, tax)
			else:
				tax_key = []
				for tax in inv.tax_line:
					if tax.manual:
						continue
					key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
					tax_key.append(key)
					if not key in compute_taxes:
						ait_obj.unlink(cr, uid, [tax.id])
						continue
					compute_taxes[key]['base'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, compute_taxes[key]['base'], context={'date': inv.date_invoice})
					if abs(compute_taxes[key]['base'] - tax.base) > inv.company_id.currency_id.rounding:
						ait_obj.write(cr, uid, [tax.id], compute_taxes[key])
				for key in compute_taxes:
					if not key in tax_key:
						ait_obj.create(cr, uid, compute_taxes[key])
			if set_total:
				self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
		return True

	def _convert_ref(self, cr, uid, ref):
		return (ref or '').replace('/','')

	def _get_analityc_lines(self, cr, uid, id):
		inv = self.browse(cr, uid, [id])[0]
		cur_obj = self.pool.get('res.currency')

		company_currency = inv.company_id.currency_id.id
		if inv.type in ('out_invoice', 'in_refund'):
			sign = 1
		else:
			sign = -1

		iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
		for il in iml:
			if il['account_analytic_id']:
				if inv.type in ('in_invoice', 'in_refund'):
					ref = inv.reference
				else:
					ref = self._convert_ref(cr, uid, inv.number)
				il['analytic_lines'] = [(0,0, {
					'name': il['name'],
					'date': inv['date_invoice'],
					'account_id': il['account_analytic_id'],
					'unit_amount': il['quantity'],
					'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
					'product_id': il['product_id'],
					'product_uom_id': il['uos_id'],
					'general_account_id': il['account_id'],
					'journal_id': self._get_journal_analytic(cr, uid, inv.type),
					'ref': ref,
				})]
		return iml

	def action_move_create(self, cr, uid, ids, *args):
		ait_obj = self.pool.get('account.invoice.tax')
		cur_obj = self.pool.get('res.currency')
		for inv in self.browse(cr, uid, ids):
			if inv.move_id:
				continue
			if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
				print inv.check_total,  inv.amount_total
				raise osv.except_osv('Bad total !', 'Please verify the price of the invoice !\nThe real total does not match the computed total.')
			company_currency = inv.company_id.currency_id.id
			# create the analytical lines
			line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
			ils = self.pool.get('account.invoice.line').read(cr, uid, line_ids)
			# one move line per invoice line
			iml = self._get_analityc_lines(cr, uid, inv.id)
			# check if taxes are all computed
			compute_taxes = ait_obj.compute(cr, uid, inv.id)
			if not inv.tax_line:
				for tax in compute_taxes.values():
					ait_obj.create(cr, uid, tax)
			else:
				tax_key = []
				for tax in inv.tax_line:
					if tax.manual:
						continue
					key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
					tax_key.append(key)
					if not key in compute_taxes:
						raise osv.except_osv('Warning !', 'Global taxes defined, but not in invoice lines !')
					base = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, compute_taxes[key]['base'], context={'date': inv.date_invoice})
					if abs(base - tax.base) > inv.company_id.currency_id.rounding:
						raise osv.except_osv('Warning !', 'Tax base different !\nClick on compute to update tax base')
				for key in compute_taxes:
					if not key in tax_key:
						raise osv.except_osv('Warning !', 'Taxes missing !')

			# one move line per tax line
			iml += ait_obj.move_line_get(cr, uid, inv.id)

			if inv.type in ('in_invoice', 'in_refund'):
				ref = inv.reference
			else:
				ref = self._convert_ref(cr, uid, inv.number)

			diff_currency_p = inv.currency_id.id <> company_currency
			# create one move line for the total and possibly adjust the other lines amount
			total = 0
			total_currency = 0
			for i in iml:
				if inv.currency_id.id != company_currency:
					i['currency_id'] = inv.currency_id.id
					i['amount_currency'] = i['price']
					i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
							company_currency, i['price'],
							context={'date': inv.date_invoice})
				else:
					i['amount_currency'] = False
					i['currency_id'] = False
				i['ref'] = ref
				if inv.type in ('out_invoice','in_refund'):
					total += i['price']
					total_currency += i['amount_currency'] or i['price']
					i['price'] = - i['price']
				else:
					total -= i['price']
					total_currency -= i['amount_currency'] or i['price']
			acc_id = inv.account_id.id

			name = inv['name'] or '/'
			if inv.payment_term:
				totlines = self.pool.get('account.payment.term').compute(cr, uid, inv.payment_term.id, total)
				res_amount_currency = total_currency
				i = 0
				for t in totlines:
					if inv.currency_id.id != company_currency:
						amount_currency = cur_obj.compute(cr, uid, company_currency, inv.currency_id.id, t[1])
					else:
						amount_currency = False

					# last line add the diff
					res_amount_currency -= amount_currency or 0
					i += 1
					if i == len(totlines):
						amount_currency += res_amount_currency

					iml.append({
						'type': 'dest',
						'name': name,
						'price': t[1],
						'account_id': acc_id,
						'date_maturity': t[0],
						'amount_currency': diff_currency_p \
								and  amount_currency or False,
						'currency_id': diff_currency_p \
								and inv.currency_id.id or False,
						'ref': ref,
					})
			else:
				iml.append({
					'type': 'dest',
					'name': name,<commit_msg>Fix invoice create with payment term without payment lines<commit_after># -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
#
# $Id: account.py 1005 2005-07-25 08:41:42Z nicoe $
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################

import time
import netsvc
from osv import fields, osv
import ir
import pooler
import mx.DateTime
from mx.DateTime import RelativeDateTime

from tools import config

class account_invoice(osv.osv):
	def _amount_untaxed(self, cr, uid, ids, name, args, context={}):
		id_set=",".join(map(str,ids))
		cr.execute("SELECT s.id,COALESCE(SUM(l.price_unit*l.quantity*(100-l.discount))/100.0,0)::decimal(16,2) AS amount FROM account_invoice s LEFT OUTER JOIN account_invoice_line l ON (s.id=l.invoice_id) WHERE s.id IN ("+id_set+") GROUP BY s.id ")
		res=dict(cr.fetchall())
		return res

	def _amount_tax(self, cr, uid, ids, name, args, context={}):
		id_set=",".join(map(str,ids))
		cr.execute("SELECT s.id,COALESCE(SUM(l.amount),0)::decimal(16,2) AS amount FROM account_invoice s LEFT OUTER JOIN account_invoice_tax l ON (s.id=l.invoice_id) WHERE s.id IN ("+id_set+") GROUP BY s.id ")
		res=dict(cr.fetchall())
		return res

	def _amount_total(self, cr, uid, ids, name, args, context={}):
		untax = self._amount_untaxed(cr, uid, ids, name, args, context)
		tax = self._amount_tax(cr, uid, ids, name, args, context)
		res = {}
		for id in ids:
			res[id] = untax.get(id,0.0) + tax.get(id,0.0)
		return res

	def _get_journal(self, cr, uid, context):
		type_inv = context.get('type', 'out_invoice')
		type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
		journal_obj = self.pool.get('account.journal')
		res = journal_obj.search(cr, uid, [('type', '=', type2journal.get(type_inv, 'sale'))], limit=1)
		if res:
			return res[0]
		else:
			return False
	
	def _get_currency(self, cr, uid, context):
		user = pooler.get_pool(cr.dbname).get('res.users').browse(cr, uid, [uid])[0]
		if user.company_id:
			return user.company_id.currency_id.id
		else:
			return pooler.get_pool(cr.dbname).get('res.currency').search(cr, uid, [('rate','=',1.0)])[0]

	def _get_journal_analytic(self, cr, uid, type_inv, context={}):
		type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
		tt = type2journal.get(type_inv, 'sale')
		cr.execute("select id from account_analytic_journal where type=%s limit 1", (tt,))
		result = cr.fetchone()
		if not result:
			raise osv.except_osv('No Analytic Journal !', "You have to define an analytic journal of type '%s' !" % (tt,))
		return result[0]

	def _get_type(self, cr, uid, context={}):
		type = context.get('type', 'out_invoice')
		return type

	def _reconciled(self, cr, uid, ids, name, args, context):
		res = {}
		for id in ids:
			res[id] = self.test_paid(cr, uid, [id])
		return res

	def _get_reference_type(self, cursor, user, context=None):
		return [('none', 'None')]

	_name = "account.invoice"
	_description = 'Invoice'
	_order = "number"
	_columns = {
		'name': fields.char('Description', size=64, select=True,readonly=True, states={'draft':[('readonly',False)]}),
		'origin': fields.char('Origin', size=64),
		'type': fields.selection([
			('out_invoice','Customer Invoice'),
			('in_invoice','Supplier Invoice'),
			('out_refund','Customer Refund'),
			('in_refund','Supplier Refund'),
			],'Type', readonly=True, select=True),

		'number': fields.char('Invoice Number', size=32, readonly=True),
		'reference': fields.char('Invoice Reference', size=64),
		'reference_type': fields.selection(_get_reference_type, 'Reference Type',
			required=True),
		'comment': fields.text('Additionnal Information'),

		'state': fields.selection([
			('draft','Draft'),
			('proforma','Pro-forma'),
			('open','Open'),
			('paid','Paid'),
			('cancel','Canceled')
		],'State', select=True, readonly=True),

		'date_invoice': fields.date('Date Invoiced', required=True, states={'open':[('readonly',True)],'close':[('readonly',True)]}),
		'date_due': fields.date('Due Date', states={'open':[('readonly',True)],'close':[('readonly',True)]}),

		'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}),
		'partner_bank_id': fields.many2one('res.partner.bank', 'Partner bank'),
		'address_contact_id': fields.many2one('res.partner.address', 'Contact Address', readonly=True, states={'draft':[('readonly',False)]}),
		'address_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft':[('readonly',False)]}),

		'payment_term': fields.many2one('account.payment.term', 'Payment Term',readonly=True, states={'draft':[('readonly',False)]} ),

		'period_id': fields.many2one('account.period', 'Force Period', help="Keep empty to use the period of the validation date."),

		'account_id': fields.many2one('account.account', 'Dest Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
		'invoice_line': fields.one2many('account.invoice.line', 'invoice_id', 'Invoice Lines', readonly=True, states={'draft':[('readonly',False)]}),
		'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),

		'move_id': fields.many2one('account.move', 'Invoice Movement', readonly=True),
		'amount_untaxed': fields.function(_amount_untaxed, method=True, digits=(16,2),string='Untaxed'),
		'amount_tax': fields.function(_amount_tax, method=True, string='Tax', store=True),
		'amount_total': fields.function(_amount_total, method=True, string='Total', store=True),
		'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
		'journal_id': fields.many2one('account.journal', 'Journal', required=True,readonly=True, states={'draft':[('readonly',False)]}),
		'company_id': fields.many2one('res.company', 'Company', required=True),
		'check_total': fields.float('Total', digits=(16,2), states={'open':[('readonly',True)],'close':[('readonly',True)]}),
		'reconciled': fields.function(_reconciled, method=True, string='Reconciled', type='boolean'),
	}
	_defaults = {
		'type': _get_type,
		'date_invoice': lambda *a: time.strftime('%Y-%m-%d'),
		'state': lambda *a: 'draft',
		'journal_id': _get_journal,
		'currency_id': _get_currency,
		'company_id': lambda self, cr, uid, context: \
				self.pool.get('res.users').browse(cr, uid, uid,
					context=context).company_id.id,
		'reference_type': lambda *a: 'none',
	}

	def unlink(self, cr, uid, ids):
		invoices = self.read(cr, uid, ids, ['state'])
		unlink_ids = []
		for t in invoices:
			if t['state'] in ('draft', 'cancel'):
				unlink_ids.append(t['id'])
			else:
				raise osv.except_osv('Invalid action !', 'Cannot delete invoice(s) which are already opened or paid !')
		osv.osv.unlink(self, cr, uid, unlink_ids)
		return True

#	def get_invoice_address(self, cr, uid, ids):
#		res = self.pool.get('res.partner').address_get(cr, uid, [part], ['invoice'])
#		return [{}]

	def onchange_partner_id(self, cr, uid, ids, type, partner_id):
		invoice_addr_id = False
		contact_addr_id = False
		partner_bank_id = False
		payment_term = False
		acc_id = False
		
		opt = [('uid', str(uid))]
		if partner_id:
			
			opt.insert(0, ('id', partner_id))
			res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
			contact_addr_id = res['contact']
			invoice_addr_id = res['invoice']
			p = self.pool.get('res.partner').browse(cr, uid, partner_id)
			if type in ('out_invoice', 'out_refund'):
				acc_id = p.property_account_receivable.id
			else:
				acc_id = p.property_account_payable.id

			partner_bank_id = p.bank_ids and p.bank_ids[0] and p.bank_ids[0].id or False
			payment_term = p.property_payment_term and p.property_payment_term or False

		result = {'value': {'address_contact_id': contact_addr_id, 'address_invoice_id': invoice_addr_id,
							'account_id': acc_id,'partner_bank_id':partner_bank_id,
							'payment_term':payment_term}}

		if partner_id and p.property_payment_term.id:
			result['value']['payment_term'] = p.property_payment_term.id
		return result

	def onchange_currency_id(self, cr, uid, ids, curr_id):
		return {}
	
	def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
		if not payment_term_id:
			return {}
		res={}
		pt_obj= self.pool.get('account.payment.term')

		if not date_invoice :
			date_invoice= time.strftime('%Y-%m-%d')

		pterm_list= pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)

		if pterm_list:
			pterm_list = [line[0] for line in pterm_list]
			pterm_list.sort()
			res= {'value':{'date_due': pterm_list[-1]}}

		return res

	def onchange_invoice_line(self, cr, uid, ids, lines):
		return {}

	# go from canceled state to draft state
	def action_cancel_draft(self, cr, uid, ids, *args):
		self.write(cr, uid, ids, {'state':'draft'})
		wf_service = netsvc.LocalService("workflow")
		for inv_id in ids:
			wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
		return True

	# Workflow stuff
	#################

	# return the ids of the move lines which has the same account than the invoice
	# whose id is in ids
	def move_line_id_payment_get(self, cr, uid, ids, *args):
		ml = self.pool.get('account.move.line')
		res = []
		for inv in self.read(cr, uid, ids, ['move_id','account_id']):
			if inv['move_id']:
				move_line_ids = ml.search(cr, uid, [('move_id', '=', inv['move_id'][0])])
				for line in ml.read(cr, uid, move_line_ids, ['account_id']):
					if line['account_id']==inv['account_id']:
						res.append(line['id'])
		return res

	def copy(self, cr, uid, id, default=None, context={}):
		if not default: default = {}
		default.update({'state':'draft', 'number':False, 'move_id':False})
		return super(account_invoice, self).copy(cr, uid, id, default, context)

	def test_paid(self, cr, uid, ids, *args):
		res = self.move_line_id_payment_get(cr, uid, ids)
		if not res:
			return False
		ok = True
		for id in res:
			cr.execute('select reconcile_id from account_move_line where id=%d', (id,))
			ok = ok and  bool(cr.fetchone()[0])
		return ok

	def button_reset_taxes(self, cr, uid, ids, context={}):
		ait_obj = self.pool.get('account.invoice.tax')
		for id in ids:
			cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%d", (id,))
			for taxe in ait_obj.compute(cr, uid, id).values():
				ait_obj.create(cr, uid, taxe)
		return True

	def button_compute(self, cr, uid, ids, context={}, set_total=False):
		ait_obj = self.pool.get('account.invoice.tax')
		cur_obj = self.pool.get('res.currency')
		for inv in self.browse(cr, uid, ids):
			company_currency = inv.company_id.currency_id.id
			compute_taxes = ait_obj.compute(cr, uid, inv.id)
			if not inv.tax_line:
				for tax in compute_taxes.values():
					ait_obj.create(cr, uid, tax)
			else:
				tax_key = []
				for tax in inv.tax_line:
					if tax.manual:
						continue
					key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
					tax_key.append(key)
					if not key in compute_taxes:
						ait_obj.unlink(cr, uid, [tax.id])
						continue
					compute_taxes[key]['base'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, compute_taxes[key]['base'], context={'date': inv.date_invoice})
					if abs(compute_taxes[key]['base'] - tax.base) > inv.company_id.currency_id.rounding:
						ait_obj.write(cr, uid, [tax.id], compute_taxes[key])
				for key in compute_taxes:
					if not key in tax_key:
						ait_obj.create(cr, uid, compute_taxes[key])
			if set_total:
				self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
		return True

	def _convert_ref(self, cr, uid, ref):
		return (ref or '').replace('/','')

	def _get_analityc_lines(self, cr, uid, id):
		inv = self.browse(cr, uid, [id])[0]
		cur_obj = self.pool.get('res.currency')

		company_currency = inv.company_id.currency_id.id
		if inv.type in ('out_invoice', 'in_refund'):
			sign = 1
		else:
			sign = -1

		iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
		for il in iml:
			if il['account_analytic_id']:
				if inv.type in ('in_invoice', 'in_refund'):
					ref = inv.reference
				else:
					ref = self._convert_ref(cr, uid, inv.number)
				il['analytic_lines'] = [(0,0, {
					'name': il['name'],
					'date': inv['date_invoice'],
					'account_id': il['account_analytic_id'],
					'unit_amount': il['quantity'],
					'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
					'product_id': il['product_id'],
					'product_uom_id': il['uos_id'],
					'general_account_id': il['account_id'],
					'journal_id': self._get_journal_analytic(cr, uid, inv.type),
					'ref': ref,
				})]
		return iml

	def action_move_create(self, cr, uid, ids, *args):
		ait_obj = self.pool.get('account.invoice.tax')
		cur_obj = self.pool.get('res.currency')
		for inv in self.browse(cr, uid, ids):
			if inv.move_id:
				continue
			if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
				print inv.check_total,  inv.amount_total
				raise osv.except_osv('Bad total !', 'Please verify the price of the invoice !\nThe real total does not match the computed total.')
			company_currency = inv.company_id.currency_id.id
			# create the analytical lines
			line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
			ils = self.pool.get('account.invoice.line').read(cr, uid, line_ids)
			# one move line per invoice line
			iml = self._get_analityc_lines(cr, uid, inv.id)
			# check if taxes are all computed
			compute_taxes = ait_obj.compute(cr, uid, inv.id)
			if not inv.tax_line:
				for tax in compute_taxes.values():
					ait_obj.create(cr, uid, tax)
			else:
				tax_key = []
				for tax in inv.tax_line:
					if tax.manual:
						continue
					key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
					tax_key.append(key)
					if not key in compute_taxes:
						raise osv.except_osv('Warning !', 'Global taxes defined, but not in invoice lines !')
					base = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, compute_taxes[key]['base'], context={'date': inv.date_invoice})
					if abs(base - tax.base) > inv.company_id.currency_id.rounding:
						raise osv.except_osv('Warning !', 'Tax base different !\nClick on compute to update tax base')
				for key in compute_taxes:
					if not key in tax_key:
						raise osv.except_osv('Warning !', 'Taxes missing !')

			# one move line per tax line
			iml += ait_obj.move_line_get(cr, uid, inv.id)

			if inv.type in ('in_invoice', 'in_refund'):
				ref = inv.reference
			else:
				ref = self._convert_ref(cr, uid, inv.number)

			diff_currency_p = inv.currency_id.id <> company_currency
			# create one move line for the total and possibly adjust the other lines amount
			total = 0
			total_currency = 0
			for i in iml:
				if inv.currency_id.id != company_currency:
					i['currency_id'] = inv.currency_id.id
					i['amount_currency'] = i['price']
					i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
							company_currency, i['price'],
							context={'date': inv.date_invoice})
				else:
					i['amount_currency'] = False
					i['currency_id'] = False
				i['ref'] = ref
				if inv.type in ('out_invoice','in_refund'):
					total += i['price']
					total_currency += i['amount_currency'] or i['price']
					i['price'] = - i['price']
				else:
					total -= i['price']
					total_currency -= i['amount_currency'] or i['price']
			acc_id = inv.account_id.id

			name = inv['name'] or '/'
			totlines = False
			if inv.payment_term:
				totlines = self.pool.get('account.payment.term').compute(cr,
						uid, inv.payment_term.id, total)
			if totlines:
				res_amount_currency = total_currency
				i = 0
				for t in totlines:
					if inv.currency_id.id != company_currency:
						amount_currency = cur_obj.compute(cr, uid,
								company_currency, inv.currency_id.id, t[1])
					else:
						amount_currency = False

					# last line add the diff
					res_amount_currency -= amount_currency or 0
					i += 1
					if i == len(totlines):
						amount_currency += res_amount_currency

					iml.append({
						'type': 'dest',
						'name': name,
						'price': t[1],
						'account_id': acc_id,
						'date_maturity': t[0],
						'amount_currency': diff_currency_p \
								and  amount_currency or False,
						'currency_id': diff_currency_p \
								and inv.currency_id.id or False,
						'ref': ref,
					})
			else:
				iml.append({
					'type': 'dest',
					'name': name,
{'fraction_non_alphanumeric': 0.16622462938663837, 'fraction_numerical': 0.006923699712443117, 'mean_word_length': 4.497237569060774, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5798668', 'n_tokens_mistral': 14276, 'n_tokens_neox': 12416, 'n_words': 3027}