mirror of
https://github.com/bringout/oca-ocb-vertical-industry.git
synced 2026-04-22 11:12:06 +02:00
19.0 vanilla
This commit is contained in:
parent
4607ccbd2e
commit
825ff6514e
487 changed files with 184979 additions and 195262 deletions
|
|
@ -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'),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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 don’t 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):
|
||||
|
|
|
|||
|
|
@ -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() """
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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("""
|
||||
|
|
|
|||
|
|
@ -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}'
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue