summaryrefslogtreecommitdiff
path: root/addons/lunch/models/lunch_order.py
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/lunch/models/lunch_order.py
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/lunch/models/lunch_order.py')
-rw-r--r--addons/lunch/models/lunch_order.py208
1 files changed, 208 insertions, 0 deletions
diff --git a/addons/lunch/models/lunch_order.py b/addons/lunch/models/lunch_order.py
new file mode 100644
index 00000000..e25a5507
--- /dev/null
+++ b/addons/lunch/models/lunch_order.py
@@ -0,0 +1,208 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models, _
+from odoo.exceptions import ValidationError
+
+
+class LunchOrder(models.Model):
+ _name = 'lunch.order'
+ _description = 'Lunch Order'
+ _order = 'id desc'
+ _display_name = 'product_id'
+
+ name = fields.Char(related='product_id.name', string="Product Name", readonly=True) # to remove
+ 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)])
+ product_id = fields.Many2one('lunch.product', string="Product", required=True)
+ category_id = fields.Many2one(
+ string='Product Category', related='product_id.category_id', store=True)
+ date = fields.Date('Order Date', required=True, readonly=True,
+ states={'new': [('readonly', False)]},
+ default=fields.Date.context_today)
+ supplier_id = fields.Many2one(
+ string='Vendor', related='product_id.supplier_id', store=True, index=True)
+ user_id = fields.Many2one('res.users', 'User', readonly=True,
+ states={'new': [('readonly', False)]},
+ default=lambda self: self.env.uid)
+ note = fields.Text('Notes')
+ price = fields.Float('Total Price', compute='_compute_total_price', readonly=True, store=True,
+ digits='Account')
+ active = fields.Boolean('Active', default=True)
+ state = fields.Selection([('new', 'To Order'),
+ ('ordered', 'Ordered'),
+ ('confirmed', 'Received'),
+ ('cancelled', 'Cancelled')],
+ 'Status', readonly=True, index=True, default='new')
+ company_id = fields.Many2one('res.company', default=lambda self: self.env.company.id)
+ currency_id = fields.Many2one(related='company_id.currency_id', store=True)
+ quantity = fields.Float('Quantity', required=True, default=1)
+
+ display_toppings = fields.Text('Extras', compute='_compute_display_toppings', store=True)
+
+ product_description = fields.Text('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')
+ 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')
+
+ @api.depends('product_id')
+ def _compute_product_images(self):
+ for line in self:
+ line.image_1920 = line.product_id.image_1920 or line.category_id.image_1920
+ line.image_128 = line.product_id.image_128 or line.category_id.image_128
+
+ @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)]))
+
+ def init(self):
+ self._cr.execute("""CREATE INDEX IF NOT EXISTS lunch_order_user_product_date ON %s (user_id, product_id, date)"""
+ % self._table)
+
+ def _extract_toppings(self, values):
+ """
+ If called in api.multi then it will pop topping_ids_1,2,3 from values
+ """
+ if self.ids:
+ # TODO This is not taking into account all the toppings for each individual order, this is usually not a problem
+ # since in the interface you usually don't update more than one order at a time but this is a bug nonetheless
+ topping_1 = values.pop('topping_ids_1')[0][2] if 'topping_ids_1' in values else self[:1].topping_ids_1.ids
+ topping_2 = values.pop('topping_ids_2')[0][2] if 'topping_ids_2' in values else self[:1].topping_ids_2.ids
+ topping_3 = values.pop('topping_ids_3')[0][2] if 'topping_ids_3' in values else self[:1].topping_ids_3.ids
+ else:
+ topping_1 = values['topping_ids_1'][0][2] if 'topping_ids_1' in values else []
+ topping_2 = values['topping_ids_2'][0][2] if 'topping_ids_2' in values else []
+ topping_3 = values['topping_ids_3'][0][2] if 'topping_ids_3' in values else []
+
+ return topping_1 + topping_2 + topping_3
+
+ @api.constrains('topping_ids_1', 'topping_ids_2', 'topping_ids_3')
+ def _check_topping_quantity(self):
+ errors = {
+ '1_more': _('You should order at least one %s'),
+ '1': _('You have to order one and only one %s'),
+ }
+ for line in self:
+ for index in range(1, 4):
+ availability = line['available_toppings_%s' % index]
+ quantity = line['topping_quantity_%s' % index]
+ toppings = line['topping_ids_%s' % index].filtered(lambda x: x.topping_category == index)
+ label = line['topping_label_%s' % index]
+
+ if availability and quantity != '0_more':
+ check = bool(len(toppings) == 1 if quantity == '1' else toppings)
+ if not check:
+ raise ValidationError(errors[quantity] % label)
+
+ @api.model
+ def create(self, values):
+ lines = self._find_matching_lines({
+ **values,
+ 'toppings': self._extract_toppings(values),
+ })
+ if lines:
+ # YTI FIXME This will update multiple lines in the case there are multiple
+ # matching lines which should not happen through the interface
+ lines.update_quantity(1)
+ return lines[:1]
+ return super().create(values)
+
+ def write(self, values):
+ merge_needed = 'note' in values or 'topping_ids_1' in values or 'topping_ids_2' in values or 'topping_ids_3' in values
+
+ if merge_needed:
+ lines_to_deactivate = self.env['lunch.order']
+ for line in self:
+ # Only write on topping_ids_1 because they all share the same table
+ # and we don't want to remove all the records
+ # _extract_toppings will pop topping_ids_1, topping_ids_2 and topping_ids_3 from values
+ # This also forces us to invalidate the cache for topping_ids_2 and topping_ids_3 that
+ # could have changed through topping_ids_1 without the cache knowing about it
+ toppings = self._extract_toppings(values)
+ self.invalidate_cache(['topping_ids_2', 'topping_ids_3'])
+ values['topping_ids_1'] = [(6, 0, toppings)]
+ matching_lines = self._find_matching_lines({
+ 'user_id': values.get('user_id', line.user_id.id),
+ 'product_id': values.get('product_id', line.product_id.id),
+ 'note': values.get('note', line.note or False),
+ 'toppings': toppings,
+ })
+ 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)
+ return super().write(values)
+
+ @api.model
+ def _find_matching_lines(self, values):
+ domain = [
+ ('user_id', '=', values.get('user_id', self.default_get(['user_id'])['user_id'])),
+ ('product_id', '=', values.get('product_id', False)),
+ ('date', '=', fields.Date.today()),
+ ('note', '=', values.get('note', False)),
+ ]
+ toppings = values.get('toppings', [])
+ return self.search(domain).filtered(lambda line: (line.topping_ids_1 | line.topping_ids_2 | line.topping_ids_3).ids == toppings)
+
+ @api.depends('topping_ids_1', 'topping_ids_2', 'topping_ids_3', 'product_id', 'quantity')
+ def _compute_total_price(self):
+ for line in self:
+ line.price = line.quantity * (line.product_id.price + sum((line.topping_ids_1 | line.topping_ids_2 | line.topping_ids_3).mapped('price')))
+
+ @api.depends('topping_ids_1', 'topping_ids_2', 'topping_ids_3')
+ def _compute_display_toppings(self):
+ for line in self:
+ toppings = line.topping_ids_1 | line.topping_ids_2 | line.topping_ids_3
+ line.display_toppings = ' + '.join(toppings.mapped('name'))
+
+ def update_quantity(self, increment):
+ for line in self.filtered(lambda line: line.state != 'confirmed'):
+ if line.quantity <= -increment:
+ # TODO: maybe unlink the order?
+ line.active = False
+ else:
+ line.quantity += increment
+ self._check_wallet()
+
+ def add_to_cart(self):
+ """
+ This method currently does nothing, we currently need it in order to
+ be able to reuse this model in place of a wizard
+ """
+ # YTI FIXME: Find a way to drop this.
+ return True
+
+ def _check_wallet(self):
+ self.flush()
+ for line in self:
+ if self.env['lunch.cashmove'].get_wallet_balance(line.user_id) < 0:
+ raise ValidationError(_('Your wallet does not contain enough money to order that. To add some money to your wallet, please contact your lunch manager.'))
+
+ 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._check_wallet()
+
+ def action_confirm(self):
+ self.write({'state': 'confirmed'})
+
+ def action_cancel(self):
+ self.write({'state': 'cancelled'})