|
@@ -201,7 +201,7 @@
Total:
|
- [[ formatLang(qty_total(o.inventory_line_id)['quantity']) ]] [[ qty_total(o.inventory_line_id)['uom'] ]]
+ [[ formatLang(qty_total(o.line_ids)['quantity']) ]] [[ qty_total(o.line_ids)['uom'] ]]
|
diff --git a/addons/stock/report/stock_inventory_move_report.py b/addons/stock/report/stock_inventory_move_report.py
index 21a7c458ecc..ee1570b2193 100644
--- a/addons/stock/report/stock_inventory_move_report.py
+++ b/addons/stock/report/stock_inventory_move_report.py
@@ -32,7 +32,7 @@ class stock_inventory_move(report_sxw.rml_parse):
def _qty_total(self, objects):
total = 0.0
- uom = objects[0].product_uom.name
+ uom = objects[0].product_uom_id.name
for obj in objects:
total += obj.product_qty
return {'quantity':total,'uom':uom}
diff --git a/addons/stock/stock.py b/addons/stock/stock.py
index 3c5734aa6e6..6bd5e8d57d1 100644
--- a/addons/stock/stock.py
+++ b/addons/stock/stock.py
@@ -26,7 +26,6 @@ import time
from openerp.osv import fields, osv
from openerp.tools.translate import _
-from openerp import tools
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, DEFAULT_SERVER_DATE_FORMAT
from openerp import SUPERUSER_ID
import openerp.addons.decimal_precision as dp
@@ -293,13 +292,15 @@ class stock_quant(osv.osv):
'''This function reserves quants for the given move (and optionally given link). If the total of quantity reserved is enough, the move's state
is also set to 'assigned'
- :param quants: list of tuple(quant browse record or None, qty to reserve). If None is given as first tuple element, the item will be ignored
+ :param quants: list of tuple(quant browse record or None, qty to reserve). If None is given as first tuple element, the item will be ignored. Negative quants should not be received as argument
:param move: browse record
:param link: browse record (stock.move.operation.link)
'''
toreserve = []
#split quants if needed
for quant, qty in quants:
+ if qty <= 0.0 or (quant and quant.qty <= 0.0):
+ raise osv.except_osv(_('Error!'), _('You can not reserve a negative quantity or a negative quant.'))
if not quant:
continue
self._quant_split(cr, uid, quant, qty, context=context)
@@ -547,6 +548,9 @@ class stock_quant(osv.osv):
'''
domain += location and [('location_id', 'child_of', location.id)] or []
domain += [('product_id', '=', product.id)] + domain
+ #don't take into account location that are production, supplier or inventory
+ ignore_location_ids = self.pool.get('stock.location').search(cr, uid, [('usage', 'in', ('production', 'supplier', 'inventory'))], context=context)
+ domain.append(('location_id','not in',ignore_location_ids))
res = []
offset = 0
while quantity > 0:
@@ -735,9 +739,9 @@ class stock_picking(osv.osv):
* Transferred: has been processed, can't be modified or cancelled anymore\n
* Cancelled: has been cancelled, can't be confirmed anymore"""
),
- 'priority': fields.selection([('0', 'Low'), ('1', 'Normal'), ('2', 'High')], string='Priority', required=True),
+ 'priority': fields.selection([('0', 'Low'), ('1', 'Normal'), ('2', 'High')], states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, string='Priority', required=True),
'min_date': fields.function(get_min_max_date, multi="min_max_date", fnct_inv=_set_min_date,
- store={'stock.move': (_get_pickings, ['state', 'date_expected'], 20)}, type='datetime', string='Scheduled Date', select=1, help="Scheduled time for the first part of the shipment to be processed. Setting manually a value here would set it as expected date for all the stock moves.", track_visibility='onchange'),
+ store={'stock.move': (_get_pickings, ['state', 'date_expected'], 20)}, type='datetime', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, string='Scheduled Date', select=1, help="Scheduled time for the first part of the shipment to be processed. Setting manually a value here would set it as expected date for all the stock moves.", track_visibility='onchange'),
'max_date': fields.function(get_min_max_date, multi="min_max_date",
store={'stock.move': (_get_pickings, ['state', 'date_expected'], 20)}, type='datetime', string='Max. Expected Date', select=2, help="Scheduled time for the last part of the shipment to be processed"),
'date': fields.datetime('Commitment Date', help="Date promised for the completion of the transfer order, usually set the time of the order and revised later on.", select=True, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, track_visibility='onchange'),
@@ -746,12 +750,12 @@ class stock_picking(osv.osv):
'quant_reserved_exist': fields.function(_get_quant_reserved_exist, type='boolean', string='Quant already reserved ?', help='technical field used to know if there is already at least one quant reserved on moves of a given picking'),
'partner_id': fields.many2one('res.partner', 'Partner', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}),
'company_id': fields.many2one('res.company', 'Company', required=True, select=True, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}),
- 'pack_operation_ids': fields.one2many('stock.pack.operation', 'picking_id', string='Related Packing Operations'),
+ 'pack_operation_ids': fields.one2many('stock.pack.operation', 'picking_id', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, string='Related Packing Operations'),
'pack_operation_exist': fields.function(_get_pack_operation_exist, type='boolean', string='Pack Operation Exists?', help='technical field for attrs in view'),
- 'picking_type_id': fields.many2one('stock.picking.type', 'Picking Type', required=True),
+ 'picking_type_id': fields.many2one('stock.picking.type', 'Picking Type', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, required=True),
'picking_type_code': fields.related('picking_type_id', 'code', type='char', string='Picking Type Code', help="Technical field used to display the correct label on print button in the picking view"),
- 'owner_id': fields.many2one('res.partner', 'Owner', help="Default Owner"),
+ 'owner_id': fields.many2one('res.partner', 'Owner', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, help="Default Owner"),
# Used to search on pickings
'product_id': fields.related('move_lines', 'product_id', type='many2one', relation='product.product', string='Product'),
'location_id': fields.related('move_lines', 'location_id', type='many2one', relation='stock.location', string='Location', readonly=True),
@@ -792,8 +796,8 @@ class stock_picking(osv.osv):
'''This function prints the delivery order'''
assert len(ids) == 1, 'This option should only be used for a single id at a time'
datas = {
- 'model': 'stock.picking',
- 'ids': ids,
+ 'model': 'stock.picking',
+ 'ids': ids,
}
return {'type': 'ir.actions.report.xml', 'report_name': 'stock.picking.list', 'datas': datas, 'nodestroy': True}
@@ -801,8 +805,8 @@ class stock_picking(osv.osv):
'''This function prints the picking list'''
assert len(ids) == 1, 'This option should only be used for a single id at a time'
datas = {
- 'model': 'stock.picking',
- 'ids': ids,
+ 'model': 'stock.picking',
+ 'ids': ids,
}
return {'type': 'ir.actions.report.xml', 'report_name': 'stock.picking.list.internal', 'datas': datas, 'nodestroy': True}
@@ -849,15 +853,6 @@ class stock_picking(osv.osv):
self.do_prepare_partial(cr, uid, [pick.id], context=None)
return True
- def cancel_assign(self, cr, uid, ids, context=None):
- """ Cancels picking for the moves that are in the assigned state
- @return: True
- """
- for pick in self.browse(cr, uid, ids, context=context):
- move_ids = [x.id for x in pick.move_lines if x not in ['done', 'cancel']]
- self.pool.get('stock.move').cancel_assign(cr, uid, move_ids, context=context)
- return True
-
def action_cancel(self, cr, uid, ids, context=None):
for pick in self.browse(cr, uid, ids, context=context):
ids2 = [move.id for move in pick.move_lines]
@@ -1019,11 +1014,11 @@ class stock_picking(osv.osv):
moves_to_unreserve = []
pack_line_to_unreserve = []
for picking in self.browse(cr, uid, picking_ids, context=context):
- moves_to_unreserve += [m.id for m in picking.move_lines]
+ moves_to_unreserve += [m.id for m in picking.move_lines if m.state not in ('done', 'cancel')]
pack_line_to_unreserve += [p.id for p in picking.pack_operation_ids]
- if pack_line_to_unreserve:
- self.pool.get('stock.pack.operation').unlink(cr, uid, pack_line_to_unreserve, context=context)
if moves_to_unreserve:
+ if pack_line_to_unreserve:
+ self.pool.get('stock.pack.operation').unlink(cr, uid, pack_line_to_unreserve, context=context)
self.pool.get('stock.move').do_unreserve(cr, uid, moves_to_unreserve, context=context)
def do_recompute_remaining_quantities(self, cr, uid, picking_ids, context=None):
@@ -1228,14 +1223,13 @@ class stock_production_lot(osv.osv):
@return: A dictionary of values
"""
quant_obj = self.pool.get("stock.quant")
- move_obj = self.pool.get("stock.move")
quants = quant_obj.search(cr, uid, [('lot_id', 'in', ids)], context=context)
moves = set()
for quant in quant_obj.browse(cr, uid, quants, context=context):
moves |= {move.id for move in quant.history_ids}
if moves:
- return {
- 'domain': "[('id','in',["+','.join(map(str, list(moves)))+"])]",
+ return {
+ 'domain': "[('id','in',[" + ','.join(map(str, list(moves))) + "])]",
'name': _('Traceability'),
'view_mode': 'tree,form',
'view_type': 'form',
@@ -1244,7 +1238,6 @@ class stock_production_lot(osv.osv):
'type': 'ir.actions.act_window',
}
return False
-
# ----------------------------------------------------
@@ -1420,6 +1413,7 @@ class stock_move(osv.osv):
'price_unit': fields.float('Unit Price', help="Technical field used to record the product cost set by the user during a picking confirmation (when costing method used is 'average price' or 'real'). Value given in company currency and in product uom."), # as it's a technical field, we intentionally don't provide the digits attribute
'company_id': fields.many2one('res.company', 'Company', required=True, select=True),
+ 'split_from': fields.many2one('stock.move', string="Move Split From", help="Technical field used to track the origin of a split move, which can be useful in case of debug"),
'backorder_id': fields.related('picking_id', 'backorder_id', type='many2one', relation="stock.picking", string="Back Order of", select=True),
'origin': fields.char("Source"),
'procure_method': fields.selection([('make_to_stock', 'Make to Stock'), ('make_to_order', 'Make to Order')], 'Procurement Method', required=True, help="Make to Stock: When needed, the product is taken from the stock or we wait for replenishment. \nMake to Order: When needed, the product is purchased or produced."),
@@ -1506,13 +1500,16 @@ class stock_move(osv.osv):
default['reserved_quant_ids'] = []
default['returned_move_ids'] = []
default['linked_move_operation_ids'] = []
- default['origin_returned_move_id'] = False
+ if not default.get('origin_returned_move_id'):
+ default['origin_returned_move_id'] = False
default['state'] = 'draft'
return super(stock_move, self).copy_data(cr, uid, id, default, context)
def do_unreserve(self, cr, uid, move_ids, context=None):
quant_obj = self.pool.get("stock.quant")
for move in self.browse(cr, uid, move_ids, context=context):
+ if move.state in ('done', 'cancel'):
+ raise osv.except_osv(_('Operation Forbidden!'), _('Cannot unreserve a done move'))
quant_obj.quants_unreserve(cr, uid, move, context=context)
putaway_values = []
for putaway_rec in move.putaway_ids:
@@ -1547,7 +1544,10 @@ class stock_move(osv.osv):
def _push_apply(self, cr, uid, moves, context=None):
push_obj = self.pool.get("stock.location.path")
for move in moves:
- if not move.move_dest_id:
+ #1) if the move is already chained, there is no need to check push rules
+ #2) if the move is a returned move, we don't want to check push rules, as returning a returned move is the only decent way
+ # to receive goods without triggering the push rules again (which would duplicate chained operations)
+ if not move.move_dest_id and not move.origin_returned_move_id:
domain = [('location_from_id', '=', move.location_dest_id.id)]
if move.warehouse_id:
domain += ['|', ('warehouse_id', '=', move.warehouse_id.id), ('warehouse_id', '=', False)]
@@ -1825,12 +1825,6 @@ class stock_move(osv.osv):
self._putaway_check(cr, uid, ids, context=context)
return self.write(cr, uid, ids, {'state': 'assigned'})
- def cancel_assign(self, cr, uid, ids, context=None):
- """ Changes the state to confirmed.
- @return: True
- """
- return self.write(cr, uid, ids, {'state': 'confirmed'})
-
def check_tracking(self, cr, uid, move, lot_id, context=None):
""" Checks if serial number is assigned to stock move or not and raise an error if it had to.
"""
@@ -1866,6 +1860,9 @@ class stock_move(osv.osv):
fallback_domain = prev_quant_ids and [('id', 'not in', prev_quant_ids)] or []
#we always keep the quants already assigned and try to find the remaining quantity on quants not assigned only
main_domain = [('reservation_id', '=', False), ('qty', '>', 0)]
+ #if the move is returned from another, restrict the choice of quants to the ones that follow the returned move
+ if move.origin_returned_move_id:
+ main_domain += [('history_ids', 'in', move.origin_returned_move_id.id)]
#first try to find quants based on specific domains given by linked operations
for record in move.linked_move_operation_ids:
domain = main_domain + self.pool.get('stock.move.operation.link').get_specific_domain(cr, uid, record, context=context)
@@ -2070,39 +2067,6 @@ class stock_move(osv.osv):
self.action_done(cr, uid, res, context=context)
return res
- def action_consume(self, cr, uid, ids, quantity, location_id=False, restrict_lot_id=False, restrict_partner_id=False, context=None):
- """ Consumed product with specific quantity from specific source location. This correspond to a split of the move (or write if the quantity to consume is >= than the quantity of the move) followed by an action_done
- @param ids: ids of stock move object to be consumed
- @param quantity : specify consume quantity (given in move UoM)
- @param location_id : specify source location
- @return: Consumed lines
- """
- uom_obj = self.pool.get('product.uom')
- if context is None:
- context = {}
- if quantity <= 0:
- raise osv.except_osv(_('Warning!'), _('Please provide proper quantity.'))
- res = []
- for move in self.browse(cr, uid, ids, context=context):
- move_qty = move.product_qty
- uom_qty = uom_obj._compute_qty(cr, uid, move.product_id.uom_id.id, quantity, move.product_uom.id)
- if move_qty <= 0:
- raise osv.except_osv(_('Error!'), _('Cannot consume a move with negative or zero quantity.'))
- quantity_rest = move.product_qty - uom_qty
- if quantity_rest > 0:
- ctx = context.copy()
- if location_id:
- ctx['source_location_id'] = location_id
- res.append(self.split(cr, uid, move, move_qty - quantity_rest, restrict_lot_id=restrict_lot_id,
- restrict_partner_id=restrict_partner_id, context=ctx))
- else:
- res.append(move.id)
- if location_id:
- self.write(cr, uid, [move.id], {'location_id': location_id, 'restrict_lot_id': restrict_lot_id,
- 'restrict_partner_id': restrict_partner_id}, context=context)
-
- self.action_done(cr, uid, res, context=context)
- return res
def split(self, cr, uid, move, qty, restrict_lot_id=False, restrict_partner_id=False, context=None):
""" Splits qty from move move into a new move
@@ -2130,7 +2094,8 @@ class stock_move(osv.osv):
'move_dest_id': False,
'reserved_quant_ids': [],
'restrict_lot_id': restrict_lot_id,
- 'restrict_partner_id': restrict_partner_id
+ 'restrict_partner_id': restrict_partner_id,
+ 'split_from': move.id,
}
if context.get('source_location_id'):
defaults['location_id'] = context['source_location_id']
@@ -2151,6 +2116,7 @@ class stock_move(osv.osv):
self.action_confirm(cr, uid, [new_move], context=context)
return new_move
+
class stock_inventory(osv.osv):
_name = "stock.inventory"
_description = "Inventory"
diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml
index a4e6b36d9d2..0afe8f8df34 100644
--- a/addons/stock/stock_view.xml
+++ b/addons/stock/stock_view.xml
@@ -859,6 +859,7 @@
+
@@ -1797,6 +1798,7 @@
+
diff --git a/addons/stock/wizard/__init__.py b/addons/stock/wizard/__init__.py
index a29f3207ca1..4e2659f8218 100644
--- a/addons/stock/wizard/__init__.py
+++ b/addons/stock/wizard/__init__.py
@@ -20,8 +20,6 @@
##############################################################################
import stock_move
-import stock_inventory_merge
-import stock_inventory_line_split
import stock_location_product
import stock_return_picking
import stock_change_product_qty
diff --git a/addons/stock/wizard/stock_change_product_qty.py b/addons/stock/wizard/stock_change_product_qty.py
index 7e86c07b28f..0bb39575bbb 100644
--- a/addons/stock/wizard/stock_change_product_qty.py
+++ b/addons/stock/wizard/stock_change_product_qty.py
@@ -74,15 +74,15 @@ class stock_change_product_qty(osv.osv_memory):
rec_id = context and context.get('active_id', False)
assert rec_id, _('Active ID is not set in Context')
- inventry_obj = self.pool.get('stock.inventory')
- inventry_line_obj = self.pool.get('stock.inventory.line')
+ inventory_obj = self.pool.get('stock.inventory')
+ inventory_line_obj = self.pool.get('stock.inventory.line')
prod_obj_pool = self.pool.get('product.product')
res_original = prod_obj_pool.browse(cr, uid, rec_id, context=context)
for data in self.browse(cr, uid, ids, context=context):
if data.new_quantity < 0:
raise osv.except_osv(_('Warning!'), _('Quantity cannot be negative.'))
- inventory_id = inventry_obj.create(cr , uid, {'name': _('INV: %s') % tools.ustr(res_original.name)}, context=context)
+ inventory_id = inventory_obj.create(cr , uid, {'name': _('INV: %s') % tools.ustr(res_original.name), 'product_id': rec_id, 'location_id': data.location_id.id, 'lot_id': data.lot_id.id}, context=context)
line_data ={
'inventory_id' : inventory_id,
'product_qty' : data.new_quantity,
@@ -91,8 +91,8 @@ class stock_change_product_qty(osv.osv_memory):
'product_uom_id' : res_original.uom_id.id,
'prod_lot_id' : data.lot_id.id
}
- inventry_line_obj.create(cr , uid, line_data, context=context)
- inventry_obj.action_done(cr, uid, [inventory_id], context=context)
+ inventory_line_obj.create(cr , uid, line_data, context=context)
+ inventory_obj.action_done(cr, uid, [inventory_id], context=context)
return {}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/stock/wizard/stock_inventory_line_split.py b/addons/stock/wizard/stock_inventory_line_split.py
deleted file mode 100644
index e06bf0d11e8..00000000000
--- a/addons/stock/wizard/stock_inventory_line_split.py
+++ /dev/null
@@ -1,112 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL ().
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-##############################################################################
-
-from openerp.osv import fields, osv
-
-class stock_inventory_line_split(osv.osv_memory):
- _inherit = "stock.move.split"
- _name = "stock.inventory.line.split"
- _description = "Split inventory lines"
-
- _columns = {
- 'line_ids': fields.one2many('stock.inventory.line.split.lines', 'wizard_id', 'Serial Numbers'),
- 'line_exist_ids': fields.one2many('stock.inventory.line.split.lines', 'wizard_exist_id', 'Serial Numbers'),
- }
-
- def default_get(self, cr, uid, fields, context=None):
- if context is None:
- context = {}
- record_id = context and context.get('active_id',False)
- res = {}
- line = self.pool.get('stock.inventory.line').browse(cr, uid, record_id, context=context)
- if 'product_id' in fields:
- res.update({'product_id':line.product_id.id})
- if 'product_uom' in fields:
- res.update({'product_uom': line.product_uom_id.id})
- if 'qty' in fields:
- res.update({'qty': line.product_qty})
- return res
-
- def split(self, cr, uid, ids, line_ids, context=None):
- """ To split stock inventory lines according to serial numbers.
-
- :param line_ids: the ID or list of IDs of inventory lines we want to split
- """
- if context is None:
- context = {}
- assert context.get('active_model') == 'stock.inventory.line',\
- 'Incorrect use of the inventory line split wizard.'
- prodlot_obj = self.pool.get('stock.production.lot')
- ir_sequence_obj = self.pool.get('ir.sequence')
- line_obj = self.pool.get('stock.inventory.line')
- new_line = []
- for data in self.browse(cr, uid, ids, context=context):
- for inv_line in line_obj.browse(cr, uid, line_ids, context=context):
- line_qty = inv_line.product_qty
- quantity_rest = inv_line.product_qty
- new_line = []
- if data.use_exist:
- lines = [l for l in data.line_exist_ids if l]
- else:
- lines = [l for l in data.line_ids if l]
- for line in lines:
- quantity = line.quantity
- if quantity <= 0 or line_qty == 0:
- continue
- quantity_rest -= quantity
- if quantity_rest < 0:
- quantity_rest = quantity
- break
- default_val = {
- 'product_qty': quantity,
- }
- if quantity_rest > 0:
- current_line = line_obj.copy(cr, uid, inv_line.id, default_val)
- new_line.append(current_line)
- if quantity_rest == 0:
- current_line = inv_line.id
- lot_id = False
- if data.use_exist:
- lot_id = line.lot_id.id
- if not lot_id:
- lot_id = prodlot_obj.create(cr, uid, {
- 'name': line.name,
- 'product_id': inv_line.product_id.id},
- context=context)
- line_obj.write(cr, uid, [current_line], {'prod_lot_id': lot_id})
- prodlot = prodlot_obj.browse(cr, uid, lot_id)
-
- update_val = {}
- if quantity_rest > 0:
- update_val['product_qty'] = quantity_rest
- line_obj.write(cr, uid, [inv_line.id], update_val)
-
- return new_line
-
-class stock_inventory_split_lines(osv.osv_memory):
- _inherit = "stock.move.split.lines"
- _name = "stock.inventory.line.split.lines"
- _description = "Inventory Split lines"
- _columns = {
- 'wizard_id': fields.many2one('stock.inventory.line.split', 'Parent Wizard'),
- 'wizard_exist_id': fields.many2one('stock.inventory.line.split', 'Parent Wizard'),
- }
-
diff --git a/addons/stock/wizard/stock_inventory_line_split_view.xml b/addons/stock/wizard/stock_inventory_line_split_view.xml
deleted file mode 100644
index 1c41533c022..00000000000
--- a/addons/stock/wizard/stock_inventory_line_split_view.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-
-
-
-
- Split Inventory Line
- stock.inventory.line.split
-
-
-
-
-
-
- Split inventory lines
- ir.actions.act_window
- stock.inventory.line.split
- form
- form
- new
-
-
-
-
diff --git a/addons/stock/wizard/stock_inventory_merge.py b/addons/stock/wizard/stock_inventory_merge.py
deleted file mode 100644
index 9245628b040..00000000000
--- a/addons/stock/wizard/stock_inventory_merge.py
+++ /dev/null
@@ -1,91 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL ().
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-##############################################################################
-
-from openerp.osv import fields, osv
-from openerp.tools.translate import _
-
-class stock_inventory_merge(osv.osv_memory):
- _name = "stock.inventory.merge"
- _description = "Merge Inventory"
-
- def fields_view_get(self, cr, uid, view_id=None, view_type='form',
- context=None, toolbar=False, submenu=False):
- """
- Changes the view dynamically
- @param self: The object pointer.
- @param cr: A database cursor
- @param uid: ID of the user currently logged in
- @param context: A standard dictionary
- @return: New arch of view.
- """
- if context is None:
- context={}
- res = super(stock_inventory_merge, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar,submenu=False)
- if context.get('active_model','') == 'stock.inventory' and len(context['active_ids']) < 2:
- raise osv.except_osv(_('Warning!'),
- _('Please select multiple physical inventories to merge in the list view.'))
- return res
-
- def do_merge(self, cr, uid, ids, context=None):
- """ To merge selected Inventories.
- @param self: The object pointer.
- @param cr: A database cursor
- @param uid: ID of the user currently logged in
- @param ids: List of IDs selected
- @param context: A standard dictionary
- @return:
- """
- invent_obj = self.pool.get('stock.inventory')
- invent_line_obj = self.pool.get('stock.inventory.line')
- invent_lines = {}
- if context is None:
- context = {}
- for inventory in invent_obj.browse(cr, uid, context['active_ids'], context=context):
- if inventory.state == "done":
- raise osv.except_osv(_('Warning!'),
- _('Merging is only allowed on draft inventories.'))
-
- for line in inventory.inventory_line_id:
- key = (line.location_id.id, line.product_id.id, line.product_uom_id.id)
- if key in invent_lines:
- invent_lines[key] += line.product_qty
- else:
- invent_lines[key] = line.product_qty
-
-
- new_invent = invent_obj.create(cr, uid, {
- 'name': 'Merged inventory'
- }, context=context)
-
- for key, quantity in invent_lines.items():
- invent_line_obj.create(cr, uid, {
- 'inventory_id': new_invent,
- 'location_id': key[0],
- 'product_id': key[1],
- 'product_uom_id': key[2],
- 'product_qty': quantity,
- })
-
- return {'type': 'ir.actions.act_window_close'}
-
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-
diff --git a/addons/stock/wizard/stock_inventory_merge_view.xml b/addons/stock/wizard/stock_inventory_merge_view.xml
deleted file mode 100644
index bf661b426a2..00000000000
--- a/addons/stock/wizard/stock_inventory_merge_view.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
- stock.inventory.merge.form
- stock.inventory.merge
-
-
-
-
-
-
-
-
diff --git a/addons/stock/wizard/stock_move.py b/addons/stock/wizard/stock_move.py
index d230f469036..f4027b4131e 100644
--- a/addons/stock/wizard/stock_move.py
+++ b/addons/stock/wizard/stock_move.py
@@ -23,9 +23,11 @@ from openerp.osv import fields, osv
from openerp.tools.translate import _
import openerp.addons.decimal_precision as dp
-class stock_move_consume(osv.osv_memory):
- _name = "stock.move.consume"
- _description = "Consume Products"
+
+
+class stock_move_scrap(osv.osv_memory):
+ _name = "stock.move.scrap"
+ _description = "Scrap Products"
_columns = {
'product_id': fields.many2one('product.product', 'Product', required=True, select=True),
@@ -35,57 +37,6 @@ class stock_move_consume(osv.osv_memory):
'restrict_lot_id': fields.many2one('stock.production.lot', 'Lot'),
}
- #TOFIX: product_uom should not have differemt category of default UOM of product. Qty should be convert into UOM of original move line before going in consume and scrap
- def default_get(self, cr, uid, fields, context=None):
- """ Get default values
- @param self: The object pointer.
- @param cr: A database cursor
- @param uid: ID of the user currently logged in
- @param fields: List of fields for default value
- @param context: A standard dictionary
- @return: default values of fields
- """
- if context is None:
- context = {}
- res = super(stock_move_consume, self).default_get(cr, uid, fields, context=context)
- move = self.pool.get('stock.move').browse(cr, uid, context['active_id'], context=context)
- if 'product_id' in fields:
- res.update({'product_id': move.product_id.id})
- if 'product_uom' in fields:
- res.update({'product_uom': move.product_uom.id})
- if 'product_qty' in fields:
- res.update({'product_qty': move.product_qty})
- if 'location_id' in fields:
- res.update({'location_id': move.location_id.id})
-
- return res
-
- def do_move_consume(self, cr, uid, ids, context=None):
- """ To move consumed products
- @param self: The object pointer.
- @param cr: A database cursor
- @param uid: ID of the user currently logged in
- @param ids: the ID or list of IDs if we want more than one
- @param context: A standard dictionary
- @return:
- """
- if context is None:
- context = {}
- move_obj = self.pool.get('stock.move')
- move_ids = context['active_ids']
- for data in self.browse(cr, uid, ids, context=context):
- move_obj.action_consume(cr, uid, move_ids,
- data.product_qty, data.location_id.id, restrict_lot_id=data.restrict_lot_id and data.restrict_lot_id.id or False,
- context=context)
- return {'type': 'ir.actions.act_window_close'}
-
-
-
-class stock_move_scrap(osv.osv_memory):
- _name = "stock.move.scrap"
- _description = "Scrap Products"
- _inherit = "stock.move.consume"
-
_defaults = {
'location_id': lambda *x: False
}
@@ -101,8 +52,9 @@ class stock_move_scrap(osv.osv_memory):
"""
if context is None:
context = {}
- res = super(stock_move_consume, self).default_get(cr, uid, fields, context=context)
+ res = super(stock_move_scrap, self).default_get(cr, uid, fields, context=context)
move = self.pool.get('stock.move').browse(cr, uid, context['active_id'], context=context)
+
location_obj = self.pool.get('stock.location')
scrap_location_id = location_obj.search(cr, uid, [('scrap_location','=',True)])
@@ -115,7 +67,6 @@ class stock_move_scrap(osv.osv_memory):
res.update({'location_id': scrap_location_id[0]})
else:
res.update({'location_id': False})
-
return res
def move_scrap(self, cr, uid, ids, context=None):
@@ -139,137 +90,4 @@ class stock_move_scrap(osv.osv_memory):
-class split_in_production_lot(osv.osv_memory):
- _name = "stock.move.split"
- _description = "Split in Serial Numbers"
-
- def default_get(self, cr, uid, fields, context=None):
- if context is None:
- context = {}
- res = super(split_in_production_lot, self).default_get(cr, uid, fields, context=context)
- if context.get('active_id'):
- move = self.pool.get('stock.move').browse(cr, uid, context['active_id'], context=context)
- if 'product_id' in fields:
- res.update({'product_id': move.product_id.id})
- if 'product_uom' in fields:
- res.update({'product_uom': move.product_uom.id})
- if 'qty' in fields:
- res.update({'qty': move.product_qty})
- if 'use_exist' in fields:
- res.update({'use_exist': (move.picking_id and move.picking_id.type=='out' and True) or False})
- if 'location_id' in fields:
- res.update({'location_id': move.location_id.id})
- return res
-
- _columns = {
- 'qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure')),
- 'product_id': fields.many2one('product.product', 'Product', required=True, select=True),
- 'product_uom': fields.many2one('product.uom', 'Unit of Measure'),
- 'line_ids': fields.one2many('stock.move.split.lines', 'wizard_id', 'Serial Numbers'),
- 'line_exist_ids': fields.one2many('stock.move.split.lines', 'wizard_exist_id', 'Serial Numbers'),
- 'use_exist' : fields.boolean('Existing Serial Numbers',
- help="Check this option to select existing serial numbers in the list below, otherwise you should enter new ones line by line."),
- 'location_id': fields.many2one('stock.location', 'Source Location')
- }
-
- def split_lot(self, cr, uid, ids, context=None):
- """ To split a lot"""
- if context is None:
- context = {}
- res = self.split(cr, uid, ids, context.get('active_ids'), context=context)
- return {'type': 'ir.actions.act_window_close'}
-
- def split(self, cr, uid, ids, move_ids, context=None):
- """ To split stock moves into serial numbers
-
- :param move_ids: the ID or list of IDs of stock move we want to split
- """
- if context is None:
- context = {}
- assert context.get('active_model') == 'stock.move',\
- 'Incorrect use of the stock move split wizard'
- inventory_id = context.get('inventory_id', False)
- prodlot_obj = self.pool.get('stock.production.lot')
- inventory_obj = self.pool.get('stock.inventory')
- move_obj = self.pool.get('stock.move')
- new_move = []
- for data in self.browse(cr, uid, ids, context=context):
- for move in move_obj.browse(cr, uid, move_ids, context=context):
- move_qty = move.product_qty
- quantity_rest = move.product_qty
- uos_qty_rest = move.product_uos_qty
- new_move = []
- if data.use_exist:
- lines = [l for l in data.line_exist_ids if l]
- else:
- lines = [l for l in data.line_ids if l]
- total_move_qty = 0.0
- for line in lines:
- quantity = line.quantity
- total_move_qty += quantity
- if total_move_qty > move_qty:
- raise osv.except_osv(_('Processing Error!'), _('Serial number quantity %d of %s is larger than available quantity (%d)!') \
- % (total_move_qty, move.product_id.name, move_qty))
- if quantity <= 0 or move_qty == 0:
- continue
- quantity_rest -= quantity
- uos_qty = quantity / move_qty * move.product_uos_qty
- uos_qty_rest = quantity_rest / move_qty * move.product_uos_qty
- if quantity_rest < 0:
- quantity_rest = quantity
- self.pool.get('stock.move').log(cr, uid, move.id, _('Unable to assign all lots to this move!'))
- return False
- default_val = {
- 'product_qty': quantity,
- 'product_uos_qty': uos_qty,
- 'state': move.state
- }
- if quantity_rest > 0:
- current_move = move_obj.copy(cr, uid, move.id, default_val, context=context)
- if inventory_id and current_move:
- inventory_obj.write(cr, uid, inventory_id, {'move_ids': [(4, current_move)]}, context=context)
- new_move.append(current_move)
-
- if quantity_rest == 0:
- current_move = move.id
- lot_id = False
- if data.use_exist:
- lot_id = line.lot_id.id
- if not lot_id:
- lot_id = prodlot_obj.create(cr, uid, {
- 'name': line.name,
- 'product_id': move.product_id.id},
- context=context)
-
- move_obj.write(cr, uid, [current_move], {'lot_id': lot_id, 'state':move.state})
-
- update_val = {}
- if quantity_rest > 0:
- update_val['product_qty'] = quantity_rest
- update_val['product_uos_qty'] = uos_qty_rest
- update_val['state'] = move.state
- move_obj.write(cr, uid, [move.id], update_val)
-
- return new_move
-
-
-class stock_move_split_lines_exist(osv.osv_memory):
- _name = "stock.move.split.lines"
- _description = "Stock move Split lines"
- _columns = {
- 'name': fields.char('Serial Number', size=64),
- 'quantity': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure')),
- 'wizard_id': fields.many2one('stock.move.split', 'Parent Wizard'),
- 'wizard_exist_id': fields.many2one('stock.move.split', 'Parent Wizard (for existing lines)'),
- 'lot_id': fields.many2one('stock.production.lot', 'Serial Number'),
- }
- _defaults = {
- 'quantity': 1.0,
- }
-
- def onchange_lot_id(self, cr, uid, ids, lot_id=False, product_qty=False,
- loc_id=False, product_id=False, uom_id=False,context=None):
- return self.pool.get('stock.move').onchange_lot_id(cr, uid, [], lot_id, product_qty,
- loc_id, product_id, uom_id, context)
-
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/stock/wizard/stock_move_view.xml b/addons/stock/wizard/stock_move_view.xml
index 0a567be651e..8c90b27960f 100644
--- a/addons/stock/wizard/stock_move_view.xml
+++ b/addons/stock/wizard/stock_move_view.xml
@@ -1,41 +1,7 @@
-
-
-
- Consume Move
- stock.move.consume
-
-
-
-
-
-
- Consume Move
- ir.actions.act_window
- stock.move.consume
- form
- form
- new
-
+
Scrap Move
@@ -74,62 +40,5 @@
new
-
- Split in Serial Numbers
- stock.move.split
-
-
-
-
-
-
- Split in Serial Numbers
- ir.actions.act_window
- stock.move.split
- form
- form
- new
-
-
diff --git a/addons/stock/wizard/stock_return_picking.py b/addons/stock/wizard/stock_return_picking.py
index c16c0d51230..112b233bf1f 100644
--- a/addons/stock/wizard/stock_return_picking.py
+++ b/addons/stock/wizard/stock_return_picking.py
@@ -19,8 +19,6 @@
#
##############################################################################
-import time
-
from openerp.osv import osv, fields
from openerp.tools.translate import _
import openerp.addons.decimal_precision as dp
@@ -34,7 +32,7 @@ class stock_return_picking_line(osv.osv_memory):
'quantity': fields.float("Quantity", digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
'wizard_id': fields.many2one('stock.return.picking', string="Wizard"),
'move_id': fields.many2one('stock.move', "Move"),
- 'lot_id': fields.related('move_id', 'quant_ids', 'lot_id', type="many2one", relation='stock.production.lot', string='Serial Number', readonly=True),
+ 'lot_id': fields.many2one('stock.production.lot', 'Serial Number', help="Used to choose the lot/serial number of the product returned"),
}
@@ -43,6 +41,7 @@ class stock_return_picking(osv.osv_memory):
_description = 'Return Picking'
_columns = {
'product_return_moves': fields.one2many('stock.return.picking.line', 'wizard_id', 'Moves'),
+ 'move_dest_exists': fields.boolean('Chained Move Exists', readonly=True, help="Technical field used to hide help tooltip if not needed"),
}
def default_get(self, cr, uid, fields, context=None):
@@ -60,24 +59,33 @@ class stock_return_picking(osv.osv_memory):
context = {}
res = super(stock_return_picking, self).default_get(cr, uid, fields, context=context)
record_id = context and context.get('active_id', False) or False
+ uom_obj = self.pool.get('product.uom')
pick_obj = self.pool.get('stock.picking')
pick = pick_obj.browse(cr, uid, record_id, context=context)
+ quant_obj = self.pool.get("stock.quant")
+ chained_move_exist = False
if pick:
if pick.state != 'done':
raise osv.except_osv(_('Warning!'), _("You may only return pickings that are Done!"))
- for line in pick.move_lines:
- qty = line.product_uom_qty
- if line.returned_move_ids:
- for returned_move in line.returned_move_ids:
- if returned_move.product_id.id == line.product_id.id:
- qty -= returned_move.product_uom_qty
- if qty > 0:
- result1.append({'product_id': line.product_id.id, 'quantity': qty, 'move_id': line.id})
+ for move in pick.move_lines:
+ if move.move_dest_id:
+ chained_move_exist = True
+ #Sum the quants in that location that can be returned (they should have been moved by the moves that were included in the returned picking)
+ qty = 0
+ quant_search = quant_obj.search(cr, uid, [('history_ids', 'in', move.id), ('qty', '>', 0.0), ('location_id', 'child_of', move.location_dest_id.id)], context=context)
+ for quant in quant_obj.browse(cr, uid, quant_search, context=context):
+ if not quant.reservation_id or quant.reservation_id.origin_returned_move_id.id != move.id:
+ qty += quant.qty
+ qty = uom_obj._compute_qty(cr, uid, move.product_id.uom_id.id, qty, move.product_uom.id)
+ result1.append({'product_id': move.product_id.id, 'quantity': qty, 'move_id': move.id})
+
if len(result1) == 0:
raise osv.except_osv(_('Warning!'), _("No products to return (only lines in Done state and not fully returned yet can be returned)!"))
if 'product_return_moves' in fields:
res.update({'product_return_moves': result1})
+ if 'move_dest_exists' in fields:
+ res.update({'move_dest_exists': chained_move_exist})
return res
def _create_returns(self, cr, uid, ids, context=None):
@@ -92,16 +100,32 @@ class stock_return_picking(osv.osv_memory):
data = self.read(cr, uid, ids[0], context=context)
returned_lines = 0
+ # Cancel assignment of existing chained assigned moves
+ moves_to_unreserve = []
+ for move in pick.move_lines:
+ to_check_moves = [move.move_dest_id]
+ while to_check_moves:
+ current_move = to_check_moves.pop()
+ if current_move.state not in ('done', 'cancel') and current_move.reserved_quant_ids:
+ moves_to_unreserve.append(current_move.id)
+ split_move_ids = move_obj.search(cr, uid, [('split_from', '=', current_move.id)], context=context)
+ if split_move_ids:
+ to_check_moves += move_obj.browse(cr, uid, split_move_ids, context=context)
+
+ if moves_to_unreserve:
+ move_obj.do_unreserve(cr, uid, moves_to_unreserve, context=context)
+ #break the link between moves in order to be able to fix them later if needed
+ move_obj.write(cr, uid, moves_to_unreserve, {'move_orig_ids': False}, context=context)
+
#Create new picking for returned products
pick_type_id = pick.picking_type_id.return_picking_type_id and pick.picking_type_id.return_picking_type_id.id or pick.picking_type_id.id
-
new_picking = pick_obj.copy(cr, uid, pick.id, {
'move_lines': [],
'picking_type_id': pick_type_id,
'state': 'draft',
'origin': pick.name,
- },context=context)
-
+ }, context=context)
+
for data_get in data_obj.browse(cr, uid, data['product_return_moves'], context=context):
move = data_get.move_id
if not move:
@@ -109,9 +133,6 @@ class stock_return_picking(osv.osv_memory):
new_qty = data_get.quantity
if new_qty:
returned_lines += 1
- quant_ids = []
- for quant in move.quant_ids:
- quant_ids.append((4, quant.id))
move_obj.copy(cr, uid, move.id, {
'product_id': data_get.product_id.id,
'product_uom_qty': new_qty,
@@ -120,9 +141,11 @@ class stock_return_picking(osv.osv_memory):
'state': 'draft',
'location_id': move.location_dest_id.id,
'location_dest_id': move.location_id.id,
- 'reserved_quant_ids': quant_ids,
'origin_returned_move_id': move.id,
+ 'procure_method': 'make_to_stock',
+ 'restrict_lot_id': data_get.lot_id.id,
})
+
if not returned_lines:
raise osv.except_osv(_('Warning!'), _("Please specify at least one non-zero quantity."))
@@ -143,13 +166,13 @@ class stock_return_picking(osv.osv_memory):
new_picking_id, pick_type_id = self._create_returns(cr, uid, ids, context=context)
# Override the context to disable all the potential filters that could have been set previously
ctx = {
- 'search_default_picking_type_id': pick_type_id,
- 'search_default_draft': False,
- 'search_default_assigned': False,
- 'search_default_confirmed': False,
- 'search_default_ready': False,
- 'search_default_late': False,
- 'search_default_available': False,
+ 'search_default_picking_type_id': pick_type_id,
+ 'search_default_draft': False,
+ 'search_default_assigned': False,
+ 'search_default_confirmed': False,
+ 'search_default_ready': False,
+ 'search_default_late': False,
+ 'search_default_available': False,
}
return {
'domain': "[('id', 'in', [" + str(new_picking_id) + "])]",
diff --git a/addons/stock/wizard/stock_return_picking_view.xml b/addons/stock/wizard/stock_return_picking_view.xml
index 49b0c9a0e5c..b26918746a1 100644
--- a/addons/stock/wizard/stock_return_picking_view.xml
+++ b/addons/stock/wizard/stock_return_picking_view.xml
@@ -41,9 +41,18 @@
stock.return.picking
-
-
-
+
+
+
+ This picking appears to be chained with another operation. Later, if you receive the goods you are returning now, make sure to reverse the returned picking in order to avoid logistic rules to be applied again (which would create duplicated operations)
+
+
+
+
+
+
+
+
|