268 lines
8.6 KiB
Python
268 lines
8.6 KiB
Python
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
# this repository contains the full copyright notices and license terms.
|
|
import functools
|
|
|
|
from trytond.i18n import gettext
|
|
from trytond.model import ModelSQL, ModelView, Workflow, fields
|
|
from trytond.modules.stock.exceptions import AssignError
|
|
from trytond.pool import Pool, PoolMeta
|
|
from trytond.pyson import Eval, If
|
|
from trytond.transaction import Transaction
|
|
|
|
|
|
class Location(metaclass=PoolMeta):
|
|
__name__ = 'stock.location'
|
|
|
|
movable = fields.Boolean(
|
|
"Movable",
|
|
states={
|
|
'invisible': Eval('type') != 'storage',
|
|
})
|
|
assigned_by = fields.Reference(
|
|
"Assigned by", 'get_assigned_by', readonly=True)
|
|
|
|
@classmethod
|
|
def _get_assigned_by(cls):
|
|
"Return list of Model names for assigned_by Reference"
|
|
return ['stock.shipment.internal', 'stock.shipment.out']
|
|
|
|
@classmethod
|
|
def get_assigned_by(cls):
|
|
pool = Pool()
|
|
IrModel = pool.get('ir.model')
|
|
get_name = IrModel.get_name
|
|
models = cls._get_assigned_by()
|
|
return [(None, '')] + [(m, get_name(m)) for m in models]
|
|
|
|
@classmethod
|
|
def deactivate_empty(cls, locations=None):
|
|
if locations is None:
|
|
locations = cls.search([
|
|
('movable', '=', True),
|
|
])
|
|
to_deactivate = cls.get_empty_locations(locations)
|
|
if to_deactivate:
|
|
cls.write(cls.browse(to_deactivate), {'active': False})
|
|
|
|
@classmethod
|
|
def forecast_location_move(cls, date):
|
|
"""Move temporary locations planned for the date
|
|
and returns a method to restore the initial parent."""
|
|
pool = Pool()
|
|
ShipmentInternal = pool.get('stock.shipment.internal')
|
|
Location = pool.get('stock.location')
|
|
Date = pool.get('ir.date')
|
|
|
|
today = Date.today()
|
|
shipments = ShipmentInternal.search([
|
|
('locations', '!=', None),
|
|
('state', 'not in', ['cancelled', 'done']),
|
|
['OR', [
|
|
('planned_date', '<=', date),
|
|
('planned_date', '>=', today),
|
|
('effective_date', '=', None),
|
|
], [
|
|
('effective_date', '<=', date),
|
|
('effective_date', '>=', today),
|
|
],
|
|
],
|
|
],
|
|
order=[('planned_date', 'ASC'), ('id', 'ASC')])
|
|
location_parents = {}
|
|
locations = []
|
|
|
|
for shipment in shipments:
|
|
for location in shipment.locations:
|
|
location_parents.setdefault(location, location.parent)
|
|
location.parent = shipment.to_location
|
|
locations.append(location)
|
|
Location.save(locations)
|
|
|
|
def restore():
|
|
locations = []
|
|
for location, parent in location_parents.items():
|
|
location.parent = parent
|
|
locations.append(location)
|
|
Location.save(locations)
|
|
return restore
|
|
|
|
|
|
def clear_location_assignation(func):
|
|
@functools.wraps(func)
|
|
def wrapper(cls, shipments, *args, **kwargs):
|
|
pool = Pool()
|
|
Location = pool.get('stock.location')
|
|
locations = []
|
|
for shipment in shipments:
|
|
for location in shipment.locations:
|
|
if location.assigned_by == shipment:
|
|
locations.append(location)
|
|
Location.write(locations, {'assigned_by': None})
|
|
return func(cls, shipments, *args, **kwargs)
|
|
return wrapper
|
|
|
|
|
|
class ShipmentInternal(metaclass=PoolMeta):
|
|
__name__ = 'stock.shipment.internal'
|
|
|
|
locations = fields.Many2Many(
|
|
'stock.shipment.internal-location', 'shipment', 'location',
|
|
"Locations",
|
|
domain=[
|
|
('type', '=', 'storage'),
|
|
('movable', '=', True),
|
|
If(Eval('state') == 'assigned',
|
|
('parent', 'child_of', Eval('from_location', -1)),
|
|
()),
|
|
],
|
|
states={
|
|
'readonly': (~Eval('state').in_(['request', 'draft'])
|
|
| ~Eval('from_location') | ~Eval('to_location')),
|
|
})
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('draft')
|
|
@clear_location_assignation
|
|
def draft(cls, shipments):
|
|
super().draft(shipments)
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('waiting')
|
|
@clear_location_assignation
|
|
def wait(cls, shipments):
|
|
super().wait(shipments)
|
|
|
|
@classmethod
|
|
@Workflow.transition('assigned')
|
|
def assign(cls, shipments):
|
|
pool = Pool()
|
|
Location = pool.get('stock.location')
|
|
locations = {}
|
|
for shipment in shipments:
|
|
for location in shipment.locations:
|
|
if not location.assigned_by:
|
|
location.assigned_by = shipment
|
|
if location in locations:
|
|
raise AssignError(
|
|
gettext('stock_location_move'
|
|
'.msg_location_already_assigned') % {
|
|
'location': location.rec_name,
|
|
'assigned_by': locations[location].rec_name,
|
|
})
|
|
locations[location] = location.assigned_by
|
|
elif location.assigned_by != shipment:
|
|
raise AssignError(
|
|
gettext('stock_location_move'
|
|
'.msg_location_already_assigned') % {
|
|
'location': location.rec_name,
|
|
'assigned_by': location.assigned_by.rec_name,
|
|
})
|
|
if locations:
|
|
Location.save(list(locations))
|
|
super().assign(shipments)
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('shipped')
|
|
def ship(cls, shipments):
|
|
pool = Pool()
|
|
Location = pool.get('stock.location')
|
|
to_write = []
|
|
for shipment in shipments:
|
|
if not shipment.transit_location or not shipment.locations:
|
|
continue
|
|
to_write.append(list(shipment.locations))
|
|
to_write.append({
|
|
'parent': shipment.transit_location.id,
|
|
})
|
|
if to_write:
|
|
Location.write(*to_write)
|
|
super().ship(shipments)
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('done')
|
|
@clear_location_assignation
|
|
def do(cls, shipments):
|
|
pool = Pool()
|
|
Location = pool.get('stock.location')
|
|
to_write = []
|
|
for shipment in shipments:
|
|
if not shipment.locations:
|
|
continue
|
|
to_write.append(list(shipment.locations))
|
|
to_write.append({
|
|
'parent': shipment.to_location.id,
|
|
})
|
|
if to_write:
|
|
Location.write(*to_write)
|
|
super().do(shipments)
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('cancelled')
|
|
@clear_location_assignation
|
|
def cancel(cls, shipments):
|
|
super().cancel(shipments)
|
|
|
|
|
|
class ShipmentInternal_Location(ModelSQL):
|
|
__name__ = 'stock.shipment.internal-location'
|
|
|
|
shipment = fields.Many2One(
|
|
'stock.shipment.internal', "Shipment", required=True)
|
|
location = fields.Many2One(
|
|
'stock.location', "Location", required=True,
|
|
domain=[
|
|
('type', '=', 'storage'),
|
|
('movable', '=', True),
|
|
])
|
|
|
|
|
|
def deactivate_empty_location(func):
|
|
@functools.wraps(func)
|
|
def wrapper(cls, shipments, *args, **kwargs):
|
|
pool = Pool()
|
|
Location = pool.get('stock.location')
|
|
result = func(cls, shipments, *args, **kwargs)
|
|
locations = set()
|
|
for shipment in shipments:
|
|
locations.update(
|
|
move.from_location for move in shipment.moves
|
|
if move.from_location.movable)
|
|
Location.deactivate_empty(list(locations))
|
|
return result
|
|
return wrapper
|
|
|
|
|
|
class ShipmentOut(metaclass=PoolMeta):
|
|
__name__ = 'stock.shipment.out'
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('done')
|
|
@deactivate_empty_location
|
|
def do(cls, shipments):
|
|
super().do(shipments)
|
|
|
|
|
|
class ShipmentInReturn(metaclass=PoolMeta):
|
|
__name__ = 'stock.shipment.in.return'
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('done')
|
|
@deactivate_empty_location
|
|
def do(cls, shipments):
|
|
super().do(shipments)
|
|
|
|
|
|
class Supply(metaclass=PoolMeta):
|
|
__name__ = 'stock.supply'
|
|
|
|
def transition_create_(self):
|
|
with Transaction().set_context(forecast_location_move=True):
|
|
return super().transition_create_()
|