19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:32:43 +01:00
parent 4607ccbd2e
commit 825ff6514e
487 changed files with 184979 additions and 195262 deletions

View file

@ -2,8 +2,8 @@
import pytz
import logging
from odoo import api, fields, models
from odoo.osv import expression
from odoo import api, fields, models, _
from odoo.fields import Domain
from .lunch_supplier import float_to_time
from datetime import datetime, timedelta
@ -57,11 +57,10 @@ class LunchAlert(models.Model):
location_ids = fields.Many2many('lunch.location', string='Location')
_sql_constraints = [
('notification_time_range',
'CHECK(notification_time >= 0 and notification_time <= 12)',
'Notification time must be between 0 and 12')
]
_notification_time_range = models.Constraint(
'CHECK(notification_time >= 0 and notification_time <= 12)',
'Notification time must be between 0 and 12',
)
@api.depends('mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun')
def _compute_available_today(self):
@ -69,23 +68,18 @@ class LunchAlert(models.Model):
fieldname = WEEKDAY_TO_NAME[today.weekday()]
for alert in self:
alert.available_today = alert.until > today if alert.until else True and alert[fieldname]
alert.available_today = (alert.until > today if alert.until else True) and alert[fieldname]
def _search_available_today(self, operator, value):
if (not operator in ['=', '!=']) or (not value in [True, False]):
return []
if operator != 'in':
return NotImplemented
searching_for_true = (operator == '=' and value) or (operator == '!=' and not value)
today = fields.Date.context_today(self)
fieldname = WEEKDAY_TO_NAME[today.weekday()]
return expression.AND([
[(fieldname, operator, value)],
expression.OR([
[('until', '=', False)],
[('until', '>' if searching_for_true else '<', today)],
])
])
return Domain(fieldname, operator, value) & (
Domain('until', '=', False) | Domain('until', '>', today)
)
def _sync_cron(self):
""" Synchronise the related cron fields to reflect this alert """
@ -127,8 +121,6 @@ class LunchAlert(models.Model):
'active': False,
'interval_type': 'days',
'interval_number': 1,
'numbercall': -1,
'doall': False,
'name': "Lunch: alert chat notification",
'model_id': self.env['ir.model']._get_id(self._name),
'state': 'code',
@ -151,17 +143,19 @@ class LunchAlert(models.Model):
alerts._sync_cron()
return alerts
def write(self, values):
super().write(values)
if not CRON_DEPENDS.isdisjoint(values):
def write(self, vals):
res = super().write(vals)
if not CRON_DEPENDS.isdisjoint(vals):
self._sync_cron()
return res
def unlink(self):
crons = self.cron_id.sudo()
server_actions = crons.ir_actions_server_id
super().unlink()
res = super().unlink()
crons.unlink()
server_actions.unlink()
return res
def _notify_chat(self):
# Called daily by cron
@ -177,10 +171,10 @@ class LunchAlert(models.Model):
if not self.active or self.mode != 'chat':
raise ValueError("Cannot send a chat notification in the current state")
order_domain = [('state', '!=', 'cancelled')]
order_domain = Domain('state', '!=', 'cancelled')
if self.location_ids.ids:
order_domain = expression.AND([order_domain, [('user_id.last_lunch_location_id', 'in', self.location_ids.ids)]])
order_domain &= Domain('user_id.last_lunch_location_id', 'in', self.location_ids.ids)
if self.recipients != 'everyone':
weeksago = fields.Date.today() - timedelta(weeks=(
@ -188,11 +182,14 @@ class LunchAlert(models.Model):
4 if self.recipients == 'last_month' else
52 # if self.recipients == 'last_year'
))
order_domain = expression.AND([order_domain, [('date', '>=', weeksago)]])
order_domain &= Domain('date', '>=', weeksago)
partners = self.env['lunch.order'].search(order_domain).user_id.partner_id
if partners:
self.env['mail.thread'].message_notify(
model=self._name,
res_id=self.id,
body=self.message,
partner_ids=partners.ids
partner_ids=partners.ids,
subject=_('Your Lunch Order'),
)

View file

