[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:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'})
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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())
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -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'),
|
||||
|
||||
@@ -3,5 +3,4 @@
|
||||
|
||||
from . import common
|
||||
from . import test_alert
|
||||
from . import test_product_report
|
||||
from . import test_supplier
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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')
|
||||
@@ -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()
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user