##############################################################################
#
# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
#
# $Id: stock.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


#----------------------------------------------------------
# Incoterms
#----------------------------------------------------------
class stock_incoterms(osv.osv):
	_name = "stock.incoterms"
	_description = "Incoterms"
	_columns = {
		'name': fields.char('Name', size=64, required=True),
		'code': fields.char('Code', size=3, required=True),
		'active': fields.boolean('Active'),
	}
	_defaults = {
		'active': lambda x,y,z: True,
	}
stock_incoterms()

class stock_lot(osv.osv):
	_name = "stock.lot"
	_description = "Lot"
	_columns = {
		'name': fields.char('Lot Group', size=64, required=True),
		'active': fields.boolean('Active'),
		'tracking': fields.char('Tracking', size=64),
		'tracking_ids': fields.one2many('stock.tracking', 'lot_id', 'Tracking lines'),
	}
	_defaults = {
		'active': lambda x,y,z: True,
	}
stock_lot()


#----------------------------------------------------------
# Stock Location
#----------------------------------------------------------
class stock_location(osv.osv):
	_name = "stock.location"
	_description = "Location"
	_columns = {
		'name': fields.char('Lot Group', size=64, required=True),
		'active': fields.boolean('Active'),
		'usage': fields.selection([('supplier','Supplier Location'),('internal','Internal Location'),('customer','Customer Location'),('inventory','Inventory')], 'Location type'),
		'allocation_method': fields.selection([('fifo','FIFO'),('lifo','LIFO'),('nearest','Nearest')], 'Allocation Method', required=True),

		'account_id': fields.many2one('account.account', string='Inventory Account', domain=[('type','=','stock_inventory')]),
		'location_id' : fields.many2one('stock.location', 'Location', required=True),
		'child_ids': fields.one2many('stock.location', 'location_id', 'Contains'),

		'comment': fields.text('Additional Information'),
		'posx': fields.integer('Position X', required=True),
		'posy': fields.integer('Position Y', required=True),
		'posz': fields.integer('Position Z', required=True)
	}
	_defaults = {
		'active': lambda x,y,z: 1,
		'usage': lambda x,y,z: 'internal',
		'allocation_method': lambda x,y,z: 'fifo',
		'posx': lambda x,y,z: 0,
		'posy': lambda x,y,z: 0,
		'posz': lambda x,y,z: 0,
	}
	def _product_get(self, cr, uid, id, product_ids):
		if not len(product_ids):
			return {}
		prod_ids_str = ','.join(map(str,product_ids))
		cr.execute('select sum(amount),product_id,product_uom from stock_move where location_id=%d and product_id in ('+prod_ids_str+') group by product_id,product_uom')
		cr.fetchall()
		for id prod_in product_ids:
			res[id] = 0.0
		for (amount, prod_id, prod_uom) in cr.fetchall():
			amount = self.pool.get('product.uom')._compute_qty(cr, uid, prod_uom, amount)
			res[prod_id]+=amount
		return res
stock_location()

#----------------------------------------------------------
# Stock Move
#----------------------------------------------------------

class stock_move_lot(osv.osv):
	_name = "stock.move.lot"
	_description = "Move Lot"
	_columns = {
		'name': fields.char('Move Description', size=64, required=True),
		'active': fields.boolean('Active'),
		'state': fields.selection( (('draft','Draft'),('done','Moved')), 'State', readonly=True),
		'serial': fields.char('Tracking Number', size=32),
		'date_planned': fields.date('Planned Date'),
		'date_moved': fields.date('Actual Date'),
		'lot_id': fields.many2one('stock.lot','Lot', required=True),
		'loc_dest_id': fields.many2one('stock.location', 'Destination Location', required=True),
		'address_id': fields.many2one('res.partner.address', 'Destination Address'),
		'origin': fields.char('Origin', size=64),
	}
	_defaults = {
		'active': lambda x,y,z: 1,
		'state': lambda x,y,z: 'draft',
		'date_planned': lambda x,y,z: time.strftime('%Y-%m-%d %H:%M:%S'),
	}
	def action_move(self, cr, uid, ids, context={}):
		raise 'todo'
stock_move_lot()


# ----------------------------------------------------
# Move
# ----------------------------------------------------

