##############################################################################
#
# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
#
# $Id: sale.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
from mx import DateTime

class sale_shop(osv.osv):
	_name = "sale.shop"
	_description = "Sale Shop"
	_columns = {
		'name': fields.char('Shop name',size=64, required=True),
		'payment_default_id': fields.many2one('account.payment.term','Default Payment Term',required=True),
		'payment_account_id': fields.many2many('account.account','sale_shop_account','shop_id','account_id','Payment accounts'),
		'warehouse_id': fields.many2one('stock.warehouse','Warehouse'),
		'pricelist_id': fields.many2one('product.pricelist', 'Pricelist'),
		'project_id': fields.many2one('account.analytic.account', 'Analytic Account'),
	}
sale_shop()

def _incoterm_get(self, cr, uid, context={}):
	cr.execute('select code, code||\', \'||name from stock_incoterms where active')
	return cr.fetchall()

class sale_order(osv.osv):
	_name = "sale.order"
	_description = "Sale Order"
	def copy(self, cr, uid, id, default=None,context={}):
		if not default:
			default = {}
		default.update({
			'state':'draft',
			'shipped':False,
			'invoiced':False,
			'invoice_ids':[],
			'picking_ids':[],
			'name': self.pool.get('ir.sequence').get(cr, uid, 'sale.order'),
		})
		return super(sale_order, self).copy(cr, uid, id, default, context)

	def _amount_untaxed(self, cr, uid, ids, field_name, arg, context):
		id_set = ",".join(map(str, ids))
		cr.execute("SELECT s.id,COALESCE(SUM(l.price_unit*l.product_uos_qty*(100-l.discount))/100.0,0)::decimal(16,2) AS amount FROM sale_order s LEFT OUTER JOIN sale_order_line l ON (s.id=l.order_id) WHERE s.id IN ("+id_set+") GROUP BY s.id ")
		res = dict(cr.fetchall())
		return res

	def _amount_tax(self, cr, uid, ids, field_name, arg, context):
		res = {}
		for order in self.browse(cr, uid, ids):
			val = 0.0
			for line in order.order_line:
				for tax in line.tax_id:
					for c in self.pool.get('account.tax').compute(cr, uid, [tax.id], line.price_unit * (1-(line.discount or 0.0)/100.0), line.product_uom_qty, order.partner_invoice_id.id):
						val += round(c['amount'], 2)
			res[order.id]=round(val,2)
		return res

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

	_columns = {
		'name': fields.char('Order Description', size=64, required=True, select=True),
		'shop_id':fields.many2one('sale.shop', 'Shop', required=True, readonly=True, states={'draft':[('readonly',False)]}),
		'origin': fields.char('Origin', size=64),
		'client_order_ref': fields.char('Partner Ref.',size=64),

		'state': fields.selection([
			('draft','Quotation'),
			('waiting_date','Waiting Schedule'),
			('manual','Manual in progress'),
			('progress','In progress'),
			('shipping_except','Shipping Exception'),
			('invoice_except','Invoice Exception'),
			('done','Done'),
			('cancel','Cancel')
		], 'Order State', readonly=True, help="Gives the state of the quotation or sale order. The exception state are automatically setted when a cancel operation occurs in the invoice validation (Invoice Exception) or in picking list process (Shipping Exception). The 'Waiting Schedule' state is set when the invoice is confirmed but waiting the 'Date Order' the schedule.", select=True),
		'date_order':fields.date('Date Ordered', required=True, readonly=True, states={'draft':[('readonly',False)]}),

		'user_id':fields.many2one('res.users', 'Salesman', states={'draft':[('readonly',False)]}, relate=True, select=True),
		'partner_id':fields.many2one('res.partner', 'Partner', readonly=True, states={'draft':[('readonly',False)]}, change_default=True, relate=True, select=True),
		'partner_invoice_id':fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft':[('readonly',False)]}),
		'partner_order_id':fields.many2one('res.partner.address', 'Ordering Contact', readonly=True, required=True, states={'draft':[('readonly',False)]}, help="The name and address of the contact that requested the order or quotation."),
		'partner_shipping_id':fields.many2one('res.partner.address', 'Shipping Address', readonly=True, required=True, states={'draft':[('readonly',False)]}),

		'incoterm': fields.selection(_incoterm_get, 'Incoterm',size=3),
		'picking_policy': fields.selection([('direct','Direct Delivery'),('one','All at once')], 'Picking Policy', required=True ),
		'order_policy': fields.selection([
			('prepaid','Invoice before delivery'),
			('manual','Shipping & Manual Invoice'),
			('postpaid','Automatic Invoice after delivery'),
			('picking','Invoice from the pickings'),
		], 'Shipping Policy', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="The Shipping Policy is used to synchronise invoive and delivery operations. The 'Pay before delivery' choice will first generate the invoice and then generate the picking order after the payment of this invoice. The 'Shipping & Manual Invoice' will create the picking order directly and wait the user to manually click on the 'Invoice Button' to generate the draft invoice. The 'Invoice after delivery' choice will generate the draft invoice after the picking list have been finnished"),
		'pricelist_id':fields.many2one('product.pricelist', 'Pricelist', required=True, readonly=True, states={'draft':[('readonly',False)]}),
		'project_id':fields.many2one('account.analytic.account', 'Profit/Cost Center', readonly=True, states={'draft':[('readonly', False)]}),

		'order_line': fields.one2many('sale.order.line', 'order_id', 'Order Lines', readonly=True, states={'draft':[('readonly',False)]}),
		'payment_line': fields.one2many('sale.order.payment', 'order_id', 'Order Payments', readonly=True, states={'draft':[('readonly',False)]}),

		'invoice_ids': fields.many2many('account.invoice', 'sale_order_invoice_rel', 'order_id', 'invoice_id', 'Invoice', readonly=True, help="This is the list of invoices that have been generated for this sale order. The same sale order may have been invoiced in several times (by line for example)."),
		'picking_ids': fields.one2many('stock.picking', 'sale_id', 'Picking List', readonly=True, help="This is the list of picking list that have been generated for this invoice"),

		'shipped':fields.boolean('Picked', readonly=True),
		'invoiced':fields.boolean('Paid', readonly=True),

		'note': fields.text('Notes'),

		'amount_untaxed': fields.function(_amount_untaxed, method=True, string='Untaxed Amount'),
		'amount_tax': fields.function(_amount_tax, method=True, string='Taxes'),
		'amount_total': fields.function(_amount_total, method=True, string='Total'),
		'invoice_quantity': fields.selection([('order','Ordered Quantities'),('procurement','Shipped Quantities')], 'Invoice on', help="The sale order will automatically create the invoice proposition (draft invoice). Ordered and delivered quantities may not be the same. You have to choose if you invoice based on ordered or shipped quantities. If the product is a service, shipped quantities means hours spent on the associated tasks."),
	}
	_defaults = {
		'picking_policy': lambda *a: 'direct',
		'date_order': lambda *a: time.strftime('%Y-%m-%d'),
		'order_policy': lambda *a: 'manual',
		'state': lambda *a: 'draft',
		'user_id': lambda obj, cr, uid, context: uid,
		'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'sale.order'),
		'invoice_quantity': lambda *a: 'order'
	}

	# Form filling
	def onchange_shop_id(self, cr, uid, ids, shop_id):
		v={}
		if shop_id:
			shop=self.pool.get('sale.shop').browse(cr,uid,shop_id)
			v['project_id']=shop.project_id.id
			# Que faire si le client a une pricelist a lui ?
			if shop.pricelist_id.id:
				v['pricelist_id']=shop.pricelist_id.id
			#v['payment_default_id']=shop.payment_default_id.id
		return {'value':v}

	def action_cancel_draft(self, cr, uid, ids, *args):
		if not len(ids):
			return False
		cr.execute('select id from sale_order_line where order_id in ('+','.join(map(str, ids))+')', ('draft',))
		line_ids = map(lambda x: x[0], cr.fetchall())
		self.write(cr, uid, ids, {'state':'draft', 'invoice_ids':[], 'shipped':0, 'invoiced':0})
		self.pool.get('sale.order.line').write(cr, uid, line_ids, {'invoiced':False, 'state':'draft', 'invoice_lines':[(6,0,[])]})
		wf_service = netsvc.LocalService("workflow")
		for inv_id in ids:
			wf_service.trg_create(uid, 'sale.order', inv_id, cr)
		return True

	def onchange_partner_id(self, cr, uid, ids, part):
		if not part:
			return {'value':{'partner_invoice_id': False, 'partner_shipping_id':False, 'partner_order_id':False}}
		addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['delivery','invoice','contact'])
		pricelist = self.pool.get('res.partner').browse(cr, uid, part).property_product_pricelist[0]
		return {'value':{'partner_invoice_id': addr['invoice'], 'partner_order_id':addr['contact'], 'partner_shipping_id':addr['delivery'], 'pricelist_id': pricelist}}

	def button_dummy(self, cr, uid, ids, context={}):
		return True

