From 3d63c15a567db74e9d65f47d9c7060527de7dcba Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Wed, 4 Feb 2015 17:44:38 +0530 Subject: [PATCH] [MIGR] lunch: migration to the new API, cleaning and new widget for displaying previous orders. Detailed changes : - previous order is not a computed field, with its own custom widget that has been designed according the new spec for widgets; - useless wizards have been removed, and replaced by some server actions that perform the same action with less code and views; - removed deprecated or incomplete reporting; - some renaming to follow the new specifications of module coding --- addons/lunch/__init__.py | 10 +- addons/lunch/__openerp__.py | 28 +- addons/lunch/data/lunch_data.xml | 60 ++ addons/lunch/data/lunch_demo.xml | 49 +- addons/lunch/models/__init__.py | 2 +- addons/lunch/models/lunch.py | 687 ++++++------------ addons/lunch/report/__init__.py | 4 - addons/lunch/report/lunch_order_templates.xml | 139 ++-- addons/lunch/report/lunch_order_views.xml | 11 + addons/lunch/report/lunch_report_views.xml | 13 - addons/lunch/report/order.py | 80 -- addons/lunch/report/report_lunch_order.py | 62 -- .../lunch/report/report_lunch_order_view.xml | 7 - addons/lunch/security/ir.model.access.csv | 3 - addons/lunch/security/lunch_security.xml | 7 +- addons/lunch/static/src/css/lunch.css | 33 +- addons/lunch/static/src/css/lunch.sass | 28 +- addons/lunch/static/src/js/lunch.js | 73 ++ addons/lunch/static/src/xml/lunch.xml | 42 ++ addons/lunch/tests/__init__.py | 21 +- addons/lunch/tests/test_lunch.py | 73 +- addons/lunch/views/lunch_templates.xml | 17 +- addons/lunch/views/lunch_views.xml | 189 +++-- addons/lunch/wizard/__init__.py | 24 - addons/lunch/wizard/lunch_cancel.py | 29 - addons/lunch/wizard/lunch_cancel_view.xml | 31 - addons/lunch/wizard/lunch_order.py | 29 - addons/lunch/wizard/lunch_order_view.xml | 30 - addons/lunch/wizard/lunch_validation.py | 29 - addons/lunch/wizard/lunch_validation_view.xml | 29 - .../web/static/src/js/views/form_widgets.js | 1 + 31 files changed, 680 insertions(+), 1160 deletions(-) create mode 100644 addons/lunch/data/lunch_data.xml delete mode 100644 addons/lunch/report/__init__.py create mode 100644 addons/lunch/report/lunch_order_views.xml delete mode 100644 addons/lunch/report/lunch_report_views.xml delete mode 100644 addons/lunch/report/order.py delete mode 100644 addons/lunch/report/report_lunch_order.py delete mode 100644 addons/lunch/report/report_lunch_order_view.xml create mode 100644 addons/lunch/static/src/js/lunch.js create mode 100644 addons/lunch/static/src/xml/lunch.xml delete mode 100644 addons/lunch/wizard/__init__.py delete mode 100644 addons/lunch/wizard/lunch_cancel.py delete mode 100644 addons/lunch/wizard/lunch_cancel_view.xml delete mode 100644 addons/lunch/wizard/lunch_order.py delete mode 100644 addons/lunch/wizard/lunch_order_view.xml delete mode 100644 addons/lunch/wizard/lunch_validation.py delete mode 100644 addons/lunch/wizard/lunch_validation_view.xml diff --git a/addons/lunch/__init__.py b/addons/lunch/__init__.py index b0b18ae282c..908fbcb0107 100644 --- a/addons/lunch/__init__.py +++ b/addons/lunch/__init__.py @@ -1,8 +1,8 @@ -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- ############################################################################## # -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2012 Tiny SPRL (). +# Odoo, Open Source Management Solution +# Copyright (C) 2004-Today Odoo SA (). # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -15,10 +15,8 @@ # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +# along with this program. If not, see . # ############################################################################## import models -import report -import wizard diff --git a/addons/lunch/__openerp__.py b/addons/lunch/__openerp__.py index 0e0ff902338..21daff10461 100644 --- a/addons/lunch/__openerp__.py +++ b/addons/lunch/__openerp__.py @@ -1,21 +1,21 @@ -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- { + 'author': 'Odoo SA', 'name': 'Lunch', - 'author': 'OpenERP SA', - 'version': '0.2', - 'depends': ['base', 'report'], + 'version': '1.0', + 'depends': ['base', 'web', 'decimal_precision'], 'category': 'Tools', 'summary': 'Lunch Order, Meal, Food', 'description': """ The base module to manage lunch. ================================ -Many companies order sandwiches, pizzas and other, from usual suppliers, for their employees to offer them more facilities. +Many companies order sandwiches, pizzas and other, from usual suppliers, for their employees to offer them more facilities. -However lunches management within the company requires proper administration especially when the number of employees or suppliers is important. +However lunches management within the company requires proper administration especially when the number of employees or suppliers is important. -The “Lunch Order” module has been developed to make this management easier but also to offer employees more tools and usability. +The “Lunch Order” module has been developed to make this management easier but also to offer employees more tools and usability. In addition to a full meal and supplier management, this module offers the possibility to display warning and provides quick order selection based on employee’s preferences. @@ -23,17 +23,15 @@ If you want to save your employees' time and avoid them to always have coins in """, 'data': [ 'security/lunch_security.xml', - 'views/lunch_views.xml', - 'views/lunch_templates.xml', - 'wizard/lunch_order_view.xml', - 'wizard/lunch_validation_view.xml', - 'wizard/lunch_cancel_view.xml', - 'report/lunch_report_views.xml', - 'report/report_lunch_order_view.xml', - 'report/lunch_order_templates.xml', 'security/ir.model.access.csv', + 'report/lunch_order_views.xml', + 'report/lunch_order_templates.xml', + 'views/lunch_templates.xml', + 'views/lunch_views.xml', + 'data/lunch_data.xml', ], 'demo': ['data/lunch_demo.xml'], + 'qweb': ['static/src/xml/lunch.xml', ], 'installable': True, 'application': True, 'certificate': '001292377792581874189', diff --git a/addons/lunch/data/lunch_data.xml b/addons/lunch/data/lunch_data.xml new file mode 100644 index 00000000000..333cb5acbdf --- /dev/null +++ b/addons/lunch/data/lunch_data.xml @@ -0,0 +1,60 @@ + + + + Order meals + + code + + if context.get('active_ids'): + self.order(cr, uid, context['active_ids'], context=context) + + + + + + Order meals + client_action_multi + + action + lunch.order.line + + + + Receive meals + + code + + if context.get('active_ids'): + self.confirm(cr, uid, context['active_ids'], context=context) + + + + + + Receive meals + client_action_multi + + action + lunch.order.line + + + + Cancel meals + + code + + if context.get('active_ids'): + self.cancel(cr, uid, context['active_ids'], context=context) + + + + + + Cancel meals + client_action_multi + + action + lunch.order.line + + + diff --git a/addons/lunch/data/lunch_demo.xml b/addons/lunch/data/lunch_demo.xml index 546c21e0c22..bfe7dcc11f5 100644 --- a/addons/lunch/data/lunch_demo.xml +++ b/addons/lunch/data/lunch_demo.xml @@ -1,9 +1,9 @@ - - + + - + @@ -109,29 +109,30 @@ - + new - + confirmed - + - partially + confirmed - + 7.70 + new +Emmental @@ -141,7 +142,8 @@ - + 7.40 + confirmed +Mushrooms @@ -151,7 +153,8 @@ - + 2.50 + cancelled +Salad +Tomatoes +Cucumbers @@ -161,16 +164,17 @@ - + 2.60 + confirmed +Salad +Tomatoes +Eggs - + - + Pizza Italiana -7.40 @@ -179,7 +183,7 @@ - + Chicken curry -2.60 @@ -188,15 +192,24 @@ - + Payment: 5 lunch tickets (6€) 30 payment - Lunch must be ordered before 10h30 am + week + + + + + + + + 7 + 23 + Lunch must be ordered before 10:30 AM - - + diff --git a/addons/lunch/models/__init__.py b/addons/lunch/models/__init__.py index 894fef6dee6..6a17f40da4a 100644 --- a/addons/lunch/models/__init__.py +++ b/addons/lunch/models/__init__.py @@ -1,3 +1,3 @@ -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- import lunch diff --git a/addons/lunch/models/lunch.py b/addons/lunch/models/lunch.py index 1d17b4468ac..4f572c6019a 100644 --- a/addons/lunch/models/lunch.py +++ b/addons/lunch/models/lunch.py @@ -1,501 +1,278 @@ -# -*- encoding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## +# -*- coding: utf-8 -*- -from xml.sax.saxutils import escape -import time -from openerp.osv import fields, osv -from datetime import datetime -from lxml import etree -from openerp import tools -from openerp.tools.translate import _ +import datetime -class lunch_order(osv.Model): - """ - lunch order (contains one or more lunch order line(s)) +from openerp import models, fields, api, _ +import openerp.addons.decimal_precision as dp +from openerp.exceptions import UserError + + +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. When creating a lunch order, applicable lunch alerts are displayed. """ _name = 'lunch.order' _description = 'Lunch Order' _order = 'date desc' - def name_get(self, cr, uid, ids, context=None): - if not ids: - return [] - res = [] - for elmt in self.browse(cr, uid, ids, context=context): - name = _("Lunch Order") - name = name + ' ' + str(elmt.id) - res.append((elmt.id, name)) - return res - - def _price_get(self, cr, uid, ids, name, arg, context=None): - """ + user_id = fields.Many2one('res.users', 'User', required=True, readonly=True, + states={'new': [('readonly', False)]}, + default=lambda self: self.env.uid) + date = fields.Date('Date', required=True, readonly=True, + states={'new': [('readonly', False)]}, + default=fields.Date.context_today) + order_line_ids = fields.One2many('lunch.order.line', 'order_id', 'Products', + ondelete="cascade", readonly=True, copy=True, + states={'new': [('readonly', False)], False: [('readonly', False)]}) + total = fields.Float(compute='_compute_total', string="Total", store=True) + state = fields.Selection([('new', 'New'), + ('confirmed', 'Received'), + ('cancelled', 'Cancelled')], + 'Status', readonly=True, index=True, copy=False, default='new', + compute='_compute_order_state') + alerts = fields.Text(compute='_compute_alerts_get', string="Alerts") + previous_order_ids = fields.One2many(comodel_name='lunch.order.line', + compute='_compute_get_previous_order_ids') + company_id = fields.Many2one('res.company', related='user_id.company_id', store=True) + currency_id = fields.Many2one('res.currency', related='company_id.currency_id', readonly=True, store=True) + + @api.one + @api.depends('order_line_ids') + def _compute_total(self): + """ get and sum the order lines' price """ - result = dict.fromkeys(ids, 0) - for order in self.browse(cr, uid, ids, context=context): - result[order.id] = sum(order_line.product_id.price - for order_line in order.order_line_ids) - return result + self.total = sum( + orderline.price for orderline in self.order_line_ids) - def _fetch_orders_from_lines(self, cr, uid, ids, name, context=None): - """ - return the list of lunch orders to which belong the order lines `ids´ + @api.multi + 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): """ - result = set() - for order_line in self.browse(cr, uid, ids, context=context): - if order_line.order_id: - result.add(order_line.order_id.id) - return list(result) - - def add_preference(self, cr, uid, ids, pref_id, context=None): - """ - create a new order line based on the preference selected (pref_id) - """ - assert len(ids) == 1 - orderline_ref = self.pool.get('lunch.order.line') - prod_ref = self.pool.get('lunch.product') - order = self.browse(cr, uid, ids[0], context=context) - pref = orderline_ref.browse(cr, uid, pref_id, context=context) - new_order_line = { - 'date': order.date, - 'user_id': uid, - 'product_id': pref.product_id.id, - 'note': pref.note, - 'order_id': order.id, - 'price': pref.product_id.price, - 'supplier': pref.product_id.supplier.id - } - return orderline_ref.create(cr, uid, new_order_line, context=context) - - def _alerts_get(self, cr, uid, ids, name, arg, context=None): - """ - get the alerts to display on the order form - """ - result = {} - alert_msg = self._default_alerts_get(cr, uid, context=context) - for order in self.browse(cr, uid, ids, context=context): - if order.state == 'new': - result[order.id] = alert_msg - return result - - def check_day(self, alert): - """ - This method is used by can_display_alert - to check if the alert day corresponds - to the current day - """ - today = datetime.now().isoweekday() - assert 1 <= today <= 7, "Should be between 1 and 7" - mapping = dict((idx, name) for idx, name in enumerate('days monday tuesday wednesday thursday friday saturday sunday'.split())) - return alert[mapping[today]] - - def can_display_alert(self, alert): - """ - This method check if the alert can be displayed today - """ - if alert.alter_type == 'specific': - #the alert is only activated on a specific day - return alert.specific_day == time.strftime(tools.DEFAULT_SERVER_DATE_FORMAT) - elif alert.alter_type == 'week': - #the alert is activated during some days of the week - return self.check_day(alert) - return True # alter_type == 'days' (every day) - - def _default_alerts_get(self, cr, uid, context=None): - """ get the alerts to display on the order form """ - alert_ref = self.pool.get('lunch.alert') - alert_ids = alert_ref.search(cr, uid, [], context=context) - alert_msg = [] - for alert in alert_ref.browse(cr, uid, alert_ids, context=context): - #check if the address must be displayed today - if self.can_display_alert(alert): - #display the address only during its active time - mynow = fields.datetime.context_timestamp(cr, uid, datetime.now(), context=context) - hour_to = int(alert.active_to) - min_to = int((alert.active_to - hour_to) * 60) - to_alert = datetime.strptime(str(hour_to) + ":" + str(min_to), "%H:%M") - hour_from = int(alert.active_from) - min_from = int((alert.active_from - hour_from) * 60) - from_alert = datetime.strptime(str(hour_from) + ":" + str(min_from), "%H:%M") - if mynow.time() >= from_alert.time() and mynow.time() <= to_alert.time(): - alert_msg.append(alert.message) - return '\n'.join(alert_msg) + alert_msg = [alert.message + for alert in self.env['lunch.alert'].search([]) + if alert.display] - def onchange_price(self, cr, uid, ids, order_line_ids, context=None): + if self.state == 'new': + self.alerts = '\n'.join(alert_msg) + + @api.depends('state') + def _compute_get_previous_order_ids(self): + prev_order = self.env['lunch.order.line'].search([('user_id', '=', self.env.uid)], limit=20, order='id desc') + # If we return return 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 = { + (order.product_id, order.note): order.id + for order in prev_order + }.values() + + @api.onchange('total') + def _onchange_total(self): """ - Onchange methode that refresh the total price of order + Calculates the forecasted balance of the user, taking into account the current order """ - res = {'value': {'total': 0.0}} - order_line_ids = self.resolve_o2m_commands_to_record_dicts(cr, uid, "order_line_ids", order_line_ids, ["price"], context=context) - if order_line_ids: - tot = 0.0 - product_ref = self.pool.get("lunch.product") - for prod in order_line_ids: - if 'product_id' in prod: - tot += product_ref.browse(cr, uid, prod['product_id'], context=context).price + prev_cashmove = self.env['lunch.cashmove'].search([('user_id', '=', self.user_id.id)]) + balance = sum(cashmove.amount for cashmove in prev_cashmove) + + if self.total > balance: + raise UserError('%s (%s %s)' % (_('Insufficient balance. The total amount of the order is larger than the available balance'), balance, self.currency_id.name)) + + @api.one + @api.constrains('date') + def _check_date(self): + """ + Prevents the user to create an order in the past + """ + date_order = datetime.datetime.strptime(self.date, '%Y-%m-%d') + date_today = datetime.datetime.strptime(fields.Date.context_today(self), '%Y-%m-%d') + if (date_order < date_today): + raise UserError(_('The date of your order is in the past.')) + + @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: - tot += prod['price'] - res = {'value': {'total': tot}} - return res + isConfirmed = False - def __getattr__(self, attr): - """ - this method catch unexisting method call and if it starts with - add_preference_'n' we execute the add_preference method with - 'n' as parameter - """ - if attr.startswith('add_preference_'): - pref_id = int(attr[15:]) - def specific_function(cr, uid, ids, context=None): - return self.add_preference(cr, uid, ids, pref_id, context=context) - return specific_function - return super(lunch_order, self).__getattr__(attr) - - def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False): - """ - Add preferences in the form view of order.line - """ - res = super(lunch_order,self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu) - line_ref = self.pool.get("lunch.order.line") - if view_type == 'form': - doc = etree.XML(res['arch']) - pref_ids = line_ref.search(cr, uid, [('user_id', '=', uid)], order='id desc', context=context) - xml_start = etree.Element("div") - #If there are no preference (it's the first time for the user) - if len(pref_ids)==0: - #create Elements - xml_no_pref_1 = etree.Element("div") - xml_no_pref_1.set('class','oe_inline oe_lunch_intro') - xml_no_pref_2 = etree.Element("h3") - xml_no_pref_2.text = _("This is the first time you order a meal") - xml_no_pref_3 = etree.Element("p") - xml_no_pref_3.set('class','oe_grey') - xml_no_pref_3.text = _("Select a product and put your order comments on the note.") - xml_no_pref_4 = etree.Element("p") - xml_no_pref_4.set('class','oe_grey') - xml_no_pref_4.text = _("Your favorite meals will be created based on your last orders.") - xml_no_pref_5 = etree.Element("p") - xml_no_pref_5.set('class','oe_grey') - xml_no_pref_5.text = _("Don't forget the alerts displayed in the reddish area") - #structure Elements - xml_start.append(xml_no_pref_1) - xml_no_pref_1.append(xml_no_pref_2) - xml_no_pref_1.append(xml_no_pref_3) - xml_no_pref_1.append(xml_no_pref_4) - xml_no_pref_1.append(xml_no_pref_5) - #Else: the user already have preferences so we display them + if isConfirmed: + self.state = 'confirmed' else: - preferences = line_ref.browse(cr, uid, pref_ids, context=context) - categories = {} #store the different categories of products in preference - count = 0 - for pref in preferences: - #For each preference - categories.setdefault(pref.product_id.category_id.name, {}) - #if this product has already been added to the categories dictionnary - if pref.product_id.id in categories[pref.product_id.category_id.name]: - #we check if for the same product the note has already been added - if pref.note not in categories[pref.product_id.category_id.name][pref.product_id.id]: - #if it's not the case then we add this to preferences - categories[pref.product_id.category_id.name][pref.product_id.id][pref.note] = pref - #if this product is not in the dictionnay, we add it - else: - categories[pref.product_id.category_id.name][pref.product_id.id] = {} - categories[pref.product_id.category_id.name][pref.product_id.id][pref.note] = pref - - currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id - - #For each preferences that we get, we will create the XML structure - for key, value in categories.items(): - xml_pref_1 = etree.Element("div") - xml_pref_1.set('class', 'oe_lunch_30pc') - xml_pref_2 = etree.Element("h2") - xml_pref_2.text = key - xml_pref_1.append(xml_pref_2) - i = 0 - value = value.values() - #TODO: sorted_values is used for a quick and dirty hack in order to display the 5 last orders of each categories. - #It would be better to fetch only the 5 items to display instead of fetching everything then sorting them in order to keep only the 5 last. - #NB: The note could also be ignored + we could fetch the preferences on the most ordered products instead of the last ones... - sorted_values = {} - for val in value: - for elmt in val.values(): - sorted_values[elmt.id] = elmt - for key, pref in sorted(sorted_values.iteritems(), key=lambda (k, v): (k, v), reverse=True): - #We only show 5 preferences per category (or it will be too long) - if i == 5: - break - i += 1 - xml_pref_3 = etree.Element("div") - xml_pref_3.set('class','oe_lunch_vignette') - xml_pref_1.append(xml_pref_3) - - xml_pref_4 = etree.Element("span") - xml_pref_4.set('class','oe_lunch_button') - xml_pref_3.append(xml_pref_4) - - xml_pref_5 = etree.Element("button") - xml_pref_5.set('name',"add_preference_"+str(pref.id)) - xml_pref_5.set('class','oe_link oe_i oe_button_plus') - xml_pref_5.set('type','object') - xml_pref_5.set('string','+') - xml_pref_4.append(xml_pref_5) - - xml_pref_6 = etree.Element("button") - xml_pref_6.set('name',"add_preference_"+str(pref.id)) - xml_pref_6.set('class','oe_link oe_button_add') - xml_pref_6.set('type','object') - xml_pref_6.set('string',_("Add")) - xml_pref_4.append(xml_pref_6) - - xml_pref_7 = etree.Element("div") - xml_pref_7.set('class','oe_group_text_button') - xml_pref_3.append(xml_pref_7) - - xml_pref_8 = etree.Element("div") - xml_pref_8.set('class','oe_lunch_text') - xml_pref_8.text = escape(pref.product_id.name)+str(" ") - xml_pref_7.append(xml_pref_8) - - price = pref.product_id.price or 0.0 - cur = currency.name or '' - xml_pref_9 = etree.Element("span") - xml_pref_9.set('class','oe_tag') - xml_pref_9.text = str(price)+str(" ")+cur - xml_pref_8.append(xml_pref_9) - - xml_pref_10 = etree.Element("div") - xml_pref_10.set('class','oe_grey') - xml_pref_10.text = escape(pref.note or '') - xml_pref_3.append(xml_pref_10) - - xml_start.append(xml_pref_1) - - first_node = doc.xpath("//div[@name='preferences']") - if first_node and len(first_node)>0: - first_node[0].append(xml_start) - res['arch'] = etree.tostring(doc) - return res - - _columns = { - 'user_id': fields.many2one('res.users', 'User Name', required=True, readonly=True, states={'new':[('readonly', False)]}), - 'date': fields.date('Date', required=True, readonly=True, states={'new':[('readonly', False)]}), - 'order_line_ids': fields.one2many('lunch.order.line', 'order_id', 'Products', - ondelete="cascade", readonly=True, states={'new':[('readonly', False)]}, - copy=True), - 'total': fields.function(_price_get, string="Total", store={ - 'lunch.order.line': (_fetch_orders_from_lines, ['product_id','order_id'], 20), - }), - 'state': fields.selection([('new', 'New'), \ - ('confirmed','Confirmed'), \ - ('cancelled','Cancelled'), \ - ('partially','Partially Confirmed')] \ - ,'Status', readonly=True, select=True, copy=False), - 'alerts': fields.function(_alerts_get, string="Alerts", type='text'), - } - - _defaults = { - 'user_id': lambda self, cr, uid, context: uid, - 'date': fields.date.context_today, - 'state': 'new', - 'alerts': _default_alerts_get, - } + self.state = 'new' + return -class lunch_order_line(osv.Model): - """ - lunch order line: one lunch order can have many order lines - """ +class LunchOrderLine(models.Model): _name = 'lunch.order.line' _description = 'lunch order line' - def onchange_price(self, cr, uid, ids, product_id, context=None): - if product_id: - price = self.pool.get('lunch.product').browse(cr, uid, product_id, context=context).price - return {'value': {'price': price}} - return {'value': {'price': 0.0}} + name = fields.Char(related='product_id.name', readonly=True) + order_id = fields.Many2one('lunch.order', 'Order', ondelete='cascade', required=True) + product_id = fields.Many2one('lunch.product', 'Product', required=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 = fields.Many2one('res.partner', string='Supplier', 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(related='product_id.price', readonly=True, store=True, + digits_compute=dp.get_precision('Account')) + state = fields.Selection([('new', 'New'), + ('confirmed', 'Received'), + ('ordered', 'Ordered'), + ('cancelled', 'Cancelled')], + 'Status', readonly=True, select=True, default='new') + cashmove = fields.One2many('lunch.cashmove', 'order_id', 'Cash Move') + currency_id = fields.Many2one('res.currency', related='order_id.currency_id') - def order(self, cr, uid, ids, context=None): - """ + @api.one + def order(self): + """ The order_line is ordered to the supplier but isn't received yet """ - self.write(cr, uid, ids, {'state': 'ordered'}, context=context) - return self._update_order_lines(cr, uid, ids, context=context) + self.state = 'ordered' - def confirm(self, cr, uid, ids, context=None): - """ - confirm one or more order line, update order status and create new cashmove + @api.one + def confirm(self): """ - cashmove_ref = self.pool.get('lunch.cashmove') - for order_line in self.browse(cr, uid, ids, context=context): - if order_line.state != 'confirmed': - values = { - 'user_id': order_line.user_id.id, - 'amount': -order_line.price, - 'description': order_line.product_id.name, - 'order_id': order_line.id, - 'state': 'order', - 'date': order_line.date, - } - cashmove_ref.create(cr, uid, values, context=context) - order_line.write({'state': 'confirmed'}) - return self._update_order_lines(cr, uid, ids, context=context) + confirm one or more order line, update order status and create new cashmove + """ + 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' - def _update_order_lines(self, cr, uid, ids, context=None): - """ - Update the state of lunch.order based on its orderlines - """ - orders_ref = self.pool.get('lunch.order') - orders = [] - for order_line in self.browse(cr, uid, ids, context=context): - orders.append(order_line.order_id) - for order in set(orders): - isconfirmed = True - for orderline in order.order_line_ids: - if orderline.state == 'new': - isconfirmed = False - if orderline.state == 'cancelled': - isconfirmed = False - orders_ref.write(cr, uid, [order.id], {'state': 'partially'}, context=context) - if isconfirmed: - orders_ref.write(cr, uid, [order.id], {'state': 'confirmed'}, context=context) - return {} - - def cancel(self, cr, uid, ids, context=None): + @api.one + def cancel(self): """ cancel one or more order.line, update order status and unlink existing cashmoves """ - cashmove_ref = self.pool.get('lunch.cashmove') - self.write(cr, uid, ids, {'state':'cancelled'}, context=context) - for order_line in self.browse(cr, uid, ids, context=context): - cash_ids = [cash.id for cash in order_line.cashmove] - cashmove_ref.unlink(cr, uid, cash_ids, context=context) - return self._update_order_lines(cr, uid, ids, context=context) - - def _get_line_order_ids(self, cr, uid, ids, context=None): - """ - return the list of lunch.order.lines ids to which belong the lunch.order 'ids' - """ - result = set() - for lunch_order in self.browse(cr, uid, ids, context=context): - for lines in lunch_order.order_line_ids: - result.add(lines.id) - return list(result) - - _columns = { - 'name': fields.related('product_id', 'name', readonly=True), - 'order_id': fields.many2one('lunch.order', 'Order', ondelete='cascade'), - 'product_id': fields.many2one('lunch.product', 'Product', required=True), - 'date': fields.related('order_id', 'date', type='date', string="Date", readonly=True, store={ - 'lunch.order': (_get_line_order_ids, ['date'], 10), - 'lunch.order.line': (lambda self, cr, uid, ids, ctx: ids, [], 10), - }), - 'supplier': fields.related('product_id', 'supplier', type='many2one', relation='res.partner', string="Supplier", readonly=True, store=True), - 'user_id': fields.related('order_id', 'user_id', type='many2one', relation='res.users', string='User', readonly=True, store=True), - 'note': fields.text('Note'), - 'price': fields.float("Price"), - 'state': fields.selection([('new', 'New'), \ - ('confirmed', 'Received'), \ - ('ordered', 'Ordered'), \ - ('cancelled', 'Cancelled')], \ - 'Status', readonly=True, select=True), - 'cashmove': fields.one2many('lunch.cashmove', 'order_id', 'Cash Move', ondelete='cascade'), - - } - _defaults = { - 'state': 'new', - } + self.state = 'cancelled' + self.cashmove.unlink() -class lunch_product(osv.Model): - """ - lunch product - """ +class LunchProduct(models.Model): + """ Products available to order. A product is linked to a specific supplier. """ _name = 'lunch.product' _description = 'lunch product' - _columns = { - 'name': fields.char('Product', required=True), - 'category_id': fields.many2one('lunch.product.category', 'Category', required=True), - 'description': fields.text('Description', size=256), - 'price': fields.float('Price', digits=(16,2)), #TODO: use decimal precision of 'Account', move it from product to decimal_precision - 'supplier': fields.many2one('res.partner', 'Supplier'), - } -class lunch_product_category(osv.Model): - """ - lunch product category - """ + name = fields.Char('Product', required=True) + category_id = fields.Many2one('lunch.product.category', 'Category', required=True) + description = fields.Text('Description') + price = fields.Float('Price', digits_compute=dp.get_precision('Account')) + supplier = fields.Many2one('res.partner', 'Supplier') + + +class LunchProductCategory(models.Model): + """ Category of the product such as pizza, sandwich, pasta, chinese, burger... """ _name = 'lunch.product.category' _description = 'lunch product category' - _columns = { - 'name': fields.char('Category', required=True), #such as PIZZA, SANDWICH, PASTA, CHINESE, BURGER, ... - } -class lunch_cashmove(osv.Model): - """ - lunch cashmove => order or payment - """ + name = fields.Char('Category', required=True) + + +class LunchCashMove(models.Model): + """ Two types of cashmoves: payment (credit) or order (debit) """ _name = 'lunch.cashmove' _description = 'lunch cashmove' - _columns = { - 'user_id': fields.many2one('res.users', 'User Name', required=True), - 'date': fields.date('Date', required=True), - 'amount': fields.float('Amount', required=True), #depending on the kind of cashmove, the amount will be positive or negative - 'description': fields.text('Description'), #the description 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'), - } - _defaults = { - 'user_id': lambda self, cr, uid, context: uid, - 'date': fields.date.context_today, - 'state': 'payment', - } -class lunch_alert(osv.Model): - """ - lunch alert - """ + user_id = fields.Many2one('res.users', 'User', required=True, + 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' - _columns = { - 'message': fields.text('Message', size=256, required=True), - 'alter_type': fields.selection([('specific', 'Specific Day'), \ - ('week', 'Every Week'), \ - ('days', 'Every Day')], \ - string='Recurrency', required=True, select=True), - 'specific_day': fields.date('Day'), - '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'), - 'active_from': fields.float('Between', required=True), - 'active_to': fields.float('And', required=True), - } - _defaults = { - 'alter_type': 'specific', - 'specific_day': fields.date.context_today, - 'active_from': 7, - 'active_to': 23, - } + + 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='Recurrency', required=True, select=True, default='specific') + 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) + + @api.multi + def name_get(self): + return [(alert.id, '%s %s' % (_('Alert'), '#%d' % alert.id)) for alert in self] + + @api.one + 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 + """ + + can_display_alert = { + 'specific': (self.specific_day == fields.Date.context_today(self)), + 'week': self[datetime.datetime.now().strftime('%A').lower()], + 'days': True + } + + if can_display_alert[self.alert_type]: + mynow = fields.Datetime.context_timestamp(self, datetime.datetime.now()) + hour_to = int(self.end_hour) + min_to = int((self.end_hour - hour_to) * 60) + to_alert = datetime.time(hour_to, min_to) + hour_from = int(self.start_hour) + min_from = int((self.start_hour - hour_from) * 60) + from_alert = datetime.time(hour_from, min_from) + + if from_alert <= mynow.time() <= to_alert: + self.display = True + else: + self.display = False diff --git a/addons/lunch/report/__init__.py b/addons/lunch/report/__init__.py deleted file mode 100644 index ddaa3b45f8e..00000000000 --- a/addons/lunch/report/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- encoding: utf-8 -*- - -import order -import report_lunch_order diff --git a/addons/lunch/report/lunch_order_templates.xml b/addons/lunch/report/lunch_order_templates.xml index 30401bc142b..58af7ebdc46 100644 --- a/addons/lunch/report/lunch_order_templates.xml +++ b/addons/lunch/report/lunch_order_templates.xml @@ -1,79 +1,80 @@ - - - + diff --git a/addons/lunch/report/lunch_order_views.xml b/addons/lunch/report/lunch_order_views.xml new file mode 100644 index 00000000000..7ba774d8805 --- /dev/null +++ b/addons/lunch/report/lunch_order_views.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/addons/lunch/report/lunch_report_views.xml b/addons/lunch/report/lunch_report_views.xml deleted file mode 100644 index 990694af4a9..00000000000 --- a/addons/lunch/report/lunch_report_views.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - diff --git a/addons/lunch/report/order.py b/addons/lunch/report/order.py deleted file mode 100644 index ef5108ff567..00000000000 --- a/addons/lunch/report/order.py +++ /dev/null @@ -1,80 +0,0 @@ -# -*- encoding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -import time -from openerp.report import report_sxw -from openerp.osv import osv - - -class order(report_sxw.rml_parse): - - def get_lines(self, user,objects): - lines=[] - for obj in objects: - if user.id==obj.user_id.id: - lines.append(obj) - return lines - - def get_total(self, user,objects): - lines=[] - for obj in objects: - if user.id==obj.user_id.id: - lines.append(obj) - total=0.0 - for line in lines: - total+=line.price - self.net_total+=total - return total - - def get_nettotal(self): - return self.net_total - - def get_users(self, objects): - users=[] - for obj in objects: - if obj.user_id not in users: - users.append(obj.user_id) - return users - - def get_note(self,objects): - notes=[] - for obj in objects: - notes.append(obj.note) - return notes - - def __init__(self, cr, uid, name, context): - super(order, self).__init__(cr, uid, name, context) - self.net_total=0.0 - self.localcontext.update({ - 'time': time, - 'get_lines': self.get_lines, - 'get_users': self.get_users, - 'get_total': self.get_total, - 'get_nettotal': self.get_nettotal, - 'get_note': self.get_note, - }) - - -class report_lunchorder(osv.AbstractModel): - _name = 'report.lunch.report_lunchorder' - _inherit = 'report.abstract_report' - _template = 'lunch.report_lunchorder' - _wrapped_report_class = order diff --git a/addons/lunch/report/report_lunch_order.py b/addons/lunch/report/report_lunch_order.py deleted file mode 100644 index ac201350142..00000000000 --- a/addons/lunch/report/report_lunch_order.py +++ /dev/null @@ -1,62 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -from openerp import tools -from openerp.osv import fields,osv - -class report_lunch_order(osv.osv): - _name = "report.lunch.order.line" - _description = "Lunch Orders Statistics" - _auto = False - _rec_name = 'date' - _columns = { - 'date': fields.date('Date Order', readonly=True, select=True), - 'year': fields.char('Year', size=4, readonly=True), - 'month':fields.selection([('01','January'), ('02','February'), ('03','March'), ('04','April'), - ('05','May'), ('06','June'), ('07','July'), ('08','August'), ('09','September'), - ('10','October'), ('11','November'), ('12','December')], 'Month', readonly=True), - 'day': fields.char('Day', size=128, readonly=True), - 'user_id': fields.many2one('res.users', 'User Name'), - 'price_total':fields.float('Total Price', readonly=True), - 'note' : fields.text('Note', readonly=True), - } - _order = 'date desc' - def init(self, cr): - tools.drop_view_if_exists(cr, 'report_lunch_order_line') - cr.execute(""" - create or replace view report_lunch_order_line as ( - select - min(lo.id) as id, - lo.user_id as user_id, - lo.date as date, - to_char(lo.date, 'YYYY') as year, - to_char(lo.date, 'MM') as month, - to_char(lo.date, 'YYYY-MM-DD') as day, - lo.note as note, - sum(lp.price) as price_total - - from - lunch_order_line as lo - left join lunch_product as lp on (lo.product_id = lp.id) - group by - lo.date,lo.user_id,lo.note - ) - """) diff --git a/addons/lunch/report/report_lunch_order_view.xml b/addons/lunch/report/report_lunch_order_view.xml deleted file mode 100644 index f7bfdcd7395..00000000000 --- a/addons/lunch/report/report_lunch_order_view.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/addons/lunch/security/ir.model.access.csv b/addons/lunch/security/ir.model.access.csv index aa56786f626..244fbf11f18 100644 --- a/addons/lunch/security/ir.model.access.csv +++ b/addons/lunch/security/ir.model.access.csv @@ -11,6 +11,3 @@ 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 alert_user,"Alert user",model_lunch_alert,group_lunch_user,1,0,0,0 -access_report_order_line,access_report_order_line,model_report_lunch_order_line,,1,1,1,1 -access_lunch_validation,access_lunch_validation,model_lunch_validation,,1,1,1,1 -access_lunch_cancel,access_lunch_cancel,model_lunch_cancel,,1,1,1,1 diff --git a/addons/lunch/security/lunch_security.xml b/addons/lunch/security/lunch_security.xml index abdd0cea0a6..a4757302500 100644 --- a/addons/lunch/security/lunch_security.xml +++ b/addons/lunch/security/lunch_security.xml @@ -1,6 +1,5 @@ - - + Lunch Helps you handle your lunch needs, if you are a manager you will be able to create new products, cashmoves and to confirm or cancel orders. @@ -29,6 +28,4 @@ [(1, '=', 1)] - - - + diff --git a/addons/lunch/static/src/css/lunch.css b/addons/lunch/static/src/css/lunch.css index 96e39c53fa6..3cafcbedfda 100644 --- a/addons/lunch/static/src/css/lunch.css +++ b/addons/lunch/static/src/css/lunch.css @@ -1,43 +1,28 @@ -@charset "utf-8"; -.openerp .oe_lunch .oe_lunch_alert textarea { - background-color: #ffc7c7; - padding: 10px; - height: 1em; - margin-bottom: 20px; +.openerp .o_lunch .o_lunch_alert { + height: 22px; } -.openerp .oe_lunch button.oe_button_add { - position: relative; - top: -2px; - left: 3px; -} -.openerp .oe_lunch button.oe_button_plus { - margin: -2px; -} -.openerp .oe_lunch .oe_lunch_30pc { +.openerp .o_lunch .o_lunch_30pc { width: 30%; display: inline-block; vertical-align: top; } -.openerp .oe_lunch .oe_lunch_30pc:nth-child(3) { +.openerp .o_lunch .o_lunch_30pc:nth-child(3) { padding-right: 0; } -.openerp .oe_lunch .oe_lunch_30pc { +.openerp .o_lunch .o_lunch_30pc { padding-right: 5%; } -.openerp .oe_lunch h2 { +.openerp .o_lunch h2 { color: #7c7bad; } -.openerp .oe_lunch .oe_lunch_button { - float: right; -} -.openerp .oe_lunch .oe_lunch_vignette { +.openerp .o_lunch .o_lunch_vignette { border-bottom: 1px solid #dddddd; padding-top: 5px; padding-bottom: 5px; } -.openerp .oe_lunch .oe_lunch_vignette:last-child { +.openerp .o_lunch .o_lunch_vignette:last-child { border: none; } -.openerp .oe_lunch .oe_group_text_button { +.openerp .o_lunch .oe_group_text_button { margin-bottom: 3px; } diff --git a/addons/lunch/static/src/css/lunch.sass b/addons/lunch/static/src/css/lunch.sass index 9612b5fdd3d..f8a31ec7f47 100644 --- a/addons/lunch/static/src/css/lunch.sass +++ b/addons/lunch/static/src/css/lunch.sass @@ -1,36 +1,24 @@ @charset "utf-8" .openerp - .oe_lunch - .oe_lunch_alert - textarea - background-color: #ffc7c7 - padding: 10px - height: 1em - margin-bottom: 20px - button.oe_button_add - position: relative - top: -2px - left: 3px - button.oe_button_plus - margin: -2px - .oe_lunch_30pc + .o_lunch + .o_lunch_alert + height: 22px + .o_lunch_30pc width: 30% display: inline-block vertical-align: top - .oe_lunch_30pc:nth-child(3) + .o_lunch_30pc:nth-child(3) padding-right: 0 - .oe_lunch_30pc + .o_lunch_30pc padding-right: 5% h2 color: #7C7BAD - .oe_lunch_button - float: right - .oe_lunch_vignette + .o_lunch_vignette border-bottom: 1px solid #dddddd padding-top: 5px padding-bottom: 5px - .oe_lunch_vignette:last-child + .o_lunch_vignette:last-child border: none .oe_group_text_button margin-bottom: 3px diff --git a/addons/lunch/static/src/js/lunch.js b/addons/lunch/static/src/js/lunch.js new file mode 100644 index 00000000000..116c24389c6 --- /dev/null +++ b/addons/lunch/static/src/js/lunch.js @@ -0,0 +1,73 @@ +odoo.define('lunch.form_widgets', function (require) { +"use strict"; + +var core = require('web.core'); +var Model = require('web.Model'); +var form_common = require('web.form_common'); +var form_widgets = require('web.form_widgets'); +var form_relational = require('web.form_relational'); +var _t = core._t; +var QWeb = core.qweb; +var lunch = {}; + +lunch.lunch_order_widget_previous_orders = form_relational.AbstractManyField.extend(form_common.ReinitializeWidgetMixin, { + template : 'lunch_order_widget_previous_orders', + events: { + 'click span.add_button': 'set_order_line', + }, + init: function(field_manager, node){ + this._super.apply(this, arguments); + this.lunch_data = {}; + this.fields_to_read = ['product_id', 'supplier', 'note', 'price', 'category_id', 'currency_id']; + this.monetary = new form_widgets.FieldMonetary(field_manager, node); // create instance to use format_value + this.monetary.__edispatcherRegisteredEvents = []; // remove all bind events + }, + fetch_value: function(){ + var self = this; + return this.dataset.read_ids(this.get('value'), this.fields_to_read) + .done(function(data){ + _.each(data, function(order){ + self.lunch_data[order['id']] = order; + }); + }); + }, + get_line_value: function (id) { + var data = _.clone(this.lunch_data[id]); + if (typeof this.lunch_data[id]['product_id'][0] != 'undefined'){ + data['product_id'] = this.lunch_data[id]['product_id'][0]; + } + if (typeof this.lunch_data[id]['supplier'][0] != 'undefined'){ + data['supplier'] = this.lunch_data[id]['supplier'][0]; + } + if (typeof this.lunch_data[id]['category_id'][0] != 'undefined'){ + data['category_id'] = this.lunch_data[id]['category_id'][0]; + } + return data; + }, + set_order_line: function(event){ + var data = this.get_line_value( parseInt($(event.currentTarget).attr('id')) ); + var order_line_ids = this.field_manager.fields.order_line_ids; + order_line_ids.data_create(data); + order_line_ids.reload_current_view(); + }, + render_value: function() { + var self = this; + return this.fetch_value().done(function(data) { + if (_.isEmpty(data)) { + return self.$el.html(QWeb.render("lunch_order_widget_no_previous_order")); + } + var categories = _.groupBy(data,function(data1){return data1['supplier'][1];}); + return self.$el.html(QWeb.render("lunch_order_widget_previous_orders_list", {'widget': self, 'categories': categories} + )); + }); + }, + destroy: function () { + this.monetary.destroy(); + this._super(); + } +}); +core.form_widget_registry.add('previous_order', lunch.lunch_order_widget_previous_orders); + +return lunch; + +}); diff --git a/addons/lunch/static/src/xml/lunch.xml b/addons/lunch/static/src/xml/lunch.xml new file mode 100644 index 00000000000..66f9a806600 --- /dev/null +++ b/addons/lunch/static/src/xml/lunch.xml @@ -0,0 +1,42 @@ + + + +
+
+
+
+ +
+

