# -*- 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()