#FIXME: the method should return the list of invoices created (invoice_ids) 
# and not the id of the last invoice created (res). The problem is that we 
# cannot change it directly since the method is called by the sale order
# workflow and I suppose it expects a single id...
	def _inv_get(self, cr, uid, order, context={}):
		return {}

	def action_invoice_create(self, cr, uid, ids, grouped=False, states=['confirmed','done']):
		res = False
		invoices = {}
		invoice_ids = []

		def make_invoice(order, lines):
			a = order.partner_id.property_account_receivable[0]
			if order.partner_id and order.partner_id.property_payment_term:
				pay_term = order.partner_id.property_payment_term[0]
			else:
				pay_term = False
			inv = {
				'name': order.name,
				'origin': order.name,
				'type': 'out_invoice',
				'reference': "P%dSO%d"%(order.partner_id.id,order.id),
				'account_id': a,
				'partner_id': order.partner_id.id,
				'address_invoice_id': order.partner_invoice_id.id,
				'address_contact_id': order.partner_invoice_id.id,
				'project_id': order.project_id.id,
				'invoice_line': [(6,0,lines)],
				'currency_id' : order.pricelist_id.currency_id.id,
				'comment': order.note,
				'payment_term': pay_term,
			}
			inv.update(self._inv_get(cr, uid, order))
			inv_obj = self.pool.get('account.invoice')
			inv_id = inv_obj.create(cr, uid, inv)
			inv_obj.button_compute(cr, uid, [inv_id])
			return inv_id

		for o in self.browse(cr,uid,ids):
			lines = []
			for line in o.order_line:
				if (line.state in states) and not line.invoiced:
					lines.append(line.id)
			created_lines = self.pool.get('sale.order.line').invoice_line_create(cr, uid, lines)
			if created_lines:
				invoices.setdefault(o.partner_id.id, []).append((o, created_lines))

		for val in invoices.values():
			if grouped:
				res = make_invoice(val[0][0], reduce(lambda x,y: x + y, [l for o,l in val], []))
				for o,l in val:
					self.write(cr, uid, [o.id], {'state' : 'progress'})
					cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%d,%d)', (o.id, res))
			else:
				for order, il in val:
					res = make_invoice(order, il)
					invoice_ids.append(res)
					self.write(cr, uid, [order.id], {'state' : 'progress'})
					cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%d,%d)', (o.id, res))
		return res

	def action_cancel(self, cr, uid, ids, context={}):
		ok = True
		for sale in self.browse(cr, uid, ids):
			for pick in sale.picking_ids:
				if pick.state not in ('draft','cancel'):
					raise osv.except_osv(
						'Could not cancel sale order !',
						'You must first cancel all pickings attached to this sale order.')
			for r in self.read(cr,uid,ids,['picking_ids']):
				for pick in r['picking_ids']:
					wf_service = netsvc.LocalService("workflow")
					wf_service.trg_validate(uid, 'stock.picking', pick, 'button_cancel', cr)
			for inv in sale.invoice_ids:
				if inv.state not in ('draft','cancel'):
					raise osv.except_osv(
						'Could not cancel this sale order !',
						'You must first cancel all invoices attached to this sale order.')
			for r in self.read(cr,uid,ids,['invoice_ids']):
				for inv in r['invoice_ids']:
					wf_service = netsvc.LocalService("workflow")
					wf_service.trg_validate(uid, 'account.invoice', inv, 'invoice_cancel', cr)
		self.write(cr,uid,ids,{'state':'cancel'})
		return True

	def action_wait(self, cr, uid, ids, *args):
		for r in self.read(cr,uid,ids,['order_policy','invoice_ids','name','amount_untaxed','partner_id','user_id','order_line']):
			if self.pool.get('res.partner.event.type').check(cr, uid, 'sale_open'):
				self.pool.get('res.partner.event').create(cr, uid, {'name':'Sale Order: '+r['name'], 'partner_id':r['partner_id'][0], 'date':time.strftime('%Y-%m-%d %H:%M:%S'), 'user_id':(r['user_id'] and r['user_id'][0]) or uid, 'partner_type':'customer', 'probability': 1.0, 'planned_revenue':r['amount_untaxed']})
			if (r['order_policy']=='manual') and (not r['invoice_ids']):
				self.write(cr,uid,[r['id']],{'state':'manual'})
			else:
				self.write(cr,uid,[r['id']],{'state':'progress'})
			self.pool.get('sale.order.line').button_confirm(cr, uid, r['order_line'])

	def procurement_lines_get(self, cr, uid, ids, *args):
		res = []
		for order in self.browse(cr, uid, ids, context={}):
			for line in order.order_line:
				if line.procurement_id:
					res.append(line.procurement_id.id)
		return res

	# if mode == 'finished':
	#   returns True if all lines are done, False otherwise
	# if mode == 'canceled':
	#	returns True if there is at least one canceled line, False otherwise
	def test_state(self, cr, uid, ids, mode, *args):
		assert mode in ('finished', 'canceled'), "invalid mode for test_state"
		finished = True
		canceled = False
		write_done_ids = []
		write_cancel_ids = []
		for order in self.browse(cr, uid, ids, context={}):
			for line in order.order_line:
				if line.procurement_id and line.procurement_id.state != 'done':
					finished = False
				if line.procurement_id and line.procurement_id.state == 'cancel':
					canceled = True
				# if a line is finished (ie its procuremnt is done or it has not procuremernt and it
				# is not already marked as done, mark it as being so...
				if ((not line.procurement_id) or line.procurement_id.state == 'done') and line.state != 'done':
					write_done_ids.append(line.id)
				# ... same for canceled lines
				if line.procurement_id and line.procurement_id.state == 'cancel' and line.state != 'cancel':
					write_cancel_ids.append(line.id)
		if write_done_ids:
			self.pool.get('sale.order.line').write(cr, uid, write_done_ids, {'state': 'done'})
		if write_cancel_ids:
			self.pool.get('sale.order.line').write(cr, uid, write_cancel_ids, {'state': 'cancel'})

		if mode=='finished':
			return finished
		elif mode=='canceled':
			return canceled

	def action_ship_create(self, cr, uid, ids, *args):
		picking_id=False
		for order in self.browse(cr, uid, ids, context={}):
			output_id = order.shop_id.warehouse_id.lot_output_id.id
			picking_id = False
			for line in order.order_line:
				proc_id=False
				date_planned = (DateTime.now() + DateTime.RelativeDateTime(days=line.delay or 0.0)).strftime('%Y-%m-%d')
				if line.state == 'done':
					continue
				if line.product_id and line.product_id.product_tmpl_id.type in ('product', 'consu'):
					location_id = order.shop_id.warehouse_id.lot_stock_id.id
					if not picking_id:
						loc_dest_id = order.partner_id.property_stock_customer[0]
						picking_id = self.pool.get('stock.picking').create(cr, uid, {
							'origin': order.name,
							'type': 'out',
							'state': 'auto',
							'move_type': order.picking_policy,
							'loc_move_id': loc_dest_id,
							'sale_id': order.id,
							'address_id': order.partner_shipping_id.id,
							'note': order.note,
							'invoice_state': (order.order_policy=='picking' and '2binvoiced') or 'none',

						})

					move_id = self.pool.get('stock.move').create(cr, uid, {
						'name':'SO:'+order.name,
						'picking_id': picking_id,
						'product_id': line.product_id.id,
						'date_planned': date_planned,
						'product_qty': line.product_uom_qty,
						'product_uom': line.product_uom.id,
						'product_uos_qty': line.product_uos_qty,
						'product_uos': line.product_uos.id,
						'product_packaging' : line.product_packaging.id,
						'address_id' : line.address_allotment_id.id or order.partner_shipping_id.id,
						'location_id': location_id,
						'location_dest_id': output_id,
						'sale_line_id': line.id,
						'tracking_id': False,
						'state': 'waiting',
						'note': line.notes,
					})
					proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
						'name': order.name,
						'origin': order.name,
						'date_planned': date_planned,
						'product_id': line.product_id.id,
						'product_qty': line.product_uom_qty,
						'product_uom': line.product_uom.id,
						'location_id': order.shop_id.warehouse_id.lot_stock_id.id,
						'procure_method': line.type,
						'move_id': move_id, 
					})
					wf_service = netsvc.LocalService("workflow")
					wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
					self.pool.get('sale.order.line').write(cr, uid, [line.id], {'procurement_id': proc_id})
				elif line.product_id and line.product_id.product_tmpl_id.type=='service':
					proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
						'name': line.name,
						'origin': order.name,
						'date_planned': date_planned,
						'product_id': line.product_id.id,
						'product_qty': line.product_uom_qty,
						'product_uom': line.product_uom.id,
						'location_id': order.shop_id.warehouse_id.lot_stock_id.id,
						'procure_method': line.type,
					})
					wf_service = netsvc.LocalService("workflow")
					wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
					self.pool.get('sale.order.line').write(cr, uid, [line.id], {'procurement_id': proc_id})
				else:
					#
					# No procurement because no product in the sale.order.line.
					#
					pass

			val = {}
			if picking_id:
				wf_service = netsvc.LocalService("workflow")
				wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
				#val = {'picking_ids':[(6,0,[picking_id])]}

			if order.state=='shipping_except':
				val['state'] = 'progress'
				if (order.order_policy == 'manual') and order.invoice_ids:
					val['state'] = 'manual'
			self.write(cr, uid, [order.id], val)
		return True

	def action_ship_end(self, cr, uid, ids, context={}):
		for order in self.browse(cr, uid, ids):
			val = {'shipped':True}
			if order.state=='shipping_except':
				if (order.order_policy=='manual') and not order.invoice_ids:
					val['state'] = 'manual'
				else:
					val['state'] = 'progress'
			self.write(cr, uid, [order.id], val)
		return True

	def _log_event(self, cr, uid, ids, factor=0.7, name='Open Order'):
		invs = self.read(cr, uid, ids, ['date_order','partner_id','amount_untaxed'])
		for inv in invs:
			part=inv['partner_id'] and inv['partner_id'][0]
			pr = inv['amount_untaxed'] or 0.0
			partnertype = 'customer'
			eventtype = 'sale'
			self.pool.get('res.partner.event').create(cr, uid, {'name':'Order: '+name, 'som':False, 'description':'Order '+str(inv['id']), 'document':'', 'partner_id':part, 'date':time.strftime('%Y-%m-%d'), 'canal_id':False, 'user_id':uid, 'partner_type':partnertype, 'probability':1.0, 'planned_revenue':pr, 'planned_cost':0.0, 'type':eventtype})

	def has_stockable_products(self,cr, uid, ids, *args):
		for order in self.browse(cr, uid, ids):
			for order_line in order.order_line:
				if order_line.product_id and order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
					return True
		return False
