diff --git a/addons/lunch/controllers/main.py b/addons/lunch/controllers/main.py index 1394a95da6f..825778d8410 100644 --- a/addons/lunch/controllers/main.py +++ b/addons/lunch/controllers/main.py @@ -18,19 +18,19 @@ class LunchController(http.Controller): lines = self._get_current_lines(user) if lines: + translated_states = dict(request.env['lunch.order']._fields['state']._description_selection(request.env)) lines = [{'id': line.id, 'product': (line.product_id.id, line.product_id.name, float_repr(float_round(line.price, 2), 2)), 'toppings': [(topping.name, float_repr(float_round(topping.price, 2), 2)) for topping in line.topping_ids_1 | line.topping_ids_2 | line.topping_ids_3], 'quantity': line.quantity, 'price': line.price, - 'state': line.state, # Only used for _get_state - 'note': line.note} for line in lines] - raw_state, state = self._get_state(lines) + 'raw_state': line.state, + 'state': translated_states[line.state], + 'note': line.note} for line in lines.sorted('date')] infos.update({ 'total': float_repr(float_round(sum(line['price'] for line in lines), 2), 2), - 'raw_state': raw_state, - 'state': state, + 'raw_state': self._get_state(lines), 'lines': lines, }) return infos @@ -41,6 +41,7 @@ class LunchController(http.Controller): user = request.env['res.users'].browse(user_id) if user_id else request.env.user lines = self._get_current_lines(user) + lines = lines.filtered_domain([('state', 'not in', ['sent', 'confirmed'])]) lines.action_cancel() lines.unlink() @@ -134,10 +135,7 @@ class LunchController(http.Controller): eg: [confirmed, confirmed, new] will return ('new', 'To Order') """ - states_to_int = {'new': 0, 'ordered': 1, 'confirmed': 2, 'cancelled': 3} - int_to_states = ['new', 'ordered', 'confirmed', 'cancelled'] - translated_states = dict(request.env['lunch.order']._fields['state']._description_selection(request.env)) + states_to_int = {'new': 0, 'ordered': 1, 'sent': 2, 'confirmed': 3, 'cancelled': 4} + int_to_states = ['new', 'ordered', 'sent', 'confirmed', 'cancelled'] - state = int_to_states[min(states_to_int[line['state']] for line in lines)] - - return (state, translated_states[state]) + return int_to_states[min(states_to_int[line['raw_state']] for line in lines)] diff --git a/addons/lunch/models/lunch_order.py b/addons/lunch/models/lunch_order.py index 64e9c6f30e0..4098b7ce344 100644 --- a/addons/lunch/models/lunch_order.py +++ b/addons/lunch/models/lunch_order.py @@ -23,6 +23,8 @@ class LunchOrder(models.Model): default=fields.Date.context_today) supplier_id = fields.Many2one( string='Vendor', related='product_id.supplier_id', store=True, index=True) + available_today = fields.Boolean(related='supplier_id.available_today') + order_deadline_passed = fields.Boolean(related='supplier_id.order_deadline_passed') user_id = fields.Many2one('res.users', 'User', readonly=True, states={'new': [('readonly', False)]}, default=lambda self: self.env.uid) @@ -31,8 +33,9 @@ class LunchOrder(models.Model): price = fields.Monetary('Total Price', compute='_compute_total_price', readonly=True, store=True) active = fields.Boolean('Active', default=True) state = fields.Selection([('new', 'To Order'), - ('ordered', 'Ordered'), - ('confirmed', 'Received'), + ('ordered', 'Ordered'), # "Internally" ordered + ('sent', 'Sent'), # Order sent to the supplier + ('confirmed', 'Received'), # Order received ('cancelled', 'Cancelled')], 'Status', readonly=True, index=True, default='new') notified = fields.Boolean(default=False) @@ -75,7 +78,7 @@ class LunchOrder(models.Model): def _compute_display_reorder_button(self): show_button = self.env.context.get('show_reorder_button') for order in self: - order.display_reorder_button = show_button and order.state == 'confirmed' + order.display_reorder_button = show_button and order.state == 'confirmed' and order.supplier_id.available_today def init(self): self._cr.execute("""CREATE INDEX IF NOT EXISTS lunch_order_user_product_date ON %s (user_id, product_id, date)""" @@ -124,7 +127,7 @@ class LunchOrder(models.Model): **vals, 'toppings': self._extract_toppings(vals), }) - if lines: + if lines.filtered(lambda l: l.state not in ['sent', 'confirmed']): # YTI FIXME This will update multiple lines in the case there are multiple # matching lines which should not happen through the interface lines.update_quantity(1) @@ -187,7 +190,7 @@ class LunchOrder(models.Model): line.display_toppings = ' + '.join(toppings.mapped('name')) def update_quantity(self, increment): - for line in self.filtered(lambda line: line.state != 'confirmed'): + for line in self.filtered(lambda line: line.state not in ['sent', 'confirmed']): if line.quantity <= -increment: # TODO: maybe unlink the order? line.active = False @@ -242,6 +245,9 @@ class LunchOrder(models.Model): def action_reset(self): self.write({'state': 'ordered'}) + def action_send(self): + self.state = 'sent' + def action_notify(self): self -= self.filtered('notified') if not self: diff --git a/addons/lunch/models/lunch_supplier.py b/addons/lunch/models/lunch_supplier.py index 8e615f49a5e..a07df0a1ad4 100644 --- a/addons/lunch/models/lunch_supplier.py +++ b/addons/lunch/models/lunch_supplier.py @@ -3,10 +3,11 @@ import math import pytz +from collections import defaultdict from datetime import datetime, time, timedelta from textwrap import dedent -from odoo import api, fields, models +from odoo import api, fields, models, _ from odoo.osv import expression from odoo.tools import float_round @@ -72,6 +73,7 @@ class LunchSupplier(models.Model): available_location_ids = fields.Many2many('lunch.location', string='Location') available_today = fields.Boolean('This is True when if the supplier is available today', compute='_compute_available_today', search='_search_available_today') + order_deadline_passed = fields.Boolean(compute='_compute_order_deadline_passed') tz = fields.Selection(_tz_get, string='Timezone', required=True, default=lambda self: self.env.user.tz or 'UTC') @@ -106,6 +108,9 @@ class LunchSupplier(models.Model): ('1_more', 'One or More'), ('1', 'Only One')], 'Extra 3 Quantity', default='0_more', required=True) + show_order_button = fields.Boolean(compute='_compute_buttons') + show_confirm_button = fields.Boolean(compute='_compute_buttons') + _sql_constraints = [ ('automatic_email_time_range', 'CHECK(automatic_email_time >= 0 AND automatic_email_time <= 12)', @@ -215,6 +220,19 @@ class LunchSupplier(models.Model): Product.search([('supplier_id', 'in', inactive_suppliers.ids)]).write({'active': False}) return res + def _get_current_orders(self, state='ordered'): + """ Returns today's orders """ + available_today = self.filtered('available_today') + if not available_today: + return self.env['lunch.order'] + + orders = self.env['lunch.order'].search([ + ('supplier_id', 'in', available_today.ids), + ('state', '=', state), + ('date', '=', fields.Date.context_today(self.with_context(tz=self.tz))), + ], order="user_id, name") + return orders + def _send_auto_email(self): """ Send an email to the supplier with the order of the day """ # Called daily by cron @@ -226,11 +244,7 @@ class LunchSupplier(models.Model): if self.send_by != 'mail': raise ValueError("Cannot send an email to this supplier") - orders = self.env['lunch.order'].search([ - ('supplier_id', '=', self.id), - ('state', '=', 'ordered'), - ('date', '=', fields.Date.context_today(self.with_context(tz=self.tz))), - ], order="user_id, name") + orders = self._get_current_orders() if not orders: return @@ -265,7 +279,7 @@ class LunchSupplier(models.Model): order=order, lines=email_orders, sites=email_sites ).send_mail(self.id) - orders.action_confirm() + orders.action_send() @api.depends('recurrency_end_date', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun') def _compute_available_today(self): @@ -274,11 +288,27 @@ class LunchSupplier(models.Model): for supplier in self: now = now.astimezone(pytz.timezone(supplier.tz)) - if supplier.recurrency_end_date and now.date() >= supplier.recurrency_end_date: - supplier.available_today = False + supplier.available_today = supplier._available_on_date(now) + + def _available_on_date(self, date): + self.ensure_one() + + fieldname = WEEKDAY_TO_NAME[date.weekday()] + return not (self.recurrency_end_date and date.date() >= self.recurrency_end_date) and self[fieldname] + + @api.depends('available_today', 'automatic_email_time', 'send_by') + def _compute_order_deadline_passed(self): + now = fields.Datetime.now().replace(tzinfo=pytz.UTC) + + for supplier in self: + if supplier.send_by == 'mail': + now = now.astimezone(pytz.timezone(supplier.tz)) + email_time = pytz.timezone(supplier.tz).localize(datetime.combine( + fields.Date.context_today(supplier), + float_to_time(supplier.automatic_email_time, supplier.moment))) + supplier.order_deadline_passed = supplier.available_today and now > email_time else: - fieldname = WEEKDAY_TO_NAME[now.weekday()] - supplier.available_today = supplier[fieldname] + supplier.order_deadline_passed = not supplier.available_today def _search_available_today(self, operator, value): if (not operator in ['=', '!=']) or (not value in [True, False]): @@ -298,3 +328,52 @@ class LunchSupplier(models.Model): recurrency_domain, [(fieldname, operator, value)] ]) + + def _compute_buttons(self): + self.env.cr.execute(""" + SELECT supplier_id, state, COUNT(*) + FROM lunch_order + WHERE supplier_id IN %s + AND state in ('ordered', 'sent') + AND date = %s + AND active + GROUP BY supplier_id, state + """, (tuple(self.ids), fields.Date.context_today(self))) + supplier_orders = defaultdict(dict) + for order in self.env.cr.fetchall(): + supplier_orders[order[0]][order[1]] = order[2] + for supplier in self: + supplier.show_order_button = supplier_orders[supplier.id].get('ordered', False) + supplier.show_confirm_button = supplier_orders[supplier.id].get('sent', False) + + def action_send_orders(self): + no_auto_mail = self.filtered(lambda s: s.send_by != 'mail') + + for supplier in self - no_auto_mail: + supplier._send_auto_email() + orders = no_auto_mail._get_current_orders() + orders.action_send() + + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'type': 'success', + 'message': _('The orders have been sent!'), + 'next': {'type': 'ir.actions.act_window_close'}, + } + } + + def action_confirm_orders(self): + orders = self._get_current_orders(state='sent') + orders.action_confirm() + + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'type': 'success', + 'message': _('The orders have been confirmed!'), + 'next': {'type': 'ir.actions.act_window_close'}, + } + } diff --git a/addons/lunch/populate/lunch.py b/addons/lunch/populate/lunch.py index 9686084e913..7e748dce3e3 100644 --- a/addons/lunch/populate/lunch.py +++ b/addons/lunch/populate/lunch.py @@ -127,6 +127,7 @@ class LunchOrder(models.Model): ('note', populate.constant('lunch_note_{counter}')), ('company_id', populate.randomize(company_ids)), ('quantity', populate.randint(0, 10)), + ('date', populate.randdatetime(relative_before=relativedelta(months=-3), relative_after=relativedelta(months=3))), ] diff --git a/addons/lunch/static/src/xml/lunch_templates.xml b/addons/lunch/static/src/xml/lunch_templates.xml index 264d21b0537..abbca8e6c27 100644 --- a/addons/lunch/static/src/xml/lunch_templates.xml +++ b/addons/lunch/static/src/xml/lunch_templates.xml @@ -30,28 +30,26 @@