@ -5,7 +5,7 @@ from odoo import api, fields, models, _
from odoo.tools import float_round
class LunchCashMove(models.Model):
class LunchCashmove(models.Model):
""" Two types of cashmoves: payment (credit) or order (debit) """
_name = 'lunch.cashmove'
_description = 'Lunch Cashmove'
@ -18,8 +18,9 @@ class LunchCashMove(models.Model):
amount = fields.Float('Amount', required=True)
description = fields.Text('Description')
def name_get(self):
return [(cashmove.id, '%s %s' % (_('Lunch Cashmove'), '#%d' % cashmove.id)) for cashmove in self]
def _compute_display_name(self):
for cashmove in self:
cashmove.display_name = '{} {}'.format(_('Lunch Cashmove'), '#%s' % (cashmove.id or "_"))
@api.model
def get_wallet_balance(self, user, include_config=True):

View file

@ -1,8 +1,9 @@
# -*- 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, UserError
from odoo.fields import Domain
class LunchOrder(models.Model):
@ -11,23 +12,22 @@ class LunchOrder(models.Model):
_order = 'id desc'
_display_name = 'product_id'
name = fields.Char(related='product_id.name', string="Product Name", store=True, readonly=True)
name = fields.Char(related='product_id.name', string="Product Name", 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)])
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)]},
date = fields.Date('Order Date', required=True, readonly=False,
default=fields.Date.context_today)
supplier_id = fields.Many2one(
string='Vendor', related='product_id.supplier_id', store=True, index=True)
available_today = fields.Boolean(related='supplier_id.available_today')
order_deadline_passed = fields.Boolean(related='supplier_id.order_deadline_passed')
user_id = fields.Many2one('res.users', 'User', readonly=True,
states={'new': [('readonly', False)]},
default=lambda self: self.env.uid)
available_on_date = fields.Boolean(compute='_compute_available_on_date')
order_deadline_passed = fields.Boolean(compute='_compute_order_deadline_passed')
user_id = fields.Many2one('res.users', 'User', 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.Monetary('Total Price', compute='_compute_total_price', readonly=True, store=True)
@ -59,6 +59,9 @@ class LunchOrder(models.Model):
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')
display_add_button = fields.Boolean(compute='_compute_display_add_button')
_user_product_date = models.Index("(user_id, product_id, date)")
@api.depends('product_id')
def _compute_product_images(self):
@ -73,6 +76,22 @@ class LunchOrder(models.Model):
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('name')
def _compute_display_add_button(self):
new_orders = dict(self.env["lunch.order"]._read_group([
("date", "in", self.mapped("date")),
("user_id", "in", self.user_id.ids),
("state", "=", "new"),
], ['user_id'], ['id:recordset']))
for order in self:
user_new_orders = new_orders.get(order.user_id)
price = 0
if user_new_orders:
user_new_orders = user_new_orders.filtered(lambda lunch_order: lunch_order.date == order.date)
price = sum(order.price for order in user_new_orders)
wallet_amount = self.env['lunch.cashmove'].get_wallet_balance(order.user_id) - price
order.display_add_button = wallet_amount >= order.price
@api.depends_context('show_reorder_button')
@api.depends('state')
def _compute_display_reorder_button(self):
@ -80,26 +99,44 @@ class LunchOrder(models.Model):
for order in self:
order.display_reorder_button = show_button and order.state == 'confirmed' and order.supplier_id.available_today
def init(self):
self._cr.execute("""CREATE INDEX IF NOT EXISTS lunch_order_user_product_date ON %s (user_id, product_id, date)"""
% self._table)
@api.depends('date', 'supplier_id')
def _compute_available_on_date(self):
for order in self:
order.available_on_date = order.supplier_id._available_on_date(order.date)
@api.depends('supplier_id', 'date')
def _compute_order_deadline_passed(self):
today = fields.Date.context_today(self)
for order in self:
if order.date < today:
order.order_deadline_passed = True
elif order.date == today:
order.order_deadline_passed = order.supplier_id.order_deadline_passed
else:
order.order_deadline_passed = False
def _get_topping_ids(self, field, values):
return list(self._fields[field].convert_to_cache(values, self))
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 []
topping_ids = []
return topping_1 + topping_2 + topping_3
for i in range(1, 4):
topping_field = f'topping_ids_{i}'
topping_values = values.get(topping_field, False)
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_ids += self._get_topping_ids(topping_field, values.pop(topping_field)) \
if topping_values else self[:1][topping_field].ids
else:
topping_ids += self._get_topping_ids(topping_field, topping_values) if topping_values else []
return topping_ids
@api.constrains('topping_ids_1', 'topping_ids_2', 'topping_ids_3')
def _check_topping_quantity(self):
@ -126,8 +163,9 @@ class LunchOrder(models.Model):
lines = self._find_matching_lines({
**vals,
'toppings': self._extract_toppings(vals),
'state': 'new',
})
if lines.filtered(lambda l: l.state not in ['sent', 'confirmed']):
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)
@ -136,8 +174,10 @@ class LunchOrder(models.Model):
orders |= super().create(vals)
return orders
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
def write(self, vals):
values = vals
change_topping = 'topping_ids_1' in values or 'topping_ids_2' in values or 'topping_ids_3' in values
merge_needed = 'note' in values or change_topping or 'state' in values
default_location_id = self.env.user.last_lunch_location_id and self.env.user.last_lunch_location_id.id or False
if merge_needed:
@ -149,14 +189,16 @@ class LunchOrder(models.Model):
# 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_model(['topping_ids_2', 'topping_ids_3'])
values['topping_ids_1'] = [(6, 0, toppings)]
if change_topping:
self.invalidate_model(['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,
'lunch_location_id': values.get('lunch_location_id', default_location_id),
'state': values.get('state'),
})
if matching_lines:
lines_to_deactivate |= line
@ -171,10 +213,12 @@ class LunchOrder(models.Model):
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()),
('date', '=', values.get('date', fields.Date.today())),
('note', '=', values.get('note', False)),
('lunch_location_id', '=', values.get('lunch_location_id', default_location_id)),
]
if values.get('state'):
domain = Domain.AND([domain, [('state', '=', values['state'])]])
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)
@ -210,19 +254,17 @@ class LunchOrder(models.Model):
self.env.flush_all()
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.'))
raise ValidationError(_('Oh no! You dont have enough money in your wallet to order your selected lunch! Contact your lunch manager to add some money to your wallet.'))
def action_order(self):
for order in self:
if not order.supplier_id.available_today:
raise UserError(_('The vendor related to this order is not available today.'))
if not order.available_on_date:
raise UserError(_('The vendor related to this order is not available at the selected date.'))
if self.filtered(lambda line: not line.product_id.active):
raise ValidationError(_('Product is no longer available.'))
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):

