922 lines
52 KiB
Python
922 lines
52 KiB
Python
# -*- coding: utf-8 -*-
|
|
##############################################################################
|
|
#
|
|
# OpenERP, Open Source Management Solution
|
|
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Affero General Public License as
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
# License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
##############################################################################
|
|
|
|
from openerp.osv import fields, osv
|
|
from datetime import *
|
|
from dateutil.relativedelta import relativedelta
|
|
from openerp.tools.translate import _
|
|
|
|
class stock_location_route(osv.osv):
|
|
_inherit = 'stock.location.route'
|
|
_description = "Inventory Routes"
|
|
|
|
_columns = {
|
|
'push_ids': fields.one2many('stock.location.path', 'route_id', 'Push Rules'),
|
|
'product_selectable': fields.boolean('Selectable on Product'),
|
|
'product_categ_selectable': fields.boolean('Selectable on Product Category'),
|
|
'warehouse_selectable': fields.boolean('Selectable on Warehouse'),
|
|
'supplied_wh_id': fields.many2one('stock.warehouse', 'Supplied Warehouse'),
|
|
'supplier_wh_id': fields.many2one('stock.warehouse', 'Supplier Warehouse'),
|
|
}
|
|
_defaults = {
|
|
'product_selectable': True,
|
|
}
|
|
|
|
class stock_warehouse(osv.osv):
|
|
_inherit = 'stock.warehouse'
|
|
_columns = {
|
|
'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', 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', 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', 'Reception Route'),
|
|
'delivery_route_id': fields.many2one('stock.location.route', 'Delivery Route'),
|
|
'resupply_from_wh': fields.boolean('Resupply From Other Warehouses'),
|
|
'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'),
|
|
'default_resupply_wh_id': fields.many2one('stock.warehouse', 'Default Resupply Warehouse'),
|
|
}
|
|
|
|
_defaults = {
|
|
'reception_steps': 'one_step',
|
|
'delivery_steps': 'ship_only',
|
|
}
|
|
|
|
def _get_inter_wh_location(self, cr, uid, warehouse, 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')
|
|
try:
|
|
inter_wh_loc = data_obj.get_object_reference(cr, uid, 'stock', 'stock_location_inter_wh')[1]
|
|
except:
|
|
inter_wh_loc = False
|
|
return inter_wh_loc
|
|
|
|
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 osv.except_osv(_('Error!'), _('Can\'t find any customer or supplier location.'))
|
|
return location_obj.browse(cr, uid, [customer_loc, supplier_loc], context=context)
|
|
|
|
def switch_location(self, cr, uid, ids, warehouse, new_reception_step=False, new_delivery_step=False, context=None):
|
|
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:
|
|
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:
|
|
location_obj.write(cr, uid, [warehouse.wh_output_stock_loc_id.id, 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, warehouse, route_name, context=None):
|
|
return {
|
|
'name': self._format_routename(cr, uid, warehouse, route_name, context=context),
|
|
'product_categ_selectable': True,
|
|
'product_selectable': False,
|
|
}
|
|
|
|
def _get_push_pull_rules(self, cr, uid, warehouse, active, values, new_route_id, context=None):
|
|
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,
|
|
})
|
|
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,
|
|
})
|
|
first_rule = False
|
|
return push_rules_list, pull_rules_list
|
|
|
|
def _get_mto_pull_rule(self, cr, uid, warehouse, values, context=None):
|
|
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', _('MTO'))], context=context)
|
|
mto_route_id = mto_route_id and mto_route_id[0] or False
|
|
if not mto_route_id:
|
|
raise osv.except_osv(_('Error!'), _('Can\'t find any generic MTO route.'))
|
|
|
|
from_loc, dest_loc, pick_type_id = values[0]
|
|
return {
|
|
'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,
|
|
}
|
|
|
|
def _get_crossdock_route(self, cr, uid, warehouse, route_name, context=None):
|
|
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',
|
|
}
|
|
|
|
def _get_all_products_to_resupply(self, cr, uid, warehouse, context=None):
|
|
return self.pool.get('product.product').search(cr, uid, [], context=context)
|
|
|
|
def _assign_route_on_products(self, cr, uid, warehouse, inter_wh_route_id, context=None):
|
|
product_ids = self._get_all_products_to_resupply(cr, uid, warehouse, context=context)
|
|
self.pool.get('product.product').write(cr, uid, product_ids, {'route_ids': [(4, inter_wh_route_id)]}, context=context)
|
|
|
|
def _get_inter_wh_route(self, cr, uid, warehouse, wh, context=None):
|
|
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, warehouse, supplier_warehouses, default_resupply_wh, context=None):
|
|
location_obj = self.pool.get('stock.location')
|
|
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
|
|
inter_wh_location_id = self._get_inter_wh_location(cr, uid, warehouse, context=context)
|
|
if inter_wh_location_id:
|
|
input_loc = warehouse.wh_input_stock_loc_id
|
|
if warehouse.reception_steps == 'one_step':
|
|
input_loc = warehouse.lot_stock_id
|
|
inter_wh_location = location_obj.browse(cr, uid, inter_wh_location_id, context=context)
|
|
for wh in supplier_warehouses:
|
|
output_loc = wh.wh_output_stock_loc_id
|
|
if wh.delivery_steps == 'ship_only':
|
|
output_loc = wh.lot_stock_id
|
|
inter_wh_route_vals = self._get_inter_wh_route(cr, uid, warehouse, wh, context=context)
|
|
inter_wh_route_id = route_obj.create(cr, uid, vals=inter_wh_route_vals, context=context)
|
|
values = [(output_loc, inter_wh_location, wh.out_type_id.id), (inter_wh_location, input_loc, warehouse.in_type_id.id)]
|
|
dummy, pull_rules_list = self._get_push_pull_rules(cr, uid, warehouse, True, 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 all product
|
|
if default_resupply_wh and default_resupply_wh.id == wh.id:
|
|
self._assign_route_on_products(cr, uid, warehouse, inter_wh_route_id, context=context)
|
|
|
|
def create_routes(self, cr, uid, ids, warehouse, context=None):
|
|
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 = self.get_routes_dict(cr, uid, ids, warehouse, context=context)
|
|
#create reception route and rules
|
|
route_name, values = routes_dict[warehouse.reception_steps]
|
|
route_vals = self._get_reception_delivery_route(cr, uid, warehouse, route_name, context=context)
|
|
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 = self._get_push_pull_rules(cr, uid, warehouse, True, values, reception_route_id, context=context)
|
|
#create the push/pull 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:
|
|
pull_obj.create(cr, uid, vals=pull_rule, context=context)
|
|
|
|
#create MTS route and pull rules for delivery a specific route MTO to be set on the product
|
|
route_name, values = routes_dict[warehouse.delivery_steps]
|
|
route_vals = self._get_reception_delivery_route(cr, uid, warehouse, route_name, context=context)
|
|
#create the route and its pull 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 = self._get_push_pull_rules(cr, uid, warehouse, True, values, delivery_route_id, context=context)
|
|
for pull_rule in pull_rules_list:
|
|
pull_obj.create(cr, uid, vals=pull_rule, context=context)
|
|
#create MTO pull rule and link it to the generic MTO route
|
|
mto_pull_vals = self._get_mto_pull_rule(cr, uid, warehouse, values, context=context)
|
|
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 = self._get_crossdock_route(cr, uid, warehouse, route_name, context=context)
|
|
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 = self._get_push_pull_rules(cr, uid, warehouse, warehouse.delivery_steps != 'ship_only' and warehouse.reception_steps != 'one_step', values, crossdock_route_id, context=context)
|
|
for pull_rule in pull_rules_list:
|
|
pull_obj.create(cr, uid, vals=pull_rule, context=context)
|
|
|
|
#create route selectable on the product to resupply the warehouse from another one
|
|
self._create_resupply_routes(cr, uid, warehouse, warehouse.resupply_wh_ids, warehouse.default_resupply_wh_id, context=context)
|
|
|
|
#set routes and mto pull rule on warehouse
|
|
return self.write(cr, uid, warehouse.id, {
|
|
'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,
|
|
}, context=context)
|
|
|
|
def change_route(self, cr, uid, ids, warehouse, new_reception_step=False, new_delivery_step=False, context=None):
|
|
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.int_type_id.id, {'active': new_reception_step != 'one_step'}, context=context)
|
|
picking_type_obj.write(cr, uid, warehouse.pick_type_id.id, {'active': new_delivery_step != 'ship_only'}, context=context)
|
|
picking_type_obj.write(cr, uid, warehouse.pack_type_id.id, {'active': new_delivery_step == 'pick_pack_ship'}, context=context)
|
|
|
|
routes_dict = self.get_routes_dict(cr, uid, ids, warehouse, context=context)
|
|
#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 = self._get_push_pull_rules(cr, uid, warehouse, True, values, warehouse.delivery_route_id.id, context=context)
|
|
#create the pull rules
|
|
for pull_rule in pull_rules_list:
|
|
pull_obj.create(cr, uid, vals=pull_rule, context=context)
|
|
|
|
#update reception route and rules: unlink the existing rules of the warehouse reception 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 = self._get_push_pull_rules(cr, uid, warehouse, True, values, warehouse.reception_route_id.id, context=context)
|
|
#create the push/pull 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:
|
|
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 = self._get_mto_pull_rule(cr, uid, warehouse, values, context=context)
|
|
pull_obj.write(cr, uid, warehouse.mto_pull_id.id, mto_pull_vals, context=context)
|
|
return True
|
|
|
|
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
|
|
wh_loc_id = location_obj.create(cr, uid, {
|
|
'name': _(vals.get('name')),
|
|
'usage': 'view',
|
|
'location_id': data_obj.get_object_reference(cr, uid, 'stock', 'stock_location_locations')[1]
|
|
}, context=context)
|
|
#create all location
|
|
reception_steps = vals.get('reception_steps', False)
|
|
delivery_steps = vals.get('delivery_steps', False)
|
|
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:
|
|
location_id = location_obj.create(cr, uid, {
|
|
'name': values['name'],
|
|
'usage': 'internal',
|
|
'location_id': wh_loc_id,
|
|
'active': values['active'],
|
|
}, context=context_with_inactive)
|
|
vals[values['field']] = location_id
|
|
|
|
#create new sequences
|
|
in_seq_id = seq_obj.create(cr, uid, values={'name': vals.get('name', '') + _(' Sequence in'), 'prefix': vals.get('code', '') + '\IN\\', 'padding': 5}, context=context)
|
|
out_seq_id = seq_obj.create(cr, uid, values={'name': vals.get('name', '') + _(' Sequence out'), 'prefix': vals.get('code', '') + '\OUT\\', 'padding': 5}, context=context)
|
|
pack_seq_id = seq_obj.create(cr, uid, values={'name': vals.get('name', '') + _(' Sequence packing'), 'prefix': vals.get('code', '') + '\PACK\\', 'padding': 5}, context=context)
|
|
pick_seq_id = seq_obj.create(cr, uid, values={'name': vals.get('name', '') + _(' Sequence picking'), 'prefix': vals.get('code', '') + '\PICK\\', 'padding': 5}, context=context)
|
|
int_seq_id = seq_obj.create(cr, uid, values={'name': vals.get('name', '') + _(' Sequence internal'), 'prefix': vals.get('code', '') + '\INT\\', 'padding': 5}, context=context)
|
|
|
|
#create WH
|
|
new_id = super(stock_warehouse, self).create(cr, uid, vals=vals, context=context)
|
|
|
|
warehouse = self.browse(cr, uid, new_id, 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
|
|
|
|
#fetch customer and supplier locations, for references
|
|
customer_loc, supplier_loc = self._get_partner_locations(cr, uid, new_id, context=context)
|
|
|
|
#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
|
|
all_used_colors = self.pool.get('stock.picking.type').search_read(cr, uid, [('warehouse_id', '!=', False), ('color', '!=', False)], ['color'], order='color')
|
|
not_used_colors = list(set(range(1, 10)) - set([x['color'] for x in all_used_colors]))
|
|
color = not_used_colors and not_used_colors[0] or 1
|
|
|
|
in_type_id = picking_type_obj.create(cr, uid, vals={
|
|
'name': _('Receptions'),
|
|
'warehouse_id': new_id,
|
|
'code_id': 'incoming',
|
|
'auto_force_assign': True,
|
|
'sequence_id': in_seq_id,
|
|
'default_location_src_id': supplier_loc.id,
|
|
'default_location_dest_id': input_loc.id,
|
|
'color': color}, context=context)
|
|
out_type_id = picking_type_obj.create(cr, uid, vals={
|
|
'name': _('Delivery Orders'),
|
|
'warehouse_id': new_id,
|
|
'code_id': 'outgoing',
|
|
'sequence_id': out_seq_id,
|
|
'delivery': True,
|
|
'default_location_src_id': output_loc.id,
|
|
'default_location_dest_id': customer_loc.id,
|
|
'color' : color}, context=context)
|
|
int_type_id = picking_type_obj.create(cr, uid, vals={
|
|
'name': _('Internal Transfers'),
|
|
'warehouse_id': new_id,
|
|
'code_id': 'internal',
|
|
'sequence_id': int_seq_id,
|
|
'default_location_src_id': wh_stock_loc.id,
|
|
'default_location_dest_id': wh_stock_loc.id,
|
|
'active': reception_steps != 'one_step',
|
|
'pack': False,
|
|
'color' : color}, context=context)
|
|
pack_type_id = picking_type_obj.create(cr, uid, vals={
|
|
'name': _('Pack'),
|
|
'warehouse_id': new_id,
|
|
'code_id': 'internal',
|
|
'sequence_id': pack_seq_id,
|
|
'default_location_src_id': wh_pack_stock_loc.id,
|
|
'default_location_dest_id': output_loc.id,
|
|
'active': delivery_steps == 'pick_pack_ship',
|
|
'pack': True,
|
|
'color' : color}, context=context)
|
|
pick_type_id = picking_type_obj.create(cr, uid, vals={
|
|
'name': _('Pick'),
|
|
'warehouse_id': new_id,
|
|
'code_id': 'internal',
|
|
'sequence_id': pick_seq_id,
|
|
'default_location_src_id': wh_stock_loc.id,
|
|
'default_location_dest_id': wh_pack_stock_loc.id,
|
|
'active': delivery_steps != 'ship_only',
|
|
'pack': False,
|
|
'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, new_id, vals=vals, context=context)
|
|
warehouse.refresh()
|
|
|
|
#create routes and push/pull rules
|
|
self.create_routes(cr, uid, new_id, warehouse, context=context)
|
|
return new_id
|
|
|
|
def _format_rulename(self, cr, uid, obj, from_loc, dest_loc, context=None):
|
|
return obj.name + ': ' + 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, warehouse, context=None):
|
|
#fetch customer and supplier locations, for references
|
|
customer_loc, supplier_loc = self._get_partner_locations(cr, uid, ids, context=context)
|
|
|
|
return {
|
|
'one_step': (_('Reception in 1 step'), []),
|
|
'two_steps': (_('Reception in 2 steps'), [(warehouse.wh_input_stock_loc_id, warehouse.lot_stock_id, warehouse.int_type_id.id)]),
|
|
'three_steps': (_('Reception 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.int_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 write(self, cr, uid, ids, vals, context=None):
|
|
if context is None:
|
|
context = {}
|
|
if isinstance(ids, (int, long)):
|
|
ids = [ids]
|
|
if context is None:
|
|
context = {}
|
|
seq_obj = self.pool.get('ir.sequence')
|
|
location_obj = self.pool.get('stock.location')
|
|
warehouse_obj = self.pool.get('stock.warehouse')
|
|
route_obj = self.pool.get('stock.location.route')
|
|
pull_obj = self.pool.get('procurement.rule')
|
|
push_obj = self.pool.get('stock.location.path')
|
|
|
|
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
|
|
self.switch_location(cr, uid, warehouse.id, warehouse, vals.get('reception_steps', False), vals.get('delivery_steps', False), context=context)
|
|
# switch between route
|
|
self.change_route(cr, uid, ids, warehouse, vals.get('reception_steps', False), vals.get('delivery_steps', False), context=context_with_inactive)
|
|
if vals.get('code') or vals.get('name'):
|
|
name = warehouse.name
|
|
#rename sequence
|
|
if vals.get('name'):
|
|
name = vals.get('name')
|
|
#rename location
|
|
location_id = warehouse.lot_stock_id.location_id.id
|
|
location_obj.write(cr, uid, location_id, {'name': name}, context=context_with_inactive)
|
|
#rename route and push-pull rules
|
|
for route in warehouse.route_ids:
|
|
route_obj.write(cr, uid, route.id, {'name': route.name.replace(warehouse.name, name, 1)}, context=context_with_inactive)
|
|
for pull in route.pull_ids:
|
|
pull_obj.write(cr, uid, pull.id, {'name': pull.name.replace(warehouse.name, name, 1)}, context=context_with_inactive)
|
|
for push in route.push_ids:
|
|
push_obj.write(cr, uid, push.id, {'name': pull.name.replace(warehouse.name, name, 1)}, context=context_with_inactive)
|
|
#change the mto pull rule name
|
|
pull_obj.write(cr, uid, warehouse.mto_pull_id.id, {'name': warehouse.mto_pull_id.name.replace(warehouse.name, name, 1)}, context=context_with_inactive)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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
|
|
supplier_warehouses = warehouse_obj.browse(cr, uid, list(to_add_wh_ids), context=context)
|
|
self._create_resupply_routes(cr, uid, warehouse, supplier_warehouses, warehouse.default_resupply_wh_id, context=context)
|
|
to_remove_wh_ids = old_ids - new_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)
|
|
route_obj.unlink(cr, uid, to_remove_route_ids, context=context)
|
|
else:
|
|
#not implemented
|
|
pass
|
|
if 'default_resupply_wh_id' in vals:
|
|
if warehouse.default_resupply_wh_id:
|
|
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)
|
|
route_obj.unlink(cr, uid, to_remove_route_ids, context=context)
|
|
self._create_resupply_routes(cr, uid, warehouse, [warehouse.default_resupply_wh_id], False, context=context)
|
|
if vals.get('default_resupply_wh_id'):
|
|
to_remove_route_ids = route_obj.search(cr, uid, [('supplied_wh_id', '=', warehouse.id), ('supplier_wh_id', '=', vals.get('default_resupply_wh_id'))], context=context)
|
|
route_obj.unlink(cr, uid, to_remove_route_ids, context=context)
|
|
def_supplier_wh = warehouse_obj.browse(cr, uid, vals['default_resupply_wh_id'], context=context)
|
|
self._create_resupply_routes(cr, uid, warehouse, [def_supplier_wh], def_supplier_wh, context=context)
|
|
|
|
return super(stock_warehouse, self).write(cr, uid, ids, vals=vals, context=context)
|
|
|
|
def unlink(self, cr, uid, ids, context=None):
|
|
#TODO try to delete location and route and if not possible, put them in inactive
|
|
return super(stock_warehouse, self).unlink(cr, uid, ids, context=context)
|
|
|
|
def view_all_routes_for_wh(self, cr, uid, ids, context):
|
|
wh_obj = self.pool.get("stock.warehouse")
|
|
|
|
wh = wh_obj.browse(cr,uid,ids[0])
|
|
|
|
all_routes = []
|
|
all_routes += [wh.crossdock_route_id.id]
|
|
all_routes += [wh.reception_route_id.id]
|
|
all_routes += [wh.delivery_route_id.id]
|
|
all_routes += [wh.mto_pull_id.route_id.id]
|
|
all_routes += [route.id for route in wh.resupply_route_ids]
|
|
all_routes += [route.id for route in wh.route_ids]
|
|
|
|
res_id = ids and ids[0] or False
|
|
domain = [('id', 'in', all_routes)]
|
|
return {
|
|
'name': _('The 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"
|
|
_description = "Pushed Flows"
|
|
_order = "name"
|
|
|
|
def _get_route(self, cr, uid, ids, context=None):
|
|
#WARNING TODO route_id is not required, so a field related seems a bad idea >-<
|
|
if context is None:
|
|
context = {}
|
|
result = {}
|
|
if context is None:
|
|
context = {}
|
|
context_with_inactive = context.copy()
|
|
context_with_inactive['active_test']=False
|
|
for route in self.pool.get('stock.location.route').browse(cr, uid, ids, context=context_with_inactive):
|
|
for push_rule in route.push_ids:
|
|
result[push_rule.id] = True
|
|
return result.keys()
|
|
|
|
_columns = {
|
|
'name': fields.char('Operation Name', size=64, required=True),
|
|
'company_id': fields.many2one('res.company', 'Company'),
|
|
'route_id': fields.many2one('stock.location.route', 'Route'),
|
|
'location_from_id': fields.many2one('stock.location', 'Source Location', ondelete='cascade', select=1, required=True),
|
|
'location_dest_id': fields.many2one('stock.location', 'Destination Location', ondelete='cascade', select=1, required=True),
|
|
'delay': fields.integer('Delay (days)', help="Number of days to do this transition"),
|
|
'invoice_state': fields.selection([
|
|
("invoiced", "Invoiced"),
|
|
("2binvoiced", "To Be Invoiced"),
|
|
("none", "Not Applicable")], "Invoice Status",
|
|
required=True,),
|
|
'picking_type_id': fields.many2one('stock.picking.type', 'Type of the new Operation', required=True, help="This is the picking type associated with the different pickings"),
|
|
'auto': fields.selection(
|
|
[('auto','Automatic Move'), ('manual','Manual Operation'),('transparent','Automatic No Step Added')],
|
|
'Automatic Move',
|
|
required=True, select=1,
|
|
help="This is used to define paths the product has to follow within the location tree.\n" \
|
|
"The 'Automatic Move' value will create a stock move after the current one that will be "\
|
|
"validated automatically. With 'Manual Operation', the stock move has to be validated "\
|
|
"by a worker. With 'Automatic No Step Added', the location is replaced in the original move."
|
|
),
|
|
'propagate': fields.boolean('Propagate cancel and split', help='If checked, when the previous move is cancelled or split, the move generated by this move will too'),
|
|
'active': fields.related('route_id', 'active', type='boolean', string='Active', store={
|
|
'stock.location.route': (_get_route, ['active'], 20),
|
|
'stock.location.path': (lambda self, cr, uid, ids, c={}: ids, ['route_id'], 20),},
|
|
help="If the active field is set to False, it will allow you to hide the rule without removing it." ),
|
|
}
|
|
_defaults = {
|
|
'auto': 'auto',
|
|
'delay': 1,
|
|
'invoice_state': 'none',
|
|
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'procurement.order', context=c),
|
|
'propagate': True,
|
|
'active': True,
|
|
}
|
|
def _apply(self, cr, uid, rule, move, context=None):
|
|
move_obj = self.pool.get('stock.move')
|
|
newdate = (datetime.strptime(move.date, '%Y-%m-%d %H:%M:%S') + relativedelta(days=rule.delay or 0)).strftime('%Y-%m-%d')
|
|
if rule.auto=='transparent':
|
|
move_obj.write(cr, uid, [move.id], {
|
|
'date': newdate,
|
|
'location_dest_id': rule.location_dest_id.id
|
|
})
|
|
if rule.location_dest_id.id<>move.location_dest_id.id:
|
|
move_obj._push_apply(self, cr, uid, move.id, context)
|
|
return move.id
|
|
else:
|
|
move_id = move_obj.copy(cr, uid, move.id, {
|
|
'location_id': move.location_dest_id.id,
|
|
'location_dest_id': rule.location_dest_id.id,
|
|
'date': datetime.now().strftime('%Y-%m-%d'),
|
|
'company_id': rule.company_id and rule.company_id.id or False,
|
|
'date_expected': newdate,
|
|
'picking_id': False,
|
|
'picking_type_id': rule.picking_type_id and rule.picking_type_id.id or False,
|
|
'rule_id': rule.id,
|
|
'propagate': rule.propagate,
|
|
})
|
|
move_obj.write(cr, uid, [move.id], {
|
|
'move_dest_id': move_id,
|
|
})
|
|
move_obj.action_confirm(cr, uid, [move_id], context=None)
|
|
return move_id
|
|
|
|
|
|
class procurement_rule(osv.osv):
|
|
_inherit = 'procurement.rule'
|
|
|
|
_columns = {
|
|
'delay': fields.integer('Number of Days'),
|
|
'partner_address_id': fields.many2one('res.partner', 'Partner Address'),
|
|
'propagate': fields.boolean('Propagate cancel and split', help='If checked, when the previous move of the move (which was generated by a next procurement) is cancelled or split, the move generated by this move will too'),
|
|
}
|
|
_defaults = {
|
|
'propagate': True,
|
|
'delay': 0,
|
|
}
|
|
|
|
|
|
class procurement_order(osv.osv):
|
|
_inherit = 'procurement.order'
|
|
_columns = {
|
|
'route_ids': fields.many2many('stock.location.route', 'stock_location_route_procurement', 'procurement_id', 'route_id', 'Followed Route', help="Preferred route to be followed by the procurement order"),
|
|
}
|
|
|
|
def _run_move_create(self, cr, uid, procurement, context=None):
|
|
d = super(procurement_order, self)._run_move_create(cr, uid, procurement, context=context)
|
|
d.update({
|
|
'route_ids': [(4,x.id) for x in procurement.route_ids],
|
|
})
|
|
if procurement.rule_id:
|
|
newdate = (datetime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S') - relativedelta(days=procurement.rule_id.delay or 0)).strftime('%Y-%m-%d %H:%M:%S')
|
|
d.update({
|
|
'date': newdate,
|
|
'propagate': procurement.rule_id.propagate,
|
|
})
|
|
return d
|
|
|
|
def _find_parent_locations(self, cr, uid, procurement, context=None):
|
|
location = procurement.location_id
|
|
res = [location.id]
|
|
while location.location_id:
|
|
location = location.location_id
|
|
res.append(location.id)
|
|
return res
|
|
|
|
def _find_suitable_rule(self, cr, uid, procurement, context=None):
|
|
rule_id = super(procurement_order, self)._find_suitable_rule(cr, uid, procurement, context=context)
|
|
if not rule_id:
|
|
#a rule defined on 'Stock' is suitable for a procurement in 'Stock\Bin A'
|
|
all_parent_location_ids = self._find_parent_locations(cr, uid, procurement, context=context)
|
|
rule_id = self._search_suitable_rule(cr, uid, procurement, [('location_id', 'in', all_parent_location_ids)], context=context)
|
|
rule_id = rule_id and rule_id[0] or False
|
|
return rule_id
|
|
|
|
def _search_suitable_rule(self, cr, uid, procurement, domain, context=None):
|
|
'''we try to first find a rule among the ones defined on the procurement order group and if none is found, we try on the routes defined for the product, and finally we fallback on the default behavior'''
|
|
product_route_ids = [x.id for x in procurement.product_id.route_ids + procurement.product_id.categ_id.total_route_ids]
|
|
procurement_route_ids = [x.id for x in procurement.route_ids]
|
|
res = self.pool.get('procurement.rule').search(cr, uid, domain + [('route_id', 'in', product_route_ids)], order = 'route_sequence, sequence', context=context)
|
|
if not res:
|
|
res = self.pool.get('procurement.rule').search(cr, uid, domain + [('route_id', 'in', procurement_route_ids)], order = 'route_sequence, sequence', context=context)
|
|
if not res:
|
|
res = self.pool.get('procurement.rule').search(cr, uid, domain + [('route_id', '=', False)], order='sequence', context=context)
|
|
return res
|
|
|
|
class product_putaway_strategy(osv.osv):
|
|
_name = 'product.putaway'
|
|
_description = 'Put Away Strategy'
|
|
_columns = {
|
|
'product_categ_id':fields.many2one('product.category', 'Product Category', required=True),
|
|
'location_id': fields.many2one('stock.location','Parent Location', help="Parent Destination Location from which a child bin location needs to be chosen", required=True), #domain=[('type', '=', 'parent')],
|
|
'method': fields.selection([('fixed', 'Fixed Location')], "Method", required = True),
|
|
'location_spec_id': fields.many2one('stock.location','Specific Location', help="When the location is specific, it will be put over there"), #domain=[('type', '=', 'parent')],
|
|
}
|
|
|
|
# TODO: move this on stock module
|
|
|
|
class product_removal_strategy(osv.osv):
|
|
_name = 'product.removal'
|
|
_description = 'Removal Strategy'
|
|
_order = 'sequence'
|
|
_columns = {
|
|
'product_categ_id': fields.many2one('product.category', 'Category', required=True),
|
|
'sequence': fields.integer('Sequence'),
|
|
'method': fields.selection([('fifo', 'FIFO'), ('lifo', 'LIFO')], "Method", required = True),
|
|
'location_id': fields.many2one('stock.location', 'Locations', required=True),
|
|
}
|
|
|
|
|
|
class product_product(osv.osv):
|
|
_inherit = 'product.product'
|
|
_columns = {
|
|
'route_ids': fields.many2many('stock.location.route', 'stock_route_product', 'product_id', 'route_id', 'Routes', domain="[('product_selectable', '=', True)]"),
|
|
}
|
|
|
|
def action_view_routes(self, cr, uid, ids, context=None):
|
|
route_obj = self.pool.get("stock.location.route")
|
|
act_obj = self.pool.get('ir.actions.act_window')
|
|
mod_obj = self.pool.get('ir.model.data')
|
|
product_route_ids = set()
|
|
for product in self.browse(cr, uid, ids, context=context):
|
|
product_route_ids |= set([r.id for r in product.route_ids])
|
|
product_route_ids |= set([r.id for r in product.categ_id.total_route_ids])
|
|
route_ids = route_obj.search(cr, uid, ['|', ('id', 'in', list(product_route_ids)), ('warehouse_selectable', '=', True)], context=context)
|
|
result = mod_obj.get_object_reference(cr, uid, 'stock_location', 'action_routes_form')
|
|
id = result and result[1] or False
|
|
result = act_obj.read(cr, uid, [id], context=context)[0]
|
|
result['domain'] = "[('id','in',[" + ','.join(map(str, route_ids)) + "])]"
|
|
return result
|
|
|
|
class product_category(osv.osv):
|
|
_inherit = 'product.category'
|
|
|
|
def calculate_total_routes(self, cr, uid, ids, name, args, context=None):
|
|
res = {}
|
|
for categ in self.browse(cr, uid, ids, context=context):
|
|
categ2 = categ
|
|
routes = set([x.id for x in categ.route_ids])
|
|
while categ2.parent_id:
|
|
categ2 = categ2.parent_id
|
|
routes |= set([x.id for x in categ2.route_ids])
|
|
res[categ.id] = list(routes)
|
|
return res
|
|
|
|
_columns = {
|
|
'route_ids': fields.many2many('stock.location.route', 'stock_location_route_categ', 'categ_id', 'route_id', 'Routes', domain="[('product_categ_selectable', '=', True)]"),
|
|
'removal_strategy_ids': fields.one2many('product.removal', 'product_categ_id', 'Removal Strategies'),
|
|
'putaway_strategy_ids': fields.one2many('product.putaway', 'product_categ_id', 'Put Away Strategies'),
|
|
'total_route_ids': fields.function(calculate_total_routes, relation='stock.location.route', type='many2many', string='Total routes', readonly=True),
|
|
}
|
|
|
|
|
|
class stock_move_putaway(osv.osv):
|
|
_name = 'stock.move.putaway'
|
|
_description = 'Proposed Destination'
|
|
_columns = {
|
|
'move_id': fields.many2one('stock.move', required=True),
|
|
'location_id': fields.many2one('stock.location', 'Location', required=True),
|
|
'lot_id': fields.many2one('stock.production.lot', 'Lot'),
|
|
'quantity': fields.float('Quantity', required=True),
|
|
}
|
|
|
|
|
|
class stock_quant(osv.osv):
|
|
_inherit = "stock.quant"
|
|
|
|
def check_preferred_location(self, cr, uid, move, context=None):
|
|
if move.putaway_ids and move.putaway_ids[0]:
|
|
#Take only first suggestion for the moment
|
|
return move.putaway_ids[0].location_id
|
|
return super(stock_quant, self).check_preferred_location(cr, uid, move, context=context)
|
|
|
|
|
|
class stock_move(osv.osv):
|
|
_inherit = 'stock.move'
|
|
_columns = {
|
|
'putaway_ids': fields.one2many('stock.move.putaway', 'move_id', 'Put Away Suggestions'),
|
|
'route_ids': fields.many2many('stock.location.route', 'stock_location_route_move', 'move_id', 'route_id', 'Destination route', help="Preferred route to be followed by the procurement order"),
|
|
}
|
|
|
|
def _push_apply(self, cr, uid, moves, context):
|
|
categ_obj = self.pool.get("product.category")
|
|
push_obj = self.pool.get("stock.location.path")
|
|
|
|
for move in moves:
|
|
if not move.move_dest_id:
|
|
categ_id = move.product_id.categ_id.id
|
|
routes = [x.id for x in move.product_id.route_ids + move.product_id.categ_id.total_route_ids]
|
|
if not routes:
|
|
routes = [x.id for x in move.route_ids]
|
|
|
|
rules = push_obj.search(cr, uid, [('route_id', 'in', routes), ('location_from_id', '=', move.location_dest_id.id)], context=context)
|
|
if rules:
|
|
rule = push_obj.browse(cr, uid, rules[0], context=context)
|
|
push_obj._apply(cr, uid, rule, move, context=context)
|
|
|
|
return True
|
|
|
|
# Create the stock.move.putaway records
|
|
def _putaway_apply(self,cr, uid, ids, context=None):
|
|
moveputaway_obj = self.pool.get('stock.move.putaway')
|
|
for move in self.browse(cr, uid, ids, context=context):
|
|
putaway = self.pool.get('stock.location').get_putaway_strategy(cr, uid, move.location_dest_id, move.product_id, context=context)
|
|
if putaway:
|
|
# Should call different methods here in later versions
|
|
# TODO: take care of lots
|
|
if putaway.method == 'fixed' and putaway.location_spec_id:
|
|
moveputaway_obj.create(cr, uid, {'move_id': move.id,
|
|
'location_id': putaway.location_spec_id.id,
|
|
'quantity': move.product_uom_qty}, context=context)
|
|
return True
|
|
|
|
def action_assign(self, cr, uid, ids, context=None):
|
|
result = super(stock_move, self).action_assign(cr, uid, ids, context=context)
|
|
self._putaway_apply(cr, uid, ids, context=context)
|
|
return result
|
|
|
|
def action_confirm(self, cr, uid, ids, context=None):
|
|
result = super(stock_move, self).action_confirm(cr, uid, ids, context)
|
|
moves = self.browse(cr, uid, ids, context=context)
|
|
self._push_apply(cr, uid, moves, context=context)
|
|
return result
|
|
|
|
def _prepare_procurement_from_move(self, cr, uid, move, context=None):
|
|
"""
|
|
Next to creating the procurement order, it will propagate the routes
|
|
"""
|
|
vals = super(stock_move, self)._prepare_procurement_from_move(cr, uid, move, context=context)
|
|
vals['route_ids'] = [(4, x.id) for x in move.route_ids]
|
|
return vals
|
|
|
|
|
|
class stock_location(osv.osv):
|
|
_inherit = 'stock.location'
|
|
_columns = {
|
|
'removal_strategy_ids': fields.one2many('product.removal', 'location_id', 'Removal Strategies'),
|
|
'putaway_strategy_ids': fields.one2many('product.putaway', 'location_id', 'Put Away Strategies'),
|
|
}
|
|
|
|
def get_putaway_strategy(self, cr, uid, location, product, context=None):
|
|
pa = self.pool.get('product.putaway')
|
|
categ = product.categ_id
|
|
categs = [categ.id, False]
|
|
while categ.parent_id:
|
|
categ = categ.parent_id
|
|
categs.append(categ.id)
|
|
|
|
result = pa.search(cr,uid, [
|
|
('location_id', '=', location.id),
|
|
('product_categ_id', 'in', categs)
|
|
], context=context)
|
|
if result:
|
|
return pa.browse(cr, uid, result[0], context=context)
|
|
#return super(stock_location, self).get_putaway_strategy(cr, uid, location, product, context=context)
|
|
|
|
def get_removal_strategy(self, cr, uid, location, product, context=None):
|
|
pr = self.pool.get('product.removal')
|
|
categ = product.categ_id
|
|
categs = [categ.id, False]
|
|
while categ.parent_id:
|
|
categ = categ.parent_id
|
|
categs.append(categ.id)
|
|
|
|
result = pr.search(cr,uid, [
|
|
('location_id', '=', location.id),
|
|
('product_categ_id', 'in', categs)
|
|
], context=context)
|
|
if result:
|
|
return pr.browse(cr, uid, result[0], context=context).method
|
|
return super(stock_location, self).get_removal_strategy(cr, uid, location, product, context=context)
|
|
|
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|