This is the first time you order a meal

+

Select a product and put your order comments on the note.

+

Your favorite meals will be created based on your last orders.

+

Don't forget the alerts displayed in the reddish area

+
+
+ + +
+

+ +
+ + + +
+
+ + + + +
+
+
+
+
+
+
+
+
diff --git a/addons/lunch/tests/__init__.py b/addons/lunch/tests/__init__.py index fd218c34e65..a83148bcdce 100644 --- a/addons/lunch/tests/__init__.py +++ b/addons/lunch/tests/__init__.py @@ -1,22 +1,3 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Business Applications -# Copyright (c) 2012-TODAY OpenERP S.A. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## -from . import test_lunch +import test_lunch diff --git a/addons/lunch/tests/test_lunch.py b/addons/lunch/tests/test_lunch.py index 2a2a6aa0ed7..6b7ea1f8461 100644 --- a/addons/lunch/tests/test_lunch.py +++ b/addons/lunch/tests/test_lunch.py @@ -1,92 +1,61 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Business Applications -# Copyright (c) 2012-TODAY OpenERP S.A. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## -from openerp import tools from openerp.tests import common + class Test_Lunch(common.TransactionCase): def setUp(self): """*****setUp*****""" super(Test_Lunch, self).setUp() - cr, uid = self.cr, self.uid - self.res_users = self.registry('res.users') - self.lunch_order = self.registry('lunch.order') - self.lunch_order_line = self.registry('lunch.order.line') - self.lunch_cashmove = self.registry('lunch.cashmove') - self.lunch_product = self.registry('lunch.product') - self.lunch_alert = self.registry('lunch.alert') - self.lunch_product_category = self.registry('lunch.product.category') - - self.demo_id = self.res_users.search(cr, uid, [('name', '=', 'Demo User')]) - self.product_bolognese_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'lunch', 'product_Bolognese') + self.demo_user = self.env['res.users'].search([('name', '=', 'Demo User')]) + self.product_bolognese_ref = self.env['ir.model.data'].get_object_reference('lunch', 'product_Bolognese') self.product_Bolognese_id = self.product_bolognese_ref and self.product_bolognese_ref[1] or False - self.new_id_order = self.lunch_order.create(cr,uid,{ - 'user_id': self.demo_id[0], - 'order_line_ids':'[]', - },context=None) - self.new_id_order_line = self.lunch_order_line.create(cr,uid,{ - 'order_id':self.new_id_order, - 'product_id':self.product_Bolognese_id, + 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, 'note': '+Emmental', 'cashmove': [], - 'price': self.lunch_product.browse(cr,uid,self.product_Bolognese_id,context=None).price, + 'price': self.env['lunch.product'].browse(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""" - cr, uid = self.cr, self.uid - self.order_one = self.lunch_order_line.browse(cr,uid,self.new_id_order_line,context=None) + 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(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.lunch_order_line.browse(cr,uid,self.new_id_order_line,context=None) + 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(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""" - cr, uid = self.cr, self.uid 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.lunch_order_line.browse(cr,uid,self.new_id_order_line,context=None) + 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.assertEqual(self.order_one.state, 'confirmed') self.assertTrue(self.order_one.cashmove) - self.assertTrue(self.order_one.cashmove[0].amount==-self.order_one.price) + 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""" - cr, uid = self.cr, self.uid 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.lunch_order_line.browse(cr,uid,self.new_id_order_line,context=None) + 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) \ No newline at end of file + self.assertEqual(self.order_one.state, 'cancelled') + self.assertFalse(self.order_one.cashmove) diff --git a/addons/lunch/views/lunch_templates.xml b/addons/lunch/views/lunch_templates.xml index d7e7244d28c..e025fc01a03 100644 --- a/addons/lunch/views/lunch_templates.xml +++ b/addons/lunch/views/lunch_templates.xml @@ -1,10 +1,9 @@ - - - - - + + + diff --git a/addons/lunch/views/lunch_views.xml b/addons/lunch/views/lunch_views.xml index 818ac216be7..9ad4a7b8393 100644 --- a/addons/lunch/views/lunch_views.xml +++ b/addons/lunch/views/lunch_views.xml @@ -1,6 +1,5 @@ - - - + + @@ -9,14 +8,14 @@ - - Search + + lunch.order.line.search lunch.order.line - + @@ -30,8 +29,8 @@ - - lunch employee payment + + lunch.cashmove.search lunch.cashmove @@ -45,8 +44,8 @@ - - lunch cashmove + + lunch.cashmove.search lunch.cashmove @@ -60,8 +59,8 @@ - - lunch orders + + lunch.order.search lunch.order @@ -75,8 +74,8 @@ - - Search + + lunch.alert.search lunch.alert @@ -86,8 +85,8 @@ - - cashmove tree + + lunch.cashmove.tree lunch.cashmove @@ -99,8 +98,8 @@ - - cashmove form + + lunch.cashmove.form lunch.cashmove
@@ -120,22 +119,22 @@ - + New Order lunch.order form - + - + Your Orders lunch.order tree,form - + {"search_default_is_mine":1}

