From ad99f09a062cd190c9863d49486e60899daed2a6 Mon Sep 17 00:00:00 2001 From: RomainLibert Date: Fri, 19 Oct 2018 10:09:56 +0200 Subject: [PATCH] [REF] lunch: split lunch.py into smaller files We should basically have one model per file, as this will facilitate the reading and maintenance of the code --- addons/lunch/__manifest__.py | 4 + addons/lunch/models/__init__.py | 5 +- addons/lunch/models/lunch_alert.py | 85 +++ addons/lunch/models/lunch_cashmove.py | 23 + .../lunch/models/{lunch.py => lunch_order.py} | 156 ---- addons/lunch/models/lunch_product.py | 63 ++ addons/lunch/views/lunch_alert_views.xml | 134 ++++ addons/lunch/views/lunch_cashmove_views.xml | 165 +++++ addons/lunch/views/lunch_orders_views.xml | 237 ++++++ addons/lunch/views/lunch_product_views.xml | 140 ++++ addons/lunch/views/lunch_views.xml | 683 ------------------ 11 files changed, 855 insertions(+), 840 deletions(-) create mode 100644 addons/lunch/models/lunch_alert.py create mode 100644 addons/lunch/models/lunch_cashmove.py rename addons/lunch/models/{lunch.py => lunch_order.py} (66%) create mode 100644 addons/lunch/models/lunch_product.py create mode 100644 addons/lunch/views/lunch_alert_views.xml create mode 100644 addons/lunch/views/lunch_cashmove_views.xml create mode 100644 addons/lunch/views/lunch_orders_views.xml create mode 100644 addons/lunch/views/lunch_product_views.xml diff --git a/addons/lunch/__manifest__.py b/addons/lunch/__manifest__.py index ef5bc49a52f..e28b88a82f2 100644 --- a/addons/lunch/__manifest__.py +++ b/addons/lunch/__manifest__.py @@ -28,6 +28,10 @@ If you want to save your employees' time and avoid them to always have coins in 'report/lunch_order_templates.xml', 'views/lunch_templates.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_views.xml', 'data/lunch_data.xml', ], diff --git a/addons/lunch/models/__init__.py b/addons/lunch/models/__init__.py index 9824b4ad3b0..f99750c87d0 100644 --- a/addons/lunch/models/__init__.py +++ b/addons/lunch/models/__init__.py @@ -1,3 +1,6 @@ # -*- coding: utf-8 -*- -from . import lunch +from . import lunch_alert +from . import lunch_cashmove +from . import lunch_order +from . import lunch_product diff --git a/addons/lunch/models/lunch_alert.py b/addons/lunch/models/lunch_alert.py new file mode 100644 index 00000000000..325a64f4385 --- /dev/null +++ b/addons/lunch/models/lunch_alert.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import datetime + +from odoo import api, fields, models, _ + + +class LunchAlert(models.Model): + """ Alerts to display during a lunch order. An alert can be specific to a + given day, weekly or daily. The alert is displayed from start to end hour. """ + _name = 'lunch.alert' + _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) + + @api.multi + def name_get(self): + return [(alert.id, '%s %s' % (_('Alert'), '#%d' % alert.id)) for alert in self] + + @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: + can_display_alert = { + 'specific': (str(alert.specific_day) == fields.Date.to_string(mynow)), + 'week': alert[days_codes[mynow.strftime('%w')]], + 'days': True + } + + 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) + + if fullday or (from_alert <= mynow.time() <= to_alert): + alert.display = True + else: + alert.display = False diff --git a/addons/lunch/models/lunch_cashmove.py b/addons/lunch/models/lunch_cashmove.py new file mode 100644 index 00000000000..7d4a6b56ae4 --- /dev/null +++ b/addons/lunch/models/lunch_cashmove.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models, _ + + +class LunchCashMove(models.Model): + """ Two types of cashmoves: payment (credit) or order (debit) """ + _name = 'lunch.cashmove' + _description = 'Lunch Cashmove' + + user_id = fields.Many2one('res.users', 'User', + default=lambda self: self.env.uid) + 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_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] diff --git a/addons/lunch/models/lunch.py b/addons/lunch/models/lunch_order.py similarity index 66% rename from addons/lunch/models/lunch.py rename to addons/lunch/models/lunch_order.py index 19aad72ef22..f4354913db8 100644 --- a/addons/lunch/models/lunch.py +++ b/addons/lunch/models/lunch_order.py @@ -4,12 +4,10 @@ from collections import OrderedDict import json -import datetime from odoo import api, fields, models, _ from odoo.exceptions import AccessError, ValidationError from odoo.addons import decimal_precision as dp -from odoo.osv import expression class LunchOrder(models.Model): @@ -315,157 +313,3 @@ class LunchOrderLine(models.Model): self.cashmove.unlink() else: raise AccessError(_("Only your lunch manager cancels the orders.")) - - -class LunchProduct(models.Model): - """ Products available to order. A product is linked to a specific vendor. """ - _name = 'lunch.product' - _description = 'Lunch Product' - - 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')) - supplier = fields.Many2one('res.partner', 'Vendor') - active = fields.Boolean(default=True) - available = fields.Boolean(compute='_get_available_product', search='_search_available_products') - - @api.depends('supplier') - def _get_available_product(self): - for product in self: - 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)] - - -class LunchProductCategory(models.Model): - """ Category of the product such as pizza, sandwich, pasta, chinese, burger... """ - _name = 'lunch.product.category' - _description = 'Lunch Product Category' - - name = fields.Char('Product Category', required=True) - - -class LunchCashMove(models.Model): - """ Two types of cashmoves: payment (credit) or order (debit) """ - _name = 'lunch.cashmove' - _description = 'Lunch Cashmove' - - user_id = fields.Many2one('res.users', 'User', - default=lambda self: self.env.uid) - 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_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] - - -class LunchAlert(models.Model): - """ Alerts to display during a lunch order. An alert can be specific to a - given day, weekly or daily. The alert is displayed from start to end hour. """ - _name = 'lunch.alert' - _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) - - @api.multi - def name_get(self): - return [(alert.id, '%s %s' % (_('Alert'), '#%d' % alert.id)) for alert in self] - - @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: - can_display_alert = { - 'specific': (str(alert.specific_day) == fields.Date.to_string(mynow)), - 'week': alert[days_codes[mynow.strftime('%w')]], - 'days': True - } - - 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) - - if fullday or (from_alert <= mynow.time() <= to_alert): - alert.display = True - else: - alert.display = False diff --git a/addons/lunch/models/lunch_product.py b/addons/lunch/models/lunch_product.py new file mode 100644 index 00000000000..8ded108fc81 --- /dev/null +++ b/addons/lunch/models/lunch_product.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models, _ + +from odoo.addons import decimal_precision as dp +from odoo.osv import expression + + +class LunchProductCategory(models.Model): + """ Category of the product such as pizza, sandwich, pasta, chinese, burger... """ + _name = 'lunch.product.category' + _description = 'Lunch Product Category' + + name = fields.Char('Product Category', required=True) + + +class LunchProduct(models.Model): + """ Products available to order. A product is linked to a specific vendor. """ + _name = 'lunch.product' + _description = 'Lunch Product' + + 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')) + supplier = fields.Many2one('res.partner', 'Vendor') + active = fields.Boolean(default=True) + available = fields.Boolean(compute='_get_available_product', search='_search_available_products') + + @api.depends('supplier') + def _get_available_product(self): + for product in self: + 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)] diff --git a/addons/lunch/views/lunch_alert_views.xml b/addons/lunch/views/lunch_alert_views.xml new file mode 100644 index 00000000000..bb6d807a9ff --- /dev/null +++ b/addons/lunch/views/lunch_alert_views.xml @@ -0,0 +1,134 @@ + + + + lunch.alert.search + lunch.alert + + + + + + + + + + lunch.alert.tree + lunch.alert + + + + + + + + + + + + lunch.alert.form + lunch.alert + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ Products from this vendor can not be ordered at that moment. +
+
+ Products from this vendor can be ordered at that moment. +
+
+
+ + + +
+
+
+
+ + + lunch.alert.kanban + lunch.alert + + + + + + + + +
+
+ +
+
+
+ Between: - + +
+
+ + + +
+
+
+
+
+
+
+
+ + + Alerts + lunch.alert + tree,kanban,form + + +

