############################################################################## # # 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 *a: True, } stock_incoterms() #---------------------------------------------------------- # Stock Location #---------------------------------------------------------- class stock_location(osv.osv): _name = "stock.location" _description = "Location" _inherits = {'stock.lot': 'lot_id'} _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')]), 'child_ids': fields.one2many('stock.location', 'location_id', 'Contains'), 'parent_id': fields.many2one('stock.lot', string='Lot'), '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 *a: 1, 'type': lambda *a: 'location', 'usage': lambda *a: 'internal', 'allocation_method': lambda *a: 'fifo', 'posx': lambda *a: 0, 'posy': lambda *a: 0, 'posz': lambda *a: 0, } stock_location() #---------------------------------------------------------- # Stock Picking #---------------------------------------------------------- class stock_picking(osv.osv): _name = "stock.picking" _description = "Picking list" _columns = { 'name': fields.char('Picking Name', size=64, required=True), 'origin': fields.char('Origin', size=64), 'type': fields.selection([('out','Sending Goods'),('in','Getting Goods'),('internal','Internal')], 'Shipping Type'), 'active': fields.boolean('Active'), 'note': fields.text('Notes'), 'move_type': fields.selection([('direct','Direct Delivery'),('one','All at once')],'Delivery Method', required=True), 'state': fields.selection([ ('draft','draft'), ('auto','waiting'), ('confirmed','confirmed'), ('assigned','assigned'), ('done','done'), ('cancel','cancel'), ], 'State'), 'date':fields.datetime('Date create'), 'reservation_lines': fields.one2many('stock.reservation', 'picking_id', 'Reservation Lines'), 'lot_line_lines': fields.one2many('stock.picking.lot.line', 'picking_id', 'Lot lines used'), 'auto_picking': fields.boolean('Auto-Picking'), 'work': fields.boolean('Work todo'), 'lot_dest_id': fields.many2one('stock.lot', 'Lot Destination'), 'loc_dest_id': fields.many2one('stock.location', 'Location Destination', required=True), 'loc_move_id': fields.many2one('stock.location', 'Move to Location'), 'address_id': fields.many2one('res.partner.address', 'Move to Address'), 'lot_lines': fields.many2many('stock.lot', 'stock_picking_lot_rel', 'picking_id','lot_id', 'Lots Created'), 'move_lines': fields.many2many('stock.move', 'stock_picking_move_line', 'picking_id','move_id', 'Moves Created') } _defaults = { 'work': lambda *a: 0, 'active': lambda *a: 1, 'state': lambda *a: 'draft', 'move_type': lambda *a: 'direct', 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'), } def action_confirm(self, cr, uid, ids, *args): self.write(cr,uid,ids, {'state':'confirmed'}) todo = [] for line in self.browse(cr,uid, ids): for r in line.reservation_lines: if r.state=='draft': todo.append(r.id) self.pool.get('stock.reservation').write(cr,uid, [r.id], {'location_dest_id': line.loc_move_id.id or line.loc_dest_id.id}) if len(todo): self.pool.get('stock.reservation').action_confirm(cr,uid, todo) return True def test_auto_picking(self, cr, uid, ids): # TODO: Check locations to see if in the same location ? return True def action_assign(self, cr, uid, ids, *args): for pick in self.browse(cr, uid, ids): reser_ids = [x.id for x in pick.reservation_lines if x.state=='confirmed'] self.pool.get('stock.reservation').action_assign(cr, uid, reser_ids) return True def action_assign_wkf(self, cr, uid, ids): self.write(cr, uid, ids, {'state':'assigned'}) return True def _action_assign_reserv(self, cr, uid, pick_id, reserv_ids): if not reserv_ids: return False cr.execute("select l.id,l.name,l.product_uom,l.product_qty,l.serial,l.tracking,l.reservation_id,l.product_id,r.state,r.picking_id from stock_lot_line l left join stock_reservation r on (l.reservation_id=r.id) where r.id in ("+','.join(map(str,reserv_ids))+")") records = cr.dictfetchall() print records ids_products = [x['product_id'] for x in records] prods = dict(osv.osv_pools.get('product.product').name_get(cr, uid, ids_products)) ids_new = [] for record in records: new_id = osv.osv_pools.get('stock.picking.lot.line').create(cr, uid, {'name': prods.get(record['product_id'],'Unknown'),'product_id':record['product_id'], 'product_uom':record['product_uom'], 'product_qty':record['product_qty'],'serial':record['serial'],'tracking':record['tracking'],'reservation_id':record['reservation_id'],'picking_id':record['picking_id'],'lot_line_id':record['id'],'state':record['state']}) ids_new.append(new_id) return True def test_finnished(self, cr, uid, ids): for id in ids: cr.execute('select id,state from stock_reservation where picking_id=%d', (id,)) for x in cr.fetchall(): if x[1] not in ('done','cancel'): return False return True def test_assigned(self, cr, uid, ids): ok = True for pick in self.browse(cr, uid, ids): mt = pick.move_type for reserv in pick.reservation_lines: if (reserv.state in ('confirmed','draft')) and (mt=='one'): return False if (mt=='direct') and reserv.state=='assigned': return True ok = ok and (reserv.state in ('cancel','done','assigned')) return ok def action_cancel(self, cr, uid, ids): for pick in self.browse(cr, uid, ids): ids2 = [reserv.id for reserv in pick.reservation_lines] self.pool.get('stock.reservation').action_cancel(cr, uid, ids2) self.write(cr,uid, ids, {'state':'cancel'}) return True # # TODO: change and create a move if not parents # def action_done(self, cr, uid, ids, *args): for pick in self.browse(cr, uid, ids): if pick.move_type=='one' and pick.loc_move_id: for lot in pick.lot_lines: id = self.pool.get('stock.move').create(cr, uid, { 'name': 'MOVE:'+pick.name, 'origin': 'PICK:'+str(pick.id)+':'+pick.name, 'lot_id': lot.id, 'loc_dest_id': pick.loc_mode_id.id, 'address_id': pick.address_id.id }) cr.execute('insert into stock_picking_move_line (move_id,picking_id) values (%d,%d)', (id, pick.id)) self.write(cr,uid, ids, {'state':'done'}) return True def action_move(self, cr, uid, ids, context={}): for pick in self.browse(cr, uid, ids): todo = [] for reserv in pick.reservation_lines: if reserv.state=='assigned': todo.append(reserv.id) if len(todo): lot_id = pick.lot_dest_id.id if not lot_id: lot_id = self.pool.get('stock.lot').create(cr, uid, {'name':'PICK:'+pick.name, 'type':'lot', 'location_id':pick.loc_dest_id.id}) todo_lst = ','.join(map(str,todo)) cr.execute('select lot_line_id from stock_picking_lot_line where picking_id=%d and state=%s', (pick.id,'assigned')) lot_ids = [x[0] for x in cr.fetchall()] cr.execute('update stock_picking_lot_line set state=%s where picking_id=%d and state=%s', ('done',pick.id,'assigned')) self.pool.get('stock.lot.line').move(cr, uid, lot_ids, pick.loc_dest_id.id, pick.address_id) self.pool.get('stock.lot.line').write(cr, uid, lot_ids, {'lot_id': lot_id}) self.pool.get('stock.reservation').action_done(cr, uid, todo) cr.execute('insert into stock_picking_lot_rel (lot_id,picking_id) values (%d,%d)', (lot_id, pick.id)) self.write(cr, uid, [pick.id], {'lot_id':False}) if pick.move_type=='direct' and pick.loc_move_id: id = self.pool.get('stock.move').create(cr, uid, { 'name': 'MOVE:'+pick.name, 'origin': 'PICK:'+str(pick.id)+':'+pick.name, 'lot_id': lot_id, 'loc_dest_id': pick.loc_move_id.id, 'address_id': pick.address_id.id }) cr.execute('insert into stock_picking_move_line (move_id,picking_id) values (%d,%d)', (id, pick.id)) return True stock_picking() # ---------------------------------------------------- # Reservation # ---------------------------------------------------- # # Fields: # location_dest_id is only used for predicting futur stocks # class stock_reservation(osv.osv): _name = "stock.reservation" _description = "Reservation" _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' ), 'lot_line_ids': fields.one2many('stock.lot.line', 'reservation_id', 'Reserved Lots'), 'reservation_dest_id': fields.many2one('stock.reservation', 'Dest. Reservation'), 'location_dest_id': fields.many2one('stock.location', 'Dest. Location'), 'picking_id': fields.many2one('stock.picking', 'Product', required=True ), 'state': fields.selection([('draft','Draft'),('waiting','Waiting'),('confirmed','Confirmed'),('assigned','Assigned'),('done','Done'),('cancel','cancel')], 'State', readonly=True) } _defaults = { 'state': lambda *a: 'draft', 'priority': lambda *a: 1, 'date_planned': lambda *a: time.strftime('%Y-%m-%d'), 'date': lambda *a: 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) 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)) cr.execute('update stock_lot_line set reservation_id=NULL where reservation_id in ('+ids_lst+')') 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 # # TODO: creer un stock.move.line.move si pas dans meme branche de location.tree # pour deplacer le lot.line au bon emplacement voulu # 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_reservation() class stock_lot_line(osv.osv): _name = "stock.lot.line" _description = "Articles - Lot Line" _columns = { 'active': fields.boolean('Active'), 'name': fields.char('Lot', size=64, required=True), 'date': fields.datetime('Date create', required=True), # Should be changed 'parent_ids' : fields.many2many('stock.lot.line', 'stock_lot_line_rel', 'parent_id','child_id', 'Parents'), 'child_ids' : fields.many2many('stock.lot.line', 'stock_lot_line_rel','child_id', 'parent_id', 'Childs'), 'tracking': fields.char('Tracking', size=64), 'serial': fields.char('Serial', size=64), 'address_id': fields.many2one('res.partner.address', 'Address'), '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), 'product_price': fields.float('Unit Costs Price', digits=(16,2) ), 'lot_id': fields.many2one('stock.lot', 'Lot / Location', ondelete='set null'), 'reservation_id': fields.many2one('stock.reservation', 'Reservation'), 'location_id': fields.many2one('stock.location', 'Location'), 'move_history_lines' : fields.many2many('stock.lot.move.history', 'stock_lot_line_move_history', 'lot_line_id','history_id', 'Lot Line'), } _defaults = { 'active': lambda *a: 1, 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'), 'serial': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'stock.lot.serial'), 'tracking': lambda x,y,z,c: 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 !' def init(self, cr): cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname='stock_lot_move_history'") if len(cr.dictfetchall())==0: cr.execute("CREATE TABLE stock_lot_move_history (id SERIAL NOT NULL, perm_id INTEGER, PRIMARY KEY(id))"); cr.commit() def _split(self, cr, uid, id, amount, serial=False, tracking=False): cr.execute('select * from stock_lot_line where id=%d', (id,)) result = cr.dictfetchone() serial = serial or result['serial'] tracking = tracking or result['tracking'] id_new = self.create(cr, uid, { 'active':result['active'], 'name':result['name'], 'tracking':tracking, 'serial':serial, 'address_id':result['address_id'], 'product_id':result['product_id'], 'product_uom':result['product_uom'], 'product_qty':amount, 'product_price':result['product_price'], 'lot_id':result['lot_id'], 'reservation_id':result['reservation_id'], 'location_id':result['location_id'], }) cr.execute('insert into stock_lot_line_rel (parent_id,child_id) values (%d,%d)', (id, id_new)) self.write(cr, uid, [id],{'product_qty':result['product_qty']-amount}) return id_new def move(self, cr, uid, ids, loc_dest_id, address_id): move_line = [] cr.execute('select account_id from stock_location where id=%d', (loc_dest_id,)) dest_account = cr.fetchone()[0] for line in self.browse(cr, uid, ids): src_account=line.location_id.account_id.id if src_account!=dest_account: pid=line.product_id.id price = self.pool.get('product.product').price_get(cr, uid, [pid], 'standard')[pid] or 0.0 if price: if not src_account: src_account = ir.ir_get(cr,uid,'meta','account.stock_expense',[('product.product', pid)])[0][2] if not dest_account: dest_account = ir.ir_get(cr,uid,'meta','account.stock_income',[('product.product',pid)])[0][2] l1={'name':'Move:'+line.name, 'account_id':src_account, 'amount':-line.product_qty*price } l2={'name':'Move:'+line.name, 'account_id':dest_account, 'amount':line.product_qty*price } move_line += [(0,0,l1), (0,0,l2)] if len(move_line): mv={'type': 'undefined', 'name': 'Move', 'line_id': move_line} move_id=self.pool.get('account.move').create(cr, uid, mv) newid = self.pool.get('stock.lot.move.history').create(cr, uid, {'lot_line_id':ids, 'name':'History', 'location_id':loc_dest_id, 'address_id':address_id}) self.write(cr, uid, ids, {'address_id':address_id, 'location_id':loc_dest_id}) return True def name_search(self, cr, user, name, args=[], operator='ilike', context={}): ids = self.search(cr, user, [('serial','=',name)]+ args) ids += self.search(cr, user, [('tracking','=',name)]+ args) if not ids: ids = self.search(cr, user, [('name',operator,name)]+ args) return self.name_get(cr, user, ids) stock_lot_line() #---------------------------------------------------------- # Stock Inventory #---------------------------------------------------------- class stock_lot_inventory(osv.osv): _name = "stock.lot.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.lot.inventory.line', 'inventory_id', 'Inventories'), 'lot_ids': fields.many2many('stock.lot.line', 'stock_inventory_lot', 'inventory_id', 'lot_id', 'Created Lots'), 'state': fields.selection( (('draft','Draft'),('done','Done')), 'State', readonly=True), } _defaults = { 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'), 'state': lambda *a: 'draft', } def action_done(self, cr, uid, ids, *args): for inv in self.browse(cr,uid,ids): # SUM(amount) GROUP BY lot_id,product_id lot_ids = [] move_line=[] for line in inv.inventory_line_id: pid=line.product_id.id # TODO good prive price=line.product_id.standard_price or 0.0 v=self.pool.get('stock.lot')._product_get(cr, uid, line.lot_id.id, [pid]) 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_lot_inventory() class stock_lot_inventory_line(osv.osv): _name = "stock.lot.inventory.line" _description = "Inventory line" _columns = { 'inventory_id': fields.many2one('stock.lot.inventory','Inventory', ondelete='cascade'), 'lot_id': fields.many2one('stock.lot','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_lot_inventory_line() class stock_lot_move_history(osv.osv): _name = "stock.lot.move.history" _description = "Lot move history" _columns = { 'name': fields.char('Lot', size=64, required=True), 'lot_line_id' : fields.many2many('stock.lot.line', 'stock_lot_line_move_history', 'history_id','lot_line_id', 'Lot Line'), 'location_id' : fields.many2one('stock.lot', 'Location'), 'address_id' : fields.many2one('res.partner.address', 'Address'), 'date': fields.datetime('Date create', required=True), } _defaults = { 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'), } stock_lot_move_history() #---------------------------------------------------------- # 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() class stock_picking_lot_line(osv.osv): _name = "stock.picking.lot.line" _description = "Picking lot lines" _columns = { 'name': fields.char('Product', size=64, required=True, readonly=True), 'product_id': fields.many2one('product.product', 'Product', readonly=True), 'product_qty': fields.float('Quantity', readonly=True), 'product_uom': fields.many2one('product.uom', 'Product UOM', readonly=True), 'lot_line_id': fields.many2one('stock.lot.line', 'Reserved Lot', required=True), 'picking_id': fields.many2one('stock.picking', 'Picking List', required=True), 'reservation_id': fields.many2one('stock.reservation', 'Reservation', readonly=True), 'tracking': fields.char('New Tracking', size=32), 'serial': fields.char('New Serial', size=32, readonly=True), 'state': fields.char('State', size=32, readonly=True), } def onchange_lot_line_id(self, cr, uid, arg, lot_line_id): if not lot_line_id: return {'value':{'product_id': False, 'product_uom':False, 'product_qty': False, 'tracking': False, 'serial':False, 'name':False}} res = self.pool.get('stock.lot.line').browse(cr, uid, [lot_line_id])[0] return {'value':{'product_id': res.product_id.id, 'product_uom':res.product_uom.id, 'product_qty': res.product_qty, 'tracking': res.tracking, 'serial':res.serial,'name':res.product_id.name}} _defaults = { 'state': lambda *a: 'assigned', } stock_picking_lot_line()