diff --git a/addons/account_voucher/i18n/fr.po b/addons/account_voucher/i18n/fr.po index ea825985448..bec649eaea1 100644 --- a/addons/account_voucher/i18n/fr.po +++ b/addons/account_voucher/i18n/fr.po @@ -7,14 +7,15 @@ msgstr "" "Project-Id-Version: OpenERP Server 6.0dev\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2012-02-08 01:37+0100\n" -"PO-Revision-Date: 2012-05-10 17:31+0000\n" -"Last-Translator: Fabien (Open ERP) \n" +"PO-Revision-Date: 2012-11-07 13:27+0000\n" +"Last-Translator: Frederic Clementi - Camptocamp.com " +"\n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-10-30 05:19+0000\n" -"X-Generator: Launchpad (build 16206)\n" +"X-Launchpad-Export-Date: 2012-11-08 04:47+0000\n" +"X-Generator: Launchpad (build 16232)\n" #. module: account_voucher #: view:sale.receipt.report:0 @@ -469,7 +470,7 @@ msgstr "Délai moyen de règlement" #. module: account_voucher #: field:res.company,income_currency_exchange_account_id:0 msgid "Income Currency Rate" -msgstr "Taux de change d'achat" +msgstr "Compte de gain de change" #. module: account_voucher #: code:addons/account_voucher/account_voucher.py:1063 @@ -625,9 +626,9 @@ msgid "" "Unable to create accounting entry for currency rate difference. You have to " "configure the field 'Income Currency Rate' on the company! " msgstr "" -"Impossible de créer une entrée de la comptabilité à cause de la différence " -"de taux de change. Vous devez configurer le champ 'Taux de change de vente' " -"sur la société! " +"Impossible de créer une écriture comptable à cause de la différence de taux " +"de change. Vous devez configurer le champ 'Compte de gain de change' au " +"niveau du formulaire de la société! " #. module: account_voucher #: view:account.voucher:0 view:sale.receipt.report:0 @@ -802,7 +803,7 @@ msgstr "Factures et transactions exceptionnelles" #. module: account_voucher #: field:res.company,expense_currency_exchange_account_id:0 msgid "Expense Currency Rate" -msgstr "Taux de change de la dépense" +msgstr "Compte de perte de change" #. module: account_voucher #: sql_constraint:account.invoice:0 @@ -1089,9 +1090,9 @@ msgid "" "Unable to create accounting entry for currency rate difference. You have to " "configure the field 'Expense Currency Rate' on the company! " msgstr "" -"Impossible de créer une entrée en comptabilité pour la différence de taux de " -"change. Vous devez configurer le champ \"Taux de change d'achat\" de la " -"société ! " +"Impossible de créer une écriture comptable à cause de la différence de taux " +"de change. Vous devez configurer le champ 'Compte de perte de change' au " +"niveau du formulaire de la société! " #. module: account_voucher #: field:account.voucher,type:0 @@ -1156,7 +1157,7 @@ msgstr "Année" #. module: account_voucher #: field:account.voucher.line,amount_unreconciled:0 msgid "Open Balance" -msgstr "Solde initial" +msgstr "Restant dû" #. module: account_voucher #: view:account.voucher:0 field:account.voucher,amount:0 diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 5ab68860874..64ba9476701 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -610,9 +610,9 @@ class crm_lead(base_stage, format_address, osv.osv): } def convert_opportunity(self, cr, uid, ids, partner_id, user_ids=False, section_id=False, context=None): - partner = self.pool.get('res.partner') customer = False if partner_id: + partner = self.pool.get('res.partner') customer = partner.browse(cr, uid, partner_id, context=context) for lead in self.browse(cr, uid, ids, context=context): if lead.state in ('done', 'cancel'): @@ -676,19 +676,17 @@ class crm_lead(base_stage, format_address, osv.osv): def convert_partner(self, cr, uid, ids, action='create', partner_id=False, context=None): """ - This function convert partner based on action. + Convert partner based on action. if action is 'create', create new partner with contact and assign lead to new partner_id. otherwise assign lead to specified partner_id """ if context is None: context = {} partner_ids = {} - force_partner_id = partner_id for lead in self.browse(cr, uid, ids, context=context): if action == 'create': if not partner_id: partner_id = self._create_lead_partner(cr, uid, lead, context) - partner_id = force_partner_id or self._create_lead_partner(cr, uid, lead, context=context) self._lead_set_partner(cr, uid, lead, partner_id, context=context) partner_ids[lead.id] = partner_id return partner_ids diff --git a/addons/crm/wizard/crm_lead_to_opportunity.py b/addons/crm/wizard/crm_lead_to_opportunity.py index 2473a83206c..ea96e1d16f1 100644 --- a/addons/crm/wizard/crm_lead_to_opportunity.py +++ b/addons/crm/wizard/crm_lead_to_opportunity.py @@ -175,12 +175,19 @@ class crm_lead2opportunity_mass_convert(osv.osv_memory): return res def _convert_opportunity(self, cr, uid, ids, vals, context=None): + """ + When "massively" (more than one at a time) converting leads to + opportunities, check the salesteam_id and salesmen_ids and update + the values before calling super. + """ + if context is None: + context = {} data = self.browse(cr, uid, ids, context=context)[0] salesteam_id = data.section_id and data.section_id.id or False - salesman = [] + salesmen_ids = [] if data.user_ids: - salesman = [x.id for x in data.user_ids] - vals.update({'user_ids': salesman, 'section_id': salesteam_id}) + salesmen_ids = [x.id for x in data.user_ids] + vals.update({'user_ids': salesmen_ids, 'section_id': salesteam_id}) return super(crm_lead2opportunity_mass_convert, self)._convert_opportunity(cr, uid, ids, vals, context=context) def mass_convert(self, cr, uid, ids, context=None): diff --git a/addons/crm/wizard/crm_lead_to_partner.py b/addons/crm/wizard/crm_lead_to_partner.py index c201f484989..c48990f5866 100644 --- a/addons/crm/wizard/crm_lead_to_partner.py +++ b/addons/crm/wizard/crm_lead_to_partner.py @@ -35,7 +35,7 @@ class crm_lead2partner(osv.osv_memory): } def view_init(self, cr, uid, fields, context=None): """ - This function checks for precondition before wizard executes + Check for precondition before wizard executes. """ if context is None: context = {} @@ -69,22 +69,19 @@ class crm_lead2partner(osv.osv_memory): return partner_id def default_get(self, cr, uid, fields, context=None): - """ - This function gets default values - """ - res = super(crm_lead2partner, self).default_get(cr, uid, fields, context=context) + res = super(crm_lead2partner, self).default_get(cr, uid, fields, context=context) partner_id = self._select_partner(cr, uid, context=context) if 'partner_id' in fields: res.update({'partner_id': partner_id}) if 'action' in fields: res.update({'action': partner_id and 'exist' or 'create'}) - + return res def open_create_partner(self, cr, uid, ids, context=None): """ - This function Opens form of create partner. + Open form of create partner. """ view_obj = self.pool.get('ir.ui.view') view_id = view_obj.search(cr, uid, [('model', '=', self._name), \ @@ -101,21 +98,21 @@ class crm_lead2partner(osv.osv_memory): def _create_partner(self, cr, uid, ids, context=None): """ - This function Creates partner based on action. + Create partner based on action. """ if context is None: context = {} lead = self.pool.get('crm.lead') - lead_ids = context and context.get('active_ids') or [] + lead_ids = context.get('active_ids', []) data = self.browse(cr, uid, ids, context=context)[0] partner_id = data.partner_id and data.partner_id.id or False return lead.convert_partner(cr, uid, lead_ids, data.action, partner_id, context=context) def make_partner(self, cr, uid, ids, context=None): """ - This function Makes partner based on action. + Make a partner based on action. + Only called from form view, so only meant to convert one lead at a time. """ - # Only called from Form view, so only meant to convert one Lead. lead_id = context and context.get('active_id') or False partner_ids_map = self._create_partner(cr, uid, ids, context=context) return self.pool.get('res.partner').redirect_partner_form(cr, uid, partner_ids_map.get(lead_id, False), context=context) diff --git a/addons/google_base_account/i18n/es.po b/addons/google_base_account/i18n/es.po new file mode 100644 index 00000000000..c0779f7159a --- /dev/null +++ b/addons/google_base_account/i18n/es.po @@ -0,0 +1,124 @@ +# Spanish translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2012-02-08 00:36+0000\n" +"PO-Revision-Date: 2012-11-07 12:33+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Spanish \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2012-11-08 04:47+0000\n" +"X-Generator: Launchpad (build 16232)\n" + +#. module: google_base_account +#: field:res.users,gmail_user:0 +msgid "Username" +msgstr "Nombre de usuario" + +#. module: google_base_account +#: model:ir.actions.act_window,name:google_base_account.act_google_login_form +msgid "Google Login" +msgstr "Acceso de Google" + +#. module: google_base_account +#: code:addons/google_base_account/wizard/google_login.py:29 +#, python-format +msgid "Google Contacts Import Error!" +msgstr "¡Error en la importacion de los contactos de Google!" + +#. module: google_base_account +#: view:res.users:0 +msgid " Synchronization " +msgstr " Sincronización " + +#. module: google_base_account +#: sql_constraint:res.users:0 +msgid "You can not have two users with the same login !" +msgstr "¡No puede tener dos usuarios con el mismo identificador!" + +#. module: google_base_account +#: code:addons/google_base_account/wizard/google_login.py:75 +#, python-format +msgid "Error" +msgstr "Error" + +#. module: google_base_account +#: view:google.login:0 +msgid "Google login" +msgstr "Acceso Google" + +#. module: google_base_account +#: model:ir.model,name:google_base_account.model_res_users +msgid "res.users" +msgstr "Usuarios" + +#. module: google_base_account +#: field:google.login,password:0 +msgid "Google Password" +msgstr "Contraseña de Google" + +#. module: google_base_account +#: view:google.login:0 +msgid "_Cancel" +msgstr "_Cancelar" + +#. module: google_base_account +#: view:res.users:0 +msgid "Google Account" +msgstr "Cuenta de Google" + +#. module: google_base_account +#: field:google.login,user:0 +msgid "Google Username" +msgstr "Usuario de Google" + +#. module: google_base_account +#: code:addons/google_base_account/wizard/google_login.py:29 +#, python-format +msgid "" +"Please install gdata-python-client from http://code.google.com/p/gdata-" +"python-client/downloads/list" +msgstr "" +"Por favor, instale gdata-python-client de http://code.google.com/p/gdata-" +"python-client/downloads/list o desde el administrador de paquetes de su " +"distribución" + +#. module: google_base_account +#: model:ir.model,name:google_base_account.model_google_login +msgid "Google Contact" +msgstr "Contacto Google" + +#. module: google_base_account +#: view:google.login:0 +msgid "_Login" +msgstr "Iniciar _sesión" + +#. module: google_base_account +#: constraint:res.users:0 +msgid "The chosen company is not in the allowed companies for this user" +msgstr "" +"La compañía seleccionada no está entre las companías permitidas para este " +"usuario" + +#. module: google_base_account +#: field:res.users,gmail_password:0 +msgid "Password" +msgstr "Contraseña" + +#. module: google_base_account +#: code:addons/google_base_account/wizard/google_login.py:75 +#, python-format +msgid "Authentication fail check the user and password !" +msgstr "Fallo en la autenticación. ¡Compruebe el usuario y la contraseña!" + +#. module: google_base_account +#: view:google.login:0 +msgid "ex: user@gmail.com" +msgstr "ejemplo: usuario@gmail.com" diff --git a/addons/google_base_account/i18n/mn.po b/addons/google_base_account/i18n/mn.po new file mode 100644 index 00000000000..417eab16b09 --- /dev/null +++ b/addons/google_base_account/i18n/mn.po @@ -0,0 +1,119 @@ +# Mongolian translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2012-02-08 00:36+0000\n" +"PO-Revision-Date: 2012-11-08 03:11+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Mongolian \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2012-11-08 04:47+0000\n" +"X-Generator: Launchpad (build 16232)\n" + +#. module: google_base_account +#: field:res.users,gmail_user:0 +msgid "Username" +msgstr "Хэрэглэгчийн нэр" + +#. module: google_base_account +#: model:ir.actions.act_window,name:google_base_account.act_google_login_form +msgid "Google Login" +msgstr "" + +#. module: google_base_account +#: code:addons/google_base_account/wizard/google_login.py:29 +#, python-format +msgid "Google Contacts Import Error!" +msgstr "" + +#. module: google_base_account +#: view:res.users:0 +msgid " Synchronization " +msgstr "" + +#. module: google_base_account +#: sql_constraint:res.users:0 +msgid "You can not have two users with the same login !" +msgstr "Ижил нэвтрэх нэртэй хоёр хэрэглэгч байж болохгүй!" + +#. module: google_base_account +#: code:addons/google_base_account/wizard/google_login.py:75 +#, python-format +msgid "Error" +msgstr "Алдаа" + +#. module: google_base_account +#: view:google.login:0 +msgid "Google login" +msgstr "" + +#. module: google_base_account +#: model:ir.model,name:google_base_account.model_res_users +msgid "res.users" +msgstr "res.users" + +#. module: google_base_account +#: field:google.login,password:0 +msgid "Google Password" +msgstr "" + +#. module: google_base_account +#: view:google.login:0 +msgid "_Cancel" +msgstr "_Цуцлах" + +#. module: google_base_account +#: view:res.users:0 +msgid "Google Account" +msgstr "" + +#. module: google_base_account +#: field:google.login,user:0 +msgid "Google Username" +msgstr "" + +#. module: google_base_account +#: code:addons/google_base_account/wizard/google_login.py:29 +#, python-format +msgid "" +"Please install gdata-python-client from http://code.google.com/p/gdata-" +"python-client/downloads/list" +msgstr "" + +#. module: google_base_account +#: model:ir.model,name:google_base_account.model_google_login +msgid "Google Contact" +msgstr "" + +#. module: google_base_account +#: view:google.login:0 +msgid "_Login" +msgstr "" + +#. module: google_base_account +#: constraint:res.users:0 +msgid "The chosen company is not in the allowed companies for this user" +msgstr "" + +#. module: google_base_account +#: field:res.users,gmail_password:0 +msgid "Password" +msgstr "" + +#. module: google_base_account +#: code:addons/google_base_account/wizard/google_login.py:75 +#, python-format +msgid "Authentication fail check the user and password !" +msgstr "" + +#. module: google_base_account +#: view:google.login:0 +msgid "ex: user@gmail.com" +msgstr "" diff --git a/addons/lunch/__init__.py b/addons/lunch/__init__.py index b94760a67fb..fb4acc9982e 100644 --- a/addons/lunch/__init__.py +++ b/addons/lunch/__init__.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (). +# Copyright (C) 2004-2012 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 @@ -20,7 +20,5 @@ ############################################################################## import lunch -import wizard import report -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - +import wizard diff --git a/addons/lunch/__openerp__.py b/addons/lunch/__openerp__.py index 0feb1af4184..a70f6d82f9e 100644 --- a/addons/lunch/__openerp__.py +++ b/addons/lunch/__openerp__.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (). +# Copyright (C) 2004-2012 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 @@ -22,31 +22,31 @@ { 'name': 'Lunch Orders', 'author': 'OpenERP SA', - 'version': '0.1', - 'depends': [], + 'version': '0.2', + 'depends': ['base'], 'category' : 'Tools', + 'summary': 'Lunch Order, Meal, Food', 'description': """ The base module to manage lunch. ================================ -keep track for the Lunch Order, Cash Moves, CashBox, Product. Apply Different -Category for the product. - """, - 'data': [ - 'security/lunch_security.xml', - 'security/ir.model.access.csv', - 'wizard/lunch_order_cancel_view.xml', - 'wizard/lunch_order_confirm_view.xml', - 'wizard/lunch_cashbox_clean_view.xml', - 'lunch_view.xml', - 'lunch_report.xml', - 'report/report_lunch_order_view.xml', - 'lunch_installer_view.xml' - ], - 'demo': ['lunch_demo.xml'], - 'test': ['test/test_lunch.yml', 'test/lunch_report.yml'], - 'installable': True, - 'images': ['images/cash_moves.jpeg','images/lunch_orders.jpeg','images/products.jpeg'], -} +Many companies order sandwiches, pizzas and other, from usual suppliers, for their employees to offer them more facilities. -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: +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. + +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. + +If you want to save your employees' time and avoid them to always have coins in their pockets, this module is essential. + """, + 'data': ['security/lunch_security.xml','lunch_view.xml','wizard/lunch_order_view.xml','wizard/lunch_validation_view.xml','wizard/lunch_cancel_view.xml','lunch_report.xml', + 'report/report_lunch_order_view.xml', + 'security/ir.model.access.csv',], + 'css':['static/src/css/lunch.css'], + 'demo': ['lunch_demo.xml',], + 'installable': True, + 'application' : True, + 'certificate' : '001292377792581874189', + 'images': [], +} diff --git a/addons/lunch/lunch.py b/addons/lunch/lunch.py index c249478ad48..0d224ef3a8e 100644 --- a/addons/lunch/lunch.py +++ b/addons/lunch/lunch.py @@ -19,234 +19,451 @@ # ############################################################################## +from xml.sax.saxutils import escape +import time from osv import osv, fields +from datetime import datetime +from lxml import etree +import tools +from tools.translate import _ -class lunch_category(osv.osv): - """ Lunch category """ - - _name = 'lunch.category' - _description = "Category" - - _columns = { - 'name': fields.char('Name', required=True, size=50), - } - _order = 'name' - -lunch_category() - - -class lunch_product(osv.osv): - """ Lunch Product """ - - _name = 'lunch.product' - _description = "Lunch Product" - - _columns = { - 'name': fields.char('Name', size=50, required=True), - 'category_id': fields.many2one('lunch.category', 'Category'), - 'description': fields.text('Description', size=128, required=False), - 'price': fields.float('Price', digits=(16,2)), - 'active': fields.boolean('Active'), - } - - _defaults = { - 'active': lambda *a : True, - } - -lunch_product() - - -class lunch_cashbox(osv.osv): - """ cashbox for Lunch """ - - _name = 'lunch.cashbox' - _description = "Cashbox for Lunch " - - - def amount_available(self, cr, uid, ids, field_name, arg, context=None): - - """ count available amount - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param ids: List of create menu’s IDs - @param context: A standard dictionary for contextual values """ - - cr.execute("SELECT box,sum(amount) from lunch_cashmove where active = 't' group by box") - amount = dict(cr.fetchall()) - for i in ids: - amount.setdefault(i, 0) - return amount - - _columns = { - 'manager': fields.many2one('res.users', 'Manager'), - 'name': fields.char('Name', size=30, required=True, unique = True), - 'sum_remain': fields.function(amount_available, string='Total Remaining'), - } - -lunch_cashbox() - - -class lunch_cashmove(osv.osv): - """ Move cash """ - - _name = 'lunch.cashmove' - _description = "Cash Move" - - _columns = { - 'name': fields.char('Description', size=128), - 'user_cashmove': fields.many2one('res.users', 'User Name', required=True), - 'amount': fields.float('Amount', digits=(16, 2)), - 'box': fields.many2one('lunch.cashbox', 'Box Name', size=30, required=True), - 'active': fields.boolean('Active'), - 'create_date': fields.datetime('Creation Date', readonly=True), - } - - _defaults = { - 'active': lambda *a: True, - } - -lunch_cashmove() - - -class lunch_order(osv.osv): - """ Apply lunch order """ - +class lunch_order(osv.Model): + """ + lunch order (contains one or more lunch order line(s)) + """ _name = 'lunch.order' - _description = "Lunch Order" - _rec_name = "user_id" + _description = 'Lunch Order' - def _price_get(self, cr, uid, ids, name, args, context=None): + def _price_get(self, cr, uid, ids, name, arg, context=None): + """ + 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 - """ Get Price of Product - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param ids: List of Lunch order’s IDs - @param context: A standard dictionary for contextual values """ + 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´ + """ + 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) - res = {} - for price in self.browse(cr, uid, ids, context=context): - res[price.id] = price.product.price + 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('monday tuestday wednesday thursday friday saturday sunday'.split())) + if today in mapping: + return 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) + + 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) + + def onchange_price(self, cr, uid, ids, order_line_ids, context=None): + """ + Onchange methode that refresh the total price of 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 + else: + tot += prod['price'] + res = {'value': {'total': tot}} + return res + + 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__(self,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='create_date 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 + 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() + for val in value: + for pref in val.values(): + #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={'draft':[('readonly', False)]}), - 'product': fields.many2one('lunch.product', 'Product', required=True, \ - readonly=True, states={'draft':[('readonly', False)]}, change_default=True), - 'date': fields.date('Date', readonly=True, states={'draft':[('readonly', False)]}), - 'cashmove': fields.many2one('lunch.cashmove', 'Cash Move' , readonly=True), - 'descript': fields.char('Comment', readonly=True, size=250, \ - states = {'draft':[('readonly', False)]}), - 'state': fields.selection([('draft', 'New'), ('confirmed', 'Confirmed'), ], \ - 'Status', readonly=True, select=True), - 'price': fields.function(_price_get, string="Price"), - 'category': fields.many2one('lunch.category','Category'), + '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)]}), + '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), + 'alerts': fields.function(_alerts_get, string="Alerts", type='text'), } _defaults = { 'user_id': lambda self, cr, uid, context: uid, 'date': fields.date.context_today, - 'state': lambda self, cr, uid, context: 'draft', + 'state': 'new', + 'alerts': _default_alerts_get, } - def confirm(self, cr, uid, ids, box, context=None): - """ confirm order - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param ids: List of confirm order’s IDs - @param context: A standard dictionary for contextual values """ +class lunch_order_line(osv.Model): + """ + lunch order line: one lunch order can have many order lines + """ + _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}} + + def order(self, cr, uid, ids, context=None): + """ + The order_line is ordered to the supplier but isn't received yet + """ + for order_line in self.browse(cr, uid, ids, context=context): + order_line.write({'state': 'ordered'}, context=context) + return self._update_order_lines(cr, uid, ids, context=context) + + def confirm(self, cr, uid, ids, context=None): + """ + confirm one or more order line, update order status and create new cashmove + """ cashmove_ref = self.pool.get('lunch.cashmove') - for order in self.browse(cr, uid, ids, context=context): - if order.state == 'confirmed': - continue - new_id = cashmove_ref.create(cr, uid, {'name': order.product.name+' order', - 'amount':-order.product.price, - 'user_cashmove':order.user_id.id, - 'box':box, - 'active':True, - }) - self.write(cr, uid, [order.id], {'cashmove': new_id, 'state': 'confirmed'}) + 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'}, context=context) + return self._update_order_lines(cr, uid, ids, context=context) + + 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 lunch_order_cancel(self, cr, uid, ids, context=None): - - """" cancel order - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param ids: List of create menu’s IDs - @param context: A standard dictionary for contextual values """ - - orders = self.browse(cr, uid, ids, context=context) - for order in orders: - if not order.cashmove: - continue - if order.cashmove.id: - self.pool.get('lunch.cashmove').unlink(cr, uid, [order.cashmove.id]) - self.write(cr, uid, ids, {'state':'draft'}) - return {} - - def onchange_product(self, cr, uid, ids, product): - - """ Get price for Product - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param ids: List of create menu’s IDs - @product: Product To Ordered """ - - if not product: - return {'value': {'price': 0.0}} - price = self.pool.get('lunch.product').read(cr, uid, product, ['price'])['price'] - categ_id = self.pool.get('lunch.product').browse(cr, uid, product).category_id.id - return {'value': {'price': price,'category':categ_id}} - -lunch_order() - - -class report_lunch_amount(osv.osv): - """ Lunch Amount Report """ - - _name = 'report.lunch.amount' - _description = "Amount available by user and box" - _auto = False - _rec_name = "user_id" + def cancel(self, cr, uid, ids, context=None): + """ + cancel one or more order.line, update order status and unlink existing cashmoves + """ + cashmove_ref = self.pool.get('lunch.cashmove') + for order_line in self.browse(cr, uid, ids, context=context): + order_line.write({'state':'cancelled'}, 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) _columns = { - 'user_id': fields.many2one('res.users', 'User Name', readonly=True), - 'amount': fields.float('Amount', readonly=True, digits=(16, 2)), - 'box': fields.many2one('lunch.cashbox', 'Box Name', size=30, readonly=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), - 'date': fields.date('Created Date', readonly=True), + '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=True), + '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', } - def init(self, cr): - """ @param cr: the current row, from the database cursor""" +class lunch_product(osv.Model): + """ + lunch product + """ + _name = 'lunch.product' + _description = 'lunch product' + _columns = { + 'name': fields.char('Product', required=True, size=64), + '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'), + } - cr.execute(""" - create or replace view report_lunch_amount as ( - select - min(lc.id) as id, - to_date(to_char(lc.create_date, 'dd-MM-YYYY'),'dd-MM-YYYY') as date, - to_char(lc.create_date, 'YYYY') as year, - to_char(lc.create_date, 'MM') as month, - to_char(lc.create_date, 'YYYY-MM-DD') as day, - lc.user_cashmove as user_id, - sum(amount) as amount, - lc.box as box - from - lunch_cashmove lc - where - active = 't' - group by lc.user_cashmove, lc.box, lc.create_date - )""") +class lunch_product_category(osv.Model): + """ + lunch product category + """ + _name = 'lunch.product.category' + _description = 'lunch product category' + _columns = { + 'name': fields.char('Category', required=True), #such as PIZZA, SANDWICH, PASTA, CHINESE, BURGER, ... + } -report_lunch_amount() - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: +class lunch_cashmove(osv.Model): + """ + lunch cashmove => order or payment + """ + _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 + """ + _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, + } diff --git a/addons/lunch/lunch_demo.xml b/addons/lunch/lunch_demo.xml index 543086d6620..a9b03137bf2 100644 --- a/addons/lunch/lunch_demo.xml +++ b/addons/lunch/lunch_demo.xml @@ -2,23 +2,180 @@ - + + + + + + + + Sandwich + + Pizza + + + Pasta + - - Club + + Coin gourmand + True + + + + Pizza Inn + True + + + + Cheese And Ham - 2.75 + 3.30 + + Cheese, Ham, Salad, Tomatoes, cucumbers, eggs - - Cashbox - + + The Country + + 3.30 + + Brie, Honey, Walnut Kernels - - + + Tuna + + 2.50 + + Tuna, Mayonnaise + + + + Gouda Cheese + + 2.50 + + + + + + Chicken Curry + + 2.60 + + + + + + Pizza Margherita + + 6.90 + + Tomatoes, Mozzarella + + + + Pizza Italiana + + 7.40 + + Fresh Tomatoes, Basil, Mozzarella + + + + Bolognese Pasta + + 7.70 + + + + + + Napoli Pasta + + 7.70 + + Tomatoes, Basil + + + + + + + new + 1 + + + + + + + confirmed + 1 + + + + + + + cancelled + 1 + + + + + + + new + + +Emmental + + + + + + + + confirmed + + +Mushrooms + + + + + + + + cancelled + + +Salad +Tomatoes +Cucumbers + + + + + + + + Pizza Italiana + -7.40 + + order + + + + + + Payment: 5 lunch tickets (6€) + 30 + payment + + + + Lunch must be ordered before 10h30 am + days diff --git a/addons/lunch/lunch_installer_view.xml b/addons/lunch/lunch_installer_view.xml deleted file mode 100644 index d63f44b5ff2..00000000000 --- a/addons/lunch/lunch_installer_view.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - Define Your Lunch Products - ir.actions.act_window - lunch.product - form - tree,form - - -

