[MOV] stock: stock_warehouse
Warehouse models and views are moved in their own file. Impacted: - stock.warehouse - stock.warehouse.orderpoint This commit only moves files. No change is done.
This commit is contained in:
@@ -76,6 +76,7 @@ Dashboard / Reports for Inventory Management will include:
|
||||
'views/stock_picking_views.xml',
|
||||
'views/stock_quant_views.xml',
|
||||
'views/procurement_views.xml',
|
||||
'views/stock_warehouse_views.xml',
|
||||
'views/product_views.xml',
|
||||
'views/stock_config_settings_views.xml',
|
||||
|
||||
|
||||
@@ -11,4 +11,5 @@ import stock_config_settings
|
||||
import stock_inventory
|
||||
import stock_picking
|
||||
import stock_quant
|
||||
import stock_warehouse
|
||||
import web_planner
|
||||
|
||||
@@ -0,0 +1,930 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
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
|
||||
|
||||
|
||||
class stock_warehouse(osv.osv):
|
||||
_name = "stock.warehouse"
|
||||
_description = "Warehouse"
|
||||
|
||||
_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"),
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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']
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
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')
|
||||
|
||||
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 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
|
||||
|
||||
def _get_reception_delivery_route(self, cr, uid, ids, route_name, context=None):
|
||||
warehouse = self.browse(cr, uid, ids, context=context)[0]
|
||||
return {
|
||||
'name': self._format_routename(cr, uid, warehouse, route_name, context=context),
|
||||
'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:
|
||||
raise UserError(_('Can\'t find any generic Make To Order route.'))
|
||||
return mto_route_id
|
||||
|
||||
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]
|
||||
return {
|
||||
'name': self._format_routename(cr, uid, warehouse, route_name, context=context),
|
||||
'warehouse_selectable': False,
|
||||
'product_selectable': True,
|
||||
'product_categ_selectable': True,
|
||||
'active': warehouse.delivery_steps != 'ship_only' and warehouse.reception_steps != 'one_step',
|
||||
'sequence': 20,
|
||||
}
|
||||
|
||||
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
|
||||
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,
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
#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)
|
||||
|
||||
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)
|
||||
|
||||
#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)
|
||||
|
||||
route_obj.write(cr, uid, warehouse.crossdock_route_id.id, {'active': new_reception_step != 'one_step' and new_delivery_step != 'ship_only'}, context=context)
|
||||
|
||||
#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 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)
|
||||
|
||||
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)
|
||||
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)
|
||||
for mto_pull_val in mto_pull_vals:
|
||||
pull_obj.create(cr, uid, mto_pull_val, context=context)
|
||||
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)
|
||||
|
||||
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')])
|
||||
if pulls:
|
||||
pull_obj.write(cr, uid, pulls, {'location_id': new_location}, context=context)
|
||||
|
||||
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)
|
||||
|
||||
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)]})
|
||||
|
||||
# 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_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
|
||||
|
||||
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()
|
||||
|
||||
domain = [('id', 'in', all_routes)]
|
||||
return {
|
||||
'name': _('Warehouse\'s Routes'),
|
||||
'domain': domain,
|
||||
'res_model': 'stock.location.route',
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_id': False,
|
||||
'view_mode': 'tree,form',
|
||||
'view_type': 'form',
|
||||
'limit': 20
|
||||
}
|
||||
|
||||
|
||||
class stock_warehouse_orderpoint(osv.osv):
|
||||
"""
|
||||
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
|
||||
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 = {}
|
||||
|
||||
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
|
||||
|
||||
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 {}
|
||||
|
||||
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}
|
||||
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':
|
||||
# These days will be substracted when creating the PO
|
||||
days += orderpoint.product_id._select_seller(orderpoint.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)
|
||||
return {
|
||||
'name': orderpoint.name,
|
||||
'date_planned': date or orderpoint._get_date_planned(datetime.today()),
|
||||
'product_id': orderpoint.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,
|
||||
}
|
||||
+1
-918
@@ -1404,771 +1404,10 @@ class stock_move(osv.osv):
|
||||
move.write({'partially_available': False})
|
||||
self.pool['stock.quant'].write(cr, SUPERUSER_ID, related_quants, {'reservation_id': False}, context=context)
|
||||
|
||||
|
||||
# WHERE IS ORDERPOINT ?
|
||||
#----------------------------------------------------------
|
||||
# Stock Warehouse
|
||||
#----------------------------------------------------------
|
||||
class stock_warehouse(osv.osv):
|
||||
_name = "stock.warehouse"
|
||||
_description = "Warehouse"
|
||||
|
||||
_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"),
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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']
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
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')
|
||||
|
||||
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 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
|
||||
|
||||
def _get_reception_delivery_route(self, cr, uid, ids, route_name, context=None):
|
||||
warehouse = self.browse(cr, uid, ids, context=context)[0]
|
||||
return {
|
||||
'name': self._format_routename(cr, uid, warehouse, route_name, context=context),
|
||||
'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:
|
||||
raise UserError(_('Can\'t find any generic Make To Order route.'))
|
||||
return mto_route_id
|
||||
|
||||
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]
|
||||
return {
|
||||
'name': self._format_routename(cr, uid, warehouse, route_name, context=context),
|
||||
'warehouse_selectable': False,
|
||||
'product_selectable': True,
|
||||
'product_categ_selectable': True,
|
||||
'active': warehouse.delivery_steps != 'ship_only' and warehouse.reception_steps != 'one_step',
|
||||
'sequence': 20,
|
||||
}
|
||||
|
||||
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
|
||||
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,
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
#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)
|
||||
|
||||
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)
|
||||
|
||||
#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)
|
||||
|
||||
route_obj.write(cr, uid, warehouse.crossdock_route_id.id, {'active': new_reception_step != 'one_step' and new_delivery_step != 'ship_only'}, context=context)
|
||||
|
||||
#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 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)
|
||||
|
||||
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)
|
||||
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)
|
||||
for mto_pull_val in mto_pull_vals:
|
||||
pull_obj.create(cr, uid, mto_pull_val, context=context)
|
||||
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)
|
||||
|
||||
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')])
|
||||
if pulls:
|
||||
pull_obj.write(cr, uid, pulls, {'location_id': new_location}, context=context)
|
||||
|
||||
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)
|
||||
|
||||
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)]})
|
||||
|
||||
# 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_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
|
||||
|
||||
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()
|
||||
|
||||
domain = [('id', 'in', all_routes)]
|
||||
return {
|
||||
'name': _('Warehouse\'s Routes'),
|
||||
'domain': domain,
|
||||
'res_model': 'stock.location.route',
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_id': False,
|
||||
'view_mode': 'tree,form',
|
||||
'view_type': 'form',
|
||||
'limit': 20
|
||||
}
|
||||
|
||||
|
||||
class stock_location_path(osv.osv):
|
||||
_name = "stock.location.path"
|
||||
@@ -2605,162 +1844,6 @@ class stock_move_operation_link(osv.osv):
|
||||
}
|
||||
|
||||
|
||||
class stock_warehouse_orderpoint(osv.osv):
|
||||
"""
|
||||
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
|
||||
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 = {}
|
||||
|
||||
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
|
||||
|
||||
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 {}
|
||||
|
||||
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}
|
||||
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':
|
||||
# These days will be substracted when creating the PO
|
||||
days += orderpoint.product_id._select_seller(orderpoint.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)
|
||||
return {
|
||||
'name': orderpoint.name,
|
||||
'date_planned': date or orderpoint._get_date_planned(datetime.today()),
|
||||
'product_id': orderpoint.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,
|
||||
}
|
||||
|
||||
|
||||
class StockPackOperation(models.Model):
|
||||
_inherit = 'stock.pack.operation'
|
||||
|
||||
|
||||
@@ -290,101 +290,6 @@
|
||||
groups="stock.group_stock_multi_locations"
|
||||
parent="menu_warehouse_config" sequence="2"/>
|
||||
|
||||
<record id="view_warehouse" model="ir.ui.view">
|
||||
<field name="name">stock.warehouse</field>
|
||||
<field name="model">stock.warehouse</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Warehouse">
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="toggle_active" type="object" class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
|
||||
</button>
|
||||
<button name="action_view_all_routes"
|
||||
string="Routes"
|
||||
icon="fa-refresh"
|
||||
class="oe_stat_button"
|
||||
type="object"/>
|
||||
</div>
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1><field name="name"/></h1>
|
||||
<group>
|
||||
<group>
|
||||
<field name="code"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
|
||||
<field name="partner_id"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook colspan="4" groups="stock.group_adv_location">
|
||||
<page string="Warehouse Configuration" colspan="4">
|
||||
<group colspan="4">
|
||||
<field name="reception_steps" widget='radio'/>
|
||||
<field name="delivery_steps" widget='radio'/>
|
||||
<field name="default_resupply_wh_id" widget='selection' on_change="onchange_filter_default_resupply_wh_id(default_resupply_wh_id, resupply_wh_ids)"/>
|
||||
<field name="resupply_wh_ids" domain="[('id', '!=', id)]" widget='many2many_checkboxes' on_change="onchange_filter_default_resupply_wh_id(default_resupply_wh_id, resupply_wh_ids)"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Technical Information" groups='base.group_no_one'>
|
||||
<group>
|
||||
<group string="Locations">
|
||||
<field name="wh_input_stock_loc_id" readonly="1"/>
|
||||
<field name="wh_qc_stock_loc_id" readonly="1"/>
|
||||
<field name="wh_pack_stock_loc_id" readonly="1"/>
|
||||
<field name="wh_output_stock_loc_id" readonly="1"/>
|
||||
</group>
|
||||
<group strinsg="Operation Types">
|
||||
<field name="in_type_id" readonly="1"/>
|
||||
<field name="int_type_id" readonly="1"/>
|
||||
<field name="pick_type_id" readonly="1"/>
|
||||
<field name="pack_type_id" readonly="1"/>
|
||||
<field name="out_type_id" readonly="1"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_warehouse_tree" model="ir.ui.view">
|
||||
<field name="name">stock.warehouse.tree</field>
|
||||
<field name="model">stock.warehouse</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Warehouse">
|
||||
<field name="name"/>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="lot_stock_id" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="partner_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="stock_warehouse_view_search" model="ir.ui.view">
|
||||
<field name="name">stock.warehouse.search</field>
|
||||
<field name="model">stock.warehouse</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Warehouse">
|
||||
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
<record id="action_warehouse_form" model="ir.actions.act_window">
|
||||
<field name="name">Warehouses</field>
|
||||
<field name="res_model">stock.warehouse</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="view_warehouse_tree"/>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to define a new warehouse.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
<menuitem action="action_warehouse_form" id="menu_action_warehouse_form"
|
||||
groups="stock.group_stock_multi_warehouses"
|
||||
parent="menu_warehouse_config" sequence="1"/>
|
||||
|
||||
<!--
|
||||
Stock picking
|
||||
Defaults and Internal Pickings
|
||||
@@ -899,121 +804,6 @@
|
||||
|
||||
<menuitem action="action_incoterms_tree" id="menu_action_incoterm_open" parent="menu_warehouse_config" sequence="5" groups="base.group_no_one"/>
|
||||
|
||||
<!-- Order Point -->
|
||||
<record id="do_view_procurement_op" model="ir.actions.act_window">
|
||||
<field name="name">Procurements</field>
|
||||
<field name="res_model">procurement.order</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="domain">[('orderpoint_id','=',active_id)]</field>
|
||||
</record>
|
||||
|
||||
<record id="view_warehouse_orderpoint_tree" model="ir.ui.view">
|
||||
<field name="name">stock.warehouse.orderpoint.tree</field>
|
||||
<field name="model">stock.warehouse.orderpoint</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Reordering Rules">
|
||||
<field name="name"/>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="warehouse_id" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="location_id" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="product_id"/>
|
||||
<field name="product_uom" options="{'no_open': True, 'no_create': True}" groups="product.group_uom"/>
|
||||
<field name="product_min_qty"/>
|
||||
<field name="product_max_qty"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="warehouse_orderpoint_search">
|
||||
<field name="name">stock.warehouse.orderpoint.search</field>
|
||||
<field name="model">stock.warehouse.orderpoint</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Reordering Rules Search">
|
||||
<field name="name" string="Reordering Rules"/>
|
||||
<field name="warehouse_id"/>
|
||||
<field name="location_id" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="product_id"/>
|
||||
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Warehouse" domain="[]" context="{'group_by':'warehouse_id'}"/>
|
||||
<filter string="Location" domain="[]" context="{'group_by':'location_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_warehouse_orderpoint_form" model="ir.ui.view">
|
||||
<field name="name">stock.warehouse.orderpoint.form</field>
|
||||
<field name="model">stock.warehouse.orderpoint</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Reordering Rules">
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="toggle_active" type="object" class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
|
||||
</button>
|
||||
<button name="%(stock.do_view_procurement_op)d" type="action" string="Procurements" class="oe_stat_button" icon="fa-refresh"/>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="name" />
|
||||
<field name="product_id" on_change="onchange_product_id(product_id)"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="warehouse_id" on_change="onchange_warehouse_id(warehouse_id)" widget="selection" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="product_uom" options="{'no_open': True, 'no_create': True}" groups="product.group_uom"/>
|
||||
<field name="location_id" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="group_id" groups="stock.group_adv_location"/>
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group string="Rules">
|
||||
<field name="product_min_qty" />
|
||||
<field name="product_max_qty" />
|
||||
<field name="qty_multiple" string="Quantity Multiple"/>
|
||||
</group>
|
||||
<group string="Misc">
|
||||
<label for="lead_days"/>
|
||||
<div class="o_row">
|
||||
<field name="lead_days"/>
|
||||
<field name="lead_type"/>
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_orderpoint_form" model="ir.actions.act_window">
|
||||
<field name="name">Reordering Rules</field>
|
||||
<field name="res_model">stock.warehouse.orderpoint</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="view_warehouse_orderpoint_tree"/>
|
||||
<field name="search_view_id" ref="warehouse_orderpoint_search" />
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to add a reordering rule.
|
||||
</p><p>You can define your minimum stock rules, so that Odoo will automatically create draft manufacturing orders or request for quotations according to the stock level. Once the virtual stock of a product (= stock on hand minus all confirmed orders and reservations) is below the minimum quantity, Odoo will generate a procurement request to increase the stock up to the maximum quantity.</p>
|
||||
</field>
|
||||
</record>
|
||||
<menuitem
|
||||
id="menu_reordering_rules_config"
|
||||
action="action_orderpoint_form"
|
||||
name="Reordering Rules" parent="menu_stock_inventory_control" sequence="10"/>
|
||||
|
||||
<act_window
|
||||
context="{'search_default_warehouse_id': active_id, 'default_warehouse_id': active_id}"
|
||||
id="act_stock_warehouse_2_stock_warehouse_orderpoint"
|
||||
name="Reordering Rules"
|
||||
res_model="stock.warehouse.orderpoint"
|
||||
src_model="stock.warehouse"
|
||||
groups="stock.group_stock_user"/>
|
||||
|
||||
<record model="ir.actions.act_window" id="stolocpath">
|
||||
<field name="context">{'search_default_route_false':1}</field>
|
||||
<field name="name">Push Rules</field>
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_warehouse" model="ir.ui.view">
|
||||
<field name="name">stock.warehouse</field>
|
||||
<field name="model">stock.warehouse</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Warehouse">
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="toggle_active" type="object" class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
|
||||
</button>
|
||||
<button name="action_view_all_routes"
|
||||
string="Routes"
|
||||
icon="fa-refresh"
|
||||
class="oe_stat_button"
|
||||
type="object"/>
|
||||
</div>
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1><field name="name"/></h1>
|
||||
<group>
|
||||
<group>
|
||||
<field name="code"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
|
||||
<field name="partner_id"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook colspan="4" groups="stock.group_adv_location">
|
||||
<page string="Warehouse Configuration" colspan="4">
|
||||
<group colspan="4">
|
||||
<field name="reception_steps" widget='radio'/>
|
||||
<field name="delivery_steps" widget='radio'/>
|
||||
<field name="default_resupply_wh_id" widget='selection' on_change="onchange_filter_default_resupply_wh_id(default_resupply_wh_id, resupply_wh_ids)"/>
|
||||
<field name="resupply_wh_ids" domain="[('id', '!=', id)]" widget='many2many_checkboxes' on_change="onchange_filter_default_resupply_wh_id(default_resupply_wh_id, resupply_wh_ids)"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Technical Information" groups='base.group_no_one'>
|
||||
<group>
|
||||
<group string="Locations">
|
||||
<field name="wh_input_stock_loc_id" readonly="1"/>
|
||||
<field name="wh_qc_stock_loc_id" readonly="1"/>
|
||||
<field name="wh_pack_stock_loc_id" readonly="1"/>
|
||||
<field name="wh_output_stock_loc_id" readonly="1"/>
|
||||
</group>
|
||||
<group strinsg="Operation Types">
|
||||
<field name="in_type_id" readonly="1"/>
|
||||
<field name="int_type_id" readonly="1"/>
|
||||
<field name="pick_type_id" readonly="1"/>
|
||||
<field name="pack_type_id" readonly="1"/>
|
||||
<field name="out_type_id" readonly="1"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_warehouse_tree" model="ir.ui.view">
|
||||
<field name="name">stock.warehouse.tree</field>
|
||||
<field name="model">stock.warehouse</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Warehouse">
|
||||
<field name="name"/>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="lot_stock_id" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="partner_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="stock_warehouse_view_search" model="ir.ui.view">
|
||||
<field name="name">stock.warehouse.search</field>
|
||||
<field name="model">stock.warehouse</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Warehouse">
|
||||
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
<record id="action_warehouse_form" model="ir.actions.act_window">
|
||||
<field name="name">Warehouses</field>
|
||||
<field name="res_model">stock.warehouse</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="view_warehouse_tree"/>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to define a new warehouse.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
<menuitem action="action_warehouse_form" id="menu_action_warehouse_form"
|
||||
groups="stock.group_stock_multi_warehouses"
|
||||
parent="menu_warehouse_config" sequence="1"/>
|
||||
|
||||
<!-- Order Point -->
|
||||
<record id="do_view_procurement_op" model="ir.actions.act_window">
|
||||
<field name="name">Procurements</field>
|
||||
<field name="res_model">procurement.order</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="domain">[('orderpoint_id','=',active_id)]</field>
|
||||
</record>
|
||||
|
||||
<record id="view_warehouse_orderpoint_tree" model="ir.ui.view">
|
||||
<field name="name">stock.warehouse.orderpoint.tree</field>
|
||||
<field name="model">stock.warehouse.orderpoint</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Reordering Rules">
|
||||
<field name="name"/>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="warehouse_id" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="location_id" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="product_id"/>
|
||||
<field name="product_uom" options="{'no_open': True, 'no_create': True}" groups="product.group_uom"/>
|
||||
<field name="product_min_qty"/>
|
||||
<field name="product_max_qty"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="warehouse_orderpoint_search">
|
||||
<field name="name">stock.warehouse.orderpoint.search</field>
|
||||
<field name="model">stock.warehouse.orderpoint</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Reordering Rules Search">
|
||||
<field name="name" string="Reordering Rules"/>
|
||||
<field name="warehouse_id"/>
|
||||
<field name="location_id" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="product_id"/>
|
||||
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Warehouse" domain="[]" context="{'group_by':'warehouse_id'}"/>
|
||||
<filter string="Location" domain="[]" context="{'group_by':'location_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_warehouse_orderpoint_form" model="ir.ui.view">
|
||||
<field name="name">stock.warehouse.orderpoint.form</field>
|
||||
<field name="model">stock.warehouse.orderpoint</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Reordering Rules">
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="toggle_active" type="object" class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
|
||||
</button>
|
||||
<button name="%(stock.do_view_procurement_op)d" type="action" string="Procurements" class="oe_stat_button" icon="fa-refresh"/>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="name" />
|
||||
<field name="product_id" on_change="onchange_product_id(product_id)"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="warehouse_id" on_change="onchange_warehouse_id(warehouse_id)" widget="selection" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="product_uom" options="{'no_open': True, 'no_create': True}" groups="product.group_uom"/>
|
||||
<field name="location_id" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="group_id" groups="stock.group_adv_location"/>
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group string="Rules">
|
||||
<field name="product_min_qty" />
|
||||
<field name="product_max_qty" />
|
||||
<field name="qty_multiple" string="Quantity Multiple"/>
|
||||
</group>
|
||||
<group string="Misc">
|
||||
<label for="lead_days"/>
|
||||
<div class="o_row">
|
||||
<field name="lead_days"/>
|
||||
<field name="lead_type"/>
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_orderpoint_form" model="ir.actions.act_window">
|
||||
<field name="name">Reordering Rules</field>
|
||||
<field name="res_model">stock.warehouse.orderpoint</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="view_warehouse_orderpoint_tree"/>
|
||||
<field name="search_view_id" ref="warehouse_orderpoint_search" />
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to add a reordering rule.
|
||||
</p><p>You can define your minimum stock rules, so that Odoo will automatically create draft manufacturing orders or request for quotations according to the stock level. Once the virtual stock of a product (= stock on hand minus all confirmed orders and reservations) is below the minimum quantity, Odoo will generate a procurement request to increase the stock up to the maximum quantity.</p>
|
||||
</field>
|
||||
</record>
|
||||
<menuitem
|
||||
id="menu_reordering_rules_config"
|
||||
action="action_orderpoint_form"
|
||||
name="Reordering Rules" parent="menu_stock_inventory_control" sequence="10"/>
|
||||
|
||||
<act_window
|
||||
context="{'search_default_warehouse_id': active_id, 'default_warehouse_id': active_id}"
|
||||
id="act_stock_warehouse_2_stock_warehouse_orderpoint"
|
||||
name="Reordering Rules"
|
||||
res_model="stock.warehouse.orderpoint"
|
||||
src_model="stock.warehouse"
|
||||
groups="stock.group_stock_user"/>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user