sale_order()

class sale_order_line(osv.osv):
	def copy(self, cr, uid, id, default=None, context={}):
		if not default: default = {}
		default.update( {'invoice_lines':[]})
		return super(sale_order_line, self).copy(cr, uid, id, default, context)

	def _amount_line_net(self, cr, uid, ids, field_name, arg, context):
		res = {}
		for line in self.browse(cr, uid, ids):
			if line.product_uos.id:
				res[line.id] = line.price_unit * (1 - (line.discount or 0.0) /100.0)
			else:
				res[line.id] = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
		return res

	def _amount_line(self, cr, uid, ids, field_name, arg, context):
		res = {}
		for line in self.browse(cr, uid, ids):
			if line.product_uos.id:
				res[line.id] = line.price_unit * line.product_uos_qty * (1 - (line.discount or 0.0) /100.0)
			else:
				res[line.id] = line.price_unit * line.product_uom_qty * (1 - (line.discount or 0.0) / 100.0)
		return res

	def _number_packages(self, cr, uid, ids, field_name, arg, context):
		res = {}
		for line in self.browse(cr, uid, ids):
			res[line.id] = int(line.product_uom_qty / line.product_packaging.qty)
		return res
	
	def _get_1st_packaging(self, cr, uid, context={}):
		cr.execute('select id from product_packaging order by id asc limit 1')
		res = cr.fetchone()
		if not res:
			return False
		return res[0]

	_name = 'sale.order.line'
	_description = 'Sale Order line'
	_columns = {
		'order_id': fields.many2one('sale.order', 'Order Ref', required=True, ondelete='cascade', select=True),
		'name': fields.char('Description', size=256, required=True, select=True),
		'sequence': fields.integer('Sequence'),
		'delay': fields.float('Delivery Delay', required=True),
		'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok','=',True)], change_default=True, relate=True),
		'invoice_lines': fields.many2many('account.invoice.line', 'sale_order_line_invoice_rel', 'order_line_id','invoice_id', 'Invoice Lines', readonly=True),
		'invoiced': fields.boolean('Paid', readonly=True, select=True),
		'procurement_id': fields.many2one('mrp.procurement', 'Procurement'),
		'price_unit': fields.float('Unit Price', required=True),
		'price_net': fields.function(_amount_line_net, method=True, string='Net Price'),
		'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal'),
		'tax_id': fields.many2many('account.tax', 'sale_order_tax', 'order_line_id', 'tax_id', 'Taxes'),
		'type': fields.selection([('make_to_stock','from stock'),('make_to_order','on order')],'Procure Method', required=True),
		'property_ids': fields.many2many('mrp.property', 'sale_order_line_property_rel', 'order_id', 'property_id', 'Properties'),
		'address_allotment_id' : fields.many2one('res.partner.address', 'Allotment Partner'),
		'product_uom_qty': fields.float('Quantity (UOM)', digits=(16,2), required=True),
		'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
		'product_uos_qty': fields.float('Quantity (UOS)'),
		'product_uos': fields.many2one('product.uom', 'Product UOS'),
		'product_packaging': fields.many2one('product.packaging', 'Packaging used'),
		'move_ids': fields.one2many('stock.move', 'sale_line_id', 'Inventory Moves', readonly=True),
		'property_ids': fields.many2many('mrp.property', 'sale_order_line_property_rel', 'order_id', 'property_id', 'Properties'),
		'discount': fields.float('Discount (%)', digits=(16,2)),
		'number_packages': fields.function(_number_packages, method=True, type='integer', string='Number packages'),
		'notes': fields.text('Notes'),
		'th_weight' : fields.float('Weight'),
		'state': fields.selection([('draft','Draft'),('confirmed','Confirmed'),('done','Done'),('cancel','Canceled')], 'State', required=True, readonly=True),
		'price_unit_customer': fields.float('Customer Unit Price'),
	}
	_order = 'sequence'
	_defaults = {
		'discount': lambda *a: 0.0,
		'delay': lambda *a: 0.0,
		'product_uom_qty': lambda *a: 1,
		'product_uos_qty': lambda *a: 1,
		'sequence': lambda *a: 10,
		'invoiced': lambda *a: 0,
		'state': lambda *a: 'draft',
		'type': lambda *a: 'make_to_stock',
		'product_packaging': _get_1st_packaging,
	}
	def invoice_line_create(self, cr, uid, ids, context={}):
		def _get_line_qty(line):
			if (line.order_id.invoice_quantity=='order') or not line.procurement_id:
				return line.product_uos_qty or line.product_uom_qty
			else:
				return self.pool.get('mrp.procurement').quantity_get(cr, uid, line.procurement_id.id, context)
		create_ids = []
		for line in self.browse(cr, uid, ids, context):
			if not line.invoiced:
				if line.product_id:
					a =  line.product_id.product_tmpl_id.property_account_income
					if not a:
						a = line.product_id.categ_id.property_account_income_categ[0]
					else:
						a = a[0]
				else:
					a = self.pool.get('ir.property').get(cr, uid, 'property_account_income_categ', 'product.category', context=context)
				uosqty = _get_line_qty(line)
				uos_id = (line.product_uos and line.product_uos.id) or False
				inv_id = self.pool.get('account.invoice.line').create(cr, uid, {
					'name': line.name,
					'account_id': a,
					'price_unit': line.price_unit,
					'quantity': uosqty,
					'discount': line.discount,
					'uos_id': uos_id,
					'product_id': line.product_id.id or False,
					'invoice_line_tax_id': [(6,0,[x.id for x in line.tax_id])],
					'note': line.notes,
				})
				cr.execute('insert into sale_order_line_invoice_rel (order_line_id,invoice_id) values (%d,%d)', (line.id, inv_id))
				self.write(cr, uid, [line.id], {'invoiced':True})
				create_ids.append(inv_id)
		return create_ids

	def button_confirm(self, cr, uid, ids, context={}):
		return self.write(cr, uid, ids, {'state':'confirmed'})

	def button_done(self, cr, uid, ids, context={}):
		return self.write(cr, uid, ids, {'state':'done'})

	def uos_change(self, cr, uid, ids, product_uos, product_uos_qty=0, product_id=None):
		if not product_id:
			return {'value': {'product_uom': product_uos, 'product_uom_qty': product_uos_qty}, 'domain':{}}
		res = self.pool.get('product.product').read(cr, uid, [product_id], ['uom_id', 'uos_id', 'uos_coeff', 'weight'])[0]
		value = {
			'product_uom' : res['uom_id'], 
		}
		try:
			value.update({
				'product_uom_qty' : product_uos_qty / res['uos_coeff'],
				'weight' : product_uos_qty / res['uos_coeff'] * res['weight']
			})
		except ZeroDivisionError:
			pass
		return {'value' : value}

	def copy(self, cr, uid, id, default=None,context={}):
		if not default:
			default = {}
		default.update({'state':'draft', 'move_ids':[], 'invoiced':False, 'invoice_lines':[]})
		return super(sale_order_line, self).copy(cr, uid, id, default, context)

	def product_id_change(self, cr, uid, ids, pricelist, product, qty=0, uom=False, qty_uos=0, uos=False, name='', partner_id=False, lang=False):
		context = {'lang':lang}
		if not product:
			return {'value': {'price_unit': 0.0, 'notes':'', 'weight' : 0}, 'domain':{'product_uom':[]}}
		if not pricelist:
			raise osv.except_osv('No Pricelist !', 'You have to select a pricelist in the sale form !\nPlease set one before choosing a product.')
		price = self.pool.get('product.pricelist').price_get(cr,uid,[pricelist], product, qty or 1.0, partner_id, {'uom': uom})[pricelist]
		if price is False:
			raise osv.except_osv('No valid pricelist line found !', "Couldn't find a pricelist line matching this product and quantity.\nYou have to change either the product, the quantity or the pricelist.")
		res = self.pool.get('product.product').read(cr, uid, [product], context=context)[0]
