mirror of
https://gitlab.com/datalifeit/trytond-stock_move_time
synced 2023-12-14 04:42:59 +01:00
364 lines
13 KiB
Python
364 lines
13 KiB
Python
# The COPYRIGHT file at the top level of
|
|
# this repository contains the full copyright notices and license terms.
|
|
import datetime
|
|
from trytond.model import fields
|
|
from trytond.pool import PoolMeta, Pool
|
|
from trytond.pyson import Eval, In, Bool, If
|
|
|
|
__all__ = ['Move', 'ShipmentOut', 'ShipmentOutReturn', 'ShipmentInternal',
|
|
'ShipmentInReturn']
|
|
|
|
DATE_FORMAT = '%H:%M:%S'
|
|
|
|
|
|
class Move(metaclass=PoolMeta):
|
|
__name__ = 'stock.move'
|
|
|
|
time_ = fields.Time('Time', format=DATE_FORMAT,
|
|
states={'readonly': In(Eval('state'), ['cancelled', 'assigned', 'done'])},
|
|
depends=['state'])
|
|
start_date = fields.Function(
|
|
fields.DateTime('Start date', format=DATE_FORMAT,
|
|
states={'readonly': In(Eval('state'),
|
|
['cancelled', 'assigned', 'done'])},
|
|
depends=['state', 'planned_date', 'effective_date', 'time_']),
|
|
'on_change_with_start_date',
|
|
setter='set_start_date', searcher='search_start_date')
|
|
end_date = fields.DateTime('End date', format=DATE_FORMAT,
|
|
states={'readonly': In(Eval('state'),
|
|
['cancelled', 'assigned', 'done'])},
|
|
depends=['state'])
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(Move, cls).__setup__()
|
|
if cls.effective_date.states.get('invisible'):
|
|
cls.effective_date.states['invisible'] |= Bool(True)
|
|
else:
|
|
cls.effective_date.states['invisible'] = Bool(True)
|
|
|
|
@classmethod
|
|
def create(cls, vlist):
|
|
vlist = [v.copy() for v in vlist]
|
|
for values in vlist:
|
|
if not values.get('time_'):
|
|
values.setdefault('time_', datetime.datetime.min.time())
|
|
if not values.get('end_date') and cls.init_end_date() and (
|
|
values.get('effective_date')
|
|
or values.get('planned_date')):
|
|
values.setdefault('end_date', datetime.datetime.combine(
|
|
values.get('effective_date') or values.get('planned_date'),
|
|
values.get('time_'))
|
|
)
|
|
return super(Move, cls).create(vlist)
|
|
|
|
@classmethod
|
|
def write(cls, *args):
|
|
super(Move, cls).write(*args)
|
|
|
|
actions = iter(args)
|
|
for records, values in zip(actions, actions):
|
|
for record in records:
|
|
if record.start_date and cls.init_end_date() and (
|
|
not record.end_date
|
|
or record.end_date < record.start_date):
|
|
cls.write([record], {'end_date': record.start_date})
|
|
|
|
@fields.depends('effective_date', 'planned_date', 'time_')
|
|
def on_change_with_start_date(self, name=None):
|
|
if self.effective_date or self.planned_date:
|
|
_date = datetime.datetime.combine(self.effective_date or
|
|
self.planned_date, self.time_ or datetime.datetime.min.time())
|
|
_date = _date.replace(microsecond=0)
|
|
return _date
|
|
return None
|
|
|
|
@fields.depends('start_date', 'end_date', 'planned_date', methods=[
|
|
'init_end_date'])
|
|
def on_change_start_date(self):
|
|
if self.start_date:
|
|
self.effective_date = self.start_date.date()
|
|
self.time_ = self.start_date.time()
|
|
if not self.planned_date:
|
|
self.planned_date = self.start_date.date()
|
|
if not self.end_date and self.init_end_date():
|
|
self.end_date = self.start_date
|
|
|
|
@classmethod
|
|
def set_start_date(cls, records, name, value):
|
|
if not value:
|
|
return
|
|
cls.write(records, {'effective_date': value.date(),
|
|
'time_': value.time().strftime(DATE_FORMAT)})
|
|
|
|
@classmethod
|
|
def search_start_date(cls, name, clause):
|
|
if clause[1] not in ('>', '<', '=', '>=', '<='):
|
|
raise NotImplementedError(
|
|
'Date time filter operand not implemented.')
|
|
if not clause[2]:
|
|
raise NotImplementedError('Filter must be a date.')
|
|
date_op = {'>': '>=',
|
|
'<': '<=',
|
|
'=': '=',
|
|
'>=': '>=',
|
|
'<=': '<='}
|
|
return [('effective_date', date_op[clause[1]], clause[2].date()),
|
|
('time_', clause[1], clause[2].time())]
|
|
|
|
@classmethod
|
|
def init_end_date(cls):
|
|
return True
|
|
|
|
|
|
class ShipmentWithTimeMixin(object):
|
|
|
|
start_time = fields.Time('Start time', format=DATE_FORMAT,
|
|
states={'readonly': ~Eval('state').in_(['draft', 'waiting'])},
|
|
depends=['state'])
|
|
start_date = fields.Function(
|
|
fields.DateTime('Start date', format=DATE_FORMAT,
|
|
states={'readonly': ~Eval('state').in_(['draft', 'waiting'])},
|
|
depends=['state']),
|
|
'on_change_with_start_date', setter='set_start_date')
|
|
interval = fields.TimeDelta('Interval')
|
|
end_date = fields.Function(
|
|
fields.DateTime('End date', format=DATE_FORMAT,
|
|
domain=[If(Bool(Eval('start_date')) & Bool(Eval('end_date')),
|
|
('end_date', '>=', Eval('start_date')),
|
|
())],
|
|
states={'readonly': ~Eval('state').in_(
|
|
['draft', 'waiting', 'assigned'])},
|
|
depends=['state', 'start_date', 'start_time']),
|
|
'on_change_with_end_date', setter='set_end_date')
|
|
|
|
@staticmethod
|
|
def default_start_date():
|
|
return datetime.datetime.now()
|
|
|
|
@staticmethod
|
|
def default_end_date():
|
|
return datetime.datetime.now()
|
|
|
|
@fields.depends('effective_date', 'planned_date', 'start_time')
|
|
def on_change_with_start_date(self, name=None):
|
|
if self.effective_date or self.planned_date:
|
|
return datetime.datetime.combine(
|
|
self.effective_date or self.planned_date,
|
|
self.start_time or datetime.datetime.min.time())
|
|
return None
|
|
|
|
@fields.depends('start_date')
|
|
def on_change_start_date(self):
|
|
if self.start_date:
|
|
self.effective_date = self.start_date.date()
|
|
self.start_time = self.start_date.time()
|
|
|
|
@classmethod
|
|
def set_start_date(cls, records, name, value):
|
|
if not value:
|
|
return
|
|
cls.write(records, {'effective_date': value.date(),
|
|
'start_time': value.time().strftime(DATE_FORMAT)})
|
|
|
|
@fields.depends('start_date', 'end_date')
|
|
def on_change_end_date(self):
|
|
if self.start_date and self.end_date:
|
|
_interval = self.end_date - self.start_date
|
|
self.interval = self._format_interval(_interval)
|
|
|
|
@fields.depends('start_date', 'interval')
|
|
def on_change_with_end_date(self, name=None):
|
|
if self.start_date:
|
|
if self.interval:
|
|
return self.start_date + self._format_interval(self.interval)
|
|
return self.start_date
|
|
return None
|
|
|
|
@classmethod
|
|
def set_end_date(cls, records, name, value):
|
|
for record in records:
|
|
if not record.start_date or not value:
|
|
continue
|
|
_interval = value - record.start_date
|
|
record.interval = cls._format_interval(_interval)
|
|
cls.save(records)
|
|
|
|
@classmethod
|
|
def _format_interval(cls, value):
|
|
return value - datetime.timedelta(microseconds=value.microseconds)
|
|
|
|
def _move_time(self):
|
|
return self.end_date.time(), self.start_time
|
|
|
|
|
|
class ShipmentOut(ShipmentWithTimeMixin, metaclass=PoolMeta):
|
|
__name__ = 'stock.shipment.out'
|
|
|
|
@property
|
|
def _move_planned_date(self):
|
|
return self.end_date.date(), self.planned_date
|
|
|
|
def _get_inventory_move(self, move):
|
|
res = super(ShipmentOut, self)._get_inventory_move(move)
|
|
res.start_date = self.start_date
|
|
res.end_date = self.end_date
|
|
return res
|
|
|
|
def _get_outgoing_move(self, move):
|
|
res = super(ShipmentOut, self)._get_outgoing_move(move)
|
|
res.start_date = self.end_date
|
|
res.end_date = self.end_date
|
|
return res
|
|
|
|
@classmethod
|
|
def _set_move_planned_date(cls, shipments):
|
|
Move = Pool().get('stock.move')
|
|
|
|
super(ShipmentOut, cls)._set_move_planned_date(shipments)
|
|
|
|
for shipment in shipments:
|
|
outgoing_time, inventory_time = shipment._move_time()
|
|
Move.write([x for x in shipment.outgoing_moves
|
|
if (x.state not in ('assigned', 'done', 'cancelled') and
|
|
x.time_ != outgoing_time)],
|
|
{'time_': outgoing_time})
|
|
Move.write([x for x in shipment.inventory_moves
|
|
if (x.state not in ('assigned', 'done', 'cancelled') and
|
|
x.time_ != inventory_time)], {
|
|
'time_': inventory_time,
|
|
})
|
|
|
|
|
|
class ShipmentOutReturn(ShipmentWithTimeMixin, metaclass=PoolMeta):
|
|
__name__ = 'stock.shipment.out.return'
|
|
|
|
def _get_move_planned_date(self):
|
|
return self.end_date.date(), self.planned_date
|
|
|
|
def _get_inventory_move(self, incoming_move):
|
|
res = super()._get_inventory_move(incoming_move)
|
|
res.start_date = incoming_move.end_date
|
|
res.end_date = incoming_move.end_date
|
|
return res
|
|
|
|
@classmethod
|
|
def _set_move_planned_date(cls, shipments):
|
|
Move = Pool().get('stock.move')
|
|
|
|
super()._set_move_planned_date(shipments)
|
|
|
|
for shipment in shipments:
|
|
inventory_time, incoming_time = shipment._move_time()
|
|
incoming_moves = [x for x in shipment.incoming_moves
|
|
if x.state not in ('assigned', 'done', 'cancelled')
|
|
and x.time_ != incoming_time]
|
|
if incoming_moves:
|
|
Move.write(incoming_moves, {
|
|
'time_': incoming_time,
|
|
'end_date': shipment.end_date
|
|
})
|
|
inventory_moves = [x for x in shipment.inventory_moves
|
|
if x.state not in ('assigned', 'done', 'cancelled')
|
|
and x.time_ != inventory_time]
|
|
if inventory_moves:
|
|
Move.write(inventory_moves, {
|
|
'time_': inventory_time,
|
|
})
|
|
|
|
|
|
class ShipmentInternal(metaclass=PoolMeta):
|
|
__name__ = 'stock.shipment.internal'
|
|
|
|
time_ = fields.Time('Time', format=DATE_FORMAT,
|
|
states={'readonly': ~Eval('state').in_(['draft', 'waiting'])},
|
|
depends=['state'])
|
|
date_time_ = fields.Function(
|
|
fields.DateTime('Effective date',
|
|
states={'readonly': ~Eval('state').in_(['draft', 'waiting'])},
|
|
depends=['state']),
|
|
'on_change_with_date_time_', setter='set_date_time_')
|
|
|
|
@staticmethod
|
|
def default_time_():
|
|
return datetime.datetime.now().time()
|
|
|
|
@staticmethod
|
|
def default_date_time_():
|
|
return datetime.datetime.now()
|
|
|
|
@fields.depends('planned_date', 'effective_date', 'time_')
|
|
def on_change_with_date_time_(self, name=None):
|
|
if self.effective_date or self.planned_date:
|
|
return datetime.datetime.combine(
|
|
self.effective_date or self.planned_date,
|
|
self.time_ or datetime.datetime.min.time())
|
|
return None
|
|
|
|
@classmethod
|
|
def set_date_time_(cls, records, name, value):
|
|
cls.write(records, {
|
|
'effective_date': value.date(),
|
|
'time_': value.time().strftime(DATE_FORMAT)})
|
|
|
|
@classmethod
|
|
def _get_date_time_changes(cls, moves, date_field):
|
|
if not moves:
|
|
return []
|
|
dates = {}
|
|
for move in moves:
|
|
dates.setdefault(move.shipment.date_time_, []).append(move)
|
|
to_write = []
|
|
for date, moves in dates.items():
|
|
to_write.extend([moves, {date_field: date}])
|
|
return to_write
|
|
|
|
@classmethod
|
|
def ship(cls, shipments):
|
|
Move = Pool().get('stock.move')
|
|
|
|
to_write = (
|
|
cls._get_date_time_changes([
|
|
m for s in shipments for m in s.incoming_moves
|
|
if m.start_date != s.date_time_], 'start_date')
|
|
+ cls._get_date_time_changes([
|
|
m for s in shipments for m in s.outgoing_moves
|
|
if m.end_date != s.date_time_], 'end_date')
|
|
)
|
|
if to_write:
|
|
Move.write(*to_write)
|
|
super().ship(shipments)
|
|
|
|
@classmethod
|
|
def done(cls, shipments):
|
|
Move = Pool().get('stock.move')
|
|
|
|
to_write = cls._get_date_time_changes([
|
|
m for s in shipments for m in s.incoming_moves
|
|
if m.end_date != s.date_time_], 'end_date')
|
|
if to_write:
|
|
Move.write(*to_write)
|
|
super().done(shipments)
|
|
|
|
|
|
class ShipmentInReturn(ShipmentWithTimeMixin, metaclass=PoolMeta):
|
|
__name__ = 'stock.shipment.in.return'
|
|
|
|
@property
|
|
def _move_planned_date(self):
|
|
if self.end_date:
|
|
return self.end_date.date()
|
|
return super()._move_planned_date()
|
|
|
|
@classmethod
|
|
def _set_move_planned_date(cls, shipments):
|
|
pool = Pool()
|
|
Move = pool.get('stock.move')
|
|
|
|
super()._set_move_planned_date(shipments)
|
|
for shipment in shipments:
|
|
move_time, _ = shipment._move_time()
|
|
Move.write([m for m in shipment.moves
|
|
if (m.state not in ('assigned', 'done', 'cancelled') and
|
|
m.time_ != move_time)], {'time_': move_time})
|