+ Create a new lunch alert +

+ 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. +

+ Example:
+ Recurency: Everyday,
+ Time interval: from 00h00 am to 11h59 pm,
+ Message: "You must order before 10h30 am". +

+
+
+
diff --git a/addons/lunch/views/lunch_cashmove_views.xml b/addons/lunch/views/lunch_cashmove_views.xml new file mode 100644 index 00000000000..b714205587a --- /dev/null +++ b/addons/lunch/views/lunch_cashmove_views.xml @@ -0,0 +1,165 @@ + + + + lunch.cashmove.search + lunch.cashmove + + + + + + + + + + + + + + lunch.cashmove.search + lunch.cashmove + + + + + + + + + + + + + lunch.cashmove.tree + lunch.cashmove + + + + + + + + + + + + lunch.cashmove.tree + lunch.cashmove + + + + + + + + + + + lunch.cashmove.form + lunch.cashmove + +
+ + + + + + + +
+
+
+ + + lunch.cashmove.kanban + lunch.cashmove + + + + + + + + +
+
+
+ + + +
+
+ + + +
+
+
+
+ + +
+
+ +
+
+
+
+
+
+
+
+ + + Your Account + lunch.cashmove + tree + + {"search_default_is_mine_group":1} + + +

+ No cash move yet +