#
# Fields:
#    location_dest_id is only used for predicting futur stocks
#
class stock_move(osv.osv):
	_name = "stock.move"
	_description = "Stock Move"
	_columns = {
		'name': fields.char('Name', size=64, required=True),
		'priority': fields.selection([('0','Not urgent'),('1','Urgent')], 'Priority'),

		'date': fields.date('Date Created', required=True),
		'date_planned': fields.date('Planned date', required=True),

		'product_id': fields.many2one('product.product', 'Product', required=True ),
		'product_qty': fields.float('Quantity', required=True),
		'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),

		'location_id': fields.many2one('stock.location', 'Location',required=True),
		'location_dest_id': fields.many2one('stock.location', 'Dest. Location',required=True),

		'tracking_ids': fields.many2many('stock.tracking', 'stock_move_tracking_rel', 'move_id', 'tracking_id', 'Tracking Lots'),

		'move_reconcile_ids': fields.many2many('stock.move', 'stock_move_reconcile_rel', 'move_id', 'move2_id', 'Reconciled Move'),
		'reconciled', fields.boolean('Reconciled'),

		'state': fields.selection([('draft','Draft'),('waiting','Waiting'),('confirmed','Confirmed'),('assigned','Assigned'),('done','Done'),('cancel','cancel')], 'State', readonly=True)
	}
	_defaults = {
		'state': lambda x,y,z: 'draft',
		'priority': lambda x,y,z: 1,
		'reconciled': lambda x,y,z: 0,
		'date_planned': lambda x,y,z: time.strftime('%Y-%m-%d'),
		'date': lambda x,y,z: time.strftime('%Y-%m-%d'),
	}
	def action_confirm(self, cr, uid, ids):
		self.write(cr, uid, ids, {'state':'confirmed'})
		return True

	def action_assign(self, cr, uid, ids, *args):
		for reserv in self.browse(cr, uid, ids):
			if reserv.state in ('confirmed','waiting'):
				total = 0
				for prod in reserv.lot_line_ids:
					if prod.product_id.id == reserv.product_id.id:
						total+=prod.product_qty
				todo = reserv.product_qty - total
				print 'TODO', todo
				if todo:
					if reserv.location_id.id:
						lot_ids = self.pool.get('stock.location').product_reserve(cr, uid, [reserv.location_id.id], reserv.product_id.id, todo)
						print 'LOT IDS', lot_ids
						if lot_ids:
							self.pool.get('stock.lot.line').write(cr, uid, lot_ids, {'reservation_id': reserv.id})
		return self.check_assign(cr, uid, ids)

	def check_assign(self, cr, uid, ids):
		done = []
		pickings = {}
		for reserv in self.browse(cr, uid, ids):
			if reserv.state in ('confirmed','waiting'):
				total = 0
				for prod in reserv.lot_line_ids:
					if prod.product_id.id == reserv.product_id.id:
						total+=prod.product_qty
				todo = reserv.product_qty - total
				if todo<=0:
					done.append(reserv.id)
					if reserv.picking_id:
						pickings.setdefault(reserv.picking_id.id, [])
						pickings[reserv.picking_id.id].append(reserv.id)
		if len(done):
			self.write(cr, uid, done, {'state':'assigned'})
			for pick_id in pickings:
				self.pool.get('stock.picking')._action_assign_reserv(cr, uid, pick_id, pickings[pick_id])
				wf_service = netsvc.LocalService("workflow")
				wf_service.trg_write(uid, 'stock.picking', pick_id, cr)
		return len(done)

	#
	# Cancel reservations => others reservations and pickings
	#
	def action_cancel(self, cr, uid, ids):
		if not len(ids):
			return True
		pickings = {}
		for reserv in self.browse(cr, uid, ids):
			if reserv.state in ('confirmed','waiting','assigned','draft'):
				pickings[reserv.picking_id.id] = True
		ids_lst = ','.join(map(str,ids))
		self.write(cr, uid, ids, {'state':'cancel'})

		for pick_id in pickings:
			wf_service = netsvc.LocalService("workflow")
			wf_service.trg_validate(uid, 'stock.picking', pick_id, 'button_cancel', cr)

		ids2 = []
		for res in self.read(cr, uid, ids, ['reservation_dest_id']):
			if res['reservation_dest_id']:
				ids2.append(res['reservation_dest_id'][0])
		self.action_cancel(cr,uid, ids2)
		return True

	def action_done(self, cr, uid, ids, *args):
		for reserv in self.browse(cr, uid, ids):
			if reserv.reservation_dest_id.id:
				rid = reserv.reservation_dest_id.id
				if reserv.reservation_dest_id.state in ('waiting','confirmed','assigned'):
					if reserv.reservation_dest_id.location_id:
						cr.execute('update stock_lot_line set location_id=%d where reservation_id=%d', (reserv.reservation_dest_id.location_id.id, reserv.id))
					cr.execute('update stock_lot_line set reservation_id=%d where reservation_id=%d', (rid, reserv.id))
					self.action_confirm(cr, uid, [reserv.reservation_dest_id.id])
					self.action_assign(cr, uid, [reserv.reservation_dest_id.id])
			else:
				cr.execute('update stock_lot_line set reservation_id=NULL where reservation_id=%d', (reserv.id,))
		self.write(cr, uid, ids, {'state':'done','lot_line_ids':[]})
		return True
