diff --git a/addons/lunch/data/lunch_demo.xml b/addons/lunch/data/lunch_demo.xml index 14dab611a67..799c212446f 100644 --- a/addons/lunch/data/lunch_demo.xml +++ b/addons/lunch/data/lunch_demo.xml @@ -13,6 +13,7 @@ 975 Bullock Orchard 02155 cynthiasanchez@gmail.com + @@ -22,6 +23,7 @@ Beef, Bacon, Salad, Cheddar, Fried Onion, BBQ Sauce + @@ -31,6 +33,7 @@ Beef, Cheddar, Salad, Fried Onions, BBQ Sauce + @@ -40,6 +43,7 @@ Ham, Cheese, Vegetables + @@ -47,6 +51,7 @@ 2.9 + @@ -55,6 +60,7 @@ 6.90 Tomatoes, Mozzarella + @@ -174,6 +180,7 @@ Beef, Bacon, Salad, Cheddar, Fried Onion, BBQ Sauce + @@ -183,6 +190,7 @@ Beef, Cheddar, Salad, Fried Onions, BBQ Sauce + @@ -191,6 +199,7 @@ 3.0 + @@ -199,6 +208,7 @@ 3.0 + @@ -208,6 +218,7 @@ Mozzarella, Pesto, Tomatoes + @@ -217,6 +228,7 @@ Ham, Cheese, Vegetables + @@ -227,6 +239,7 @@ 6 Maki Salmon - 6 Maki Tuna - 6 Maki Shrimp/Avocado + @@ -237,6 +250,7 @@ 4 Sushi Salmon - 6 Maki Salmon - 4 Sashimi Salmon + @@ -247,6 +261,7 @@ 1 Avocado - 1 Salmon - 1 Eggs - 1 Tuna + @@ -257,6 +272,7 @@ 2 Tempuras, Cabbages, Onions, Sesame Sauce + @@ -266,6 +282,7 @@ Cheese, Ham, Salad, Tomatoes, cucumbers, eggs + @@ -275,6 +292,7 @@ Brie, Honey, Walnut Kernels + @@ -284,6 +302,7 @@ Tuna, Mayonnaise + @@ -293,6 +312,7 @@ + @@ -302,6 +322,7 @@ + @@ -311,6 +332,7 @@ Tomatoes, Mozzarella + @@ -320,6 +342,7 @@ Tomatoes, Mushrooms, Mozzarella + @@ -329,6 +352,7 @@ Tomatoes, Mozzarella, Mushrooms, Peppers, Olives + @@ -338,6 +362,7 @@ Fresh Tomatoes, Basil, Mozzarella + @@ -347,6 +372,7 @@ + @@ -356,12 +382,13 @@ Tomatoes, Basil + Olives 0.30 - + @@ -371,6 +398,7 @@ Tomato sauce, Olive oil, Fresh Tomatoes, Onions, Vegetables, Parmesan + @@ -395,6 +423,7 @@ new 1 + @@ -405,6 +434,7 @@ confirmed 1 + @@ -415,6 +445,7 @@ cancelled 1 + @@ -425,6 +456,7 @@ confirmed 1 + @@ -434,6 +466,7 @@ confirmed + diff --git a/addons/lunch/models/__init__.py b/addons/lunch/models/__init__.py index 94739d3cf9f..ada49399758 100644 --- a/addons/lunch/models/__init__.py +++ b/addons/lunch/models/__init__.py @@ -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 diff --git a/addons/lunch/models/lunch_order.py b/addons/lunch/models/lunch_order.py index 25adf1eb681..794b00803c0 100644 --- a/addons/lunch/models/lunch_order.py +++ b/addons/lunch/models/lunch_order.py @@ -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'}) diff --git a/addons/lunch/models/lunch_product.py b/addons/lunch/models/lunch_product.py index a09fc64c636..1cbb60519c6 100644 --- a/addons/lunch/models/lunch_product.py +++ b/addons/lunch/models/lunch_product.py @@ -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) diff --git a/addons/lunch/models/lunch_product_category.py b/addons/lunch/models/lunch_product_category.py new file mode 100644 index 00000000000..4a40cfb6307 --- /dev/null +++ b/addons/lunch/models/lunch_product_category.py @@ -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 diff --git a/addons/lunch/models/lunch_supplier.py b/addons/lunch/models/lunch_supplier.py index 1e8586aee22..d3b205d8da6 100644 --- a/addons/lunch/models/lunch_supplier.py +++ b/addons/lunch/models/lunch_supplier.py @@ -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 diff --git a/addons/lunch/models/lunch_topping.py b/addons/lunch/models/lunch_topping.py new file mode 100644 index 00000000000..f075b3bebd0 --- /dev/null +++ b/addons/lunch/models/lunch_topping.py @@ -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()) diff --git a/addons/lunch/report/__init__.py b/addons/lunch/report/__init__.py index 2fc4ee848a1..b2c5c89a614 100644 --- a/addons/lunch/report/__init__.py +++ b/addons/lunch/report/__init__.py @@ -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 diff --git a/addons/lunch/report/lunch_product_report.py b/addons/lunch/report/lunch_product_report.py deleted file mode 100644 index b2080022daa..00000000000 --- a/addons/lunch/report/lunch_product_report.py +++ /dev/null @@ -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,)) diff --git a/addons/lunch/security/ir.model.access.csv b/addons/lunch/security/ir.model.access.csv index 91ea62dc3f8..dbed1fd6a45 100644 --- a/addons/lunch/security/ir.model.access.csv +++ b/addons/lunch/security/ir.model.access.csv @@ -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 diff --git a/addons/lunch/security/lunch_security.xml b/addons/lunch/security/lunch_security.xml index 4bf129e1104..cedbab1eccf 100644 --- a/addons/lunch/security/lunch_security.xml +++ b/addons/lunch/security/lunch_security.xml @@ -71,6 +71,12 @@ ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] + + Lunch order: Multi Company + + ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] + + Lunch product: Multi Company @@ -88,11 +94,5 @@ ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] - - - Lunch product report: Multi Company - - ['|', ('company_id', '=', False), ('company_id', 'in', company_ids), '|', ('category_id.company_id', '=', False), ('category_id.company_id', 'in', company_ids)] - diff --git a/addons/lunch/static/src/js/lunch_controller_common.js b/addons/lunch/static/src/js/lunch_controller_common.js index 8cd09316220..e9d86897844 100644 --- a/addons/lunch/static/src/js/lunch_controller_common.js +++ b/addons/lunch/static/src/js/lunch_controller_common.js @@ -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'), diff --git a/addons/lunch/tests/__init__.py b/addons/lunch/tests/__init__.py index ce3a01ead3a..9359b9e5d9c 100644 --- a/addons/lunch/tests/__init__.py +++ b/addons/lunch/tests/__init__.py @@ -3,5 +3,4 @@ from . import common from . import test_alert -from . import test_product_report from . import test_supplier diff --git a/addons/lunch/tests/common.py b/addons/lunch/tests/common.py index 2739d71d943..5206001a16e 100644 --- a/addons/lunch/tests/common.py +++ b/addons/lunch/tests/common.py @@ -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({ diff --git a/addons/lunch/tests/test_product_report.py b/addons/lunch/tests/test_product_report.py deleted file mode 100644 index 624d6c5ed58..00000000000 --- a/addons/lunch/tests/test_product_report.py +++ /dev/null @@ -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') diff --git a/addons/lunch/tests/test_supplier.py b/addons/lunch/tests/test_supplier.py index 9040db7befd..1d2f901c2ba 100644 --- a/addons/lunch/tests/test_supplier.py +++ b/addons/lunch/tests/test_supplier.py @@ -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() diff --git a/addons/lunch/views/lunch_orders_views.xml b/addons/lunch/views/lunch_orders_views.xml index 9ea8265cd2e..15017e4b8bf 100644 --- a/addons/lunch/views/lunch_orders_views.xml +++ b/addons/lunch/views/lunch_orders_views.xml @@ -35,17 +35,21 @@