- Click to add a new product that can be ordered for the lunch. -

- We suggest you to put the real price so that the exact due - amount is deduced from each employee's cash boxes when they - order. -

- If you order lunch at several places, you can use the product - categories to split by supplier. It will be easier to filter - lunch orders. -

-
-
- - - - 50 - - - - Create Lunch Cash Boxes - ir.actions.act_window - lunch.cashbox - form - tree,form - -

- Click to add a new cash box. -

- You can create on cash box by employee if you want to keep - track of the amount due by employee according to what have been - ordered. -

-
-
- - - - 51 - -
-
diff --git a/addons/lunch/lunch_report.xml b/addons/lunch/lunch_report.xml index c5e933847cd..942caadeed3 100644 --- a/addons/lunch/lunch_report.xml +++ b/addons/lunch/lunch_report.xml @@ -4,8 +4,8 @@ diff --git a/addons/lunch/lunch_view.xml b/addons/lunch/lunch_view.xml index b560ada323c..32b978dfab0 100644 --- a/addons/lunch/lunch_view.xml +++ b/addons/lunch/lunch_view.xml @@ -1,393 +1,486 @@ - - + + + + + + + + + + Search + lunch.order.line + search + + + + + + + + + + + + + + + + - + + + lunch employee payment + lunch.cashmove + search + + + + + + + + + + - + + lunch cashmove + lunch.cashmove + search + + + + + + + + + + - + + + lunch orders + lunch.order + search + + + + + + + + - - + + Search + lunch.alert + search + + + + + + - + + + New Order + lunch.order + form + + - - Order + + Your Orders + lunch.order + tree,form + + {"search_default_is_mine":1} + +

+ Click to create a lunch order. +

+

+ A lunch order is defined by its user, date and order lines. + Each order line corresponds to a product, an additional note and a price. + Before selecting your order lines, don't forget to read the warnings displayed in the reddish area. +

+
+
+ + + + + Your Account + lunch.cashmove + tree + + {"search_default_is_mine":1} + +

+ Here you can see your cash moves.
A cash moves can be either 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 Supplier + lunch.order.line + tree + + {"search_default_group_by_supplier":1, "search_default_today":1} + +

+ Here you can see today's orders grouped by suppliers. +

+

+ - 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 Suppliers + lunch.order.line + tree + + {"search_default_group_by_date":1, "search_default_group_by_supplier":1} + +

+ Here you can see every orders grouped by suppliers 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,form + + {"search_default_group_by_user":1} + +

+ Click to 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,form + + {"search_default_is_payment":1} + +

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

+

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

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

+ Click to create a lunch category. +

+

+ Here you can find every lunch categories for products. +

+
+
+ + + Product category Form + lunch.product.category + form + +
+ + + + + +
+
+
+ + + + + + Alerts + lunch.alert + tree,form + + +

+ Click to create a 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 recurrency, 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" +

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