trytond-patches/issue5587-stock.diff

817 lines
32 KiB
Diff

exporting patch:
# HG changeset patch
# User Cédric Krier <ced@b2ck.com>
# Date 1466498190 -7200
# Tue Jun 21 10:36:30 2016 +0200
# Node ID 48fdd650ed0c3a42495711a1a16eaba2aef30bae
# Parent 6de0e55a67d0dc6211d7a7d74d561a5cc8686640
Add lead time and transit location for internal shipments between warehouses
issue5587
review25331002
diff -r 6de0e55a67d0 -r 48fdd650ed0c __init__.py
--- a/trytond/trytond/modules/stock/__init__.py Wed Jun 15 10:45:54 2016 +0200
+++ b/trytond/trytond/modules/stock/__init__.py Tue Jun 21 10:36:30 2016 +0200
@@ -16,6 +16,7 @@
Location,
Party,
ProductsByLocationsStart,
+ LocationLeadTime,
Move,
ShipmentIn,
ShipmentInReturn,
diff -r 6de0e55a67d0 -r 48fdd650ed0c configuration.py
--- a/trytond/trytond/modules/stock/configuration.py Wed Jun 15 10:45:54 2016 +0200
+++ b/trytond/trytond/modules/stock/configuration.py Tue Jun 21 10:36:30 2016 +0200
@@ -39,6 +39,10 @@
[Eval('context', {}).get('company', -1), None]),
('code', '=', 'stock.shipment.internal'),
], required=True))
+ shipment_internal_transit = fields.Property(fields.Many2One(
+ 'stock.location', 'Internal Shipment Transit', domain=[
+ ('type', '=', 'storage'),
+ ], required=True))
inventory_sequence = fields.Property(fields.Many2One(
'ir.sequence', 'Inventory Sequence', domain=[
('company', 'in',
diff -r 6de0e55a67d0 -r 48fdd650ed0c doc/index.rst
--- a/trytond/trytond/modules/stock/doc/index.rst Wed Jun 15 10:45:54 2016 +0200
+++ b/trytond/trytond/modules/stock/doc/index.rst Tue Jun 21 10:36:30 2016 +0200
@@ -50,6 +50,12 @@
Locations are organised in tree structures, allowing to define
fine grained structures.
+Location Lead Time
+------------------
+
+It allows to define the time needed for an *Internal Shipment* between two
+warehouses.
+
Move
****
diff -r 4c390625785a location.py
--- a/trytond/trytond/modules/stock/location.py Tue Jun 14 16:17:48 2016 +0200
+++ b/trytond/trytond/modules/stock/location.py Thu Aug 04 01:14:15 2016 +0200
@@ -2,7 +2,11 @@
# this repository contains the full copyright notices and license terms.
import datetime
from decimal import Decimal
-from trytond.model import ModelView, ModelSQL, fields
+
+from sql import Null
+from sql.conditionals import Case
+
+from trytond.model import ModelView, ModelSQL, MatchMixin, fields
from trytond.wizard import Wizard, StateView, Button, StateAction
from trytond import backend
from trytond.pyson import Eval, PYSONEncoder, Date, If
@@ -11,7 +15,7 @@
from trytond.tools import grouped_slice
__all__ = ['Location', 'Party', 'ProductsByLocationsStart',
- 'ProductsByLocations']
+ 'ProductsByLocations', 'LocationLeadTime']
STATES = {
'readonly': ~Eval('active'),
@@ -456,3 +460,38 @@
action['name'] += ' - (%s) @ %s' % (
','.join(l.name for l in locations), date)
return action, {}
+
+
+
+class LocationLeadTime(ModelSQL, ModelView, MatchMixin):
+ 'Location Lead Time'
+ __name__ = 'stock.location.lead_time'
+
+ sequence = fields.Integer('Sequence')
+ warehouse_from = fields.Many2One('stock.location', 'Warehouse From',
+ ondelete='CASCADE',
+ domain=[
+ ('type', '=', 'warehouse'),
+ ])
+ warehouse_to = fields.Many2One('stock.location', 'Warehouse To',
+ ondelete='CASCADE',
+ domain=[
+ ('type', '=', 'warehouse'),
+ ])
+ lead_time = fields.TimeDelta('Lead Time')
+
+ @classmethod
+ def __setup__(cls):
+ super(LocationLeadTime, cls).__setup__()
+ cls._order.insert(0, ('sequence', 'ASC'))
+
+ @classmethod
+ def order_sequence(cls, tables):
+ table, _ = tables[None]
+ return [Case((table.sequence == Null, 0), else_=1), table.sequence]
+
+ @classmethod
+ def get_lead_time(cls, pattern):
+ for record in cls.search([]):
+ if record.match(pattern):
+ return record.lead_time
diff -r 6de0e55a67d0 -r 48fdd650ed0c location.xml
--- a/trytond/trytond/modules/stock/location.xml Wed Jun 15 10:45:54 2016 +0200
+++ b/trytond/trytond/modules/stock/location.xml Tue Jun 21 10:36:30 2016 +0200
@@ -121,6 +121,56 @@
</record>
+ <record model="ir.ui.view" id="location_lead_time_view_list">
+ <field name="model">stock.location.lead_time</field>
+ <field name="type">tree</field>
+ <field name="name">location_lead_time_list</field>
+ </record>
+
+ <record model="ir.ui.view" id="location_lead_time_view_form">
+ <field name="model">stock.location.lead_time</field>
+ <field name="type">form</field>
+ <field name="name">location_lead_time_form</field>
+ </record>
+
+ <record model="ir.action.act_window" id="act_location_lead_time_form">
+ <field name="name">Location Lead Times</field>
+ <field name="res_model">stock.location.lead_time</field>
+ </record>
+ <record model="ir.action.act_window.view"
+ id="act_location_lead_time_form_view1">
+ <field name="sequence" eval="10"/>
+ <field name="view" ref="location_lead_time_view_list"/>
+ <field name="act_window" ref="act_location_lead_time_form"/>
+ </record>
+ <record model="ir.action.act_window.view"
+ id="act_location_lead_time_form_view2">
+ <field name="sequence" eval="20"/>
+ <field name="view" ref="location_lead_time_view_form"/>
+ <field name="act_window" ref="act_location_lead_time_form"/>
+ </record>
+
+ <menuitem parent="menu_location_form"
+ action="act_location_lead_time_form"
+ id="menu_location_lead_time_form"/>
+
+ <record model="ir.model.access" id="access_location_lead_time">
+ <field name="model"
+ search="[('model', '=', 'stock.location.lead_time')]"/>
+ <field name="perm_read" eval="True"/>
+ <field name="perm_write" eval="False"/>
+ <field name="perm_create" eval="False"/>
+ <field name="perm_delete" eval="False"/>
+ </record>
+ <record model="ir.model.access" id="access_location_lead_time_admin">
+ <field name="model"
+ search="[('model', '=', 'stock.location.lead_time')]"/>
+ <field name="group" ref="group_stock_admin"/>
+ <field name="perm_read" eval="True"/>
+ <field name="perm_write" eval="True"/>
+ <field name="perm_create" eval="True"/>
+ <field name="perm_delete" eval="True"/>
+ </record>
</data>
<data noupdate="1">
<!-- Default locations -->
@@ -161,6 +211,10 @@
<field name="name">Lost and Found</field>
<field name="type">lost_found</field>
</record>
+ <record model="stock.location" id="location_transit">
+ <field name="name">Transit</field>
+ <field name="type">storage</field>
+ </record>
<record model="ir.property" id="property_supplier_location">
<field name="field"
@@ -174,5 +228,12 @@
('name', '=', 'customer_location')]"/>
<field name="value" eval="'stock.location,' + str(ref('location_customer'))"/>
</record>
+
+ <record model="ir.property" id="property_shipment_internal_transit">
+ <field name="field"
+ search="[('model.model', '=', 'stock.configuration'),
+ ('name', '=', 'shipment_internal_transit')]"/>
+ <field name="value" eval="'stock.location,' + str(ref('location_transit'))"/>
+ </record>
</data>
</tryton>
diff -r 6de0e55a67d0 -r 48fdd650ed0c shipment.py
--- a/trytond/trytond/modules/stock/shipment.py Wed Jun 15 10:45:54 2016 +0200
+++ b/trytond/trytond/modules/stock/shipment.py Tue Jun 21 10:36:30 2016 +0200
@@ -4,7 +4,9 @@
import itertools
import functools
import datetime
-from sql import Table
+from collections import defaultdict
+
+from sql import Table, Null
from sql.functions import Overlay, Position
from sql.aggregate import Max
from sql.operators import Concat
@@ -13,7 +15,7 @@
from trytond.modules.company import CompanyReport
from trytond.wizard import Wizard, StateTransition, StateView, Button
from trytond import backend
-from trytond.pyson import Eval, If, Id
+from trytond.pyson import Eval, If, Id, Bool
from trytond.transaction import Transaction
from trytond.pool import Pool, PoolMeta
from trytond.tools import reduce_ids, grouped_slice
@@ -1819,6 +1821,17 @@
states={
'readonly': Eval('state') != 'draft',
}, depends=['state'])
+ effective_start_date = fields.Date('Effective Start Date',
+ states={
+ 'readonly': Eval('state').in_(['cancel', 'shipped', 'done']),
+ },
+ depends=['state'])
+ planned_start_date = fields.Date('Planned Start Date',
+ states={
+ 'readonly': ~Eval('state').in_(['draft']),
+ 'required': Bool(Eval('planned_date')),
+ },
+ depends=['state'])
company = fields.Many2One('company.company', 'Company', required=True,
states={
'readonly': Eval('state') != 'draft',
@@ -1846,32 +1859,88 @@
}, domain=[
('type', 'in', ['view', 'storage', 'lost_found']),
], depends=['state'])
+ transit_location = fields.Function(fields.Many2One('stock.location',
+ 'Transit Location'), 'on_change_with_transit_location')
moves = fields.One2Many('stock.move', 'shipment', 'Moves',
states={
'readonly': (Eval('state').in_(['cancel', 'assigned', 'done'])
| ~Eval('from_location') | ~Eval('to_location')),
+ 'invisible': (Bool(Eval('transit_location'))
+ & (Eval('state') != 'draft')),
},
domain=[
If(Eval('state') == 'draft', [
('from_location', '=', Eval('from_location')),
('to_location', '=', Eval('to_location')),
- ], [
+ ],
+ If(~Eval('transit_location'),
+ [
+ ('from_location', 'child_of',
+ [Eval('from_location', -1)], 'parent'),
+ ('to_location', 'child_of',
+ [Eval('to_location', -1)], 'parent'),
+ ],
+ ['OR',
+ [
+ ('from_location', 'child_of',
+ [Eval('from_location', -1)], 'parent'),
+ ('to_location', '=', Eval('transit_location')),
+ ],
+ [
+ ('from_location', '=', Eval('transit_location')),
+ ('to_location', 'child_of',
+ [Eval('to_location', -1)], 'parent'),
+ ],
+ ])),
+ ('company', '=', Eval('company')),
+ ],
+ depends=['state', 'from_location', 'to_location', 'transit_location',
+ 'company'])
+ outgoing_moves = fields.Function(fields.One2Many('stock.move', 'shipment',
+ 'Outgoing Moves',
+ domain=[
+ ('from_location', 'child_of', [Eval('from_location', -1)],
+ 'parent'),
+ If(~Eval('transit_location'),
+ ('to_location', 'child_of', [Eval('to_location', -1)],
+ 'parent'),
+ ('to_location', '=', Eval('transit_location'))),
+ ],
+ states={
+ 'readonly': Eval('state').in_(
+ ['assigned', 'shipped', 'done', 'cancel']),
+ 'invisible': (~Eval('transit_location')
+ | (Eval('state') == 'draft')),
+ },
+ depends=['from_location', 'to_location', 'transit_location',
+ 'state']),
+ 'get_outgoing_moves', setter='set_moves')
+ incoming_moves = fields.Function(fields.One2Many('stock.move', 'shipment',
+ 'Incoming Moves',
+ domain=[
+ If(~Eval('transit_location'),
('from_location', 'child_of', [Eval('from_location', -1)],
'parent'),
- ('to_location', 'child_of', [Eval('to_location', -1)],
- 'parent'),
- ]),
- ('company', '=', Eval('company')),
- ],
- depends=['state', 'from_location', 'to_location', 'planned_date',
- 'company'])
+ ('from_location', '=', Eval('transit_location'))),
+ ('to_location', 'child_of', [Eval('to_location', -1)],
+ 'parent'),
+ ],
+ states={
+ 'readonly': Eval('state').in_(['done', 'cancel']),
+ 'invisible': (~Eval('transit_location')
+ | (Eval('state') == 'draft')),
+ },
+ depends=['from_location', 'to_location', 'transit_location',
+ 'state']),
+ 'get_incoming_moves', setter='set_moves')
state = fields.Selection([
- ('draft', 'Draft'),
- ('cancel', 'Canceled'),
- ('assigned', 'Assigned'),
- ('waiting', 'Waiting'),
- ('done', 'Done'),
- ], 'State', readonly=True)
+ ('draft', 'Draft'),
+ ('cancel', 'Canceled'),
+ ('waiting', 'Waiting'),
+ ('assigned', 'Assigned'),
+ ('shipped', 'Shipped'),
+ ('done', 'Done'),
+ ], 'State', readonly=True)
@classmethod
def __setup__(cls):
@@ -1885,7 +1954,9 @@
('draft', 'waiting'),
('waiting', 'waiting'),
('waiting', 'assigned'),
+ ('assigned', 'shipped'),
('assigned', 'done'),
+ ('shipped', 'done'),
('waiting', 'draft'),
('assigned', 'waiting'),
('draft', 'cancel'),
@@ -1895,7 +1966,8 @@
))
cls._buttons.update({
'cancel': {
- 'invisible': Eval('state').in_(['cancel', 'done']),
+ 'invisible': Eval('state').in_(
+ ['cancel', 'shipped', 'done']),
},
'draft': {
'invisible': ~Eval('state').in_(['cancel', 'waiting']),
@@ -1912,8 +1984,15 @@
'tryton-clear',
'tryton-go-next')),
},
+ 'ship': {
+ 'invisible': ((Eval('state') != 'assigned') |
+ ~Eval('transit_location')),
+ },
'done': {
- 'invisible': Eval('state') != 'assigned',
+ 'invisible': If(
+ ~Eval('transit_location'),
+ Eval('state') != 'assigned',
+ Eval('state') != 'shipped'),
},
'assign_wizard': {
'invisible': Eval('state') != 'waiting',
@@ -1971,6 +2050,14 @@
where=red_sql))
table.not_null_action('company', action='add')
+ # Migration from 4.0: fill planned_start_date
+ cursor = Transaction().connection.cursor()
+ cursor.execute(*sql_table.update(
+ [sql_table.planned_start_date],
+ [sql_table.planned_date],
+ where=(sql_table.planned_start_date == Null)
+ & (sql_table.planned_date != Null)))
+
# Add index on create_date
table = TableHandler(cls, module_name)
table.index_action('create_date', action='add')
@@ -1983,6 +2070,59 @@
def default_company():
return Transaction().context.get('company')
+ @fields.depends('planned_date', 'planned_start_date')
+ def on_change_with_transit_location(self, name=None):
+ pool = Pool()
+ Config = pool.get('stock.configuration')
+ if self.planned_date != self.planned_start_date:
+ return Config(1).shipment_internal_transit.id
+
+ @fields.depends('planned_date', 'from_location', 'to_location')
+ def on_change_with_planned_start_date(self, pattern=None):
+ pool = Pool()
+ LocationLeadTime = pool.get('stock.location.lead_time')
+ if self.planned_date:
+ if pattern is None:
+ pattern = {}
+ pattern.setdefault('warehouse_from',
+ self.from_location.warehouse.id
+ if self.from_location and self.from_location.warehouse
+ else None)
+ pattern.setdefault('warehouse_to',
+ self.to_location.warehouse.id
+ if self.to_location and self.to_location.warehouse
+ else None)
+ lead_time = LocationLeadTime.get_lead_time(pattern)
+ if lead_time:
+ return self.planned_date - lead_time
+ return self.planned_date
+
+ def get_outgoing_moves(self, name):
+ if not self.transit_location:
+ return [m.id for m in self.moves]
+ moves = []
+ for move in self.moves:
+ if move.to_location == self.transit_location:
+ moves.append(move.id)
+ return moves
+
+ def get_incoming_moves(self, name):
+ if not self.transit_location:
+ return [m.id for m in self.moves]
+ moves = []
+ for move in self.moves:
+ if move.from_location == self.transit_location:
+ moves.append(move.id)
+ return moves
+
+ @classmethod
+ def set_moves(cls, shipments, name, value):
+ if not value:
+ return
+ cls.write(shipments, {
+ 'moves': value,
+ })
+
@classmethod
def create(cls, vlist):
pool = Pool()
@@ -2009,13 +2149,76 @@
super(ShipmentInternal, cls).delete(shipments)
@classmethod
+ def copy(cls, shipments, default=None):
+ pool = Pool()
+ Move = pool.get('stock.move')
+
+ if default is None:
+ default = {}
+ else:
+ default = default.copy()
+ default['outgoing_moves'] = None
+ default['incoming_moves'] = None
+ default['moves'] = None
+ default.setdefault('number')
+ copies = super(ShipmentInternal, cls).copy(shipments, default=default)
+ for shipment, copy in zip(shipments, copies):
+ Move.copy(shipment.outgoing_moves, default={
+ 'shipment': str(copy),
+ 'from_location': shipment.from_location.id,
+ 'to_location': shipment.to_location.id,
+ 'planned_date': shipment.planned_date,
+ })
+ return copies
+
+ @classmethod
+ def _sync_moves(cls, shipments):
+ 'Synchronise incoming moves with outgoing moves'
+ pool = Pool()
+ Move = pool.get('stock.move')
+ Uom = pool.get('product.uom')
+ to_delete = []
+ to_save = []
+ for shipment in shipments:
+ product_qty = defaultdict(lambda: 0)
+ for move in shipment.outgoing_moves:
+ if move.state == 'cancel':
+ continue
+ product_qty[move.product] += Uom.compute_qty(
+ move.uom, move.quantity, move.product.default_uom,
+ round=False)
+
+ for move in shipment.incoming_moves:
+ if move.state == 'cancel':
+ continue
+ if product_qty[move.product] <= 0:
+ to_delete.append(move)
+ else:
+ quantity = Uom.compute_qty(
+ move.uom, move.quantity, move.product.default_uom,
+ round=False)
+ quantity = min(product_qty[move.product], quantity)
+ move.quantity = Uom.compute_qty(
+ move.product.default_uom, quantity, move.uom)
+ product_qty[move.product] -= quantity
+ to_save.append(move)
+
+ if to_save:
+ Move.save(to_save)
+ if to_delete:
+ Move.delete(to_delete)
+
+ @classmethod
@ModelView.button
@Workflow.transition('draft')
def draft(cls, shipments):
Move = Pool().get('stock.move')
+
# First reset state to draft to allow update from and to location
Move.draft([m for s in shipments for m in s.moves
if m.state != 'staging'])
+ Move.delete([m for s in shipments for m in s.moves
+ if m.from_location == s.transit_location])
for shipment in shipments:
Move.write([m for m in shipment.moves
if m.state != 'done'], {
@@ -2029,28 +2232,69 @@
@Workflow.transition('waiting')
def wait(cls, shipments):
Move = Pool().get('stock.move')
+
Move.draft([m for s in shipments for m in s.moves])
+
+ direct = []
+ transit = []
+ for shipment in shipments:
+ if not shipment.transit_location:
+ direct.append(shipment)
+ else:
+ transit.append(shipment)
+
moves = []
- for shipment in shipments:
+ for shipment in direct:
for move in shipment.moves:
if move.state != 'done':
move.planned_date = shipment.planned_date
moves.append(move)
Move.save(moves)
+ to_write = []
+ for shipment in transit:
+ moves = [m for m in shipment.moves
+ if m.state != 'done'
+ and m.from_location != shipment.transit_location
+ and m.to_location != shipment.transit_location]
+ Move.copy(moves, default={
+ 'from_location': shipment.transit_location.id,
+ 'planned_date': shipment.planned_date,
+ })
+ to_write.append(moves)
+ to_write.append({
+ 'to_location': shipment.transit_location.id,
+ 'planned_date': shipment.planned_start_date,
+ })
+ if to_write:
+ Move.write(*to_write)
+ cls._sync_moves(transit)
+
@classmethod
@Workflow.transition('assigned')
def assign(cls, shipments):
pass
@classmethod
+ @Workflow.transition('shipped')
+ def ship(cls, shipments):
+ pool = Pool()
+ Move = pool.get('stock.move')
+ Date = pool.get('ir.date')
+ Move.do([m for s in shipments for m in s.outgoing_moves])
+ cls._sync_moves(shipments)
+ cls.write([s for s in shipments if not s.effective_start_date], {
+ 'effective_start_date': Date.today(),
+ })
+
+ @classmethod
@ModelView.button
@Workflow.transition('done')
def done(cls, shipments):
pool = Pool()
Move = pool.get('stock.move')
Date = pool.get('ir.date')
- Move.do([m for s in shipments for m in s.moves])
+ Move.do([m for s in shipments for m in s.incoming_moves])
cls.write([s for s in shipments if not s.effective_date], {
'effective_date': Date.today(),
})
@@ -2071,7 +2315,7 @@
@ModelView.button
def assign_try(cls, shipments):
Move = Pool().get('stock.move')
- to_assign = [m for s in shipments for m in s.moves
+ to_assign = [m for s in shipments for m in s.outgoing_moves
if m.from_location.type != 'lost_found']
if not to_assign or Move.assign_try(to_assign):
cls.assign(shipments)
@@ -2083,7 +2327,7 @@
@ModelView.button
def assign_force(cls, shipments):
Move = Pool().get('stock.move')
- Move.assign([m for s in shipments for m in s.moves])
+ Move.assign([m for s in shipments for m in s.outgoing_moves])
cls.assign(shipments)
@@ -2106,7 +2350,7 @@
if not shipment_id:
return []
shipment = ShipmentInternal(shipment_id)
- return [x.id for x in shipment.moves if x.state == 'draft']
+ return [x.id for x in shipment.outgoing_moves if x.state == 'draft']
class AssignShipmentInternal(Wizard):
diff -r 6de0e55a67d0 -r 48fdd650ed0c shipment.xml
--- a/trytond/trytond/modules/stock/shipment.xml Wed Jun 15 10:45:54 2016 +0200
+++ b/trytond/trytond/modules/stock/shipment.xml Tue Jun 21 10:36:30 2016 +0200
@@ -263,6 +263,13 @@
<field name="act_window" ref="act_shipment_internal_form"/>
</record>
<record model="ir.action.act_window.domain"
+ id="act_shipment_internal_form_domain_shipped">
+ <field name="name">Shipped</field>
+ <field name="sequence" eval="40"/>
+ <field name="domain" eval="[('state', '=', 'shipped')]" pyson="1"/>
+ <field name="act_window" ref="act_shipment_internal_form"/>
+ </record>
+ <record model="ir.action.act_window.domain"
id="act_shipment_internal_form_domain_all">
<field name="name">All</field>
<field name="sequence" eval="9999"/>
diff -r 6de0e55a67d0 -r 48fdd650ed0c tests/scenario_stock_shipment_internal.rst
--- a/trytond/trytond/modules/stock/tests/scenario_stock_shipment_internal.rst Wed Jun 15 10:45:54 2016 +0200
+++ b/trytond/trytond/modules/stock/tests/scenario_stock_shipment_internal.rst Tue Jun 21 10:36:30 2016 +0200
@@ -12,6 +12,7 @@
... get_company
>>> today = datetime.date.today()
>>> yesterday = today - relativedelta(days=1)
+ >>> tomorrow = today + relativedelta(days=1)
Create database::
@@ -103,7 +104,7 @@
>>> move = lost_found_shipment.moves.new()
>>> move.product = product
>>> move.oum = unit
- >>> move.quantity = 1
+ >>> move.quantity = 2
>>> move.from_location = lost_found_loc
>>> move.to_location = internal_loc
>>> move.currency = company.currency
@@ -123,3 +124,54 @@
>>> shipment.click('done')
>>> shipment.state
u'done'
+
+Add lead time inside the warehouse::
+
+ >>> config.user = 1
+ >>> LeadTime = Model.get('stock.location.lead_time')
+ >>> lead_time = LeadTime()
+ >>> lead_time.from_warehouse = storage_loc.warehouse
+ >>> lead_time.to_warehouse = storage_loc.warehouse
+ >>> lead_time.lead_time = datetime.timedelta(1)
+ >>> lead_time.save()
+
+Create Internal Shipment with lead time::
+
+ >>> config.user = stock_user.id
+ >>> shipment = Shipment()
+ >>> shipment.planned_date = tomorrow
+ >>> shipment.from_location = internal_loc
+ >>> shipment.to_location = storage_loc
+ >>> shipment.planned_start_date == today
+ True
+ >>> move = shipment.moves.new()
+ >>> move.product = product
+ >>> move.quantity = 1
+ >>> move.from_location = internal_loc
+ >>> move.to_location = storage_loc
+ >>> shipment.click('wait')
+ >>> len(shipment.moves)
+ 2
+ >>> outgoing_move, = shipment.outgoing_moves
+ >>> outgoing_move.quantity
+ 1.0
+ >>> outgoing_move.from_location == internal_loc
+ True
+ >>> outgoing_move.to_location == shipment.transit_location
+ True
+ >>> incoming_move, = shipment.incoming_moves
+ >>> incoming_move.quantity
+ 1.0
+ >>> incoming_move.from_location == shipment.transit_location
+ True
+ >>> incoming_move.to_location == storage_loc
+ True
+
+ >>> shipment.click('assign_try')
+ True
+ >>> shipment.click('ship')
+ >>> shipment.outgoing_moves[0].state
+ u'done'
+ >>> shipment.click('done')
+ >>> shipment.incoming_moves[0].state
+ u'done'
diff -r 6de0e55a67d0 -r 48fdd650ed0c view/configuration_form.xml
--- a/trytond/trytond/modules/stock/view/configuration_form.xml Wed Jun 15 10:45:54 2016 +0200
+++ b/trytond/trytond/modules/stock/view/configuration_form.xml Tue Jun 21 10:36:30 2016 +0200
@@ -13,6 +13,8 @@
<field name="shipment_out_return_sequence"/>
<label name="shipment_internal_sequence"/>
<field name="shipment_internal_sequence"/>
+ <label name="shipment_internal_transit"/>
+ <field name="shipment_internal_transit"/>
<separator id="inventory" colspan="4" string="Inventory"/>
<label name="inventory_sequence"/>
<field name="inventory_sequence"/>
diff -r 6de0e55a67d0 -r 48fdd650ed0c view/location_lead_time_form.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/trytond/trytond/modules/stock/view/location_lead_time_form.xml Tue Jun 21 10:36:30 2016 +0200
@@ -0,0 +1,13 @@
+<?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. -->
+<form string="Location Lead Time" col="6">
+ <label name="warehouse_from"/>
+ <field name="warehouse_from"/>
+ <label name="warehouse_to"/>
+ <field name="warehouse_to"/>
+ <label name="sequence"/>
+ <field name="sequence"/>
+ <label name="lead_time"/>
+ <field name="lead_time"/>
+</form>
diff -r 6de0e55a67d0 -r 48fdd650ed0c view/location_lead_time_list.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/trytond/trytond/modules/stock/view/location_lead_time_list.xml Tue Jun 21 10:36:30 2016 +0200
@@ -0,0 +1,8 @@
+<?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. -->
+<tree string="Location Lead Times" sequence="sequence" editable="bottom">
+ <field name="warehouse_from"/>
+ <field name="warehouse_to"/>
+ <field name="lead_time"/>
+</tree>
diff -r 6de0e55a67d0 -r 48fdd650ed0c view/shipment_internal_form.xml
--- a/trytond/trytond/modules/stock/view/shipment_internal_form.xml Wed Jun 15 10:45:54 2016 +0200
+++ b/trytond/trytond/modules/stock/view/shipment_internal_form.xml Tue Jun 21 10:36:30 2016 +0200
@@ -12,18 +12,33 @@
<field name="to_location"/>
<label name="planned_date"/>
<field name="planned_date"/>
+ <label name="planned_start_date"/>
+ <field name="planned_start_date"/>
<label name="effective_date"/>
<field name="effective_date"/>
+ <label name="effective_start_date"/>
+ <field name="effective_start_date"/>
<label name="company"/>
<field name="company"/>
- <field name="moves" colspan="4"/>
+ <notebook colspan="4">
+ <page name="moves">
+ <field name="moves" colspan="4"/>
+ </page>
+ <page name="outgoing_moves">
+ <field name="outgoing_moves" colspan="4"/>
+ </page>
+ <page name="incoming_moves">
+ <field name="incoming_moves" colspan="4"/>
+ </page>
+ </notebook>
<label name="state"/>
<field name="state"/>
- <group col="5" colspan="2" id="buttons">
+ <group col="6" colspan="2" id="buttons">
<button string="Cancel" name="cancel" icon="tryton-cancel"/>
<button string="Draft" name="draft"/>
<button string="Wait" name="wait"/>
<button string="Assign" name="assign_wizard" icon="tryton-go-next"/>
+ <button string="Ship" name="ship" icon="tryton-ok"/>
<button string="Done" name="done" icon="tryton-ok"/>
</group>
</form>
diff -r 6de0e55a67d0 -r 48fdd650ed0c view/shipment_internal_tree.xml
--- a/trytond/trytond/modules/stock/view/shipment_internal_tree.xml Wed Jun 15 10:45:54 2016 +0200
+++ b/trytond/trytond/modules/stock/view/shipment_internal_tree.xml Tue Jun 21 10:36:30 2016 +0200
@@ -4,6 +4,7 @@
<tree string="Internal Shipments">
<field name="number"/>
<field name="reference"/>
+ <field name="planned_start_date"/>
<field name="planned_date"/>
<field name="effective_date"/>
<field name="from_location"/>