+ Here you can see your cash moves.
A cash move can either be an expense or a payment. + An expense is automatically created when an order is received while a payment is a reimbursement to the company encoded by the manager. +

+
+
+ + + Control Accounts + lunch.cashmove + tree,kanban,form + + {"search_default_group_by_user":1} + + +

+ Create a new payment +

+ A cashmove can either be an expense or a payment.
+ An expense is automatically created at the order receipt.
+ A payment represents the employee reimbursement to the company. +

+
+
+ + + Register Cash Moves + lunch.cashmove + tree,kanban,form + + {"search_default_is_payment":1} + + +

+ Register a payment +

+ Payments are used to register liquidity movements. You can process those payments by your own means or by using installed facilities. +

+
+
+
diff --git a/addons/lunch/views/lunch_orders_views.xml b/addons/lunch/views/lunch_orders_views.xml new file mode 100644 index 00000000000..8cd4658a8cb --- /dev/null +++ b/addons/lunch/views/lunch_orders_views.xml @@ -0,0 +1,237 @@ + + + + lunch.order.tree + lunch.order + + + + + + + + + + + + lunch.order.form + lunch.order + +
+
+
+ + + +
+ +
+ + + + + + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + lunch.order.line.search + lunch.order.line + + + + + + + + + + + + + + + + + + + + + + lunch.order.line.tree + lunch.order.line + + + + + + + + + + + + + + + + + + + + + + + + + lunch.product.kanban + lunch.product + + + + + + + + + +
+
+
+ +
+ + Price: + +
+
+ +
+
+ +
+
+
+
+
+
+
+ + + Product category Form + lunch.product.category + +
+ + + + + +
+
+
+ + + Products + lunch.product + tree,kanban,form + +

+ Create a new product for lunch +

+ A product is defined by its name, category, price and vendor. +

+
+
+ + + Product Categories + lunch.product.category + tree,form + +

+ Create a new lunch category +

+ Here you can access all categories for the lunch products. +

+
+
+
diff --git a/addons/lunch/views/lunch_views.xml b/addons/lunch/views/lunch_views.xml index bdb16983772..454085fb92b 100644 --- a/addons/lunch/views/lunch_views.xml +++ b/addons/lunch/views/lunch_views.xml @@ -6,549 +6,19 @@ - - - lunch.order.line.search - lunch.order.line - - - - - - - - - - - - - - - - - - - - - - - lunch.cashmove.search - lunch.cashmove - - - - - - - - - - - - - - lunch.cashmove.search - lunch.cashmove - - - - - - - - - - - - - lunch.alert.search - lunch.alert - - - - - - - - - - - lunch.cashmove.tree - lunch.cashmove - - - - - - - - - - - - lunch.cashmove.kanban - lunch.cashmove - - - - - - - - -
-
-
- - - -
-
- - - -
-
-
-
- - -
-
- -
-
-
-
-
-
-
-
- - - lunch.cashmove.form - lunch.cashmove - -
- - - - - - - -
-
-
- - - - - New Order - lunch.order - form - - - - Your Orders - lunch.order.line - tree,kanban,form - - {"search_default_is_mine":1} - -

