Add default internal provisioning per location
issue4913 review18491002
This commit is contained in:
1 parent
81d2e483f0
commit
3fd8afa2e1
9 files changed
+138
-16
No files matched your search
@@ -1,3 +1,5 @@
|
||||
* Add default internal provisioning per location
|
||||
|
||||
Version 3.6.0 - 2015-04-20
|
||||
* Bug fixes (see mercurial logs for details)
|
||||
* Add support for PyPy
|
||||
|
||||
@@ -6,6 +6,7 @@ from .order_point import *
|
||||
from .product import *
|
||||
from .purchase_request import *
|
||||
from .shipment import *
|
||||
from .location import *
|
||||
|
||||
|
||||
def register():
|
||||
@@ -17,6 +18,7 @@ def register():
|
||||
CreatePurchaseAskParty,
|
||||
ShipmentInternal,
|
||||
CreateShipmentInternalStart,
|
||||
Location,
|
||||
module='stock_supply', type_='model')
|
||||
Pool.register(
|
||||
CreatePurchaseRequest,
|
||||
|
||||
+3
-1
@@ -66,4 +66,6 @@ The internal shipments and purchase requests are created by schedulers
|
||||
with respect to stock levels and existing shipments and requests. The
|
||||
stock levels are computed between the next two supply dates. If the
|
||||
stock level of a product without order point on the given warehouse is
|
||||
below zero, a purchase request is also created.
|
||||
below zero, a purchase request is also created. The same happens if
|
||||
the stock level of a storage location with a provisioning location is
|
||||
below zero.
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
from trytond.pool import PoolMeta
|
||||
from trytond.model import fields
|
||||
from trytond.pyson import Eval
|
||||
|
||||
|
||||
__all__ = ['Location']
|
||||
|
||||
|
||||
class Location:
|
||||
__metaclass__ = PoolMeta
|
||||
__name__ = 'stock.location'
|
||||
|
||||
provisioning_location = fields.Many2One('stock.location',
|
||||
'Provisioning Location',
|
||||
states={
|
||||
'invisible': Eval('type') != 'storage',
|
||||
'readonly': ~Eval('active'),
|
||||
},
|
||||
domain=[
|
||||
('type', '=', 'storage'),
|
||||
],
|
||||
depends=['type', 'active'],
|
||||
help='Leave empty for no default provisioning')
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<tryton>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="location_view_form">
|
||||
<field name="model">stock.location</field>
|
||||
<field name="inherit" ref="stock.location_view_form"/>
|
||||
<field name="name">location_form</field>
|
||||
</record>
|
||||
</data>
|
||||
</tryton>
|
||||
+42
-13
@@ -46,6 +46,7 @@ class ShipmentInternal(ModelSQL, ModelView):
|
||||
"""
|
||||
pool = Pool()
|
||||
OrderPoint = pool.get('stock.order_point')
|
||||
Location = pool.get('stock.location')
|
||||
Product = pool.get('product.product')
|
||||
Date = pool.get('ir.date')
|
||||
User = pool.get('res.user')
|
||||
@@ -57,25 +58,50 @@ class ShipmentInternal(ModelSQL, ModelView):
|
||||
('type', '=', 'internal'),
|
||||
])
|
||||
id2product = {}
|
||||
location_ids = []
|
||||
product2op = {}
|
||||
id2location = {}
|
||||
for op in order_points:
|
||||
id2product[op.product.id] = op.product
|
||||
location_ids.append(op.storage_location.id)
|
||||
product2op[
|
||||
(op.storage_location.id, op.product.id)
|
||||
] = op
|
||||
id2location[op.storage_location.id] = op.storage_location
|
||||
provisioned = Location.search([
|
||||
('provisioning_location', '!=', None),
|
||||
])
|
||||
id2location.update({l.id: l for l in provisioned})
|
||||
|
||||
# ordered by ids to speedup reduce_ids in products_by_location
|
||||
if provisioned:
|
||||
products = Product.search([
|
||||
('type', 'in', ['goods', 'assets']),
|
||||
], order=[('id', 'ASC')])
|
||||
product_ids = [p.id for p in products]
|
||||
else:
|
||||
product_ids = id2product.keys()
|
||||
product_ids.sort()
|
||||
# TODO Allow to compute for other future date
|
||||
with Transaction().set_context(forecast=True, stock_date_end=today):
|
||||
pbl = Product.products_by_location(location_ids,
|
||||
list(id2product.iterkeys()), with_childs=True)
|
||||
pbl = Product.products_by_location(id2location.keys(),
|
||||
product_ids, with_childs=True)
|
||||
|
||||
# Create a list of move to create
|
||||
moves = {}
|
||||
for op in order_points:
|
||||
qty = pbl.get((op.storage_location.id, op.product.id), 0)
|
||||
if qty < op.min_quantity:
|
||||
key = (op.provisioning_location.id,
|
||||
op.storage_location.id,
|
||||
op.product.id)
|
||||
moves[key] = op.max_quantity - qty
|
||||
for location in id2location.itervalues():
|
||||
for product_id in product_ids:
|
||||
qty = pbl.get((location.id, product_id), 0)
|
||||
op = product2op.get((location.id, product_id))
|
||||
if op:
|
||||
min_qty, max_qty = op.min_quantity, op.max_quantity
|
||||
provisioning_location = op.provisioning_location
|
||||
elif location and location.provisioning_location:
|
||||
min_qty, max_qty = 0, 0
|
||||
provisioning_location = location.provisioning_location
|
||||
else:
|
||||
continue
|
||||
if qty < min_qty:
|
||||
key = (provisioning_location.id, location.id, product_id)
|
||||
moves[key] = max_qty - qty
|
||||
|
||||
# Group moves by {from,to}_location
|
||||
to_create = {}
|
||||
@@ -94,13 +120,16 @@ class ShipmentInternal(ModelSQL, ModelView):
|
||||
)
|
||||
shipment_moves = []
|
||||
for move in moves:
|
||||
product, qty = move
|
||||
product_id, qty = move
|
||||
product = id2product.setdefault(
|
||||
product_id, Product(product_id))
|
||||
shipment_moves.append(Move(
|
||||
from_location=from_location,
|
||||
to_location=to_location,
|
||||
planned_date=today,
|
||||
product=product,
|
||||
quantity=qty,
|
||||
uom=id2product[product].default_uom,
|
||||
uom=product.default_uom,
|
||||
company=user_record.company,
|
||||
))
|
||||
shipment.moves = shipment_moves
|
||||
|
||||
@@ -94,8 +94,9 @@ Get stock locations::
|
||||
>>> customer_loc, = Location.find([('code', '=', 'CUS')])
|
||||
>>> output_loc, = Location.find([('code', '=', 'OUT')])
|
||||
>>> storage_loc, = Location.find([('code', '=', 'STO')])
|
||||
>>> lost_loc, = Location.find([('type', '=', 'lost_found')])
|
||||
|
||||
Create new internal location::
|
||||
Create provisioning location::
|
||||
|
||||
>>> Location = Model.get('stock.location')
|
||||
>>> provisioning_loc = Location()
|
||||
@@ -104,12 +105,20 @@ Create new internal location::
|
||||
>>> provisioning_loc.parent = warehouse_loc
|
||||
>>> provisioning_loc.save()
|
||||
|
||||
Create a new storage location::
|
||||
|
||||
>>> sec_storage_loc = Location()
|
||||
>>> sec_storage_loc.name = 'Second Storage'
|
||||
>>> sec_storage_loc.type = 'storage'
|
||||
>>> sec_storage_loc.parent = warehouse_loc
|
||||
>>> sec_storage_loc.provisioning_location = provisioning_loc
|
||||
>>> sec_storage_loc.save()
|
||||
|
||||
Create internal order point::
|
||||
|
||||
>>> OrderPoint = Model.get('stock.order_point')
|
||||
>>> order_point = OrderPoint()
|
||||
>>> order_point.product = product
|
||||
>>> order_point.warehouse_location = warehouse_loc
|
||||
>>> order_point.storage_location = storage_loc
|
||||
>>> order_point.provisioning_location = provisioning_loc
|
||||
>>> order_point.type = 'internal'
|
||||
@@ -148,3 +157,33 @@ Execute internal supply::
|
||||
u'Provisioning Location'
|
||||
>>> move.to_location.code
|
||||
u'STO'
|
||||
|
||||
Create negative quantity in Second Storage::
|
||||
|
||||
>>> Move = Model.get('stock.move')
|
||||
>>> move = Move()
|
||||
>>> move.product = product
|
||||
>>> move.quantity = 10
|
||||
>>> move.from_location = sec_storage_loc
|
||||
>>> move.to_location = lost_loc
|
||||
>>> move.click('do')
|
||||
>>> move.state
|
||||
u'done'
|
||||
|
||||
Execute internal supply::
|
||||
|
||||
>>> Wizard('stock.shipment.internal.create').execute('create_')
|
||||
>>> shipment, = ShipmentInternal.find([('id', '!=', shipment.id)])
|
||||
>>> shipment.state
|
||||
u'waiting'
|
||||
>>> len(shipment.moves)
|
||||
1
|
||||
>>> move, = shipment.moves
|
||||
>>> move.product.template.name
|
||||
u'Product'
|
||||
>>> move.quantity
|
||||
10.0
|
||||
>>> move.from_location.name
|
||||
u'Provisioning Location'
|
||||
>>> move.to_location.name
|
||||
u'Second Storage'
|
||||
@@ -13,3 +13,4 @@ xml:
|
||||
purchase_request.xml
|
||||
shipment.xml
|
||||
product.xml
|
||||
location.xml
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<data>
|
||||
<xpath expr="/form/field[@name='address']" position="after">
|
||||
<newline/>
|
||||
<label name="provisioning_location"/>
|
||||
<field name="provisioning_location"/>
|
||||
</xpath>
|
||||
</data>
|
||||
Reference in new issue
Block a user