trytond-stock_lot_expiry-zz/stock.py

227 lines
7.6 KiB
Python

# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
from datetime import date, timedelta
from trytond.model import Workflow, ModelView, fields
from trytond.pool import Pool, PoolMeta
from trytond.pyson import And, Bool, Equal, Eval, If, Not
from trytond.transaction import Transaction
__all__ = ['Template', 'Lot', 'Location', 'Move']
__metaclass__ = PoolMeta
class Template:
__name__ = 'product.template'
life_time = fields.Integer('Life Time',
help='The number of days before a lot may become dangerous and should '
'not be consumed.')
expiry_time = fields.Integer('Expiry Time',
help='The number of days before a lot starts deteriorating without '
'becoming dangerous.')
removal_time = fields.Integer('Removal Time',
help='The number of days before a lot should be removed.')
alert_time = fields.Integer('Alert Time',
help='The number of days after which an alert should be notified '
'about the lot.')
class Lot:
__name__ = 'stock.lot'
life_date = fields.Date('End of Life Date',
help='The date on which the lot may become dangerous and should not '
'be consumed.')
expiry_date = fields.Date('Expiry Date',
help='The date on which the lot starts deteriorating without becoming '
'dangerous.')
removal_date = fields.Date('Removal Date',
help='The date on which the lot should be removed.')
alert_date = fields.Date('Alert Date',
help='The date on which an alert should be notified about the lot.')
expired = fields.Function(fields.Boolean('Expired'),
'get_expired', searcher='search_expired')
@classmethod
def __setup__(cls):
super(Lot, cls).__setup__()
cls._error_messages.update({
'Expired': 'Expired',
})
def get_rec_name(self, name):
rec_name = super(Lot, self).get_rec_name(name)
if self.expired:
rec_name += ' (%s)' % self.raise_user_error('Expired',
raise_exception=False)
return rec_name
@fields.depends('product', 'life_date', 'expiry_date', 'removal_date',
'alert_date')
def on_change_product(self):
try:
result = super(Lot, self).on_change_product()
except AttributeError:
result = {}
if not self.product:
return result
for fname in ('life_date', 'expiry_date', 'removal_date',
'alert_date'):
product_field = fname.replace('date', 'time')
margin = getattr(self.product.template, product_field)
result[fname] = (margin
and date.today() + timedelta(days=margin))
return result
def get_expired(self, name):
pool = Pool()
Date = pool.get('ir.date')
if not self.expiry_date:
return False
context = Transaction().context
date = Date.today()
if context.get('stock_move_date'):
date = context['stock_move_date']
elif context.get('stock_date_end'):
date = context['stock_date_end']
return self.expiry_date <= date
@classmethod
def search_expired(cls, name, domain=None):
pool = Pool()
Date = pool.get('ir.date')
if not domain:
return []
context = Transaction().context
date = Date.today()
if context.get('stock_move_date'):
date = context['stock_move_date']
elif context.get('stock_date_end'):
date = context['stock_date_end']
_, op, operand = domain
search_expired = (op == '=' and operand
or op == '!=' and not operand)
if search_expired:
return [
('expiry_date', '!=', None),
('expiry_date', '<=', date),
]
else:
return [
'OR', [
('expiry_date', '=', None),
], [
('expiry_date', '>', date),
]]
class Location:
__name__ = 'stock.location'
expired = fields.Boolean('Expired Products\' Location',
help='This option identifies this location as a container for expired '
'products (provably it is a temporal location until the product is '
'returned or removed).\n'
'Take care that if you set this location as a child of the Storage '
'location of a Warehouse, the products in this location will be '
'computed as available stock.')
allow_expired = fields.Boolean('Allow Expired', states={
'invisible': Eval('expired', False),
}, depends=['expired'],
help='Check this option to allow move expired lots to this location.')
@fields.depends('expired', 'allow_expired')
def on_change_expired(self):
if self.expired:
return {
'allow_expired': True,
}
return {}
@classmethod
def create(cls, vlist):
for vals in vlist:
if vals.get('expired'):
vals['allow_expired'] = True
return super(Location, cls).create(vlist)
@classmethod
def write(cls, *args):
actions = iter(args)
args = []
for locations, values in zip(actions, actions):
if values.get('expired'):
values['allow_expired'] = True
args.extend((locations, values))
super(Location, cls).write(*args)
class Move:
__name__ = 'stock.move'
to_location_allow_expired = fields.Function(
fields.Boolean('Destination Allow Expired'),
'on_change_with_to_location_allow_expired')
@classmethod
def __setup__(cls):
super(Move, cls).__setup__()
cls.lot.domain.append(
If(And(Equal(Eval('state', 'draft'), 'draft'),
Not(Bool(Eval('to_location_allow_expired', True)))),
('expired', '=', False),
()),
)
if not cls.lot.context:
cls.lot.context = {}
cls.lot.context['stock_move_date'] = If(
Bool(Eval('effective_date', False)),
Eval('effective_date'),
Eval('planned_date'))
cls.lot.loading = 'lazy'
for fname in ('state', 'to_location_allow_expired', 'effective_date',
'planned_date'):
if fname not in cls.lot.depends:
cls.lot.depends.append(fname)
cls._error_messages.update({
'expired_lot_invalid_destination': ('You are trying to do the '
'Stock Move "%(move)s" but its Lot "%(lot)s" is expired and '
'the Destination Location "%(to_location)s" doesn\'t accept '
'expired lots.'),
})
@fields.depends('to_location')
def on_change_with_to_location_allow_expired(self, name=None):
return self.to_location and self.to_location.allow_expired or False
@classmethod
@ModelView.button
@Workflow.transition('done')
def do(cls, moves):
for move in moves:
move.check_allow_lot_expired()
super(Move, cls).do(moves)
def check_allow_lot_expired(self):
if self.to_location.allow_expired or not self.lot:
return
error = False
with Transaction().set_context(stock_move_date=self.effective_date):
error = not self.to_location.allow_expired and self.lot.expired
if error:
self.raise_user_error('expired_lot_invalid_destination', {
'move': self.rec_name,
'lot': self.lot.rec_name,
'to_location': self.to_location.rec_name,
})