- Click to create a lunch order. + Click to create a lunch order.

A lunch order is defined by its user, date and order lines. @@ -144,10 +143,10 @@

- - - - cashmove tree + + + + lunch.cashmove.tree lunch.cashmove @@ -160,13 +159,13 @@ - + Your Account lunch.cashmove tree - + {"search_default_is_mine_group":1} - +

Here you can see your cash moves.
A cash moves can be either an expense or a payment. @@ -174,14 +173,14 @@

- + - + Orders by Supplier lunch.order.line tree - + {"search_default_group_by_supplier":1, "search_default_today":1}

@@ -194,14 +193,14 @@

- + - + Control Suppliers lunch.order.line tree - + {"search_default_group_by_date":1, "search_default_group_by_supplier":1}

@@ -214,19 +213,19 @@

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

- Click to create a new payment. + Click to create a new payment.

A cashmove can either be an expense or a payment.
@@ -235,52 +234,52 @@

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

- Click to create a payment. + Click to create a payment.

Here you can see the employees' payment. A payment is a cash move from the employee to the company.

- + - + Products lunch.product tree,form

- Click to create a product for lunch. + Click to create a product for lunch.

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

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

- Click to create a lunch category. + Click to create a lunch category.

Here you can find every lunch categories for products. @@ -288,7 +287,7 @@ - + Product category Form lunch.product.category @@ -300,17 +299,17 @@ - + - + Alerts lunch.alert tree,form - +

- Click to create a lunch alert. + Click to create a lunch alert.

Alerts are used to warn employee from possible issues concerning the lunch orders. @@ -324,31 +323,31 @@

- + - - Order lines Tree + + lunch.order.line.tree lunch.order.line - - + +