diff --git a/addons/mrp/stock.py b/addons/mrp/stock.py
index f1c3ba524f1..be17a61b6b1 100644
--- a/addons/mrp/stock.py
+++ b/addons/mrp/stock.py
@@ -3,6 +3,7 @@
import time
+from openerp import api
from openerp.osv import fields
from openerp.osv import osv
from openerp.tools.translate import _
@@ -246,7 +247,7 @@ class stock_warehouse(osv.osv):
raise UserError(_('Can\'t find any generic Manufacture route.'))
return {
- 'name': self._format_routename(cr, uid, warehouse, _(' Manufacture'), context=context),
+ 'name': warehouse._format_routename(_(' Manufacture')),
'location_id': warehouse.lot_stock_id.id,
'route_id': manufacture_route_id,
'action': 'manufacture',
@@ -283,12 +284,11 @@ class stock_warehouse(osv.osv):
pull_obj.unlink(cr, uid, warehouse.manufacture_pull_id.id, context=context)
return super(stock_warehouse, self).write(cr, uid, ids, vals, context=context)
- def get_all_routes_for_wh(self, cr, uid, ids, context=None):
- all_routes = super(stock_warehouse, self).get_all_routes_for_wh(cr, uid, ids, context=context)
- warehouse = self.browse(cr, uid, ids[0], context=context)
- if warehouse.manufacture_to_resupply and warehouse.manufacture_pull_id and warehouse.manufacture_pull_id.route_id:
- all_routes += [warehouse.manufacture_pull_id.route_id.id]
- return all_routes
+ @api.multi
+ def _get_all_routes(self):
+ routes = super(stock_warehouse, self).get_all_routes_for_wh()
+ routes |= self.filtered(lambda self: self.manufacture_to_resupply and self.manufacture_pull_id and self.manufacture_pull_id.route_id).mapped('manufacture_pull_id').mapped('route_id')
+ return routes
def _handle_renaming(self, cr, uid, ids, name, code, context=None):
res = super(stock_warehouse, self)._handle_renaming(cr, uid, ids, name, code, context=context)
diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py
index 2d8d63a490c..743a1684557 100644
--- a/addons/point_of_sale/models/pos_order.py
+++ b/addons/point_of_sale/models/pos_order.py
@@ -526,7 +526,7 @@ class PosOrder(models.Model):
destination_id = order.partner_id.property_stock_customer.id
else:
if (not picking_type) or (not picking_type.default_location_dest_id):
- customerloc, supplierloc = StockWarehouse._get_partner_locations([])
+ customerloc, supplierloc = StockWarehouse._get_partner_locations()
destination_id = customerloc.id
else:
destination_id = picking_type.default_location_dest_id.id
diff --git a/addons/product/product.py b/addons/product/product.py
index f04c53b2cd5..eebfd38a550 100644
--- a/addons/product/product.py
+++ b/addons/product/product.py
@@ -104,6 +104,7 @@ class product_uom(osv.osv):
('factor_gt_zero', 'CHECK (factor!=0)', 'The conversion ratio for a unit of measure cannot be 0!')
]
+ @api.cr_uid
def _compute_qty(self, cr, uid, from_uom_id, qty, to_uom_id=False, round=True, rounding_method='UP'):
if not from_uom_id or not qty or not to_uom_id:
return qty
diff --git a/addons/purchase/stock.py b/addons/purchase/stock.py
index 36eb8c45b45..168f4cdeef4 100644
--- a/addons/purchase/stock.py
+++ b/addons/purchase/stock.py
@@ -80,7 +80,7 @@ class stock_warehouse(osv.osv):
raise UserError(_('Can\'t find any generic Buy route.'))
return {
- 'name': self._format_routename(cr, uid, warehouse, _(' Buy'), context=context),
+ 'name': warehouse._format_routename(_(' Buy')),
'location_id': warehouse.in_type_id.default_location_dest_id.id,
'route_id': buy_route_id,
'action': 'buy',
@@ -117,12 +117,11 @@ class stock_warehouse(osv.osv):
buy_pull_id = pull_obj.unlink(cr, uid, warehouse.buy_pull_id.id, context=context)
return super(stock_warehouse, self).write(cr, uid, ids, vals, context=None)
- def get_all_routes_for_wh(self, cr, uid, ids, context=None):
- all_routes = super(stock_warehouse, self).get_all_routes_for_wh(cr, uid, ids, context=context)
- warehouse = self.browse(cr, uid, ids[0], context=context)
- if warehouse.buy_to_resupply and warehouse.buy_pull_id and warehouse.buy_pull_id.route_id:
- all_routes += [warehouse.buy_pull_id.route_id.id]
- return all_routes
+ @api.multi
+ def _get_all_routes(self):
+ routes = super(stock_warehouse, self).get_all_routes_for_wh()
+ routes |= self.filtered(lambda self: self.buy_to_resupply and self.buy_pull_id and self.buy_pull_id.route_id).mapped('buy_pull_id').mapped('route_id')
+ return routes
def _get_all_products_to_resupply(self, cr, uid, warehouse, context=None):
res = super(stock_warehouse, self)._get_all_products_to_resupply(cr, uid, warehouse, context=context)
@@ -143,9 +142,9 @@ class stock_warehouse(osv.osv):
pull_obj.write(cr, uid, warehouse.buy_pull_id.id, {'name': warehouse.buy_pull_id.name.replace(warehouse.name, name, 1)}, context=context)
return res
- def change_route(self, cr, uid, ids, new_reception_step=False, new_delivery_step=False, context=None):
- res = super(stock_warehouse, self).change_route(cr, uid, ids, new_reception_step=new_reception_step, new_delivery_step=new_delivery_step, context=context)
- warehouse = self.browse(cr, uid, ids[0], context=context)
- if warehouse.in_type_id.default_location_dest_id != warehouse.buy_pull_id.location_id:
- self.pool.get('procurement.rule').write(cr, uid, warehouse.buy_pull_id.id, {'location_id': warehouse.in_type_id.default_location_dest_id.id}, context=context)
+ def change_route(self, cr, uid, ids, context=None):
+ res = super(stock_warehouse, self).change_route(cr, uid, ids, context=context)
+ for warehouse in self.browse(cr, uid, ids[0], context=context):
+ if warehouse.in_type_id.default_location_dest_id != warehouse.buy_pull_id.location_id:
+ self.pool.get('procurement.rule').write(cr, uid, warehouse.buy_pull_id.id, {'location_id': warehouse.in_type_id.default_location_dest_id.id}, context=context)
return res
diff --git a/addons/sale_stock/sale_stock.py b/addons/sale_stock/sale_stock.py
index b4e5bceed22..979acce6a25 100644
--- a/addons/sale_stock/sale_stock.py
+++ b/addons/sale_stock/sale_stock.py
@@ -228,13 +228,13 @@ class SaleOrderLine(models.Model):
if wh_mto_route and wh_mto_route <= product_routes:
is_available = True
else:
- mto_route_id = False
+ mto_route = False
try:
- mto_route_id = self.env['stock.warehouse']._get_mto_route()
+ mto_route = self.env['stock.warehouse']._get_mto_route()
except UserError:
# if route MTO not found in ir_model_data, we treat the product as in MTS
pass
- if mto_route_id and mto_route_id in product_routes.ids:
+ if mto_route and mto_route in product_routes:
is_available = True
# Check Drop-Shipping
diff --git a/addons/stock/models/stock_warehouse.py b/addons/stock/models/stock_warehouse.py
index 763260046fc..e3f6f1407e5 100644
--- a/addons/stock/models/stock_warehouse.py
+++ b/addons/stock/models/stock_warehouse.py
@@ -1,770 +1,692 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from collections import namedtuple
from datetime import datetime
from dateutil import relativedelta
-from openerp.osv import fields, osv
-from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
-from openerp.tools.translate import _
-from openerp import SUPERUSER_ID
-import openerp.addons.decimal_precision as dp
-from openerp.exceptions import UserError
+from odoo import api, fields, models, _
+from odoo.addons import decimal_precision as dp
+from odoo.exceptions import UserError, ValidationError
+from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
-class stock_warehouse(osv.osv):
+class Warehouse(models.Model):
_name = "stock.warehouse"
_description = "Warehouse"
+ # namedtuple used in helper methods generating values for routes
+ Routing = namedtuple('Routing', ['from_loc', 'dest_loc', 'picking_type'])
- _columns = {
- 'name': fields.char('Warehouse Name', required=True, select=True),
- 'active': fields.boolean('Active'),
- 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, select=True, help='The company is automatically set from your user preferences.'),
- 'partner_id': fields.many2one('res.partner', 'Address'),
- 'view_location_id': fields.many2one('stock.location', 'View Location', required=True, domain=[('usage', '=', 'view')]),
- 'lot_stock_id': fields.many2one('stock.location', 'Location Stock', domain=[('usage', '=', 'internal')], required=True),
- 'code': fields.char('Short Name', size=5, required=True, help="Short name used to identify your warehouse"),
- 'route_ids': fields.many2many('stock.location.route', 'stock_route_warehouse', 'warehouse_id', 'route_id', 'Routes', domain="[('warehouse_selectable', '=', True)]", help='Defaults routes through the warehouse'),
- 'reception_steps': fields.selection([
- ('one_step', 'Receive goods directly in stock (1 step)'),
- ('two_steps', 'Unload in input location then go to stock (2 steps)'),
- ('three_steps', 'Unload in input location, go through a quality control before being admitted in stock (3 steps)')], 'Incoming Shipments',
- help="Default incoming route to follow", required=True),
- 'delivery_steps': fields.selection([
- ('ship_only', 'Ship directly from stock (Ship only)'),
- ('pick_ship', 'Bring goods to output location before shipping (Pick + Ship)'),
- ('pick_pack_ship', 'Make packages into a dedicated location, then bring them to the output location for shipping (Pick + Pack + Ship)')], 'Outgoing Shippings',
- help="Default outgoing route to follow", required=True),
- 'wh_input_stock_loc_id': fields.many2one('stock.location', 'Input Location'),
- 'wh_qc_stock_loc_id': fields.many2one('stock.location', 'Quality Control Location'),
- 'wh_output_stock_loc_id': fields.many2one('stock.location', 'Output Location'),
- 'wh_pack_stock_loc_id': fields.many2one('stock.location', 'Packing Location'),
- 'mto_pull_id': fields.many2one('procurement.rule', 'MTO rule'),
- 'pick_type_id': fields.many2one('stock.picking.type', 'Pick Type'),
- 'pack_type_id': fields.many2one('stock.picking.type', 'Pack Type'),
- 'out_type_id': fields.many2one('stock.picking.type', 'Out Type'),
- 'in_type_id': fields.many2one('stock.picking.type', 'In Type'),
- 'int_type_id': fields.many2one('stock.picking.type', 'Internal Type'),
- 'crossdock_route_id': fields.many2one('stock.location.route', 'Crossdock Route'),
- 'reception_route_id': fields.many2one('stock.location.route', 'Receipt Route'),
- 'delivery_route_id': fields.many2one('stock.location.route', 'Delivery Route'),
- 'resupply_wh_ids': fields.many2many('stock.warehouse', 'stock_wh_resupply_table', 'supplied_wh_id', 'supplier_wh_id', 'Resupply Warehouses'),
- 'resupply_route_ids': fields.one2many('stock.location.route', 'supplied_wh_id', 'Resupply Routes',
- help="Routes will be created for these resupply warehouses and you can select them on products and product categories"),
- 'default_resupply_wh_id': fields.many2one('stock.warehouse', 'Default Resupply Warehouse', help="Goods will always be resupplied from this warehouse"),
- }
+ name = fields.Char('Warehouse Name', index=True, required=True)
+ active = fields.Boolean('Active', default=True)
+ company_id = fields.Many2one(
+ 'res.company', 'Company', default=lambda self: self.env['res.company']._company_default_get('stock.inventory'),
+ index=True, readonly=True, required=True,
+ help='The company is automatically set from your user preferences.')
+ partner_id = fields.Many2one('res.partner', 'Address')
+ view_location_id = fields.Many2one('stock.location', 'View Location', domain=[('usage', '=', 'view')], required=True)
+ lot_stock_id = fields.Many2one('stock.location', 'Location Stock', domain=[('usage', '=', 'internal')], required=True)
+ code = fields.Char('Short Name', required=True, size=5, help="Short name used to identify your warehouse")
+ route_ids = fields.Many2many(
+ 'stock.location.route', 'stock_route_warehouse', 'warehouse_id', 'route_id',
+ 'Routes', domain="[('warehouse_selectable', '=', True)]",
+ help='Defaults routes through the warehouse')
+ reception_steps = fields.Selection([
+ ('one_step', 'Receive goods directly in stock (1 step)'),
+ ('two_steps', 'Unload in input location then go to stock (2 steps)'),
+ ('three_steps', 'Unload in input location, go through a quality control before being admitted in stock (3 steps)')],
+ 'Incoming Shipments', default='one_step', required=True,
+ help="Default incoming route to follow")
+ delivery_steps = fields.Selection([
+ ('ship_only', 'Ship directly from stock (Ship only)'),
+ ('pick_ship', 'Bring goods to output location before shipping (Pick + Ship)'),
+ ('pick_pack_ship', 'Make packages into a dedicated location, then bring them to the output location for shipping (Pick + Pack + Ship)')],
+ 'Outgoing Shippings', default='ship_only', required=True,
+ help="Default outgoing route to follow")
+ wh_input_stock_loc_id = fields.Many2one('stock.location', 'Input Location')
+ wh_qc_stock_loc_id = fields.Many2one('stock.location', 'Quality Control Location')
+ wh_output_stock_loc_id = fields.Many2one('stock.location', 'Output Location')
+ wh_pack_stock_loc_id = fields.Many2one('stock.location', 'Packing Location')
+ mto_pull_id = fields.Many2one('procurement.rule', 'MTO rule')
+ pick_type_id = fields.Many2one('stock.picking.type', 'Pick Type')
+ pack_type_id = fields.Many2one('stock.picking.type', 'Pack Type')
+ out_type_id = fields.Many2one('stock.picking.type', 'Out Type')
+ in_type_id = fields.Many2one('stock.picking.type', 'In Type')
+ int_type_id = fields.Many2one('stock.picking.type', 'Internal Type')
+ crossdock_route_id = fields.Many2one('stock.location.route', 'Crossdock Route')
+ reception_route_id = fields.Many2one('stock.location.route', 'Receipt Route')
+ delivery_route_id = fields.Many2one('stock.location.route', 'Delivery Route')
+ resupply_wh_ids = fields.Many2many(
+ 'stock.warehouse', 'stock_wh_resupply_table', 'supplied_wh_id', 'supplier_wh_id',
+ 'Resupply Warehouses')
+ resupply_route_ids = fields.One2many(
+ 'stock.location.route', 'supplied_wh_id', 'Resupply Routes',
+ help="Routes will be created for these resupply warehouses and you can select them on products and product categories")
+ default_resupply_wh_id = fields.Many2one(
+ 'stock.warehouse', 'Default Resupply Warehouse',
+ help="Goods will always be resupplied from this warehouse")
- def onchange_filter_default_resupply_wh_id(self, cr, uid, ids, default_resupply_wh_id, resupply_wh_ids, context=None):
- resupply_wh_ids = set([x['id'] for x in (self.resolve_2many_commands(cr, uid, 'resupply_wh_ids', resupply_wh_ids, ['id']))])
- if default_resupply_wh_id: #If we are removing the default resupply, we don't have default_resupply_wh_id
- resupply_wh_ids.add(default_resupply_wh_id)
- resupply_wh_ids = list(resupply_wh_ids)
- return {'value': {'resupply_wh_ids': resupply_wh_ids}}
-
- def _get_external_transit_location(self, cr, uid, ids, context=None):
- ''' returns browse record of inter company transit location, if found'''
- warehouse = self.browse(cr, uid, ids[0], context=context)
- data_obj = self.pool.get('ir.model.data')
- location_obj = self.pool.get('stock.location')
- try:
- inter_wh_loc = data_obj.get_object_reference(cr, uid, 'stock', 'stock_location_inter_wh')[1]
- except:
- return False
- return location_obj.browse(cr, uid, inter_wh_loc, context=context)
-
- def _get_inter_wh_route(self, cr, uid, ids, wh, context=None):
- warehouse = self.browse(cr, uid, ids[0], context=context)
- return {
- 'name': _('%s: Supply Product from %s') % (warehouse.name, wh.name),
- 'warehouse_selectable': False,
- 'product_selectable': True,
- 'product_categ_selectable': True,
- 'supplied_wh_id': warehouse.id,
- 'supplier_wh_id': wh.id,
- }
-
- def _create_resupply_routes(self, cr, uid, ids, supplier_warehouses, default_resupply_wh, context=None):
- warehouse = self.browse(cr, uid, ids, context=context)[0]
- route_obj = self.pool.get('stock.location.route')
- pull_obj = self.pool.get('procurement.rule')
- #create route selectable on the product to resupply the warehouse from another one
- external_transit_location = warehouse._get_external_transit_location()
- internal_transit_location = warehouse.company_id.internal_transit_location_id
- input_loc = warehouse.wh_input_stock_loc_id
- if warehouse.reception_steps == 'one_step':
- input_loc = warehouse.lot_stock_id
- for wh in supplier_warehouses:
- transit_location = wh.company_id.id == warehouse.company_id.id and internal_transit_location or external_transit_location
- if transit_location:
- output_loc = wh.wh_output_stock_loc_id
- if wh.delivery_steps == 'ship_only':
- output_loc = wh.lot_stock_id
- # Create extra MTO rule (only for 'ship only' because in the other cases MTO rules already exists)
- mto_pull_vals = wh._get_mto_pull_rule([(output_loc, transit_location, wh.out_type_id.id)])[0]
- pull_obj.create(cr, uid, mto_pull_vals, context=context)
- inter_wh_route_vals = warehouse._get_inter_wh_route(wh)
- inter_wh_route_id = route_obj.create(cr, uid, vals=inter_wh_route_vals, context=context)
- values = [(output_loc, transit_location, wh.out_type_id.id, wh), (transit_location, input_loc, warehouse.in_type_id.id, warehouse)]
- pull_rules_list = self._get_supply_pull_rules(cr, uid, [wh.id], values, inter_wh_route_id, context=context)
- for pull_rule in pull_rules_list:
- pull_obj.create(cr, uid, vals=pull_rule, context=context)
- #if the warehouse is also set as default resupply method, assign this route automatically to the warehouse
- if default_resupply_wh and default_resupply_wh.id == wh.id:
- self.write(cr, uid, [warehouse.id, wh.id], {'route_ids': [(4, inter_wh_route_id)]}, context=context)
-
- _defaults = {
- 'active': True,
- 'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.inventory', context=c),
- 'reception_steps': 'one_step',
- 'delivery_steps': 'ship_only',
- }
_sql_constraints = [
('warehouse_name_uniq', 'unique(name, company_id)', 'The name of the warehouse must be unique per company!'),
('warehouse_code_uniq', 'unique(code, company_id)', 'The code of the warehouse must be unique per company!'),
]
- def _get_partner_locations(self, cr, uid, ids, context=None):
- ''' returns a tuple made of the browse record of customer location and the browse record of supplier location'''
- data_obj = self.pool.get('ir.model.data')
- location_obj = self.pool.get('stock.location')
- try:
- customer_loc = data_obj.get_object_reference(cr, uid, 'stock', 'stock_location_customers')[1]
- supplier_loc = data_obj.get_object_reference(cr, uid, 'stock', 'stock_location_suppliers')[1]
- except:
- customer_loc = location_obj.search(cr, uid, [('usage', '=', 'customer')], context=context)
- customer_loc = customer_loc and customer_loc[0] or False
- supplier_loc = location_obj.search(cr, uid, [('usage', '=', 'supplier')], context=context)
- supplier_loc = supplier_loc and supplier_loc[0] or False
- if not (customer_loc and supplier_loc):
- raise UserError(_('Can\'t find any customer or supplier location.'))
- return location_obj.browse(cr, uid, [customer_loc, supplier_loc], context=context)
+ @api.depends('default_resupply_wh_id', 'resupply_wh_ids')
+ def onchange_resupply_warehouses(self):
+ # If we are removing the default resupply, we don't have default_resupply_wh_id # TDE note: and we want one
+ self.resupply_wh_ids |= self.default_resupply_wh_id
- def _location_used(self, cr, uid, ids, location_id, context=None):
- warehouse = self.browse(cr, uid, ids[0], context=context)
- pull_obj = self.pool['procurement.rule']
- push_obj = self.pool['stock.location.path']
+ @api.model
+ def create(self, vals):
+ # create view location for warehouse then create all locations
+ loc_vals = {'name': _(vals.get('code')), 'usage': 'view',
+ 'location_id': self.env.ref('stock.stock_location_locations').id}
+ if vals.get('company_id'):
+ loc_vals['company_id'] = vals.get('company_id')
+ vals['view_location_id'] = self.env['stock.location'].create(loc_vals).id
- domain = ['&', ('route_id', 'not in', [x.id for x in warehouse.route_ids]),
- '|', ('location_src_id', '=', location_id), # noqa
- ('location_id', '=', location_id)
- ]
- pulls = pull_obj.search_count(cr, uid, domain, context=context)
+ def_values = self.default_get(['reception_steps', 'delivery_steps'])
+ reception_steps = vals.get('reception_steps', def_values['reception_steps'])
+ delivery_steps = vals.get('delivery_steps', def_values['delivery_steps'])
+ sub_locations = {
+ 'lot_stock_id': {'name': _('Stock'), 'active': True, 'usage': 'internal'},
+ 'wh_input_stock_loc_id': {'name': _('Input'), 'active': reception_steps != 'one_step', 'usage': 'internal'},
+ 'wh_qc_stock_loc_id': {'name': _('Quality Control'), 'active': reception_steps == 'three_steps', 'usage': 'internal'},
+ 'wh_output_stock_loc_id': {'name': _('Output'), 'active': delivery_steps != 'ship_only', 'usage': 'internal'},
+ 'wh_pack_stock_loc_id': {'name': _('Packing Zone'), 'active': delivery_steps == 'pick_pack_ship', 'usage': 'internal'},
+ }
+ for field_name, values in sub_locations.iteritems():
+ values['location_id'] = vals['view_location_id']
+ if vals.get('company_id'):
+ values['company_id'] = vals.get('company_id')
+ vals[field_name] = self.env['stock.location'].with_context(active_test=False).create(values).id
- domain = ['&', ('route_id', 'not in', [x.id for x in warehouse.route_ids]),
- '|', ('location_from_id', '=', location_id), # noqa
- ('location_dest_id', '=', location_id)
- ]
- pushs = push_obj.search_count(cr, uid, domain, context=context)
- if pulls or pushs:
- return True
- return False
+ # actually create WH
+ warehouse = super(Warehouse, self).create(vals)
+ # create sequences and picking types
+ new_vals = warehouse.create_sequences_and_picking_types()
+ warehouse.write(new_vals) # TDE FIXME: use super ?
+ # create routes and push/procurement rules
+ route_vals = warehouse.create_routes()
+ warehouse.write(route_vals)
+ # update partner data if partner assigned
+ if vals.get('partner_id'):
+ self._update_partner_data(vals['partner_id'], vals.get('company_id'))
+ return warehouse
- def switch_location(self, cr, uid, ids, new_reception_step=False, new_delivery_step=False, context=None):
- warehouse = self.browse(cr, uid, ids[0], context=context)
- location_obj = self.pool.get('stock.location')
+ @api.multi
+ def write(self, vals):
+ Route = self.env['stock.location.route']
+ warehouses = self.with_context(active_test=False) # TDE FIXME: check this
- new_reception_step = new_reception_step or warehouse.reception_steps
- new_delivery_step = new_delivery_step or warehouse.delivery_steps
- if warehouse.reception_steps != new_reception_step:
- if not warehouse._location_used(warehouse.wh_input_stock_loc_id.id):
- location_obj.write(cr, uid, [warehouse.wh_input_stock_loc_id.id, warehouse.wh_qc_stock_loc_id.id], {'active': False}, context=context)
- if new_reception_step != 'one_step':
- location_obj.write(cr, uid, warehouse.wh_input_stock_loc_id.id, {'active': True}, context=context)
- if new_reception_step == 'three_steps':
- location_obj.write(cr, uid, warehouse.wh_qc_stock_loc_id.id, {'active': True}, context=context)
+ if vals.get('code') or vals.get('name'):
+ warehouses._update_name_and_code(vals.get('name'), vals.get('code'))
- if warehouse.delivery_steps != new_delivery_step:
- if not warehouse._location_used(warehouse.wh_output_stock_loc_id.id):
- location_obj.write(cr, uid, [warehouse.wh_output_stock_loc_id.id], {'active': False}, context=context)
- if not warehouse._location_used(warehouse.wh_pack_stock_loc_id.id):
- location_obj.write(cr, uid, [warehouse.wh_pack_stock_loc_id.id], {'active': False}, context=context)
- if new_delivery_step != 'ship_only':
- location_obj.write(cr, uid, warehouse.wh_output_stock_loc_id.id, {'active': True}, context=context)
- if new_delivery_step == 'pick_pack_ship':
- location_obj.write(cr, uid, warehouse.wh_pack_stock_loc_id.id, {'active': True}, context=context)
- return True
+ # activate and deactivate location according to reception and delivery option
+ if vals.get('reception_steps'):
+ warehouses._update_location_reception(vals['reception_steps'])
+ if vals.get('delivery_steps'):
+ warehouses._update_location_delivery(vals['delivery_steps'])
+ if vals.get('reception_steps') or vals.get('delivery_steps'):
+ warehouses._update_reception_delivery_resupply(vals.get('reception_steps'), vals.get('delivery_steps'))
- def _get_reception_delivery_route(self, cr, uid, ids, route_name, context=None):
- warehouse = self.browse(cr, uid, ids, context=context)[0]
+ if vals.get('resupply_wh_ids') and not vals.get('resupply_route_ids'):
+ resupply_whs = self.resolve_2many_commands('resupply_wh_ids', vals['resupply_wh_ids'])
+ new_resupply_whs = self.browse([wh['id'] for wh in resupply_whs])
+ old_resupply_whs = {(warehouse.id, warehouse.resupply_wh_ids) for warehouse in warehouses}
+
+ if 'default_resupply_wh_id' in vals:
+ if vals.get('default_resupply_wh_id') and any(vals['default_resupply_wh_id'] == warehouse.id for warehouse in warehouses):
+ raise UserError(_('The default resupply warehouse should be different than the warehouse itself!'))
+ for warehouse in warehouses.filtered(lambda wh: wh.default_resupply_wh_id):
+ # remove the existing resupplying route on the warehouse
+ to_remove_routes = Route.search([('supplied_wh_id', '=', warehouse.id), ('supplier_wh_id', '=', warehouse.default_resupply_wh_id.id)])
+ for inter_wh_route in to_remove_routes:
+ warehouse.write({'route_ids': [(3, inter_wh_route.id)]})
+
+ # If another partner assigned
+ if vals.get('partner_id'):
+ warehouses._update_partner_data(vals['partner_id'], vals.get('company_id'))
+ res = super(Warehouse, self).write(vals)
+
+ # check if we need to delete and recreate route
+ if vals.get('reception_steps') or vals.get('delivery_steps'):
+ warehouses._update_routes()
+
+ if vals.get('resupply_wh_ids') and not vals.get('resupply_route_ids'):
+ for warehouse in warehouses:
+ to_add = new_resupply_whs - old_resupply_whs[warehouse.id]
+ to_remove = old_resupply_whs[warehouse.id] - new_resupply_whs
+ if to_add:
+ warehouse.create_resupply_routes(to_add, warehouse.default_resupply_wh_id)
+ if to_remove:
+ Route.search([('supplied_wh_id', '=', warehouse.id), ('supplier_wh_id', 'in', to_remove.ids)]).unlink()
+ # TDE FIXME: shouldn't we remove procurement rules also ? because this could make them global (not sure)
+
+ return res
+
+ @api.model
+ def _update_partner_data(self, partner_id, company_id):
+ if not partner_id:
+ return
+ ResCompany = self.env['res.company']
+ if company_id:
+ transit_loc = ResCompany.browse(company_id).internal_transit_location_id.id
+ else:
+ transit_loc = ResCompany._company_default_get('stock.warehouse').internal_transit_location_id.id
+ self.env['res.partner'].browse(partner_id).write({'property_stock_customer': transit_loc, 'property_stock_supplier': transit_loc})
+
+ def create_sequences_and_picking_types(self):
+ IrSequenceSudo = self.env['ir.sequence'].sudo()
+ PickingType = self.env['stock.picking.type']
+
+ input_loc, output_loc = self._get_input_output_locations(self.reception_steps, self.delivery_steps)
+
+ # choose the next available color for the picking types of this warehouse
+ all_used_colors = [res['color'] for res in PickingType.search_read([('warehouse_id', '!=', False), ('color', '!=', False)], ['color'], order='color')]
+ available_colors = [zef for zef in [0, 3, 4, 5, 6, 7, 8, 1, 2] if zef not in all_used_colors]
+ color = available_colors and available_colors[0] or 0
+
+ # suit for each warehouse: reception, internal, pick, pack, ship
+ max_sequence = PickingType.search_read([], ['sequence'], limit=1, order='sequence desc')
+ max_sequence = max_sequence and max_sequence[0]['sequence'] or 0
+
+ warehouse_data = {}
+ sequence_data = self._get_sequence_values()
+ # tde todo: backport sequence fix
+ create_data = {
+ 'in_type_id': {
+ 'name': _('Receipts'),
+ 'code': 'incoming',
+ 'use_create_lots': True,
+ 'use_existing_lots': False,
+ 'default_location_src_id': False,
+ 'sequence': max_sequence + 1,
+ }, 'out_type_id': {
+ 'name': _('Delivery Orders'),
+ 'code': 'outgoing',
+ 'use_create_lots': False,
+ 'use_existing_lots': True,
+ 'default_location_dest_id': False,
+ 'sequence': max_sequence + 5,
+ }, 'pack_type_id': {
+ 'name': _('Pack'),
+ 'code': 'internal',
+ 'use_create_lots': False,
+ 'use_existing_lots': True,
+ 'default_location_src_id': self.wh_pack_stock_loc_id.id,
+ 'default_location_dest_id': output_loc.id,
+ 'sequence': max_sequence + 4,
+ }, 'pick_type_id': {
+ 'name': _('Pick'),
+ 'code': 'internal',
+ 'use_create_lots': False,
+ 'use_existing_lots': True,
+ 'default_location_src_id': self.lot_stock_id.id,
+ 'sequence': max_sequence + 3,
+ }, 'int_type_id': {
+ 'name': _('Internal Transfers'),
+ 'code': 'internal',
+ 'use_create_lots': False,
+ 'use_existing_lots': True,
+ 'default_location_src_id': self.lot_stock_id.id,
+ 'default_location_dest_id': self.lot_stock_id.id,
+ 'active': self.reception_steps != 'one_step' or self.delivery_steps != 'ship_only' or self.user_has_groups('stock.group_stock_multi_locations'),
+ 'sequence': max_sequence + 2,
+ },
+ }
+ data = self._get_picking_type_values(self.reception_steps, self.delivery_steps, self.wh_pack_stock_loc_id)
+ for field_name, values in data.iteritems():
+ data[field_name].update(create_data[field_name])
+
+ for picking_type, values in data.iteritems():
+ sequence = IrSequenceSudo.create(sequence_data[picking_type])
+ values.update(warehouse_id=self.id, color=color, sequence_id=sequence.id)
+ warehouse_data[picking_type] = PickingType.create(values).id
+ PickingType.browse(warehouse_data['out_type_id']).write({'return_picking_type_id': warehouse_data['in_type_id']})
+ PickingType.browse(warehouse_data['in_type_id']).write({'return_picking_type_id': warehouse_data['out_type_id']})
+ return warehouse_data
+
+ @api.multi
+ def create_routes(self):
+ self.ensure_one()
+ routes_data = self.get_routes_dict()
+
+ reception_route = self._create_or_update_reception_route(routes_data)
+ delivery_route = self._create_or_update_delivery_route(routes_data)
+ mto_pull = self._create_or_update_mto_pull(routes_data)
+ crossdock_route = self._create_or_update_crossdock_route(routes_data)
+
+ # create route selectable on the product to resupply the warehouse from another one
+ self.create_resupply_routes(self.resupply_wh_ids, self.default_resupply_wh_id)
+
+ # return routes and mto procurement rule to store on the warehouse
return {
- 'name': self._format_routename(cr, uid, warehouse, route_name, context=context),
+ 'route_ids': [(4, route.id) for route in reception_route | delivery_route | crossdock_route],
+ 'mto_pull_id': mto_pull.id,
+ 'reception_route_id': reception_route.id,
+ 'delivery_route_id': delivery_route.id,
+ 'crossdock_route_id': crossdock_route.id,
+ }
+
+ def _create_or_update_reception_route(self, routes_data):
+ routes_data = routes_data or self.get_routes_dict()
+ for warehouse in self:
+ if warehouse.reception_route_id:
+ reception_route = warehouse.reception_route_id
+ reception_route.write({'name': warehouse._format_routename(route_type=warehouse.reception_steps)})
+ reception_route.pull_ids.unlink()
+ reception_route.push_ids.unlink()
+ else:
+ reception_route = self.env['stock.location.route'].create(warehouse._get_reception_delivery_route_values(warehouse.reception_steps))
+ # push / procurement (pull) rules for reception
+ routings = routes_data[warehouse.id][warehouse.reception_steps]
+ push_rules_list, pull_rules_list = warehouse._get_push_pull_rules_values(
+ routings, values={'active': True, 'route_id': reception_route.id},
+ push_values=None, pull_values={'procure_method': 'make_to_order'})
+ for push_vals in push_rules_list:
+ self.env['stock.location.path'].create(push_vals)
+ for pull_vals in pull_rules_list:
+ self.env['procurement.rule'].create(pull_vals)
+ return reception_route
+
+ def _create_or_update_delivery_route(self, routes_data):
+ """ Delivery (MTS) route """
+ routes_data = routes_data or self.get_routes_dict()
+ for warehouse in self:
+ if warehouse.delivery_route_id:
+ delivery_route = warehouse.delivery_route_id
+ delivery_route.write({'name': warehouse._format_routename(route_type=warehouse.delivery_steps)})
+ delivery_route.pull_ids.unlink()
+ else:
+ delivery_route = self.env['stock.location.route'].create(warehouse._get_reception_delivery_route_values(warehouse.delivery_steps))
+ # procurement (pull) rules for delivery
+ routings = routes_data[warehouse.id][warehouse.delivery_steps]
+ dummy, pull_rules_list = warehouse._get_push_pull_rules_values(
+ routings, values={'active': True, 'route_id': delivery_route.id})
+ for pull_vals in pull_rules_list:
+ self.env['procurement.rule'].create(pull_vals)
+ return delivery_route
+
+ def _create_or_update_mto_pull(self, routes_data):
+ """ Create MTO procurement rule and link it to the generic MTO route """
+ routes_data = routes_data or self.get_routes_dict()
+ for warehouse in self:
+ routings = routes_data[warehouse.id][warehouse.delivery_steps]
+ if warehouse.mto_pull_id:
+ mto_pull = warehouse.mto_pull_id
+ mto_pull.write(warehouse._get_mto_pull_rules_values(routings)[0])
+ else:
+ mto_pull = self.env['procurement.rule'].create(warehouse._get_mto_pull_rules_values(routings)[0])
+ return mto_pull
+
+ def _create_or_update_crossdock_route(self, routes_data):
+ """ Create or update the cross dock operations route, that can be set on
+ products and product categories """
+ routes_data = routes_data or self.get_routes_dict()
+ for warehouse in self:
+ if warehouse.crossdock_route_id:
+ crossdock_route = warehouse.crossdock_route_id
+ crossdock_route.write({'active': warehouse.reception_steps != 'one_step' and warehouse.delivery_steps != 'ship_only'})
+ else:
+ crossdock_route = self.env['stock.location.route'].create(warehouse._get_crossdock_route_values())
+ # note: fixed cross-dock is logically mto
+ routings = routes_data[warehouse.id]['crossdock']
+ dummy, pull_rules_list = warehouse._get_push_pull_rules_values(
+ routings,
+ values={'active': warehouse.delivery_steps != 'ship_only' and warehouse.reception_steps != 'one_step', 'route_id': crossdock_route.id},
+ push_values=None, pull_values={'procure_method': 'make_to_order'})
+ for pull_vals in pull_rules_list:
+ self.env['procurement.rule'].create(pull_vals)
+ return crossdock_route
+
+ def create_resupply_routes(self, supplier_warehouses, default_resupply_wh):
+ Route = self.env['stock.location.route']
+ Pull = self.env['procurement.rule']
+
+ input_location, output_location = self._get_input_output_locations(self.reception_steps, self.delivery_steps)
+ internal_transit_location, external_transit_location = self._get_transit_locations()
+
+ for supplier_wh in supplier_warehouses:
+ transit_location = internal_transit_location if supplier_wh.company_id == self.company_id else external_transit_location
+ if not transit_location:
+ continue
+ output_location = supplier_wh.lot_stock_id if supplier_wh.delivery_steps == 'ship_only' else supplier_wh.wh_output_stock_loc_id
+ # Create extra MTO rule (only for 'ship only' because in the other cases MTO rules already exists)
+ if supplier_wh.delivery_steps == 'ship_only':
+ Pull.create(supplier_wh._get_mto_pull_rules_values([
+ self.Routing(output_location, transit_location, supplier_wh.out_type_id)])[0])
+
+ inter_wh_route = Route.create(self._get_inter_warehouse_route_values(supplier_wh))
+
+ pull_rules_list = supplier_wh._get_supply_pull_rules_values(
+ [self.Routing(output_location, transit_location, supplier_wh.out_type_id)],
+ values={'route_id': inter_wh_route.id, 'propagate_warehouse_id': self.id})
+ pull_rules_list += self._get_supply_pull_rules_values(
+ [self.Routing(transit_location, input_location, self.in_type_id)],
+ values={'route_id': inter_wh_route.id, 'propagate_warehouse_id': supplier_wh.id})
+ for pull_rule_vals in pull_rules_list:
+ Pull.create(pull_rule_vals)
+
+ # if the warehouse is also set as default resupply method, assign this route automatically to the warehouse
+ if supplier_wh == default_resupply_wh:
+ (self | supplier_wh).write({'route_ids': [(4, inter_wh_route.id)]})
+
+ # Routing tools
+ # ------------------------------------------------------------
+
+ def _get_input_output_locations(self, reception_steps, delivery_steps):
+ return (self.lot_stock_id if reception_steps == 'one_step' else self.wh_input_stock_loc_id,
+ self.lot_stock_id if delivery_steps == 'ship_only' else self.wh_output_stock_loc_id)
+
+ def _get_transit_locations(self):
+ return self.company_id.internal_transit_location_id, self.env.ref('stock.stock_location_inter_wh', raise_if_not_found=False) or self.env['stock.location']
+
+ @api.model
+ def _get_partner_locations(self):
+ ''' returns a tuple made of the browse record of customer location and the browse record of supplier location'''
+ Location = self.env['stock.location']
+ customer_loc = self.env.ref('stock.stock_location_customers', raise_if_not_found=False)
+ supplier_loc = self.env.ref('stock.stock_location_suppliers', raise_if_not_found=False)
+ if not customer_loc:
+ customer_loc = Location.search([('usage', '=', 'customer')], limit=1)
+ if not supplier_loc:
+ supplier_loc = Location.search([('usage', '=', 'supplier')], limit=1)
+ if not customer_loc and not supplier_loc:
+ raise UserError(_('Can\'t find any customer or supplier location.'))
+ return customer_loc, supplier_loc
+
+ def _get_route_name(self, route_type):
+ names = {'one_step': _('Receipt in 1 step'), 'two_steps': _('Receipt in 2 steps'),
+ 'three_steps': _('Receipt in 3 steps'), 'crossdock': _('Cross-Dock'),
+ 'ship_only': _('Ship Only'), 'pick_ship': _('Pick + Ship'),
+ 'pick_pack_ship': _('Pick + Pack + Ship')}
+ return names[route_type]
+
+ def get_routes_dict(self):
+ # TDE todo: rename me (was get_routes_dict)
+ customer_loc, supplier_loc = self._get_partner_locations()
+ return dict((warehouse.id, {
+ 'one_step': [],
+ 'two_steps': [self.Routing(warehouse.wh_input_stock_loc_id, warehouse.lot_stock_id, warehouse.int_type_id)],
+ 'three_steps': [
+ self.Routing(warehouse.wh_input_stock_loc_id, warehouse.wh_qc_stock_loc_id, warehouse.int_type_id),
+ self.Routing(warehouse.wh_qc_stock_loc_id, warehouse.lot_stock_id, warehouse.int_type_id)],
+ 'crossdock': [
+ self.Routing(warehouse.wh_input_stock_loc_id, warehouse.wh_output_stock_loc_id, warehouse.int_type_id),
+ self.Routing(warehouse.wh_output_stock_loc_id, customer_loc, warehouse.out_type_id)],
+ 'ship_only': [self.Routing(warehouse.lot_stock_id, customer_loc, warehouse.out_type_id)],
+ 'pick_ship': [
+ self.Routing(warehouse.lot_stock_id, warehouse.wh_output_stock_loc_id, warehouse.pick_type_id),
+ self.Routing(warehouse.wh_output_stock_loc_id, customer_loc, warehouse.out_type_id)],
+ 'pick_pack_ship': [
+ self.Routing(warehouse.lot_stock_id, warehouse.wh_pack_stock_loc_id, warehouse.pick_type_id),
+ self.Routing(warehouse.wh_pack_stock_loc_id, warehouse.wh_output_stock_loc_id, warehouse.pack_type_id),
+ self.Routing(warehouse.wh_output_stock_loc_id, customer_loc, warehouse.out_type_id)],
+ }) for warehouse in self)
+
+ @api.multi
+ def _get_reception_delivery_route_values(self, route_type):
+ return {
+ 'name': self._format_routename(route_type=route_type),
'product_categ_selectable': True,
'product_selectable': False,
'sequence': 10,
}
- def _get_supply_pull_rules(self, cr, uid, ids, values, new_route_id, context=None):
- supply_warehouse = ids[0]
- pull_rules_list = []
- for from_loc, dest_loc, pick_type_id, warehouse in values:
- pull_rules_list.append({
- 'name': self._format_rulename(cr, uid, warehouse, from_loc, dest_loc, context=context),
- 'location_src_id': from_loc.id,
- 'location_id': dest_loc.id,
- 'route_id': new_route_id,
- 'action': 'move',
- 'picking_type_id': pick_type_id,
- 'procure_method': warehouse.lot_stock_id.id != from_loc.id and 'make_to_order' or 'make_to_stock', # first part of the resuply route is MTS
- 'warehouse_id': warehouse.id,
- 'propagate_warehouse_id': supply_warehouse,
- })
- return pull_rules_list
-
- def _get_push_pull_rules(self, cr, uid, ids, active, values, new_route_id, context=None):
- warehouse = self.browse(cr, uid, ids, context=context)[0]
- first_rule = True
- push_rules_list = []
- pull_rules_list = []
- for from_loc, dest_loc, pick_type_id in values:
- push_rules_list.append({
- 'name': self._format_rulename(cr, uid, warehouse, from_loc, dest_loc, context=context),
- 'location_from_id': from_loc.id,
- 'location_dest_id': dest_loc.id,
- 'route_id': new_route_id,
- 'auto': 'manual',
- 'picking_type_id': pick_type_id,
- 'active': active,
- 'warehouse_id': warehouse.id,
- })
- pull_rules_list.append({
- 'name': self._format_rulename(cr, uid, warehouse, from_loc, dest_loc, context=context),
- 'location_src_id': from_loc.id,
- 'location_id': dest_loc.id,
- 'route_id': new_route_id,
- 'action': 'move',
- 'picking_type_id': pick_type_id,
- 'procure_method': first_rule is True and 'make_to_stock' or 'make_to_order',
- 'active': active,
- 'warehouse_id': warehouse.id,
- })
- first_rule = False
- return push_rules_list, pull_rules_list
-
- def _get_mto_route(self, cr, uid, context=None):
- route_obj = self.pool.get('stock.location.route')
- data_obj = self.pool.get('ir.model.data')
- try:
- mto_route_id = data_obj.get_object_reference(cr, uid, 'stock', 'route_warehouse0_mto')[1]
- except:
- mto_route_id = route_obj.search(cr, uid, [('name', 'like', _('Make To Order'))], context=context)
- mto_route_id = mto_route_id and mto_route_id[0] or False
- if not mto_route_id:
+ @api.model
+ @api.returns('stock.location.route', lambda value: value.id)
+ def _get_mto_route(self):
+ mto_route = self.env.ref('stock.route_warehouse0_mto', raise_if_not_found=False)
+ if not mto_route:
+ mto_route = self.env['stock.location.route'].search([('name', 'like', _('Make To Order'))], limit=1)
+ if not mto_route:
raise UserError(_('Can\'t find any generic Make To Order route.'))
- return mto_route_id
+ return mto_route
- def _get_mto_pull_rule(self, cr, uid, ids, values, context=None):
- warehouse = self.browse(cr, uid, ids, context=context)[0]
- mto_route_id = self._get_mto_route(cr, uid, context=context)
- res = []
- for value in values:
- from_loc, dest_loc, pick_type_id = value
- res += [{
- 'name': self._format_rulename(cr, uid, warehouse, from_loc, dest_loc, context=context) + _(' MTO'),
- 'location_src_id': from_loc.id,
- 'location_id': dest_loc.id,
- 'route_id': mto_route_id,
- 'action': 'move',
- 'picking_type_id': pick_type_id,
- 'procure_method': 'make_to_order',
- 'active': True,
- 'warehouse_id': warehouse.id,
- }]
- return res
-
- def _get_crossdock_route(self, cr, uid, ids, route_name, context=None):
- warehouse = self.browse(cr, uid, ids, context=context)[0]
+ def _get_inter_warehouse_route_values(self, supplier_warehouse):
return {
- 'name': self._format_routename(cr, uid, warehouse, route_name, context=context),
+ 'name': _('%s: Supply Product from %s') % (self.name, supplier_warehouse.name),
'warehouse_selectable': False,
'product_selectable': True,
'product_categ_selectable': True,
- 'active': warehouse.delivery_steps != 'ship_only' and warehouse.reception_steps != 'one_step',
- 'sequence': 20,
- }
+ 'supplied_wh_id': self.id,
+ 'supplier_wh_id': supplier_warehouse.id}
+ _get_inter_wh_route = _get_inter_warehouse_route_values # compatibility
- def create_routes(self, cr, uid, ids, context=None):
- warehouse = self.browse(cr, uid, ids, context=context)[0]
- wh_route_ids = []
- route_obj = self.pool.get('stock.location.route')
- pull_obj = self.pool.get('procurement.rule')
- push_obj = self.pool.get('stock.location.path')
- routes_dict = warehouse.get_routes_dict()
- #create reception route and rules
- route_name, values = routes_dict[warehouse.reception_steps]
- route_vals = warehouse._get_reception_delivery_route(route_name)
- reception_route_id = route_obj.create(cr, uid, route_vals, context=context)
- wh_route_ids.append((4, reception_route_id))
- push_rules_list, pull_rules_list = warehouse._get_push_pull_rules(True, values, reception_route_id)
- #create the push/procurement rules
- for push_rule in push_rules_list:
- push_obj.create(cr, uid, vals=push_rule, context=context)
- for pull_rule in pull_rules_list:
- #all procurement rules in reception route are mto, because we don't want to wait for the scheduler to trigger an orderpoint on input location
- pull_rule['procure_method'] = 'make_to_order'
- pull_obj.create(cr, uid, vals=pull_rule, context=context)
-
- #create MTS route and procurement rules for delivery and a specific route MTO to be set on the product
- route_name, values = routes_dict[warehouse.delivery_steps]
- route_vals = warehouse._get_reception_delivery_route(route_name)
- #create the route and its procurement rules
- delivery_route_id = route_obj.create(cr, uid, route_vals, context=context)
- wh_route_ids.append((4, delivery_route_id))
- dummy, pull_rules_list = warehouse._get_push_pull_rules(True, values, delivery_route_id)
- for pull_rule in pull_rules_list:
- pull_obj.create(cr, uid, vals=pull_rule, context=context)
- #create MTO procurement rule and link it to the generic MTO route
- mto_pull_vals = warehouse._get_mto_pull_rule(values)[0]
- mto_pull_id = pull_obj.create(cr, uid, mto_pull_vals, context=context)
-
- #create a route for cross dock operations, that can be set on products and product categories
- route_name, values = routes_dict['crossdock']
- crossdock_route_vals = warehouse._get_crossdock_route(route_name)
- crossdock_route_id = route_obj.create(cr, uid, vals=crossdock_route_vals, context=context)
- wh_route_ids.append((4, crossdock_route_id))
- dummy, pull_rules_list = warehouse._get_push_pull_rules(warehouse.delivery_steps != 'ship_only' and warehouse.reception_steps != 'one_step', values, crossdock_route_id)
- for pull_rule in pull_rules_list:
- # Fixed cross-dock is logically mto
- pull_rule['procure_method'] = 'make_to_order'
- pull_obj.create(cr, uid, vals=pull_rule, context=context)
-
- #create route selectable on the product to resupply the warehouse from another one
- warehouse._create_resupply_routes(warehouse.resupply_wh_ids, warehouse.default_resupply_wh_id)
-
- #return routes and mto procurement rule to store on the warehouse
+ def _get_crossdock_route_values(self):
return {
- 'route_ids': wh_route_ids,
- 'mto_pull_id': mto_pull_id,
- 'reception_route_id': reception_route_id,
- 'delivery_route_id': delivery_route_id,
- 'crossdock_route_id': crossdock_route_id,
- }
+ 'name': self._format_routename(route_type='crossdock'),
+ 'warehouse_selectable': False,
+ 'product_selectable': True,
+ 'product_categ_selectable': True,
+ 'active': self.delivery_steps != 'ship_only' and self.reception_steps != 'one_step',
+ 'sequence': 20}
- def change_route(self, cr, uid, ids, new_reception_step=False, new_delivery_step=False, context=None):
- warehouse = self.browse(cr, uid, ids[0], context=context)
- picking_type_obj = self.pool.get('stock.picking.type')
- pull_obj = self.pool.get('procurement.rule')
- push_obj = self.pool.get('stock.location.path')
- route_obj = self.pool.get('stock.location.route')
- new_reception_step = new_reception_step or warehouse.reception_steps
- new_delivery_step = new_delivery_step or warehouse.delivery_steps
+ def _get_crossdock_route(self, route_name): # compatibility
+ return self._get_crossdock_route_values()
- #change the default source and destination location and (de)activate picking types
- input_loc = warehouse.wh_input_stock_loc_id
- if new_reception_step == 'one_step':
- input_loc = warehouse.lot_stock_id
- output_loc = warehouse.wh_output_stock_loc_id
- if new_delivery_step == 'ship_only':
- output_loc = warehouse.lot_stock_id
- picking_type_obj.write(cr, uid, warehouse.in_type_id.id, {'default_location_dest_id': input_loc.id}, context=context)
- picking_type_obj.write(cr, uid, warehouse.out_type_id.id, {'default_location_src_id': output_loc.id}, context=context)
- picking_type_obj.write(cr, uid, warehouse.pick_type_id.id, {
- 'active': new_delivery_step != 'ship_only',
- 'default_location_dest_id': output_loc.id if new_delivery_step == 'pick_ship' else warehouse.wh_pack_stock_loc_id.id,
- }, context=context)
- picking_type_obj.write(cr, uid, warehouse.pack_type_id.id, {'active': new_delivery_step == 'pick_pack_ship'}, context=context)
+ # Pull / Push tools
+ # ------------------------------------------------------------
- routes_dict = warehouse.get_routes_dict()
- #update delivery route and rules: unlink the existing rules of the warehouse delivery route and recreate it
- pull_obj.unlink(cr, uid, [pu.id for pu in warehouse.delivery_route_id.pull_ids], context=context)
- route_name, values = routes_dict[new_delivery_step]
- route_obj.write(cr, uid, warehouse.delivery_route_id.id, {'name': self._format_routename(cr, uid, warehouse, route_name, context=context)}, context=context)
- dummy, pull_rules_list = warehouse._get_push_pull_rules(True, values, warehouse.delivery_route_id.id)
- #create the procurement rules
- for pull_rule in pull_rules_list:
- pull_obj.create(cr, uid, vals=pull_rule, context=context)
+ @api.multi
+ def _get_push_pull_rules_values(self, route_values, values=None, push_values=None, pull_values=None, name_suffix=''):
+ first_rule = True
+ push_rules_list, pull_rules_list = [], []
+ for routing in route_values:
+ route_push_values = {
+ 'name': self._format_rulename(routing.from_loc, routing.dest_loc, name_suffix),
+ 'location_from_id': routing.from_loc.id,
+ 'location_dest_id': routing.dest_loc.id,
+ 'auto': 'manual',
+ 'picking_type_id': routing.picking_type.id,
+ 'warehouse_id': self.id}
+ route_push_values.update((values or {}).items() + (push_values or {}).items())
+ push_rules_list.append(route_push_values)
+ route_pull_values = {
+ 'name': self._format_rulename(routing.from_loc, routing.dest_loc, name_suffix),
+ 'location_src_id': routing.from_loc.id,
+ 'location_id': routing.dest_loc.id,
+ 'action': 'move',
+ 'picking_type_id': routing.picking_type.id,
+ 'procure_method': first_rule is True and 'make_to_stock' or 'make_to_order',
+ 'warehouse_id': self.id}
+ route_pull_values.update((values or {}).items() + (pull_values or {}).items())
+ pull_rules_list.append(route_pull_values)
+ first_rule = False
+ return push_rules_list, pull_rules_list
- #update receipt route and rules: unlink the existing rules of the warehouse receipt route and recreate it
- pull_obj.unlink(cr, uid, [pu.id for pu in warehouse.reception_route_id.pull_ids], context=context)
- push_obj.unlink(cr, uid, [pu.id for pu in warehouse.reception_route_id.push_ids], context=context)
- route_name, values = routes_dict[new_reception_step]
- route_obj.write(cr, uid, warehouse.reception_route_id.id, {'name': self._format_routename(cr, uid, warehouse, route_name, context=context)}, context=context)
- push_rules_list, pull_rules_list = warehouse._get_push_pull_rules(True, values, warehouse.reception_route_id.id)
- #create the push/procurement rules
- for push_rule in push_rules_list:
- push_obj.create(cr, uid, vals=push_rule, context=context)
- for pull_rule in pull_rules_list:
- #all procurement rules in receipt route are mto, because we don't want to wait for the scheduler to trigger an orderpoint on input location
- pull_rule['procure_method'] = 'make_to_order'
- pull_obj.create(cr, uid, vals=pull_rule, context=context)
+ def _get_mto_pull_rules_values(self, route_values):
+ mto_route = self._get_mto_route()
+ dummy, pull_rules_list = self._get_push_pull_rules_values(route_values, pull_values={
+ 'route_id': mto_route.id,
+ 'procure_method': 'make_to_order',
+ 'active': True}, name_suffix=_('MTO'))
+ return pull_rules_list
+ _get_mto_pull_rule = _get_mto_pull_rules_values # compatibility
- route_obj.write(cr, uid, warehouse.crossdock_route_id.id, {'active': new_reception_step != 'one_step' and new_delivery_step != 'ship_only'}, context=context)
+ def _get_push_pull_rules(self, active, values, new_route_id): # compatibility
+ return self._get_push_pull_rules_values(values, values={'active': active, 'route_id': new_route_id})
- #change MTO rule
- dummy, values = routes_dict[new_delivery_step]
- mto_pull_vals = warehouse._get_mto_pull_rule(values)[0]
- pull_obj.write(cr, uid, warehouse.mto_pull_id.id, mto_pull_vals, context=context)
- return True
+ def _get_supply_pull_rules_values(self, route_values, values=None):
+ dummy, pull_rules_list = self._get_push_pull_rules_values(route_values, values=values, pull_values={'active': True})
+ for pull_rules in pull_rules_list:
+ pull_rules['procure_method'] = self.lot_stock_id.id != pull_rules['location_src_id'] and 'make_to_order' or 'make_to_stock' # first part of the resuply route is MTS
+ return pull_rules_list
- def create_sequences_and_picking_types(self, cr, uid, ids, context=None):
- warehouse = self.browse(cr, uid, ids, context=context)[0]
- seq_obj = self.pool.get('ir.sequence')
- picking_type_obj = self.pool.get('stock.picking.type')
- #create new sequences
- in_seq_id = seq_obj.create(cr, SUPERUSER_ID, {'name': warehouse.name + _(' Sequence in'), 'prefix': warehouse.code + '/IN/', 'padding': 5}, context=context)
- out_seq_id = seq_obj.create(cr, SUPERUSER_ID, {'name': warehouse.name + _(' Sequence out'), 'prefix': warehouse.code + '/OUT/', 'padding': 5}, context=context)
- pack_seq_id = seq_obj.create(cr, SUPERUSER_ID, {'name': warehouse.name + _(' Sequence packing'), 'prefix': warehouse.code + '/PACK/', 'padding': 5}, context=context)
- pick_seq_id = seq_obj.create(cr, SUPERUSER_ID, {'name': warehouse.name + _(' Sequence picking'), 'prefix': warehouse.code + '/PICK/', 'padding': 5}, context=context)
- int_seq_id = seq_obj.create(cr, SUPERUSER_ID, {'name': warehouse.name + _(' Sequence internal'), 'prefix': warehouse.code + '/INT/', 'padding': 5}, context=context)
+ def _update_reception_delivery_resupply(self, reception_new, delivery_new):
+ """ Check if we need to change something to resupply warehouses and associated MTO rules """
+ input_loc, output_loc = self._get_input_output_locations(reception_new, delivery_new)
+ for warehouse in self:
+ if reception_new and warehouse.reception_steps != reception_new and (warehouse.reception_steps == 'one_step' or reception_new == 'one_step'):
+ warehouse._check_reception_resupply(input_loc)
+ if delivery_new and warehouse.delivery_steps != delivery_new and (warehouse.delivery_steps == 'ship_only' or delivery_new == 'ship_only'):
+ change_to_multiple = warehouse.delivery_steps == 'ship_only'
+ warehouse._check_delivery_resupply(output_loc, change_to_multiple)
+ _check_resupply = _update_reception_delivery_resupply
- wh_stock_loc = warehouse.lot_stock_id
- wh_input_stock_loc = warehouse.wh_input_stock_loc_id
- wh_output_stock_loc = warehouse.wh_output_stock_loc_id
- wh_pack_stock_loc = warehouse.wh_pack_stock_loc_id
-
- #create in, out, internal picking types for warehouse
- input_loc = wh_input_stock_loc
- if warehouse.reception_steps == 'one_step':
- input_loc = wh_stock_loc
- output_loc = wh_output_stock_loc
- if warehouse.delivery_steps == 'ship_only':
- output_loc = wh_stock_loc
-
- #choose the next available color for the picking types of this warehouse
- color = 0
- available_colors = [0, 3, 4, 5, 6, 7, 8, 1, 2] # put white color first
- all_used_colors = self.pool.get('stock.picking.type').search_read(cr, uid, [('warehouse_id', '!=', False), ('color', '!=', False)], ['color'], order='color')
- #don't use sets to preserve the list order
- for x in all_used_colors:
- if x['color'] in available_colors:
- available_colors.remove(x['color'])
- if available_colors:
- color = available_colors[0]
-
- #order the picking types with a sequence allowing to have the following suit for each warehouse: reception, internal, pick, pack, ship.
- max_sequence = self.pool.get('stock.picking.type').search_read(cr, uid, [], ['sequence'], order='sequence desc')
- max_sequence = max_sequence and max_sequence[0]['sequence'] or 0
- internal_active_false = (warehouse.reception_steps == 'one_step') and (warehouse.delivery_steps == 'ship_only')
- internal_active_false = internal_active_false and not self.user_has_groups(cr, uid, 'stock.group_stock_multi_locations')
-
- in_type_id = picking_type_obj.create(cr, uid, vals={
- 'name': _('Receipts'),
- 'warehouse_id': warehouse.id,
- 'code': 'incoming',
- 'use_create_lots': True,
- 'use_existing_lots': False,
- 'sequence_id': in_seq_id,
- 'default_location_src_id': False,
- 'default_location_dest_id': input_loc.id,
- 'sequence': max_sequence + 1,
- 'color': color}, context=context)
- out_type_id = picking_type_obj.create(cr, uid, vals={
- 'name': _('Delivery Orders'),
- 'warehouse_id': warehouse.id,
- 'code': 'outgoing',
- 'use_create_lots': False,
- 'use_existing_lots': True,
- 'sequence_id': out_seq_id,
- 'return_picking_type_id': in_type_id,
- 'default_location_src_id': output_loc.id,
- 'default_location_dest_id': False,
- 'sequence': max_sequence + 4,
- 'color': color}, context=context)
- picking_type_obj.write(cr, uid, [in_type_id], {'return_picking_type_id': out_type_id}, context=context)
- int_type_id = picking_type_obj.create(cr, uid, vals={
- 'name': _('Internal Transfers'),
- 'warehouse_id': warehouse.id,
- 'code': 'internal',
- 'use_create_lots': False,
- 'use_existing_lots': True,
- 'sequence_id': int_seq_id,
- 'default_location_src_id': wh_stock_loc.id,
- 'default_location_dest_id': wh_stock_loc.id,
- 'active': not internal_active_false,
- 'sequence': max_sequence + 2,
- 'color': color}, context=context)
- pack_type_id = picking_type_obj.create(cr, uid, vals={
- 'name': _('Pack'),
- 'warehouse_id': warehouse.id,
- 'code': 'internal',
- 'use_create_lots': False,
- 'use_existing_lots': True,
- 'sequence_id': pack_seq_id,
- 'default_location_src_id': wh_pack_stock_loc.id,
- 'default_location_dest_id': output_loc.id,
- 'active': warehouse.delivery_steps == 'pick_pack_ship',
- 'sequence': max_sequence + 3,
- 'color': color}, context=context)
- pick_type_id = picking_type_obj.create(cr, uid, vals={
- 'name': _('Pick'),
- 'warehouse_id': warehouse.id,
- 'code': 'internal',
- 'use_create_lots': False,
- 'use_existing_lots': True,
- 'sequence_id': pick_seq_id,
- 'default_location_src_id': wh_stock_loc.id,
- 'default_location_dest_id': output_loc.id if warehouse.delivery_steps == 'pick_ship' else wh_pack_stock_loc.id,
- 'active': warehouse.delivery_steps != 'ship_only',
- 'sequence': max_sequence + 2,
- 'color': color}, context=context)
-
- #write picking types on WH
- vals = {
- 'in_type_id': in_type_id,
- 'out_type_id': out_type_id,
- 'pack_type_id': pack_type_id,
- 'pick_type_id': pick_type_id,
- 'int_type_id': int_type_id,
- }
- super(stock_warehouse, self).write(cr, uid, warehouse.id, vals=vals, context=context)
-
- def create(self, cr, uid, vals, context=None):
- if context is None:
- context = {}
- if vals is None:
- vals = {}
- data_obj = self.pool.get('ir.model.data')
- seq_obj = self.pool.get('ir.sequence')
- picking_type_obj = self.pool.get('stock.picking.type')
- location_obj = self.pool.get('stock.location')
-
- #create view location for warehouse
- loc_vals = {
- 'name': _(vals.get('code')),
- 'usage': 'view',
- 'location_id': data_obj.get_object_reference(cr, uid, 'stock', 'stock_location_locations')[1],
- }
- if vals.get('company_id'):
- loc_vals['company_id'] = vals.get('company_id')
- wh_loc_id = location_obj.create(cr, uid, loc_vals, context=context)
- vals['view_location_id'] = wh_loc_id
- #create all location
- def_values = self.default_get(cr, uid, {'reception_steps', 'delivery_steps'})
- reception_steps = vals.get('reception_steps', def_values['reception_steps'])
- delivery_steps = vals.get('delivery_steps', def_values['delivery_steps'])
- context_with_inactive = context.copy()
- context_with_inactive['active_test'] = False
- sub_locations = [
- {'name': _('Stock'), 'active': True, 'field': 'lot_stock_id'},
- {'name': _('Input'), 'active': reception_steps != 'one_step', 'field': 'wh_input_stock_loc_id'},
- {'name': _('Quality Control'), 'active': reception_steps == 'three_steps', 'field': 'wh_qc_stock_loc_id'},
- {'name': _('Output'), 'active': delivery_steps != 'ship_only', 'field': 'wh_output_stock_loc_id'},
- {'name': _('Packing Zone'), 'active': delivery_steps == 'pick_pack_ship', 'field': 'wh_pack_stock_loc_id'},
- ]
- for values in sub_locations:
- loc_vals = {
- 'name': values['name'],
- 'usage': 'internal',
- 'location_id': wh_loc_id,
- 'active': values['active'],
- }
- if vals.get('company_id'):
- loc_vals['company_id'] = vals.get('company_id')
- location_id = location_obj.create(cr, uid, loc_vals, context=context_with_inactive)
- vals[values['field']] = location_id
-
- #create WH
- new_id = super(stock_warehouse, self).create(cr, uid, vals=vals, context=context)
- warehouse = self.browse(cr, uid, new_id, context=context)
- self.create_sequences_and_picking_types(cr, uid, [warehouse.id], context=context)
-
- #create routes and push/procurement rules
- new_objects_dict = self.create_routes(cr, uid, [new_id], context=context)
- self.write(cr, uid, warehouse.id, new_objects_dict, context=context)
-
- # If partner assigned
- if vals.get('partner_id'):
- comp_obj = self.pool['res.company']
- if vals.get('company_id'):
- transit_loc = comp_obj.browse(cr, uid, vals.get('company_id'), context=context).internal_transit_location_id.id
- else:
- transit_loc = comp_obj.browse(cr, uid, comp_obj._company_default_get(cr, uid, 'stock.warehouse', context=context)).internal_transit_location_id.id
- self.pool['res.partner'].write(cr, uid, [vals['partner_id']], {'property_stock_customer': transit_loc,
- 'property_stock_supplier': transit_loc}, context=context)
- return new_id
-
- def _format_rulename(self, cr, uid, obj, from_loc, dest_loc, context=None):
- return obj.code + ': ' + from_loc.name + ' -> ' + dest_loc.name
-
- def _format_routename(self, cr, uid, obj, name, context=None):
- return obj.name + ': ' + name
-
- def get_routes_dict(self, cr, uid, ids, context=None):
- warehouse = self.browse(cr, uid, ids, context=context)[0]
- #fetch customer and supplier locations, for references
- customer_loc, supplier_loc = self._get_partner_locations(cr, uid, ids, context=context)
-
- return {
- 'one_step': (_('Receipt in 1 step'), []),
- 'two_steps': (_('Receipt in 2 steps'), [(warehouse.wh_input_stock_loc_id, warehouse.lot_stock_id, warehouse.int_type_id.id)]),
- 'three_steps': (_('Receipt in 3 steps'), [(warehouse.wh_input_stock_loc_id, warehouse.wh_qc_stock_loc_id, warehouse.int_type_id.id), (warehouse.wh_qc_stock_loc_id, warehouse.lot_stock_id, warehouse.int_type_id.id)]),
- 'crossdock': (_('Cross-Dock'), [(warehouse.wh_input_stock_loc_id, warehouse.wh_output_stock_loc_id, warehouse.int_type_id.id), (warehouse.wh_output_stock_loc_id, customer_loc, warehouse.out_type_id.id)]),
- 'ship_only': (_('Ship Only'), [(warehouse.lot_stock_id, customer_loc, warehouse.out_type_id.id)]),
- 'pick_ship': (_('Pick + Ship'), [(warehouse.lot_stock_id, warehouse.wh_output_stock_loc_id, warehouse.pick_type_id.id), (warehouse.wh_output_stock_loc_id, customer_loc, warehouse.out_type_id.id)]),
- 'pick_pack_ship': (_('Pick + Pack + Ship'), [(warehouse.lot_stock_id, warehouse.wh_pack_stock_loc_id, warehouse.pick_type_id.id), (warehouse.wh_pack_stock_loc_id, warehouse.wh_output_stock_loc_id, warehouse.pack_type_id.id), (warehouse.wh_output_stock_loc_id, customer_loc, warehouse.out_type_id.id)]),
- }
-
- def _handle_renaming(self, cr, uid, ids, name, code, context=None):
- warehouse = self.browse(cr, uid, ids[0], context=context)
- location_obj = self.pool.get('stock.location')
- route_obj = self.pool.get('stock.location.route')
- pull_obj = self.pool.get('procurement.rule')
- push_obj = self.pool.get('stock.location.path')
- #rename location
- location_id = warehouse.lot_stock_id.location_id.id
- location_obj.write(cr, uid, location_id, {'name': code}, context=context)
- #rename route and push-procurement rules
- for route in warehouse.route_ids:
- route_obj.write(cr, uid, route.id, {'name': route.name.replace(warehouse.name, name, 1)}, context=context)
- for pull in route.pull_ids:
- pull_obj.write(cr, uid, pull.id, {'name': pull.name.replace(warehouse.name, name, 1)}, context=context)
- for push in route.push_ids:
- push_obj.write(cr, uid, push.id, {'name': push.name.replace(warehouse.name, name, 1)}, context=context)
- #change the mto procurement rule name
- if warehouse.mto_pull_id.id:
- pull_obj.write(cr, uid, warehouse.mto_pull_id.id, {'name': warehouse.mto_pull_id.name.replace(warehouse.name, name, 1)}, context=context)
-
- def _check_delivery_resupply(self, cr, uid, ids, new_location, change_to_multiple, context=None):
- """ Will check if the resupply routes from this warehouse follow the changes of number of delivery steps """
- warehouse = self.browse(cr, uid, ids[0], context=context)
- #Check routes that are being delivered by this warehouse and change the rule going to transit location
- route_obj = self.pool.get("stock.location.route")
- pull_obj = self.pool.get("procurement.rule")
- routes = route_obj.search(cr, uid, [('supplier_wh_id','=', warehouse.id)], context=context)
- pulls = pull_obj.search(cr, uid, ['&', ('route_id', 'in', routes), ('location_id.usage', '=', 'transit')], context=context)
- if pulls:
- pull_obj.write(cr, uid, pulls, {'location_src_id': new_location, 'procure_method': change_to_multiple and "make_to_order" or "make_to_stock"}, context=context)
- # Create or clean MTO rules
- mto_route_id = self._get_mto_route(cr, uid, context=context)
+ def _check_delivery_resupply(self, new_location, change_to_multiple):
+ """ Check if the resupply routes from this warehouse follow the changes of number of delivery steps
+ Check routes being delivery bu this warehouse and change the rule going to transit location """
+ Pull = self.env["procurement.rule"]
+ routes = self.env['stock.location.route'].search([('supplier_wh_id', '=', self.id)])
+ pulls = Pull.search(['&', ('route_id', 'in', routes.ids), ('location_id.usage', '=', 'transit')])
+ pulls.write({
+ 'location_src_id': new_location,
+ 'procure_method': change_to_multiple and "make_to_order" or "make_to_stock"})
if not change_to_multiple:
- # If single delivery we should create the necessary MTO rules for the resupply
- # pulls = pull_obj.search(cr, uid, ['&', ('route_id', '=', mto_route_id), ('location_id.usage', '=', 'transit'), ('location_src_id', '=', warehouse.lot_stock_id.id)], context=context)
- pull_recs = pull_obj.browse(cr, uid, pulls, context=context)
- transfer_locs = list(set([x.location_id for x in pull_recs]))
- vals = [(warehouse.lot_stock_id , x, warehouse.out_type_id.id) for x in transfer_locs]
- mto_pull_vals = warehouse._get_mto_pull_rule(vals)
+ # If single delivery we should create the necessary MTO rules for the resupply
+ routings = [self.Routing(self.lot_stock_id , location, self.out_type_id) for location in pulls.mapped('location_id')]
+ mto_pull_vals = self._get_mto_pull_rules_values(routings)
for mto_pull_val in mto_pull_vals:
- pull_obj.create(cr, uid, mto_pull_val, context=context)
+ Pull.create(mto_pull_val)
else:
# We need to delete all the MTO procurement rules, otherwise they risk to be used in the system
- pulls = pull_obj.search(cr, uid, ['&', ('route_id', '=', mto_route_id), ('location_id.usage', '=', 'transit'), ('location_src_id', '=', warehouse.lot_stock_id.id)], context=context)
- if pulls:
- pull_obj.unlink(cr, uid, pulls, context=context)
+ Pull.search([
+ '&', ('route_id', '=', self._get_mto_route().id),
+ ('location_id.usage', '=', 'transit'),
+ ('location_src_id', '=', self.lot_stock_id.id)]).unlink()
- def _check_reception_resupply(self, cr, uid, ids, new_location, context=None):
- """
- Will check if the resupply routes to this warehouse follow the changes of number of receipt steps
- """
- warehouse = self.browse(cr, uid, ids[0], context=context)
- #Check routes that are being delivered by this warehouse and change the rule coming from transit location
- route_obj = self.pool.get("stock.location.route")
- pull_obj = self.pool.get("procurement.rule")
- routes = route_obj.search(cr, uid, [('supplied_wh_id','=', warehouse.id)], context=context)
- pulls= pull_obj.search(cr, uid, ['&', ('route_id', 'in', routes), ('location_src_id.usage', '=', 'transit')])
+ def _check_reception_resupply(self, new_location):
+ """ Check routes being delivered by the warehouses (resupply routes) and
+ change their rule coming from the transit location """
+ routes = self.env['stock.location.route'].search([('supplied_wh_id', 'in', self.ids)])
+ self.env['procurement.rule'].search([
+ '&', ('route_id', 'in', routes.ids),
+ ('location_src_id.usage', '=', 'transit')]).write({'location_id': new_location})
+
+ @api.multi
+ def _update_routes(self):
+ routes_data = self.get_routes_dict()
+ # change the default source and destination location and (de)activate picking types
+ self._update_picking_type()
+ # update delivery route and rules: unlink the existing rules of the warehouse delivery route and recreate it
+ self._create_or_update_delivery_route(routes_data)
+ # update receipt route and rules: unlink the existing rules of the warehouse receipt route and recreate it
+ self._create_or_update_reception_route(routes_data)
+ self._create_or_update_crossdock_route(routes_data)
+ self._create_or_update_mto_pull(routes_data)
+ return True
+ change_route = _update_routes
+
+ @api.one
+ def _update_picking_type(self):
+ picking_type_values = self._get_picking_type_values(self.reception_steps, self.delivery_steps, self.wh_pack_stock_loc_id)
+ for field_name, values in picking_type_values.iteritems():
+ getattr(self, field_name).write(values)
+
+ @api.multi
+ def _update_name_and_code(self, new_name=False, new_code=False):
+ if new_code:
+ self.mapped('lot_stock_id').mapped('location_id').write({'name': new_code})
+ if new_name:
+ # TDE FIXME: replacing the route name ? not better to re-generate the route naming ?
+ for warehouse in self:
+ routes = warehouse.route_ids
+ for route in routes:
+ route.write({'name': route.name.replace(warehouse.name, new_name, 1)})
+ for pull in route.pull_ids:
+ pull.write({'name': pull.name.replace(warehouse.name, new_name, 1)})
+ for push in route.push_ids:
+ push.write({'name': push.name.replace(warehouse.name, new_name, 1)})
+ warehouse.mto_pull_id.write({'name': warehouse.mto_pull_id.name.replace(warehouse.name, new_name, 1)})
+ for warehouse in self:
+ sequence_data = warehouse._get_sequence_values()
+ warehouse.in_type_id.sequence_id.write(sequence_data['in_type_id'])
+ warehouse.out_type_id.sequence_id.write(sequence_data['out_type_id'])
+ warehouse.pack_type_id.sequence_id.write(sequence_data['pack_type_id'])
+ warehouse.pick_type_id.sequence_id.write(sequence_data['pick_type_id'])
+ warehouse.int_type_id.sequence_id.write(sequence_data['int_type_id'])
+ _handle_renaming = _update_name_and_code
+
+ def _update_location_reception(self, new_reception_step):
+ switch_warehouses = self.filtered(lambda wh: wh.reception_steps != new_reception_step and not wh._location_used(wh.wh_input_stock_loc_id))
+ if switch_warehouses:
+ (switch_warehouses.mapped('wh_input_stock_loc_id') + switch_warehouses.mapped('wh_qc_stock_loc_id')).write({'active': False})
+ if new_reception_step == 'three_steps':
+ self.mapped('wh_qc_stock_loc_id').write({'active': True})
+ if new_reception_step != 'one_step':
+ self.mapped('wh_input_stock_loc_id').write({'active': True})
+
+ def _update_location_delivery(self, new_delivery_step):
+ switch_warehouses = self.filtered(lambda wh: wh.delivery_steps != new_delivery_step)
+ loc_warehouse = switch_warehouses.filtered(lambda wh: not wh._location_used(wh.wh_output_stock_loc_id))
+ if loc_warehouse:
+ loc_warehouse.mapped('wh_output_stock_loc_id').write({'active': False})
+ loc_warehouse = switch_warehouses.filtered(lambda wh: not wh._location_used(wh.wh_pack_stock_loc_id))
+ if loc_warehouse:
+ loc_warehouse.mapped('wh_pack_stock_loc_id').write({'active': False})
+ if new_delivery_step == 'pick_pack_ship':
+ self.mapped('wh_pack_stock_loc_id').write({'active': True})
+ if new_delivery_step != 'ship_only':
+ self.mapped('wh_output_stock_loc_id').write({'active': True})
+
+ def _location_used(self, location):
+ pulls = self.env['procurement.rule'].search_count([
+ '&',
+ ('route_id', 'not in', [x.id for x in self.route_ids]),
+ '|', ('location_src_id', '=', location.id),
+ ('location_id', '=', location.id)])
if pulls:
- pull_obj.write(cr, uid, pulls, {'location_id': new_location}, context=context)
+ return True
+ pushs = self.env['stock.location.path'].search_count([
+ '&',
+ ('route_id', 'not in', [x.id for x in self.route_ids]),
+ '|', ('location_from_id', '=', location.id),
+ ('location_dest_id', '=', location.id)])
+ if pushs:
+ return True
+ return False
- def _check_resupply(self, cr, uid, ids, reception_new, delivery_new, context=None):
- warehouse = self.browse(cr, uid, ids[0], context=context)
- if reception_new:
- old_val = warehouse.reception_steps
- new_val = reception_new
- change_to_one = (old_val != 'one_step' and new_val == 'one_step')
- change_to_multiple = (old_val == 'one_step' and new_val != 'one_step')
- if change_to_one or change_to_multiple:
- new_location = change_to_one and warehouse.lot_stock_id.id or warehouse.wh_input_stock_loc_id.id
- self._check_reception_resupply(cr, uid, [warehouse.id], new_location, context=context)
- if delivery_new:
- old_val = warehouse.delivery_steps
- new_val = delivery_new
- change_to_one = (old_val != 'ship_only' and new_val == 'ship_only')
- change_to_multiple = (old_val == 'ship_only' and new_val != 'ship_only')
- if change_to_one or change_to_multiple:
- new_location = change_to_one and warehouse.lot_stock_id.id or warehouse.wh_output_stock_loc_id.id
- self._check_delivery_resupply(cr, uid, [warehouse.id], new_location, change_to_multiple, context=context)
+ # Misc
+ # ------------------------------------------------------------
- def write(self, cr, uid, ids, vals, context=None):
- if context is None:
- context = {}
- if isinstance(ids, (int, long)):
- ids = [ids]
- seq_obj = self.pool.get('ir.sequence')
- route_obj = self.pool.get('stock.location.route')
- context_with_inactive = context.copy()
- context_with_inactive['active_test'] = False
- for warehouse in self.browse(cr, uid, ids, context=context_with_inactive):
- #first of all, check if we need to delete and recreate route
- if vals.get('reception_steps') or vals.get('delivery_steps'):
- #activate and deactivate location according to reception and delivery option
- warehouse.switch_location(vals.get('reception_steps', False), vals.get('delivery_steps', False))
- # switch between route
- self.change_route(cr, uid, [warehouse.id], vals.get('reception_steps', False), vals.get('delivery_steps', False), context=context_with_inactive)
- # Check if we need to change something to resupply warehouses and associated MTO rules
- warehouse._check_resupply(vals.get('reception_steps'), vals.get('delivery_steps'))
- if vals.get('code') or vals.get('name'):
- name = warehouse.name
- #rename sequence
- if vals.get('name'):
- name = vals.get('name', warehouse.name)
- self._handle_renaming(cr, uid, [warehouse.id], name, vals.get('code', warehouse.code), context=context_with_inactive)
- if warehouse.in_type_id:
- seq_obj.write(cr, uid, [warehouse.in_type_id.sequence_id.id], {'name': name + _(' Sequence in'), 'prefix': vals.get('code', warehouse.code) + '\IN\\'}, context=context)
- if warehouse.out_type_id:
- seq_obj.write(cr, uid, [warehouse.out_type_id.sequence_id.id], {'name': name + _(' Sequence out'), 'prefix': vals.get('code', warehouse.code) + '\OUT\\'}, context=context)
- if warehouse.pack_type_id:
- seq_obj.write(cr, uid, [warehouse.pack_type_id.sequence_id.id], {'name': name + _(' Sequence packing'), 'prefix': vals.get('code', warehouse.code) + '\PACK\\'}, context=context)
- if warehouse.pick_type_id:
- seq_obj.write(cr, uid, [warehouse.pick_type_id.sequence_id.id], {'name': name + _(' Sequence picking'), 'prefix': vals.get('code', warehouse.code) + '\PICK\\'}, context=context)
- if warehouse.int_type_id:
- seq_obj.write(cr, uid, [warehouse.int_type_id.sequence_id.id], {'name': name + _(' Sequence internal'), 'prefix': vals.get('code', warehouse.code) + '\INT\\'}, context=context)
- if vals.get('resupply_wh_ids') and not vals.get('resupply_route_ids'):
- for cmd in vals.get('resupply_wh_ids'):
- if cmd[0] == 6:
- new_ids = set(cmd[2])
- old_ids = set([wh.id for wh in warehouse.resupply_wh_ids])
- to_add_wh_ids = new_ids - old_ids
- if to_add_wh_ids:
- supplier_warehouses = self.browse(cr, uid, list(to_add_wh_ids), context=context)
- warehouse._create_resupply_routes(supplier_warehouses, warehouse.default_resupply_wh_id)
- to_remove_wh_ids = old_ids - new_ids
- if to_remove_wh_ids:
- to_remove_route_ids = route_obj.search(cr, uid, [('supplied_wh_id', '=', warehouse.id), ('supplier_wh_id', 'in', list(to_remove_wh_ids))], context=context)
- if to_remove_route_ids:
- route_obj.unlink(cr, uid, to_remove_route_ids, context=context)
- else:
- #not implemented
- pass
- if 'default_resupply_wh_id' in vals:
- if vals.get('default_resupply_wh_id') == warehouse.id:
- raise UserError(_('The default resupply warehouse should be different than the warehouse itself!'))
- if warehouse.default_resupply_wh_id:
- #remove the existing resupplying route on the warehouse
- to_remove_route_ids = route_obj.search(cr, uid, [('supplied_wh_id', '=', warehouse.id), ('supplier_wh_id', '=', warehouse.default_resupply_wh_id.id)], context=context)
- for inter_wh_route_id in to_remove_route_ids:
- self.write(cr, uid, [warehouse.id], {'route_ids': [(3, inter_wh_route_id)]})
- if vals.get('default_resupply_wh_id'):
- #assign the new resupplying route on all products
- to_assign_route_ids = route_obj.search(cr, uid, [('supplied_wh_id', '=', warehouse.id), ('supplier_wh_id', '=', vals.get('default_resupply_wh_id'))], context=context)
- for inter_wh_route_id in to_assign_route_ids:
- self.write(cr, uid, [warehouse.id], {'route_ids': [(4, inter_wh_route_id)]})
+ def _get_picking_type_values(self, reception_steps, delivery_steps, pack_stop_location):
+ input_loc, output_loc = self._get_input_output_locations(reception_steps, delivery_steps)
+ return {
+ 'in_type_id': {'default_location_dest_id': input_loc.id},
+ 'out_type_id': {'default_location_src_id': output_loc.id},
+ 'pick_type_id': {
+ 'active': delivery_steps != 'ship_only',
+ 'default_location_dest_id': output_loc.id if delivery_steps == 'pick_ship' else pack_stop_location.id},
+ 'pack_type_id': {'active': delivery_steps == 'pick_pack_ship'},
+ 'int_type_id': {},
+ }
- # If another partner assigned
- if vals.get('partner_id'):
- if not vals.get('company_id'):
- company = self.browse(cr, uid, ids[0], context=context).company_id
- else:
- company = self.pool['res.company'].browse(cr, uid, vals['company_id'])
- transit_loc = company.internal_transit_location_id.id
- self.pool['res.partner'].write(cr, uid, [vals['partner_id']], {'property_stock_customer': transit_loc,
- 'property_stock_supplier': transit_loc}, context=context)
- return super(stock_warehouse, self).write(cr, uid, ids, vals=vals, context=context)
+ def _get_sequence_values(self):
+ return {
+ 'in_type_id': {'name': self.name + _('Sequence in'), 'prefix': self.code + '/IN/', 'padding': 5},
+ 'out_type_id': {'name': self.name + _('Sequence out'), 'prefix': self.code + '/OUT/', 'padding': 5},
+ 'pack_type_id': {'name': self.name + _('Sequence packing'), 'prefix': self.code + '/PACK/', 'padding': 5},
+ 'pick_type_id': {'name': self.name + _('Sequence picking'), 'prefix': self.code + '/PICK/', 'padding': 5},
+ 'int_type_id': {'name': self.name + _('Sequence internal'), 'prefix': self.code + '/INT/', 'padding': 5},
+ }
- def get_all_routes_for_wh(self, cr, uid, ids, context=None):
- warehouse = self.browse(cr, uid, ids[0], context=context)
- route_obj = self.pool.get("stock.location.route")
- all_routes = [route.id for route in warehouse.route_ids]
- all_routes += route_obj.search(cr, uid, [('supplied_wh_id', '=', warehouse.id)], context=context)
- all_routes += [warehouse.mto_pull_id.route_id.id]
- return all_routes
+ @api.multi
+ def _format_rulename(self, from_loc, dest_loc, suffix):
+ return '%s: %s -> %s%s' % (self.code, from_loc.name, dest_loc.name, suffix)
- def action_view_all_routes(self, cr, uid, ids, context=None):
- all_routes = []
- for wh in self.browse(cr, uid, ids, context=context):
- all_routes += wh.get_all_routes_for_wh()
+ @api.multi
+ def _format_routename(self, name=None, route_type=None):
+ if route_type:
+ name = self._get_route_name(route_type)
+ return '%s: %s' % (self.name, name)
- domain = [('id', 'in', all_routes)]
+ @api.returns('self')
+ @api.multi
+ def _get_all_routes(self):
+ # TDE FIXME: check overrides
+ routes = self.mapped('route_ids') | self.mapped('mto_pull_id').mapped('route_id')
+ routes |= self.env["stock.location.route"].search([('supplied_wh_id', 'in', self.ids)])
+ return routes
+ get_all_routes_for_wh = _get_all_routes
+
+ @api.multi
+ def action_view_all_routes(self):
+ routes = self._get_all_routes()
return {
'name': _('Warehouse\'s Routes'),
- 'domain': domain,
+ 'domain': [('id', 'in', routes.ids)],
'res_model': 'stock.location.route',
'type': 'ir.actions.act_window',
'view_id': False,
@@ -774,157 +696,136 @@ class stock_warehouse(osv.osv):
}
-class stock_warehouse_orderpoint(osv.osv):
- """
- Defines Minimum stock rules.
- """
+class Orderpoint(models.Model):
+ """ Defines Minimum stock rules. """
_name = "stock.warehouse.orderpoint"
_description = "Minimum Inventory Rule"
- def subtract_procurements_from_orderpoints(self, cr, uid, orderpoint_ids, context=None):
- '''This function returns quantity of product that needs to be deducted from the orderpoint computed quantity because there's already a procurement created with aim to fulfill it.
- '''
-
- cr.execute("""select op.id, p.id, p.product_uom, p.product_qty, pt.uom_id, sm.product_qty from procurement_order as p left join stock_move as sm ON sm.procurement_id = p.id,
- stock_warehouse_orderpoint op, product_product pp, product_template pt
- WHERE p.orderpoint_id = op.id AND p.state not in ('done', 'cancel') AND (sm.state IS NULL OR sm.state not in ('draft'))
- AND pp.id = p.product_id AND pp.product_tmpl_id = pt.id
- AND op.id IN %s
- ORDER BY op.id, p.id
- """, (tuple(orderpoint_ids),))
- results = cr.fetchall()
- current_proc = False
- current_op = False
- uom_obj = self.pool.get("product.uom")
- op_qty = 0
- res = dict.fromkeys(orderpoint_ids, 0.0)
- for move_result in results:
- op = move_result[0]
- if current_op != op:
- if current_op:
- res[current_op] = op_qty
- current_op = op
- op_qty = 0
- proc = move_result[1]
- if proc != current_proc:
- op_qty += uom_obj._compute_qty(cr, uid, move_result[2], move_result[3], move_result[4], round=False)
- current_proc = proc
- if move_result[5]: #If a move is associated (is move qty)
- op_qty -= move_result[5]
- if current_op:
- res[current_op] = op_qty
+ @api.model
+ def default_get(self, fields):
+ res = super(Orderpoint, self).default_get(fields)
+ warehouse = None
+ if 'warehouse_id' not in res and res.get('company_id'):
+ warehouse = self.env['stock.warehouse'].search([('company_id', '=', res['company_id'])], limit=1)
+ if warehouse:
+ res['warehouse_id'] = warehouse.id
+ res['location_id'] = warehouse.lot_stock_id.id
return res
- def _check_product_uom(self, cr, uid, ids, context=None):
- '''
- Check if the UoM has the same category as the product standard UoM
- '''
- if not context:
- context = {}
+ name = fields.Char(
+ 'Name', copy=False, required=True,
+ default=lambda self: self.env['ir.sequence'].next_by_code('stock.orderpoint'))
+ active = fields.Boolean(
+ 'Active', default=True,
+ help="If the active field is set to False, it will allow you to hide the orderpoint without removing it.")
+ warehouse_id = fields.Many2one(
+ 'stock.warehouse', 'Warehouse',
+ ondelete="cascade", required=True)
+ location_id = fields.Many2one(
+ 'stock.location', 'Location',
+ ondelete="cascade", required=True)
+ product_id = fields.Many2one(
+ 'product.product', 'Product',
+ domain=[('type', '=', 'product')], ondelete='cascade', required=True)
+ product_uom = fields.Many2one(
+ 'product.uom', 'Product Unit of Measure', related='product_id.uom_id',
+ readonly=True, required=True,
+ default=lambda self: self._context.get('product_uom', False))
+ product_min_qty = fields.Float(
+ 'Minimum Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True,
+ help="When the virtual stock goes below the Min Quantity specified for this field, Odoo generates "
+ "a procurement to bring the forecasted quantity to the Max Quantity.")
+ product_max_qty = fields.Float(
+ 'Maximum Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True,
+ help="When the virtual stock goes below the Min Quantity, Odoo generates "
+ "a procurement to bring the forecasted quantity to the Quantity specified as Max Quantity.")
+ qty_multiple = fields.Float(
+ 'Qty Multiple', digits_compute=dp.get_precision('Product Unit of Measure'),
+ default=1, required=True,
+ help="The procurement quantity will be rounded up to this multiple. If it is 0, the exact quantity will be used.")
+ procurement_ids = fields.One2many('procurement.order', 'orderpoint_id', 'Created Procurements')
+ group_id = fields.Many2one(
+ 'procurement.group', 'Procurement Group', copy=False,
+ help="Moves created through this orderpoint will be put in this procurement group. If none is given, the moves generated by procurement rules will be grouped into one big picking.")
+ company_id = fields.Many2one(
+ 'res.company', 'Company', required=True,
+ default=lambda self: self.env['res.company']._company_default_get('stock.warehouse.orderpoint'))
+ lead_days = fields.Integer(
+ 'Lead Time', default=1,
+ help="Number of days after the orderpoint is triggered to receive the products or to order to the vendor")
+ lead_type = fields.Selection(
+ [('net', 'Day(s) to get the products'), ('supplier', 'Day(s) to purchase')], 'Lead Type',
+ required=True, default='supplier')
- for rule in self.browse(cr, uid, ids, context=context):
- if rule.product_id.uom_id.category_id.id != rule.product_uom.category_id.id:
- return False
- return True
-
- _columns = {
- 'name': fields.char('Name', required=True, copy=False),
- 'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the orderpoint without removing it."),
- 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', required=True, ondelete="cascade"),
- 'location_id': fields.many2one('stock.location', 'Location', required=True, ondelete="cascade"),
- 'product_id': fields.many2one('product.product', 'Product', required=True, ondelete='cascade', domain=[('type', '=', 'product')]),
- 'product_uom': fields.related('product_id', 'uom_id', type='many2one', relation='product.uom', string='Product Unit of Measure', readonly=True, required=True),
- 'product_min_qty': fields.float('Minimum Quantity', required=True,
- digits_compute=dp.get_precision('Product Unit of Measure'),
- help="When the virtual stock goes below the Min Quantity specified for this field, Odoo generates "\
- "a procurement to bring the forecasted quantity to the Max Quantity."),
- 'product_max_qty': fields.float('Maximum Quantity', required=True,
- digits_compute=dp.get_precision('Product Unit of Measure'),
- help="When the virtual stock goes below the Min Quantity, Odoo generates "\
- "a procurement to bring the forecasted quantity to the Quantity specified as Max Quantity."),
- 'qty_multiple': fields.float('Qty Multiple', required=True,
- digits_compute=dp.get_precision('Product Unit of Measure'),
- help="The procurement quantity will be rounded up to this multiple. If it is 0, the exact quantity will be used. "),
- 'procurement_ids': fields.one2many('procurement.order', 'orderpoint_id', 'Created Procurements'),
- 'group_id': fields.many2one('procurement.group', 'Procurement Group', help="Moves created through this orderpoint will be put in this procurement group. If none is given, the moves generated by procurement rules will be grouped into one big picking.", copy=False),
- 'company_id': fields.many2one('res.company', 'Company', required=True),
- 'lead_days': fields.integer('Lead Time', help="Number of days after the orderpoint is triggered to receive the products or to order to the vendor"),
- 'lead_type': fields.selection([
- ('net', 'Day(s) to get the products'),
- ('supplier', 'Day(s) to purchase')
- ], 'Lead Type', required=True)
- }
- _defaults = {
- 'active': lambda *a: 1,
- 'lead_days': lambda *a: 1,
- 'lead_type': lambda *a: 'supplier',
- 'qty_multiple': lambda *a: 1,
- 'name': lambda self, cr, uid, context: self.pool.get('ir.sequence').next_by_code(cr, uid, 'stock.orderpoint') or '',
- 'product_uom': lambda self, cr, uid, context: context.get('product_uom', False),
- 'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.warehouse.orderpoint', context=context)
- }
_sql_constraints = [
('qty_multiple_check', 'CHECK( qty_multiple >= 0 )', 'Qty Multiple must be greater than or equal to zero.'),
]
- _constraints = [
- (_check_product_uom, 'You have to select a product unit of measure in the same category than the default unit of measure of the product', ['product_id', 'product_uom']),
- ]
- def default_get(self, cr, uid, fields, context=None):
- warehouse_obj = self.pool.get('stock.warehouse')
- res = super(stock_warehouse_orderpoint, self).default_get(cr, uid, fields, context)
- # default 'warehouse_id' and 'location_id'
- if 'warehouse_id' not in res:
- warehouse_ids = res.get('company_id') and warehouse_obj.search(cr, uid, [('company_id', '=', res['company_id'])], limit=1, context=context) or []
- res['warehouse_id'] = warehouse_ids and warehouse_ids[0] or False
- if 'location_id' not in res:
- res['location_id'] = res.get('warehouse_id') and warehouse_obj.browse(cr, uid, res['warehouse_id'], context).lot_stock_id.id or False
- return res
+ @api.constrains('product_id', 'product_uom')
+ def _check_product_uom(self):
+ ''' Check if the UoM has the same category as the product standard UoM '''
+ if any(orderpoint.product_id.uom_id.category_id != orderpoint.product_uom.category_id for orderpoint in self):
+ raise ValidationError(_('You have to select a product unit of measure in the same category than the default unit of measure of the product'))
- def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, context=None):
- """ Finds location id for changed warehouse.
- @param warehouse_id: Changed id of warehouse.
- @return: Dictionary of values.
- """
- if warehouse_id:
- w = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context)
- v = {'location_id': w.lot_stock_id.id}
- return {'value': v}
- return {}
+ @api.onchange('warehouse_id')
+ def onchange_warehouse_id(self):
+ """ Finds location id for changed warehouse. """
+ if self.warehouse_id:
+ self.location_id = self.warehoues_id.lot_stock_id.id
- def onchange_product_id(self, cr, uid, ids, product_id, context=None):
- """ Finds UoM for changed product.
- @param product_id: Changed id of product.
- @return: Dictionary of values.
- """
- if product_id:
- prod = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
- d = {'product_uom': [('category_id', '=', prod.uom_id.category_id.id)]}
- v = {'product_uom': prod.uom_id.id}
- return {'value': v, 'domain': d}
+ @api.onchange('product_id')
+ def onchange_product_id(self):
+ if self.product_id:
+ self.product_uom = self.product_id.uom_id.id
+ return {'domain': {'product_uom': [('category_id', '=', self.product_id.uom_id.category_id.id)]}}
return {'domain': {'product_uom': []}}
- def _get_date_planned(self, cr, uid, ids, start_date, context=None):
- orderpoint = self.browse(cr, uid, ids[0], context=context)
- days = orderpoint.lead_days or 0.0
- if orderpoint.lead_type == 'purchase':
+ @api.multi
+ def subtract_procurements_from_orderpoints(self):
+ '''This function returns quantity of product that needs to be deducted from the orderpoint computed quantity because there's already a procurement created with aim to fulfill it.
+ '''
+ self._cr.execute("""SELECT orderpoint.id, procurement.id, procurement.product_uom, procurement.product_qty, template.uom_id, move.product_qty
+ FROM stock_warehouse_orderpoint orderpoint
+ JOIN procurement_order AS procurement ON procurement.orderpoint_id = orderpoint.id
+ JOIN product_product AS product ON product.id = procurement.product_id
+ JOIN product_template AS template ON template.id = product.product_tmpl_id
+ LEFT JOIN stock_move AS move ON move.procurement_id = procurement.id
+ WHERE procurement.state not in ('done', 'cancel')
+ AND (move.state IS NULL OR move.state != 'draft')
+ AND orderpoint.id IN %s
+ ORDER BY orderpoint.id, procurement.id
+ """, (tuple(self.ids),))
+ UoM = self.env["product.uom"]
+ procurements_done = set()
+ res = dict.fromkeys(self.ids, 0.0)
+ for orderpoint_id, procurement_id, product_uom_id, procurement_qty, template_uom_id, move_qty in self._cr.fetchall():
+ if procurement_id not in procurements_done: # count procurement once, if multiple move in this orderpoint/procurement combo
+ procurements_done.add(procurement_id)
+ res[orderpoint_id] += UoM._compute_qty(product_uom_id, procurement_qty, template_uom_id, round=False)
+ if move_qty:
+ res[orderpoint_id] -= move_qty
+ return res
+
+ def _get_date_planned(self, start_date):
+ days = self.lead_days or 0.0
+ if self.lead_type == 'purchase':
# These days will be substracted when creating the PO
- days += orderpoint.product_id._select_seller(orderpoint.product_id).delay or 0.0
+ days += self.product_id._select_seller(self.product_id).delay or 0.0
date_planned = start_date + relativedelta.relativedelta(days=days)
return date_planned.strftime(DEFAULT_SERVER_DATE_FORMAT)
- def _prepare_procurement_values(self, cr, uid, ids, product_qty, date=False, group=False, context=None):
- orderpoint = self.browse(cr, uid, ids[0], context=context)
+ @api.multi
+ def _prepare_procurement_values(self, product_qty, date=False, group=False):
return {
- 'name': orderpoint.name,
- 'date_planned': date or orderpoint._get_date_planned(datetime.today()),
- 'product_id': orderpoint.product_id.id,
+ 'name': self.name,
+ 'date_planned': date or self._get_date_planned(datetime.today()),
+ 'product_id': self.product_id.id,
'product_qty': product_qty,
- 'company_id': orderpoint.company_id.id,
- 'product_uom': orderpoint.product_uom.id,
- 'location_id': orderpoint.location_id.id,
- 'origin': orderpoint.name,
- 'warehouse_id': orderpoint.warehouse_id.id,
- 'orderpoint_id': orderpoint.id,
- 'group_id': group or orderpoint.group_id.id,
+ 'company_id': self.company_id.id,
+ 'product_uom': self.product_uom.id,
+ 'location_id': self.location_id.id,
+ 'origin': self.name,
+ 'warehouse_id': self.warehouse_id.id,
+ 'orderpoint_id': self.id,
+ 'group_id': group or self.group_id.id,
}
diff --git a/addons/stock/views/stock_warehouse_views.xml b/addons/stock/views/stock_warehouse_views.xml
index c3a6883482e..c13e4455c20 100644
--- a/addons/stock/views/stock_warehouse_views.xml
+++ b/addons/stock/views/stock_warehouse_views.xml
@@ -33,8 +33,8 @@
-
-
+
+
@@ -156,10 +156,10 @@
-
+
-
+