======= PURPOSE ======= Improve duplicated serial number message to show more information. ======= SPEC ======= In this commit, we show full information about all invalide product name and lot/sn number when the user enters duplicated ones. - Change the sql constraint for checking duplicate sn/lot to python constraint to support customised error message. - Create lot in batch to show all invalide ones in error message. ======= LINKS ======= task 2083605 pr #44366 Related: odoo/upgrade#954 Signed-off-by: Simon Lejeune (sle) <sle@openerp.com>
135 lines
5.9 KiB
Python
135 lines
5.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
import datetime
|
|
from odoo import api, fields, models, SUPERUSER_ID, _
|
|
|
|
|
|
class StockProductionLot(models.Model):
|
|
_inherit = 'stock.production.lot'
|
|
|
|
use_expiration_date = fields.Boolean(
|
|
string='Use Expiration Date', related='product_id.use_expiration_date')
|
|
expiration_date = fields.Datetime(string='Expiration Date',
|
|
help='This is the date on which the goods with this Serial Number may become dangerous and must not be consumed.')
|
|
use_date = fields.Datetime(string='Best before Date',
|
|
help='This is the date on which the goods with this Serial Number start deteriorating, without being dangerous yet.')
|
|
removal_date = fields.Datetime(string='Removal Date',
|
|
help='This is the date on which the goods with this Serial Number should be removed from the stock. This date will be used in FEFO removal strategy.')
|
|
alert_date = fields.Datetime(string='Alert Date',
|
|
help='Date to determine the expired lots and serial numbers using the filter "Expiration Alerts".')
|
|
product_expiry_alert = fields.Boolean(compute='_compute_product_expiry_alert', help="The Expiration Date has been reached.")
|
|
product_expiry_reminded = fields.Boolean(string="Expiry has been reminded")
|
|
|
|
@api.depends('expiration_date')
|
|
def _compute_product_expiry_alert(self):
|
|
current_date = fields.Datetime.now()
|
|
for lot in self:
|
|
if lot.expiration_date:
|
|
lot.product_expiry_alert = lot.expiration_date <= current_date
|
|
else:
|
|
lot.product_expiry_alert = False
|
|
|
|
def _get_dates(self, product_id=None):
|
|
"""Returns dates based on number of days configured in current lot's product."""
|
|
mapped_fields = {
|
|
'expiration_date': 'expiration_time',
|
|
'use_date': 'use_time',
|
|
'removal_date': 'removal_time',
|
|
'alert_date': 'alert_time'
|
|
}
|
|
res = dict.fromkeys(mapped_fields, False)
|
|
product = self.env['product.product'].browse(product_id) or self.product_id
|
|
if product:
|
|
for field in mapped_fields:
|
|
duration = getattr(product, mapped_fields[field])
|
|
if duration:
|
|
date = datetime.datetime.now() + datetime.timedelta(days=duration)
|
|
res[field] = fields.Datetime.to_string(date)
|
|
return res
|
|
|
|
# Assign dates according to products data
|
|
@api.model_create_multi
|
|
def create(self, vals_list):
|
|
for vals in vals_list:
|
|
dates = self._get_dates(vals.get('product_id') or self.env.context.get('default_product_id'))
|
|
for d in dates:
|
|
if not vals.get(d):
|
|
vals[d] = dates[d]
|
|
return super().create(vals_list)
|
|
|
|
@api.onchange('expiration_date')
|
|
def _onchange_expiration_date(self):
|
|
if not self._origin or not (self.expiration_date and self._origin.expiration_date):
|
|
return
|
|
time_delta = self.expiration_date - self._origin.expiration_date
|
|
# As we compare expiration_date with _origin.expiration_date, we need to
|
|
# use `_get_date_values` with _origin to keep a stability in the values.
|
|
# Otherwise it will recompute from the updated values if the user calls
|
|
# this onchange multiple times without save between each onchange.
|
|
vals = self._origin._get_date_values(time_delta)
|
|
self.update(vals)
|
|
|
|
@api.onchange('product_id')
|
|
def _onchange_product(self):
|
|
dates_dict = self._get_dates()
|
|
for field, value in dates_dict.items():
|
|
setattr(self, field, value)
|
|
|
|
@api.model
|
|
def _alert_date_exceeded(self):
|
|
"""Log an activity on internally stored lots whose alert_date has been reached.
|
|
|
|
No further activity will be generated on lots whose alert_date
|
|
has already been reached (even if the alert_date is changed).
|
|
"""
|
|
alert_lots = self.env['stock.production.lot'].search([
|
|
('alert_date', '<=', fields.Date.today()),
|
|
('product_expiry_reminded', '=', False)])
|
|
|
|
lot_stock_quants = self.env['stock.quant'].search([
|
|
('lot_id', 'in', alert_lots.ids),
|
|
('quantity', '>', 0),
|
|
('location_id.usage', '=', 'internal')])
|
|
alert_lots = lot_stock_quants.mapped('lot_id')
|
|
|
|
for lot in alert_lots:
|
|
lot.activity_schedule(
|
|
'product_expiry.mail_activity_type_alert_date_reached',
|
|
user_id=lot.product_id.responsible_id.id or SUPERUSER_ID,
|
|
note=_("The alert date has been reached for this lot/serial number")
|
|
)
|
|
alert_lots.write({
|
|
'product_expiry_reminded': True
|
|
})
|
|
|
|
def _update_date_values(self, new_date):
|
|
if new_date:
|
|
time_delta = new_date - (self.expiration_date or fields.Datetime.now())
|
|
vals = self._get_date_values(time_delta)
|
|
vals['expiration_date'] = new_date
|
|
self.write(vals)
|
|
|
|
def _get_date_values(self, time_delta):
|
|
''' Return a dict with different date values updated depending of the
|
|
time_delta. Used in the onchange of `expiration_date` and when user
|
|
defines a date at the receipt. '''
|
|
vals = {}
|
|
if self.use_date:
|
|
vals['use_date'] = self.use_date + time_delta
|
|
if self.removal_date:
|
|
vals['removal_date'] = self.removal_date + time_delta
|
|
if self.alert_date:
|
|
vals['alert_date'] = self.alert_date + time_delta
|
|
return vals
|
|
|
|
|
|
class ProcurementGroup(models.Model):
|
|
_inherit = 'procurement.group'
|
|
|
|
@api.model
|
|
def _run_scheduler_tasks(self, use_new_cursor=False, company_id=False):
|
|
super(ProcurementGroup, self)._run_scheduler_tasks(use_new_cursor=use_new_cursor, company_id=company_id)
|
|
self.env['stock.production.lot']._alert_date_exceeded()
|
|
if use_new_cursor:
|
|
self.env.cr.commit()
|