- No previous order found -

- There is no previous order recorded. Click on "My Lunch" and then create a new lunch order. -

-
-
- - - lunch.cashmove.tree - lunch.cashmove - - - - - - - - - - - - Your Account - lunch.cashmove - tree - - {"search_default_is_mine_group":1} - - -

- No cash move yet -

- Here you can see your cash moves.
A cash move can either be an expense or a payment. - An expense is automatically created when an order is received while a payment is a reimbursement to the company encoded by the manager. -

-
-
- - - - Orders by Vendor - lunch.order.line - tree,kanban - - {"search_default_group_by_supplier":1, "search_default_date_filter":1} - -

- Nothing to order today -

- Here you can see today's orders grouped by vendors. -

- Click on the to announce that the order is ordered.
- Click on the to announce that the order is received.
- Click on the to announce that the order isn't available. -

-
-
- - - - Control Vendors - lunch.order.line - tree,kanban - - {"search_default_group_by_date":1, "search_default_group_by_supplier":1} - -

- No lunch order yet -

- Summary of all lunch orders, grouped by vendor and by date. -

- Click on the to announce that the order is ordered.
- Click on the to announce that the order is received.
- Click on the red X to announce that the order isn't available. -

-
-
- - - - Control Accounts - lunch.cashmove - tree,kanban,form - - {"search_default_group_by_user":1} - - -

- Create a new payment -

- A cashmove can either be an expense or a payment.
- An expense is automatically created at the order receipt.
- A payment represents the employee reimbursement to the company. -

-
-
- - - - Register Cash Moves - lunch.cashmove - tree,kanban,form - - {"search_default_is_payment":1} - - -

- Register a payment -

- Payments are used to register liquidity movements. You can process those payments by your own means or by using installed facilities. -

-
-
- - - Products - lunch.product - tree,kanban,form - -

- Create a new product for lunch -

- A product is defined by its name, category, price and vendor. -

-
-
- - - - Product Categories - lunch.product.category - tree,form - -

- Create a new lunch category -

- Here you can access all categories for the lunch products. -

-
-
- - - Product category Form - lunch.product.category - -
- - - - - -
-
-
- - - - - Alerts - lunch.alert - tree,kanban,form - - -

- Create a new lunch alert -

- 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. -

- Example:
- Recurency: Everyday,
- Time interval: from 00h00 am to 11h59 pm,
- Message: "You must order before 10h30 am". -

-
-
- - - lunch.order.line.tree - lunch.order.line - - - - - - - - - - - - - - - - - -
- -
-
- -
- -
- - - - - - - - - - - - - - - - - - - - - - - -
-
- - - - lunch.product.tree - lunch.product - - - - - - - - - - - - - lunch.product.kanban - lunch.product - - - - - - - - - -
-
-
- -
- - Price: - -
-
- -
-
- -
-
-
-
-
-
-
Lunch: Archive/Restore products @@ -558,157 +28,4 @@ code records.toggle_active() - - - - lunch.product.search - lunch.product - - - - - - - - - - - - - - - - - lunch.product.form - lunch.product - -
-
-
- -
- -
- - - - - - - - - - -
-
-
-
- - - - lunch.alert.tree - lunch.alert - - - - - - - - - - - - lunch.alert.kanban - lunch.alert - - - - - - - - -
-
- -
-
-
- Between: - - -
-
- - - -
-
-
-
-
-
-
-
- - - lunch.alert.form - lunch.alert - -
- -
- -
- - - - - - - - - - - - - - - - - - - - - - - -
- Products from this vendor can not be ordered at that moment. -
-
- Products from this vendor can be ordered at that moment. -
-
-
- - - -
-
-
-