#		dt = (DateTime.now() + DateTime.RelativeDateTime(days=res['sale_delay'] or 0.0)).strftime('%Y-%m-%d')

		result = {'price_unit': price, 'type':res['procure_method'], 'delay':(res['sale_delay'] or 0.0), 'notes':res['description_sale']}

		taxes = self.pool.get('account.tax').browse(cr, uid, res['taxes_id'])
		taxep = self.pool.get('res.partner').browse(cr, uid, partner_id).property_account_tax
		if not taxep:
			result['tax_id'] = res['taxes_id']
		else:
			res5 = [taxep[0]]
			tp = self.pool.get('account.tax').browse(cr, uid, taxep[0])
			for t in taxes:
				if not t.tax_group==tp.tax_group:
					res5.append(t.id)
			result['tax_id'] = res5

		result['name'] = res['partner_ref']
		domain = {}
		if not uom and not uos:
			result['product_uom'] = res['uom_id'] and res['uom_id'][0]
			if result['product_uom']:
				result['product_uos'] = res['uos_id']
				result['product_uos_qty'] = qty * res['uos_coeff']
				result['weight'] = qty * res['weight']
				res2 = self.pool.get('product.uom').read(cr, uid, [result['product_uom']], ['category_id'])
				if res2 and res2[0]['category_id']:
					domain = {'product_uom':[('category_id','=',res2[0]['category_id'][0])]}
		elif uom: # whether uos is set or not
			default_uom = res['uom_id'] and res['uom_id'][0]
			q = self.pool.get('product.uom')._compute_qty(cr, uid, uom, qty, default_uom)
			result['product_uos'] = res['uos_id']
			result['product_uos_qty'] = q * res['uos_coeff']
			result['weight'] = q * res['weight']
		elif uos: # only happens if uom is False
			result['product_uom'] = res['uom_id'] and res['uom_id'][0]
			result['product_uom_qty'] = qty_uos / res['uos_coeff']
			result['weight'] = result['product_uom_qty'] * res['weight']
		return {'value':result, 'domain':domain}
sale_order_line()

class sale_payment_line(osv.osv):
	_name = 'sale.order.payment'
	_description = 'Sale Order payment'
	_columns = {
		'order_id': fields.many2one('sale.order', 'Order Ref', select=True),
		'name': fields.char('Description', size=64, required=True),
		'account_id': fields.many2one('account.account', 'Account'),
		'amount': fields.float('Amount', required=True),
	}
	_defaults = {
	}
sale_payment_line()

