From 8f22f23e61d9fded7eb893008b8f0d173bc0e8fb Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Wed, 11 Jul 2018 11:01:24 +0200 Subject: [PATCH] [ADD] lunch: prevent ordering unavailable products Add a field partner_id on a lunch.alert If the field is set, the alert becomes blocking and will prevent people from ordering a lunch. Add a available field on the lunch.product that will check if any alert forbids ordering that product at the time of the order. Pass the lunch.order date in the context to be able to plan a lunch order another day if the provider is forbidden today. --- addons/lunch/models/lunch.py | 112 +++++++++++++++++++++++------ addons/lunch/views/lunch_views.xml | 16 ++++- 2 files changed, 106 insertions(+), 22 deletions(-) diff --git a/addons/lunch/models/lunch.py b/addons/lunch/models/lunch.py index 5808015d09b..5102b0253f4 100644 --- a/addons/lunch/models/lunch.py +++ b/addons/lunch/models/lunch.py @@ -9,6 +9,7 @@ import datetime from odoo import api, fields, models, _ from odoo.exceptions import AccessError, ValidationError from odoo.addons import decimal_precision as dp +from odoo.osv import expression class LunchOrder(models.Model): @@ -164,7 +165,8 @@ class LunchOrderLine(models.Model): name = fields.Char(related='product_id.name', string="Product Name", readonly=True) order_id = fields.Many2one('lunch.order', 'Order', ondelete='cascade', required=True) - product_id = fields.Many2one('lunch.product', 'Product', required=True) + product_id = fields.Many2one('lunch.product', 'Product', required=True, + domain=[('available', '=', True)]) category_id = fields.Many2one('lunch.product.category', string='Product Category', related='product_id.category_id', readonly=True, store=True) date = fields.Date(string='Date', related='order_id.date', readonly=True, store=True) @@ -183,6 +185,28 @@ class LunchOrderLine(models.Model): cashmove = fields.One2many('lunch.cashmove', 'order_id', 'Cash Move') currency_id = fields.Many2one('res.currency', related='order_id.currency_id') + def _check_supplier_availibility(self): + products = self.mapped('product_id') + if not all(product.available for product in products): + supplier_name = ", ".join(product.supplier.display_name for product in products if not product.available) + raise ValidationError(_("Vendor(s) '%s' is not available today") % supplier_name) + + @api.model + def create(self, vals): + """ Override as an onchange would not apply if using the history buttons """ + res = super(LunchOrderLine, self).create(vals) + res.with_context(lunch_date=res.order_id.date)._check_supplier_availibility() + return res + + @api.model + def write(self, vals): + """ Override as an onchange would not apply if using the history buttons """ + res = super(LunchOrderLine, self).write(vals) + if vals.get('product_id'): + for line in self: + line.with_context(lunch_date=line.order_id.date)._check_supplier_availibility() + return res + @api.one def order(self): """ @@ -236,6 +260,41 @@ class LunchProduct(models.Model): price = fields.Float('Price', digits=dp.get_precision('Account')) supplier = fields.Many2one('res.partner', 'Vendor') active = fields.Boolean(default=True) + available = fields.Boolean(compute='_get_available_product', search='_search_available_products') + + @api.depends('supplier') + def _get_available_product(self): + for product in self: + if not product.supplier: + product.available = True + else: + alerts = self.env['lunch.alert'].search([ + ('partner_id', '=', self.supplier.id) + ]) + if alerts and not any(alert.display for alert in alerts): + # every alert is not available + product.available = False + else: + # no alert for the supplier or at least one is not available + product.available = True + + def _search_available_products(self, operator, value): + alerts = self.env['lunch.alert'].search([]) + supplier_w_alerts = alerts.mapped('partner_id') + available_suppliers = alerts.filtered(lambda a: a.display).mapped('partner_id') + available_products = self.search([ + '|', + ('supplier', 'not in', supplier_w_alerts.ids), + ('supplier', 'in', available_suppliers.ids) + ]) + + if (operator in expression.NEGATIVE_TERM_OPERATORS and value) or \ + (operator not in expression.NEGATIVE_TERM_OPERATORS and not value): + # e.g. (available = False) or (available != True) + return [('id', 'not in', available_products.ids)] + else: + # e.g. (available = True) or (available != False) + return [('id', 'in', available_products.ids)] class LunchProductCategory(models.Model): @@ -270,6 +329,7 @@ class LunchAlert(models.Model): given day, weekly or daily. The alert is displayed from start to end hour. """ _name = 'lunch.alert' _description = 'Lunch Alert' + _rec_name = 'message' display = fields.Boolean(compute='_compute_display_get') message = fields.Text('Message', required=True) @@ -277,6 +337,8 @@ class LunchAlert(models.Model): ('week', 'Every Week'), ('days', 'Every Day')], string='Recurrence', required=True, index=True, default='specific') + partner_id = fields.Many2one('res.partner', string="Vendor", + help="If specified, the selected vendor can be ordered only on selected days") specific_day = fields.Date('Day', default=fields.Date.context_today) monday = fields.Boolean('Monday') tuesday = fields.Boolean('Tuesday') @@ -293,7 +355,8 @@ class LunchAlert(models.Model): def name_get(self): return [(alert.id, '%s %s' % (_('Alert'), '#%d' % alert.id)) for alert in self] - @api.one + @api.depends('alert_type', 'specific_day', 'monday', 'tuesday', 'thursday', + 'friday', 'saturday', 'sunday', 'start_hour', 'end_hour') def _compute_display_get(self): """ This method check if the alert can be displayed today @@ -302,7 +365,6 @@ class LunchAlert(models.Model): if alert type is day : True return : Message if can_display_alert is True else False """ - days_codes = {'0': 'sunday', '1': 'monday', '2': 'tuesday', @@ -310,22 +372,32 @@ class LunchAlert(models.Model): '4': 'thursday', '5': 'friday', '6': 'saturday'} - can_display_alert = { - 'specific': (str(self.specific_day) == fields.Date.context_today(self)), - 'week': self[days_codes[datetime.datetime.now().strftime('%w')]], - 'days': True - } + fullday = False + now = datetime.datetime.now() + if self.env.context.get('lunch_date'): + # lunch_date is a fields.Date -> 00:00:00 + lunch_date = fields.Datetime.from_string(self.env.context['lunch_date']) + # if lunch_date is in the future, planned lunch, ignore hours + fullday = lunch_date > now + now = max(lunch_date, now) + mynow = fields.Datetime.context_timestamp(self, now) - if can_display_alert[self.alert_type]: - mynow = fields.Datetime.context_timestamp(self, datetime.datetime.now()) - hour_to = int(self.end_hour) - min_to = int((self.end_hour - hour_to) * 60) - to_alert = datetime.time(hour_to, min_to) - hour_from = int(self.start_hour) - min_from = int((self.start_hour - hour_from) * 60) - from_alert = datetime.time(hour_from, min_from) + for alert in self: + can_display_alert = { + 'specific': (str(alert.specific_day) == fields.Date.to_string(mynow)), + 'week': alert[days_codes[mynow.strftime('%w')]], + 'days': True + } - if from_alert <= mynow.time() <= to_alert: - self.display = True - else: - self.display = False + if can_display_alert[alert.alert_type]: + hour_to = int(alert.end_hour) + min_to = int((alert.end_hour - hour_to) * 60) + to_alert = datetime.time(hour_to, min_to) + hour_from = int(alert.start_hour) + min_from = int((alert.start_hour - hour_from) * 60) + from_alert = datetime.time(hour_from, min_from) + + if fullday or (from_alert <= mynow.time() <= to_alert): + alert.display = True + else: + alert.display = False diff --git a/addons/lunch/views/lunch_views.xml b/addons/lunch/views/lunch_views.xml index 13582e9bac7..43f5f554515 100644 --- a/addons/lunch/views/lunch_views.xml +++ b/addons/lunch/views/lunch_views.xml @@ -477,7 +477,7 @@ - + @@ -485,7 +485,7 @@
- + @@ -688,6 +688,18 @@ + + + + + +
+ Products from this vendor can not be order at the moment. +
+
+ Products from this vendor can be order at the moment. +
+