############################################################################## # # Copyright (c) 2004-2006 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 datetime 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() class stock_lot(osv.osv): _name = "stock.lot" _description = "Lot" _columns = { 'name': fields.char('Lot Name', size=64, required=True), 'active': fields.boolean('Active'), 'tracking': fields.char('Tracking', size=64), 'move_ids': fields.one2many('stock.move', 'lot_id', 'Move lines'), } _defaults = { 'active': lambda *a: True, } stock_lot() #---------------------------------------------------------- # Stock Location #---------------------------------------------------------- class stock_location(osv.osv): _name = "stock.location" _description = "Location" _columns = { 'name': fields.char('Location Name', size=64, required=True), 'active': fields.boolean('Active'), 'usage': fields.selection([('supplier','Supplier Location'),('internal','Internal Location'),('customer','Customer Location'),('inventory','Inventory'),('procurement','Procurement'),('production','Production')], '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', 'Parent Location', select=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 *a: 1, 'usage': lambda *a: 'internal', 'allocation_method': lambda *a: 'fifo', 'posx': lambda *a: 0, 'posy': lambda *a: 0, 'posz': lambda *a: 0, } def _product_get_all_report(self, cr, uid, ids, product_ids=False, context={}): return self._product_get_report(cr, uid, ids, product_ids, context, recursive=True) def _product_get_report(self, cr, uid, ids, product_ids=False, context={}, recursive=False): if not product_ids: product_ids = self.pool.get('product.product').search(cr, uid, []) result = [] for id in ids: for prod_id in product_ids: product = self.pool.get('product.product').browse(cr, uid, [prod_id])[0] fnc = self._product_get if recursive: fnc = self._product_all_get qty = fnc(cr, uid, id, [prod_id], {'uom': product.uom_id.id}) if qty[prod_id]: result.append({ 'price': product.list_price, 'name': product.name, 'code': product.default_code, # used by lot_overview_all report ! 'variants': product.variants or '', 'uom': product.uom_id.name, 'amount':qty[prod_id] }) return result def _product_get_multi_location(self, cr, uid, ids, product_ids=False, context={}, states=['done'], what=('in', 'out')): states_str = ','.join(map(lambda s: "'%s'" % s, states)) if not product_ids: product_ids = self.pool.get('product.product').search(cr, uid, []) res = {} for id in product_ids: res[id] = 0.0 if not ids: return res prod_ids_str = ','.join(map(str, product_ids)) location_ids_str = ','.join(map(str, ids)) results = [] if 'in' in what: # all moves from a location out of the set to a location in the set cr.execute( 'select sum(product_qty), product_id, product_uom '\ 'from stock_move '\ 'where location_id not in ('+location_ids_str+') '\ 'and location_dest_id in ('+location_ids_str+') '\ 'and product_id in ('+prod_ids_str+') '\ 'and state in ('+states_str+') '\ 'group by product_id,product_uom' ) results += cr.fetchall() if 'out' in what: # all moves from a location in the set to a location out of the set cr.execute( 'select -sum(product_qty), product_id, product_uom '\ 'from stock_move '\ 'where location_id in ('+location_ids_str+') '\ 'and location_dest_id not in ('+location_ids_str+') '\ 'and product_id in ('+prod_ids_str+') '\ 'and state in ('+states_str+') '\ 'group by product_id,product_uom' ) results += cr.fetchall() uom_obj = self.pool.get('product.uom') for amount, prod_id, prod_uom in results: amount = uom_obj._compute_qty(cr, uid, prod_uom, amount, context.get('uom', False)) res[prod_id] += amount return res def _product_get(self, cr, uid, id, product_ids=False, context={}, states=['done']): ids = id and [id] or [] return self._product_get_multi_location(cr, uid, ids, product_ids, context, states) def _product_all_get(self, cr, uid, id, product_ids=False, context={}, states=['done']): # build the list of ids of children of the location given by id ids = id and [id] or [] location_ids = self.search(cr, uid, [('location_id', 'child_of', ids)]) return self._product_get_multi_location(cr, uid, location_ids, product_ids, context, states) def _product_virtual_get(self, cr, uid, id, product_ids=False, context={}, states=['done']): return self._product_all_get(cr, uid, id, product_ids, context, ['confirmed','waiting','assigned','done']) # # TODO: # Improve this function # # Returns: # [ (tracking_id, product_qty, location_id) ] # def _product_reserve(self, cr, uid, ids, product_id, product_qty, context={}): result = [] amount = 0.0 for id in self.search(cr, uid, [('location_id', 'child_of', ids)]): cr.execute("select product_uom,sum(product_qty) as product_qty from stock_move where location_dest_id=%d and product_id=%d and state='done' group by product_uom", (id,product_id)) results = cr.dictfetchall() cr.execute("select product_uom,-sum(product_qty) as product_qty from stock_move where location_id=%d and product_id=%d and state in ('done', 'assigned') group by product_uom", (id,product_id)) results += cr.dictfetchall() total = 0.0 results2 = 0.0 for r in results: amount = self.pool.get('product.uom')._compute_qty(cr, uid, r['product_uom'],r['product_qty'], context.get('uom',False)) results2 += amount total += amount if total<=0.0: continue amount = results2 if amount>0: if amount>min(total,product_qty): amount = min(product_qty,total) result.append((amount,id)) product_qty -= amount total -= amount if product_qty<=0.0: return result if total<=0.0: continue return False 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 *a: 1, 'state': lambda *a: 'draft', 'date_planned': lambda *a: time.strftime('%Y-%m-%d'), } # # TODO: test if valid # ERROR: does this function should call action_done instead of doing him self on # stock.move # def action_move(self, cr, uid, ids, context={}): for move in self.browse(cr, uid, ids, context): lot_remove = [] for m in move.lot_id.move_ids: new_id = self.pool.get('stock.move').copy(cr, uid, m.id, {'location_id': m.location_dest_id.id, 'location_dest_id': move.loc_dest_id.id, 'date_moved': time.strftime('%Y-%m-%d'), 'picking_id': False, 'state':'draft','prodlot_id':False, 'tracking_id':False, 'lot_id': False, 'move_history_ids':[], 'move_history_ids2':[]}) self.pool.get('stock.move').action_done(cr, uid, [new_id], context) cr.execute('insert into stock_move_history_ids (parent_id,child_id) values (%d,%d)', (m.id, new_id)) lot_remove.append(m.id) self.pool.get('stock.move').write(cr, uid, lot_remove, {'lot_id':False}) self.write(cr,uid, ids, {'state':'done','date_moved':time.strftime('%Y-%m-%d')}) return True stock_move_lot() class stock_tracking(osv.osv): _name = "stock.tracking" _description = "Stock Tracking Lots" def checksum(sscc): salt = '31' * 8 + '3' sum = 0 for sscc_part, salt_part in zip(sscc, salt): sum += int(sscc_part) * int(salt_part) return (10 - (sum % 10)) % 10 checksum = staticmethod(checksum) def make_sscc(self, cr, uid, context={}): sequence = self.pool.get('ir.sequence').get(cr, uid, 'stock.lot.tracking') return sequence + str(self.checksum(sequence)) _columns = { 'name': fields.char('Tracking', size=64, required=True), 'active': fields.boolean('Active'), 'serial': fields.char('Reference', size=64), 'move_ids' : fields.one2many('stock.move', 'tracking_id', 'Moves tracked'), 'date': fields.datetime('Date create', required=True), } _defaults = { 'active': lambda *a: 1, # 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'stock.lot.tracking'), 'name' : make_sscc, 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'), } #_sql = 'ALTER TABLE stock_tracking ADD CONSTRAINT stock_lot_tracking_unique UNIQUE (name,serial)' def name_search(self, cr, user, name, args=[], operator='ilike', context={}): ids = self.search(cr, user, [('serial','=',name)]+ args) ids += self.search(cr, user, [('name',operator,name)]+ args) return self.name_get(cr, user, ids) def name_get(self, cr, uid, ids, context={}): if not len(ids): return [] res = [(r['id'], r['name']+' ['+(r['serial'] or '')+']') for r in self.read(cr, uid, ids, ['name','serial'], context)] return res def unlink(self, cr ,uid, ids): raise 'You can not remove a lot line !' stock_tracking() #---------------------------------------------------------- # Stock Picking #---------------------------------------------------------- class stock_picking(osv.osv): _name = "stock.picking" _description = "Picking list" _columns = { 'name': fields.char('Picking Name', size=64, required=True, select=True), 'origin': fields.char('Origin', size=64), 'type': fields.selection([('out','Sending Goods'),('in','Getting Goods'),('internal','Internal')], 'Shipping Type', required=True, select=True), 'active': fields.boolean('Active'), 'note': fields.text('Notes'), 'location_id': fields.many2one('stock.location', 'Location'), 'location_dest_id': fields.many2one('stock.location', 'Dest. Location'), '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', readonly=True), 'date':fields.datetime('Date create'), 'move_lines': fields.one2many('stock.move', 'picking_id', 'Move Lines'), 'auto_picking': fields.boolean('Auto-Picking'), 'work': fields.boolean('Work todo'), 'loc_move_id': fields.many2one('stock.location', 'Move to Location'), 'address_id': fields.many2one('res.partner.address', 'Partner Address'), 'lot_id': fields.many2one('stock.lot', 'Consumer Lot Created'), 'move_lot_id': fields.many2one('stock.move.lot', 'Moves Created'), 'invoice_state':fields.selection([ ("invoiced","Invoiced"), ("2binvoiced","To be invoiced"), ("none","No invoice")], "Invoice state"), } _defaults = { 'name': lambda *a: 'Unconfirmed picking', 'work': lambda *a: 0, 'active': lambda *a: 1, 'state': lambda *a: 'draft', 'move_type': lambda *a: 'direct', 'type': lambda *a: 'in', 'invoice_state': lambda *a: 'none', 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'), } def action_confirm(self, cr, uid, ids, *args): print 'Confirmed', ids self.write(cr, uid, ids, {'state': 'confirmed'}) todo = [] for picking in self.browse(cr, uid, ids): number = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.%s' % picking.type) self.write(cr, uid, [picking.id], {'name': number}) for r in picking.move_lines: if r.state=='draft': todo.append(r.id) if len(todo): self.pool.get('stock.move').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): move_ids = [x.id for x in pick.move_lines if x.state=='confirmed'] self.pool.get('stock.move').action_assign(cr, uid, move_ids) return True def force_assign(self, cr, uid, ids, *args): wf_service = netsvc.LocalService("workflow") for pick in self.browse(cr, uid, ids): # move_ids = [x.id for x in pick.move_lines if x.state == 'confirmed'] move_ids = [x.id for x in pick.move_lines] self.pool.get('stock.move').force_assign(cr, uid, move_ids) wf_service.trg_write(uid, 'stock.picking', pick.id, cr) return True def cancel_assign(self, cr, uid, ids, *args): wf_service = netsvc.LocalService("workflow") for pick in self.browse(cr, uid, ids): move_ids = [x.id for x in pick.move_lines] self.pool.get('stock.move').cancel_assign(cr, uid, move_ids) wf_service.trg_write(uid, 'stock.picking', pick.id, cr) return True def action_assign_wkf(self, cr, uid, ids): self.write(cr, uid, ids, {'state':'assigned'}) return True def test_finnished(self, cr, uid, ids): for id in ids: cr.execute('select id,state from stock_move 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 move in pick.move_lines: if (move.state in ('confirmed','draft')) and (mt=='one'): return False if (mt=='direct') and (move.state=='assigned') and (move.product_qty): return True ok = ok and (move.state in ('cancel','done','assigned')) return ok def action_cancel(self, cr, uid, ids, context={}): for pick in self.browse(cr, uid, ids): ids2 = [move.id for move in pick.move_lines] self.pool.get('stock.move').action_cancel(cr, uid, ids2, context) self.write(cr,uid, ids, {'state':'cancel', 'invoice_state':'none'}) 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: if pick.lot_id: id = self.pool.get('stock.move.lot').create(cr, uid, { 'name': 'MOVE:'+pick.name, 'origin': 'PICK:'+str(pick.id), 'lot_id': pick.lot_id.id, 'loc_dest_id': pick.loc_move_id.id, 'address_id': pick.address_id.id }) self.write(cr, uid, [pick.id], {'move_lot_id':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 move in pick.move_lines: if move.state=='assigned': todo.append(move.id) if len(todo): self.pool.get('stock.move').action_done(cr, uid, todo) lot_id = self.pool.get('stock.lot').create(cr, uid, {'name':'PICK:'+pick.name}) self.pool.get('stock.move').write(cr,uid, todo, {'lot_id':lot_id}) self.write(cr, uid, [pick.id], {'lot_id':lot_id}) if pick.move_type=='direct' and pick.loc_move_id: id = self.pool.get('stock.move.lot').create(cr, uid, { 'name': 'MOVE:'+pick.name, 'origin': 'PICK:'+str(pick.id), 'lot_id': lot_id, 'loc_dest_id': pick.loc_move_id.id, 'address_id': pick.address_id.id }) self.write(cr, uid, [pick.id], {'move_lot_id':id}) return True def action_invoice_create(self, cr, uid, ids, journal_id=False, group=False, type='out_invoice', context={}): res={} pgroup = {} get_ids = lambda y: map(lambda x: x.id, y or []) sales = {} for p in self.browse(cr,uid,ids, context): if p.invoice_state<>'2binvoiced': continue a = p.address_id.partner_id.property_account_receivable[0] if p.address_id.partner_id and p.address_id.partner_id.property_payment_term: pay_term = p.address_id.partner_id.property_payment_term[0] else: pay_term = False if p.sale_id: pinv_id = p.sale_id.partner_invoice_id.id pcon_id = p.sale_id.partner_order_id.id if p.sale_id.id not in sales: sales[p.sale_id.id] = [x.id for x in p.sale_id.invoice_ids] else: # # ideal: get_address('invoice') on partner # pinv_id = p.address_id.id pcon_id = p.address_id.id val = { 'name': p.name, 'origin': p.name+':'+p.origin, 'type': type, 'reference': "P%dSO%d"%(p.address_id.partner_id.id,p.id), 'account_id': a, 'partner_id': p.address_id.partner_id.id, 'address_invoice_id': pinv_id, 'address_contact_id': pcon_id, 'project_id': (p.sale_id and p.sale_id.project_id.id) or False, 'comment': (p.note or '') + '\n' + (p.sale_id and p.sale_id.note or ''), 'payment_term': pay_term, } if p.sale_id: val['currency_id'] = (p.sale_id and p.sale_id.pricelist_id.currency_id.id) or False if journal_id: val['journal_id'] = journal_id if group and p.address_id.partner_id.id in pgroup: invoice_id= pgroup[p.address_id.partner_id.id] else: invoice_id = self.pool.get('account.invoice').create(cr, uid, val ,context= context) pgroup[p.address_id.partner_id.id] = invoice_id res[p.id]= invoice_id for line in p.move_lines: tax_ids = map(lambda x: x.id, line.product_id.taxes_id) a = line.product_id.product_tmpl_id.property_account_income if not a: a = line.product_id.categ_id.property_account_income_categ account_id = a[0] punit = line.sale_line_id and line.sale_line_id.price_unit or line.product_id.list_price if type in ('in_invoice','in_refund'): punit = line.product_id.standard_price iline_id = self.pool.get('account.invoice.line').create(cr, uid, { 'name': p.name + ' - ' + line.name, 'invoice_id': invoice_id, 'uos_id': line.product_uos.id, 'product_id': line.product_id.id, 'account_id': account_id, 'price_unit': line.sale_line_id and line.sale_line_id.price_unit or line.product_id.list_price, 'discount': line.sale_line_id and line.sale_line_id.discount or 0.0, 'quantity': line.product_uos_qty, 'invoice_line_tax_id': [(6,0,tax_ids)] }) if line.sale_line_id: self.pool.get('sale.order.line').write(cr, uid, [line.sale_line_id.id], { 'invoice_line_ids': [(6, 0, [iline_id])] }) self.pool.get('stock.picking').write(cr, uid, [p.id], {'invoice_state': 'invoiced'}) if p.sale_id: sids = sales[p.sale_id.id] if invoice_id not in sids: sales[p.sale_id.id].append(invoice_id) self.pool.get('sale.order').write(cr, uid, [p.sale_id.id], { 'invoice_ids': [(6, 0, sales[p.sale_id.id])] }) self.pool.get('sale.order').write(cr, uid, [p.sale_id.id], { 'invoiced': True }) return res stock_picking() class stock_production_lot(osv.osv): def name_get(self, cr, uid, ids, context={}): if not ids: return [] reads = self.read(cr, uid, ids, ['name', 'ref'], context) res=[] for record in reads: name=record['name'] if record['ref']: name=name+'/'+record['ref'] res.append((record['id'], name)) return res _name = 'stock.production.lot' _description = 'Production lot' _columns = { 'name': fields.char('Code', size=64, required=True), 'ref': fields.char('Reference', size=64), 'date': fields.datetime('Date create', required=True), 'revisions': fields.one2many('stock.production.lot.revision','lot_id','Revisions'), } _defaults = { 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'), 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'stock.lot.serial'), } stock_production_lot() class stock_production_lot_revision(osv.osv): _name = 'stock.production.lot.revision' _description = 'Production lot revisions' _columns = { 'name': fields.char('Revision name', size=64, required=True), 'description': fields.text('Description'), 'date': fields.date('Revision date'), 'indice': fields.char('Revision', size=16), 'author_id': fields.many2one('res.users', 'Author'), 'lot_id': fields.many2one('stock.production.lot', 'Production lot', select=True), } _defaults = { 'author_id': lambda x,y,z,c: z, 'date': lambda *a: time.strftime('%Y-%m-%d'), } stock_production_lot_revision() # ---------------------------------------------------- # Move # ---------------------------------------------------- # # Fields: # location_dest_id is only used for predicting futur stocks # class stock_move(osv.osv): def _getSSCC(self, cr, uid, context={}): cr.execute('select id from stock_tracking where create_uid=%d order by id desc limit 1', (uid,)) res = cr.fetchone() return (res and res[0]) or False _name = "stock.move" _description = "Stock Move" _columns = { 'name': fields.char('Name', size=64, required=True, select=True), 'priority': fields.selection([('0','Not urgent'),('1','Urgent')], 'Priority'), 'date': fields.datetime('Date Created'), 'date_planned': fields.date('Planned date', required=True), 'product_id': fields.many2one('product.product', 'Product', required=True), 'product_qty': fields.float('Quantity (UOM)', 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', 'Product packaging'), 'location_id': fields.many2one('stock.location', 'Location', required=True), 'location_dest_id': fields.many2one('stock.location', 'Dest. Location', required=True), 'address_id' : fields.many2one('res.partner.address', 'Dest. Address'), 'prodlot_id' : fields.many2one('stock.production.lot', 'Production lot'), 'tracking_id': fields.many2one('stock.tracking', 'Lot Tracking', select=True), 'lot_id': fields.many2one('stock.lot', 'Lot', select=True), 'move_dest_id': fields.many2one('stock.move', 'Dest. Move'), 'move_history_ids': fields.many2many('stock.move', 'stock_move_history_ids', 'parent_id', 'child_id', 'Move History'), 'move_history_ids2': fields.many2many('stock.move', 'stock_move_history_ids', 'child_id', 'parent_id', 'Move History'), 'picking_id': fields.many2one('stock.picking', 'Picking list', select=True), 'note': fields.text('Notes'), 'state': fields.selection([('draft','Draft'),('waiting','Waiting'),('confirmed','Confirmed'),('assigned','Assigned'),('done','Done'),('cancel','cancel')], 'State', readonly=True, select=True), } _defaults = { 'state': lambda *a: 'draft', 'priority': lambda *a: '1', 'product_qty': lambda *a: 1.0, 'date_planned': lambda *a: time.strftime('%Y-%m-%d'), 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'), } def onchange_product_id(self, cr, uid, context, prod_id=False, loc_id=False, loc_dest_id=False): if not prod_id: return {} product = self.pool.get('product.product').browse(cr, uid, [prod_id])[0] result = { 'name': product.name, 'product_uom': product.uom_id.id, } if loc_id: result['location_id'] = loc_id if loc_dest_id: result['location_dest_id'] = loc_dest_id return {'value':result} def action_confirm(self, cr, uid, ids, context={}): print 'Confirmed state', ids self.write(cr, uid, ids, {'state':'confirmed'}) return True def action_assign(self, cr, uid, ids, *args): todo = [] for move in self.browse(cr, uid, ids): if move.state in ('confirmed','waiting'): todo.append(move.id) res = self.check_assign(cr, uid, todo) return res def force_assign(self, cr, uid, ids, context={}): self.write(cr, uid, ids, {'state' : 'assigned'}) return True def cancel_assign(self, cr, uid, ids, context={}): self.write(cr, uid, ids, {'state': 'confirmed'}) return True # # Duplicate stock.move # def check_assign(self, cr, uid, ids, context={}): done = [] count=0 pickings = {} for move in self.browse(cr, uid, ids): if move.product_id.type == 'consu': done.append(move.id) continue if move.state in ('confirmed','waiting'): res = self.pool.get('stock.location')._product_reserve(cr, uid, [move.location_id.id], move.product_id.id, move.product_qty, {'uom': move.product_uom.id}) if res: done.append(move.id) pickings[move.picking_id.id] = 1 r = res.pop(0) cr.execute('update stock_move set location_id=%d, product_qty=%f where id=%d', (r[1],r[0], move.id)) while res: r = res.pop(0) move_id = self.copy(cr, uid, move.id, {'product_qty':r[0], 'location_id':r[1]}) done.append(move_id) #cr.execute('insert into stock_move_history_ids values (%d,%d)', (move.id,move_id)) if done: count += len(done) self.write(cr, uid, done, {'state':'assigned'}) done = [] if count: for pick_id in pickings: wf_service = netsvc.LocalService("workflow") wf_service.trg_write(uid, 'stock.picking', pick_id, cr) return count # # Cancel move => cancel others move and pickings # def action_cancel(self, cr, uid, ids, context={}): if not len(ids): return True pickings = {} for move in self.browse(cr, uid, ids): if move.state in ('confirmed','waiting','assigned','draft'): if move.picking_id: pickings[move.picking_id.id] = True self.write(cr, uid, ids, {'state':'cancel'}) ids_lst = ','.join(map(str,ids)) 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, ['move_dest_id']): if res['move_dest_id']: ids2.append(res['move_dest_id'][0]) wf_service = netsvc.LocalService("workflow") for id in ids: wf_service.trg_trigger(uid, 'stock.move', id, cr) self.action_cancel(cr,uid, ids2, context) return True def action_done(self, cr, uid, ids, *args): for move in self.browse(cr, uid, ids): if move.move_dest_id.id and (move.state != 'done'): mid = move.move_dest_id.id if move.move_dest_id.id: cr.execute('insert into stock_move_history_ids (parent_id,child_id) values (%d,%d)', (move.id, move.move_dest_id.id)) if move.move_dest_id.state in ('waiting','confirmed'): self.write(cr, uid, [move.move_dest_id.id], {'state':'assigned'}) if move.move_dest_id.picking_id: wf_service = netsvc.LocalService("workflow") wf_service.trg_write(uid, 'stock.picking', move.move_dest_id.picking_id.id, cr) else: pass # self.action_done(cr, uid, [move.move_dest_id.id]) # # Accounting Entries # # acc_src = None # acc_dest = None # if move.location_id.account_id: # acc_src = move.location_id.account_id.id # if move.location_dest_id.account_id: # acc_dest = move.location_dest_id.account_id.id # if acc_src or acc_dest: # test = [('product.product', move.product_id.id)] # if move.product_id.categ_id: # test.append( ('product.category', move.product_id.categ_id.id) ) # if not acc_src: # acc_src = move.product_id.product_tmpl_id.property_account_expense[0] # if not acc_dest: # acc_dest = move.product_id.product_tmpl_id.property_account_income[0] # if acc_src != acc_dest: # amount = move.product_qty * move.product_id.standard_price # lines = [ # (0,0,{'name': 'Stock', 'amount': -amount, 'account_id': acc_src}), # (0,0,{'name': 'Stock', 'amount': amount, 'account_id': acc_dest}) # ] # self.pool.get('account.move').create(cr, uid, {'name': 'Stock', 'type':'undefined', 'line_id': lines}) self.write(cr, uid, ids, {'state':'done'}) wf_service = netsvc.LocalService("workflow") for id in ids: wf_service.trg_trigger(uid, 'stock.move', id, cr) return True stock_move() 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 *a: time.strftime('%Y-%m-%d %H:%M:%S'), 'state': lambda *a: 'draft', } # # Update to support tracking # def action_done(self, cr, uid, ids, *args): for inv in self.browse(cr,uid,ids): move_ids = [] move_line=[] for line in inv.inventory_line_id: pid=line.product_id.id price=line.product_id.standard_price or 0.0 amount=self.pool.get('stock.location')._product_get(cr, uid, line.location_id.id, [pid], {'uom': line.product_uom.id})[pid] change=line.product_qty-amount if change: location_id = line.product_id.product_tmpl_id.property_stock_inventory[0] value = { 'name': 'INV:'+str(line.inventory_id.id)+':'+line.inventory_id.name, 'product_id': line.product_id.id, 'product_uom': line.product_uom.id, 'date': inv.date, 'date_planned': inv.date, 'state': 'assigned' } if change>0: value.update( { 'product_qty': change, 'location_id': location_id, 'location_dest_id': line.location_id.id, }) else: value.update( { 'product_qty': -change, 'location_id': line.location_id.id, 'location_dest_id': location_id, }) move_ids.append(self.pool.get('stock.move').create(cr, uid, value)) if len(move_ids): self.pool.get('stock.move').action_done(cr, uid, move_ids) self.write(cr, uid, [inv.id], {'state':'done', 'date_done': time.strftime('%Y-%m-%d %H:%M:%S'), 'move_ids': [(6,0,move_ids)]}) return True def action_cancel(self, cr, uid, ids, context={}): for inv in self.browse(cr,uid,ids): self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in inv.move_ids], context) self.write(cr, uid, [inv.id], {'state':'draft'}) 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', select=True), 'location_id': fields.many2one('stock.location','Location', required=True), 'product_id': fields.many2one('product.product', 'Product', required=True ), 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True ), 'product_qty': fields.float('Quantity') } def on_change_product_id(self, cr, uid, ids, location_id, product, uom=False): if not product: return {} if not uom: prod = self.pool.get('product.product').browse(cr, uid, [product], {'uom': uom})[0] uom = prod.uom_id.id amount=self.pool.get('stock.location')._product_get(cr, uid, location_id, [product], {'uom': uom})[product] result = {'product_qty':amount, 'product_uom':uom} return {'value':result} 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', required=True ), 'lot_stock_id': fields.many2one('stock.location', 'Location Stock', required=True ), 'lot_output_id': fields.many2one('stock.location', 'Location Output', required=True ), } stock_warehouse() # Product class product_product(osv.osv): _inherit = "product.product" # # Utiliser browse pour limiter les queries ! # def _get_product_available_func(states, what): def _product_available(self, cr, uid, ids, name, arg, context={}): if context.get('shop', False): cr.execute('select warehouse_id from sale_shop where id=%d', (int(context['shop']),)) res2 = cr.fetchone() if res2: context['warehouse'] = res2[0] if context.get('warehouse', False): cr.execute('select lot_stock_id from stock_warehouse where id=%d', (int(context['warehouse']),)) res2 = cr.fetchone() if res2: context['location'] = res2[0] if context.get('location', False): location_ids = [context['location']] else: # get the list of ids of the stock location of all warehouses cr.execute("select lot_stock_id from stock_warehouse") location_ids = [id for (id,) in cr.fetchall()] # build the list of ids of children of the location given by id location_ids = self.pool.get('stock.location').search(cr, uid, [('location_id', 'child_of', location_ids)]) res = self.pool.get('stock.location')._product_get_multi_location(cr, uid, location_ids, ids, context, states, what) for id in ids: res.setdefault(id, 0.0) return res return _product_available _product_qty_available = _get_product_available_func(('done',), ('in', 'out')) _product_virtual_available = _get_product_available_func(('confirmed','waiting','assigned','done'), ('in', 'out')) _product_outgoing_qty = _get_product_available_func(('confirmed','waiting','assigned'), ('out',)) _product_incoming_qty = _get_product_available_func(('confirmed','waiting','assigned'), ('in',)) _columns = { 'qty_available': fields.function(_product_qty_available, method=True, type='float', string='Real Stock'), 'virtual_available': fields.function(_product_virtual_available, method=True, type='float', string='Virtual Stock'), 'incoming_qty': fields.function(_product_incoming_qty, method=True, type='float', string='Incoming'), 'outgoing_qty': fields.function(_product_outgoing_qty, method=True, type='float', string='Outgoing'), } product_product()