[REV] lunch: Revert changes introduced in f727b8f0b5

YOLO

closes odoo/odoo#30025
This commit is contained in:
Robot Odoo
2019-01-08 14:02:06 +01:00
committed by GitHub
77 changed files with 1023 additions and 2223 deletions
-5
View File
@@ -387,11 +387,6 @@ file_filter = addons/lunch/i18n/<lang>.po
source_file = addons/lunch/i18n/lunch.pot
source_lang = en
[odoo-master.lunch_hr]
file_filter = addons/lunch_hr/i18n/<lang>.po
source_file = addons/lunch_hr/i18n/lunch_hr.pot
source_lang = en
[odoo-master.mail]
file_filter = addons/mail/i18n/<lang>.po
source_file = addons/mail/i18n/mail.pot
+1 -2
View File
@@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import controllers
from . import models
from . import wizard
from . import models
+2 -4
View File
@@ -27,18 +27,16 @@ If you want to save your employees' time and avoid them to always have coins in
'report/lunch_order_views.xml',
'report/lunch_order_templates.xml',
'views/lunch_templates.xml',
'wizard/lunch_line_wizard_views.xml',
'wizard/lucky_order_view.xml',
'views/lunch_alert_views.xml',
'views/lunch_cashmove_views.xml',
'views/lunch_orders_views.xml',
'views/lunch_product_views.xml',
'views/lunch_supplier_views.xml',
'views/lunch_views.xml',
'data/lunch_data.xml',
'data/ir_cron_data.xml'
],
'demo': ['data/lunch_demo.xml'],
'qweb': ['static/src/xml/lunch_kanban.xml', ],
'qweb': ['static/src/xml/lunch.xml', ],
'installable': True,
'application': True,
'certificate': '001292377792581874189',
-1
View File
@@ -1 +0,0 @@
from . import main
-68
View File
@@ -1,68 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import http, fields
from odoo.http import request
class LunchController(http.Controller):
@http.route('/lunch/infos', type='json', auth='user')
def infos(self, user_id=None):
user = request.env['res.users'].browse(user_id) if user_id else request.env.user
infos = {
'order': False,
'wallet': request.env['lunch.cashmove'].get_wallet_balance(user),
'username': user.sudo().name,
'userimage': '/web/image?model=res.users&id=%s&field=image_small' % user.id,
'is_manager': request.env.user.has_group('lunch.group_lunch_manager'),
'users': request.env['res.users'].search_read([('groups_id', 'not in', [request.env.ref('base.group_portal').id])], ['name']),
'alerts': request.env['lunch.alert'].search_read([('available_today', '=', True)], ['message'])
}
order = self._get_current_order(user.id)
if order:
lines = [{'id': line.id,
'product': (line.product_id.name, line.product_id.price),
'toppings': [(topping.name, topping.price) for topping in line.topping_ids],
'quantity': line.quantity,
'price': line.price} for line in order.order_line_ids]
infos.update({
'order': order.id,
'total': order.total,
'state': order.state,
'lines': lines,
})
return infos
@http.route('/lunch/trash', type='json', auth='user')
def trash(self, user_id=None):
user = request.env['res.users'].browse(user_id) if user_id else request.env.user
order = self._get_current_order(user.id)
order.unlink()
@http.route('/lunch/pay', type='json', auth='user')
def pay(self, user_id=None):
user = request.env['res.users'].browse(user_id) if user_id else request.env.user
order = self._get_current_order(user.id)
if order:
wallet_balance = request.env['lunch.cashmove'].get_wallet_balance(user)
if order.state == 'new' and order.total <= wallet_balance:
return self._make_payment(order)
return False
@http.route('/lunch/payment_message', type='json', auth='user')
def payment_message(self):
return {'message': request.env['ir.qweb'].render('lunch.lunch_payment_dialog', {})}
def _get_current_order(self, user_id):
order = request.env['lunch.order'].search([('user_id', '=', user_id), ('date', '=', fields.Date.today())], limit=1)
return order
def _make_payment(self, order):
order.action_order()
return True
-17
View File
@@ -1,17 +0,0 @@
<?xml version="1.0"?>
<odoo>
<data noupdate="1">
<record id="ir_cron_lunch" model="ir.cron">
<field name="name">Lunch: automatic email send</field>
<field name="model_id" ref="model_lunch_supplier"/>
<field name="state">code</field>
<field name="code">model._auto_email_send()</field>
<field name="active" eval="True"/>
<field name="user_id" ref="base.user_root"/>
<field name="interval_number">10</field>
<field name="interval_type">minutes</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
</record>
</data>
</odoo>
+13 -2
View File
@@ -1,12 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="lunch_order_line_action_order" model="ir.actions.server">
<field name="name">Lunch: Order meals</field>
<field name="model_id" ref="model_lunch_order_line"/>
<field name="binding_model_id" ref="model_lunch_order_line"/>
<field name="state">code</field>
<field name="code">
if records:
action = records.order()
</field>
</record>
<record id="lunch_order_line_action_confirm" model="ir.actions.server">
<field name="name">Lunch: Receive meals</field>
<field name="model_id" ref="model_lunch_order_line"/>
<field name="binding_model_id" ref="model_lunch_order_line"/>
<field name="state">code</field>
<field name="code">records.action_confirm()</field>
<field name="code">records.confirm()</field>
</record>
<record id="lunch_order_line_action_cancel" model="ir.actions.server">
@@ -14,7 +25,7 @@
<field name="model_id" ref="model_lunch_order_line"/>
<field name="binding_model_id" ref="model_lunch_order_line"/>
<field name="state">code</field>
<field name="code">records.action_cancel()</field>
<field name="code">records.cancel()</field>
</record>
</data>
<data noupdate="0">
+52 -107
View File
@@ -28,7 +28,7 @@
<field name="name">Chirashi</field>
</record>
<record id="partner_coin_gourmand" model="res.partner">
<record model="res.partner" id="partner_coin_gourmand">
<field name="name">Coin gourmand</field>
<field name="city">Tirana</field>
<field name="country_id" ref="base.al"/>
@@ -37,13 +37,7 @@
<field name="email">coin.gourmand@yourcompany.example.com</field>
</record>
<record model="lunch.supplier" id="supplier_coin_gourmand">
<field name="partner_id" ref="partner_coin_gourmand"/>
<field name="recurrency_from">8</field>
<field name="recurrency_to">23</field>
</record>
<record id="partner_pizza_inn" model="res.partner">
<record model="res.partner" id="partner_pizza_inn">
<field name="name">Pizza Inn</field>
<field name="city">New Delhi TN</field>
<field name="country_id" ref="base.us"/>
@@ -62,12 +56,6 @@
<field name="email">info@corner.com</field>
</record>
<record model="lunch.supplier" id="supplier_corner">
<field name="partner_id" ref="partner_corner"/>
<field name="recurrency_from">8</field>
<field name="recurrency_to">23</field>
</record>
<record model="res.partner" id="partner_sushi_shop">
<field name="name">Sushi Shop</field>
<field name="city">Paris</field>
@@ -77,232 +65,178 @@
<field name="email">order@sushi.com</field>
</record>
<record model="lunch.supplier" id="supplier_sushi_shop">
<field name="partner_id" ref="partner_sushi_shop"/>
<field name="recurrency_from">8</field>
<field name="recurrency_to">23</field>
</record>
<record model="lunch.product" id="product_bacon">
<field name="name">Bacon</field>
<field name="category_id" eval="str(ref('categ_burger'))"/>
<field name="price">7.5</field>
<field name="supplier_id" ref="supplier_corner"/>
<field name="supplier" eval="str(ref('partner_corner'))"/>
<field name="description">Beef, Bacon, Salad, Cheddar, Fried Onion, BBQ Sauce</field>
<field name="image" type="base64" file="lunch/static/img/bacon_burger.png"/>
</record>
<record model="lunch.product" id="product_cheeseburger">
<field name="name">Cheese Burger</field>
<field name="category_id" eval="str(ref('categ_burger'))"/>
<field name="price">7.0</field>
<field name="supplier_id" ref="supplier_corner"/>
<field name="supplier" eval="str(ref('partner_corner'))"/>
<field name="description">Beef, Cheddar, Salad, Fried Onions, BBQ Sauce</field>
<field name="image" type="base64" file="lunch/static/img/cheeseburger.png"/>
</record>
<record model="lunch.product" id="product_chicken_curry">
<field name="name">Chicken Curry</field>
<field name="category_id" eval="str(ref('categ_sandwich'))"/>
<field name="price">3.0</field>
<field name="supplier_id" ref="supplier_corner"/>
<field name="image" type="base64" file="lunch/static/img/chicken_curry.png"/>
<field name="supplier" eval="str(ref('partner_corner'))"/>
</record>
<record model="lunch.product" id="product_spicy_tuna">
<field name="name">Spicy Tuna</field>
<field name="category_id" eval="str(ref('categ_sandwich'))"/>
<field name="price">3.0</field>
<field name="supplier_id" ref="supplier_corner"/>
<field name="image" type="base64" file="lunch/static/img/chicken_curry.png"/>
<field name="supplier" eval="str(ref('partner_corner'))"/>
</record>
<record model="lunch.product" id="product_mozzarella">
<field name="name">Mozzarella</field>
<field name="category_id" eval="str(ref('categ_sandwich'))"/>
<field name="price">3.9</field>
<field name="supplier_id" ref="supplier_corner"/>
<field name="supplier" eval="str(ref('partner_corner'))"/>
<field name="description">Mozzarella, Pesto, Tomatoes</field>
<field name="image" type="base64" file="lunch/static/img/mozza.png"/>
</record>
<record model="lunch.product" id="product_club">
<field name="name">Club</field>
<field name="category_id" eval="str(ref('categ_sandwich'))"/>
<field name="price">3.4</field>
<field name="supplier_id" ref="supplier_corner"/>
<field name="supplier" eval="str(ref('partner_corner'))"/>
<field name="description">Ham, Cheese, Vegetables</field>
<field name="image" type="base64" file="lunch/static/img/club.png"/>
</record>
<record model="lunch.product" id="product_maki">
<field name="name">Lunch Maki 18pc</field>
<field name="category_id" eval="str(ref('categ_sushi'))"/>
<field name="price">12.0</field>
<field name="supplier_id" ref="supplier_sushi_shop"/>
<field name="supplier" eval="str(ref('partner_sushi_shop'))"/>
<field name="description">6 Maki Salmon - 6 Maki Tuna - 6 Maki Shrimp/Avocado</field>
<field name="image" type="base64" file="lunch/static/img/maki.png"/>
</record>
<record model="lunch.product" id="product_salmon">
<field name="name">Lunch Salmon 20pc</field>
<field name="category_id" eval="str(ref('categ_sushi'))"/>
<field name="price">13.80</field>
<field name="supplier_id" ref="supplier_sushi_shop"/>
<field name="supplier" eval="str(ref('partner_sushi_shop'))"/>
<field name="description">4 Sushi Salmon - 6 Maki Salmon - 4 Sashimi Salmon </field>
<field name="image" type="base64" file="lunch/static/img/salmon_sushi.png"/>
</record>
<record model="lunch.product" id="product_temaki">
<field name="name">Lunch Temaki mix 3pc</field>
<field name="category_id" eval="str(ref('categ_temaki'))"/>
<field name="price">14.0</field>
<field name="supplier_id" ref="supplier_sushi_shop"/>
<field name="description">1 Avocado - 1 Salmon - 1 Eggs - 1 Tuna</field>
<field name="image" type="base64" file="lunch/static/img/temaki.png"/>
<field name="supplier" eval="str(ref('partner_sushi_shop'))"/>
<field name="description">1 Avocado/Cooked Tuna - 1 Salmon/Eggs - 1 Salmon/Avocado - 1 Tuna/Mango</field>
</record>
<record model="lunch.product" id="product_chirashi">
<field name="name">Salmon and Avocado</field>
<field name="category_id" eval="str(ref('categ_chirashi'))"/>
<field name="price">9.25</field>
<field name="supplier_id" ref="supplier_sushi_shop"/>
<field name="description">2 Tempuras, Cabbages, Onions, Sesame Sauce</field>
<field name="image" type="base64" file="lunch/static/img/chirashi.png"/>
</record>
<record model="lunch.supplier" id="supplier_pizza_inn">
<field name="partner_id" ref="partner_pizza_inn"/>
<field name="send_by">mail</field>
<field name="automatic_email_send" eval="True"/>
<field name="automatic_email_time">11</field>
<field name="recurrency_from">8</field>
<field name="recurrency_to">23</field>
<field name="recurrency">reccurent</field>
<field name="recurrency_monday" eval="True"/>
<field name="recurrency_tuesday" eval="True"/>
<field name="recurrency_wednesday" eval="True"/>
<field name="recurrency_thursday" eval="True"/>
<field name="recurrency_friday" eval="True"/>
<field name="supplier" eval="str(ref('partner_sushi_shop'))"/>
<field name="description">Shrimp Tempura, Yam Tempura, Chinese Cabbage, Pickled Onions, Sesame Sauce</field>
</record>
<record model="lunch.product" id="product_cheese_ham">
<field name="name">Cheese And Ham</field>
<field name="category_id" eval="str(ref('categ_sandwich'))"/>
<field name="price">3.30</field>
<field name="supplier_id" eval="str(ref('supplier_coin_gourmand'))"/>
<field name="supplier" eval="str(ref('partner_coin_gourmand'))"/>
<field name="description">Cheese, Ham, Salad, Tomatoes, cucumbers, eggs</field>
<field name="image" type="base64" file="lunch/static/img/club.png"/>
</record>
<record model="lunch.product" id="product_country">
<field name="name">The Country</field>
<field name="category_id" eval="str(ref('categ_sandwich'))"/>
<field name="price">3.30</field>
<field name="supplier_id" eval="str(ref('supplier_coin_gourmand'))"/>
<field name="supplier" eval="str(ref('partner_coin_gourmand'))"/>
<field name="description">Brie, Honey, Walnut Kernels</field>
<field name="image" type="base64" file="lunch/static/img/brie.png"/>
</record>
<record model="lunch.product" id="product_tuna">
<field name="name">Tuna</field>
<field name="category_id" eval="str(ref('categ_sandwich'))"/>
<field name="price">2.50</field>
<field name="supplier_id" eval="str(ref('supplier_coin_gourmand'))"/>
<field name="supplier" eval="str(ref('partner_coin_gourmand'))"/>
<field name="description">Tuna, Mayonnaise</field>
<field name="image" type="base64" file="lunch/static/img/tuna_sandwich.png"/>
</record>
<record model="lunch.product" id="product_gouda">
<field name="name">Gouda Cheese</field>
<field name="category_id" eval="str(ref('categ_sandwich'))"/>
<field name="price">2.50</field>
<field name="supplier_id" eval="str(ref('supplier_coin_gourmand'))"/>
<field name="supplier" eval="str(ref('partner_coin_gourmand'))"/>
<field name="description"></field>
<field name="image" type="base64" file="lunch/static/img/gouda.png"/>
</record>
<record model="lunch.product" id="product_chicken_curry">
<field name="name">Chicken Curry</field>
<field name="category_id" eval="str(ref('categ_sandwich'))"/>
<field name="price">2.60</field>
<field name="supplier_id" eval="str(ref('supplier_coin_gourmand'))"/>
<field name="supplier" eval="str(ref('partner_coin_gourmand'))"/>
<field name="description"></field>
<field name="image" type="base64" file="lunch/static/img/chicken_curry.png"/>
</record>
<record model="lunch.product" id="product_margherita">
<field name="name">Pizza Margherita</field>
<field name="category_id" eval="str(ref('categ_pizza'))"/>
<field name="price">6.90</field>
<field name="supplier_id" eval="str(ref('supplier_pizza_inn'))"/>
<field name="supplier" eval="str(ref('partner_pizza_inn'))"/>
<field name="description">Tomatoes, Mozzarella</field>
<field name="image" type="base64" file="lunch/static/img/pizza_margherita.png"/>
</record>
<record model="lunch.product" id="product_funghi">
<field name="name">Pizza Funghi</field>
<field name="category_id" eval="str(ref('categ_pizza'))"/>
<field name="price">7.00</field>
<field name="supplier_id" ref="supplier_pizza_inn"/>
<field name="supplier" eval="str(ref('partner_pizza_inn'))"/>
<field name="description">Tomatoes, Mushrooms, Mozzarella</field>
<field name="image" type="base64" file="lunch/static/img/pizza_funghi.png"/>
</record>
<record model="lunch.product" id="product_vege">
<field name="name">Pizza Vegetarian</field>
<field name="category_id" eval="str(ref('categ_pizza'))"/>
<field name="price">7.00</field>
<field name="supplier_id" ref="supplier_pizza_inn"/>
<field name="supplier" eval="str(ref('partner_pizza_inn'))"/>
<field name="description">Tomatoes, Mozzarella, Mushrooms, Peppers, Olives</field>
<field name="image" type="base64" file="lunch/static/img/pizza_veggie.png"/>
</record>
<record model="lunch.product" id="product_italiana">
<field name="name">Pizza Italiana</field>
<field name="category_id" eval="str(ref('categ_pizza'))"/>
<field name="price">7.40</field>
<field name="supplier_id" eval="str(ref('supplier_pizza_inn'))"/>
<field name="supplier" eval="str(ref('partner_pizza_inn'))"/>
<field name="description">Fresh Tomatoes, Basil, Mozzarella</field>
<field name="image" type="base64" file="lunch/static/img/italiana.png"/>
</record>
<record model="lunch.product" id="product_Bolognese">
<field name="name">Bolognese Pasta</field>
<field name="category_id" eval="str(ref('categ_pasta'))"/>
<field name="price">7.70</field>
<field name="supplier_id" eval="str(ref('supplier_pizza_inn'))"/>
<field name="supplier" eval="str(ref('partner_pizza_inn'))"/>
<field name="description"></field>
<field name="image" type="base64" file="lunch/static/img/pasta_bolognese.png"/>
</record>
<record model="lunch.product" id="product_Napoli">
<field name="name">Napoli Pasta</field>
<field name="category_id" eval="str(ref('categ_pasta'))"/>
<field name="price">7.70</field>
<field name="supplier_id" eval="str(ref('supplier_pizza_inn'))"/>
<field name="supplier" eval="str(ref('partner_pizza_inn'))"/>
<field name="description">Tomatoes, Basil</field>
<field name="image" type="base64" file="lunch/static/img/napoli.png"/>
</record>
<record model="lunch.topping.type" id="type_garniture">
<field name="name">Garniture</field>
</record>
<record model="lunch.topping" id="product_olives">
<field name="name">Olives</field>
<field name="price">0.30</field>
<field name="category_id" ref="categ_pizza"/>
<field name="type_id" ref="type_garniture"/>
</record>
<record model="lunch.product" id="product_4formaggi">
<field name="name">4 Formaggi</field>
<field name="category_id" eval="str(ref('categ_pasta'))"/>
<field name="price">5.50</field>
<field name="supplier_id" ref="supplier_pizza_inn"/>
<field name="supplier" eval="str(ref('partner_pizza_inn'))"/>
<field name="description">Tomato sauce, Olive oil, Fresh Tomatoes, Onions, Vegetables, Parmesan</field>
<field name="image" type="base64" file="lunch/static/img/4formaggio.png"/>
</record>
<record model="lunch.order" id="order_1">
@@ -310,7 +244,6 @@
<field name="date" eval="(DateTime.now() + timedelta(days=2)).strftime('%Y-%m-%d')"/>
<field name="order_line_ids" eval="[]"/>
<field name="state">new</field>
<field name="mail_sent" eval="True"/>
</record>
<record model="lunch.order" id="order_2">
@@ -318,7 +251,6 @@
<field name="date" eval="(DateTime.now() + timedelta(days=1)).strftime('%Y-%m-%d')"/>
<field name="order_line_ids" eval="[]"/>
<field name="state">confirmed</field>
<field name="mail_sent" eval="True"/>
</record>
<record model="lunch.order" id="order_3">
@@ -326,7 +258,6 @@
<field name="date" eval="(DateTime.now() + timedelta(days=3)).strftime('%Y-%m-%d')"/>
<field name="order_line_ids" eval="[]"/>
<field name="state">confirmed</field>
<field name="mail_sent" eval="True"/>
</record>
<record model="lunch.order" id="order_4">
@@ -342,9 +273,9 @@
<field name="price">7.70</field>
<field name="date" eval="(DateTime.now() + timedelta(days=2)).strftime('%Y-%m-%d')"/>
<field name="state">new</field>
<field name="supplier_id" ref="supplier_pizza_inn"/>
<field name="supplier" ref="partner_pizza_inn"/>
<field name="note">+Emmental</field>
<field name="order_id" ref="order_1"/>
<field name="quantity">1</field>
</record>
<record model="lunch.order.line" id="order_line_2">
@@ -353,9 +284,9 @@
<field name="price">7.40</field>
<field name="date" eval="(DateTime.now() + timedelta(days=1)).strftime('%Y-%m-%d')"/>
<field name="state">confirmed</field>
<field name="supplier_id" ref="supplier_pizza_inn"/>
<field name="supplier" ref="partner_pizza_inn"/>
<field name="note">+Mushrooms</field>
<field name="order_id" ref="order_2"/>
<field name="quantity">1</field>
</record>
<record model="lunch.order.line" id="order_line_3">
@@ -364,9 +295,9 @@
<field name="price">2.50</field>
<field name="date" eval="(DateTime.now() + timedelta(days=3)).strftime('%Y-%m-%d')"/>
<field name="state">cancelled</field>
<field name="supplier_id" ref="supplier_coin_gourmand"/>
<field name="supplier" ref="partner_coin_gourmand"/>
<field name="note">+Salad +Tomatoes +Cucumbers</field>
<field name="order_id" ref="order_3"/>
<field name="quantity">1</field>
</record>
<record model="lunch.order.line" id="order_line_4">
@@ -375,9 +306,9 @@
<field name="price">2.60</field>
<field name="date" eval="(DateTime.now() + timedelta(days=3)).strftime('%Y-%m-%d')"/>
<field name="state">confirmed</field>
<field name="supplier_id" ref="supplier_coin_gourmand"/>
<field name="supplier" ref="partner_coin_gourmand"/>
<field name="note">+Salad +Tomatoes +Eggs</field>
<field name="order_id" ref="order_3"/>
<field name="quantity">1</field>
</record>
<record model="lunch.order.line" id="order_line_5">
@@ -386,7 +317,7 @@
<field name="price">5.50</field>
<field name="date" eval="(DateTime.now() + timedelta(days=3)).strftime('%Y-%m-%d')"/>
<field name="state">confirmed</field>
<field name="supplier_id" ref="supplier_pizza_inn"/>
<field name="supplier" ref="partner_pizza_inn"/>
<field name="order_id" ref="order_4"/>
</record>
@@ -395,7 +326,7 @@
<field name="date" eval="(DateTime.now() + timedelta(days=2)).strftime('%Y-%m-%d')"/>
<field name="description">Pizza Italiana</field>
<field name="amount">-7.40</field>
<field name="order_line_id" ref="order_line_1"/>
<field name="order_id" ref="order_2"/>
<field name="state">order</field>
</record>
@@ -404,7 +335,7 @@
<field name="date" eval="(DateTime.now() + timedelta(days=3)).strftime('%Y-%m-%d')"/>
<field name="description">Chicken curry</field>
<field name="amount">-2.60</field>
<field name="order_line_id" ref="order_line_2"/>
<field name="order_id" ref="order_3"/>
<field name="state">order</field>
</record>
@@ -429,8 +360,22 @@
<field name="date" eval="(DateTime.now() + timedelta(days=3)).strftime('%Y-%m-%d')"/>
<field name="description">Pasta 4 Formaggi</field>
<field name="amount">-5.50</field>
<field name="order_line_id" ref="order_line_3"/>
<field name="order_id" ref="order_4"/>
<field name="state">order</field>
</record>
<record model="lunch.alert" id="alert_1">
<field name="alert_type">week</field>
<field name="monday" eval="True"/>
<field name="tuesday" eval="True"/>
<field name="wednesday" eval="True"/>
<field name="thursday" eval="True"/>
<field name="friday" eval="True"/>
<field name="saturday" eval="False"/>
<field name="sunday" eval="False"/>
<field name="start_hour">7</field>
<field name="end_hour">23</field>
<field name="message">Lunch must be ordered before 10:30 AM</field>
</record>
</data>
</odoo>
-1
View File
@@ -4,4 +4,3 @@ from . import lunch_alert
from . import lunch_cashmove
from . import lunch_order
from . import lunch_product
from . import lunch_supplier
+65 -95
View File
@@ -1,31 +1,9 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import math
import pytz
import datetime
from datetime import datetime, time
from odoo import api, fields, models
from odoo.osv import expression
from odoo.tools import float_round
WEEKDAY_TO_NAME = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']
def float_to_time(hours, tz=None):
""" Convert a number of hours into a time object. """
if hours == 24.0:
return time.max
fractional, integral = math.modf(hours)
res = time(int(integral), int(float_round(60 * fractional, precision_digits=0)), 0)
if tz:
res = res.replace(tzinfo=pytz.timezone(tz))
return res
def time_to_float(t):
return float_round(t.hour + t.minute/60 + t.second/3600, precision_digits=2)
from odoo import api, fields, models, _
class LunchAlert(models.Model):
@@ -35,81 +13,73 @@ class LunchAlert(models.Model):
_description = 'Lunch Alert'
_rec_name = 'message'
display = fields.Boolean(compute='_compute_display_get')
message = fields.Text('Message', required=True)
alert_type = fields.Selection([('specific', 'Specific Day'),
('week', 'Every Week'),
('days', 'Every Day')],
string='Recurrence', required=True, index=True, default='specific')
partner_id = fields.Many2one('res.partner', string="Vendor",
help="If specified, the selected vendor can be ordered only on selected days")
specific_day = fields.Date('Day', default=fields.Date.context_today)
monday = fields.Boolean('Monday')
tuesday = fields.Boolean('Tuesday')
wednesday = fields.Boolean('Wednesday')
thursday = fields.Boolean('Thursday')
friday = fields.Boolean('Friday')
saturday = fields.Boolean('Saturday')
sunday = fields.Boolean('Sunday')
start_hour = fields.Float('Between', oldname='active_from', required=True, default=7)
end_hour = fields.Float('And', oldname='active_to', required=True, default=23)
active = fields.Boolean(default=True)
recurrency = fields.Selection([('once', 'Specific Day'), ('reccurent', 'Reccurent')], 'Recurrency', default='once')
recurrency_from = fields.Float('From', default=7)
recurrency_to = fields.Float('To', default=23)
recurrency_date = fields.Date('Day', default=fields.Date.today())
recurrency_date_from = fields.Datetime('from', compute='_compute_recurrency_date_from', store=True)
recurrency_date_to = fields.Datetime('to', compute='_compute_recurrency_date_to', store=True)
recurrency_monday = fields.Boolean('Monday')
recurrency_tuesday = fields.Boolean('Tuesday')
recurrency_wednesday = fields.Boolean('Wednesday')
recurrency_thursday = fields.Boolean('Thursday')
recurrency_friday = fields.Boolean('Friday')
recurrency_saturday = fields.Boolean('Saturday')
recurrency_sunday = fields.Boolean('Sunday')
@api.multi
def name_get(self):
return [(alert.id, '%s %s' % (_('Alert'), '#%d' % alert.id)) for alert in self]
available_today = fields.Boolean('This is True when if the supplier is available today',
compute='_compute_available_today', search='_search_available_today')
@api.depends('recurrency_date', 'recurrency_from')
def _compute_recurrency_date_from(self):
for alert in self:
if alert.recurrency_date and alert.recurrency_from:
alert.recurrency_date_from = datetime.combine(alert.recurrency_date, float_to_time(alert.recurrency_from))
@api.depends('recurrency_date', 'recurrency_to')
def _compute_recurrency_date_to(self):
for alert in self:
if alert.recurrency_date and alert.recurrency_to:
alert.recurrency_date_to = datetime.combine(alert.recurrency_date, float_to_time(alert.recurrency_to))
@api.depends('recurrency', 'recurrency_date', 'recurrency_from', 'recurrency_to', 'recurrency_monday',
'recurrency_tuesday', 'recurrency_wednesday', 'recurrency_thursday',
'recurrency_friday', 'recurrency_saturday', 'recurrency_sunday')
def _compute_available_today(self):
now = fields.Datetime.now()
@api.depends('alert_type', 'specific_day', 'monday', 'tuesday', 'thursday',
'friday', 'saturday', 'sunday', 'start_hour', 'end_hour')
def _compute_display_get(self):
"""
This method check if the alert can be displayed today
if alert type is specific : compare specific_day(date) with today's date
if alert type is week : check today is set as alert (checkbox true) eg. self['monday']
if alert type is day : True
return : Message if can_display_alert is True else False
"""
days_codes = {'0': 'sunday',
'1': 'monday',
'2': 'tuesday',
'3': 'wednesday',
'4': 'thursday',
'5': 'friday',
'6': 'saturday'}
fullday = False
now = datetime.datetime.now()
if self.env.context.get('lunch_date'):
# lunch_date is a fields.Date -> 00:00:00
lunch_date = fields.Datetime.from_string(self.env.context['lunch_date'])
# if lunch_date is in the future, planned lunch, ignore hours
fullday = lunch_date > now
now = max(lunch_date, now)
mynow = fields.Datetime.context_timestamp(self, now)
for alert in self:
time_from = float_to_time(alert.recurrency_from)
time_to = float_to_time(alert.recurrency_to)
can_display_alert = {
'specific': (str(alert.specific_day) == fields.Date.to_string(mynow)),
'week': alert[days_codes[mynow.strftime('%w')]],
'days': True
}
if alert.recurrency == 'once':
alert.available_today = (alert.recurrency_date_from <= now <= alert.recurrency_date_to)
else:
fieldname = 'recurrency_%s' % (WEEKDAY_TO_NAME[now.weekday()])
alert.available_today = alert[fieldname] and (time_from <= now.time() <= time_to)
if can_display_alert[alert.alert_type]:
hour_to = int(alert.end_hour)
min_to = int((alert.end_hour - hour_to) * 60)
to_alert = datetime.time(hour_to, min_to)
hour_from = int(alert.start_hour)
min_from = int((alert.start_hour - hour_from) * 60)
from_alert = datetime.time(hour_from, min_from)
def _search_available_today(self, operator, value):
if (not operator in ['=', '!=']) or (not value in [True, False]):
return []
searching_for_true = (operator == '=' and value) or (operator == '!=' and not value)
now = fields.Datetime.now()
float_now = time_to_float(now.time())
fieldname = 'recurrency_%s' % (WEEKDAY_TO_NAME[now.weekday()])
if searching_for_true:
specific = expression.AND([
[('recurrency', '=', 'once')],
[('recurrency_date_from', '<=', now)],
[('recurrency_date_to', '>=', now)]
])
else:
specific = expression.AND([
[('recurrency', '=', 'once')],
expression.OR([
[('recurrency_date_from', '>=', now)],
[('recurrency_date_to', '<=', now)]
])
])
recurrence = expression.AND([
[(fieldname, operator, value)],
[('recurrency_from', '<=' if searching_for_true else '>=', float_now)],
[('recurrency_to', '>=' if searching_for_true else '<=', float_now)]
])
return expression.OR([specific, recurrence])
if fullday or (from_alert <= mynow.time() <= to_alert):
alert.display = True
else:
alert.display = False
+1 -6
View File
@@ -2,7 +2,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo.tools import float_round
class LunchCashMove(models.Model):
@@ -15,14 +14,10 @@ class LunchCashMove(models.Model):
date = fields.Date('Date', required=True, default=fields.Date.context_today)
amount = fields.Float('Amount', required=True, help='Can be positive (payment) or negative (order or payment if user wants to get his money back)')
description = fields.Text('Description', help='Can be an order or a payment')
order_line_id = fields.Many2one('lunch.order.line', 'Order', ondelete='cascade')
order_id = fields.Many2one('lunch.order.line', 'Order', ondelete='cascade')
state = fields.Selection([('order', 'Order'), ('payment', 'Payment')],
'Is an order or a payment', default='payment')
@api.multi
def name_get(self):
return [(cashmove.id, '%s %s' % (_('Lunch Cashmove'), '#%d' % cashmove.id)) for cashmove in self]
@api.model
def get_wallet_balance(self, user):
return float_round(sum(move['amount'] for move in self.search_read([('user_id', '=', user.id)], ['amount'])), precision_digits=2)
+216 -81
View File
@@ -1,15 +1,19 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import OrderedDict
import json
from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError
from odoo.exceptions import AccessError, ValidationError
from odoo.addons import decimal_precision as dp
class LunchOrder(models.Model):
"""
A lunch order contains one or more lunch order line(s). It is associated to a user for a given
date.
date. When creating a lunch order, applicable lunch alerts are displayed.
"""
_name = 'lunch.order'
_description = 'Lunch Order'
@@ -35,29 +39,24 @@ class LunchOrder(models.Model):
states={'new': [('readonly', False)], False: [('readonly', False)]})
total = fields.Float(compute='_compute_total', string="Total", store=True)
state = fields.Selection([('new', 'New'),
('ordered', 'Ordered'),
('confirmed', 'Received'),
('cancelled', 'Cancelled')],
'Status', readonly=True, index=True, copy=False, default='new')
'Status', readonly=True, index=True, copy=False,
compute='_compute_order_state', store=True)
alerts = fields.Text(compute='_compute_alerts_get', string="Alerts")
company_id = fields.Many2one('res.company', related='user_id.company_id', store=True, readonly=False)
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', readonly=True, store=True)
cash_move_balance = fields.Monetary(compute='_compute_cash_move_balance', multi='cash_move_balance')
balance_visible = fields.Boolean(compute='_compute_cash_move_balance', multi='cash_move_balance')
previous_order_ids = fields.Many2many('lunch.order.line', compute='_compute_previous_order')
previous_order_widget = fields.Text(compute='_compute_previous_order')
mail_sent = fields.Boolean('Mail Sent', readonly=True)
supplier_ids = fields.Many2many('lunch.supplier', compute='_compute_supplier_ids', store=True)
@api.depends('order_line_ids', 'order_line_ids.supplier_id', 'order_line_ids.product_id')
def _compute_supplier_ids(self):
for order in self:
order.supplier_ids = order.mapped('order_line_ids.supplier_id')
@api.depends('order_line_ids', 'order_line_ids.quantity', 'order_line_ids.price')
@api.one
@api.depends('order_line_ids')
def _compute_total(self):
"""
get and sum the order lines' price
"""
self.ensure_one()
self.total = sum(
orderline.price for orderline in self.order_line_ids)
@@ -65,55 +64,96 @@ class LunchOrder(models.Model):
def name_get(self):
return [(order.id, '%s %s' % (_('Lunch Order'), '#%d' % order.id)) for order in self]
@api.depends('state')
def _compute_alerts_get(self):
"""
get the alerts to display on the order form
"""
alert_msg = [alert.message
for alert in self.env['lunch.alert'].search([])
if alert.display]
if self.state == 'new':
self.alerts = alert_msg and '\n'.join(alert_msg) or False
@api.multi
@api.depends('user_id', 'state')
def _compute_previous_order(self):
self.ensure_one()
self.previous_order_widget = json.dumps(False)
prev_order = self.env['lunch.order.line'].search([('user_id', '=', self.env.uid), ('product_id.active', '!=', False)], limit=20, order='date desc, id desc')
# If we use prev_order.ids, we will have duplicates (identical orders).
# Therefore, this following part removes duplicates based on product_id and note.
self.previous_order_ids = list({
(order.product_id, order.note): order.id
for order in prev_order
}.values())
if self.previous_order_ids:
lunch_data = {}
for line in self.previous_order_ids:
lunch_data[line.id] = {
'line_id': line.id,
'product_id': line.product_id.id,
'product_name': line.product_id.name,
'supplier': line.supplier.name,
'note': line.note,
'price': line.price,
'date': fields.Date.to_string(line.date),
'currency_id': line.currency_id.id,
}
# sort the old lunch orders by (date, id)
lunch_data = OrderedDict(sorted(lunch_data.items(), key=lambda t: (t[1]['date'], t[0]), reverse=True))
self.previous_order_widget = json.dumps(lunch_data)
@api.one
@api.depends('user_id')
def _compute_cash_move_balance(self):
self.ensure_one()
domain = [('user_id', '=', self.user_id.id)]
lunch_cash = self.env['lunch.cashmove'].read_group(domain, ['amount', 'user_id'], ['user_id'])
if lunch_cash:
if len(lunch_cash):
self.cash_move_balance = lunch_cash[0]['amount']
self.balance_visible = (self.user_id == self.env.user) or self.user_has_groups('lunch.group_lunch_manager')
@api.one
@api.constrains('date')
def _check_date(self):
"""
Prevents the user to create an order in the past
"""
self.ensure_one()
date_order = self.date
date_today = fields.Date.context_today(self)
if date_order < date_today:
raise ValidationError(_('The date of your order is in the past.'))
def action_order(self):
for order in self:
for line in order.order_line_ids:
self.env['lunch.cashmove'].sudo().create({
'user_id': line.user_id.id,
'amount': -line.price,
'description': line.product_id.name,
'order_line_id': line.id,
'state': 'order',
'date': line.date,
})
@api.one
@api.depends('order_line_ids.state')
def _compute_order_state(self):
"""
Update the state of lunch.order based on its orderlines. Here is the logic:
- if at least one order line is cancelled, the order is set as cancelled
- if no line is cancelled but at least one line is not confirmed, the order is set as new
- if all lines are confirmed, the order is set as confirmed
"""
if not self.order_line_ids:
self.state = 'new'
else:
isConfirmed = True
for orderline in self.order_line_ids:
if orderline.state == 'cancelled':
self.state = 'cancelled'
return
elif orderline.state == 'confirmed':
continue
else:
isConfirmed = False
self.mapped('order_line_ids').write({'state': 'ordered'})
self.write({'state': 'ordered'})
def action_confirm(self, supplier=None):
for order in self:
if supplier:
order.order_line_ids.filtered(lambda line: line.state == 'ordered' and line.supplier_id == supplier).write({'state': 'confirmed'})
if isConfirmed:
self.state = 'confirmed'
else:
order.order_line_ids.filtered(lambda line: line.state == 'ordered').write({'state': 'confirmed'})
order.write({'state': 'confirmed',
'mail_sent': True})
def action_cancel(self):
for order in self:
# RLI FIXME maybe we don't need to filter
order.order_line_ids.filtered(lambda line: line.state != 'confirmed').action_cancel()
order.write({'state': 'cancelled'})
self.state = 'new'
return
class LunchOrderLine(models.Model):
@@ -123,58 +163,153 @@ class LunchOrderLine(models.Model):
name = fields.Char(related='product_id.name', string="Product Name", readonly=True)
order_id = fields.Many2one('lunch.order', 'Order', ondelete='cascade', required=True)
topping_ids = fields.Many2many('lunch.topping', string='Toppings')
product_id = fields.Many2one('lunch.product', string="Product", required=True)
product_id = fields.Many2one('lunch.product', 'Product', required=True,
domain=[('available', '=', True)])
category_id = fields.Many2one('lunch.product.category', string='Product Category',
related='product_id.category_id', readonly=True, store=True)
date = fields.Date(string='Date', related='order_id.date', readonly=True, store=True)
supplier_id = fields.Many2one('lunch.supplier', string='Vendor', related='product_id.supplier_id',
readonly=True, store=True, index=True)
supplier = fields.Many2one('res.partner', string='Vendor', related='product_id.supplier',
readonly=True, store=True)
user_id = fields.Many2one('res.users', string='User', related='order_id.user_id',
readonly=True, store=True)
note = fields.Text('Note')
price = fields.Float('Total Price', compute='_compute_total_price', readonly=True, store=True,
price = fields.Float(related='product_id.price', readonly=True, store=True,
digits=dp.get_precision('Account'))
state = fields.Selection([('new', 'New'),
('ordered', 'Ordered'),
('confirmed', 'Received'),
('ordered', 'Ordered'),
('cancelled', 'Cancelled')],
'Status', readonly=True, index=True, default='new')
cashmove = fields.One2many('lunch.cashmove', 'order_line_id', 'Cash Move')
cashmove = fields.One2many('lunch.cashmove', 'order_id', 'Cash Move')
currency_id = fields.Many2one('res.currency', related='order_id.currency_id', readonly=False)
quantity = fields.Float('Quantity', required=True, default=1)
@api.depends('topping_ids', 'product_id', 'quantity')
def _compute_total_price(self):
for line in self:
line.price = line.quantity * (line.product_id.price + sum(line.topping_ids.mapped('price')))
def _check_supplier_availibility(self):
products = self.mapped('product_id')
if not all(product.available for product in products):
supplier_name = ", ".join(product.supplier.display_name for product in products if not product.available)
raise ValidationError(_("Vendor(s) '%s' is not available today") % supplier_name)
def update_quantity(self, increment):
for line in self:
old_cost = line.price
line.quantity += increment
new_cost = line.price
if new_cost - old_cost > self.env['lunch.cashmove'].get_wallet_balance(line.user_id):
raise UserError(_('Your wallet does not contain enough money to order that.'
'To add some money to your wallet, please contact your lunch manager.'))
cashmove = self.env['lunch.cashmove'].search([('order_line_id', '=', line.id)], limit=1)
if cashmove:
cashmove.amount = -new_cost
if line.quantity <= 0:
order_id = line.order_id
line.sudo().unlink()
if not order_id.order_line_ids:
order_id.sudo().unlink()
@api.model
def create(self, vals):
""" Override as an onchange would not apply if using the history buttons """
res = super(LunchOrderLine, self).create(vals)
res.with_context(lunch_date=res.order_id.date)._check_supplier_availibility()
return res
def action_confirm(self):
self.write({'state': 'confirmed'})
@api.model
def write(self, vals):
""" Override as an onchange would not apply if using the history buttons """
res = super(LunchOrderLine, self).write(vals)
if vals.get('product_id'):
for line in self:
line.with_context(lunch_date=line.order_id.date)._check_supplier_availibility()
return res
for order in self.mapped('order_id'):
if all(line.state == 'confirmed' for line in order.order_line_ids):
order.write({'state': 'confirmed', 'mail_sent': True})
def order(self):
"""
The order_line is ordered to the vendor but isn't received yet
"""
if self.user_has_groups("lunch.group_lunch_manager"):
self.write({'state': 'ordered'})
def action_cancel(self):
for line in self:
line.cashmove.sudo().unlink()
order = {
'supplier': False,
'company': False,
'currency': False,
}
group_lines = {}
for line in self:
if not line.supplier:
# do not send emails for products with no suppliers
continue
self.write({'state': 'cancelled'})
if order['supplier'] and line.supplier != order['supplier']:
raise ValidationError(_("Validate order for one supplier at a time to send emails (mixed orders from %s and %s)") % (
order['supplier'].display_name, line.supplier.display_name))
order['supplier'] = line.supplier
if order['company'] and line.order_id.company_id != order['company']:
raise ValidationError(_("Validate order for one company at a time to send emails (mixed orders from %s and %s)") % (
order['company'].name, line.order_id.company_id.name))
order['company'] = line.order_id.company_id
if order['currency'] and line.currency_id != order['currency']:
raise ValidationError(_("Validate order for one currency at a time to send emails (mixed orders from %s and %s)") % (
order['currency'].name, line.currency_id.name))
order['currency'] = line.currency_id
# group the order by products and note
key = (line.product_id, line.note)
group_lines.setdefault(key, 0)
group_lines[key] += 1
order['company_name'] = order['company'].name
order['currency_id'] = order['currency'].id
order['supplier_id'] = order['supplier'].id
order['supplier_name'] = order['supplier'].name
order['supplier_email'] = order['supplier'].email_formatted
lines = []
# sort by product name, note
for product, note in sorted(group_lines, key=lambda k: (k[0].name, bool(k[1]))):
quantity = group_lines[(product, note)]
lines.append({
'product': product.name,
'note': note or '',
'quantity': quantity,
'price': product.price * quantity,
})
order['amount_total'] = sum(l['price'] for l in lines)
template = self.env.ref('lunch.lunch_order_mail_supplier', raise_if_not_found=False)
ctx = dict(
default_composition_mode='mass_mail',
default_use_template=bool(template),
default_template_id=template.id,
default_lang=order['supplier'].lang or self.env.user.lang,
order=order,
lines=lines,
)
return {
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'mail.compose.message',
'target': 'new',
'context': ctx,
}
else:
raise AccessError(_("Only your lunch manager processes the orders."))
@api.one
def confirm(self):
"""
confirm one or more order line, update order status and create new cashmove
"""
if self.user_has_groups("lunch.group_lunch_manager"):
if self.state != 'confirmed':
values = {
'user_id': self.user_id.id,
'amount': -self.price,
'description': self.product_id.name,
'order_id': self.id,
'state': 'order',
'date': self.date,
}
self.env['lunch.cashmove'].create(values)
self.state = 'confirmed'
else:
raise AccessError(_("Only your lunch manager sets the orders as received."))
@api.one
def cancel(self):
"""
cancel one or more order.line, update order status and unlink existing cashmoves
"""
if self.user_has_groups("lunch.group_lunch_manager"):
self.state = 'cancelled'
self.cashmove.unlink()
else:
raise AccessError(_("Only your lunch manager cancels the orders."))
+38 -77
View File
@@ -1,11 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, tools, _
from odoo import api, fields, models, _
from odoo.addons import decimal_precision as dp
from odoo.osv import expression
class LunchProductCategory(models.Model):
@@ -14,37 +13,6 @@ class LunchProductCategory(models.Model):
_description = 'Lunch Product Category'
name = fields.Char('Product Category', required=True)
topping_ids = fields.One2many('lunch.topping', 'category_id')
class LunchToppingType(models.Model):
""""""
_name = 'lunch.topping.type'
_description = 'Lunch Topping Type'
name = fields.Char('Name', required=True)
class LunchTopping(models.Model):
""""""
_name = 'lunch.topping'
_description = 'Lunch Toppings'
name = fields.Char('Name', required=True)
price = fields.Float('Price', digits=dp.get_precision('Account'), required=True)
category_id = fields.Many2one('lunch.product.category')
type_id = fields.Many2one('lunch.topping.type')
def name_get(self):
currency_id = self.env.user.company_id.currency_id
res = dict(super(LunchTopping, self).name_get())
for topping in self:
if currency_id.position == 'before':
price = '%s %s' % (currency_id.symbol, topping.price)
else:
price = '%s %s' % (topping.price, currency_id.symbol)
res[topping.id] = '%s %s' % (topping.name, price)
return list(res.items())
class LunchProduct(models.Model):
@@ -52,51 +20,44 @@ class LunchProduct(models.Model):
_name = 'lunch.product'
_description = 'Lunch Product'
name = fields.Char('Name', required=True)
name = fields.Char('Product', required=True)
category_id = fields.Many2one('lunch.product.category', 'Product Category', required=True)
description = fields.Text('Description')
price = fields.Float('Price', digits=dp.get_precision('Account'), required=True)
supplier_id = fields.Many2one('lunch.supplier', 'Vendor', required=True)
price = fields.Float('Price', digits=dp.get_precision('Account'))
supplier = fields.Many2one('res.partner', 'Vendor')
active = fields.Boolean(default=True)
available = fields.Boolean(compute='_get_available_product', search='_search_available_products')
# image: all image fields are base64 encoded and PIL-supported
image = fields.Binary(
"Image", attachment=True,
help="This field holds the image used as image for the product, limited to 1024x1024px.")
image_medium = fields.Binary(
"Medium-sized image", attachment=True,
help="Medium-sized image of the product. It is automatically "
"resized as a 128x128px image, with aspect ratio preserved, "
"only when the image exceeds one of those sizes. Use this field in form views or some kanban views.")
image_small = fields.Binary(
"Small-sized image", attachment=True,
help="Small-sized image of the product. It is automatically "
"resized as a 64x64px image, with aspect ratio preserved. "
"Use this field anywhere a small image is required.")
already_ordered = fields.Boolean('Has Already Been Ordered', compute='_compute_already_ordered')
@api.model
def search_read(self, domain=None, fields=None, offset=0, limit=None, order=None):
res = super(LunchProduct, self).search_read(domain, fields, offset, limit, order)
if not order and 'already_ordered' in fields:
res = sorted(res, key=lambda x: x['already_ordered'], reverse=True)
return res
@api.model_create_multi
def create(self, vals_list):
for values in vals_list:
tools.image_resize_images(values)
return super(LunchProduct, self).create(vals_list)
def write(self, values):
tools.image_resize_images(values)
return super(LunchProduct, self).write(values)
def _compute_already_ordered(self):
last_ordered = fields.Date.today() - relativedelta(weeks=2)
@api.depends('supplier')
def _get_available_product(self):
for product in self:
product.already_ordered = bool(self.env['lunch.order.line'].search_count(
[('product_id', '=', product.id), ('date', '>=', last_ordered), ('user_id', '=', self.env.user.id)]))
if not product.supplier:
product.available = True
else:
alerts = self.env['lunch.alert'].search([
('partner_id', '=', self.supplier.id)
])
if alerts and not any(alert.display for alert in alerts):
# every alert is not available
product.available = False
else:
# no alert for the supplier or at least one is not available
product.available = True
def _search_available_products(self, operator, value):
alerts = self.env['lunch.alert'].search([])
supplier_w_alerts = alerts.mapped('partner_id')
available_suppliers = alerts.filtered(lambda a: a.display).mapped('partner_id')
available_products = self.search([
'|',
('supplier', 'not in', supplier_w_alerts.ids),
('supplier', 'in', available_suppliers.ids)
])
if (operator in expression.NEGATIVE_TERM_OPERATORS and value) or \
(operator not in expression.NEGATIVE_TERM_OPERATORS and not value):
# e.g. (available = False) or (available != True)
return [('id', 'not in', available_products.ids)]
else:
# e.g. (available = True) or (available != False)
return [('id', 'in', available_products.ids)]
-183
View File
@@ -1,183 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import math
import pytz
from datetime import datetime, time
from odoo import api, fields, models
from odoo.osv import expression
from odoo.tools import float_round
from odoo.addons.base.models.res_partner import _tz_get
WEEKDAY_TO_NAME = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']
def float_to_time(hours, tz=None):
""" Convert a number of hours into a time object. """
if hours == 24.0:
return time.max
fractional, integral = math.modf(hours)
res = time(int(integral), int(float_round(60 * fractional, precision_digits=0)), 0)
if tz:
res = res.replace(tzinfo=pytz.timezone(tz))
return res
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'
partner_id = fields.Many2one('res.partner', string='Linked partner', required=True)
name = fields.Char('Name', related='partner_id.name')
description = fields.Text('Description')
email = fields.Char(related='partner_id.email')
email_formatted = fields.Char(related='partner_id.email_formatted', readonly=True)
phone = fields.Char(related='partner_id.phone')
street = fields.Char(related='partner_id.street')
street2 = fields.Char(related='partner_id.street2')
zip_code = fields.Char(related='partner_id.zip')
city = fields.Char(related='partner_id.city')
state_id = fields.Many2one("res.country.state", related='partner_id.state_id')
country_id = fields.Many2one('res.country', related='partner_id.country_id')
vat = fields.Char(related='partner_id.vat')
responsible_id = fields.Many2one('res.users', string="Responsible", domain=lambda self: [('groups_id', 'in', self.env.ref('lunch.group_lunch_manager').id)],
help="This is used to set a responsible for this particular vendor")
image = fields.Binary(related='partner_id.image', readonly=False)
image_medium = fields.Binary(related='partner_id.image_medium', readonly=False)
image_small = fields.Binary(related='partner_id.image_small', readonly=False)
send_by = fields.Selection([
('phone', 'Phone'),
('mail', 'Email'),
], 'Send Order By', default='phone')
automatic_email_send = fields.Boolean('Automatic Email Sending')
automatic_email_time = fields.Float('Hour')
recurrency = fields.Selection([('once', 'Specific Day'), ('reccurent', 'Reccurent')], 'Recurrency', default='reccurent')
recurrency_from = fields.Float('From', default=7)
recurrency_to = fields.Float('To', default=23)
recurrency_date = fields.Date('Day', default=fields.Date.today())
recurrency_date_from = fields.Datetime('from', compute='_compute_recurrency_date_from', store=True)
recurrency_date_to = fields.Datetime('to', compute='_compute_recurrency_date_to', store=True)
recurrency_monday = fields.Boolean('Monday', default=True)
recurrency_tuesday = fields.Boolean('Tuesday', default=True)
recurrency_wednesday = fields.Boolean('Wednesday', default=True)
recurrency_thursday = fields.Boolean('Thursday', default=True)
recurrency_friday = fields.Boolean('Friday', default=True)
recurrency_saturday = fields.Boolean('Saturday')
recurrency_sunday = fields.Boolean('Sunday')
available_today = fields.Boolean('This is True when if the supplier is available today',
compute='_compute_available_today', search='_search_available_today')
tz = fields.Selection(_tz_get, string='Timezone', required=True, default='UTC')
_sql_constraints = [
('automatic_email_time_range', 'CHECK(automatic_email_time >= 0 AND automatic_email_time <= 24)', 'Automatic Email Sending Time should be between 0 and 24'),
('recurrency_from', 'CHECK(recurrency_from >= 0 AND recurrency_from <= 24)', 'Recurrency From should be between 0 and 24'),
('recurrency_to', 'CHECK(recurrency_to >= 0 AND recurrency_to <= 24)', 'Recurrency To should be between 0 and 24')
]
@api.model
def _auto_email_send(self):
"""
This method is called every 10 minutes via a cron.
Its job is simply to get all the orders made for each supplier and send an email
automatically to the supplier if the supplier is configured for it and we are ready
to send it (usually at 11am or so)
"""
records = self.search([('automatic_email_send', '=', True)])
for supplier in records:
send_at = datetime.combine(fields.Date.today(), float_to_time(supplier.automatic_email_time, supplier.tz)).astimezone(pytz.UTC).replace(tzinfo=None)
if supplier.available_today and fields.Datetime.now() > send_at:
orders = self.env['lunch.order'].search([('mail_sent', '=', False), ('supplier_ids', 'in', [supplier.id]), ('state', '=', 'ordered')])
if orders:
order = {
'company_name': orders[0].company_id.name,
'currency_id': orders[0].currency_id.id,
'supplier_id': supplier.partner_id.id,
'supplier_name': supplier.name,
'supplier_email': supplier.email_formatted
}
lines = orders.mapped('order_line_ids').filtered(lambda line: line.supplier_id == supplier)
lines = [{'product': line.product_id.name, 'note': line.note, 'quantity': line.quantity, 'price': line.price} for line in lines]
order['amount_total'] = sum(line['price'] for line in lines)
self.env.ref('lunch.lunch_order_mail_supplier').with_context(order=order, lines=lines).send_mail(supplier.id)
orders.action_confirm(supplier)
@api.depends('recurrency_date', 'recurrency_from')
def _compute_recurrency_date_from(self):
for supplier in self:
if supplier.recurrency_date and supplier.recurrency_from:
supplier.recurrency_date_from = datetime.combine(supplier.recurrency_date, float_to_time(supplier.recurrency_from))
@api.depends('recurrency_date', 'recurrency_to')
def _compute_recurrency_date_to(self):
for supplier in self:
if supplier.recurrency_date and supplier.recurrency_to:
supplier.recurrency_date_to = datetime.combine(supplier.recurrency_date, float_to_time(supplier.recurrency_to))
@api.depends('recurrency', 'recurrency_date', 'recurrency_from', 'recurrency_to', 'recurrency_monday',
'recurrency_tuesday', 'recurrency_wednesday', 'recurrency_thursday',
'recurrency_friday', 'recurrency_saturday', 'recurrency_sunday')
def _compute_available_today(self):
now = fields.Datetime.now().replace(tzinfo=pytz.UTC)
for supplier in self:
now = now.astimezone(pytz.timezone(supplier.tz))
time_from = float_to_time(supplier.recurrency_from)
time_to = float_to_time(supplier.recurrency_to)
if supplier.recurrency == 'once':
supplier.available_today = (supplier.recurrency_date_from <= now <= supplier.recurrency_date_to)
else:
fieldname = 'recurrency_%s' % (WEEKDAY_TO_NAME[now.weekday()])
supplier.available_today = supplier[fieldname] and (time_from <= now.time() <= time_to)
def _search_available_today(self, operator, value):
if (not operator in ['=', '!=']) or (not value in [True, False]):
return []
searching_for_true = (operator == '=' and value) or (operator == '!=' and not value)
now = fields.Datetime.now()
float_now = time_to_float(now.time())
fieldname = 'recurrency_%s' % (WEEKDAY_TO_NAME[now.weekday()])
if searching_for_true:
specific = expression.AND([
[('recurrency', '=', 'once')],
[('recurrency_date_from', '<=', now)],
[('recurrency_date_to', '>=', now)]
])
else:
specific = expression.AND([
[('recurrency', '=', 'once')],
expression.OR([
[('recurrency_date_from', '>=', now)],
[('recurrency_date_to', '<=', now)]
])
])
recurrence = expression.AND([
[(fieldname, operator, value)],
[('recurrency_from', '<=' if searching_for_true else '>=', float_now)],
[('recurrency_to', '>=' if searching_for_true else '<=', float_now)]
])
return expression.OR([specific, recurrence])
+2 -9
View File
@@ -4,17 +4,10 @@ order_line_manager,"Order Line user",model_lunch_order_line,group_lunch_manager,
cashmove_manager,"Cashmove user",model_lunch_cashmove,group_lunch_manager,1,1,1,1
product_manager,"Product user",model_lunch_product,group_lunch_manager,1,1,1,1
product_category_manager,"Product category user",model_lunch_product_category,group_lunch_manager,1,1,1,1
alert_manager,"Alert user",model_lunch_alert,group_lunch_manager,1,1,1,1
order_user,"Order user",model_lunch_order,group_lunch_user,1,1,1,0
order_line_user,"Order Line user",model_lunch_order_line,group_lunch_user,1,1,1,1
cashmove_user,"Cashmove user",model_lunch_cashmove,group_lunch_user,1,0,0,0
product_user,"Product user",model_lunch_product,group_lunch_user,1,0,0,0
product_category_user,"Product category user",model_lunch_product_category,group_lunch_user,1,0,0,0
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_alert_access,access_lunch_alert_user,model_lunch_alert,base.group_user,1,0,0,0
lunch_alert_user,access_lunch_alert_lunch_user,model_lunch_alert,group_lunch_user,1,1,1,1
lunch_alert_manager,access_lunch_alert_manager,model_lunch_alert,group_lunch_manager,1,1,1,1
lunch_topping_type_user,access_lunch_topping_type,model_lunch_topping_type,base.group_user,1,0,0,0
lunch_topping_type_manager,access_lunch_topping_type,model_lunch_topping_type,group_lunch_manager,1,1,1,1
lunch_topping_user,access_lunch_topping,model_lunch_topping,base.group_user,1,0,0,0
lunch_topping_manager,access_lunch_topping,model_lunch_topping,group_lunch_manager,1,1,1,1
alert_user,"Alert user",model_lunch_alert,group_lunch_user,1,0,0,0
1 id name model_id/id group_id/id perm_read perm_write perm_create perm_unlink
4 cashmove_manager Cashmove user model_lunch_cashmove group_lunch_manager 1 1 1 1
5 product_manager Product user model_lunch_product group_lunch_manager 1 1 1 1
6 product_category_manager Product category user model_lunch_product_category group_lunch_manager 1 1 1 1
7 alert_manager Alert user model_lunch_alert group_lunch_manager 1 1 1 1
8 order_user Order user model_lunch_order group_lunch_user 1 1 1 0
9 order_line_user Order Line user model_lunch_order_line group_lunch_user 1 1 1 1
10 cashmove_user Cashmove user model_lunch_cashmove group_lunch_user 1 0 0 0
11 product_user Product user model_lunch_product group_lunch_user 1 0 0 0
12 product_category_user Product category user model_lunch_product_category group_lunch_user 1 0 0 0
13 lunch_supplier_user alert_user Lunch Supplier User Rights Alert user model_lunch_supplier model_lunch_alert 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_alert_access access_lunch_alert_user model_lunch_alert base.group_user 1 0 0 0
lunch_alert_user access_lunch_alert_lunch_user model_lunch_alert group_lunch_user 1 1 1 1
lunch_alert_manager access_lunch_alert_manager model_lunch_alert group_lunch_manager 1 1 1 1
lunch_topping_type_user access_lunch_topping_type model_lunch_topping_type base.group_user 1 0 0 0
lunch_topping_type_manager access_lunch_topping_type model_lunch_topping_type group_lunch_manager 1 1 1 1
lunch_topping_user access_lunch_topping model_lunch_topping base.group_user 1 0 0 0
lunch_topping_manager access_lunch_topping model_lunch_topping group_lunch_manager 1 1 1 1
Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

+94
View File
@@ -0,0 +1,94 @@
odoo.define('lunch.previous_orders', function (require) {
"use strict";
var AbstractField = require('web.AbstractField');
var core = require('web.core');
var field_registry = require('web.field_registry');
var field_utils = require('web.field_utils');
var QWeb = core.qweb;
var LunchPreviousOrdersWidget = AbstractField.extend({
events: {
'click .o_add_button': '_onAddOrder',
},
supportedFieldTypes: ['one2many'],
/**
* @override
*/
init: function () {
this._super.apply(this, arguments);
this.lunchData = JSON.parse(this.value);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Used by the widget to render the previous order price like a monetary.
*
* @private
* @param {Object} order
* @returns {string} the monetary formatting of order price
*/
_formatValue: function (order) {
var options = _.extend({}, this.nodeOptions, order);
return field_utils.format.monetary(order.price, this.field, options);
},
/**
* @private
* @override
*/
_render: function () {
if (this.lunchData !== false) {
// group data by supplier for display
var categories = _.groupBy(this.lunchData, 'supplier');
this.$el.html(QWeb.render('LunchPreviousOrdersWidgetList', {
formatValue: this._formatValue.bind(this),
categories: categories,
}));
} else {
return this.$el.html(QWeb.render('LunchPreviousOrdersWidgetNoOrder'));
}
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
* @param {MouseEvent} event
*/
_onAddOrder: function (event) {
// Get order details from line
var lineID = parseInt($(event.currentTarget).data('id'));
if (!lineID) {
return;
}
var values = {
product_id: {
id: this.lunchData[lineID].product_id,
display_name: this.lunchData[lineID].product_name,
},
note: this.lunchData[lineID].note,
price: this.lunchData[lineID].price,
};
// create a new order line
this.trigger_up('field_changed', {
dataPointID: this.dataPointID,
changes: {
order_line_ids: {
operation: 'CREATE',
data: values,
},
},
});
},
});
field_registry.add('previous_order', LunchPreviousOrdersWidget);
});
@@ -1,190 +0,0 @@
odoo.define('lunch.LunchKanbanController', function (require) {
"use strict";
/**
* This file defines the Controller for the Lunch Kanban view, which is an
* override of the KanbanController.
*/
var core = require('web.core');
var KanbanController = require('web.KanbanController');
var LunchKanbanWidget = require('lunch.LunchKanbanWidget');
var LunchPaymentDialog = require('lunch.LunchPaymentDialog');
var session = require('web.session');
var qweb = core.qweb;
var _t = core._t;
var LunchKanbanController = KanbanController.extend({
custom_events: _.extend({}, KanbanController.prototype.custom_events, {
add_money: '_onAddMoney',
add_product: '_onAddProduct',
change_user: '_onUserChanged',
edit_order: '_onEditOrder',
open_wizard: '_onOpenWizard',
order_now: '_onOrderNow',
remove_product: '_onRemoveProduct',
save_order: '_onSaveOrder',
unlink_order: '_onUnlinkOrder',
}),
/**
* @override
*/
init: function () {
this.userId = null;
this.editMode = false;
return this._super.apply(this, arguments);
},
start: function () {
this.$el.addClass('o_lunch_kanban');
return this._super.apply(this, arguments);
},
_fetchPaymentInfo: function (){
return this._rpc({
route: '/lunch/payment_message',
});
},
_onAddMoney: function (ev) {
var self = this;
ev.stopPropagation();
this._showPaymentDialog(_t('Your wallet is empty!'));
},
_onAddProduct: function (ev) {
var self = this;
ev.stopPropagation();
this._rpc({
model: 'lunch.order.line',
method: 'update_quantity',
args: [[ev.data.lineId], 1],
}).then(function () {
self.reload();
});
},
_onEditOrder: function (ev) {
ev.stopPropagation();
this.editMode = true;
this.reload();
},
_onOpenWizard: function (ev) {
var self = this;
ev.stopPropagation();
var ctx = this.userId ? {default_user_id: this.userId}: {};
var options = {
on_close: function () {
self.reload();
},
};
this.do_action({
res_model: 'lunch.order.line.temp',
type: 'ir.actions.act_window',
views: [[false, 'form']],
target: 'new',
context: _.extend(ctx, {default_product_id: ev.data.res_id}),
}, options);
},
_onOrderNow: function (ev) {
var self = this;
ev.stopPropagation();
this._rpc({
route: '/lunch/pay',
params: {
user_id: this.userId,
},
}).then(function (isPaid) {
if (isPaid) {
// TODO: feedback?
self.reload();
} else {
self._showPaymentDialog();
self.reload();
}
});
},
_onRemoveProduct: function (ev) {
var self = this;
ev.stopPropagation();
this._rpc({
model: 'lunch.order.line',
method: 'update_quantity',
args: [[ev.data.lineId], -1],
}).then(function () {
self.reload();
});
},
_onSaveOrder: function (ev) {
ev.stopPropagation();
this.editMode = false;
this.reload();
},
_onUserChanged: function (ev) {
ev.stopPropagation();
this.userId = ev.data.userId;
this.reload();
},
_onUnlinkOrder: function (ev) {
var self = this;
ev.stopPropagation();
this._rpc({
route: '/lunch/trash',
params: {
user_id: this.userId,
},
}).then(function () {
self.reload();
});
},
_orderPaid: function () {
Dialog.alert(this, _t('Your order has been paid have a nice day'), {'title': 'Order Paid'});
},
_orderNotPaid: function () {
this._showPaymentDialog();
},
_showPaymentDialog: function (title) {
var self = this;
title = title || '';
this._fetchPaymentInfo().then(function (data) {
var paymentDialog = new LunchPaymentDialog(self, _.extend(data, {title: title}));
paymentDialog.open();
});
},
_update: function () {
var self = this;
this._rpc({
route: '/lunch/infos',
params: {
user_id: this.userId,
},
}).then(function (data) {
if (self.widget) {
self.widget.destroy();
}
data.wallet = parseFloat(data.wallet).toFixed(2);
self.widget = new LunchKanbanWidget(self, _.extend(data, {edit: self.editMode}));
self.widget.insertBefore(self.$('.o_kanban_view'));
});
return this._super.apply(this, arguments);
},
});
return LunchKanbanController;
});
@@ -1,19 +0,0 @@
odoo.define('lunch.LunchKanbanModel', function (require) {
"use strict";
/**
* This file defines the Model for the Lunch Kanban view, which is an
* override of the KanbanModel.
*/
var KanbanModel = require('web.KanbanModel');
var core = require('web.core');
var _t = core._t;
var LunchKanbanModel = KanbanModel.extend({
});
return LunchKanbanModel;
});
@@ -1,22 +0,0 @@
odoo.define('lunch.LunchKanbanRecord', function (require) {
"use strict";
/**
* This file defines the KanbanRecord for the Lunch Kanban view.
*/
var KanbanRecord = require('web.KanbanRecord');
var LunchKanbanRecord = KanbanRecord.extend({
_onGlobalClick: function (ev) {
ev.preventDefault();
// ignore clicks on oe_kanban_action elements
if (!$(ev.target).hasClass('oe_kanban_action')) {
this.trigger_up('open_wizard', {res_id: this.id});
}
},
});
return LunchKanbanRecord;
});
@@ -1,29 +0,0 @@
odoo.define('lunch.LunchKanbanRenderer', function (require) {
"use strict";
/**
* This file defines the Renderer for the Lunch Kanban view, which is an
* override of the KanbanRenderer.
*/
var LunchKanbanRecord = require('lunch.LunchKanbanRecord');
var KanbanRenderer = require('web.KanbanRenderer');
var LunchKanbanRenderer = KanbanRenderer.extend({
config: _.extend({}, KanbanRenderer.prototype.config, {
KanbanRecord: LunchKanbanRecord,
}),
/**
* @override
*/
start: function () {
this.$el.addClass('o_lunch_kanban_view position-relative align-content-start flex-grow-1 flex-shrink-1');
return this._super.apply(this, arguments);
},
});
return LunchKanbanRenderer;
});
@@ -1,42 +0,0 @@
odoo.define('lunch.LunchKanbanView', function (require) {
"use strict";
var LunchKanbanController = require('lunch.LunchKanbanController');
var LunchKanbanModel = require('lunch.LunchKanbanModel');
var LunchKanbanRenderer = require('lunch.LunchKanbanRenderer');
var config = require('web.config');
var core = require('web.core');
var KanbanView = require('web.KanbanView');
var view_registry = require('web.view_registry');
var _lt = core._lt;
if (config.device.isMobile) {
// use the classical KanbanView in mobile
view_registry.add('lunch_kanban', KanbanView);
return;
}
var LunchKanbanView = KanbanView.extend({
config: _.extend({}, KanbanView.prototype.config, {
Controller: LunchKanbanController,
Model: LunchKanbanModel,
Renderer: LunchKanbanRenderer,
}),
display_name: _lt('Lunch Kanban'),
groupable: false,
/**
* @override
*/
init: function () {
return this._super.apply(this, arguments);
},
});
view_registry.add('lunch_kanban', LunchKanbanView);
return LunchKanbanView;
});
@@ -1,87 +0,0 @@
odoo.define('lunch.LunchKanbanWidget', function (require) {
"use strict";
var core = require('web.core');
var session = require('web.session');
var Widget = require('web.Widget');
var qweb = core.qweb;
var LunchKanbanWidget = Widget.extend({
template: 'LunchKanbanWidget',
custom_events: {
},
events: {
'click .o_add_money': '_onAddMoney',
'click .o_add_product': '_onAddProduct',
'click .o_lunch_widget_edit': '_onEditOrder',
'click .o_lunch_widget_order_button': '_onOrderNow',
'click .o_remove_product': '_onRemoveProduct',
'click .o_lunch_widget_save': '_onSaveOrder',
'click .o_lunch_widget_unlink': '_onUnlinkOrder',
'change select': '_onUserChanged',
},
init: function (parent, params) {
var self = this;
this._super.apply(this, arguments);
this.data = params;
this.currency = session.get_currency(session.company_currency_id);
},
renderElement: function () {
var self = this;
this._super.apply(this, arguments);
this.$('select').val(this.data.username);
},
_onAddMoney: function (ev) {
ev.preventDefault();
ev.stopPropagation();
this.trigger_up('add_money', {});
},
_onAddProduct: function (ev) {
ev.preventDefault();
ev.stopPropagation();
this.trigger_up('add_product', {lineId: $(ev.currentTarget).data('id')});
},
_onEditOrder: function (ev) {
ev.preventDefault();
ev.stopPropagation();
this.trigger_up('edit_order', {});
},
_onOrderNow: function (ev) {
ev.preventDefault();
ev.stopPropagation();
this.trigger_up('order_now', {});
},
_onRemoveProduct: function (ev) {
ev.preventDefault();
ev.stopPropagation();
this.trigger_up('remove_product', {lineId: $(ev.currentTarget).data('id')});
},
_onSaveOrder: function (ev) {
ev.preventDefault();
ev.stopPropagation();
this.trigger_up('save_order', {});
},
_onUnlinkOrder: function (ev) {
ev.preventDefault();
ev.stopPropagation();
this.trigger_up('unlink_order', {});
},
_onUserChanged: function (ev) {
ev.stopPropagation();
this.trigger_up('change_user', {userId: $(ev.currentTarget).find(':selected').data('user-id')});
}
});
return LunchKanbanWidget;
});
@@ -1,24 +0,0 @@
odoo.define('lunch.LunchPaymentDialog', function (require) {
"use strict";
var core = require('web.core');
var Dialog = require('web.Dialog');
var qweb = core.qweb;
var LunchPaymentDialog = Dialog.extend({
template: 'lunch.LunchPaymentDialog',
init: function (parent, options) {
var self = this;
this._super.apply(this, arguments);
options = (options || {});
this.message = options.message || '';
},
});
return LunchPaymentDialog;
});
+14
View File
@@ -0,0 +1,14 @@
.o_web_client .o_lunch {
.o_lunch_last_orders {
width: 100%;
.o_lunch_vignette {
padding: 5px 0;
+ .o_lunch_vignette {
border-top: 1px solid $o-brand-lightsecondary;
}
}
}
}
@@ -1,47 +0,0 @@
.o_lunch_kanban {
.o_pink {
color: nth($o-colors, 10);
}
.o_lunch_widget {
display: flex;
border-bottom: 1px solid #CED4DA;
background-color: white;
min-height: 90px;
overflow-y: visible;
.o_lunch_widget_info {
padding: 5px 12px;
margin: 4px 8px;
width: 300px;
flex: 1 1 auto;
overflow-y: visible;
.o_lunch_ordered {
color: white;
background-color: #F0D970;
padding: 1px 3px;
border-radius: .25em;
}
.o_lunch_confirmed {
color: white;
background-color: #00BA4E;
padding: 1px 3px;
border-radius: .25em;
}
.o_lunch_widget_title {
font-weight: bold;
font-size: initial;
}
.o_lunch_widget_order_button {
display: block;
width: 100%;
margin-bottom: 4px;
margin-top: 12px;
}
}
}
}
@@ -1,125 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<div t-name="LunchKanbanWidget">
<div class="o_lunch_widget">
<div class="o_lunch_widget_info d-flex flex-row">
<div>
<img class="rounded-circle" t-attf-src="{{ widget.data.userimage }}"/>
</div>
<div class="w-50 ml-2 py-3">
<div>
<t t-if="widget.data.is_manager">
<select class="o_input">
<t t-foreach="widget.data.users" t-as="user">
<option t-attf-data-user-id="{{ user.id }}" t-attf-value="{{ user.name }}"><t t-esc="user.name"/></option>
</t>
</select>
</t>
<t t-else="">
<t t-esc="widget.data.username"/>
</t>
</div>
<div class="d-flex flex-row">
<span class="flex-grow-1">
<i class="fa fa-money"/>
<t t-call="currency_field">
<t t-set="value" t-value="widget.data.wallet"/>
<t t-set="currency" t-value="widget.currency"/>
</t>
</span>
<span>
<a class="o_add_money" href="#">Add money</a>
</span>
</div>
</div>
</div>
<div class="o_lunch_widget_info">
<t t-if="widget.data.order">
<t t-if="widget.data.state == 'ordered'">
<t t-set="state_class" t-value="'o_lunch_ordered'"/>
</t>
<t t-else="widget.data.state == 'confirmed'">
<t t-set="state_class" t-value="'o_lunch_confirmed'"/>
</t>
<div class="d-flex flex-row">
<div class="flex-grow-1">
<span class="o_lunch_widget_title">Your order</span>
<a href="#"><i t-if="widget.data.state != 'confirmed'" class="fa fa-trash o_lunch_widget_unlink"/></a>
<a t-if="widget.data.state == 'ordered' &amp;&amp; !widget.data.edit" href="#" class="o_lunch_widget_edit">Edit</a>
<a t-if="widget.data.state == 'ordered' &amp;&amp; widget.data.edit" href="#" class="o_lunch_widget_save">Save</a>
</div>
<div t-if="widget.data.state != 'new'" t-esc="widget.data.state" t-attf-class="flex-grow-0 {{ state_class }}"/>
</div>
<div t-foreach="widget.data.lines" t-as="line">
<div class="d-flex flex-row">
<div class="flex-grow-0">
<a class="o_remove_product" t-if="widget.data.state == 'new' || widget.data.edit" t-attf-data-id="{{ line.id }}" href="#"><i class="fa fa-minus-circle"/></a>
<span t-esc="line.quantity"/>
<a class="o_add_product" t-if="widget.data.state == 'new' || widget.data.edit" t-attf-data-id="{{ line.id }}" href="#"><i class="fa fa-plus-circle"/></a>
</div>
<div class="flex-grow-1 pl-2">
<span t-esc="line.product[0]"/>
</div>
<div class="flex-grow-0">
<t t-call="currency_field">
<t t-set="value" t-value="line.product[1]"/>
<t t-set="currency" t-value="widget.currency"/>
</t>
</div>
</div>
<div t-foreach="line.toppings" t-as="topping" class="d-flex flex-row">
<div class="flex-grow-1 pl-5 ml-5">
<span>+ <t t-esc="topping[0]"/></span>
</div>
<div class="flex-grow-0">
<t t-call="currency_field">
<t t-set="value" t-value="topping[1]"/>
<t t-set="currency" t-value="widget.currency"/>
</t>
</div>
</div>
</div>
</t>
</div>
<div class="o_lunch_widget_info">
<t t-if="widget.data.order &amp;&amp; widget.data.state == 'new'">
<div class="d-flex flex-row">
<div class="o_lunch_widget_title flex-grow-1">
Total
</div>
<div class="flex-grow-0 pr-1">
<t t-call="currency_field">
<t t-set="value" t-value="widget.data.total"/>
<t t-set="currency" t-value="widget.currency"/>
</t>
</div>
</div>
<button class="btn btn-primary o_lunch_widget_order_button" t-attf-data-order-id="{{ widget.data.order }}">ORDER NOW</button>
</t>
</div>
</div>
<t t-foreach="widget.data.alerts" t-as="alert">
<div class="alert alert-danger" role="alert">
<t t-esc="alert.message"/>
</div>
</t>
</div>
<t t-name="currency_field">
<t t-if="currency">
<t t-if="currency.position == 'after'">
<t t-esc="value"/><t t-esc="currency.symbol"/>
</t>
<t t-else="">
<t t-esc="currency.symbol"/><t t-esc="value"/>
</t>
</t>
<t t-else="">
<t t-esc="value"/>
</t>
</t>
<div t-name="lunch.LunchPaymentDialog">
<span t-esc="widget.message"/>
</div>
</templates>
+145
View File
@@ -0,0 +1,145 @@
odoo.define('lunch.tests', function (require) {
"use strict";
var FormView = require('web.FormView');
var testUtils = require('web.test_utils');
var createView = testUtils.createView;
QUnit.module('lunch', {
beforeEach: function () {
this.data = {
order: {
fields: {
previous_order_widget: {type: 'text'},
order_line_ids: {type: 'many2many', relation: 'order_line'}
},
records: [{
id: 1,
name: 'coucou',
}, {
id: 2,
name: 'coucou',
}, {
id: 11,
}],
},
order_line: {
fields: {
product_id: {type: 'many2one', relation: 'product'},
note: {type: 'text'},
price: {type: 'float'},
},
},
supplier: {
fields: {},
records: [{
id: 1,
display_name: "Coin Gourmant",
}, {
id: 2,
display_name: "Pizza Inn",
}]
},
product: {
fields: {},
records: [{
id: 1,
name: 'Pizza Margherita',
}],
}
};
},
}, function () {
QUnit.test("empty previous_order widget", function (assert) {
assert.expect(2);
var form = createView({
View: FormView,
arch: '<form>' +
'<sheet>' +
'<field name="previous_order_widget" widget="previous_order"/>' +
'<field name="order_line_ids"/>' +
'</sheet>' +
'</form>',
data: this.data,
model: 'order',
archs: {
"order_line,false,list": '<tree string="Orders">' +
'<field name="product_id"/>' +
'<field name="note"/>' +
'<field name="price"/>' +
'</tree>',
},
});
var widgetText = form.$('.o_field_widget[name="previous_order_widget"]').text();
assert.ok(widgetText.indexOf('This is the first time you order a meal') !== -1,
"the widget should display its no content text");
assert.containsNone(form, '.o_lunch_vignette',
"there should be no vignette");
form.destroy();
});
QUnit.test("add an order line with previous_order widget", function (assert) {
assert.expect(4);
var form = createView({
View: FormView,
arch: '<form>' +
'<sheet>' +
'<field name="previous_order_widget" widget="previous_order"/>' +
'<field name="order_line_ids"/>' +
'</sheet>' +
'</form>',
data: this.data,
model: 'order',
mockRPC: function (route, args) {
if (args.method === 'default_get') {
return $.when({
previous_order_widget: '{' +
'"8": {' +
'"note": "coucou",' +
'"line_id": 8,' +
'"digits": [69, 2],' +
'"product_id": 1,' +
'"currency": "$",' +
'"supplier": "Pizza Inn",' +
'"position": "before",' +
'"price": 6.9,' +
'"product_name": "Pizza Margherita"' +
'}' +
'}'
});
}
return this._super.apply(this, arguments);
},
archs: {
"order_line,false,list": '<tree string="Orders">' +
'<field name="product_id"/>' +
'<field name="note"/>' +
'<field name="price"/>' +
'</tree>',
},
});
assert.containsNone(form, '.o_list_view tr.o_data_row',
"there should be no row in the one2many list");
assert.containsOnce(form, '.o_lunch_vignette',
"there should be a vignette");
// add an order
testUtils.dom.click(form.$('.o_lunch_vignette .o_add_button'));
assert.containsOnce(form, '.o_list_view tr.o_data_row',
"there should be one new row in the one2many list");
assert.strictEqual(form.$('.o_list_view tr.o_data_row').text(), "Pizza Margheritacoucou6.90",
"the line should contain the correct data");
form.destroy();
});
});
});
@@ -1,210 +0,0 @@
odoo.define('lunch.lunchKanbanTests', function (require) {
"use strict";
var LunchKanbanView = require('lunch.LunchKanbanView');
var testUtils = require('web.test_utils');
var createView = testUtils.createView;
QUnit.module('Views', {
beforeEach: function () {
this.data = {
product: {
fields: {
id: {string: 'ID', type: 'integer'},
},
records: [
{id: 1, name: 'Tuna sandwich', price: 3.0},
]
},
};
},
}, function () {
QUnit.module('LunchKanbanView');
QUnit.test('Simple rendering of LunchKanbanView', function (assert) {
assert.expect(8);
var lunchKanban = createView({
View: LunchKanbanView,
model: 'product',
data: this.data,
arch: '<kanban><templates><t t-name="kanban-box">' +
'<div>' +
'</div>' +
'</t></templates></kanban>',
mockRPC: function(route, args) {
if (route === '/lunch/infos') {
return $.when({
order: 1,
wallet: 20,
username: 'Marc Demo',
userimage: '',
is_manager: false,
users: [
{id: 1, name: 'Mitchell Admin'},
{id: 2, name: 'Marc Demo'},
],
total: 7.4,
state: 'new',
lines: [
{id: 1, product: ['Pizza Italiana', 7.4], toppings: [], quantity: 1.0, price: 7.4}
],
alerts: [],
});
} else if (route.startsWith('data:image/png;base64,')) {
return $.when();
}
return this._super(route, args);
},
});
var $section = $(lunchKanban.$('.o_lunch_widget_info')[0]);
// username
assert.strictEqual($section.find('div:eq(1) div:eq(0)').text().trim(), 'Marc Demo', 'Username should have been Marc Demo');
// wallet_balance
assert.strictEqual($section.find('div:eq(1) div:eq(1) span:eq(0)').text().trim(), '20.00', 'Wallet balance should have been 20');
// your order section
$section = $(lunchKanban.$('.o_lunch_widget_info')[1]);
// only one line
assert.strictEqual($section.find('div:eq(2) div.d-flex').length, 1, 'There should be only one line');
// quantity = 1
assert.strictEqual($section.find('div:eq(2) div:eq(0) span:eq(0)').text().trim(), '1', 'The line should contain only one product');
// buttons to remove and to add product
assert.strictEqual($section.find('.o_remove_product').length, 1, 'There should be a remove product button');
assert.strictEqual($section.find('.o_add_product').length, 1, 'There should be a add product button');
$section = $(lunchKanban.$('.o_lunch_widget_info')[2]);
// total
assert.strictEqual($section.find('div:eq(0) div:eq(1)').text().trim(), '7.4', 'total should be of 7.4');
// order now button
assert.strictEqual($section.find('button').length, 1, 'order now button should be available');
lunchKanban.destroy();
});
QUnit.test('User interactions', function (assert) {
assert.expect(8);
var state = 'new';
var lunchKanban = createView({
View: LunchKanbanView,
model: 'product',
data: this.data,
arch: '<kanban><templates><t t-name="kanban-box">' +
'<div>' +
'</div>' +
'</t></templates></kanban>',
mockRPC: function(route, args) {
if (route === '/lunch/infos') {
return $.when({
order: 1,
wallet: 20,
username: 'Marc Demo',
userimage: '',
is_manager: false,
users: [
{id: 1, name: 'Mitchell Admin'},
{id: 2, name: 'Marc Demo'},
],
total: 7.4,
state: state,
lines: [
{id: 1, product: ['Pizza Italiana', 7.4], toppings: [], quantity: 1.0, price: 7.4}
],
alerts: [],
});
} else if (route === '/lunch/payment_message') {
return $.when({message: 'Hello'});
} else if (route === '/lunch/pay') {
return $.when(true);
} else if (args.method === 'update_quantity') {
assert.step(args.args);
return $.when();
} else if (route.startsWith('data:image/png;base64,')) {
return $.when();
}
return this._super(route, args);
},
});
lunchKanban.$('.o_add_money').click();
var modal = $('.modal-dialog');
assert.strictEqual(modal.find('.modal-body').text().trim(), 'Hello', 'Message should have been Hello');
modal.find('.btn.btn-primary').click();
lunchKanban.$('.o_add_product').click();
lunchKanban.$('.o_remove_product').click();
state = 'ordered';
lunchKanban.$('.o_lunch_widget_order_button').click();
// state is shown
assert.strictEqual(lunchKanban.$('.o_lunch_ordered').length, 1, 'state should be shown as ordered');
// no more buttons
assert.strictEqual(lunchKanban.$('.o_remove_product').length, 0, 'button to remove product should not be shown anymore');
assert.strictEqual(lunchKanban.$('.o_add_product').length, 0, 'button to add product should not be shown anymore');
state = 'confirmed';
lunchKanban.reload();
// state is updated
assert.strictEqual(lunchKanban.$('.o_lunch_confirmed').length, 1, 'state should be shown as confirmed');
assert.verifySteps([
[[1], 1],
[[1], -1],
]);
lunchKanban.destroy();
});
QUnit.test('Manager interactions', function (assert) {
assert.expect(5);
var lunchKanban = createView({
View: LunchKanbanView,
model: 'product',
data: this.data,
arch: '<kanban><templates><t t-name="kanban-box">' +
'<div>' +
'</div>' +
'</t></templates></kanban>',
mockRPC: function(route, args) {
if (route === '/lunch/infos') {
return $.when({
order: 1,
wallet: 20,
username: 'Marc Demo',
userimage: '',
is_manager: true,
users: [
{id: 1, name: 'Mitchell Admin'},
{id: 2, name: 'Marc Demo'},
],
total: 7.4,
state: 'new',
lines: [
{id: 1, product: ['Pizza Italiana', 7.4], toppings: [], quantity: 1.0, price: 7.4}
],
alerts: [],
});
} else if (route.startsWith('data:image/png;base64,')) {
return $.when();
}
return this._super(route, args);
},
});
var select = lunchKanban.$('select.o_input');
var options = select.find('option');
assert.strictEqual(select.length, 1, 'There should be a selection field');
assert.strictEqual(options.length, 2, 'Select should have two options');
assert.strictEqual(options.text().trim(), 'Mitchell AdminMarc Demo', 'Options should be the name of the users');
assert.strictEqual($(options[0]).data('user-id'), 1, 'user id should be on option');
assert.strictEqual($(options[1]).data('user-id'), 2, 'user id should be on option');
lunchKanban.destroy();
});
});
});
+1 -2
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
from . import common
from . import test_supplier
from . import test_lunch
-79
View File
@@ -1,79 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests import common
class TestsCommon(common.TransactionCase):
def setUp(self):
super(TestsCommon, self).setUp()
self.partner_pizza_inn = self.env['res.partner'].create({
'name': 'Pizza Inn',
})
self.supplier_pizza_inn = self.env['lunch.supplier'].create({
'partner_id': self.partner_pizza_inn.id,
'send_by': 'mail',
'automatic_email_send': True,
'automatic_email_time': 11,
'recurrency': 'reccurent',
'recurrency_from': 8,
'recurrency_to': 23,
'recurrency_monday': True,
'recurrency_tuesday': True,
'recurrency_wednesday': True,
'recurrency_thursday': True,
'recurrency_friday': True,
})
self.partner_coin_gourmand = self.env['res.partner'].create({
'name': 'Coin Gourmand',
})
self.supplier_coin_gourmand = self.env['lunch.supplier'].create({
'partner_id': self.partner_coin_gourmand.id,
'send_by': 'phone',
'recurrency': 'reccurent',
'recurrency_from': 8,
'recurrency_to': 23,
'recurrency_monday': True,
'recurrency_tuesday': True,
'recurrency_wednesday': True,
'recurrency_thursday': True,
'recurrency_friday': True,
})
self.category_pizza = self.env['lunch.product.category'].create({
'name': 'Pizza',
})
self.category_sandwich = self.env['lunch.product.category'].create({
'name': 'Sandwich',
})
self.product_pizza = self.env['lunch.product'].create({
'name': 'Pizza',
'category_id': self.category_pizza.id,
'price': 9,
'supplier_id': self.supplier_pizza_inn.id,
})
self.product_sandwich_tuna = self.env['lunch.product'].create({
'name': 'Tuna Sandwich',
'category_id': self.category_sandwich.id,
'price': 3,
'supplier_id': self.supplier_coin_gourmand.id,
})
self.garniture_type = self.env['lunch.topping.type'].create({
'name': 'Graniture',
})
self.topping_olives = self.env['lunch.topping'].create({
'name': 'Olives',
'price': 0.3,
'category_id': self.category_pizza.id,
'type_id': self.garniture_type.id,
})
+143
View File
@@ -0,0 +1,143 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.exceptions import ValidationError
from odoo.tests import common
from datetime import date, datetime
from dateutil import relativedelta
class Test_Lunch(common.TransactionCase):
def setUp(self):
"""*****setUp*****"""
super(Test_Lunch, self).setUp()
self.demo_user = self.env['res.users'].search([('name', '=', 'Marc Demo')])
self.product_Bolognese_id = self.env.ref('lunch.product_Bolognese')
self.new_id_order = self.env['lunch.order'].create({
'user_id': self.demo_user.id,
'order_line_ids': '[]',
})
self.new_id_order_line = self.env['lunch.order.line'].create({
'order_id': self.new_id_order.id,
'product_id': self.product_Bolognese_id.id,
'note': '+Emmental',
'cashmove': [],
'price': self.product_Bolognese_id.price,
})
def test_00_lunch_order(self):
"""Change the state of an order line from 'new' to 'ordered'. Check that there are no cashmove linked to that order line"""
self.order_one = self.new_id_order_line
#we check that our order_line is a 'new' one and that there are no cashmove linked to that order_line:
self.assertEqual(self.order_one.state, 'new')
self.assertEqual(list(self.order_one.cashmove), [])
#we order that orderline so it's state will be 'ordered'
self.order_one.order()
self.order_one = self.new_id_order_line
#we check that our order_line is a 'ordered' one and that there are no cashmove linked to that order_line:
self.assertEqual(self.order_one.state, 'ordered')
self.assertEqual(list(self.order_one.cashmove), [])
def test_01_lunch_order(self):
"""Change the state of an order line from 'new' to 'ordered' then to 'confirmed'. Check that there is a cashmove linked to the order line"""
self.test_00_lunch_order()
#We receive the order so we confirm the order line so it's state will be 'confirmed'
#A cashmove will be created and we will test that the cashmove amount equals the order line price
self.order_one.confirm()
self.order_one = self.new_id_order_line
#we check that our order_line is a 'confirmed' one and that there are a cashmove linked to that order_line with an amount equals to the order line price:
self.assertEqual(self.order_one.state, 'confirmed')
self.assertTrue(self.order_one.cashmove)
self.assertTrue(self.order_one.cashmove[0].amount == -self.order_one.price)
def test_02_lunch_order(self):
"""Change the state of an order line from 'confirmed' to 'cancelled' and check that the cashmove linked to that order line will be deleted"""
self.test_01_lunch_order()
#We have a confirmed order with its associate cashmove
#We execute the cancel function
self.order_one.cancel()
self.order_one = self.new_id_order_line
#We check that the state is cancelled and that the cashmove has been deleted
self.assertEqual(self.order_one.state, 'cancelled')
self.assertFalse(self.order_one.cashmove)
def test_03_lunch_alert(self):
""" specify a lunch alert and make sure it is not possible to make an
order that day
"""
alert_01 = self.env['lunch.alert'].create({
'message': 'Order Bolognese only on Tuesday',
'partner_id': self.product_Bolognese_id.supplier.id,
'alert_type': 'week',
'tuesday': True
})
wednesday = date.today() + relativedelta.relativedelta(days=7, weekday=2)
next_tuesday = datetime.today() + relativedelta.relativedelta(days=14, weekday=1)
next_wednesday = datetime.today() + relativedelta.relativedelta(days=14, weekday=2)
# We check that we can make an order on a Tuesday
order_01 = self.env['lunch.order'].create({
'user_id': self.demo_user.id,
'date': date.today() + relativedelta.relativedelta(days=7, weekday=1),
'order_line_ids': [(0, 0, {
'product_id': self.product_Bolognese_id.id,
})]
})
order_01.order_line_ids.order()
with self.assertRaises(ValidationError):
# Planning an order to an non-authorized day must not be possible
order_02 = self.env['lunch.order'].create({
'user_id': self.demo_user.id,
'date': wednesday,
'order_line_ids': [(0, 0, {
'product_id': self.product_Bolognese_id.id,
})]
})
alert_02 = self.env['lunch.alert'].create({
'message': 'Working one more day',
'partner_id': self.product_Bolognese_id.supplier.id,
'alert_type': 'specific',
'specific_day': wednesday,
})
# should now be possible to order tomorrow
order_03 = self.env['lunch.order'].create({
'user_id': self.demo_user.id,
'date': wednesday,
'order_line_ids': [(0, 0, {
'product_id': self.product_Bolognese_id.id,
})]
})
# should be possible to planned on an allowed day
order_04 = self.env['lunch.order'].create({
'user_id': self.demo_user.id,
'date': next_tuesday,
'order_line_ids': [(0, 0, {
'product_id': self.product_Bolognese_id.id,
})]
})
order_04.order_line_ids.order()
# combinaision of alerts should work
alert_03 = self.env['lunch.alert'].create({
'message': 'Order Bolognese also on Wednesday',
'partner_id': self.product_Bolognese_id.supplier.id,
'alert_type': 'week',
'wednesday': True
})
# Planning an order Wednesday must now be possible
order_05 = self.env['lunch.order'].create({
'user_id': self.demo_user.id,
'date': next_wednesday,
'order_line_ids': [(0, 0, {
'product_id': self.product_Bolognese_id.id,
})]
})
order_05.order_line_ids.order()
-163
View File
@@ -1,163 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime
from unittest.mock import patch
from odoo import fields
from odoo.addons.lunch.tests.common import TestsCommon
class TestSupplier(TestsCommon):
def setUp(self):
super(TestSupplier, self).setUp()
self.monday_3am = datetime(2018, 10, 29, 3, 0, 0)
self.monday_10am = datetime(2018, 10, 29, 10, 0, 0)
self.monday_1pm = datetime(2018, 10, 29, 13, 0, 0)
self.monday_8pm = datetime(2018, 10, 29, 20, 0, 0)
self.saturday_3am = datetime(2018, 11, 3, 3, 0, 0)
self.saturday_10am = datetime(2018, 11, 3, 10, 0, 0)
self.saturday_1pm = datetime(2018, 11, 3, 13, 0, 0)
self.saturday_8pm = datetime(2018, 11, 3, 20, 0, 0)
def test_compute_available_today(self):
tests = [(self.monday_3am, False), (self.monday_10am, True),
(self.monday_1pm, True), (self.monday_8pm, True),
(self.saturday_3am, False), (self.saturday_10am, False),
(self.saturday_1pm, False), (self.saturday_8pm, False)]
for value, result in tests:
with patch.object(fields.Datetime, 'now', return_value=value) as _:
assert self.supplier_pizza_inn.available_today == result,\
'supplier pizza inn should %s considered available on %s' % ('be' if result else 'not be', value)
self.env['lunch.supplier'].invalidate_cache(['available_today'], [self.supplier_pizza_inn.id])
def test_search_available_today(self):
'''
This test checks that _search_available_today returns a valid domain
'''
Supplier = self.env['lunch.supplier']
tests = [(self.monday_3am, 3.0, 'monday'), (self.monday_10am, 10.0, 'monday'),
(self.monday_1pm, 13.0, 'monday'), (self.monday_8pm, 20.0, 'monday'),
(self.saturday_3am, 3.0, 'saturday'), (self.saturday_10am, 10.0, 'saturday'),
(self.saturday_1pm, 13.0, 'saturday'), (self.saturday_8pm, 20.0, 'saturday')]
# It should return an empty domain if we compare to values other than datetime
assert Supplier._search_available_today('>', 7) == []
assert Supplier._search_available_today('>', True) == []
for value, rvalue, dayname in tests:
with patch.object(fields.Datetime, 'now', return_value=value) as _:
assert Supplier._search_available_today('=', True) == ['|', '&', '&', ('recurrency', '=', 'once'), ('recurrency_date_from', '<=', value),
('recurrency_date_to', '>=', value), '&', '&', ('recurrency_%s' % (dayname), '=', True),
('recurrency_from', '<=', rvalue), ('recurrency_to', '>=', rvalue)], 'Wrong domain generated for values (%s, %s)' % (value, rvalue)
with patch.object(fields.Datetime, 'now', return_value=self.monday_10am) as _:
assert self.supplier_pizza_inn in Supplier.search([('available_today', '=', True)])
def test_auto_email_send(self):
order = self.env['lunch.order'].create({})
line = self.env['lunch.order.line'].create({
'order_id': order.id,
'product_id': self.product_pizza.id,
})
assert len(order.supplier_ids) == 1 and self.supplier_pizza_inn in order.supplier_ids
order.action_order()
assert order.state == 'ordered'
assert line.state == 'ordered'
with patch.object(fields.Datetime, 'now', return_value=self.monday_1pm):
with patch.object(fields.Date, 'today', return_value=self.monday_1pm.date()):
self.supplier_pizza_inn._auto_email_send()
assert order.state == 'confirmed'
assert line.state == 'confirmed'
assert order.mail_sent
# Do the same but this time order contains multiple lines with products from at least two suppliers
# Check if the lines for pizza_inn have been sent, and put in state='confirmed' only those lines should be
# moreover the order should not yet be in state='confirmed' as there still are some lines to confirm
order = self.env['lunch.order'].create({})
line = self.env['lunch.order.line'].create({
'order_id': order.id,
'product_id': self.product_pizza.id,
'topping_ids': [(6, 0, [self.topping_olives.id])],
})
line2 = self.env['lunch.order.line'].create({
'order_id': order.id,
'product_id': self.product_sandwich_tuna.id,
})
assert len(order.supplier_ids) == 2 and self.supplier_coin_gourmand in order.supplier_ids and self.supplier_pizza_inn in order.supplier_ids
order.action_order()
assert order.state == 'ordered'
assert line.state == 'ordered'
assert line2.state == 'ordered'
with patch.object(fields.Datetime, 'now', return_value=self.monday_1pm):
with patch.object(fields.Date, 'today', return_value=self.monday_1pm.date()):
self.supplier_pizza_inn._auto_email_send()
assert order.state == 'confirmed'
assert line.state == 'confirmed'
assert line2.state == 'ordered'
assert order.mail_sent
# Check with multiple orders
order_1 = self.env['lunch.order'].create({})
line_1_1 = self.env['lunch.order.line'].create({
'order_id': order_1.id,
'product_id': self.product_pizza.id,
})
line_1_2 = line_1_1 = self.env['lunch.order.line'].create({
'order_id': order_1.id,
'product_id': self.product_pizza.id,
'topping_ids': [(6, 0, [self.topping_olives.id])]
})
order_2 = self.env['lunch.order'].create({})
line_2_1 = self.env['lunch.order.line'].create({
'order_id': order_2.id,
'product_id': self.product_sandwich_tuna.id
})
line_2_2 = self.env['lunch.order.line'].create({
'order_id': order_2.id,
'product_id': self.product_pizza.id,
})
order_3 = self.env['lunch.order'].create({})
line_3_1 = self.env['lunch.order.line'].create({
'order_id': order_3.id,
'product_id': self.product_sandwich_tuna.id
})
(order_1 | order_2 | order_3).action_order()
assert all(order.state == 'ordered' for order in [order_1, order_2, order_3])
assert all(line.state == 'ordered' for line in [line_1_1, line_1_2, line_2_1, line_2_2, line_3_1])
with patch.object(fields.Datetime, 'now', return_value=self.monday_1pm):
with patch.object(fields.Date, 'today', return_value=self.monday_1pm.date()):
self.supplier_pizza_inn._auto_email_send()
assert all(order.state == 'confirmed' for order in [order_1, order_2])
assert order_3.state == 'ordered'
assert all(line.state == 'confirmed' for line in [line_1_1, line_1_2, line_2_2])
assert all(line.state == 'ordered' for line in [line_2_1, line_3_1])
assert all(order.mail_sent for order in [order_1, order_2])
assert not order_3.mail_sent
+95 -21
View File
@@ -6,7 +6,7 @@
<field name="arch" type="xml">
<search string="Search">
<field name="message"/>
<filter name="inactive" string="Currently inactive" domain="[('available_today','=',False)]"/>
<filter name="inactive" string="Archived" domain="[('active','=',False)]"/>
</search>
</field>
</record>
@@ -17,9 +17,9 @@
<field name="arch" type="xml">
<tree>
<field name="message"/>
<field name="recurrency"/>
<field name='recurrency_from' widget='float_time'/>
<field name='recurrency_to' widget='float_time'/>
<field name="alert_type"/>
<field name='start_hour' widget='float_time'/>
<field name='end_hour' widget='float_time'/>
</tree>
</field>
</record>
@@ -28,33 +28,107 @@
<field name="name">lunch.alert.form</field>
<field name="model">lunch.alert</field>
<field name="arch" type="xml">
<form string="alert form">
<form>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="toggle_active" type="object"
class="oe_stat_button" icon="fa-archive">
<field name="active" widget="boolean_button"
options='{"terminology": "archive"}'/>
</button>
</div>
<group>
<field name="recurrency"/>
<field name="recurrency_date" attrs="{'invisible': [('recurrency', '!=', 'once')]}"/>
<field name="recurrency_monday" attrs="{'invisible': [('recurrency', '!=', 'reccurent')]}"/>
<field name="recurrency_tuesday" attrs="{'invisible': [('recurrency', '!=', 'reccurent')]}"/>
<field name="recurrency_wednesday" attrs="{'invisible': [('recurrency', '!=', 'reccurent')]}"/>
<field name="recurrency_thursday" attrs="{'invisible': [('recurrency', '!=', 'reccurent')]}"/>
<field name="recurrency_friday" attrs="{'invisible': [('recurrency', '!=', 'reccurent')]}"/>
<field name="recurrency_saturday" attrs="{'invisible': [('recurrency', '!=', 'reccurent')]}"/>
<field name="recurrency_sunday" attrs="{'invisible': [('recurrency', '!=', 'reccurent')]}"/>
<field name="recurrency_from" widget="float_time"/>
<field name="recurrency_to" widget="float_time"/>
<group name="schedule" string="Schedule">
<group colspan="4">
<field name="alert_type"/>
<field name="specific_day"
attrs="{'invisible': [('alert_type','!=','specific')], 'required':[('alert_type','=','specific')]}"/>
<field name='start_hour' widget='float_time' />
<field name='end_hour' widget='float_time' />
</group>
<group name="weekdays" col="4" attrs="{'invisible': [('alert_type','!=','week')]}">
<field name="monday"/>
<field name="tuesday"/>
<field name="wednesday"/>
<field name="thursday"/>
<field name="friday"/>
<field name="saturday"/>
<field name="sunday"/>
</group>
</group>
<group name="vendor" string="Vendor">
<group name="partner" colspan="4">
<field name="partner_id"/>
<field name="display" invisible="True"/>
</group>
<div class="oe_grey" colspan="4"
attrs="{'invisible': ['|', ('partner_id', '=', False), ('display', '=', True)]}">
Products from this vendor can <strong>not</strong> be ordered at that moment.
</div>
<div class="oe_grey" colspan="4"
attrs="{'invisible': ['|', ('partner_id', '=', False), ('display', '=', False)]}">
Products from this vendor can be ordered at that moment.
</div>
</group>
</group>
<group string='Message'>
<field name='message' nolabel='1' placeholder="Write the message you want to display during the defined period..."/>
</group>
<label for='message'/>
<field name="message"/>
</sheet>
</form>
</field>
</record>
<record id="view_lunch_alert_kanban" model="ir.ui.view">
<field name="name">lunch.alert.kanban</field>
<field name="model">lunch.alert</field>
<field name="arch" type="xml">
<kanban class="o_kanban_mobile">
<field name="message"/>
<field name="alert_type"/>
<field name='start_hour'/>
<field name='end_hour'/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_global_click">
<div class="o_kanban_record_top">
<strong class="o_kanban_record_title"><t t-esc="record.message.value"/></strong>
</div>
<div class="row mb4">
<div class="col-6">
Between: <t t-esc="record.start_hour.value"/> -
<t t-esc="record.end_hour.value"/>
</div>
<div class="col-6">
<span class="badge badge-pill float-right">
<t t-esc="record.alert_type.value"/>
</span>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record id="lunch_alert_action" model="ir.actions.act_window">
<field name="name">Lunch Alerts</field>
<field name="name">Alerts</field>
<field name="res_model">lunch.alert</field>
<field name="view_mode">tree,kanban,form</field>
<field name="search_view_id" ref="lunch_alert_view_search"/>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="lunch_alert_view_tree"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new lunch alert
</p><p>
Alerts are used to warn employee from possible issues concerning the lunch orders.
To create a lunch alert you have to define its recurrence, the time interval during which the alert should be executed and the message to display.
</p><p>
Example:<br/>
Recurency: Everyday,<br/>
Time interval: from 00h00 am to 11h59 pm,<br/>
Message: "You must order before 10h30 am".
</p>
</field>
</record>
</odoo>
+21 -10
View File
@@ -19,6 +19,7 @@
<field name="arch" type="xml">
<form string='Orders Form' class="o_lunch">
<header>
<button name="%(action_lunch_order_line_lucky)d" type="action" string="Feeling Lucky" class="oe_highlight"/>
<field name='state' widget='statusbar' statusbar_visible='new,confirmed'/>
</header>
<sheet>
@@ -42,15 +43,24 @@
<field name='user_id' class="oe_inline" required='1'/>
<field name='date'/>
</group>
<div>
<div attrs="{'invisible': ['|',('state','!=','new'),('alerts','=',False)]}" class="alert alert-danger oe_edit_only" role="alert">
<field name='alerts'/>
</div>
</div>
</group>
<div>
<field name='previous_order_widget' widget='previous_order' class='o_lunch_last_orders'/>
</div>
<separator string='Select your order'/>
<field name='order_line_ids'>
<tree string='List' editable='bottom'>
<field name='product_id' context="{'lunch_date': parent.date}"/>
<field name='note' />
<field name='price'/>
<field name='supplier_id' invisible="1"/>
<field name='supplier' invisible="1"/>
<field name="state" invisible="1"/>
</tree>
<form string='Lunch Order Form'>
@@ -85,7 +95,7 @@
<field name="user_id"/>
<group expand="0" string="Group By">
<filter name="group_by_order_id" string="By Order" context="{'group_by':'order_id'}"/>
<filter name="group_by_supplier" string="By Vendor" context="{'group_by':'supplier_id'}"/>
<filter name="group_by_supplier" string="By Vendor" context="{'group_by':'supplier'}"/>
<filter name="group_by_date" string="Order Date" context="{'group_by':'date'}" help="Vendor Orders by Date"/>
</group>
</search>
@@ -98,14 +108,15 @@
<field name="arch" type="xml">
<tree string="Order lines Tree" create="false" edit="false">
<field name='date'/>
<field name='supplier_id' invisible='1'/>
<field name='supplier' invisible='1'/>
<field name='product_id'/>
<field name='note'/>
<field name='state'/>
<field name='user_id'/>
<field name='price' sum="Total" string="Price"/>
<button name="action_confirm" string="Confirm" type="object" icon="fa-phone text-success" attrs="{'invisible': [('state', '!=', 'ordered')]}" groups="lunch.group_lunch_manager"/>
<button name="action_cancel" string="Cancel" type="object" icon="fa-times-circle text-danger" attrs="{'invisible': [('state', 'in', ['cancelled', 'confirmed'])]}" groups="lunch.group_lunch_manager"/>
<button name="order" string="Order" type="object" icon="fa-phone text-success" attrs="{'invisible': ['|',('state','=','confirmed'),('state','=','ordered')]}" groups="lunch.group_lunch_manager"/>
<button name="confirm" string="Receive" type="object" icon="fa-check text-success" attrs="{'invisible': [('state','!=','ordered')]}" groups="lunch.group_lunch_manager"/>
<button name="cancel" string="Cancel" type="object" icon="fa-times-circle text-danger" attrs="{'invisible': [('state','=','cancelled')]}" groups="lunch.group_lunch_manager"/>
</tree>
</field>
</record>
@@ -115,11 +126,11 @@
<field name="model">lunch.order.line</field>
<field name="arch" type="xml">
<kanban>
<field name="product_id"/>
<field name="note"/>
<field name="state"/>
<field name="user_id"/>
<field name="date"/>
<field name='product_id'/>
<field name='note'/>
<field name='state'/>
<field name='user_id'/>
<field name='date'/>
<field name="price"/>
<templates>
<t t-name="kanban-box">
+33 -67
View File
@@ -7,12 +7,12 @@
<search string="Product Search">
<field name="name"/>
<field name="category_id"/>
<field name="supplier_id"/>
<field name="supplier"/>
<field name="description"/>
<filter name="inactive" string="Archived" domain="[('active','=',False)]"/>
<group expand="0" string="Group By">
<filter name="group_by_supplier" string="Vendor" context="{'group_by': 'supplier_id'}"/>
<filter name="group_by_category" string="Product Category" context="{'group_by': 'category_id'}"/>
<filter name="supplier" string="Vendor" context="{'group_by': 'supplier'}"/>
<filter name="category" string="Product Category" context="{'group_by': 'category_id'}"/>
<filter name="inactive" string="Archived" domain="[('active','=',False)]"/>
</group>
</search>
</field>
@@ -25,7 +25,7 @@
<tree string="Products Tree">
<field name="name"/>
<field name="category_id"/>
<field name="supplier_id"/>
<field name="supplier"/>
<field name="description"/>
<field name="price"/>
</tree>
@@ -37,6 +37,8 @@
<field name="model">lunch.product</field>
<field name="arch" type="xml">
<form string="Products Form">
<header>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="toggle_active" type="object"
@@ -45,20 +47,18 @@
options='{"terminology": "archive"}'/>
</button>
</div>
<field name="image" widget="image" class="oe_avatar" options="{'preview_image': 'image_medium'}"/>
<label for="name"/>
<field name='name'/>
<group>
<field name='category_id'/>
<field name='supplier_id'/>
<field name='price'/>
<group>
<field name='name'/>
<field name='category_id'/>
</group>
<group>
<field name='supplier'/>
<field name='price'/>
</group>
</group>
<notebook>
<page string="Description" name="description">
<label for='description'/>
<field name='description'/>
</page>
</notebook>
<label for='description'/>
<field name='description'/>
</sheet>
</form>
</field>
@@ -68,37 +68,28 @@
<field name="name">lunch.product.kanban</field>
<field name="model">lunch.product</field>
<field name="arch" type="xml">
<kanban js_class="lunch_kanban" create="0" edit="0" group_create="0" group_edit="0" group_delete="0" class="o_kanban_mobile">
<field name="id"/>
<kanban class="o_kanban_mobile">
<field name="name"/>
<field name="category_id"/>
<field name="supplier_id"/>
<field name="supplier"/>
<field name="description"/>
<field name="price"/>
<field name="image_small"/>
<field name="already_ordered"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click o_kanban_record_has_image_fill">
<div class="o_kanban_image_fill_left d-none d-md-block flex-grow-0"
t-attf-style="background-image:url('#{kanban_image('lunch.product', 'image_medium', record.id.raw_value)}')"/>
<div class="d-flex flex-column flex-grow-1">
<div class="o_kanban_record_top d-flex flex-row">
<div class="o_kanban_record_headings flex-grow-1">
<i t-if="record.already_ordered.raw_value" class="fa fa-heart mr-1 o_pink"/><strong class="o_kanban_record_title"><t t-esc="record.name.value"/></strong>
</div>
<span class="badge badge-pill">
Price: <t t-esc="record.price.value"/>
</span>
</div>
<div class="o_kanban_record_bottom d-flex flex-column">
<div>
<t t-esc="record.supplier_id.value"/>
</div>
<div>
<t t-esc="record.description.value"/>
</div>
<div t-attf-class="oe_kanban_global_click">
<div class="o_kanban_record_top">
<div class="o_kanban_record_headings">
<strong class="o_kanban_record_title"><t t-esc="record.name.value"/></strong>
</div>
<span class="badge badge-pill float-right">
Price: <t t-esc="record.price.value"/>
</span>
</div>
<div>
<t t-esc="record.supplier.value"/>
</div>
<div>
<t t-esc="record.description.value"/>
</div>
</div>
</t>
@@ -107,16 +98,6 @@
</field>
</record>
<record id="lunch_product_category_view_tree" model="ir.ui.view">
<field name="name">Product category Tree</field>
<field name="model">lunch.product.category</field>
<field name="arch" type="xml">
<tree string="Products List">
<field name='name' string="Product Category: "/>
</tree>
</field>
</record>
<record id="lunch_product_category_view_form" model="ir.ui.view">
<field name="name">Product category Form</field>
<field name="model">lunch.product.category</field>
@@ -124,14 +105,7 @@
<form string="Products Form">
<sheet>
<group>
<field name="name" string="Product Category: "/>
<field name="topping_ids">
<tree editable="bottom">
<field name="type_id"/>
<field name="name"/>
<field name="price"/>
</tree>
</field>
<field name='name' string="Product Category: "/>
</group>
</sheet>
</form>
@@ -151,14 +125,6 @@
</field>
</record>
<record id="lunch_product_action_order" model="ir.actions.act_window">
<field name="name">Order</field>
<field name="res_model">lunch.product</field>
<field name="view_mode">kanban</field>
<field name="search_view_id" ref="lunch_product_view_search"/>
<field name="domain">[('supplier_id.available_today', '=', True)]</field>
</record>
<record id="lunch_product_category_action" model="ir.actions.act_window">
<field name="name">Product Categories</field>
<field name="res_model">lunch.product.category</field>
-116
View File
@@ -1,116 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="lunch_supplier_view_tree" model="ir.ui.view">
<field name="name">lunch.supplier.view.tree</field>
<field name="model">lunch.supplier</field>
<field name="arch" type="xml">
<tree>
<field name="name"/>
<field name="phone"/>
<field name="email"/>
</tree>
</field>
</record>
<record id="lunch_supplier_view_form" model="ir.ui.view">
<field name="name">lunch.supplier.view.form</field>
<field name="model">lunch.supplier</field>
<field name="arch" type="xml">
<form>
<sheet>
<field name="image" widget="image" class="oe_avatar" options="{'preview_image': 'image_medium'}"/>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1><field name="name" required="1"/></h1>
</div>
<group>
<field name="partner_id"/>
<label for="street" string="Address"/>
<div class="o_address_format">
<field name="street" placeholder="Street..." class="o_address_street"/>
<field name="street2" placeholder="Street 2..." class="o_address_street"/>
<field name="city" placeholder="City" class="o_address_city"/>
<field name="state_id" class="o_address_state" placeholder="State" options="{'no_open': True}" context="{'country_id': country_id, 'zip': zip_code}"/>
<field name="zip_code" placeholder="ZIP" class="o_address_zip"/>
<field name="country_id" placeholder="Country" class="o_address_country" options="{'no_open': True, 'no_create': True}"/>
</div>
</group>
<group>
<field name="vat"/>
<field name="email"/>
<field name="phone"/>
</group>
<group>
<field name="responsible_id"/>
</group>
<notebook colspan="4">
<page string="Availability">
<group>
<field name="tz"/>
<field name="recurrency"/>
<field name="recurrency_date" attrs="{'invisible': [('recurrency', '!=', 'once')]}"/>
<field name="recurrency_monday" attrs="{'invisible': [('recurrency', '!=', 'reccurent')]}"/>
<field name="recurrency_tuesday" attrs="{'invisible': [('recurrency', '!=', 'reccurent')]}"/>
<field name="recurrency_wednesday" attrs="{'invisible': [('recurrency', '!=', 'reccurent')]}"/>
<field name="recurrency_thursday" attrs="{'invisible': [('recurrency', '!=', 'reccurent')]}"/>
<field name="recurrency_friday" attrs="{'invisible': [('recurrency', '!=', 'reccurent')]}"/>
<field name="recurrency_saturday" attrs="{'invisible': [('recurrency', '!=', 'reccurent')]}"/>
<field name="recurrency_sunday" attrs="{'invisible': [('recurrency', '!=', 'reccurent')]}"/>
<field name="recurrency_from" widget="float_time"/>
<field name="recurrency_to" widget="float_time"/>
</group>
</page>
<page string="Description">
<label for="description" string="Type your description here" class="oe_edit_only"/>
<field name="description"/>
</page>
<page string="Order">
<label for="send_by"/>
<field name="send_by" widget="radio"/>
<div>
<label for="automatic_email_send" string="Send Orders automatically at" attrs="{'invisible': [('send_by', '!=', 'mail')]}"/>
<field name="automatic_email_time" widget="float_time" attrs="{'invisible': [('send_by', '!=', 'mail')]}"/>
</div>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="lunch_supplier_view_kanban" model="ir.ui.view">
<field name="name">lunch.supplier.view.kanban</field>
<field name="model">lunch.supplier</field>
<field name="arch" type="xml">
<kanban>
<field name="partner_id"/>
<field name="image_small"/>
<field name="city"/>
<field name="country_id"/>
<field name="email"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click o_kanban_record_has_image_fill o_res_partner_kanban">
<div class="o_kanban_image_fill_left d-none d-md-block" t-attf-style="background-image:url('#{kanban_image('res.partner', 'image_medium', record.partner_id.raw_value)}')"/>
<div class="oe_kanban_details">
<strong class="o_kanban_record_title oe_partner_heading"><field name="display_name"/></strong>
<ul>
<li t-if="record.city.raw_value and !record.country_id.raw_value"><field name="city"/></li>
<li t-if="!record.city.raw_value and record.country_id.raw_value"><field name="country_id"/></li>
<li t-if="record.city.raw_value and record.country_id.raw_value"><field name="city"/>, <field name="country_id"/></li>
<li t-if="record.email.raw_value" class="o_text_overflow"><field name="email"/></li>
</ul>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record id="lunch_vendors_action" model="ir.actions.act_window">
<field name="name">Vendors</field>
<field name="res_model">lunch.supplier</field>
<field name="view_mode">kanban,tree,form</field>
</record>
</odoo>
+3 -14
View File
@@ -2,25 +2,14 @@
<odoo>
<template id="assets_backend" name="lunch assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" type="text/scss" href="/lunch/static/src/scss/lunch_kanban.scss"/>
<script type="text/javascript" src="/lunch/static/src/js/lunch_kanban_widget.js"></script>
<script type="text/javascript" src="/lunch/static/src/js/lunch_payment_dialog.js"></script>
<script type="text/javascript" src="/lunch/static/src/js/lunch_kanban_view.js"></script>
<script type="text/javascript" src="/lunch/static/src/js/lunch_kanban_controller.js"></script>
<script type="text/javascript" src="/lunch/static/src/js/lunch_kanban_renderer.js"></script>
<script type="text/javascript" src="/lunch/static/src/js/lunch_kanban_record.js"></script>
<script type="text/javascript" src="/lunch/static/src/js/lunch_kanban_model.js"></script>
<link rel="stylesheet" type="text/scss" href="/lunch/static/src/scss/lunch.scss"/>
<script type="text/javascript" src="/lunch/static/src/js/lunch.js"></script>
</xpath>
</template>
<template id="qunit_suite" name="lunch tests" inherit_id="web.qunit_suite">
<xpath expr="//t[@t-set='head']" position="inside">
<script type="text/javascript" src="/lunch/static/tests/test_lunch_kanban.js"></script>
<script type="text/javascript" src="/lunch/static/tests/lunch_tests.js"/>
</xpath>
</template>
<template id="lunch_payment_dialog" name="Lunch Payment Dialog">
To add some money to your wallet, please contact your lunch manager.
</template>
</odoo>
+4 -5
View File
@@ -6,7 +6,7 @@
<menuitem name="Manager" parent="menu_lunch" id="menu_lunch_admin" sequence="51" groups="group_lunch_manager"/>
<menuitem name="Configuration" parent="menu_lunch" id="menu_lunch_config" sequence="53" groups="group_lunch_manager"/>
<menuitem name="New Order" parent="menu_lunch_title" id="lunch_order_menu_form" action="lunch.lunch_product_action_order" sequence="1"/>
<menuitem name="New Order" parent="menu_lunch_title" id="lunch_order_menu_form" action="lunch_order_action_form" sequence="1"/>
<menuitem name="Previous Orders" parent="menu_lunch_title" id="lunch_order_menu_tree" action="lunch_order_line_action" sequence="2"/>
<menuitem name="Your Lunch Account" parent="menu_lunch_title" id="lunch_cashmove_menu_form" action="lunch_cashmove_action_account" sequence="3"/>
@@ -15,10 +15,9 @@
<menuitem name="Control Accounts" parent="menu_lunch_admin" id="lunch_cashmove_menu_control_accounts" action="lunch_cashmove_action_control_accounts"/>
<menuitem name="Employee Payments" parent="menu_lunch_admin" id="lunch_cashmove_menu_payment" action="lunch_cashmove_action_payment"/>
<menuitem name="Vendors" parent="menu_lunch_config" id="lunch_vendors_menu" action="lunch_vendors_action" sequence="1"/>
<menuitem name="Products" parent="menu_lunch_config" id="lunch_product_menu" action="lunch_product_action" sequence="2"/>
<menuitem name="Product Categories" parent="menu_lunch_config" id="lunch_product_category_menu" action="lunch_product_category_action" sequence="3"/>
<menuitem name="Alerts" parent="menu_lunch_config" id="lunch_alert_menu" action="lunch_alert_action" sequence="4"/>
<menuitem name="Products" parent="menu_lunch_config" id="lunch_product_menu" action="lunch_product_action"/>
<menuitem name="Product Categories" parent="menu_lunch_config" id="lunch_product_category_menu" action="lunch_product_category_action" />
<menuitem name="Alerts" parent="menu_lunch_config" id="lunch_alert_menu" action="lunch_alert_action" />
<record id="action_server_lunch_archive_product" model="ir.actions.server">
+1 -1
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import lunch_line_wizard
from . import lucky_order
+44
View File
@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import random
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class LunchOrderLineLucky(models.TransientModel):
_name = 'lunch.order.line.lucky'
_description = 'Lunch Order Line Lucky'
def _default_supplier(self):
suppliers_obj = self.env['lunch.product'].search([]).mapped("supplier")
return [(4, supplier.id) for supplier in suppliers_obj]
product_id = fields.Many2one('lunch.product', 'Product', store=True)
supplier_ids = fields.Many2many(comodel_name='res.partner', string='Vendor', domain=lambda self: [("id", "in", self.env['lunch.product'].search([]).mapped("supplier").ids)])
is_max_budget = fields.Boolean("I'm not feeling rich", help="Enable this option to set a maximal budget for your lucky order.", store=True)
max_budget = fields.Float('Max Budget', store=True)
@api.multi
def random_pick(self):
"""
To pick a random product from the selected suppliers, and create an order with this one
"""
self.ensure_one()
if self.is_max_budget:
products_obj = self.env['lunch.product'].search([('supplier', "in", self.supplier_ids.ids), ('price', '<=', self.max_budget)])
else:
products_obj = self.env['lunch.product'].search([('supplier', "in", self.supplier_ids.ids)])
if len(products_obj) != 0:
random_product_obj = self.env['lunch.product'].browse([random.choice(products_obj.ids)])
order_line = self.env['lunch.order.line'].create({
'product_id': random_product_obj.id,
'order_id': self._context['active_id']
})
else:
raise UserError(_('No product is matching your request. Now you will starve to death.'))
+35
View File
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_lunch_order_line_lucky" model="ir.ui.view">
<field name="name">view.lunch.order.line.lucky</field>
<field name="model">lunch.order.line.lucky</field>
<field name="arch" type="xml">
<form string="I'm feeling lucky today !">
<group string="Select your vendor">
<field name="is_max_budget"/>
<field name="max_budget" attrs="{'required': [('is_max_budget', '=', True)], 'invisible': [('is_max_budget', '=', False)]}"/>
<field name="supplier_ids" widget="many2many_checkboxes"/>
</group>
<footer>
<button name="random_pick" string="I'm feeling lucky" type="object" class="oe_highlight"/>
<button string="Cancel" class="btn btn-secondary" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="action_lunch_order_line_lucky" model="ir.actions.act_window">
<field name="name">I'm feeling lucky today !</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">lunch.order.line.lucky</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="view_id" ref="view_lunch_order_line_lucky"/>
<field name="context">{'record_id' : active_id}</field>
<field name="target">new</field>
</record>
</data>
</odoo>
-91
View File
@@ -1,91 +0,0 @@
# -*- 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 UserError
class LunchOrderLineWizard(models.TransientModel):
_name = 'lunch.order.line.temp'
_description = 'Lunch Order Line Temp'
def _default_topping_ids(self):
last_time_ordered = self.env['lunch.order.line'].search([('product_id', '=', self.env.context.get('default_product_id', 0))],
order="date desc", limit=1)
return last_time_ordered.topping_ids
currency_id = fields.Many2one('res.currency', default=lambda self: self.env.user.company_id.currency_id)
product_id = fields.Many2one('lunch.product', string='Product ID')
product_description = fields.Text('Description', related='product_id.description')
product_name = fields.Char('Product', related='product_id.name')
product_category = fields.Many2one('lunch.product.category', related='product_id.category_id')
topping_ids = fields.Many2many('lunch.topping', string="Extra Garniture", domain="[('category_id', '=', product_category)]", default=_default_topping_ids)
available_toppings = fields.Boolean(help='Are toppings available for this product', compute='_compute_available_toppings')
quantity = fields.Float('Quantity', default=1)
price_total = fields.Float('Total Price', compute='_compute_price_total')
note = fields.Text('Special Instructions')
user_id = fields.Many2one('res.users', default=lambda self: self.env.user.id)
@api.depends('product_id')
def _compute_available_toppings(self):
for wizard in self:
wizard.available_toppings = bool(wizard.env['lunch.topping'].search_count([('category_id', '=', wizard.product_category.id)]))
@api.depends('product_id', 'topping_ids', 'quantity')
def _compute_price_total(self):
for wizard in self:
wizard.price_total = wizard.quantity * (wizard.product_id.price + sum(wizard.topping_ids.mapped('price')))
def find_current_order(self):
order_id = self.env['lunch.order'].search([('state', '!=', 'cancel'),
('user_id', '=', self.user_id.id),
('date', '=', fields.Date.today())], limit=1)
if not order_id:
order_id = self.env['lunch.order'].create({})
return order_id
def add_to_cart(self):
self.ensure_one()
order_id = self.find_current_order()
# Do not add a line to confirmed orders as they are uneditable
if order_id.state == 'confirmed':
return
lines = order_id.order_line_ids.filtered(lambda line: line.product_id == self.product_id and line.topping_ids == self.topping_ids)
if lines:
line = lines[0]
line.quantity += 1
else:
line = self.env['lunch.order.line'].create({
'order_id': order_id.id,
'product_id': self.product_id.id,
'topping_ids': [(6, 0, self.topping_ids.ids)],
'quantity': self.quantity,
})
wallet_balance = self.env['lunch.cashmove'].get_wallet_balance(self.user_id)
if wallet_balance < line.price:
raise UserError(_('Your wallet does not contain enough money to order that.'
'To add some money to your wallet, please contact your lunch manager.'))
if order_id.state == 'ordered':
# Need to update cashmove
cashmove = self.env['lunch.cashmove'].search([('order_line_id', '=', line.id)], limit=1)
if cashmove:
cashmove.amount = -line.price
else:
self.env['lunch.cashmove'].create({
'order_line_id': line.id,
'user_id': line.user_id.id,
'description': line.product_id.name,
'state': 'order',
'date': line.date,
'amount': -line.price,
})
@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_lunch_order_line_temp" model="ir.ui.view">
<field name="name">view.lunch.order.line.temp</field>
<field name="model">lunch.order.line.temp</field>
<field name="arch" type="xml">
<form>
<field name="currency_id" invisible="1"/>
<field name="quantity" invisible="1"/>
<field name="product_id" invisible="1"/>
<field name="product_category" invisible="1"/>
<h2 class="d-flex">
<div class="flex-grow-1 pr-5"><field name="product_name"/></div>
<div class="flex-grow-0"><field name="price_total" widget="monetary"/></div>
</h2>
<group>
<field name="available_toppings" invisible="1"/>
<field name="topping_ids" attrs="{'invisible': [('available_toppings', '=', False)]}" widget="many2many_checkboxes"/>
</group>
<group>
<field name="note"/>
</group>
<footer>
<button name="add_to_cart" string="Add To Cart" type="object" class="oe_highlight"/>
<button string="Discard" special="cancel"/>
</footer>
</form>
</field>
</record>
</data>
</odoo>
-4
View File
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models
-19
View File
@@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
{
'name': 'Lunch HR',
'sequence': 120,
'version': '1.0',
'depends': ['lunch', 'hr'],
'category': 'Human Resources',
'summary': 'Handle lunch orders of your employees',
'description': """""",
'data': [
'views/lunch_product_views.xml',
'views/lunch_supplier_views.xml',
],
'demo': ['data/lunch_hr_demo.xml',],
'auto_install': True,
'installable': True,
'application': False,
}
-28
View File
@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="partner_farm_1" model="res.partner">
<field name="name">Farm 1</field>
</record>
<record id="partner_farm_2" model="res.partner">
<field name="name">Farm 2</field>
</record>
<record id="partner_brussels" model="res.partner">
<field name="name">Brussels</field>
</record>
<record id="hr.employee_admin" model="hr.employee">
<field name="address_id" ref="partner_farm_1"/>
</record>
<record id="lunch.supplier_pizza_inn" model="lunch.supplier">
<field name="available_location_ids" eval="[
(6, 0, [ref('lunch_hr.partner_farm_1'), ref('lunch_hr.partner_farm_2')]),
]"/>
</record>
</data>
</odoo>
-42
View File
@@ -1,42 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * lunch_hr
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~12.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-24 10:59+0000\n"
"PO-Revision-Date: 2018-12-24 10:59+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: lunch_hr
#: model_terms:ir.ui.view,arch_db:lunch_hr.lunch_product_view_search_inherit
msgid "Available at current location"
msgstr ""
#. module: lunch_hr
#: model:ir.model,name:lunch_hr.model_lunch_product
msgid "Lunch Product"
msgstr ""
#. module: lunch_hr
#: model:ir.model,name:lunch_hr.model_lunch_supplier
msgid "Lunch Supplier"
msgstr ""
#. module: lunch_hr
#: model:ir.model.fields,field_description:lunch_hr.field_lunch_product__is_available_at
msgid "Product Availability"
msgstr ""
#. module: lunch_hr
#: model:ir.model.fields,field_description:lunch_hr.field_lunch_supplier__available_location_ids
msgid "This Supplier is available at"
msgstr ""
-5
View File
@@ -1,5 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import lunch_product
from . import lunch_supplier
-33
View File
@@ -1,33 +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
from odoo.tools import pycompat
class LunchProduct(models.Model):
_inherit = 'lunch.product'
# This field is used only for searching
is_available_at = fields.Many2one('res.partner', 'Product Availability', compute='_compute_is_available_at', search='_search_is_available_at')
def _compute_is_available_at(self):
for product in self:
product.is_available_at = False
def _search_is_available_at(self, operator, value):
if operator in ['=', '!=', 'ilike', 'not ilike'] and isinstance(value, str):
location_ids = self.env['res.partner'].search([('name', operator, value)]).ids
if operator in ['='] and not value:
if self.env.user.employee_ids and self.env.user.employee_ids[0].address_id:
location_ids = self.env.user.employee_ids[0].address_id.ids
if location_ids:
suppliers = self.env['lunch.supplier'].search([('available_location_ids', 'in', location_ids)])
product_ids = self.env['lunch.product'].search([('supplier_id', 'in', suppliers.ids)])
return [('id', 'in', product_ids.ids)]
return expression.TRUE_DOMAIN
-10
View File
@@ -1,10 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class LunchSupplier(models.Model):
_inherit = 'lunch.supplier'
available_location_ids = fields.Many2many('res.partner', string='This Supplier is available at')
@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="lunch_product_view_search_inherit" model="ir.ui.view">
<field name="name">lunch.product.search.inherit</field>
<field name="model">lunch.product</field>
<field name="inherit_id" ref="lunch.lunch_product_view_search"/>
<field name="arch" type="xml">
<xpath expr="//filter[@name='inactive']" position="after">
<filter name="available" string="Available at current location" domain="[('is_available_at', '=', False)]"/>
</xpath>
</field>
</record>
</odoo>
@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="lunch_supplier_view_form_inherit" model="ir.ui.view">
<field name="name">lunch.supplier.form.view.inherit</field>
<field name="model">lunch.supplier</field>
<field name="inherit_id" ref="lunch.lunch_supplier_view_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='recurrency_to']" position="after">
<field name="available_location_ids" widget="many2many_tags"/>
</xpath>
</field>
</record>
</odoo>