View file

@ -1,20 +1,17 @@
# -*- 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.osv import expression
from odoo.fields import Domain
class LunchProduct(models.Model):
""" Products available to order. A product is linked to a specific vendor. """
_name = 'lunch.product'
_description = 'Lunch Product'
_inherit = 'image.mixin'
_inherit = ['image.mixin']
_order = 'name'
_check_company_auto = True
@ -80,35 +77,29 @@ class LunchProduct(models.Model):
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
self.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)]])
if operator != 'in':
return NotImplemented
return Domain('supplier_id.available_location_ids', 'in', value) | Domain('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()
self.filtered(lambda p: p.active and not (p.category_id.active and p.supplier_id.active)).action_archive()
self.filtered(lambda p: not p.active and (p.category_id.active and p.supplier_id.active)).action_unarchive()
def toggle_active(self):
invalid_products = self.filtered(lambda product: not product.active and not product.category_id.active)
@api.constrains('active', 'category_id')
def _check_active_categories(self):
invalid_products = self.filtered(lambda product: 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)
@api.constrains('active', 'supplier_id')
def _check_active_suppliers(self):
invalid_products = self.filtered(lambda product: 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 _inverse_is_favorite(self):
""" Handled in the write() """

View file

@ -5,19 +5,19 @@ import base64
from odoo import api, fields, models
from odoo.modules.module import get_module_resource
from odoo.tools.misc import file_open
class LunchProductCategory(models.Model):
""" Category of the product such as pizza, sandwich, pasta, chinese, burger... """
_name = 'lunch.product.category'
_inherit = 'image.mixin'
_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())
with file_open('lunch/static/img/lunch.png', 'rb') as f:
return base64.b64encode(f.read())
name = fields.Char('Product Category', required=True, translate=True)
company_id = fields.Many2one('res.company')
@ -27,15 +27,21 @@ class LunchProductCategory(models.Model):
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}
product_data = self.env['lunch.product']._read_group([('category_id', 'in', self.ids)], ['category_id'], ['__count'])
data = {category.id: count for category, count in product_data}
for category in self:
category.product_count = data.get(category.id, 0)
def toggle_active(self):
def _sync_active_products(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
def action_archive(self):
super().action_archive()
self._sync_active_products()
def action_unarchive(self):
super().action_unarchive()
self._sync_active_products()

View file

@ -9,7 +9,7 @@ from textwrap import dedent
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.osv import expression
from odoo.fields import Domain
from odoo.tools import float_round
from odoo.addons.base.models.res_partner import _tz_get
@ -30,6 +30,7 @@ def float_to_time(hours, moment='am'):
def time_to_float(t):
return float_round(t.hour + t.minute/60 + t.second/3600, precision_digits=2)
class LunchSupplier(models.Model):
_name = 'lunch.supplier'
_description = 'Lunch Supplier'
@ -50,7 +51,7 @@ class LunchSupplier(models.Model):
country_id = fields.Many2one('res.country', related='partner_id.country_id', readonly=False)
company_id = fields.Many2one('res.company', related='partner_id.company_id', readonly=False, store=True)
responsible_id = fields.Many2one('res.users', string="Responsible", domain=lambda self: [('groups_id', 'in', self.env.ref('lunch.group_lunch_manager').id)],
responsible_id = fields.Many2one('res.users', string="Responsible", domain=lambda self: [('all_group_ids', 'in', self.env.ref('lunch.group_lunch_manager').id)],
default=lambda self: self.env.user,
help="The responsible is the person that will order lunch for everyone. It will be used as the 'from' when sending the automatic email.")
@ -112,20 +113,18 @@ class LunchSupplier(models.Model):
show_order_button = fields.Boolean(compute='_compute_buttons')
show_confirm_button = fields.Boolean(compute='_compute_buttons')
_sql_constraints = [
('automatic_email_time_range',
'CHECK(automatic_email_time >= 0 AND automatic_email_time <= 12)',
'Automatic Email Sending Time should be between 0 and 12'),
]
_automatic_email_time_range = models.Constraint(
'CHECK(automatic_email_time >= 0 AND automatic_email_time <= 12)',
'Automatic Email Sending Time should be between 0 and 12',
)
def name_get(self):
res = []
@api.depends('phone')
def _compute_display_name(self):
for supplier in self:
if supplier.phone:
res.append((supplier.id, '%s %s' % (supplier.name, supplier.phone)))
supplier.display_name = f'{supplier.name} {supplier.phone}'
else:
res.append((supplier.id, supplier.name))
return res
supplier.display_name = supplier.name
def _sync_cron(self):
for supplier in self:
@ -165,8 +164,6 @@ class LunchSupplier(models.Model):
'active': False,
'interval_type': 'days',
'interval_number': 1,
'numbercall': -1,
'doall': False,
'name': "Lunch: send automatic email",
'model_id': self.env['ir.model']._get_id(self._name),
'state': 'code',
@ -189,41 +186,51 @@ class LunchSupplier(models.Model):
suppliers._sync_cron()
return suppliers
def write(self, values):
def write(self, vals):
values = vals
for topping in values.get('topping_ids_2', []):
topping_values = topping[2]
topping_values = topping[2] if len(topping) > 2 else False
if topping_values:
topping_values.update({'topping_category': 2})
for topping in values.get('topping_ids_3', []):
topping_values = topping[2]
topping_values = topping[2] if len(topping) > 2 else False
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)
res = super().write(values)
if 'active' in values:
active_suppliers = self.filtered(lambda s: s.active)
inactive_suppliers = self - active_suppliers
Product = self.env['lunch.product'].with_context(active_test=False)
Product.search([('supplier_id', 'in', active_suppliers.ids)]).write({'active': True})
Product.search([('supplier_id', 'in', inactive_suppliers.ids)]).write({'active': False})
if not CRON_DEPENDS.isdisjoint(values):
# flush automatic_email_time field to call _sql_constraints
if 'automatic_email_time' in values:
self.flush_model(['automatic_email_time'])
self._sync_cron()
days_removed = [val for val in values if val in ('mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun') and not values[val]]
if days_removed:
self._cancel_future_days(days_removed)
return res
def unlink(self):
crons = self.cron_id.sudo()
server_actions = crons.ir_actions_server_id
super().unlink()
res = super().unlink()
crons.unlink()
server_actions.unlink()
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)
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 _cancel_future_days(self, weekdays):
weekdays_n = [WEEKDAY_TO_NAME.index(wd) for wd in weekdays]
self.env['lunch.order'].search([
('supplier_id', 'in', self.ids),
('state', 'in', ('new', 'ordered')),
('date', '>=', fields.Date.context_today(self.with_context(tz=self.tz))),
]).filtered(lambda lo: lo.date.weekday() in weekdays_n).write({'state': 'cancelled'})
def _get_current_orders(self, state='ordered'):
""" Returns today's orders """
available_today = self.filtered('available_today')
@ -234,7 +241,7 @@ class LunchSupplier(models.Model):
('supplier_id', 'in', available_today.ids),
('state', '=', state),
('date', '=', fields.Date.context_today(self.with_context(tz=self.tz))),
], order="user_id, name")
], order="user_id, product_id")
return orders
def _send_auto_email(self):
@ -290,9 +297,8 @@ class LunchSupplier(models.Model):
now = fields.Datetime.now().replace(tzinfo=pytz.UTC)
for supplier in self:
now = now.astimezone(pytz.timezone(supplier.tz))
supplier.available_today = supplier._available_on_date(now)
supplier_date = now.astimezone(pytz.timezone(supplier.tz))
supplier.available_today = supplier._available_on_date(supplier_date)
def _available_on_date(self, date):
self.ensure_one()
@ -315,23 +321,17 @@ class LunchSupplier(models.Model):
supplier.order_deadline_passed = not supplier.available_today
def _search_available_today(self, operator, value):
if (not operator in ['=', '!=']) or (not value in [True, False]):
return []
if operator not in ('in', 'not in'):
return NotImplemented
searching_for_true = (operator == '=' and value) or (operator == '!=' and not value)
today = fields.Date.context_today(self)
fieldname = WEEKDAY_TO_NAME[today.weekday()]
truth = operator == 'in'
now = fields.Datetime.now().replace(tzinfo=pytz.UTC).astimezone(pytz.timezone(self.env.user.tz or 'UTC'))
fieldname = WEEKDAY_TO_NAME[now.weekday()]
recurrency_domain = Domain('recurrency_end_date', '=', False) \
| Domain('recurrency_end_date', '>' if truth else '<', today)
recurrency_domain = expression.OR([
[('recurrency_end_date', '=', False)],
[('recurrency_end_date', '>' if searching_for_true else '<', now)]
])
return expression.AND([
recurrency_domain,
[(fieldname, operator, value)]
])
return recurrency_domain & Domain(fieldname, operator, value)
def _compute_buttons(self):
self.env.cr.execute("""

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
from odoo import api, fields, models
from odoo.tools import formatLang
@ -14,13 +14,13 @@ class LunchTopping(models.Model):
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')
supplier_id = fields.Many2one('lunch.supplier', ondelete='cascade', index='btree_not_null')
topping_category = fields.Integer('Topping Category', required=True, default=1)
def name_get(self):
@api.depends('price')
@api.depends_context('company')
def _compute_display_name(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())
topping.display_name = f'{topping.name} {price}'

View file

@ -4,7 +4,7 @@
from odoo import models, fields
class Company(models.Model):
class ResCompany(models.Model):
_inherit = 'res.company'
lunch_minimum_threshold = fields.Float()

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
@ -7,5 +6,5 @@ from odoo import fields, models
class ResUsers(models.Model):
_inherit = 'res.users'
last_lunch_location_id = fields.Many2one('lunch.location')
favorite_lunch_product_ids = fields.Many2many('lunch.product', 'lunch_product_favorite_user_rel', 'user_id', 'product_id')
last_lunch_location_id = fields.Many2one('lunch.location', groups='lunch.group_lunch_user', copy=False)
favorite_lunch_product_ids = fields.Many2many('lunch.product', 'lunch_product_favorite_user_rel', 'user_id', 'product_id', groups='lunch.group_lunch_user', copy=False)