[REV] lunch: Revert changes introduced in f727b8f0b5
YOLO closes odoo/odoo#30025
@@ -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,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
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
from . import main
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -4,4 +4,3 @@ from . import lunch_alert
|
||||
from . import lunch_cashmove
|
||||
from . import lunch_order
|
||||
from . import lunch_product
|
||||
from . import lunch_supplier
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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."))
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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])
|
||||
@@ -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
|
||||
|
||||
|
|
Before Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
@@ -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;
|
||||
|
||||
});
|
||||
@@ -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' && !widget.data.edit" href="#" class="o_lunch_widget_edit">Edit</a>
|
||||
<a t-if="widget.data.state == 'ordered' && 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 && 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>
|
||||
@@ -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,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import common
|
||||
from . import test_supplier
|
||||
from . import test_lunch
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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.'))
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1,4 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||