stock_move()

class stock_tracking(osv.osv):
	_name = "stock.tracking"
	_description = "Stock Tracking Lots"
	_columns = {
		'active': fields.boolean('Active'),
		'name': fields.char('Lot', size=64, required=True),
		'date': fields.datetime('Date create', required=True),
		'tracking': fields.char('Tracking', size=64),
		'serial': fields.char('Serial', size=64),
	}
	_defaults = {
		'active': lambda x,y,z: 1,
		'date': lambda x,y,z: time.strftime('%Y-%m-%d %H:%M:%S'),
		'serial': lambda x,y,z: x.pool.get('ir.sequence').get(y,z,'stock.lot.serial'),
		'tracking': lambda x,y,z: x.pool.get('ir.sequence').get(y,z,'stock.lot.tracking'),
	}
	def unlink(self, cr ,uid, ids):
		raise 'You can not remove a lot line !'
stock_lot_line()

class stock_inventory(osv.osv):
	_name = "stock.inventory"
	_description = "Inventory"
	_columns = {
		'name': fields.char('Inventory', size=64, required=True),
		'date': fields.datetime('Date create', required=True),
		'date_done': fields.datetime('Date done'),
		'inventory_line_id': fields.one2many('stock.inventory.line', 'inventory_id', 'Inventories'),
		'move_ids': fields.many2many('stock.move', 'stock_inventory_move_rel', 'inventory_id', 'move_id', 'Created Moves'),
		'state': fields.selection( (('draft','Draft'),('done','Done')), 'State', readonly=True),
	}
	_defaults = {
		'date': lambda x,y,z: time.strftime('%Y-%m-%d %H:%M:%S'),
		'state': lambda x,y,z: 'draft',
	}
	def action_done(self, cr, uid, ids, *args):
		for inv in self.browse(cr,uid,ids):
			lot_ids = []
			move_line=[]
			for line in inv.inventory_line_id:
				pid=line.product_id.id
				price=line.product_id.standard_price or 0.0


				v=self.pool.get('stock.location')._product_get(cr, uid, line.location_id.id, [pid])
				amount = self.pool.get('product.uom')._compute_qty(cr, uid, line.product_uom.id, line.amount)
				
				old=v[pid]
				change=line.amount-old

				if change:
					ainc,loc_id = self.pool.get('stock.lot')._account_get(cr, uid, line.lot_id.id)
					if ainc:
						ainv = ir.ir_get(cr,uid,'meta','account.stock_inventory',[('product.product',pid)])[0][2]
						l1={'name':'Inv:'+line.product_id.name, 'account_id':ainc, 'amount':change*price }
						l2={'name':'Inv:'+line.product_id.name, 'account_id':ainv, 'amount':-change*price}
						move_line += [(0,0,l1), (0,0,l2)]
					ll={ 'name': inv.name, 'location_id': loc_id, 'product_id': pid, 'product_uom':line.product_uom.id, 'product_qty': change/line.product_uom.factor, 'product_price': price}

					ll['lot_id'] = line.lot_id.id
					lot_ids.append(self.pool.get('stock.lot.line').create(cr,uid,ll))
			if len(move_line):
				move={'type': 'undefined', 'name': 'Inventory:'+inv.name, 'line_id': move_line}
				move_id=self.pool.get('account.move').create(cr, uid, move)
			self.write(cr, uid, [inv.id], {'state':'done', 'date_done': time.strftime('%Y-%m-%d %H:%M:%S'), 'lot_ids': lot_ids})
		return True
stock_inventory()


class stock_inventory_line(osv.osv):
	_name = "stock.inventory.line"
	_description = "Inventory line"
	_columns = {
		'inventory_id': fields.many2one('stock.inventory','Inventory', ondelete='cascade'),
		'location_id': fields.many2one('stock.location','Lot'),
		'product_id': fields.many2one('product.product', 'Product', required=True ),
		'product_uom': fields.many2one('product.uom', 'Product UOM', required=True ),
		'amount': fields.float('Quantity'),
	}
stock_inventory_line()


#----------------------------------------------------------
# Stock Warehouse
#----------------------------------------------------------
class stock_warehouse(osv.osv):
	_name = "stock.warehouse"
	_description = "Warehouse"
	_columns = {
		'name': fields.char('Name', size=60, required=True),
#		'partner_id': fields.many2one('res.partner', 'Owner'),
#		'partner_address_id': fields.many2one('res.partner.address', 'Owner Address'),
		'lot_input_id': fields.many2one('stock.location', 'Location Input' ),
		'lot_stock_id': fields.many2one('stock.location', 'Location Stock' ),
		'lot_output_id': fields.many2one('stock.location', 'Location Output' ),
	}
stock_warehouse()

