1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
22 from datetime import datetime
23 from dateutil.relativedelta import relativedelta
25 from osv import fields, osv
26 from tools import config
27 from tools.translate import _
33 import decimal_precision as dp
36 #----------------------------------------------------------
38 #----------------------------------------------------------
39 class stock_incoterms(osv.osv):
40 _name = "stock.incoterms"
41 _description = "Incoterms"
43 'name': fields.char('Name', size=64, required=True,help="Incoterms are series of sales terms.They are used to divide transaction costs and responsibilities between buyer and seller and reflect state-of-the-art transportation practices."),
44 'code': fields.char('Code', size=3, required=True,help="Code for Incoterms"),
45 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the incoterms without removing it."),
48 'active': lambda *a: True,
54 #----------------------------------------------------------
56 #----------------------------------------------------------
57 class stock_location(osv.osv):
58 _name = "stock.location"
59 _description = "Location"
60 _parent_name = "location_id"
63 _order = 'parent_left'
65 def name_get(self, cr, uid, ids, context={}):
68 reads = self.read(cr, uid, ids, ['name','location_id'], context)
72 if context.get('full',False):
73 if record['location_id']:
74 name = record['location_id'][1]+' / '+name
75 res.append((record['id'], name))
77 res.append((record['id'], name))
80 def _complete_name(self, cr, uid, ids, name, args, context):
81 def _get_one_full_name(location, level=4):
82 if location.location_id:
83 parent_path = _get_one_full_name(location.location_id, level-1) + "/"
86 return parent_path + location.name
88 for m in self.browse(cr, uid, ids, context=context):
89 res[m.id] = _get_one_full_name(m)
92 def _product_qty_available(self, cr, uid, ids, field_names, arg, context={}):
95 res[id] = {}.fromkeys(field_names, 0.0)
96 if ('product_id' not in context) or not ids:
98 #location_ids = self.search(cr, uid, [('location_id', 'child_of', ids)])
100 context['location'] = [loc]
101 prod = self.pool.get('product.product').browse(cr, uid, context['product_id'], context)
102 if 'stock_real' in field_names:
103 res[loc]['stock_real'] = prod.qty_available
104 if 'stock_virtual' in field_names:
105 res[loc]['stock_virtual'] = prod.virtual_available
108 def product_detail(self, cr, uid, id, field, context={}):
112 field_to_read = 'virtual_available'
113 if field == 'stock_real_value':
114 field_to_read = 'qty_available'
115 cr.execute('select distinct product_id from stock_move where (location_id=%s) or (location_dest_id=%s)', (id, id))
116 result = cr.dictfetchall()
118 # Choose the right filed standard_price to read
119 # Take the user company
120 price_type_id=self.pool.get('res.users').browse(cr,uid,uid).company_id.property_valuation_price_type.id
121 pricetype=self.pool.get('product.price.type').browse(cr,uid,price_type_id)
123 c = (context or {}).copy()
125 product = self.pool.get('product.product').read(cr, uid, r['product_id'], [field_to_read], context=c)
126 # Compute the amount_unit in right currency
128 context['currency_id']=self.pool.get('res.users').browse(cr,uid,uid).company_id.currency_id.id
129 amount_unit=self.pool.get('product.product').browse(cr,uid,r['product_id']).price_get(pricetype.field, context)[r['product_id']]
131 final_value += (product[field_to_read] * amount_unit)
134 def _product_value(self, cr, uid, ids, field_names, arg, context={}):
137 result[id] = {}.fromkeys(field_names, 0.0)
138 for field_name in field_names:
140 ret_dict = self.product_detail(cr, uid, loc, field=field_name)
141 result[loc][field_name] = ret_dict
145 'name': fields.char('Location Name', size=64, required=True, translate=True),
146 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the stock location without removing it."),
147 'usage': fields.selection([('supplier', 'Supplier Location'), ('view', 'View'), ('internal', 'Internal Location'), ('customer', 'Customer Location'), ('inventory', 'Inventory'), ('procurement', 'Procurement'), ('production', 'Production')], 'Location Type', required=True),
148 'allocation_method': fields.selection([('fifo', 'FIFO'), ('lifo', 'LIFO'), ('nearest', 'Nearest')], 'Allocation Method', required=True),
150 'complete_name': fields.function(_complete_name, method=True, type='char', size=100, string="Location Name"),
152 'stock_real': fields.function(_product_qty_available, method=True, type='float', string='Real Stock', multi="stock"),
153 'stock_virtual': fields.function(_product_qty_available, method=True, type='float', string='Virtual Stock', multi="stock"),
155 'account_id': fields.many2one('account.account', string='Inventory Account', domain=[('type', '!=', 'view')]),
156 'location_id': fields.many2one('stock.location', 'Parent Location', select=True, ondelete='cascade'),
157 'child_ids': fields.one2many('stock.location', 'location_id', 'Contains'),
159 'chained_location_id': fields.many2one('stock.location', 'Chained Location If Fixed'),
160 'chained_location_type': fields.selection([('none', 'None'), ('customer', 'Customer'), ('fixed', 'Fixed Location')],
161 'Chained Location Type', required=True),
162 'chained_auto_packing': fields.selection(
163 [('auto', 'Automatic Move'), ('manual', 'Manual Operation'), ('transparent', 'Automatic No Step Added')],
166 help="This is used only if you select a chained location type.\n" \
167 "The 'Automatic Move' value will create a stock move after the current one that will be "\
168 "validated automatically. With 'Manual Operation', the stock move has to be validated "\
169 "by a worker. With 'Automatic No Step Added', the location is replaced in the original move."
171 'chained_delay': fields.integer('Chained lead time (days)'),
172 'address_id': fields.many2one('res.partner.address', 'Location Address'),
173 'icon': fields.selection(tools.icons, 'Icon', size=64),
175 'comment': fields.text('Additional Information'),
176 'posx': fields.integer('Corridor (X)'),
177 'posy': fields.integer('Shelves (Y)'),
178 'posz': fields.integer('Height (Z)'),
180 'parent_left': fields.integer('Left Parent', select=1),
181 'parent_right': fields.integer('Right Parent', select=1),
182 'stock_real_value': fields.function(_product_value, method=True, type='float', string='Real Stock Value', multi="stock"),
183 'stock_virtual_value': fields.function(_product_value, method=True, type='float', string='Virtual Stock Value', multi="stock"),
184 'company_id': fields.many2one('res.company', 'Company', required=True,select=1),
187 'active': lambda *a: 1,
188 'usage': lambda *a: 'internal',
189 'allocation_method': lambda *a: 'fifo',
190 'chained_location_type': lambda *a: 'none',
191 'chained_auto_packing': lambda *a: 'manual',
192 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.location', context=c),
193 'posx': lambda *a: 0,
194 'posy': lambda *a: 0,
195 'posz': lambda *a: 0,
196 'icon': lambda *a: False,
199 def chained_location_get(self, cr, uid, location, partner=None, product=None, context={}):
201 if location.chained_location_type == 'customer':
203 result = partner.property_stock_customer
204 elif location.chained_location_type == 'fixed':
205 result = location.chained_location_id
207 return result, location.chained_auto_packing, location.chained_delay
210 def picking_type_get(self, cr, uid, from_location, to_location, context={}):
212 if (from_location.usage=='internal') and (to_location and to_location.usage in ('customer', 'supplier')):
214 elif (from_location.usage in ('supplier', 'customer')) and (to_location.usage=='internal'):
218 def _product_get_all_report(self, cr, uid, ids, product_ids=False,
220 return self._product_get_report(cr, uid, ids, product_ids, context,
223 def _product_get_report(self, cr, uid, ids, product_ids=False,
224 context=None, recursive=False):
227 product_obj = self.pool.get('product.product')
228 # Take the user company and pricetype
229 price_type_id=self.pool.get('res.users').browse(cr,uid,uid).company_id.property_valuation_price_type.id
230 pricetype=self.pool.get('product.price.type').browse(cr,uid,price_type_id)
231 context['currency_id']=self.pool.get('res.users').browse(cr,uid,uid).company_id.currency_id.id
234 product_ids = product_obj.search(cr, uid, [])
236 products = product_obj.browse(cr, uid, product_ids, context=context)
239 for product in products:
240 products_by_uom.setdefault(product.uom_id.id, [])
241 products_by_uom[product.uom_id.id].append(product)
242 products_by_id.setdefault(product.id, [])
243 products_by_id[product.id] = product
246 result['product'] = []
250 for uom_id in products_by_uom.keys():
251 fnc = self._product_get
253 fnc = self._product_all_get
256 qty = fnc(cr, uid, id, [x.id for x in products_by_uom[uom_id]],
258 for product_id in qty.keys():
259 if not qty[product_id]:
261 product = products_by_id[product_id]
262 quantity_total += qty[product_id]
264 # Compute based on pricetype
265 # Choose the right filed standard_price to read
266 amount_unit=product.price_get(pricetype.field, context)[product.id]
267 price = qty[product_id] * amount_unit
268 # price = qty[product_id] * product.standard_price
271 result['product'].append({
272 'price': amount_unit,
273 'prod_name': product.name,
274 'code': product.default_code, # used by lot_overview_all report!
275 'variants': product.variants or '',
276 'uom': product.uom_id.name,
277 'prod_qty': qty[product_id],
278 'price_value': price,
280 result['total'] = quantity_total
281 result['total_price'] = total_price
284 def _product_get_multi_location(self, cr, uid, ids, product_ids=False, context={}, states=['done'], what=('in', 'out')):
285 product_obj = self.pool.get('product.product')
291 return product_obj.get_product_available(cr, uid, product_ids, context=context)
293 def _product_get(self, cr, uid, id, product_ids=False, context={}, states=['done']):
294 ids = id and [id] or []
295 return self._product_get_multi_location(cr, uid, ids, product_ids, context, states)
297 def _product_all_get(self, cr, uid, id, product_ids=False, context={}, states=['done']):
298 # build the list of ids of children of the location given by id
299 ids = id and [id] or []
300 location_ids = self.search(cr, uid, [('location_id', 'child_of', ids)])
301 return self._product_get_multi_location(cr, uid, location_ids, product_ids, context, states)
303 def _product_virtual_get(self, cr, uid, id, product_ids=False, context={}, states=['done']):
304 return self._product_all_get(cr, uid, id, product_ids, context, ['confirmed', 'waiting', 'assigned', 'done'])
308 # Improve this function
311 # [ (tracking_id, product_qty, location_id) ]
313 def _product_reserve(self, cr, uid, ids, product_id, product_qty, context={}):
316 for id in self.search(cr, uid, [('location_id', 'child_of', ids)]):
317 cr.execute("select product_uom,sum(product_qty) as product_qty from stock_move where location_dest_id=%s and location_id<>%s and product_id=%s and state='done' group by product_uom", (id, id, product_id))
318 results = cr.dictfetchall()
319 cr.execute("select product_uom,-sum(product_qty) as product_qty from stock_move where location_id=%s and location_dest_id<>%s and product_id=%s and state in ('done', 'assigned') group by product_uom", (id, id, product_id))
320 results += cr.dictfetchall()
325 amount = self.pool.get('product.uom')._compute_qty(cr, uid, r['product_uom'], r['product_qty'], context.get('uom', False))
334 if amount > min(total, product_qty):
335 amount = min(product_qty, total)
336 result.append((amount, id))
337 product_qty -= amount
339 if product_qty <= 0.0:
348 class stock_tracking(osv.osv):
349 _name = "stock.tracking"
350 _description = "Stock Tracking Lots"
353 salt = '31' * 8 + '3'
355 for sscc_part, salt_part in zip(sscc, salt):
356 sum += int(sscc_part) * int(salt_part)
357 return (10 - (sum % 10)) % 10
358 checksum = staticmethod(checksum)
360 def make_sscc(self, cr, uid, context={}):
361 sequence = self.pool.get('ir.sequence').get(cr, uid, 'stock.lot.tracking')
362 return sequence + str(self.checksum(sequence))
365 'name': fields.char('Tracking ID', size=64, required=True),
366 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the tracking lots without removing it."),
367 'serial': fields.char('Reference', size=64),
368 'move_ids': fields.one2many('stock.move', 'tracking_id', 'Moves Tracked'),
369 'date': fields.datetime('Created Date', required=True),
372 'active': lambda *a: 1,
374 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
377 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
382 ids = self.search(cr, user, [('serial', '=', name)]+ args, limit=limit, context=context)
383 ids += self.search(cr, user, [('name', operator, name)]+ args, limit=limit, context=context)
384 return self.name_get(cr, user, ids, context)
386 def name_get(self, cr, uid, ids, context={}):
389 res = [(r['id'], r['name']+' ['+(r['serial'] or '')+']') for r in self.read(cr, uid, ids, ['name', 'serial'], context)]
392 def unlink(self, cr, uid, ids, context=None):
393 raise osv.except_osv(_('Error'), _('You can not remove a lot line !'))
398 #----------------------------------------------------------
400 #----------------------------------------------------------
401 class stock_picking(osv.osv):
402 _name = "stock.picking"
403 _description = "Picking List"
405 def _set_maximum_date(self, cr, uid, ids, name, value, arg, context):
408 if isinstance(ids, (int, long)):
410 for pick in self.browse(cr, uid, ids, context):
411 sql_str = """update stock_move set
414 picking_id=%d """ % (value, pick.id)
417 sql_str += " and (date_planned='" + pick.max_date + "' or date_planned>'" + value + "')"
421 def _set_minimum_date(self, cr, uid, ids, name, value, arg, context):
424 if isinstance(ids, (int, long)):
426 for pick in self.browse(cr, uid, ids, context):
427 sql_str = """update stock_move set
430 picking_id=%s """ % (value, pick.id)
432 sql_str += " and (date_planned='" + pick.min_date + "' or date_planned<'" + value + "')"
436 def get_min_max_date(self, cr, uid, ids, field_name, arg, context={}):
439 res[id] = {'min_date': False, 'max_date': False}
451 picking_id""",(ids,))
452 for pick, dt1, dt2 in cr.fetchall():
453 res[pick]['min_date'] = dt1
454 res[pick]['max_date'] = dt2
457 def create(self, cr, user, vals, context=None):
458 if ('name' not in vals) or (vals.get('name')=='/'):
459 vals['name'] = self.pool.get('ir.sequence').get(cr, user, 'stock.picking')
461 return super(stock_picking, self).create(cr, user, vals, context)
464 'name': fields.char('Reference', size=64, select=True),
465 'origin': fields.char('Origin', size=64, help="Reference of the document that produced this picking."),
466 'backorder_id': fields.many2one('stock.picking', 'Back Order', help="If the picking is splitted then the picking id in available state of move for this picking is stored in Backorder."),
467 'type': fields.selection([('out', 'Sending Goods'), ('in', 'Getting Goods'), ('internal', 'Internal'), ('delivery', 'Delivery')], 'Shipping Type', required=True, select=True, help="Shipping type specify, goods coming in or going out."),
468 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the picking without removing it."),
469 'note': fields.text('Notes'),
471 'location_id': fields.many2one('stock.location', 'Location', help="Keep empty if you produce at the location where the finished products are needed." \
472 "Set a location if you produce at a fixed location. This can be a partner location " \
473 "if you subcontract the manufacturing operations."),
474 'location_dest_id': fields.many2one('stock.location', 'Dest. Location',help="Location where the system will stock the finished products."),
475 'move_type': fields.selection([('direct', 'Direct Delivery'), ('one', 'All at once')], 'Delivery Method', required=True, help="It specifies goods to be delivered all at once or by direct delivery"),
476 'state': fields.selection([
479 ('confirmed', 'Confirmed'),
480 ('assigned', 'Available'),
482 ('cancel', 'Cancelled'),
483 ], 'State', readonly=True, select=True,
484 help=' * The \'Draft\' state is used when a user is encoding a new and unconfirmed picking. \
485 \n* The \'Confirmed\' state is used for stock movement to do with unavailable products. \
486 \n* The \'Available\' state is set automatically when the products are ready to be moved.\
487 \n* The \'Waiting\' state is used in MTO moves when a movement is waiting for another one.'),
488 'min_date': fields.function(get_min_max_date, fnct_inv=_set_minimum_date, multi="min_max_date",
489 method=True, store=True, type='datetime', string='Expected Date', select=1, help="Expected date for Picking. Default it takes current date"),
490 'date': fields.datetime('Order Date', help="Date of Order"),
491 'date_done': fields.datetime('Date Done', help="Date of completion"),
492 'max_date': fields.function(get_min_max_date, fnct_inv=_set_maximum_date, multi="min_max_date",
493 method=True, store=True, type='datetime', string='Max. Expected Date', select=2),
494 'move_lines': fields.one2many('stock.move', 'picking_id', 'Entry lines', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}),
495 'auto_picking': fields.boolean('Auto-Picking'),
496 'address_id': fields.many2one('res.partner.address', 'Partner', help="Address of partner"),
497 'invoice_state': fields.selection([
498 ("invoiced", "Invoiced"),
499 ("2binvoiced", "To Be Invoiced"),
500 ("none", "Not from Picking")], "Invoice Status",
501 select=True, required=True, readonly=True, states={'draft': [('readonly', False)]}),
502 'company_id': fields.many2one('res.company', 'Company', required=True,select=1),
505 'name': lambda self, cr, uid, context: '/',
506 'active': lambda *a: 1,
507 'state': lambda *a: 'draft',
508 'move_type': lambda *a: 'direct',
509 'type': lambda *a: 'in',
510 'invoice_state': lambda *a: 'none',
511 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
512 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock_picking', context=c)
515 def copy(self, cr, uid, id, default=None, context={}):
518 default = default.copy()
519 if not default.get('name',False):
520 default['name'] = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking')
521 return super(stock_picking, self).copy(cr, uid, id, default, context)
523 def onchange_partner_in(self, cr, uid, context, partner_id=None):
526 def action_explode(self, cr, uid, moves, context={}):
529 def action_confirm(self, cr, uid, ids, context={}):
530 self.write(cr, uid, ids, {'state': 'confirmed'})
532 for picking in self.browse(cr, uid, ids):
533 for r in picking.move_lines:
534 if r.state == 'draft':
536 todo = self.action_explode(cr, uid, todo, context)
538 self.pool.get('stock.move').action_confirm(cr, uid, todo, context)
541 def test_auto_picking(self, cr, uid, ids):
542 # TODO: Check locations to see if in the same location ?
545 # def button_confirm(self, cr, uid, ids, *args):
547 # wf_service = netsvc.LocalService("workflow")
548 # wf_service.trg_validate(uid, 'stock.picking', id, 'button_confirm', cr)
549 # self.force_assign(cr, uid, ids, *args)
552 def action_assign(self, cr, uid, ids, *args):
553 for pick in self.browse(cr, uid, ids):
554 move_ids = [x.id for x in pick.move_lines if x.state == 'confirmed']
556 raise osv.except_osv(_('Warning !'),_('Not Available. Moves are not confirmed.'))
557 self.pool.get('stock.move').action_assign(cr, uid, move_ids)
560 def force_assign(self, cr, uid, ids, *args):
561 wf_service = netsvc.LocalService("workflow")
562 for pick in self.browse(cr, uid, ids):
563 move_ids = [x.id for x in pick.move_lines if x.state in ['confirmed','waiting']]
564 # move_ids = [x.id for x in pick.move_lines]
565 self.pool.get('stock.move').force_assign(cr, uid, move_ids)
566 wf_service.trg_write(uid, 'stock.picking', pick.id, cr)
569 def draft_force_assign(self, cr, uid, ids, *args):
570 wf_service = netsvc.LocalService("workflow")
571 for pick in self.browse(cr, uid, ids):
572 wf_service.trg_validate(uid, 'stock.picking', pick.id,
573 'button_confirm', cr)
574 #move_ids = [x.id for x in pick.move_lines]
575 #self.pool.get('stock.move').force_assign(cr, uid, move_ids)
576 #wf_service.trg_write(uid, 'stock.picking', pick.id, cr)
579 def draft_validate(self, cr, uid, ids, *args):
580 wf_service = netsvc.LocalService("workflow")
581 self.draft_force_assign(cr, uid, ids)
582 for pick in self.browse(cr, uid, ids):
583 move_ids = [x.id for x in pick.move_lines]
584 self.pool.get('stock.move').force_assign(cr, uid, move_ids)
585 wf_service.trg_write(uid, 'stock.picking', pick.id, cr)
587 self.action_move(cr, uid, [pick.id])
588 wf_service.trg_validate(uid, 'stock.picking', pick.id, 'button_done', cr)
591 def cancel_assign(self, cr, uid, ids, *args):
592 wf_service = netsvc.LocalService("workflow")
593 for pick in self.browse(cr, uid, ids):
594 move_ids = [x.id for x in pick.move_lines]
595 self.pool.get('stock.move').cancel_assign(cr, uid, move_ids)
596 wf_service.trg_write(uid, 'stock.picking', pick.id, cr)
599 def action_assign_wkf(self, cr, uid, ids):
600 self.write(cr, uid, ids, {'state': 'assigned'})
603 def test_finnished(self, cr, uid, ids):
604 move_ids = self.pool.get('stock.move').search(cr, uid, [('picking_id', 'in', ids)])
605 for move in self.pool.get('stock.move').browse(cr, uid, move_ids):
606 if move.state not in ('done', 'cancel'):
607 if move.product_qty != 0.0:
610 move.write(cr, uid, [move.id], {'state': 'done'})
613 def test_assigned(self, cr, uid, ids):
615 for pick in self.browse(cr, uid, ids):
617 for move in pick.move_lines:
618 if (move.state in ('confirmed', 'draft')) and (mt=='one'):
620 if (mt=='direct') and (move.state=='assigned') and (move.product_qty):
622 ok = ok and (move.state in ('cancel', 'done', 'assigned'))
625 def action_cancel(self, cr, uid, ids, context={}):
626 for pick in self.browse(cr, uid, ids):
627 ids2 = [move.id for move in pick.move_lines]
628 self.pool.get('stock.move').action_cancel(cr, uid, ids2, context)
629 self.write(cr, uid, ids, {'state': 'cancel', 'invoice_state': 'none'})
633 # TODO: change and create a move if not parents
635 def action_done(self, cr, uid, ids, context=None):
636 self.write(cr, uid, ids, {'state': 'done', 'date_done': time.strftime('%Y-%m-%d %H:%M:%S')})
639 def action_move(self, cr, uid, ids, context={}):
640 for pick in self.browse(cr, uid, ids):
642 for move in pick.move_lines:
643 if move.state == 'assigned':
647 self.pool.get('stock.move').action_done(cr, uid, todo,
651 def get_currency_id(self, cr, uid, picking):
654 def _get_payment_term(self, cr, uid, picking):
655 '''Return {'contact': address, 'invoice': address} for invoice'''
656 partner_obj = self.pool.get('res.partner')
657 partner = picking.address_id.partner_id
658 return partner.property_payment_term and partner.property_payment_term.id or False
660 def _get_address_invoice(self, cr, uid, picking):
661 '''Return {'contact': address, 'invoice': address} for invoice'''
662 partner_obj = self.pool.get('res.partner')
663 partner = picking.address_id.partner_id
665 return partner_obj.address_get(cr, uid, [partner.id],
666 ['contact', 'invoice'])
668 def _get_comment_invoice(self, cr, uid, picking):
669 '''Return comment string for invoice'''
670 return picking.note or ''
672 def _get_price_unit_invoice(self, cr, uid, move_line, type, context=None):
673 '''Return the price unit for the move line'''
677 if type in ('in_invoice', 'in_refund'):
678 # Take the user company and pricetype
679 price_type_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.property_valuation_price_type.id
680 pricetype = self.pool.get('product.price.type').browse(cr, uid, price_type_id)
681 context['currency_id'] = move_line.company_id.currency_id.id
683 amount_unit = move_line.product_id.price_get(pricetype.field, context)[move_line.product_id.id]
686 return move_line.product_id.list_price
688 def _get_discount_invoice(self, cr, uid, move_line):
689 '''Return the discount for the move line'''
692 def _get_taxes_invoice(self, cr, uid, move_line, type):
693 '''Return taxes ids for the move line'''
694 if type in ('in_invoice', 'in_refund'):
695 taxes = move_line.product_id.supplier_taxes_id
697 taxes = move_line.product_id.taxes_id
699 if move_line.picking_id and move_line.picking_id.address_id and move_line.picking_id.address_id.partner_id:
700 return self.pool.get('account.fiscal.position').map_tax(
703 move_line.picking_id.address_id.partner_id.property_account_position,
707 return map(lambda x: x.id, taxes)
709 def _get_account_analytic_invoice(self, cr, uid, picking, move_line):
712 def _invoice_line_hook(self, cr, uid, move_line, invoice_line_id):
713 '''Call after the creation of the invoice line'''
716 def _invoice_hook(self, cr, uid, picking, invoice_id):
717 '''Call after the creation of the invoice'''
720 def action_invoice_create(self, cr, uid, ids, journal_id=False,
721 group=False, type='out_invoice', context=None):
722 '''Return ids of created invoices for the pickings'''
726 invoice_obj = self.pool.get('account.invoice')
727 invoice_line_obj = self.pool.get('account.invoice.line')
731 for picking in self.browse(cr, uid, ids, context=context):
732 if picking.invoice_state != '2binvoiced':
734 payment_term_id = False
735 partner = picking.address_id and picking.address_id.partner_id
737 raise osv.except_osv(_('Error, no partner !'),
738 _('Please put a partner on the picking list if you want to generate invoice.'))
740 if type in ('out_invoice', 'out_refund'):
741 account_id = partner.property_account_receivable.id
742 payment_term_id = self._get_payment_term(cr, uid, picking)
744 account_id = partner.property_account_payable.id
746 address_contact_id, address_invoice_id = \
747 self._get_address_invoice(cr, uid, picking).values()
749 comment = self._get_comment_invoice(cr, uid, picking)
750 if group and partner.id in invoices_group:
751 invoice_id = invoices_group[partner.id]
752 invoice = invoice_obj.browse(cr, uid, invoice_id)
754 'name': (invoice.name or '') + ', ' + (picking.name or ''),
755 'origin': (invoice.origin or '') + ', ' + (picking.name or '') + (picking.origin and (':' + picking.origin) or ''),
756 'comment': (comment and (invoice.comment and invoice.comment+"\n"+comment or comment)) or (invoice.comment and invoice.comment or ''),
757 'date_invoice':context.get('date_inv',False)
759 invoice_obj.write(cr, uid, [invoice_id], invoice_vals, context=context)
762 'name': picking.name,
763 'origin': (picking.name or '') + (picking.origin and (':' + picking.origin) or ''),
765 'account_id': account_id,
766 'partner_id': partner.id,
767 'address_invoice_id': address_invoice_id,
768 'address_contact_id': address_contact_id,
770 'payment_term': payment_term_id,
771 'fiscal_position': partner.property_account_position.id,
772 'date_invoice': context.get('date_inv',False),
773 'company_id': picking.company_id.id,
775 cur_id = self.get_currency_id(cr, uid, picking)
777 invoice_vals['currency_id'] = cur_id
779 invoice_vals['journal_id'] = journal_id
780 invoice_id = invoice_obj.create(cr, uid, invoice_vals,
782 invoices_group[partner.id] = invoice_id
783 res[picking.id] = invoice_id
784 for move_line in picking.move_lines:
785 origin = move_line.picking_id.name
786 if move_line.picking_id.origin:
787 origin += ':' + move_line.picking_id.origin
789 name = (picking.name or '') + '-' + move_line.name
791 name = move_line.name
793 if type in ('out_invoice', 'out_refund'):
794 account_id = move_line.product_id.product_tmpl_id.\
795 property_account_income.id
797 account_id = move_line.product_id.categ_id.\
798 property_account_income_categ.id
800 account_id = move_line.product_id.product_tmpl_id.\
801 property_account_expense.id
803 account_id = move_line.product_id.categ_id.\
804 property_account_expense_categ.id
806 price_unit = self._get_price_unit_invoice(cr, uid,
808 discount = self._get_discount_invoice(cr, uid, move_line)
809 tax_ids = self._get_taxes_invoice(cr, uid, move_line, type)
810 account_analytic_id = self._get_account_analytic_invoice(cr, uid, picking, move_line)
812 #set UoS if it's a sale and the picking doesn't have one
813 uos_id = move_line.product_uos and move_line.product_uos.id or False
814 if not uos_id and type in ('out_invoice', 'out_refund'):
815 uos_id = move_line.product_uom.id
817 account_id = self.pool.get('account.fiscal.position').map_account(cr, uid, partner.property_account_position, account_id)
819 if move_line.sale_line_id:
820 notes = move_line.sale_line_id.notes
821 elif move_line.purchase_line_id:
822 notes = move_line.purchase_line_id.notes
824 invoice_line_id = invoice_line_obj.create(cr, uid, {
827 'invoice_id': invoice_id,
829 'product_id': move_line.product_id.id,
830 'account_id': account_id,
831 'price_unit': price_unit,
832 'discount': discount,
833 'quantity': move_line.product_uos_qty or move_line.product_qty,
834 'invoice_line_tax_id': [(6, 0, tax_ids)],
835 'account_analytic_id': account_analytic_id,
838 self._invoice_line_hook(cr, uid, move_line, invoice_line_id)
840 invoice_obj.button_compute(cr, uid, [invoice_id], context=context,
841 set_total=(type in ('in_invoice', 'in_refund')))
842 self.write(cr, uid, [picking.id], {
843 'invoice_state': 'invoiced',
845 self._invoice_hook(cr, uid, picking, invoice_id)
846 self.write(cr, uid, res.keys(), {
847 'invoice_state': 'invoiced',
851 def test_cancel(self, cr, uid, ids, context={}):
852 for pick in self.browse(cr, uid, ids, context=context):
853 if not pick.move_lines:
855 for move in pick.move_lines:
856 if move.state not in ('cancel',):
860 def unlink(self, cr, uid, ids, context=None):
861 for pick in self.browse(cr, uid, ids, context=context):
862 if pick.state in ['done','cancel']:
863 raise osv.except_osv(_('Error'), _('You cannot remove the picking which is in %s state !')%(pick.state,))
864 elif pick.state in ['confirmed','assigned']:
865 ids2 = [move.id for move in pick.move_lines]
866 context.update({'call_unlink':True})
867 self.pool.get('stock.move').action_cancel(cr, uid, ids2, context)
870 return super(stock_picking, self).unlink(cr, uid, ids, context=context)
872 def do_partial(self, cr, uid, ids, partial_datas, context={}):
874 @ partial_datas : dict. contain details of partial picking
875 like partner_id, address_id, delivery_date, delivery moves with product_id, product_qty, uom
878 move_obj = self.pool.get('stock.move')
879 product_obj = self.pool.get('product.product')
880 currency_obj = self.pool.get('res.currency')
881 users_obj = self.pool.get('res.users')
882 uom_obj = self.pool.get('product.uom')
883 price_type_obj = self.pool.get('product.price.type')
884 sequence_obj = self.pool.get('ir.sequence')
885 wf_service = netsvc.LocalService("workflow")
886 partner_id = partial_datas.get('partner_id', False)
887 address_id = partial_datas.get('address_id', False)
888 delivery_date = partial_datas.get('delivery_date', False)
889 for pick in self.browse(cr, uid, ids, context=context):
893 complete, too_many, too_few = [], [], []
894 move_product_qty = {}
895 for move in pick.move_lines:
896 if move.state in ('done', 'cancel'):
898 partial_data = partial_datas.get('move%s'%(move.id), False)
899 assert partial_data, _('Do not Found Partial data of Stock Move Line :%s' %(move.id))
900 product_qty = partial_data.get('product_qty',0.0)
901 move_product_qty[move.id] = product_qty
902 product_uom = partial_data.get('product_uom',False)
903 product_price = partial_data.get('product_price',0.0)
904 product_currency = partial_data.get('product_currency',False)
905 if move.product_qty == product_qty:
906 complete.append(move)
907 elif move.product_qty > product_qty:
910 too_many.append(move)
912 # Average price computation
913 if (pick.type == 'in') and (move.product_id.cost_method == 'average'):
914 product = product_obj.browse(cr, uid, move.product_id.id)
915 user = users_obj.browse(cr, uid, uid)
916 context['currency_id'] = move.company_id.currency_id.id
917 qty = uom_obj._compute_qty(cr, uid, product_uom, product_qty, product.uom_id.id)
919 if user.company_id.property_valuation_price_type:
920 pricetype = price_type_obj.browse(cr, uid, user.company_id.property_valuation_price_type.id)
921 if pricetype and qty > 0:
922 new_price = currency_obj.compute(cr, uid, product_currency,
923 user.company_id.currency_id.id, product_price)
924 new_price = uom_obj._compute_price(cr, uid, product_uom, new_price,
926 if product.qty_available <= 0:
927 new_std_price = new_price
929 # Get the standard price
930 amount_unit = product.price_get(pricetype.field, context)[product.id]
931 new_std_price = ((amount_unit * product.qty_available)\
932 + (new_price * qty))/(product.qty_available + qty)
934 # Write the field according to price type field
935 product_obj.write(cr, uid, [product.id],
936 {pricetype.field: new_std_price})
937 move_obj.write(cr, uid, [move.id], {'price_unit': new_price})
941 product_qty = move_product_qty[move.id]
944 new_picking = self.copy(cr, uid, pick.id,
946 'name': sequence_obj.get(cr, uid, 'stock.picking.%s'%(pick.type)),
952 new_obj = move_obj.copy(cr, uid, move.id,
954 'product_qty' : product_qty,
955 'product_uos_qty': product_qty, #TODO: put correct uos_qty
956 'picking_id' : new_picking,
958 'move_dest_id': False,
959 'price_unit': move.price_unit,
962 move_obj.write(cr, uid, [move.id],
964 'product_qty' : move.product_qty - product_qty,
965 'product_uos_qty':move.product_qty - product_qty, #TODO: put correct uos_qty
970 move_obj.write(cr, uid, [c.id for c in complete], {'picking_id': new_picking})
971 for move in too_many:
972 product_qty = move_product_qty[move.id]
973 move_obj.write(cr, uid, [move.id],
975 'product_qty' : product_qty,
976 'product_uos_qty': product_qty, #TODO: put correct uos_qty
977 'picking_id': new_picking,
980 for move in too_many:
981 product_qty = move_product_qty[move.id]
982 move_obj.write(cr, uid, [move.id],
984 'product_qty': product_qty,
985 'product_uos_qty': product_qty #TODO: put correct uos_qty
988 # At first we confirm the new picking (if necessary)
990 wf_service.trg_validate(uid, 'stock.picking', new_picking, 'button_confirm', cr)
991 # Then we finish the good picking
993 self.write(cr, uid, [pick.id], {'backorder_id': new_picking})
994 self.action_move(cr, uid, [new_picking])
995 wf_service.trg_validate(uid, 'stock.picking', new_picking, 'button_done', cr)
996 wf_service.trg_write(uid, 'stock.picking', pick.id, cr)
997 delivered_pack_id = new_picking
999 self.action_move(cr, uid, [pick.id])
1000 wf_service.trg_validate(uid, 'stock.picking', pick.id, 'button_done', cr)
1001 delivered_pack_id = pick.id
1003 delivered_pack = self.browse(cr, uid, delivered_pack_id, context=context)
1004 res[pick.id] = {'delivered_picking': delivered_pack.id or False}
1010 class stock_production_lot(osv.osv):
1011 def name_get(self, cr, uid, ids, context={}):
1014 reads = self.read(cr, uid, ids, ['name', 'prefix', 'ref'], context)
1016 for record in reads:
1017 name = record['name']
1018 prefix = record['prefix']
1020 name = prefix + '/' + name
1022 name = '%s [%s]' % (name, record['ref'])
1023 res.append((record['id'], name))
1026 _name = 'stock.production.lot'
1027 _description = 'Production lot'
1029 def _get_stock(self, cr, uid, ids, field_name, arg, context={}):
1030 if 'location_id' not in context:
1031 locations = self.pool.get('stock.location').search(cr, uid, [('usage', '=', 'internal')], context=context)
1033 locations = context['location_id'] and [context['location_id']] or []
1035 if isinstance(ids, (int, long)):
1038 res = {}.fromkeys(ids, 0.0)
1040 cr.execute('''select
1044 stock_report_prodlots
1046 location_id =ANY(%s) and prodlot_id =ANY(%s) group by prodlot_id''',(locations,ids,))
1047 res.update(dict(cr.fetchall()))
1050 def _stock_search(self, cr, uid, obj, name, args, context):
1051 locations = self.pool.get('stock.location').search(cr, uid, [('usage', '=', 'internal')])
1052 cr.execute('''select
1056 stock_report_prodlots
1058 location_id =ANY(%s) group by prodlot_id
1059 having sum(name) '''+ str(args[0][1]) + str(args[0][2]),(locations,))
1061 ids = [('id', 'in', map(lambda x: x[0], res))]
1065 'name': fields.char('Serial', size=64, required=True),
1066 'ref': fields.char('Internal Reference', size=256),
1067 'prefix': fields.char('Prefix', size=64),
1068 'product_id': fields.many2one('product.product', 'Product', required=True),
1069 'date': fields.datetime('Created Date', required=True),
1070 'stock_available': fields.function(_get_stock, fnct_search=_stock_search, method=True, type="float", string="Available", select="2"),
1071 'revisions': fields.one2many('stock.production.lot.revision', 'lot_id', 'Revisions'),
1072 'company_id': fields.many2one('res.company','Company',select=1),
1075 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
1076 'name': lambda x, y, z, c: x.pool.get('ir.sequence').get(y, z, 'stock.lot.serial'),
1077 'product_id': lambda x, y, z, c: c.get('product_id', False),
1079 _sql_constraints = [
1080 ('name_ref_uniq', 'unique (name, ref)', 'The serial/ref must be unique !'),
1083 stock_production_lot()
1085 class stock_production_lot_revision(osv.osv):
1086 _name = 'stock.production.lot.revision'
1087 _description = 'Production lot revisions'
1089 'name': fields.char('Revision Name', size=64, required=True),
1090 'description': fields.text('Description'),
1091 'date': fields.date('Revision Date'),
1092 'indice': fields.char('Revision', size=16),
1093 'author_id': fields.many2one('res.users', 'Author'),
1094 'lot_id': fields.many2one('stock.production.lot', 'Production lot', select=True, ondelete='cascade'),
1095 'company_id': fields.related('lot_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
1099 'author_id': lambda x, y, z, c: z,
1100 'date': lambda *a: time.strftime('%Y-%m-%d'),
1103 stock_production_lot_revision()
1107 # location_dest_id is only used for predicting futur stocks
1109 class stock_move(osv.osv):
1110 def _getSSCC(self, cr, uid, context={}):
1111 cr.execute('select id from stock_tracking where create_uid=%s order by id desc limit 1', (uid,))
1113 return (res and res[0]) or False
1114 _name = "stock.move"
1115 _description = "Stock Move"
1117 def name_get(self, cr, uid, ids, context={}):
1119 for line in self.browse(cr, uid, ids, context):
1120 res.append((line.id, (line.product_id.code or '/')+': '+line.location_id.name+' > '+line.location_dest_id.name))
1123 def _check_tracking(self, cr, uid, ids):
1124 for move in self.browse(cr, uid, ids):
1125 if not move.prodlot_id and \
1126 (move.state == 'done' and \
1128 (move.product_id.track_production and move.location_id.usage=='production') or \
1129 (move.product_id.track_production and move.location_dest_id.usage=='production') or \
1130 (move.product_id.track_incoming and move.location_id.usage in ('supplier','internal')) or \
1131 (move.product_id.track_outgoing and move.location_dest_id.usage in ('customer','internal')) \
1136 def _check_product_lot(self, cr, uid, ids):
1137 for move in self.browse(cr, uid, ids):
1138 if move.prodlot_id and (move.prodlot_id.product_id.id != move.product_id.id):
1143 'name': fields.char('Name', size=64, required=True, select=True),
1144 'priority': fields.selection([('0', 'Not urgent'), ('1', 'Urgent')], 'Priority'),
1146 'date': fields.datetime('Created Date'),
1147 'date_planned': fields.datetime('Date', required=True, help="Scheduled date for the movement of the products or real date if the move is done."),
1149 'product_id': fields.many2one('product.product', 'Product', required=True, select=True),
1151 'product_qty': fields.float('Quantity', required=True),
1152 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
1153 'product_uos_qty': fields.float('Quantity (UOS)'),
1154 'product_uos': fields.many2one('product.uom', 'Product UOS'),
1155 'product_packaging': fields.many2one('product.packaging', 'Packaging', help="It specifies attributes of packaging like type, quantity of packaging,etc."),
1157 'location_id': fields.many2one('stock.location', 'Source Location', required=True, select=True, help="Sets a location if you produce at a fixed location. This can be a partner location if you subcontract the manufacturing operations."),
1158 'location_dest_id': fields.many2one('stock.location', 'Dest. Location', required=True, select=True, help="Location where the system will stock the finished products."),
1159 'address_id': fields.many2one('res.partner.address', 'Dest. Address', help="Address where goods are to be delivered"),
1161 'prodlot_id': fields.many2one('stock.production.lot', 'Production Lot', help="Production lot is used to put a serial number on the production"),
1162 'tracking_id': fields.many2one('stock.tracking', 'Tracking Lot', select=True, help="Tracking lot is the code that will be put on the logistical unit/pallet"),
1163 # 'lot_id': fields.many2one('stock.lot', 'Consumer lot', select=True, readonly=True),
1165 'auto_validate': fields.boolean('Auto Validate'),
1167 'move_dest_id': fields.many2one('stock.move', 'Dest. Move'),
1168 'move_history_ids': fields.many2many('stock.move', 'stock_move_history_ids', 'parent_id', 'child_id', 'Move History'),
1169 'move_history_ids2': fields.many2many('stock.move', 'stock_move_history_ids', 'child_id', 'parent_id', 'Move History'),
1170 'picking_id': fields.many2one('stock.picking', 'Picking List', select=True),
1172 'note': fields.text('Notes'),
1174 'state': fields.selection([('draft', 'Draft'), ('waiting', 'Waiting'), ('confirmed', 'Confirmed'), ('assigned', 'Available'), ('done', 'Done'), ('cancel', 'Cancelled')], 'State', readonly=True, select=True,
1175 help='When the stock move is created it is in the \'Draft\' state.\n After that it is set to \'Confirmed\' state.\n If stock is available state is set to \'Avaiable\'.\n When the picking it done the state is \'Done\'.\
1176 \nThe state is \'Waiting\' if the move is waiting for another one.'),
1177 'price_unit': fields.float('Unit Price',
1178 digits_compute= dp.get_precision('Account')),
1179 'company_id': fields.many2one('res.company', 'Company', required=True,select=1),
1180 'partner_id': fields.related('picking_id','address_id','partner_id',type='many2one', relation="res.partner", string="Partner"),
1181 'backorder_id': fields.related('picking_id','backorder_id',type='many2one', relation="stock.picking", string="Back Orders"),
1182 'origin': fields.related('picking_id','origin',type='char', size=64, relation="stock.picking", string="Origin"),
1183 'move_stock_return_history': fields.many2many('stock.move', 'stock_move_return_history', 'move_id', 'return_move_id', 'Move Return History',readonly=True),
1184 'scraped': fields.boolean('Scraped'),
1188 'You must assign a production lot for this product',
1190 (_check_product_lot,
1191 'You try to assign a lot which is not from the same product',
1194 def _default_location_destination(self, cr, uid, context={}):
1195 if context.get('move_line', []):
1196 if context['move_line'][0]:
1197 if isinstance(context['move_line'][0], (tuple, list)):
1198 return context['move_line'][0][2] and context['move_line'][0][2]['location_dest_id'] or False
1200 move_list = self.pool.get('stock.move').read(cr, uid, context['move_line'][0], ['location_dest_id'])
1201 return move_list and move_list['location_dest_id'][0] or False
1202 if context.get('address_out_id', False):
1203 return self.pool.get('res.partner.address').browse(cr, uid, context['address_out_id'], context).partner_id.property_stock_customer.id
1206 def _default_location_source(self, cr, uid, context={}):
1207 if context.get('move_line', []):
1209 return context['move_line'][0][2]['location_id']
1212 if context.get('address_in_id', False):
1213 return self.pool.get('res.partner.address').browse(cr, uid, context['address_in_id'], context).partner_id.property_stock_supplier.id
1217 'location_id': _default_location_source,
1218 'location_dest_id': _default_location_destination,
1219 'state': lambda *a: 'draft',
1220 'priority': lambda *a: '1',
1221 'scraped' : lambda *a:False,
1222 'product_qty': lambda *a: 1.0,
1223 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
1224 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
1225 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.move', context=c)
1228 def copy(self, cr, uid, id, default=None, context={}):
1231 default = default.copy()
1232 default['move_stock_return_history'] = []
1233 return super(stock_move, self).copy(cr, uid, id, default, context)
1235 def create(self, cr, user, vals, context=None):
1236 if vals.get('move_stock_return_history',False):
1237 vals['move_stock_return_history'] = []
1238 return super(stock_move, self).create(cr, user, vals, context)
1240 def _auto_init(self, cursor, context):
1241 res = super(stock_move, self)._auto_init(cursor, context)
1242 cursor.execute('SELECT indexname \
1244 WHERE indexname = \'stock_move_location_id_location_dest_id_product_id_state\'')
1245 if not cursor.fetchone():
1246 cursor.execute('CREATE INDEX stock_move_location_id_location_dest_id_product_id_state \
1247 ON stock_move (location_id, location_dest_id, product_id, state)')
1251 def onchange_lot_id(self, cr, uid, ids, prodlot_id=False, product_qty=False, loc_id=False, product_id=False, context=None):
1252 if not prodlot_id or not loc_id:
1254 ctx = context and context.copy() or {}
1255 ctx['location_id'] = loc_id
1256 prodlot = self.pool.get('stock.production.lot').browse(cr, uid, prodlot_id, ctx)
1257 location = self.pool.get('stock.location').browse(cr, uid, loc_id)
1259 if (location.usage == 'internal') and (product_qty > (prodlot.stock_available or 0.0)):
1261 'title': 'Bad Lot Assignation !',
1262 'message': 'You are moving %.2f products but only %.2f available in this lot.' % (product_qty, prodlot.stock_available or 0.0)
1264 return {'warning': warning}
1266 def onchange_quantity(self, cr, uid, ids, product_id, product_qty, product_uom, product_uos):
1268 'product_uos_qty': 0.00
1271 if (not product_id) or (product_qty <=0.0):
1272 return {'value': result}
1274 product_obj = self.pool.get('product.product')
1275 uos_coeff = product_obj.read(cr, uid, product_id, ['uos_coeff'])
1277 if product_uos and product_uom and (product_uom != product_uos):
1278 result['product_uos_qty'] = product_qty * uos_coeff['uos_coeff']
1280 result['product_uos_qty'] = product_qty
1282 return {'value': result}
1284 def onchange_product_id(self, cr, uid, ids, prod_id=False, loc_id=False, loc_dest_id=False, address_id=False):
1289 addr_rec = self.pool.get('res.partner.address').browse(cr, uid, address_id)
1291 lang = addr_rec.partner_id and addr_rec.partner_id.lang or False
1292 ctx = {'lang': lang}
1294 product = self.pool.get('product.product').browse(cr, uid, [prod_id], context=ctx)[0]
1295 uos_id = product.uos_id and product.uos_id.id or False
1297 'product_uom': product.uom_id.id,
1298 'product_uos': uos_id,
1299 'product_qty': 1.00,
1300 'product_uos_qty' : self.pool.get('stock.move').onchange_quantity(cr, uid, ids, prod_id, 1.00, product.uom_id.id, uos_id)['value']['product_uos_qty']
1303 result['name'] = product.partner_ref
1305 result['location_id'] = loc_id
1307 result['location_dest_id'] = loc_dest_id
1308 return {'value': result}
1310 def _chain_compute(self, cr, uid, moves, context={}):
1313 dest = self.pool.get('stock.location').chained_location_get(
1317 m.picking_id and m.picking_id.address_id and m.picking_id.address_id.partner_id,
1322 if dest[1] == 'transparent':
1323 self.write(cr, uid, [m.id], {
1324 'date_planned': (datetime.strptime(m.date_planned, '%Y-%m-%d %H:%M:%S') + \
1325 relativedelta(days=dest[2] or 0)).strftime('%Y-%m-%d'),
1326 'location_dest_id': dest[0].id})
1328 result.setdefault(m.picking_id, [])
1329 result[m.picking_id].append( (m, dest) )
1332 def action_confirm(self, cr, uid, ids, context={}):
1333 # ids = map(lambda m: m.id, moves)
1334 moves = self.browse(cr, uid, ids)
1335 self.write(cr, uid, ids, {'state': 'confirmed'})
1338 def create_chained_picking(self, cr, uid, moves, context):
1340 for picking, todo in self._chain_compute(cr, uid, moves, context).items():
1341 ptype = self.pool.get('stock.location').picking_type_get(cr, uid, todo[0][0].location_dest_id, todo[0][1][0])
1342 pickid = self.pool.get('stock.picking').create(cr, uid, {
1343 'name': picking.name,
1344 'origin': str(picking.origin or ''),
1346 'note': picking.note,
1347 'move_type': picking.move_type,
1348 'auto_picking': todo[0][1][1] == 'auto',
1349 'address_id': picking.address_id.id,
1350 'invoice_state': 'none'
1352 for move, (loc, auto, delay) in todo:
1353 # Is it smart to copy ? May be it's better to recreate ?
1354 new_id = self.pool.get('stock.move').copy(cr, uid, move.id, {
1355 'location_id': move.location_dest_id.id,
1356 'location_dest_id': loc.id,
1357 'date_moved': time.strftime('%Y-%m-%d'),
1358 'picking_id': pickid,
1360 'move_history_ids': [],
1361 'date_planned': (datetime.strptime(move.date_planned, '%Y-%m-%d %H:%M:%S') + relativedelta(days=delay or 0)).strftime('%Y-%m-%d'),
1362 'move_history_ids2': []}
1364 self.pool.get('stock.move').write(cr, uid, [move.id], {
1365 'move_dest_id': new_id,
1366 'move_history_ids': [(4, new_id)]
1368 new_moves.append(self.browse(cr, uid, [new_id])[0])
1369 wf_service = netsvc.LocalService("workflow")
1370 wf_service.trg_validate(uid, 'stock.picking', pickid, 'button_confirm', cr)
1372 create_chained_picking(self, cr, uid, new_moves, context)
1373 create_chained_picking(self, cr, uid, moves, context)
1376 def action_assign(self, cr, uid, ids, *args):
1378 for move in self.browse(cr, uid, ids):
1379 if move.state in ('confirmed', 'waiting'):
1380 todo.append(move.id)
1381 res = self.check_assign(cr, uid, todo)
1384 def force_assign(self, cr, uid, ids, context={}):
1385 self.write(cr, uid, ids, {'state': 'assigned'})
1388 def cancel_assign(self, cr, uid, ids, context={}):
1389 self.write(cr, uid, ids, {'state': 'confirmed'})
1393 # Duplicate stock.move
1395 def check_assign(self, cr, uid, ids, context={}):
1399 for move in self.browse(cr, uid, ids):
1400 if move.product_id.type == 'consu':
1401 if move.state in ('confirmed', 'waiting'):
1402 done.append(move.id)
1403 pickings[move.picking_id.id] = 1
1405 if move.state in ('confirmed', 'waiting'):
1406 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})
1408 #_product_available_test depends on the next status for correct functioning
1409 #the test does not work correctly if the same product occurs multiple times
1410 #in the same order. This is e.g. the case when using the button 'split in two' of
1411 #the stock outgoing form
1412 self.write(cr, uid, move.id, {'state':'assigned'})
1413 done.append(move.id)
1414 pickings[move.picking_id.id] = 1
1416 cr.execute('update stock_move set location_id=%s, product_qty=%s where id=%s', (r[1], r[0], move.id))
1420 move_id = self.copy(cr, uid, move.id, {'product_qty': r[0], 'location_id': r[1]})
1421 done.append(move_id)
1422 #cr.execute('insert into stock_move_history_ids values (%s,%s)', (move.id,move_id))
1425 self.write(cr, uid, done, {'state': 'assigned'})
1428 for pick_id in pickings:
1429 wf_service = netsvc.LocalService("workflow")
1430 wf_service.trg_write(uid, 'stock.picking', pick_id, cr)
1434 # Cancel move => cancel others move and pickings
1436 def action_cancel(self, cr, uid, ids, context={}):
1440 for move in self.browse(cr, uid, ids):
1441 if move.state in ('confirmed', 'waiting', 'assigned', 'draft'):
1443 pickings[move.picking_id.id] = True
1444 if move.move_dest_id and move.move_dest_id.state == 'waiting':
1445 self.write(cr, uid, [move.move_dest_id.id], {'state': 'assigned'})
1446 if context.get('call_unlink',False) and move.move_dest_id.picking_id:
1447 wf_service = netsvc.LocalService("workflow")
1448 wf_service.trg_write(uid, 'stock.picking', move.move_dest_id.picking_id.id, cr)
1449 self.write(cr, uid, ids, {'state': 'cancel', 'move_dest_id': False})
1450 if not context.get('call_unlink',False):
1451 for pick in self.pool.get('stock.picking').browse(cr, uid, pickings.keys()):
1452 if all(move.state == 'cancel' for move in pick.move_lines):
1453 self.pool.get('stock.picking').write(cr, uid, [pick.id], {'state': 'cancel'})
1455 wf_service = netsvc.LocalService("workflow")
1457 wf_service.trg_trigger(uid, 'stock.move', id, cr)
1458 #self.action_cancel(cr,uid, ids2, context)
1461 def action_done(self, cr, uid, ids, context=None):
1464 for move in self.browse(cr, uid, ids):
1465 if move.picking_id: picking_ids.append(move.picking_id.id)
1466 if move.move_dest_id.id and (move.state != 'done'):
1467 cr.execute('insert into stock_move_history_ids (parent_id,child_id) values (%s,%s)', (move.id, move.move_dest_id.id))
1468 if move.move_dest_id.state in ('waiting', 'confirmed'):
1469 self.write(cr, uid, [move.move_dest_id.id], {'state': 'assigned'})
1470 if move.move_dest_id.picking_id:
1471 wf_service = netsvc.LocalService("workflow")
1472 wf_service.trg_write(uid, 'stock.picking', move.move_dest_id.picking_id.id, cr)
1475 # self.action_done(cr, uid, [move.move_dest_id.id])
1476 if move.move_dest_id.auto_validate:
1477 self.action_done(cr, uid, [move.move_dest_id.id], context=context)
1480 # Accounting Entries
1484 if move.location_id.account_id:
1485 acc_src = move.location_id.account_id.id
1486 if move.location_dest_id.account_id:
1487 acc_dest = move.location_dest_id.account_id.id
1488 if acc_src or acc_dest:
1489 test = [('product.product', move.product_id.id)]
1490 if move.product_id.categ_id:
1491 test.append( ('product.category', move.product_id.categ_id.id) )
1493 acc_src = move.product_id.product_tmpl_id.\
1494 property_stock_account_input.id
1496 acc_src = move.product_id.categ_id.\
1497 property_stock_account_input_categ.id
1499 raise osv.except_osv(_('Error!'),
1500 _('There is no stock input account defined ' \
1501 'for this product: "%s" (id: %d)') % \
1502 (move.product_id.name,
1503 move.product_id.id,))
1505 acc_dest = move.product_id.product_tmpl_id.\
1506 property_stock_account_output.id
1508 acc_dest = move.product_id.categ_id.\
1509 property_stock_account_output_categ.id
1511 raise osv.except_osv(_('Error!'),
1512 _('There is no stock output account defined ' \
1513 'for this product: "%s" (id: %d)') % \
1514 (move.product_id.name,
1515 move.product_id.id,))
1516 if not move.product_id.categ_id.property_stock_journal.id:
1517 raise osv.except_osv(_('Error!'),
1518 _('There is no journal defined '\
1519 'on the product category: "%s" (id: %d)') % \
1520 (move.product_id.categ_id.name,
1521 move.product_id.categ_id.id,))
1522 journal_id = move.product_id.categ_id.property_stock_journal.id
1523 if acc_src != acc_dest:
1524 ref = move.picking_id and move.picking_id.name or False
1525 product_uom_obj = self.pool.get('product.uom')
1526 default_uom = move.product_id.uom_id.id
1527 date = time.strftime('%Y-%m-%d')
1528 q = product_uom_obj._compute_qty(cr, uid, move.product_uom.id, move.product_qty, default_uom)
1529 if move.product_id.cost_method == 'average' and move.price_unit:
1530 amount = q * move.price_unit
1531 # Base computation on valuation price type
1533 company_id=move.company_id.id
1534 context['currency_id']=move.company_id.currency_id.id
1535 pricetype=self.pool.get('product.price.type').browse(cr,uid,move.company_id.property_valuation_price_type.id)
1536 amount_unit=move.product_id.price_get(pricetype.field, context)[move.product_id.id]
1537 amount=amount_unit * q or 1.0
1538 # amount = q * move.product_id.standard_price
1542 partner_id = move.picking_id.address_id and (move.picking_id.address_id.partner_id and move.picking_id.address_id.partner_id.id or False) or False
1546 'quantity': move.product_qty,
1547 'product_id': move.product_id and move.product_id.id or False,
1549 'account_id': acc_src,
1552 'partner_id': partner_id}),
1555 'product_id': move.product_id and move.product_id.id or False,
1556 'quantity': move.product_qty,
1558 'account_id': acc_dest,
1561 'partner_id': partner_id})
1563 self.pool.get('account.move').create(cr, uid, {
1565 'journal_id': journal_id,
1569 self.write(cr, uid, ids, {'state': 'done', 'date_planned': time.strftime('%Y-%m-%d %H:%M:%S')})
1570 for pick in self.pool.get('stock.picking').browse(cr, uid, picking_ids):
1571 if all(move.state == 'done' for move in pick.move_lines):
1572 self.pool.get('stock.picking').action_done(cr, uid, [pick.id])
1574 wf_service = netsvc.LocalService("workflow")
1576 wf_service.trg_trigger(uid, 'stock.move', id, cr)
1579 def unlink(self, cr, uid, ids, context=None):
1580 for move in self.browse(cr, uid, ids, context=context):
1581 if move.state != 'draft':
1582 raise osv.except_osv(_('UserError'),
1583 _('You can only delete draft moves.'))
1584 return super(stock_move, self).unlink(
1585 cr, uid, ids, context=context)
1587 def _create_lot(self, cr, uid, ids, product_id, prefix=False):
1588 prodlot_obj = self.pool.get('stock.production.lot')
1589 ir_sequence_obj = self.pool.get('ir.sequence')
1590 sequence = ir_sequence_obj.get(cr, uid, 'stock.lot.serial')
1592 raise osv.except_osv(_('Error!'), _('No production sequence defined'))
1593 prodlot_id = prodlot_obj.create(cr, uid, {'name': sequence, 'prefix': prefix}, {'product_id': product_id})
1594 prodlot = prodlot_obj.browse(cr, uid, prodlot_id)
1595 ref = ','.join(map(lambda x:str(x),ids))
1597 ref = '%s, %s' % (prodlot.ref, ref)
1598 prodlot_obj.write(cr, uid, [prodlot_id], {'ref': ref})
1602 def action_scrap(self, cr, uid, ids, quantity, location_id, context=None):
1604 Move the scrap/damaged product into scrap location
1606 @ param cr: the database cursor
1607 @ param uid: the user id
1608 @ param ids: ids of stock move object to be scraped
1609 @ param quantity : specify scrap qty
1610 @ param location_id : specify scrap location
1611 @ param context: context arguments
1613 @ return: Scraped lines
1616 raise osv.except_osv(_('Warning!'), _('Please provide Proper Quantity !'))
1618 for move in self.browse(cr, uid, ids, context=context):
1619 move_qty = move.product_qty
1620 uos_qty = quantity / move_qty * move.product_uos_qty
1622 'product_qty': quantity,
1623 'product_uos_qty': uos_qty,
1624 'state': move.state,
1626 'location_dest_id': location_id
1628 new_move = self.copy(cr, uid, move.id, default_val)
1629 #self.write(cr, uid, [new_move], {'move_history_ids':[(4,move.id)]}) #TODO : to track scrap moves
1631 self.action_done(cr, uid, res)
1634 def action_split(self, cr, uid, ids, quantity, split_by_qty=1, prefix=False, with_lot=True, context=None):
1636 Split Stock Move lines into production lot which specified split by quantity.
1638 @ param cr: the database cursor
1639 @ param uid: the user id
1640 @ param ids: ids of stock move object to be splited
1641 @ param split_by_qty : specify split by qty
1642 @ param prefix : specify prefix of production lot
1643 @ param with_lot : if true, prodcution lot will assign for split line otherwise not.
1644 @ param context: context arguments
1646 @ return: splited move lines
1650 raise osv.except_osv(_('Warning!'), _('Please provide Proper Quantity !'))
1654 for move in self.browse(cr, uid, ids):
1655 if split_by_qty <= 0 or quantity == 0:
1658 uos_qty = split_by_qty / move.product_qty * move.product_uos_qty
1660 quantity_rest = quantity % split_by_qty
1661 uos_qty_rest = split_by_qty / move.product_qty * move.product_uos_qty
1664 'product_qty': split_by_qty,
1665 'product_uos_qty': uos_qty,
1667 for idx in range(int(quantity//split_by_qty)):
1668 if not idx and move.product_qty<=quantity:
1669 current_move = move.id
1671 current_move = self.copy(cr, uid, move.id, {'state': move.state})
1672 res.append(current_move)
1674 update_val['prodlot_id'] = self._create_lot(cr, uid, [current_move], move.product_id.id)
1676 self.write(cr, uid, [current_move], update_val)
1679 if quantity_rest > 0:
1680 idx = int(quantity//split_by_qty)
1681 update_val['product_qty'] = quantity_rest
1682 update_val['product_uos_qty'] = uos_qty_rest
1683 if not idx and move.product_qty<=quantity:
1684 current_move = move.id
1686 current_move = self.copy(cr, uid, move.id, {'state': move.state})
1688 res.append(current_move)
1692 update_val['prodlot_id'] = self._create_lot(cr, uid, [current_move], move.product_id.id)
1694 self.write(cr, uid, [current_move], update_val)
1697 def action_consume(self, cr, uid, ids, quantity, location_id=False, context=None):
1699 Consumed product with specific quatity from specific source location
1701 @ param cr: the database cursor
1702 @ param uid: the user id
1703 @ param ids: ids of stock move object to be consumed
1704 @ param quantity : specify consume quantity
1705 @ param location_id : specify source location
1706 @ param context: context arguments
1708 @ return: Consumed lines
1714 raise osv.except_osv(_('Warning!'), _('Please provide Proper Quantity !'))
1717 for move in self.browse(cr, uid, ids, context=context):
1718 move_qty = move.product_qty
1719 quantity_rest = move.product_qty
1721 quantity_rest -= quantity
1722 uos_qty_rest = quantity_rest / move_qty * move.product_uos_qty
1723 if quantity_rest <= 0:
1726 quantity = move.product_qty
1728 uos_qty = quantity / move_qty * move.product_uos_qty
1730 if quantity_rest > 0:
1732 'product_qty': quantity,
1733 'product_uos_qty': uos_qty,
1734 'state': move.state,
1735 'location_id': location_id
1737 if move.product_id.track_production and location_id:
1738 # IF product has checked track for production lot, move lines will be split by 1
1739 res += self.action_split(cr, uid, [move.id], quantity, split_by_qty=1, context=context)
1741 current_move = self.copy(cr, uid, move.id, default_val)
1742 res += [current_move]
1745 update_val['product_qty'] = quantity_rest
1746 update_val['product_uos_qty'] = uos_qty_rest
1747 self.write(cr, uid, [move.id], update_val)
1750 quantity_rest = quantity
1751 uos_qty_rest = uos_qty
1753 if move.product_id.track_production and location_id:
1754 res += self.split_lines(cr, uid, [move.id], quantity_rest, split_by_qty=1, context=context)
1758 'product_qty' : quantity_rest,
1759 'product_uos_qty' : uos_qty_rest,
1760 'location_id': location_id
1763 self.write(cr, uid, [move.id], update_val)
1765 self.action_done(cr, uid, res)
1768 def do_partial(self, cr, uid, ids, partial_datas, context={}):
1770 @ partial_datas : dict. contain details of partial picking
1771 like partner_id, address_id, delivery_date, delivery moves with product_id, product_qty, uom
1774 picking_obj = self.pool.get('stock.picking')
1775 product_obj = self.pool.get('product.product')
1776 currency_obj = self.pool.get('res.currency')
1777 users_obj = self.pool.get('res.users')
1778 uom_obj = self.pool.get('product.uom')
1779 price_type_obj = self.pool.get('product.price.type')
1780 sequence_obj = self.pool.get('ir.sequence')
1781 wf_service = netsvc.LocalService("workflow")
1782 partner_id = partial_datas.get('partner_id', False)
1783 address_id = partial_datas.get('address_id', False)
1784 delivery_date = partial_datas.get('delivery_date', False)
1788 complete, too_many, too_few = [], [], []
1789 move_product_qty = {}
1790 for move in self.browse(cr, uid, ids, context=context):
1791 if move.state in ('done', 'cancel'):
1793 partial_data = partial_datas.get('move%s'%(move.id), False)
1794 assert partial_data, _('Do not Found Partial data of Stock Move Line :%s' %(move.id))
1795 product_qty = partial_data.get('product_qty',0.0)
1796 move_product_qty[move.id] = product_qty
1797 product_uom = partial_data.get('product_uom',False)
1798 product_price = partial_data.get('product_price',0.0)
1799 product_currency = partial_data.get('product_currency',False)
1800 if move.product_qty == product_qty:
1801 complete.append(move)
1802 elif move.product_qty > product_qty:
1803 too_few.append(move)
1805 too_many.append(move)
1807 # Average price computation
1808 if (move.picking_id.type == 'in') and (move.product_id.cost_method == 'average'):
1809 product = product_obj.browse(cr, uid, move.product_id.id)
1810 user = users_obj.browse(cr, uid, uid)
1811 context['currency_id'] = move.company_id.currency_id.id
1812 qty = uom_obj._compute_qty(cr, uid, product_uom, product_qty, product.uom_id.id)
1814 if user.company_id.property_valuation_price_type:
1815 pricetype = price_type_obj.browse(cr, uid, user.company_id.property_valuation_price_type.id)
1816 if pricetype and qty > 0:
1817 new_price = currency_obj.compute(cr, uid, product_currency,
1818 user.company_id.currency_id.id, product_price)
1819 new_price = uom_obj._compute_price(cr, uid, product_uom, new_price,
1821 if product.qty_available <= 0:
1822 new_std_price = new_price
1824 # Get the standard price
1825 amount_unit = product.price_get(pricetype.field, context)[product.id]
1826 new_std_price = ((amount_unit * product.qty_available)\
1827 + (new_price * qty))/(product.qty_available + qty)
1829 # Write the field according to price type field
1830 product_obj.write(cr, uid, [product.id],
1831 {pricetype.field: new_std_price})
1832 self.write(cr, uid, [move.id], {'price_unit': new_price})
1834 for move in too_few:
1835 product_qty = move_product_qty[move.id]
1836 if product_qty != 0:
1837 new_move = self.copy(cr, uid, move.id,
1839 'product_qty' : product_qty,
1840 'product_uos_qty': product_qty,
1841 'picking_id' : move.picking_id.id,
1842 'state': 'assigned',
1843 'move_dest_id': False,
1844 'price_unit': move.price_unit,
1846 complete.append(self.browse(cr, uid, new_move))
1847 self.write(cr, uid, move.id,
1849 'product_qty' : move.product_qty - product_qty,
1850 'product_uos_qty':move.product_qty - product_qty,
1854 for move in too_many:
1855 self.write(cr, uid, move.id,
1857 'product_qty': product_qty,
1858 'product_uos_qty': product_qty
1860 complete.append(move)
1862 for move in complete:
1863 self.action_done(cr, uid, [move.id])
1865 # TOCHECK : Done picking if all moves are done
1867 SELECT move.id FROM stock_picking pick
1868 RIGHT JOIN stock_move move ON move.picking_id = pick.id AND move.state = %s
1869 WHERE pick.id = %s""",
1870 ('done', move.picking_id.id))
1872 if len(res) == len(move.picking_id.move_lines):
1873 picking_obj.action_move(cr, uid, [move.picking_id.id])
1874 wf_service.trg_validate(uid, 'stock.picking', move.picking_id.id, 'button_done', cr)
1878 for move in complete:
1879 done_move_ids.append(move.id)
1880 return done_move_ids
1885 class stock_inventory(osv.osv):
1886 _name = "stock.inventory"
1887 _description = "Inventory"
1889 'name': fields.char('Inventory', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
1890 'date': fields.datetime('Date create', required=True, readonly=True, states={'draft': [('readonly', False)]}),
1891 'date_done': fields.datetime('Date done'),
1892 'inventory_line_id': fields.one2many('stock.inventory.line', 'inventory_id', 'Inventories', states={'done': [('readonly', True)]}),
1893 'move_ids': fields.many2many('stock.move', 'stock_inventory_move_rel', 'inventory_id', 'move_id', 'Created Moves'),
1894 'state': fields.selection( (('draft', 'Draft'), ('done', 'Done'), ('cancel','Cancelled')), 'State', readonly=True),
1895 'company_id': fields.many2one('res.company','Company',required=True,select=1),
1898 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
1899 'state': lambda *a: 'draft',
1900 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.inventory', context=c)
1904 def _inventory_line_hook(self, cr, uid, inventory_line, move_vals):
1905 '''Creates a stock move from an inventory line'''
1906 return self.pool.get('stock.move').create(cr, uid, move_vals)
1908 def action_done(self, cr, uid, ids, context=None):
1909 for inv in self.browse(cr, uid, ids):
1912 for line in inv.inventory_line_id:
1913 pid = line.product_id.id
1915 # price = line.product_id.standard_price or 0.0
1916 amount = self.pool.get('stock.location')._product_get(cr, uid, line.location_id.id, [pid], {'uom': line.product_uom.id})[pid]
1917 change = line.product_qty - amount
1918 lot_id = line.prod_lot_id.id
1920 location_id = line.product_id.product_tmpl_id.property_stock_inventory.id
1922 'name': 'INV:' + str(line.inventory_id.id) + ':' + line.inventory_id.name,
1923 'product_id': line.product_id.id,
1924 'product_uom': line.product_uom.id,
1925 'prodlot_id': lot_id,
1927 'date_planned': inv.date,
1932 'product_qty': change,
1933 'location_id': location_id,
1934 'location_dest_id': line.location_id.id,
1938 'product_qty': -change,
1939 'location_id': line.location_id.id,
1940 'location_dest_id': location_id,
1944 'prodlot_id': lot_id,
1945 'product_qty': line.product_qty
1947 move_ids.append(self._inventory_line_hook(cr, uid, line, value))
1949 self.pool.get('stock.move').action_done(cr, uid, move_ids,
1951 self.write(cr, uid, [inv.id], {'state': 'done', 'date_done': time.strftime('%Y-%m-%d %H:%M:%S'), 'move_ids': [(6, 0, move_ids)]})
1954 def action_cancel(self, cr, uid, ids, context={}):
1955 for inv in self.browse(cr, uid, ids):
1956 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in inv.move_ids], context)
1957 self.write(cr, uid, [inv.id], {'state': 'draft'})
1960 def action_cancel_inventary(self, cr, uid, ids, context={}):
1961 for inv in self.browse(cr,uid,ids):
1962 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in inv.move_ids], context)
1963 self.write(cr, uid, [inv.id], {'state':'cancel'})
1969 class stock_inventory_line(osv.osv):
1970 _name = "stock.inventory.line"
1971 _description = "Inventory line"
1973 'inventory_id': fields.many2one('stock.inventory', 'Inventory', ondelete='cascade', select=True),
1974 'location_id': fields.many2one('stock.location', 'Location', required=True),
1975 'product_id': fields.many2one('product.product', 'Product', required=True),
1976 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
1977 'product_qty': fields.float('Quantity'),
1978 'company_id': fields.related('inventory_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
1979 'prod_lot_id': fields.many2one('stock.production.lot', 'Production Lot', domain="[('product_id','=',product_id)]"),
1980 'state': fields.related('inventory_id','state',type='char',string='State',readonly=True),
1983 def on_change_product_id(self, cr, uid, ids, location_id, product, uom=False):
1987 prod = self.pool.get('product.product').browse(cr, uid, [product], {'uom': uom})[0]
1988 uom = prod.uom_id.id
1989 amount = self.pool.get('stock.location')._product_get(cr, uid, location_id, [product], {'uom': uom})[product]
1990 result = {'product_qty': amount, 'product_uom': uom}
1991 return {'value': result}
1993 stock_inventory_line()
1996 #----------------------------------------------------------
1998 #----------------------------------------------------------
1999 class stock_warehouse(osv.osv):
2000 _name = "stock.warehouse"
2001 _description = "Warehouse"
2003 'name': fields.char('Name', size=60, required=True),
2004 # 'partner_id': fields.many2one('res.partner', 'Owner'),
2005 'company_id': fields.many2one('res.company','Company',required=True,select=1),
2006 'partner_address_id': fields.many2one('res.partner.address', 'Owner Address'),
2007 'lot_input_id': fields.many2one('stock.location', 'Location Input', required=True),
2008 'lot_stock_id': fields.many2one('stock.location', 'Location Stock', required=True),
2009 'lot_output_id': fields.many2one('stock.location', 'Location Output', required=True),
2012 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.inventory', context=c),
2018 # get confirm or assign stock move lines of partner and put in current picking.
2019 class stock_picking_move_wizard(osv.osv_memory):
2020 _name = 'stock.picking.move.wizard'
2022 def _get_picking(self, cr, uid, ctx):
2023 if ctx.get('action_id', False):
2024 return ctx['action_id']
2027 def _get_picking_address(self, cr, uid, ctx):
2028 picking_obj = self.pool.get('stock.picking')
2029 if ctx.get('action_id', False):
2030 picking = picking_obj.browse(cr, uid, [ctx['action_id']])[0]
2031 return picking.address_id and picking.address_id.id or False
2035 'name': fields.char('Name', size=64, invisible=True),
2036 #'move_lines': fields.one2many('stock.move', 'picking_id', 'Move lines',readonly=True),
2037 'move_ids': fields.many2many('stock.move', 'picking_move_wizard_rel', 'picking_move_wizard_id', 'move_id', 'Entry lines', required=True),
2038 'address_id': fields.many2one('res.partner.address', 'Dest. Address', invisible=True),
2039 'picking_id': fields.many2one('stock.picking', 'Picking list', select=True, invisible=True),
2042 'picking_id': _get_picking,
2043 'address_id': _get_picking_address,
2046 def action_move(self, cr, uid, ids, context=None):
2047 move_obj = self.pool.get('stock.move')
2048 picking_obj = self.pool.get('stock.picking')
2049 for act in self.read(cr, uid, ids):
2050 move_lines = move_obj.browse(cr, uid, act['move_ids'])
2051 for line in move_lines:
2053 picking_obj.write(cr, uid, [line.picking_id.id], {'move_lines': [(1, line.id, {'picking_id': act['picking_id']})]})
2054 picking_obj.write(cr, uid, [act['picking_id']], {'move_lines': [(1, line.id, {'picking_id': act['picking_id']})]})
2055 old_picking = picking_obj.read(cr, uid, [line.picking_id.id])[0]
2056 if not len(old_picking['move_lines']):
2057 picking_obj.write(cr, uid, [old_picking['id']], {'state': 'done'})
2059 raise osv.except_osv(_('UserError'),
2060 _('You can not create new moves.'))
2061 return {'type': 'ir.actions.act_window_close'}
2063 stock_picking_move_wizard()
2065 class report_products_to_received_planned(osv.osv):
2066 _name = "report.products.to.received.planned"
2067 _description = "Product to Received Vs Planned"
2070 'date':fields.date('Date'),
2071 'qty': fields.integer('Actual Qty'),
2072 'planned_qty': fields.integer('Planned Qty'),
2077 tools.drop_view_if_exists(cr, 'report_products_to_received_planned')
2079 create or replace view report_products_to_received_planned as (
2080 select stock.date, min(stock.id), sum(stock.product_qty) as qty, 0 as planned_qty
2081 from stock_picking picking
2082 inner join stock_move stock
2083 on picking.id = stock.picking_id and picking.type = 'in'
2084 where stock.date between (select cast(date_trunc('week', current_date) as date)) and (select cast(date_trunc('week', current_date) as date) + 7)
2089 select stock.date_planned, min(stock.id), 0 as actual_qty, sum(stock.product_qty) as planned_qty
2090 from stock_picking picking
2091 inner join stock_move stock
2092 on picking.id = stock.picking_id and picking.type = 'in'
2093 where stock.date_planned between (select cast(date_trunc('week', current_date) as date)) and (select cast(date_trunc('week', current_date) as date) + 7)
2094 group by stock.date_planned
2097 report_products_to_received_planned()
2099 class report_delivery_products_planned(osv.osv):
2100 _name = "report.delivery.products.planned"
2101 _description = "Number of Delivery products vs planned"
2104 'date':fields.date('Date'),
2105 'qty': fields.integer('Actual Qty'),
2106 'planned_qty': fields.integer('Planned Qty'),
2111 tools.drop_view_if_exists(cr, 'report_delivery_products_planned')
2113 create or replace view report_delivery_products_planned as (
2114 select stock.date, min(stock.id), sum(stock.product_qty) as qty, 0 as planned_qty
2115 from stock_picking picking
2116 inner join stock_move stock
2117 on picking.id = stock.picking_id and picking.type = 'out'
2118 where stock.date between (select cast(date_trunc('week', current_date) as date)) and (select cast(date_trunc('week', current_date) as date) + 7)
2123 select stock.date_planned, min(stock.id), 0 as actual_qty, sum(stock.product_qty) as planned_qty
2124 from stock_picking picking
2125 inner join stock_move stock
2126 on picking.id = stock.picking_id and picking.type = 'out'
2127 where stock.date_planned between (select cast(date_trunc('week', current_date) as date)) and (select cast(date_trunc('week', current_date) as date) + 7)
2128 group by stock.date_planned
2133 report_delivery_products_planned()
2134 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: