[MERGE] lunch: Improve perf + general UX (back2basics)

closes odoo/odoo#73475

Taskid: 2586326
Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
This commit is contained in:
Odoo's Mergebot
2021-07-16 13:17:16 +02:00
committed by GitHub
22 changed files with 368 additions and 363 deletions
+34 -1
View File
@@ -13,6 +13,7 @@
<field name="street">975 Bullock Orchard</field>
<field name="zip">02155</field>
<field name="email">cynthiasanchez@gmail.com</field>
<field name="company_id" ref="base.main_company"/>
</record>
<record id="product_bacon_0" model="lunch.product">
@@ -22,6 +23,7 @@
<field name="supplier_id" ref="supplier_hungry_dog"/>
<field name="description">Beef, Bacon, Salad, Cheddar, Fried Onion, BBQ Sauce</field>
<field name="image_1920" type="base64" file="lunch/static/img/bacon_burger.png"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record id="product_cheese_burger_0" model="lunch.product">
@@ -31,6 +33,7 @@
<field name="supplier_id" ref="supplier_hungry_dog"/>
<field name="description">Beef, Cheddar, Salad, Fried Onions, BBQ Sauce</field>
<field name="image_1920" type="base64" file="lunch/static/img/cheeseburger.png"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record id="product_club_0" model="lunch.product">
@@ -40,6 +43,7 @@
<field name="supplier_id" ref="supplier_hungry_dog"/>
<field name="description">Ham, Cheese, Vegetables</field>
<field name="image_1920" type="base64" file="lunch/static/img/club.png"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record id="product_coke_0" model="lunch.product">
@@ -47,6 +51,7 @@
<field name="category_id" ref="categ_drinks"/>
<field name="price">2.9</field>
<field name="supplier_id" ref="supplier_hungry_dog"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record id="product_pizza_0" model="lunch.product">
@@ -55,6 +60,7 @@
<field name="price">6.90</field>
<field name="supplier_id" ref="supplier_hungry_dog"/>
<field name="description">Tomatoes, Mozzarella</field>
<field name="company_id" ref="base.main_company"/>
</record>
@@ -174,6 +180,7 @@
<field name="description">Beef, Bacon, Salad, Cheddar, Fried Onion, BBQ Sauce</field>
<field name="image_1920" type="base64" file="lunch/static/img/bacon_burger.png"/>
<field name="new_until" eval="datetime.today() + relativedelta(weeks=1)"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record model="lunch.product" id="product_cheeseburger">
@@ -183,6 +190,7 @@
<field name="supplier_id" ref="supplier_corner"/>
<field name="description">Beef, Cheddar, Salad, Fried Onions, BBQ Sauce</field>
<field name="image_1920" type="base64" file="lunch/static/img/cheeseburger.png"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record model="lunch.product" id="product_chicken_curry">
@@ -191,6 +199,7 @@
<field name="price">3.0</field>
<field name="supplier_id" ref="supplier_corner"/>
<field name="image_1920" type="base64" file="lunch/static/img/chicken_curry.png"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record model="lunch.product" id="product_spicy_tuna">
@@ -199,6 +208,7 @@
<field name="price">3.0</field>
<field name="supplier_id" ref="supplier_corner"/>
<field name="image_1920" type="base64" file="lunch/static/img/chicken_curry.png"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record model="lunch.product" id="product_mozzarella">
@@ -208,6 +218,7 @@
<field name="supplier_id" ref="supplier_corner"/>
<field name="description">Mozzarella, Pesto, Tomatoes</field>
<field name="image_1920" type="base64" file="lunch/static/img/mozza.png"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record model="lunch.product" id="product_club">
@@ -217,6 +228,7 @@
<field name="supplier_id" ref="supplier_corner"/>
<field name="description">Ham, Cheese, Vegetables</field>
<field name="image_1920" type="base64" file="lunch/static/img/club.png"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record model="lunch.product" id="product_maki">
@@ -227,6 +239,7 @@
<field name="description">6 Maki Salmon - 6 Maki Tuna - 6 Maki Shrimp/Avocado</field>
<field name="image_1920" type="base64" file="lunch/static/img/maki.png"/>
<field name="new_until" eval="datetime.today() + relativedelta(weeks=1)"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record model="lunch.product" id="product_salmon">
@@ -237,6 +250,7 @@
<field name="description">4 Sushi Salmon - 6 Maki Salmon - 4 Sashimi Salmon </field>
<field name="image_1920" type="base64" file="lunch/static/img/salmon_sushi.png"/>
<field name="new_until" eval="datetime.today() + relativedelta(weeks=1)"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record model="lunch.product" id="product_temaki">
@@ -247,6 +261,7 @@
<field name="description">1 Avocado - 1 Salmon - 1 Eggs - 1 Tuna</field>
<field name="image_1920" type="base64" file="lunch/static/img/temaki.png"/>
<field name="new_until" eval="datetime.today() + relativedelta(weeks=1)"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record model="lunch.product" id="product_chirashi">
@@ -257,6 +272,7 @@
<field name="description">2 Tempuras, Cabbages, Onions, Sesame Sauce</field>
<field name="image_1920" type="base64" file="lunch/static/img/chirashi.png"/>
<field name="new_until" eval="datetime.today() + relativedelta(weeks=1)"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record model="lunch.product" id="product_cheese_ham">
@@ -266,6 +282,7 @@
<field name="supplier_id" ref="supplier_coin_gourmand"/>
<field name="description">Cheese, Ham, Salad, Tomatoes, cucumbers, eggs</field>
<field name="image_1920" type="base64" file="lunch/static/img/club.png"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record model="lunch.product" id="product_country">
@@ -275,6 +292,7 @@
<field name="supplier_id" ref="supplier_coin_gourmand"/>
<field name="description">Brie, Honey, Walnut Kernels</field>
<field name="image_1920" type="base64" file="lunch/static/img/brie.png"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record model="lunch.product" id="product_tuna">
@@ -284,6 +302,7 @@
<field name="supplier_id" ref="supplier_coin_gourmand"/>
<field name="description">Tuna, Mayonnaise</field>
<field name="image_1920" type="base64" file="lunch/static/img/tuna_sandwich.png"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record model="lunch.product" id="product_gouda">
@@ -293,6 +312,7 @@
<field name="supplier_id" ref="supplier_coin_gourmand"/>
<field name="description"></field>
<field name="image_1920" type="base64" file="lunch/static/img/gouda.png"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record model="lunch.product" id="product_chicken_curry">
@@ -302,6 +322,7 @@
<field name="supplier_id" ref="supplier_coin_gourmand"/>
<field name="description"></field>
<field name="image_1920" type="base64" file="lunch/static/img/chicken_curry.png"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record model="lunch.product" id="product_margherita">
@@ -311,6 +332,7 @@
<field name="supplier_id" ref="supplier_pizza_inn"/>
<field name="description">Tomatoes, Mozzarella</field>
<field name="image_1920" type="base64" file="lunch/static/img/pizza_margherita.png"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record model="lunch.product" id="product_funghi">
@@ -320,6 +342,7 @@
<field name="supplier_id" ref="supplier_pizza_inn"/>
<field name="description">Tomatoes, Mushrooms, Mozzarella</field>
<field name="image_1920" type="base64" file="lunch/static/img/pizza_funghi.png"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record model="lunch.product" id="product_vege">
@@ -329,6 +352,7 @@
<field name="supplier_id" ref="supplier_pizza_inn"/>
<field name="description">Tomatoes, Mozzarella, Mushrooms, Peppers, Olives</field>
<field name="image_1920" type="base64" file="lunch/static/img/pizza_veggie.png"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record model="lunch.product" id="product_italiana">
@@ -338,6 +362,7 @@
<field name="supplier_id" ref="supplier_pizza_inn"/>
<field name="description">Fresh Tomatoes, Basil, Mozzarella</field>
<field name="image_1920" type="base64" file="lunch/static/img/italiana.png"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record model="lunch.product" id="product_Bolognese">
@@ -347,6 +372,7 @@
<field name="supplier_id" ref="supplier_pizza_inn"/>
<field name="description"></field>
<field name="image_1920" type="base64" file="lunch/static/img/pasta_bolognese.png"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record model="lunch.product" id="product_Napoli">
@@ -356,12 +382,13 @@
<field name="supplier_id" ref="supplier_pizza_inn"/>
<field name="description">Tomatoes, Basil</field>
<field name="image_1920" type="base64" file="lunch/static/img/napoli.png"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record model="lunch.topping" id="product_olives">
<field name="name">Olives</field>
<field name="price">0.30</field>
<field name="category_id" ref="categ_pizza"/>
<field name="supplier_id" ref="supplier_pizza_inn"/>
</record>
<record model="lunch.product" id="product_4formaggi">
@@ -371,6 +398,7 @@
<field name="supplier_id" ref="supplier_pizza_inn"/>
<field name="description">Tomato sauce, Olive oil, Fresh Tomatoes, Onions, Vegetables, Parmesan</field>
<field name="image_1920" type="base64" file="lunch/static/img/4formaggio.png"/>
<field name="company_id" ref="base.main_company"/>
</record>
<record model="lunch.cashmove" id="cashmove_1">
@@ -395,6 +423,7 @@
<field name="state">new</field>
<field name="supplier_id" ref="supplier_pizza_inn"/>
<field name="quantity">1</field>
<field name="lunch_location_id" ref="location_office_3"/>
</record>
<record model="lunch.order" id="order_line_2">
@@ -405,6 +434,7 @@
<field name="state">confirmed</field>
<field name="supplier_id" ref="supplier_pizza_inn"/>
<field name="quantity">1</field>
<field name="lunch_location_id" ref="location_office_3"/>
</record>
<record model="lunch.order" id="order_line_3">
@@ -415,6 +445,7 @@
<field name="state">cancelled</field>
<field name="supplier_id" ref="supplier_coin_gourmand"/>
<field name="quantity">1</field>
<field name="lunch_location_id" ref="location_office_3"/>
</record>
<record model="lunch.order" id="order_line_4">
@@ -425,6 +456,7 @@
<field name="state">confirmed</field>
<field name="supplier_id" ref="supplier_coin_gourmand"/>
<field name="quantity">1</field>
<field name="lunch_location_id" ref="location_office_3"/>
</record>
<record model="lunch.order" id="order_line_5">
@@ -434,6 +466,7 @@
<field name="date" eval="(DateTime.now() + timedelta(days=3)).strftime('%Y-%m-%d')"/>
<field name="state">confirmed</field>
<field name="supplier_id" ref="supplier_pizza_inn"/>
<field name="lunch_location_id" ref="location_office_2"/>
</record>
</data>
</odoo>
+2
View File
@@ -6,6 +6,8 @@ from . import lunch_cashmove
from . import lunch_location
from . import lunch_order
from . import lunch_product
from . import lunch_product_category
from . import lunch_topping
from . import lunch_supplier
from . import res_company
from . import res_config_settings
+41 -18
View File
@@ -2,7 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
from odoo.exceptions import ValidationError, UserError
class LunchOrder(models.Model):
@@ -11,7 +11,7 @@ class LunchOrder(models.Model):
_order = 'id desc'
_display_name = 'product_id'
name = fields.Char(related='product_id.name', string="Product Name", readonly=True) # to remove
name = fields.Char(related='product_id.name', string="Product Name", store=True, readonly=True)
topping_ids_1 = fields.Many2many('lunch.topping', 'lunch_order_topping', 'order_id', 'topping_id', string='Extras 1', domain=[('topping_category', '=', 1)])
topping_ids_2 = fields.Many2many('lunch.topping', 'lunch_order_topping', 'order_id', 'topping_id', string='Extras 2', domain=[('topping_category', '=', 2)])
topping_ids_3 = fields.Many2many('lunch.topping', 'lunch_order_topping', 'order_id', 'topping_id', string='Extras 3', domain=[('topping_category', '=', 3)])
@@ -26,9 +26,9 @@ class LunchOrder(models.Model):
user_id = fields.Many2one('res.users', 'User', readonly=True,
states={'new': [('readonly', False)]},
default=lambda self: self.env.uid)
lunch_location_id = fields.Many2one('lunch.location', default=lambda self: self.env.user.last_lunch_location_id)
note = fields.Text('Notes')
price = fields.Float('Total Price', compute='_compute_total_price', readonly=True, store=True,
digits='Account')
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'),
@@ -42,18 +42,19 @@ class LunchOrder(models.Model):
display_toppings = fields.Text('Extras', compute='_compute_display_toppings', store=True)
product_description = fields.Html('Description', related='product_id.description')
topping_label_1 = fields.Char(related='product_id.category_id.topping_label_1')
topping_label_2 = fields.Char(related='product_id.category_id.topping_label_2')
topping_label_3 = fields.Char(related='product_id.category_id.topping_label_3')
topping_quantity_1 = fields.Selection(related='product_id.category_id.topping_quantity_1')
topping_quantity_2 = fields.Selection(related='product_id.category_id.topping_quantity_2')
topping_quantity_3 = fields.Selection(related='product_id.category_id.topping_quantity_3')
topping_label_1 = fields.Char(related='product_id.supplier_id.topping_label_1')
topping_label_2 = fields.Char(related='product_id.supplier_id.topping_label_2')
topping_label_3 = fields.Char(related='product_id.supplier_id.topping_label_3')
topping_quantity_1 = fields.Selection(related='product_id.supplier_id.topping_quantity_1')
topping_quantity_2 = fields.Selection(related='product_id.supplier_id.topping_quantity_2')
topping_quantity_3 = fields.Selection(related='product_id.supplier_id.topping_quantity_3')
image_1920 = fields.Image(compute='_compute_product_images')
image_128 = fields.Image(compute='_compute_product_images')
available_toppings_1 = fields.Boolean(help='Are extras available for this product', compute='_compute_available_toppings')
available_toppings_2 = fields.Boolean(help='Are extras available for this product', compute='_compute_available_toppings')
available_toppings_3 = fields.Boolean(help='Are extras available for this product', compute='_compute_available_toppings')
display_reorder_button = fields.Boolean(compute='_compute_display_reorder_button')
@api.depends('product_id')
def _compute_product_images(self):
@@ -63,10 +64,17 @@ class LunchOrder(models.Model):
@api.depends('category_id')
def _compute_available_toppings(self):
for line in self:
line.available_toppings_1 = bool(line.env['lunch.topping'].search_count([('category_id', '=', line.category_id.id), ('topping_category', '=', 1)]))
line.available_toppings_2 = bool(line.env['lunch.topping'].search_count([('category_id', '=', line.category_id.id), ('topping_category', '=', 2)]))
line.available_toppings_3 = bool(line.env['lunch.topping'].search_count([('category_id', '=', line.category_id.id), ('topping_category', '=', 3)]))
for order in self:
order.available_toppings_1 = bool(order.env['lunch.topping'].search_count([('supplier_id', '=', order.supplier_id.id), ('topping_category', '=', 1)]))
order.available_toppings_2 = bool(order.env['lunch.topping'].search_count([('supplier_id', '=', order.supplier_id.id), ('topping_category', '=', 2)]))
order.available_toppings_3 = bool(order.env['lunch.topping'].search_count([('supplier_id', '=', order.supplier_id.id), ('topping_category', '=', 3)]))
@api.depends_context('show_reorder_button')
@api.depends('state')
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'
def init(self):
self._cr.execute("""CREATE INDEX IF NOT EXISTS lunch_order_user_product_date ON %s (user_id, product_id, date)"""
@@ -142,9 +150,6 @@ class LunchOrder(models.Model):
})
if matching_lines:
lines_to_deactivate |= line
# YTI TODO Try to batch it, be careful there might be multiple matching
# lines for the same order hence quantity should not always be
# line.quantity, but rather a sum
matching_lines.update_quantity(line.quantity)
lines_to_deactivate.write({'active': False})
return super(LunchOrder, self - lines_to_deactivate).write(values)
@@ -198,11 +203,29 @@ class LunchOrder(models.Model):
def action_order(self):
if self.filtered(lambda line: not line.product_id.active):
raise ValidationError(_('Product is no longer available.'))
self.write({'state': 'ordered'})
self.write({
'state': 'ordered',
})
for order in self:
order.lunch_location_id = order.user_id.last_lunch_location_id
self._check_wallet()
def action_reorder(self):
self.ensure_one()
if not self.supplier_id.available_today:
raise UserError(_('The vendor related to this order is not available today.'))
self.copy({
'date': fields.Date.context_today(self),
'state': 'ordered',
})
action = self.env['ir.actions.act_window']._for_xml_id('lunch.lunch_order_action')
return action
def action_confirm(self):
self.write({'state': 'confirmed'})
def action_cancel(self):
self.write({'state': 'cancelled'})
def action_reset(self):
self.write({'state': 'ordered'})
+87 -96
View File
@@ -1,101 +1,13 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
from collections import defaultdict
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.modules.module import get_module_resource
from odoo.tools import formatLang
class LunchProductCategory(models.Model):
""" Category of the product such as pizza, sandwich, pasta, chinese, burger... """
_name = 'lunch.product.category'
_inherit = 'image.mixin'
_description = 'Lunch Product Category'
@api.model
def _default_image(self):
image_path = get_module_resource('lunch', 'static/img', 'lunch.png')
return base64.b64encode(open(image_path, 'rb').read())
name = fields.Char('Product Category', required=True, translate=True)
company_id = fields.Many2one('res.company')
currency_id = fields.Many2one('res.currency', related='company_id.currency_id')
topping_label_1 = fields.Char('Extra 1 Label', required=True, default='Extras')
topping_label_2 = fields.Char('Extra 2 Label', required=True, default='Beverages')
topping_label_3 = fields.Char('Extra 3 Label', required=True, default='Extra Label 3')
topping_ids_1 = fields.One2many('lunch.topping', 'category_id', domain=[('topping_category', '=', 1)])
topping_ids_2 = fields.One2many('lunch.topping', 'category_id', domain=[('topping_category', '=', 2)])
topping_ids_3 = fields.One2many('lunch.topping', 'category_id', domain=[('topping_category', '=', 3)])
topping_quantity_1 = fields.Selection([
('0_more', 'None or More'),
('1_more', 'One or More'),
('1', 'Only One')], 'Extra 1 Quantity', default='0_more', required=True)
topping_quantity_2 = fields.Selection([
('0_more', 'None or More'),
('1_more', 'One or More'),
('1', 'Only One')], 'Extra 2 Quantity', default='0_more', required=True)
topping_quantity_3 = fields.Selection([
('0_more', 'None or More'),
('1_more', 'One or More'),
('1', 'Only One')], 'Extra 3 Quantity', default='0_more', required=True)
product_count = fields.Integer(compute='_compute_product_count', help="The number of products related to this category")
active = fields.Boolean(string='Active', default=True)
image_1920 = fields.Image(default=_default_image)
def _compute_product_count(self):
product_data = self.env['lunch.product'].read_group([('category_id', 'in', self.ids)], ['category_id'], ['category_id'])
data = {product['category_id'][0]: product['category_id_count'] for product in product_data}
for category in self:
category.product_count = data.get(category.id, 0)
@api.model
def create(self, vals):
for topping in vals.get('topping_ids_2', []):
topping[2].update({'topping_category': 2})
for topping in vals.get('topping_ids_3', []):
topping[2].update({'topping_category': 3})
return super(LunchProductCategory, self).create(vals)
def write(self, vals):
for topping in vals.get('topping_ids_2', []):
topping_values = topping[2]
if topping_values:
topping_values.update({'topping_category': 2})
for topping in vals.get('topping_ids_3', []):
topping_values = topping[2]
if topping_values:
topping_values.update({'topping_category': 3})
return super(LunchProductCategory, self).write(vals)
def toggle_active(self):
""" Archiving related lunch product """
res = super().toggle_active()
Product = self.env['lunch.product'].with_context(active_test=False)
all_products = Product.search([('category_id', 'in', self.ids)])
all_products._sync_active_from_related()
return res
class LunchTopping(models.Model):
""""""
_name = 'lunch.topping'
_description = 'Lunch Extras'
name = fields.Char('Name', required=True)
company_id = fields.Many2one('res.company', default=lambda self: self.env.company)
currency_id = fields.Many2one('res.currency', related='company_id.currency_id')
price = fields.Float('Price', digits='Account', required=True)
category_id = fields.Many2one('lunch.product.category', ondelete='cascade')
topping_category = fields.Integer('Topping Category', help="This field is a technical field", required=True, default=1)
def name_get(self):
currency_id = self.env.company.currency_id
res = dict(super(LunchTopping, self).name_get())
for topping in self:
price = formatLang(self.env, topping.price, currency_obj=currency_id)
res[topping.id] = '%s %s' % (topping.name, price)
return list(res.items())
from odoo.osv import expression
class LunchProduct(models.Model):
@@ -117,15 +29,94 @@ class LunchProduct(models.Model):
currency_id = fields.Many2one('res.currency', related='company_id.currency_id')
new_until = fields.Date('New Until')
is_new = fields.Boolean(compute='_compute_is_new')
favorite_user_ids = fields.Many2many('res.users', 'lunch_product_favorite_user_rel', 'product_id', 'user_id', check_company=True)
is_favorite = fields.Boolean(compute='_compute_is_favorite')
last_order_date = fields.Date(compute='_compute_last_order_date')
product_image = fields.Image(compute='_compute_product_image')
# This field is used only for searching
is_available_at = fields.Many2one('lunch.location', 'Product Availability', compute='_compute_is_available_at', search='_search_is_available_at')
@api.depends('image_128', 'category_id.image_128')
def _compute_product_image(self):
for product in self:
product.product_image = product.image_128 or product.category_id.image_128
@api.depends('new_until')
def _compute_is_new(self):
today = fields.Date.context_today(self)
for product in self:
if product.new_until:
product.is_new = today <= product.new_until
else:
product.is_new = False
@api.depends_context('uid')
@api.depends('favorite_user_ids')
def _compute_is_favorite(self):
for product in self:
product.is_favorite = self.env.user in self.favorite_user_ids
@api.depends_context('uid')
def _compute_last_order_date(self):
all_orders = self.env['lunch.order'].search([
('user_id', '=', self.env.user.id),
('product_id', 'in', self.ids),
])
mapped_orders = defaultdict(lambda: self.env['lunch.order'])
for order in all_orders:
mapped_orders[order.product_id] |= order
for product in self:
if not mapped_orders[product]:
product.last_order_date = False
else:
product.last_order_date = max(mapped_orders[product].mapped('date'))
def _compute_is_available_at(self):
"""
Is available_at is always false when browsing it
this field is there only to search (see _search_is_available_at)
"""
for product in self:
product.is_available_at = False
def _search_is_available_at(self, operator, value):
supported_operators = ['in', 'not in', '=', '!=']
if not operator in supported_operators:
return expression.TRUE_DOMAIN
if isinstance(value, int):
value = [value]
if operator in expression.NEGATIVE_TERM_OPERATORS:
return expression.AND([[('supplier_id.available_location_ids', 'not in', value)], [('supplier_id.available_location_ids', '!=', False)]])
return expression.OR([[('supplier_id.available_location_ids', 'in', value)], [('supplier_id.available_location_ids', '=', False)]])
def _sync_active_from_related(self):
""" Archive/unarchive product after related field is archived/unarchived """
return self.filtered(lambda p: (p.category_id.active and p.supplier_id.active) != p.active).toggle_active()
def toggle_active(self):
if self.filtered(lambda product: not product.active and not product.category_id.active):
raise UserError(_("The product category is archived. The user have to unarchive the category or change the category of the product."))
if self.filtered(lambda product: not product.active and not product.supplier_id.active):
raise UserError(_("The product supplier is archived. The user have to unarchive the supplier or change the supplier of the product."))
invalid_products = self.filtered(lambda product: not product.active and not product.category_id.active)
if invalid_products:
raise UserError(_("The following product categories are archived. You should either unarchive the categories or change the category of the product.\n%s", '\n'.join(invalid_products.category_id.mapped('name'))))
invalid_products = self.filtered(lambda product: not product.active and not product.supplier_id.active)
if invalid_products:
raise UserError(_("The following suppliers are archived. You should either unarchive the suppliers or change the supplier of the product.\n%s", '\n'.join(invalid_products.supplier_id.mapped('name'))))
return super().toggle_active()
def write(self, values):
if 'is_favorite' in values:
if values['is_favorite']:
commands = [(4, product.id) for product in self]
else:
commands = [(3, product.id) for product in self]
self.env.user.write({
'favorite_lunch_product_ids': commands,
})
return super().write(values)
@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
from odoo import api, fields, models
from odoo.modules.module import get_module_resource
class LunchProductCategory(models.Model):
""" Category of the product such as pizza, sandwich, pasta, chinese, burger... """
_name = 'lunch.product.category'
_inherit = 'image.mixin'
_description = 'Lunch Product Category'
@api.model
def _default_image(self):
image_path = get_module_resource('lunch', 'static/img', 'lunch.png')
return base64.b64encode(open(image_path, 'rb').read())
name = fields.Char('Product Category', required=True, translate=True)
company_id = fields.Many2one('res.company')
currency_id = fields.Many2one('res.currency', related='company_id.currency_id')
product_count = fields.Integer(compute='_compute_product_count', help="The number of products related to this category")
active = fields.Boolean(string='Active', default=True)
image_1920 = fields.Image(default=_default_image)
def _compute_product_count(self):
product_data = self.env['lunch.product'].read_group([('category_id', 'in', self.ids)], ['category_id'], ['category_id'])
data = {product['category_id'][0]: product['category_id_count'] for product in product_data}
for category in self:
category.product_count = data.get(category.id, 0)
def toggle_active(self):
""" Archiving related lunch product """
res = super().toggle_active()
Product = self.env['lunch.product'].with_context(active_test=False)
all_products = Product.search([('category_id', 'in', self.ids)])
all_products._sync_active_from_related()
return res
+39 -3
View File
@@ -90,6 +90,25 @@ class LunchSupplier(models.Model):
('no_delivery', 'No Delivery')
], default='no_delivery')
topping_label_1 = fields.Char('Extra 1 Label', required=True, default='Extras')
topping_label_2 = fields.Char('Extra 2 Label', required=True, default='Beverages')
topping_label_3 = fields.Char('Extra 3 Label', required=True, default='Extra Label 3')
topping_ids_1 = fields.One2many('lunch.topping', 'supplier_id', domain=[('topping_category', '=', 1)])
topping_ids_2 = fields.One2many('lunch.topping', 'supplier_id', domain=[('topping_category', '=', 2)])
topping_ids_3 = fields.One2many('lunch.topping', 'supplier_id', domain=[('topping_category', '=', 3)])
topping_quantity_1 = fields.Selection([
('0_more', 'None or More'),
('1_more', 'One or More'),
('1', 'Only One')], 'Extra 1 Quantity', default='0_more', required=True)
topping_quantity_2 = fields.Selection([
('0_more', 'None or More'),
('1_more', 'One or More'),
('1', 'Only One')], 'Extra 2 Quantity', default='0_more', required=True)
topping_quantity_3 = fields.Selection([
('0_more', 'None or More'),
('1_more', 'One or More'),
('1', 'Only One')], 'Extra 3 Quantity', default='0_more', required=True)
_sql_constraints = [
('automatic_email_time_range',
'CHECK(automatic_email_time >= 0 AND automatic_email_time <= 12)',
@@ -132,6 +151,11 @@ class LunchSupplier(models.Model):
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
for topping in vals.get('topping_ids_2', []):
topping[2].update({'topping_category': 2})
for topping in vals.get('topping_ids_3', []):
topping[2].update({'topping_category': 3})
crons = self.env['ir.cron'].sudo().create([
{
'user_id': self.env.ref('base.user_root').id,
@@ -155,6 +179,16 @@ class LunchSupplier(models.Model):
return suppliers
def write(self, values):
for topping in values.get('topping_ids_2', []):
topping_values = topping[2]
if topping_values:
topping_values.update({'topping_category': 2})
for topping in values.get('topping_ids_3', []):
topping_values = topping[2]
if topping_values:
topping_values.update({'topping_category': 3})
if values.get('company_id'):
self.env['lunch.order'].search([('supplier_id', 'in', self.ids)]).write({'company_id': values['company_id']})
super().write(values)
if not CRON_DEPENDS.isdisjoint(values):
self._sync_cron()
@@ -167,9 +201,11 @@ class LunchSupplier(models.Model):
def toggle_active(self):
""" Archiving related lunch product """
res = super().toggle_active()
active_suppliers = self.filtered(lambda s: s.active)
inactive_suppliers = self - active_suppliers
Product = self.env['lunch.product'].with_context(active_test=False)
all_products = Product.search([('supplier_id', 'in', self.ids)])
all_products._sync_active_from_related()
Product.search([('supplier_id', 'in', active_suppliers.ids)]).write({'active': True})
Product.search([('supplier_id', 'in', inactive_suppliers.ids)]).write({'active': False})
return res
def _send_auto_email(self):
@@ -187,7 +223,7 @@ class LunchSupplier(models.Model):
('supplier_id', '=', self.id),
('state', '=', 'ordered'),
('date', '=', fields.Date.context_today(self.with_context(tz=self.tz))),
])
], order="user_id, name")
if not orders:
return
+26
View File
@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
from odoo.tools import formatLang
class LunchTopping(models.Model):
_name = 'lunch.topping'
_description = 'Lunch Extras'
name = fields.Char('Name', required=True)
company_id = fields.Many2one('res.company', default=lambda self: self.env.company)
currency_id = fields.Many2one('res.currency', related='company_id.currency_id')
price = fields.Monetary('Price', required=True)
supplier_id = fields.Many2one('lunch.supplier', ondelete='cascade')
topping_category = fields.Integer('Topping Category', help="This field is a technical field", required=True, default=1)
def name_get(self):
currency_id = self.env.company.currency_id
res = dict(super(LunchTopping, self).name_get())
for topping in self:
price = formatLang(self.env, topping.price, currency_obj=currency_id)
res[topping.id] = '%s %s' % (topping.name, price)
return list(res.items())
-1
View File
@@ -2,4 +2,3 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import lunch_cashmove_report
from . import lunch_product_report
-111
View File
@@ -1,111 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, tools
from odoo.osv import expression
class LunchProductReport(models.Model):
_name = "lunch.product.report"
_description = 'Product report'
_auto = False
_order = 'is_favorite desc, is_new desc, last_order_date asc, product_id asc'
id = fields.Integer('ID')
product_id = fields.Many2one('lunch.product', 'Product')
name = fields.Char('Product Name', related='product_id.name')
category_id = fields.Many2one('lunch.product.category', 'Product Category')
description = fields.Html('Description', related='product_id.description')
price = fields.Float('Price')
supplier_id = fields.Many2one('lunch.supplier', 'Vendor')
company_id = fields.Many2one('res.company')
currency_id = fields.Many2one('res.currency', related='company_id.currency_id')
is_favorite = fields.Boolean('Favorite')
user_id = fields.Many2one('res.users')
is_new = fields.Boolean('New')
active = fields.Boolean('Active')
last_order_date = fields.Date('Last Order Date')
image_128 = fields.Image(compute="_compute_image_128")
# This field is used only for searching
is_available_at = fields.Many2one('lunch.location', 'Product Availability', compute='_compute_is_available_at', search='_search_is_available_at')
def _compute_image_128(self):
for product_r in self:
product = product_r.product_id
category = product_r.sudo().category_id
if product.image_128:
product_r.image_128 = product.image_128
elif category.image_128:
product_r.image_128 = category.image_128
else:
product_r.image_128 = False
def _compute_concurrency_field(self):
"""Image caching is based on the `__last_update` field (=self.CONCURRENCY_CHECK_FIELD)
But the image is never cached by the browser because the value fallbacks to
`now` when access logging is disabled. This override sets a "real" value based on the
product or category last update.
"""
for report in self:
product_last_update = report.product_id[self.CONCURRENCY_CHECK_FIELD]
category_last_update = report.category_id[self.CONCURRENCY_CHECK_FIELD]
report[self.CONCURRENCY_CHECK_FIELD] = max(product_last_update, category_last_update)
def _compute_is_available_at(self):
"""
Is available_at is always false when browsing it
this field is there only to search (see _search_is_available_at)
"""
for product in self:
product.is_available_at = False
def _search_is_available_at(self, operator, value):
supported_operators = ['in', 'not in', '=', '!=']
if not operator in supported_operators:
return expression.TRUE_DOMAIN
if isinstance(value, int):
value = [value]
if operator in expression.NEGATIVE_TERM_OPERATORS:
return expression.AND([[('supplier_id.available_location_ids', 'not in', value)], [('supplier_id.available_location_ids', '!=', False)]])
return expression.OR([[('supplier_id.available_location_ids', 'in', value)], [('supplier_id.available_location_ids', '=', False)]])
def write(self, values):
if 'is_favorite' in values:
if values['is_favorite']:
commands = [(4, product_id) for product_id in self.mapped('product_id').ids]
else:
commands = [(3, product_id) for product_id in self.mapped('product_id').ids]
self.env.user.write({
'favorite_lunch_product_ids': commands,
})
def init(self):
tools.drop_view_if_exists(self._cr, self._table)
self._cr.execute("""
CREATE or REPLACE view %s AS (
SELECT
row_number() over (ORDER BY users.id,product.id) AS id,
product.id AS product_id,
product.category_id,
product.price,
product.supplier_id,
product.company_id,
product.active,
users.id AS user_id,
fav.user_id IS NOT NULL AS is_favorite,
product.new_until >= current_date AS is_new,
orders.last_order_date
FROM lunch_product product
CROSS JOIN res_users users
INNER JOIN res_groups_users_rel groups ON groups.uid = users.id -- only generate for internal users
LEFT JOIN LATERAL (select max(date) AS last_order_date FROM lunch_order where user_id=users.id and product_id=product.id) AS orders ON TRUE
LEFT JOIN LATERAL (select user_id FROM lunch_product_favorite_user_rel where user_id=users.id and product_id=product.id) AS fav ON TRUE
WHERE users.active AND product.active AND groups.gid = %%s --only take into account active products and users
);
""" % self._table, (self.env.ref('base.group_user').id,))
@@ -17,4 +17,3 @@ lunch_topping_user,access_lunch_topping,model_lunch_topping,group_lunch_user,1,0
lunch_topping_manager,access_lunch_topping,model_lunch_topping,group_lunch_manager,1,1,1,1
lunch_supplier_user,"Lunch Supplier User Rights",model_lunch_supplier,group_lunch_user,1,0,0,0
lunch_supplier_manager,"Lunch Supplier Manager Rights",model_lunch_supplier,group_lunch_manager,1,1,1,1
lunch_product_report_user,access_lunch_product_report,model_lunch_product_report,group_lunch_user,1,1,0,0
1 id name model_id/id group_id/id perm_read perm_write perm_create perm_unlink
17 lunch_topping_manager access_lunch_topping model_lunch_topping group_lunch_manager 1 1 1 1
18 lunch_supplier_user Lunch Supplier User Rights model_lunch_supplier group_lunch_user 1 0 0 0
19 lunch_supplier_manager Lunch Supplier Manager Rights model_lunch_supplier group_lunch_manager 1 1 1 1
lunch_product_report_user access_lunch_product_report model_lunch_product_report group_lunch_user 1 1 0 0
+6 -6
View File
@@ -71,6 +71,12 @@
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
</record>
<record id="ir_rule_lunch_order_multi_company" model="ir.rule">
<field name="name">Lunch order: Multi Company</field>
<field name="model_id" ref="model_lunch_order"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
</record>
<record id="ir_rule_lunch_product_multi_company" model="ir.rule">
<field name="name">Lunch product: Multi Company</field>
<field name="model_id" ref="model_lunch_product"/>
@@ -88,11 +94,5 @@
<field name="model_id" ref="model_lunch_location"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
</record>
<record id="ir_rule_lunch_product_report_multi_company" model="ir.rule">
<field name="name">Lunch product report: Multi Company</field>
<field name="model_id" ref="model_lunch_product_report"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids), '|', ('category_id.company_id', '=', False), ('category_id.company_id', 'in', company_ids)]</field>
</record>
</data>
</odoo>
@@ -140,7 +140,6 @@ var LunchControllerCommon = {
},
};
// YTI TODO Maybe don't always pass the default_product_id
var action = {
res_model: 'lunch.order',
name: _t('Configure Your Order'),
-1
View File
@@ -3,5 +3,4 @@
from . import common
from . import test_alert
from . import test_product_report
from . import test_supplier
+1 -1
View File
@@ -93,7 +93,7 @@ class TestsCommon(common.TransactionCase):
self.topping_olives = self.env['lunch.topping'].create({
'name': 'Olives',
'price': 0.3,
'category_id': self.category_pizza.id,
'supplier_id': self.supplier_pizza_inn.id,
})
self.env['lunch.cashmove'].create({
-21
View File
@@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.lunch.tests.common import TestsCommon
class TestLunchProductReport(TestsCommon):
def test_product_available(self):
self.assertTrue(self.env['lunch.product.report'].search([]), 'There should be some record on lunch_product_report')
def test_order_in_report(self):
pizza = self.env['lunch.product.report'].search([('product_id', '=', self.product_pizza.id)], limit=1)
self.assertEqual(pizza.name, 'Pizza')
pizza = pizza.with_user(pizza.user_id)
pizza.write({'is_favorite': True})
self.assertTrue(pizza.product_id in pizza.user_id.favorite_lunch_product_ids)
new_pizza = self.env['lunch.product.report'].search([('product_id', '=', self.product_pizza.id), ('user_id', '=', pizza.user_id.id)])
self.assertEqual(new_pizza.id, pizza.id)
self.assertEqual(new_pizza.name, 'Pizza')
+12 -6
View File
@@ -86,7 +86,8 @@ env['lunch.supplier'].browse([{self.supplier_kothai.id}])._send_auto_email()""")
with patch.object(fields.Date, 'context_today', return_value=self.monday_1pm.date()) as _:
line = self.env['lunch.order'].create({
'product_id': self.product_pizza.id,
'date': self.monday_1pm.date()
'date': self.monday_1pm.date(),
'supplier_id': self.supplier_pizza_inn.id,
})
line.action_order()
@@ -99,11 +100,13 @@ env['lunch.supplier'].browse([{self.supplier_kothai.id}])._send_auto_email()""")
line = self.env['lunch.order'].create({
'product_id': self.product_pizza.id,
'topping_ids_1': [(6, 0, [self.topping_olives.id])],
'date': self.monday_1pm.date()
'date': self.monday_1pm.date(),
'supplier_id': self.supplier_pizza_inn.id,
})
line2 = self.env['lunch.order'].create({
'product_id': self.product_sandwich_tuna.id,
'date': self.monday_1pm.date()
'date': self.monday_1pm.date(),
'supplier_id': self.supplier_coin_gourmand.id,
})
(line | line2).action_order()
@@ -118,19 +121,22 @@ env['lunch.supplier'].browse([{self.supplier_kothai.id}])._send_auto_email()""")
line_1 = self.env['lunch.order'].create({
'product_id': self.product_pizza.id,
'quantity': 2,
'date': self.monday_1pm.date()
'date': self.monday_1pm.date(),
'supplier_id': self.product_pizza.id,
})
line_2 = self.env['lunch.order'].create({
'product_id': self.product_pizza.id,
'topping_ids_1': [(6, 0, [self.topping_olives.id])],
'date': self.monday_1pm.date()
'date': self.monday_1pm.date(),
'supplier_id': self.product_pizza.id,
})
line_3 = self.env['lunch.order'].create({
'product_id': self.product_sandwich_tuna.id,
'quantity': 2,
'date': self.monday_1pm.date()
'date': self.monday_1pm.date(),
'supplier_id': self.supplier_coin_gourmand.id,
})
(line_1 | line_2 | line_3).action_order()
+14 -7
View File
@@ -35,17 +35,21 @@
<button name="action_confirm" type="object" string="Receive"/>
</header>
<field name='date'/>
<field name='supplier_id' invisible='1'/>
<field name='supplier_id'/>
<field name='product_id'/>
<field name="display_toppings" class="o_text_overflow"/>
<field name='note' class="o_text_overflow"/>
<field name='user_id'/>
<field name="lunch_location_id"/>
<field name="currency_id" invisible="1"/>
<field name='price' sum="Total" string="Price" widget="monetary"/>
<field name='state'/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="display_reorder_button" invisible="1"/>
<button name="action_reorder" string="Re-order" type="object" icon="fa-history" attrs="{'invisible': [('display_reorder_button', '=', False)]}" groups="lunch.group_lunch_user"/>
<button name="action_confirm" string="Confirm" type="object" icon="fa-check" attrs="{'invisible': [('state', '!=', 'ordered')]}" groups="lunch.group_lunch_manager"/>
<button name="action_cancel" string="Cancel" type="object" icon="fa-times" attrs="{'invisible': [('state', 'in', ['cancelled', 'confirmed'])]}" groups="lunch.group_lunch_manager"/>
<button name="action_reset" string="Reset" type="object" icon="fa-undo" attrs="{'invisible': [('state', '!=', 'cancelled')]}" groups="lunch.group_lunch_manager"/>
</tree>
</field>
</record>
@@ -60,7 +64,8 @@
<field name="state"/>
<field name="user_id"/>
<field name="date"/>
<field name="price"/>
<field name="company_id"/>
<field name="currency_id"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click">
@@ -132,7 +137,7 @@
<field name="res_model">lunch.order</field>
<field name="view_mode">tree,kanban,pivot</field>
<field name="search_view_id" ref="lunch_order_view_search"/>
<field name="context">{"search_default_is_mine":1, "search_default_group_by_date": 1}</field>
<field name="context">{"search_default_is_mine":1, "search_default_group_by_date": 1, 'show_reorder_button': True}</field>
<field name="help" type="html">
<p class="o_view_nocontent_empty_folder">
No previous order found
@@ -181,6 +186,7 @@
<field name="model">lunch.order</field>
<field name="arch" type="xml">
<form>
<field name="company_id" invisible="1"/>
<field name="currency_id" invisible="1"/>
<field name="quantity" invisible="1"/>
<field name="product_id" invisible="1"/>
@@ -188,13 +194,14 @@
<field name="available_toppings_1" invisible="1"/>
<field name="available_toppings_2" invisible="1"/>
<field name="available_toppings_3" invisible="1"/>
<field name="supplier_id" invisible="1"/>
<div class="d-flex">
<div class="flex-grow-0 pr-5">
<field name="image_1920" widget="image" class="o_lunch_image" options="{'image_preview': 'image_128'}"/>
</div>
<div class="flex-grow-1 pr-5">
<h2><field name="name"/></h2>
<h3 class="pt-3"><field name="price" widget="monetary"/></h3>
<h3 class="pt-3"><field name="price"/></h3>
</div>
</div>
<div class="o_lunch_wizard">
@@ -203,7 +210,7 @@
<field name="topping_label_1" nolabel="1" attrs="{'invisible': [('available_toppings_1', '=', False)]}" class="font-weight-bold"/>
</div>
<div class="col-10">
<field name="topping_ids_1" attrs="{'invisible': [('available_toppings_1', '=', False)]}" widget="many2many_checkboxes" nolabel="1"/>
<field name="topping_ids_1" attrs="{'invisible': [('available_toppings_1', '=', False)]}" widget="many2many_checkboxes" nolabel="1" domain="[('topping_category', '=', 1), ('supplier_id', '=', supplier_id)]"/>
</div>
</div>
<div class="row">
@@ -211,7 +218,7 @@
<field name="topping_label_2" nolabel="1" attrs="{'invisible': [('available_toppings_2', '=', False)]}" class="font-weight-bold"/>
</div>
<div class="col-10">
<field name="topping_ids_2" attrs="{'invisible': [('available_toppings_2', '=', False)]}" widget="many2many_checkboxes" nolabel="1"/>
<field name="topping_ids_2" attrs="{'invisible': [('available_toppings_2', '=', False)]}" widget="many2many_checkboxes" nolabel="1" domain="[('topping_category', '=', 2), ('supplier_id', '=', supplier_id)]"/>
</div>
</div>
<div class="row">
@@ -219,7 +226,7 @@
<field name="topping_label_3" nolabel="1" attrs="{'invisible': [('available_toppings_3', '=', False)]}" class="font-weight-bold"/>
</div>
<div class="col-10">
<field name="topping_ids_3" attrs="{'invisible': [('available_toppings_3', '=', False)]}" widget="many2many_checkboxes" nolabel="1"/>
<field name="topping_ids_3" attrs="{'invisible': [('available_toppings_3', '=', False)]}" widget="many2many_checkboxes" nolabel="1" domain="[('topping_category', '=', 3), ('supplier_id', '=', supplier_id)]"/>
</div>
</div>
<div class="row">
+16 -85
View File
@@ -10,6 +10,16 @@
<field name="supplier_id"/>
<field name="description"/>
<separator/>
<filter name="available_today" string="Available Today" domain="[('supplier_id.available_today', '=', True)]"/>
<separator/>
<filter name="available_on_mon" string="Monday" domain="[('supplier_id.mon', '=', True)]"/>
<filter name="available_on_tue" string="Tuesday" domain="[('supplier_id.tue', '=', True)]"/>
<filter name="available_on_wed" string="Wednesday" domain="[('supplier_id.wed', '=', True)]"/>
<filter name="available_on_thu" string="Thursday" domain="[('supplier_id.thu', '=', True)]"/>
<filter name="available_on_fri" string="Friday" domain="[('supplier_id.fri', '=', True)]"/>
<filter name="available_on_sat" string="Saturday" domain="[('supplier_id.sat', '=', True)]"/>
<filter name="available_on_sun" string="Sunday" domain="[('supplier_id.sun', '=', True)]"/>
<separator/>
<filter name="inactive" string="Archived" domain="[('active', '=', False)]"/>
<group expand="0" string="Group By">
<filter name="group_by_supplier" string="Vendor" context="{'group_by': 'supplier_id'}"/>
@@ -23,27 +33,6 @@
</field>
</record>
<record id="lunch_product_report_view_search" model="ir.ui.view">
<field name="name">lunch.product.report.search</field>
<field name="model">lunch.product.report</field>
<field name="arch" type="xml">
<search string="Product Search">
<field name="name" string="Product"/>
<field name="category_id" string="Category"/>
<field name="supplier_id"/>
<field name="description"/>
<group expand="0" string="Group By">
<filter name="group_by_supplier" string="Vendor" context="{'group_by': 'supplier_id'}"/>
<filter name="group_by_category" string="Category" context="{'group_by': 'category_id'}"/>
</group>
<searchpanel>
<field name="category_id" select="multi" string="Categories" icon="fa-cutlery" color="#875A7B"/>
<field name="supplier_id" select="multi" string="Vendors" icon="fa-truck"/>
</searchpanel>
</search>
</field>
</record>
<record id="lunch_product_view_tree" model="ir.ui.view">
<field name="name">lunch.product.tree</field>
<field name="model">lunch.product</field>
@@ -93,26 +82,10 @@
</form>
</field>
</record>
<record id="lunch_product_report_view_tree" model="ir.ui.view">
<field name="name">lunch.product.tree</field>
<field name="model">lunch.product.report</field>
<field name="priority">999</field>
<field name="arch" type="xml">
<tree js_class="lunch_list">
<field name="name"/>
<field name="category_id"/>
<field name="supplier_id"/>
<field name="description"/>
<field name="product_id" invisible="1"/>
<field name="price" widget="monetary"/>
<field name="currency_id" invisible="1"/>
</tree>
</field>
</record>
<record id="view_lunch_product_kanban_order" model="ir.ui.view">
<field name="name">lunch.product.kanban</field>
<field name="model">lunch.product.report</field>
<field name="model">lunch.product</field>
<field name="priority">999</field>
<field name="arch" type="xml">
<kanban js_class="lunch_kanban" create="0" edit="0" group_create="0" class="o_kanban_mobile">
@@ -122,8 +95,7 @@
<field name="supplier_id"/>
<field name="description"/>
<field name="currency_id"/>
<field name="user_id"/>
<field name="product_id"/>
<field name="company_id"/>
<field name="is_new"/>
<templates>
<t t-name="kanban-box">
@@ -252,48 +224,6 @@
</div>
<label for="company_id" groups="base.group_multi_company"/>
<field name="company_id" groups="base.group_multi_company"/>
<group>
<group>
<field name="active" invisible="1"/>
<field name="topping_label_1"/>
<field name="topping_quantity_1"/>
</group>
<div>
<field name="topping_ids_1" nolabel="1">
<tree editable="bottom">
<field name="name"/>
<field name="currency_id" invisible="1"/>
<field name="price" widget="monetary"/>
</tree>
</field>
</div>
<group>
<field name="topping_label_2"/>
<field name="topping_quantity_2"/>
</group>
<div>
<field name="topping_ids_2" nolabel="1">
<tree editable="bottom">
<field name="name"/>
<field name="currency_id" invisible="1"/>
<field name="price" widget="monetary"/>
</tree>
</field>
</div>
<group>
<field name="topping_label_3"/>
<field name="topping_quantity_3"/>
</group>
<div>
<field name="topping_ids_3" nolabel="1">
<tree editable="bottom">
<field name="name"/>
<field name="currency_id" invisible="1"/>
<field name="price" widget="monetary"/>
</tree>
</field>
</div>
</group>
</sheet>
</form>
</field>
@@ -361,11 +291,12 @@
<record id="lunch_product_action_order" model="ir.actions.act_window">
<field name="name">Order Your Lunch</field>
<field name="res_model">lunch.product.report</field>
<field name="res_model">lunch.product</field>
<field name="view_mode">kanban,tree</field>
<field name="view_id" ref="view_lunch_product_kanban_order"/>
<field name="search_view_id" ref="lunch_product_report_view_search"/>
<field name="domain">[('supplier_id.available_today', '=', True), '|', ('user_id', '=', False), ('user_id', '=', uid)]</field>
<field name="search_view_id" ref="lunch_product_view_search"/>
<field name="context">{'search_default_available_today': 1}</field>
<field name="domain">[]</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
There is no product available today
@@ -63,6 +63,51 @@
<div class="o_row" attrs="{'invisible': [('send_by', '!=', 'mail')]}"><field name="automatic_email_time" widget="float_time"/> <field name="moment"/></div>
</group>
</group>
<group>
<group>
<field name="active" invisible="1"/>
<field name="topping_label_1"/>
<field name="topping_quantity_1"/>
</group>
<div>
<field name="topping_ids_1" nolabel="1">
<tree editable="bottom">
<field name="name"/>
<field name="company_id" invisible="1"/>
<field name="currency_id" invisible="1"/>
<field name="price" widget="monetary"/>
</tree>
</field>
</div>
<group>
<field name="topping_label_2"/>
<field name="topping_quantity_2"/>
</group>
<div>
<field name="topping_ids_2" nolabel="1">
<tree editable="bottom">
<field name="name"/>
<field name="company_id" invisible="1"/>
<field name="currency_id" invisible="1"/>
<field name="price" widget="monetary"/>
</tree>
</field>
</div>
<group>
<field name="topping_label_3"/>
<field name="topping_quantity_3"/>
</group>
<div>
<field name="topping_ids_3" nolabel="1">
<tree editable="bottom">
<field name="name"/>
<field name="company_id" invisible="1"/>
<field name="currency_id" invisible="1"/>
<field name="price" widget="monetary"/>
</tree>
</field>
</div>
</group>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" groups="base.group_user"/>
@@ -548,6 +548,9 @@
limit: filter.limit,
search_domain: this.searchDomain,
},
params: {
context: this.config.context,
},
});
this._createFilterTree(filter.id, result);
}));
@@ -60,10 +60,6 @@
line-height: 1.8em;
height: 2.8em;
&.focused {
background: #00a09d26;
}
kbd {
background-color: #f4f7f8;
border-radius: 3px;
@@ -1996,6 +1996,7 @@ QUnit.module('Views', {
search_domain: [],
comodel_domain: [['parent_id', '=', false]],
group_domain: [],
context: {},
enable_counters: true,
limit: 200,
});