diff --git a/addons/account/account.py b/addons/account/account.py index c9dd1950347..cfba30856a9 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -541,10 +541,18 @@ class account_account(osv.osv): return False return True + def _check_company_account(self, cr, uid, ids, context=None): + for account in self.browse(cr, uid, ids, context=context): + if account.parent_id: + if account.company_id != account.parent_id.company_id: + return False + return True + _constraints = [ (_check_recursion, 'Error!\nYou cannot create recursive accounts.', ['parent_id']), (_check_type, 'Configuration Error!\nYou cannot define children to an account with internal type different of "View".', ['type']), (_check_account_type, 'Configuration Error!\nYou cannot select an account type with a deferral method different of "Unreconciled" for accounts with internal type "Payable/Receivable".', ['user_type','type']), + (_check_company_account, 'Error!\nYou cannot create an account which has parent account of different company.', ['parent_id']), ] _sql_constraints = [ ('code_company_uniq', 'unique (code,company_id)', 'The code of the account must be unique per company !') diff --git a/addons/account/account_analytic_line.py b/addons/account/account_analytic_line.py index f0a420f7496..066f8d1abec 100644 --- a/addons/account/account_analytic_line.py +++ b/addons/account/account_analytic_line.py @@ -107,7 +107,7 @@ class account_analytic_line(osv.osv): if journal_id: journal = analytic_journal_obj.browse(cr, uid, journal_id, context=context) if journal.type == 'sale': - product_price_type_ids = product_price_type_obj.search(cr, uid, [('field','=','list_price')], context) + product_price_type_ids = product_price_type_obj.search(cr, uid, [('field','=','list_price')], context=context) if product_price_type_ids: pricetype = product_price_type_obj.browse(cr, uid, product_price_type_ids, context=context)[0] # Take the company currency as the reference one diff --git a/addons/account/account_bank_statement.py b/addons/account/account_bank_statement.py index 8e79376aa87..4422537007f 100644 --- a/addons/account/account_bank_statement.py +++ b/addons/account/account_bank_statement.py @@ -486,6 +486,19 @@ class account_bank_statement(osv.osv): default['move_line_ids'] = [] return super(account_bank_statement, self).copy(cr, uid, id, default, context=context) + def button_journal_entries(self, cr, uid, ids, context=None): + ctx = (context or {}).copy() + ctx['journal_id'] = self.browse(cr, uid, ids[0], context=context).journal_id.id + return { + 'view_type':'form', + 'view_mode':'tree', + 'res_model':'account.move.line', + 'view_id':False, + 'type':'ir.actions.act_window', + 'domain':[('statement_id','in',ids)], + 'context':ctx, + } + account_bank_statement() class account_bank_statement_line(osv.osv): diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py index b01fc974bef..3e86c4963b7 100644 --- a/addons/account/account_invoice.py +++ b/addons/account/account_invoice.py @@ -402,6 +402,7 @@ class account_invoice(osv.osv): 'default_res_id': ids[0], 'default_use_template': True, 'default_template_id': template_id, + 'default_composition_mode': 'comment', }) return { 'view_type': 'form', @@ -431,7 +432,7 @@ class account_invoice(osv.osv): if t['state'] in ('draft', 'cancel') and t['internal_number']== False: unlink_ids.append(t['id']) else: - raise osv.except_osv(_('Invalid Action!'), _('You cannot delete an invoice which is open or paid. You should refund it instead.')) + raise osv.except_osv(_('Invalid Action!'), _('You can not delete an invoice which is not cancelled. You should refund it instead.')) osv.osv.unlink(self, cr, uid, unlink_ids, context=context) return True @@ -983,13 +984,13 @@ class account_invoice(osv.osv): for i in line: i[2]['period_id'] = period_id + ctx.update(invoice=inv) move_id = move_obj.create(cr, uid, move, context=ctx) new_move_name = move_obj.browse(cr, uid, move_id, context=ctx).name # make the invoice point to that move self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name}, context=ctx) # Pass invoice in context in method post: used if you want to get the same # account move reference when creating the same invoice after a cancelled one: - ctx.update({'invoice':inv}) move_obj.post(cr, uid, [move_id], context=ctx) self._log_event(cr, uid, ids) return True diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml index 2f722d07aeb..d3697b13ea9 100644 --- a/addons/account/account_invoice_view.xml +++ b/addons/account/account_invoice_view.xml @@ -168,10 +168,8 @@ context="{'default_customer': 0, 'search_default_supplier': 1, 'default_supplier': 1}" domain="[('supplier', '=', True)]"/> - - - - + + + + + fleet.vehicle.tree + fleet.vehicle + 1 + + + + + + + + + + + + + + + + + + + fleet.vehicle.search + fleet.vehicle + + + + + + + + + + + + + + + + fleet.vehicle.kanban + fleet.vehicle + + + + + + + + + + + + + + + + + +
+ + + +
+

+ +
+ +
+

+ + + + and other(s) + + + + + + + + and other(s) + + + + +
    +
  • + +
  • +
  • + +
  • + +
+ +
+ +
+ +
+ +
+
+ + + + + + + + + Vehicles + fleet.vehicle + form + kanban,tree,form + +

+ Click to create a new vehicle. +

+ You will be able to manage your fleet by keeping track of the + contracts, services, fixed and recurring costs, odometers and + fuel logs associated to each vehicle. +

+ OpenERP will warn you when services or contract have to be + renewed. +

+
+
+ + + + + + + + fleet.vehicle.log_contract.form + fleet.vehicle.log.contract + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + +
+ + + +
+
+
+
+ + + + + + fleet.vehicle.log.contract.tree + fleet.vehicle.log.contract + + + + + + + + + + + + + + + + + + fleet.vehicle.log.contract.graph + fleet.vehicle.log.contract + + + + + + + + + + + + Vehicles Contracts + fleet.vehicle.log.contract + form + tree,form,graph + +

+ Click to create a new contract. +

+ Manage all your contracts (leasing, insurances, etc.) with + their related services, costs. OpenERP will automatically warn + you when some contracts have to be renewed. +

+ Each contract (e.g.: leasing) may include several services + (reparation, insurances, periodic maintenance). +

+
+
+ + + + + fleet.vehicle.odometer.form + fleet.vehicle.odometer + +
+ + + +
+ + +
+ +
+
+
+
+
+ + + fleet.vehicle.odometer.tree + fleet.vehicle.odometer + + + + + + + + + + + + + fleet.vehicle.odometer.graph + fleet.vehicle.odometer + + + + + + + + + + + + Vehicles Odometer + fleet.vehicle.odometer + form + tree,graph + {"search_default_groupby_vehicle" : True} + +

+ Here you can add various odometer entries for all vehicles. + You can also show odometer value for a particular vehicle using + the search field. +

+
+
+ + + + + fleet.vehicle.log.fuel.form + fleet.vehicle.log.fuel + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + fleet.vehicle.log.fuel.tree + fleet.vehicle.log.fuel + + + + + + + + + + + + + + + + + + fleet.vehicle.log.fuel.graph + fleet.vehicle.log.fuel + + + + + + + + + + + + Vehicles Fuel Logs + fleet.vehicle.log.fuel + form + tree,form,graph + +

+ Click to create a new fuel log. +

+ Here you can add refuelling entries for all vehicles. You can + also filter logs of a particular vehicle using the search + field. +

+
+
+ + + + + + fleet.vehicle.log.services.form + fleet.vehicle.log.services + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + fleet.vehicle.log.services.tree + fleet.vehicle.log.services + + + + + + + + + + + + + + + + fleet.vehicle.log.services.graph + fleet.vehicle.log.services + + + + + + + + + + + Vehicles Services Logs + fleet.vehicle.log.services + form + tree,form,graph + +

+ Click to create a new service entry. +

+ OpenERP helps you keeping track of all the services done + on your vehicle. Services can be of many type: occasional + repair, fixed maintenance, etc. +

+
+
+ + + + + fleet.service.type.tree + fleet.service.type + + + + + + + + + + Service Types + fleet.service.type + form + tree,form + +

+ Click to create a new type of service. +

+ Each service can used in contracts, as a standalone service or both. +

+
+
+ + + + + + fleet.vehicle.cost.tree + fleet.vehicle.cost + + + + + + + + + + + + + + + fleet.vehicle.cost.search + fleet.vehicle.cost + + + + + + + + + + + + + + + + + + + + + + + fleet.vehicle.cost.form + fleet.vehicle.cost + +
+ + + + + + + + + + + + + +
+
+
+ + + fleet.vehicle.cost.graph + fleet.vehicle.cost + + + + + + + + + + + Vehicle Costs + fleet.vehicle.cost + form + tree,form,graph + {"search_default_parent_false" : True, "search_default_groupby_vehicle_id" : True,} + +

+ Click to create a new cost. +

+ OpenERP helps you managing the costs for your different + vehicles. Costs are created automatically from services, + contracts (fixed or recurring) and fuel logs. +

+
+
+ + + + + diff --git a/addons/fleet/security/ir.model.access.csv b/addons/fleet/security/ir.model.access.csv new file mode 100644 index 00000000000..0c50a6fbee3 --- /dev/null +++ b/addons/fleet/security/ir.model.access.csv @@ -0,0 +1,11 @@ +id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink +fleet_vehicle_model_access_right,fleet_vehicle_model_access_right,model_fleet_vehicle_model,,1,1,1,1 +fleet_vehicle_tag_access_right,fleet_vehicle_tag_access_right,model_fleet_vehicle_tag,,1,1,1,1 +fleet_vehicle_state_access_right,fleet_vehicle_state_access_right,model_fleet_vehicle_state,,1,1,1,1 +fleet_vehicle_odometer_access_right,fleet_vehicle_odometer_access_right,model_fleet_vehicle_odometer,,1,1,1,1 +fleet_vehicle_model_brand_access_right,fleet_vehicle_model_brand_access_right,model_fleet_vehicle_model_brand,,1,1,1,1 +fleet_vehicle_access_right,fleet_vehicle_access_right,model_fleet_vehicle,,1,1,1,1 +fleet_vehicle_log_fuel_access_right,fleet_vehicle_log_fuel_access_right,model_fleet_vehicle_log_fuel,,1,1,1,1 +fleet_vehicle_log_services_access_right,fleet_vehicle_log_services_access_right,model_fleet_vehicle_log_services,,1,1,1,1 +fleet_vehicle_log_contract_access_right,fleet_vehicle_log_contract_access_right,model_fleet_vehicle_log_contract,,1,1,1,1 +fleet_service_type_access_right,fleet_service_type_access_right,model_fleet_service_type,,1,1,1,1 \ No newline at end of file diff --git a/addons/fleet/static/src/img/icon.png b/addons/fleet/static/src/img/icon.png new file mode 100644 index 00000000000..5b41e43c5bd Binary files /dev/null and b/addons/fleet/static/src/img/icon.png differ 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..940a83c37d7 --- /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-09 04:39+0000\n" +"X-Generator: Launchpad (build 16250)\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/google_docs/static/src/js/gdocs.js b/addons/google_docs/static/src/js/gdocs.js index 0cc93b2510c..45e1d09d42c 100644 --- a/addons/google_docs/static/src/js/gdocs.js +++ b/addons/google_docs/static/src/js/gdocs.js @@ -16,9 +16,9 @@ var _t = instance.web._t, var view = self.getParent(); var ids = ( view.fields_view.type != "form" )? view.groups.get_selection().ids : [ view.datarecord.id ]; if( !_.isEmpty(ids) ){ - view.sidebar_context().then(function (context) { + view.sidebar_context().done(function (context) { var ds = new instance.web.DataSet(this, 'ir.attachment', context); - ds.call('google_doc_get', [view.dataset.model, ids, context]).then(function(r) { + ds.call('google_doc_get', [view.dataset.model, ids, context]).done(function(r) { if (r == 'False') { var params = { error: response, diff --git a/addons/hr/hr.py b/addons/hr/hr.py index d57fcf49ab2..5f3a5653307 100644 --- a/addons/hr/hr.py +++ b/addons/hr/hr.py @@ -158,6 +158,8 @@ class hr_employee(osv.osv): return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context) _columns = { + #we need a related field in order to be able to sort the employee by name + 'name_related': fields.related('resource_id', 'name', type='char', string='Name', readonly=True, store=True), 'country_id': fields.many2one('res.country', 'Nationality'), 'birthday': fields.date("Date of Birth"), 'ssnid': fields.char('SSN No', size=32, help='Social Security Number'), @@ -207,6 +209,8 @@ class hr_employee(osv.osv): 'last_login': fields.related('user_id', 'date', type='datetime', string='Latest Connection', readonly=1), } + _order='name_related' + def create(self, cr, uid, data, context=None): employee_id = super(hr_employee, self).create(cr, uid, data, context=context) try: diff --git a/addons/hr/hr_data.xml b/addons/hr/hr_data.xml index 514cf1f80f6..25c882f2201 100644 --- a/addons/hr/hr_data.xml +++ b/addons/hr/hr_data.xml @@ -6,11 +6,11 @@ mail.group notification + Employee Directory application installed! - Manage your human resources with OpenERP: employees and their hierarchy, HR departments and job positions. - -More HR features are available via extra applications: Recruitment Process (manage job positions and recruitment), Timesheet Validation (record timesheets and attendance), -Leave Management (keep track of employee leaves), Expense Management (manage employee expenses), Employee Appraisals (organize employee surveys, where employees evaluate their subordinates or their manager). + Manage your human resources with OpenERP: employees and their hierarchy, HR departments and job positions.

+

More HR features are available via extra applications: Recruitment Process (manage job positions and recruitment), Timesheet Validation (record timesheets and attendance), +Leave Management (keep track of employee leaves), Expense Management (manage employee expenses), Employee Appraisals (organize employee surveys, where employees evaluate their subordinates or their manager).

]]>
diff --git a/addons/hr/hr_view.xml b/addons/hr/hr_view.xml index c4a622d7a4f..8918c5012c3 100644 --- a/addons/hr/hr_view.xml +++ b/addons/hr/hr_view.xml @@ -288,7 +288,7 @@ - Categories of Employee + Categories of Employees hr.employee.category form tree,form @@ -342,8 +342,8 @@ - + diff --git a/addons/hr/i18n/es_EC.po b/addons/hr/i18n/es_EC.po index 32e81b6d14d..bfbbb7fa180 100644 --- a/addons/hr/i18n/es_EC.po +++ b/addons/hr/i18n/es_EC.po @@ -8,15 +8,14 @@ msgstr "" "Project-Id-Version: openobject-addons\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2012-02-08 01:37+0100\n" -"PO-Revision-Date: 2012-01-03 02:55+0000\n" -"Last-Translator: Christopher Ormaza - (Ecuadorenlinea.net) " -"\n" +"PO-Revision-Date: 2012-11-10 17:20+0000\n" +"Last-Translator: Cristian Salamea (Gnuthink) \n" "Language-Team: Spanish (Ecuador) \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:17+0000\n" -"X-Generator: Launchpad (build 16206)\n" +"X-Launchpad-Export-Date: 2012-11-11 04:57+0000\n" +"X-Generator: Launchpad (build 16251)\n" #. module: hr #: model:process.node,name:hr.process_node_openerpuser0 @@ -199,6 +198,8 @@ msgstr "Mujer" msgid "" "Expected number of employees for this job position after new recruitment." msgstr "" +"Número de Empleados para este puesto de trabajo después de las nuevas " +"contrataciones" #. module: hr #: model:ir.ui.menu,name:hr.menu_open_view_attendance_reason_new_config @@ -291,7 +292,7 @@ msgstr "Categorías" #. module: hr #: field:hr.job,expected_employees:0 msgid "Total Employees" -msgstr "" +msgstr "Total de Empleados" #. module: hr #: selection:hr.employee,marital:0 @@ -435,7 +436,7 @@ msgstr "Estado" #: model:ir.actions.act_window,name:hr.open_view_categ_tree #: model:ir.ui.menu,name:hr.menu_view_employee_category_tree msgid "Categories Structure" -msgstr "" +msgstr "Estructura de categorías" #. module: hr #: field:hr.employee,partner_id:0 @@ -465,7 +466,7 @@ msgstr "¡Error! No se puede crear una jerarquía recursiva de empleados." #. module: hr #: model:ir.actions.act_window,name:hr.action2 msgid "Subordinate Hierarchy" -msgstr "" +msgstr "Jerarquía subirdinada" #. module: hr #: model:ir.actions.act_window,help:hr.view_department_form_installer @@ -715,12 +716,12 @@ msgstr "Subordinados" #. module: hr #: field:hr.job,no_of_employee:0 msgid "Number of employees currently occupying this job position." -msgstr "" +msgstr "Número de empleados ocupando actualmente este puesto de trabajo" #. module: hr #: field:hr.job,no_of_recruitment:0 msgid "Number of new employees you expect to recruit." -msgstr "" +msgstr "Número de empleados que espera contratar" #~ msgid "Working Time Categories" #~ msgstr "Categorías de Horarios de Trabajo" diff --git a/addons/hr/static/src/css/hr.css b/addons/hr/static/src/css/hr.css index 00ef92c0fd8..f395f9939e5 100644 --- a/addons/hr/static/src/css/hr.css +++ b/addons/hr/static/src/css/hr.css @@ -18,10 +18,11 @@ -o-border-radius: 3px; -ms-border-radius: 3px; border-radius: 3px; + border-collapse: separate; -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); -o-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); - -box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); + box-shadow: 0 1px 4px 3px rgba(0, 0, 0, 0.4); } .oe_employee_picture { diff --git a/addons/hr_attendance/report/attendance_by_month.py b/addons/hr_attendance/report/attendance_by_month.py index f104b9c287a..6d41869ff23 100644 --- a/addons/hr_attendance/report/attendance_by_month.py +++ b/addons/hr_attendance/report/attendance_by_month.py @@ -31,6 +31,7 @@ from report.interface import toxml from report import report_sxw from tools import ustr from tools.translate import _ +from tools import to_xml one_day = relativedelta(days=1) month2name = [0, 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] @@ -111,7 +112,7 @@ class report_custom(report_rml): %s %s - ''' % (str(rml_obj.formatLang(time.strftime("%Y-%m-%d"),date=True))+' ' + str(time.strftime("%H:%M")),pooler.get_pool(cr.dbname).get('res.users').browse(cr,uid,uid).company_id.name) + ''' % (str(rml_obj.formatLang(time.strftime("%Y-%m-%d"),date=True))+' ' + str(time.strftime("%H:%M")),to_xml(pooler.get_pool(cr.dbname).get('res.users').browse(cr,uid,uid).company_id.name)) first_date = str(month) som = datetime.strptime(first_date, '%Y-%m-%d %H:%M:%S') diff --git a/addons/hr_attendance/static/src/js/attendance.js b/addons/hr_attendance/static/src/js/attendance.js index d92e7534786..84516353b9d 100644 --- a/addons/hr_attendance/static/src/js/attendance.js +++ b/addons/hr_attendance/static/src/js/attendance.js @@ -57,7 +57,7 @@ openerp.hr_attendance = function (instance) { var employee = new instance.web.DataSetSearch(self, 'hr.employee', self.session.user_context, [ ['user_id', '=', self.session.uid] ]); - return employee.read_slice(['id', 'name', 'state', 'last_sign', 'attendance_access']).pipe(function (res) { + return employee.read_slice(['id', 'name', 'state', 'last_sign', 'attendance_access']).then(function (res) { if (_.isEmpty(res) ) return; if (res[0].attendance_access == false){ @@ -75,7 +75,7 @@ openerp.hr_attendance = function (instance) { do_update: function () { this._super(); var self = this; - this.update_promise = this.update_promise.then(function () { + this.update_promise = this.update_promise.done(function () { if (self.attendanceslider) return; self.attendanceslider = new instance.hr_attendance.AttendanceSlider(self); diff --git a/addons/hr_evaluation/hr_evaluation.py b/addons/hr_evaluation/hr_evaluation.py index b13e90cd627..eece0c7497e 100644 --- a/addons/hr_evaluation/hr_evaluation.py +++ b/addons/hr_evaluation/hr_evaluation.py @@ -291,6 +291,7 @@ survey_request() class hr_evaluation_interview(osv.osv): _name = 'hr.evaluation.interview' _inherits = {'survey.request': 'request_id'} + _inherit = 'mail.thread' _rec_name = 'request_id' _description = 'Appraisal Interview' _columns = { diff --git a/addons/hr_evaluation/hr_evaluation_data.xml b/addons/hr_evaluation/hr_evaluation_data.xml index 01915de76a0..2afb5200091 100644 --- a/addons/hr_evaluation/hr_evaluation_data.xml +++ b/addons/hr_evaluation/hr_evaluation_data.xml @@ -11,9 +11,9 @@ mail.group notification + Employee Appraisals application installed! - Manage employee reviews: you can define an appraisal campaign with several steps, with specific evaluation surveys according to hierarchy levels. -Evaluations filled by employees may be exported as pdf files. + Manage employee reviews: you can define an appraisal campaign with several steps, with specific evaluation surveys according to hierarchy levels. Evaluations filled by employees may be exported as pdf files.

]]>
@@ -811,8 +811,8 @@ Once the form had been filled, the employee send it to his supervisor. The comment you entered is in an invalid format. * His direct reports will be invited through OpenERP to express a feedback on their supervisor's leadership and to give their opinion about their own engagement and effectiveness, the continuous improvement and openness in action in the company, ... -* The employees will send back their anonymous answers to OpenERP. The data will be handled by the HR manager and a brief summary of the data will be sent to the concerned supervisor, to his team and to the supervisor's supervisor. -* The appraiser should rate the employee’s major work accomplishments and performance according to the metric provided below : +* The employees will send back their anonymous answers to OpenERP. The data will be handled by the HR manager and a brief summary of the data will be sent to the concerned supervisor, to his team and to the supervisor's supervisor. +* The appraiser should rate the employee’s major work accomplishments and performance according to the metric provided below : 1 - Significantly exceeds standards and expectations required of the position 2 - Exceeds standards and expectations diff --git a/addons/hr_evaluation/hr_evaluation_view.xml b/addons/hr_evaluation/hr_evaluation_view.xml index 697ed6bacea..e0978847acb 100644 --- a/addons/hr_evaluation/hr_evaluation_view.xml +++ b/addons/hr_evaluation/hr_evaluation_view.xml @@ -266,7 +266,7 @@ Each employee may be assigned an Appraisal Plan. Such a plan defines the frequency and the way you manage your periodic personnel evaluation. You will be able to define steps and - attach interviews to each step. OpenERP manages all kind of + attach interviews to each step. OpenERP manages all kinds of evaluations: bottom-up, top-down, self-evaluation and final evaluation by the manager.

diff --git a/addons/hr_expense/hr_expense.py b/addons/hr_expense/hr_expense.py index c9961fa8a1d..ccb8af68e21 100644 --- a/addons/hr_expense/hr_expense.py +++ b/addons/hr_expense/hr_expense.py @@ -64,22 +64,22 @@ class hr_expense_expense(osv.osv): _description = "Expense" _order = "id desc" _columns = { - 'name': fields.char('Description', size=128), + 'name': fields.char('Description', size=128, required=True, readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}), 'id': fields.integer('Sheet ID', readonly=True), - 'date': fields.date('Date', select=True), + 'date': fields.date('Date', select=True, readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}), 'journal_id': fields.many2one('account.journal', 'Force Journal', help = "The journal used when the expense is done."), - 'employee_id': fields.many2one('hr.employee', "Employee", required=True), + 'employee_id': fields.many2one('hr.employee', "Employee", required=True, readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}), 'user_id': fields.many2one('res.users', 'User', required=True), 'date_confirm': fields.date('Confirmation Date', select=True, help="Date of the confirmation of the sheet expense. It's filled when the button Confirm is pressed."), 'date_valid': fields.date('Validation Date', select=True, help="Date of the acceptation of the sheet expense. It's filled when the button Accept is pressed."), - 'user_valid': fields.many2one('res.users', 'Validation By'), + 'user_valid': fields.many2one('res.users', 'Validation By', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}), 'account_move_id': fields.many2one('account.move', 'Ledger Posting'), 'line_ids': fields.one2many('hr.expense.line', 'expense_id', 'Expense Lines', readonly=True, states={'draft':[('readonly',False)]} ), 'note': fields.text('Note'), 'amount': fields.function(_amount, string='Total Amount', digits_compute= dp.get_precision('Account')), 'voucher_id': fields.many2one('account.voucher', "Employee's Receipt"), - 'currency_id': fields.many2one('res.currency', 'Currency', required=True), - 'department_id':fields.many2one('hr.department','Department'), + 'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}), + 'department_id':fields.many2one('hr.department','Department', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}), 'company_id': fields.many2one('res.company', 'Company', required=True), 'state': fields.selection([ ('draft', 'New'), @@ -100,6 +100,12 @@ class hr_expense_expense(osv.osv): 'currency_id': _get_currency, } + def unlink(self, cr, uid, ids, context=None): + for rec in self.browse(cr, uid, ids, context=context): + if rec.state != 'draft': + raise osv.except_osv(_('Warning!'),_('You can only delete draft expenses!')) + return super(hr_expense_expense, self).unlink(cr, uid, ids, context) + def onchange_currency_id(self, cr, uid, ids, currency_id=False, company_id=False, context=None): res = {'value': {'journal_id': False}} journal_ids = self.pool.get('account.journal').search(cr, uid, [('type','=','purchase'), ('currency','=',currency_id), ('company_id', '=', company_id)], context=context) @@ -236,16 +242,6 @@ class product_product(osv.osv): 'hr_expense_ok': fields.boolean('Can be Expensed', help="Specify if the product can be selected in an HR expense line."), } - def on_change_hr_expense_ok(self, cr, uid, id, hr_expense_ok): - - if not hr_expense_ok: - return {} - data_obj = self.pool.get('ir.model.data') - cat_id = data_obj._get_id(cr, uid, 'hr_expense', 'cat_expense') - categ_id = data_obj.browse(cr, uid, cat_id).res_id - res = {'value' : {'type':'service','sale_ok' :False,'categ_id':categ_id }} - return res - product_product() class hr_expense_line(osv.osv): diff --git a/addons/hr_expense/hr_expense_data.xml b/addons/hr_expense/hr_expense_data.xml index 2a9f3ae0a56..f54156ac5ec 100644 --- a/addons/hr_expense/hr_expense_data.xml +++ b/addons/hr_expense/hr_expense_data.xml @@ -6,10 +6,10 @@ mail.group notification + Expense Management application installed! - Manage your employees' expenses, after due validation by their manager and the accountant, then generate and pay the corresponding invoices. - -This feature is also linked to analytic accounting and compatible with timesheet invoices, so you will be able to automatically re-invoice project-related expenses to your customers. + Manage your employees' expenses, after due validation by their manager and the accountant, then generate and pay the corresponding invoices.

+

This feature is also linked to analytic accounting and compatible with timesheet invoices, so you will be able to automatically re-invoice project-related expenses to your customers.

]]>
diff --git a/addons/hr_expense/hr_expense_view.xml b/addons/hr_expense/hr_expense_view.xml index fbaa4c1f87e..d4b7cbae1ce 100644 --- a/addons/hr_expense/hr_expense_view.xml +++ b/addons/hr_expense/hr_expense_view.xml @@ -195,7 +195,7 @@
- +
@@ -211,8 +211,7 @@ - - + diff --git a/addons/hr_holidays/hr_holidays.py b/addons/hr_holidays/hr_holidays.py index f315f3d4ef3..92b1d57ce15 100644 --- a/addons/hr_holidays/hr_holidays.py +++ b/addons/hr_holidays/hr_holidays.py @@ -90,6 +90,18 @@ class hr_holidays_status(osv.osv): 'color_name': 'red', 'active': True, } + + def name_get(self, cr, uid, ids, context=None): + if not ids: + return [] + res = [] + for record in self.browse(cr, uid, ids, context=context): + name = record.name + if not record.limit: + name = name + (' (%d/%d)' % (record.leaves_taken or 0.0, record.max_leaves or 0.0)) + res.append((record.id, name)) + return res + hr_holidays_status() class hr_holidays(osv.osv): @@ -113,6 +125,13 @@ class hr_holidays(osv.osv): result[hol.id] = hol.number_of_days_temp return result + def _check_date(self, cr, uid, ids): + for holiday in self.browse(cr, uid, ids): + holiday_ids = self.search(cr, uid, [('date_from', '<=', holiday.date_to), ('date_to', '>=', holiday.date_from), ('employee_id', '=', holiday.employee_id.id), ('id', '<>', holiday.id)]) + if holiday_ids: + return False + return True + _columns = { 'name': fields.char('Description', size=64), 'state': fields.selection([('draft', 'To Submit'), ('cancel', 'Cancelled'),('confirm', 'To Approve'), ('refuse', 'Refused'), ('validate1', 'Second Approval'), ('validate', 'Approved')], @@ -146,6 +165,10 @@ class hr_holidays(osv.osv): 'user_id': lambda obj, cr, uid, context: uid, 'holiday_type': 'employee' } + _constraints = [ + (_check_date, 'You can not have 2 leaves that overlaps on same day!', ['date_from','date_to']), + ] + _sql_constraints = [ ('type_value', "CHECK( (holiday_type='employee' AND employee_id IS NOT NULL) or (holiday_type='category' AND category_id IS NOT NULL))", "The employee or employee category of this request is missing."), ('date_check2', "CHECK ( (type='add') OR (date_from <= date_to))", "The start date must be anterior to the end date."), @@ -252,6 +275,13 @@ class hr_holidays(osv.osv): result['value']['number_of_days_temp'] = 0 return result + + def write(self, cr, uid, ids, vals, context=None): + check_fnct = self.pool.get('hr.holidays.status').check_access_rights + for holiday in self.browse(cr, uid, ids, context=context): + if holiday.state in ('validate','validate1') and not check_fnct(cr, uid, 'write', raise_exception=False): + raise osv.except_osv(_('Warning!'),_('You cannot modify a leave request that has been approved. Contact a human resource manager.')) + return super(hr_holidays, self).write(cr, uid, ids, vals, context=context) def set_to_draft(self, cr, uid, ids, context=None): self.write(cr, uid, ids, { diff --git a/addons/hr_holidays/hr_holidays_data.xml b/addons/hr_holidays/hr_holidays_data.xml index afe2e69e697..5af0c60fc44 100644 --- a/addons/hr_holidays/hr_holidays_data.xml +++ b/addons/hr_holidays/hr_holidays_data.xml @@ -11,9 +11,10 @@ mail.group notification + Leave Management application installed! - Manage employee leaves from the top menu "Human Resources". Employees can create leave requests that are validated by their manager and/or HR officers. -Once validated, they are visible in the employee's calendar. HR officers can define leave types and allocate leaves to employees and employee categories. + Manage employee leaves from the top menu "Human Resources". Employees can create leave requests that are validated by their manager and/or HR officers.

+

Once validated, they are visible in the employee's calendar. HR officers can define leave types and allocate leaves to employees and employee categories.

]]>
diff --git a/addons/hr_holidays/hr_holidays_demo.xml b/addons/hr_holidays/hr_holidays_demo.xml index 4c9d18683c7..accf8c26935 100644 --- a/addons/hr_holidays/hr_holidays_demo.xml +++ b/addons/hr_holidays/hr_holidays_demo.xml @@ -34,8 +34,8 @@ Summer Vacation - - + + add draft 7 @@ -45,8 +45,8 @@ International Tour - - + + add 7 diff --git a/addons/hr_holidays/hr_holidays_view.xml b/addons/hr_holidays/hr_holidays_view.xml index 4691f7ebd71..752d278baed 100644 --- a/addons/hr_holidays/hr_holidays_view.xml +++ b/addons/hr_holidays/hr_holidays_view.xml @@ -122,7 +122,7 @@
- +
@@ -159,7 +159,7 @@
- +
@@ -466,7 +466,7 @@
- Leave Type + Leave Types ir.actions.act_window hr.holidays.status form diff --git a/addons/hr_holidays/report/holidays_summary_report.py b/addons/hr_holidays/report/holidays_summary_report.py index 8a782075853..cadd3a22ce6 100644 --- a/addons/hr_holidays/report/holidays_summary_report.py +++ b/addons/hr_holidays/report/holidays_summary_report.py @@ -31,6 +31,7 @@ import time from report import report_sxw from tools import ustr from tools.translate import _ +from tools import to_xml def lengthmonth(year, month): if month == 2 and ((year % 4 == 0) and ((year % 100 != 0) or (year % 400 == 0))): @@ -240,7 +241,7 @@ class report_custom(report_rml): %s %s - ''' % (str(rml_obj.formatLang(time.strftime("%Y-%m-%d"),date=True))+' ' + str(time.strftime("%H:%M")),pooler.get_pool(cr.dbname).get('res.users').browse(cr,uid,uid).company_id.name) + ''' % (str(rml_obj.formatLang(time.strftime("%Y-%m-%d"),date=True))+' ' + str(time.strftime("%H:%M")),to_xml(pooler.get_pool(cr.dbname).get('res.users').browse(cr,uid,uid).company_id.name)) # Computing the xml xml=''' diff --git a/addons/hr_payroll/hr_payroll.py b/addons/hr_payroll/hr_payroll.py index d1f38ad4a0f..c0e4bc39321 100644 --- a/addons/hr_payroll/hr_payroll.py +++ b/addons/hr_payroll/hr_payroll.py @@ -312,12 +312,7 @@ class hr_payslip(osv.osv): company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id default.update({ 'line_ids': [], - 'move_ids': [], - 'move_line_ids': [], 'company_id': company_id, - 'period_id': False, - 'basic_before_leaves': 0.0, - 'basic_amount': 0.0, 'number': '', 'payslip_run_id': False, 'paid': False, @@ -331,6 +326,7 @@ class hr_payslip(osv.osv): return self.write(cr, uid, ids, {'paid': True, 'state': 'done'}, context=context) def hr_verify_sheet(self, cr, uid, ids, context=None): + self.compute_sheet(cr, uid, ids, context) return self.write(cr, uid, ids, {'state': 'verify'}, context=context) def refund_sheet(self, cr, uid, ids, context=None): @@ -363,6 +359,12 @@ class hr_payslip(osv.osv): def check_done(self, cr, uid, ids, context=None): return True + def unlink(self, cr, uid, ids, context=None): + for payslip in self.browse(cr, uid, ids, context=context): + if payslip.state not in ['draft','cancel']: + raise osv.except_osv(_('Warning!'),_('You cannot delete a payslip which is not draft or cancelled!')) + return super(hr_payslip, self).unlink(cr, uid, ids, context) + #TODO move this function into hr_contract module, on hr.employee object def get_contract(self, cr, uid, employee, date_from, date_to, context=None): """ diff --git a/addons/hr_payroll/hr_payroll_demo.xml b/addons/hr_payroll/hr_payroll_demo.xml index 836814860c1..a22809b00e3 100644 --- a/addons/hr_payroll/hr_payroll_demo.xml +++ b/addons/hr_payroll/hr_payroll_demo.xml @@ -75,7 +75,7 @@ fix - worked_days.WORK100.number_of_days + worked_days.WORK100 and worked_days.WORK100.number_of_days MA @@ -89,7 +89,7 @@ Get 1% of sales - result = (inputs.SALEURO.amount + inputs.SALASIA.amount) * 0.01 + result = ((inputs.SALEURO and inputs.SALEURO.amount) + (inputs.SALASIA and inputs.SALASIA.amount)) * 0.01 diff --git a/addons/hr_recruitment/hr_recruitment_data.xml b/addons/hr_recruitment/hr_recruitment_data.xml index 330c86652dd..adaeed81205 100644 --- a/addons/hr_recruitment/hr_recruitment_data.xml +++ b/addons/hr_recruitment/hr_recruitment_data.xml @@ -6,10 +6,10 @@ mail.group notification + Recruitment Process application installed! - Manage job positions and your company's recruitment process. This application is integrated with the Survey application to help you define interviews for different jobs. - -You can automatically receive job application though an email gateway, see the Human Resources settings. + Manage job positions and your company's recruitment process. This application is integrated with the Survey application to help you define interviews for different jobs.

+

You can automatically receive job application though an email gateway, see the Human Resources settings.

]]>
diff --git a/addons/hr_recruitment/hr_recruitment_view.xml b/addons/hr_recruitment/hr_recruitment_view.xml index 03561da569e..c3281b43b94 100644 --- a/addons/hr_recruitment/hr_recruitment_view.xml +++ b/addons/hr_recruitment/hr_recruitment_view.xml @@ -106,7 +106,7 @@
-
- ''' % (str(rml_obj.formatLang(time.strftime("%Y-%m-%d"),date=True))+' ' + str(time.strftime("%H:%M")),pooler.get_pool(cr.dbname).get('res.users').browse(cr,uid,user_id).company_id.name) + ''' % (str(rml_obj.formatLang(time.strftime("%Y-%m-%d"),date=True))+' ' + str(time.strftime("%H:%M")),to_xml(pooler.get_pool(cr.dbname).get('res.users').browse(cr,uid,user_id).company_id.name)) account_xml = [] for account, telems in accounts.iteritems(): diff --git a/addons/hr_timesheet/wizard/hr_timesheet_print_employee.py b/addons/hr_timesheet/wizard/hr_timesheet_print_employee.py index 75bb089bbc7..96ce23fb855 100644 --- a/addons/hr_timesheet/wizard/hr_timesheet_print_employee.py +++ b/addons/hr_timesheet/wizard/hr_timesheet_print_employee.py @@ -27,8 +27,9 @@ class analytical_timesheet_employee(osv.osv_memory): _name = 'hr.analytical.timesheet.employee' _description = 'Print Employee Timesheet & Print My Timesheet' _columns = { - 'month': fields.selection([(x, datetime.date(2000, x, 1).strftime('%B')) for x in range(1, 13)], - 'Month', required=True), + 'month': fields.selection([(1,'January'), (2,'February'), (3,'March'), (4,'April'), + (5,'May'), (6,'June'), (7,'July'), (8,'August'), (9,'September'), + (10,'October'), (11,'November'), (12,'December')], 'Month', required=True), 'year': fields.integer('Year', required=True), 'employee_id': fields.many2one('hr.employee', 'Employee', required=True) diff --git a/addons/hr_timesheet/wizard/hr_timesheet_print_users.py b/addons/hr_timesheet/wizard/hr_timesheet_print_users.py index 865ccacb393..2a91bb607a4 100644 --- a/addons/hr_timesheet/wizard/hr_timesheet_print_users.py +++ b/addons/hr_timesheet/wizard/hr_timesheet_print_users.py @@ -27,8 +27,9 @@ class analytical_timesheet_employees(osv.osv_memory): _name = 'hr.analytical.timesheet.users' _description = 'Print Employees Timesheet' _columns = { - 'month': fields.selection([(x, datetime.date(2000, x, 1).strftime('%B')) for x in range(1, 13)], - 'Month', required=True), + 'month': fields.selection([(1,'January'), (2,'February'), (3,'March'), (4,'April'), + (5,'May'), (6,'June'), (7,'July'), (8,'August'), (9,'September'), + (10,'October'), (11,'November'), (12,'December')], 'Month', required=True), 'year': fields.integer('Year', required=True), 'employee_ids': fields.many2many('hr.employee', 'timesheet_employee_rel', 'timesheet_id', 'employee_id', 'employees', required=True) } diff --git a/addons/hr_timesheet_invoice/hr_timesheet_invoice_demo.xml b/addons/hr_timesheet_invoice/hr_timesheet_invoice_demo.xml index cc531fc8f10..0ebe16ad49f 100644 --- a/addons/hr_timesheet_invoice/hr_timesheet_invoice_demo.xml +++ b/addons/hr_timesheet_invoice/hr_timesheet_invoice_demo.xml @@ -7,7 +7,7 @@ 50.0
- Gratis + Free of charge Offered developments 100.0 diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet_data.xml b/addons/hr_timesheet_sheet/hr_timesheet_sheet_data.xml index 87edf949972..095a92c032d 100644 --- a/addons/hr_timesheet_sheet/hr_timesheet_sheet_data.xml +++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet_data.xml @@ -6,8 +6,9 @@ mail.group notification + Timesheet Validation application installed! - From the top menu "Human Resources", enter and validate timesheets and attendances. + From the top menu "Human Resources", enter and validate timesheets and attendances.

]]>
diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml b/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml index ee5a329c800..6de6be6c08d 100644 --- a/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml +++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml @@ -162,7 +162,7 @@
- +
diff --git a/addons/hr_timesheet_sheet/static/src/js/timesheet.js b/addons/hr_timesheet_sheet/static/src/js/timesheet.js index e64c4d602e8..efff48d93b8 100644 --- a/addons/hr_timesheet_sheet/static/src/js/timesheet.js +++ b/addons/hr_timesheet_sheet/static/src/js/timesheet.js @@ -46,7 +46,7 @@ openerp.hr_timesheet_sheet = function(instance) { var commands = this.field_manager.get_field_value("timesheet_ids"); this.res_o2m_drop.add(new instance.web.Model(this.view.model).call("resolve_2many_commands", ["timesheet_ids", commands, [], new instance.web.CompoundContext()])) - .then(function(result) { + .done(function(result) { self.querying = true; self.set({sheets: result}); self.querying = false; @@ -57,7 +57,7 @@ openerp.hr_timesheet_sheet = function(instance) { if (self.querying) return; self.updating = true; - self.field_manager.set_values({timesheet_ids: self.get("sheets")}).then(function() { + self.field_manager.set_values({timesheet_ids: self.get("sheets")}).done(function() { self.updating = false; }); }, @@ -85,7 +85,7 @@ openerp.hr_timesheet_sheet = function(instance) { var default_get; return this.render_drop.add(new instance.web.Model("hr.analytic.timesheet").call("default_get", [ ['account_id','general_account_id', 'journal_id','date','name','user_id','product_id','product_uom_id','to_invoice','amount','unit_amount'], - new instance.web.CompoundContext({'user_id': self.get('user_id')})]).pipe(function(result) { + new instance.web.CompoundContext({'user_id': self.get('user_id')})]).then(function(result) { default_get = result; // calculating dates dates = []; @@ -108,9 +108,9 @@ openerp.hr_timesheet_sheet = function(instance) { var account_ids = _.map(_.keys(accounts), function(el) { return el === "false" ? false : Number(el) }); return new instance.web.Model("hr.analytic.timesheet").call("multi_on_change_account_id", [[], account_ids, - new instance.web.CompoundContext({'user_id': self.get('user_id')})]).pipe(function(accounts_defaults) { + new instance.web.CompoundContext({'user_id': self.get('user_id')})]).then(function(accounts_defaults) { accounts = _(accounts).chain().map(function(lines, account_id) { - account_defaults = _.extend({}, default_get, accounts_defaults[account_id]); + account_defaults = _.extend({}, default_get, (accounts_defaults[account_id] || {}).value || {}); // group by days account_id = account_id === "false" ? false : Number(account_id); var index = _.groupBy(lines, "date"); @@ -136,7 +136,7 @@ openerp.hr_timesheet_sheet = function(instance) { // we need the name_get of the analytic accounts return new instance.web.Model("account.analytic.account").call("name_get", [_.pluck(accounts, "account"), - new instance.web.CompoundContext()]).pipe(function(result) { + new instance.web.CompoundContext()]).then(function(result) { account_names = {}; _.each(result, function(el) { account_names[el[0]] = el[1]; @@ -146,7 +146,7 @@ openerp.hr_timesheet_sheet = function(instance) { }); });; }); - })).pipe(function(result) { + })).then(function(result) { // we put all the gathered data in self, then we render self.dates = dates; self.accounts = accounts; @@ -222,7 +222,7 @@ openerp.hr_timesheet_sheet = function(instance) { return; } var ops = self.generate_o2m_value(); - new instance.web.Model("hr.analytic.timesheet").call("on_change_account_id", [[], id]).pipe(function(res) { + new instance.web.Model("hr.analytic.timesheet").call("on_change_account_id", [[], id]).then(function(res) { var def = _.extend({}, self.default_get, res.value, { name: self.description_line, unit_amount: 0, diff --git a/addons/l10n_ar/i18n/es_AR.po b/addons/l10n_ar/i18n/es_AR.po index 2260a43cf74..1f2b2dd250a 100644 --- a/addons/l10n_ar/i18n/es_AR.po +++ b/addons/l10n_ar/i18n/es_AR.po @@ -1,21 +1,21 @@ -# Spanish (Paraguay) translation for openobject-addons -# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011 +# Spanish (Argentina) 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 , 2011. +# FIRST AUTHOR , 2012. # msgid "" msgstr "" "Project-Id-Version: openobject-addons\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-01-11 11:15+0000\n" -"PO-Revision-Date: 2011-03-21 16:23+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Spanish (Paraguay) \n" +"PO-Revision-Date: 2012-11-13 15:04+0000\n" +"Last-Translator: Eduardo Alberto Calvo \n" +"Language-Team: Spanish (Argentina) \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-03-22 04:36+0000\n" -"X-Generator: Launchpad (build 12559)\n" +"X-Launchpad-Export-Date: 2012-11-14 04:40+0000\n" +"X-Generator: Launchpad (build 16251)\n" #. module: l10n_ar #: model:ir.module.module,description:l10n_ar.module_meta_information @@ -31,7 +31,7 @@ msgstr "" #. module: l10n_ar #: model:ir.module.module,shortdesc:l10n_ar.module_meta_information msgid "Argentinian Chart of Account" -msgstr "Plan de cuentas de la Argentina" +msgstr "Plan de cuentas de Argentina" #. module: l10n_ar #: model:ir.actions.todo,note:l10n_ar.config_call_account_template_in_minimal @@ -45,7 +45,7 @@ msgid "" "Chart of Accounts from a Chart Template." msgstr "" "Generar el plan contable a partir de una plantilla de plan contable. Se le " -"pedirá el nombre de la compañía, la plantilla de plan contable a utilizar, " +"pedirá el nombre de la compañia, la plantilla de plan contable a utilizar, " "el número de dígitos para generar el código de las cuentas y de la cuenta " "bancaria, la moneda para crear los diarios. Así pues, se genere una copia " "exacta de la plantilla de plan contable.\n" diff --git a/addons/l10n_ar/l10n_ar_wizard.xml b/addons/l10n_ar/l10n_ar_wizard.xml index 3c94d3db7a8..82e7671b4d3 100644 --- a/addons/l10n_ar/l10n_ar_wizard.xml +++ b/addons/l10n_ar/l10n_ar_wizard.xml @@ -1,6 +1,6 @@ - + open diff --git a/addons/l10n_at/l10n_chart_at_wizard.xml b/addons/l10n_at/l10n_chart_at_wizard.xml index efc68ce13e9..19d27fb47c7 100644 --- a/addons/l10n_at/l10n_chart_at_wizard.xml +++ b/addons/l10n_at/l10n_chart_at_wizard.xml @@ -1,5 +1,5 @@ - + open diff --git a/addons/l10n_be/wizard/l10n_be_partner_vat_listing.py b/addons/l10n_be/wizard/l10n_be_partner_vat_listing.py index d15b300597e..53948945e04 100644 --- a/addons/l10n_be/wizard/l10n_be_partner_vat_listing.py +++ b/addons/l10n_be/wizard/l10n_be_partner_vat_listing.py @@ -65,6 +65,8 @@ class partner_vat(osv.osv_memory): partners = [] partner_ids = obj_partner.search(cr, uid, [('vat_subjected', '!=', False), ('vat','ilike','BE%')], context=context) + if not partner_ids: + raise osv.except_osv(_('Error'),_('No belgian contact with a VAT number in your database.')) cr.execute("""SELECT sub1.partner_id, sub1.name, sub1.vat, sub1.turnover, sub2.vat_amount FROM (SELECT l.partner_id, p.name, p.vat, SUM(CASE WHEN c.code ='49' THEN -l.tax_amount ELSE l.tax_amount END) as turnover FROM account_move_line l @@ -190,7 +192,7 @@ class partner_vat_list(osv.osv_memory): addr = obj_partner.address_get(cr, uid, [obj_cmpny.partner_id.id], ['invoice']) if addr.get('invoice',False): ads = obj_partner.browse(cr, uid, [addr['invoice']], context=context)[0] - phone = ads.phone.replace(' ','') or '' + phone = ads.phone and ads.phone.replace(' ','') or '' email = ads.email or '' name = ads.name or '' city = ads.city or '' @@ -259,6 +261,8 @@ class partner_vat_list(osv.osv_memory): # Turnover and Farmer tags are not included client_datas = self._get_datas(cr, uid, ids, context=context) + if not client_datas: + raise osv.except_osv(_('Data Insufficient!'),_('No data available for the client.')) data_client_info = '' for amount_data in client_datas: data_client_info += """ @@ -309,6 +313,8 @@ class partner_vat_list(osv.osv_memory): datas['year'] = context['year'] datas['limit_amount'] = context['limit_amount'] datas['client_datas'] = self._get_datas(cr, uid, ids, context=context) + if not datas['client_datas']: + raise osv.except_osv(_('Error!'),_('No record to print.')) return { 'type': 'ir.actions.report.xml', 'report_name': 'partner.vat.listing.print', diff --git a/addons/l10n_be_coda/wizard/account_coda_import.py b/addons/l10n_be_coda/wizard/account_coda_import.py index ca89cf78325..46c545f0f48 100644 --- a/addons/l10n_be_coda/wizard/account_coda_import.py +++ b/addons/l10n_be_coda/wizard/account_coda_import.py @@ -28,6 +28,7 @@ import logging import re from traceback import format_exception from sys import exc_info +from base_iban import base_iban _logger = logging.getLogger(__name__) class account_coda_import(osv.osv_memory): @@ -398,6 +399,7 @@ class account_coda_import(osv.osv_memory): st_line['account_id'] = transfer_acc match = True elif find_partner: + counterparty_number = base_iban._pretty_iban(base_iban._format_iban(counterparty_number)) bank_ids = partner_bank_obj.search(cr,uid,[('acc_number','=', counterparty_number)]) if not match and find_partner and bank_ids: if len(bank_ids) > 1: diff --git a/addons/l10n_be_hr_payroll/i18n/es.po b/addons/l10n_be_hr_payroll/i18n/es.po new file mode 100644 index 00000000000..c0deb99952c --- /dev/null +++ b/addons/l10n_be_hr_payroll/i18n/es.po @@ -0,0 +1,158 @@ +# 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-12 16:09+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-13 05:17+0000\n" +"X-Generator: Launchpad (build 16251)\n" + +#. module: l10n_be_hr_payroll +#: help:hr.employee,disabled_spouse_bool:0 +msgid "if recipient spouse is declared disabled by law" +msgstr "" + +#. module: l10n_be_hr_payroll +#: help:hr.employee,disabled_children_bool:0 +msgid "if recipient children is/are declared disabled by law" +msgstr "" + +#. module: l10n_be_hr_payroll +#: field:hr.contract,misc_onss_deduction:0 +msgid "Miscellaneous exempt ONSS " +msgstr "" + +#. module: l10n_be_hr_payroll +#: model:ir.model,name:l10n_be_hr_payroll.model_hr_employee +msgid "Employee" +msgstr "" + +#. module: l10n_be_hr_payroll +#: field:hr.employee,disabled_spouse_bool:0 +msgid "Disabled Spouse" +msgstr "" + +#. module: l10n_be_hr_payroll +#: field:hr.contract,retained_net_amount:0 +msgid "Net retained " +msgstr "" + +#. module: l10n_be_hr_payroll +#: field:hr.employee,resident_bool:0 +msgid "Nonresident" +msgstr "" + +#. module: l10n_be_hr_payroll +#: help:hr.employee,resident_bool:0 +msgid "if recipient lives in a foreign country" +msgstr "" + +#. module: l10n_be_hr_payroll +#: view:hr.employee:0 +msgid "if spouse has professionnel income or not" +msgstr "" + +#. module: l10n_be_hr_payroll +#: view:hr.contract:0 +msgid "Miscellaneous" +msgstr "" + +#. module: l10n_be_hr_payroll +#: field:hr.contract,insurance_employee_deduction:0 +msgid "Insurance Group - by worker " +msgstr "" + +#. module: l10n_be_hr_payroll +#: selection:hr.employee,spouse_fiscal_status:0 +msgid "With Income" +msgstr "" + +#. module: l10n_be_hr_payroll +#: selection:hr.employee,spouse_fiscal_status:0 +msgid "Without Income" +msgstr "" + +#. module: l10n_be_hr_payroll +#: field:hr.employee,disabled_children_number:0 +msgid "Number of disabled children" +msgstr "" + +#. module: l10n_be_hr_payroll +#: field:hr.contract,additional_net_amount:0 +msgid "Net supplements" +msgstr "" + +#. module: l10n_be_hr_payroll +#: constraint:hr.employee:0 +msgid "Error ! You cannot create recursive Hierarchy of Employees." +msgstr "" + +#. module: l10n_be_hr_payroll +#: field:hr.contract,car_company_amount:0 +msgid "Company car employer" +msgstr "" + +#. module: l10n_be_hr_payroll +#: field:hr.contract,misc_advantage_amount:0 +msgid "Benefits of various nature " +msgstr "" + +#. module: l10n_be_hr_payroll +#: field:hr.contract,car_employee_deduction:0 +msgid "Company Car Deduction for Worker" +msgstr "" + +#. module: l10n_be_hr_payroll +#: field:hr.employee,disabled_children_bool:0 +msgid "Disabled Children" +msgstr "" + +#. module: l10n_be_hr_payroll +#: model:ir.model,name:l10n_be_hr_payroll.model_hr_contract +msgid "Contract" +msgstr "" + +#. module: l10n_be_hr_payroll +#: field:hr.contract,meal_voucher_amount:0 +msgid "Check Value Meal " +msgstr "" + +#. module: l10n_be_hr_payroll +#: field:hr.contract,travel_reimbursement_amount:0 +msgid "Reimbursement of travel expenses" +msgstr "" + +#. module: l10n_be_hr_payroll +#: constraint:hr.contract:0 +msgid "Error! contract start-date must be lower then contract end-date." +msgstr "" + +#. module: l10n_be_hr_payroll +#: field:hr.employee,spouse_fiscal_status:0 +msgid "Tax status for spouse" +msgstr "" + +#. module: l10n_be_hr_payroll +#: view:hr.contract:0 +msgid "by Worker" +msgstr "" + +#. module: l10n_be_hr_payroll +#: view:hr.employee:0 +msgid "number of dependent children declared as disabled" +msgstr "" + +#. module: l10n_be_hr_payroll +#: field:hr.contract,meal_voucher_employee_deduction:0 +msgid "Check Value Meal - by worker " +msgstr "" diff --git a/addons/l10n_be_invoice_bba/i18n/es.po b/addons/l10n_be_invoice_bba/i18n/es.po new file mode 100644 index 00000000000..42f11478e9d --- /dev/null +++ b/addons/l10n_be_invoice_bba/i18n/es.po @@ -0,0 +1,141 @@ +# 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-12 16:43+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-13 05:17+0000\n" +"X-Generator: Launchpad (build 16251)\n" + +#. module: l10n_be_invoice_bba +#: sql_constraint:account.invoice:0 +msgid "Invoice Number must be unique per Company!" +msgstr "" + +#. module: l10n_be_invoice_bba +#: model:ir.model,name:l10n_be_invoice_bba.model_account_invoice +msgid "Invoice" +msgstr "" + +#. module: l10n_be_invoice_bba +#: constraint:res.partner:0 +msgid "Error ! You cannot create recursive associated members." +msgstr "" + +#. module: l10n_be_invoice_bba +#: constraint:account.invoice:0 +msgid "Invalid BBA Structured Communication !" +msgstr "" + +#. module: l10n_be_invoice_bba +#: selection:res.partner,out_inv_comm_algorithm:0 +msgid "Random" +msgstr "" + +#. module: l10n_be_invoice_bba +#: help:res.partner,out_inv_comm_type:0 +msgid "Select Default Communication Type for Outgoing Invoices." +msgstr "" + +#. module: l10n_be_invoice_bba +#: help:res.partner,out_inv_comm_algorithm:0 +msgid "" +"Select Algorithm to generate the Structured Communication on Outgoing " +"Invoices." +msgstr "" + +#. module: l10n_be_invoice_bba +#: code:addons/l10n_be_invoice_bba/invoice.py:114 +#: code:addons/l10n_be_invoice_bba/invoice.py:140 +#, python-format +msgid "" +"The daily maximum of outgoing invoices with an automatically generated BBA " +"Structured Communications has been exceeded!\n" +"Please create manually a unique BBA Structured Communication." +msgstr "" + +#. module: l10n_be_invoice_bba +#: code:addons/l10n_be_invoice_bba/invoice.py:155 +#, python-format +msgid "Error!" +msgstr "" + +#. module: l10n_be_invoice_bba +#: code:addons/l10n_be_invoice_bba/invoice.py:126 +#, python-format +msgid "" +"The Partner should have a 3-7 digit Reference Number for the generation of " +"BBA Structured Communications!\n" +"Please correct the Partner record." +msgstr "" + +#. module: l10n_be_invoice_bba +#: code:addons/l10n_be_invoice_bba/invoice.py:113 +#: code:addons/l10n_be_invoice_bba/invoice.py:125 +#: code:addons/l10n_be_invoice_bba/invoice.py:139 +#: code:addons/l10n_be_invoice_bba/invoice.py:167 +#: code:addons/l10n_be_invoice_bba/invoice.py:177 +#: code:addons/l10n_be_invoice_bba/invoice.py:202 +#, python-format +msgid "Warning!" +msgstr "" + +#. module: l10n_be_invoice_bba +#: selection:res.partner,out_inv_comm_algorithm:0 +msgid "Customer Reference" +msgstr "" + +#. module: l10n_be_invoice_bba +#: field:res.partner,out_inv_comm_type:0 +msgid "Communication Type" +msgstr "" + +#. module: l10n_be_invoice_bba +#: code:addons/l10n_be_invoice_bba/invoice.py:178 +#: code:addons/l10n_be_invoice_bba/invoice.py:203 +#, python-format +msgid "" +"The BBA Structured Communication has already been used!\n" +"Please create manually a unique BBA Structured Communication." +msgstr "" + +#. module: l10n_be_invoice_bba +#: selection:res.partner,out_inv_comm_algorithm:0 +msgid "Date" +msgstr "" + +#. module: l10n_be_invoice_bba +#: model:ir.model,name:l10n_be_invoice_bba.model_res_partner +msgid "Partner" +msgstr "" + +#. module: l10n_be_invoice_bba +#: code:addons/l10n_be_invoice_bba/invoice.py:156 +#, python-format +msgid "" +"Unsupported Structured Communication Type Algorithm '%s' !\n" +"Please contact your OpenERP support channel." +msgstr "" + +#. module: l10n_be_invoice_bba +#: field:res.partner,out_inv_comm_algorithm:0 +msgid "Communication Algorithm" +msgstr "" + +#. module: l10n_be_invoice_bba +#: code:addons/l10n_be_invoice_bba/invoice.py:168 +#, python-format +msgid "" +"Empty BBA Structured Communication!\n" +"Please fill in a unique BBA Structured Communication." +msgstr "" diff --git a/addons/l10n_ca/l10n_ca_wizard.xml b/addons/l10n_ca/l10n_ca_wizard.xml index efc68ce13e9..19d27fb47c7 100644 --- a/addons/l10n_ca/l10n_ca_wizard.xml +++ b/addons/l10n_ca/l10n_ca_wizard.xml @@ -1,5 +1,5 @@ - + open diff --git a/addons/l10n_ch/bank.py b/addons/l10n_ch/bank.py index 79dcb1a6834..4e4c0e0aa8c 100644 --- a/addons/l10n_ch/bank.py +++ b/addons/l10n_ch/bank.py @@ -52,21 +52,6 @@ class ResPartnerBank(osv.osv): 'my_bank': fields.boolean('Use my account to print BVR ?', help="Check to print BVR invoices"), } - def name_get(self, cursor, uid, ids, context=None): - if not len(ids): - return [] - bank_type_obj = self.pool.get('res.partner.bank.type') - - type_ids = bank_type_obj.search(cursor, uid, []) - bank_type_names = {} - for bank_type in bank_type_obj.browse(cursor, uid, type_ids, - context=context): - bank_type_names[bank_type.code] = bank_type.name - res = [] - for r in self.read(cursor, uid, ids, ['name','state'], context): - res.append((r['id'], r['name']+' : '+bank_type_names.get(r['state'], ''))) - return res - def _prepare_name(self, bank): "Hook to get bank number of bank account" res = u'' diff --git a/addons/l10n_ch/wizard.xml b/addons/l10n_ch/wizard.xml index 792bd21a2fc..dde7a77516a 100644 --- a/addons/l10n_ch/wizard.xml +++ b/addons/l10n_ch/wizard.xml @@ -1,5 +1,5 @@ - + open diff --git a/addons/l10n_cl/l10n_cl_wizard.xml b/addons/l10n_cl/l10n_cl_wizard.xml index bcbd9a43f27..704ac4daf38 100644 --- a/addons/l10n_cl/l10n_cl_wizard.xml +++ b/addons/l10n_cl/l10n_cl_wizard.xml @@ -1,6 +1,6 @@ - + open diff --git a/addons/l10n_cn/l10n_chart_cn_wizard.xml b/addons/l10n_cn/l10n_chart_cn_wizard.xml index 6919eb199b6..52aaa88fdab 100644 --- a/addons/l10n_cn/l10n_chart_cn_wizard.xml +++ b/addons/l10n_cn/l10n_chart_cn_wizard.xml @@ -1,5 +1,5 @@ - + open diff --git a/addons/l10n_de/l10n_de_wizard.xml b/addons/l10n_de/l10n_de_wizard.xml index efc68ce13e9..19d27fb47c7 100644 --- a/addons/l10n_de/l10n_de_wizard.xml +++ b/addons/l10n_de/l10n_de_wizard.xml @@ -1,5 +1,5 @@ - + open diff --git a/addons/l10n_ec/l10n_chart_ec_wizard.xml b/addons/l10n_ec/l10n_chart_ec_wizard.xml index 6919eb199b6..52aaa88fdab 100644 --- a/addons/l10n_ec/l10n_chart_ec_wizard.xml +++ b/addons/l10n_ec/l10n_chart_ec_wizard.xml @@ -1,5 +1,5 @@ - + open diff --git a/addons/l10n_fr/fr_tax.xml b/addons/l10n_fr/fr_tax.xml index fc5d4608f45..36c5feb47b6 100644 --- a/addons/l10n_fr/fr_tax.xml +++ b/addons/l10n_fr/fr_tax.xml @@ -131,8 +131,8 @@ - - + + diff --git a/addons/l10n_fr/l10n_fr_wizard.xml b/addons/l10n_fr/l10n_fr_wizard.xml index bcbd9a43f27..704ac4daf38 100644 --- a/addons/l10n_fr/l10n_fr_wizard.xml +++ b/addons/l10n_fr/l10n_fr_wizard.xml @@ -1,6 +1,6 @@ - + open diff --git a/addons/l10n_fr/plan_comptable_general.xml b/addons/l10n_fr/plan_comptable_general.xml index 525dabfcc8a..268f4bd6336 100644 --- a/addons/l10n_fr/plan_comptable_general.xml +++ b/addons/l10n_fr/plan_comptable_general.xml @@ -3903,7 +3903,7 @@ Associés - Comptes courants 455 view - + Pour frais avancés personnellement @@ -3912,8 +3912,8 @@ Principal 4551 - receivable - + payable + @@ -3921,8 +3921,8 @@ Intérêts courus 4558 - receivable - + payable + diff --git a/addons/l10n_fr_rib/i18n/es.po b/addons/l10n_fr_rib/i18n/es.po new file mode 100644 index 00000000000..e0accc79fcb --- /dev/null +++ b/addons/l10n_fr_rib/i18n/es.po @@ -0,0 +1,136 @@ +# 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-08 16:01+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-09 04:39+0000\n" +"X-Generator: Launchpad (build 16250)\n" + +#. module: l10n_fr_rib +#: constraint:res.partner.bank:0 +msgid "" +"\n" +"Please define BIC/Swift code on bank for bank type IBAN Account to make " +"valid payments" +msgstr "" +"\n" +"Por favor defina el código BIC/Swift del banco para una cuenta de tipo IBAN " +"para realizar pagos válidos" + +#. module: l10n_fr_rib +#: model:res.partner.bank.type,name:l10n_fr_rib.bank_rib +msgid "RIB and optional IBAN" +msgstr "CC e IBAN opcional" + +#. module: l10n_fr_rib +#: field:res.partner.bank,rib_acc_number:0 +msgid "RIB account number" +msgstr "Número de la cuenta" + +#. module: l10n_fr_rib +#: field:res.partner.bank,bank_code:0 +msgid "Bank Code" +msgstr "Código de banco" + +#. module: l10n_fr_rib +#: code:addons/l10n_fr_rib/bank.py:54 +#, python-format +msgid "The RIB key %s does not correspond to the other codes: %s %s %s." +msgstr "La clave de la CC %s no se corresponde con otros códigos: %s %s %s." + +#. module: l10n_fr_rib +#: model:res.partner.bank.type.field,name:l10n_fr_rib.rib_office_field +msgid "office" +msgstr "oficina" + +#. module: l10n_fr_rib +#: field:res.bank,rib_code:0 +msgid "RIB Bank Code" +msgstr "Código CC" + +#. module: l10n_fr_rib +#: code:addons/l10n_fr_rib/bank.py:58 +#, python-format +msgid "The IBAN %s is not valid." +msgstr "El IBAN %s no es válido." + +#. module: l10n_fr_rib +#: model:ir.model,name:l10n_fr_rib.model_res_partner_bank +msgid "Bank Accounts" +msgstr "Cuentas bancarias" + +#. module: l10n_fr_rib +#: field:res.partner.bank,office:0 +msgid "Office Code" +msgstr "Código de oficina" + +#. module: l10n_fr_rib +#: model:res.partner.bank.type.field,name:l10n_fr_rib.rib_bic_field +msgid "bank_bic" +msgstr "Número BIC" + +#. module: l10n_fr_rib +#: model:res.partner.bank.type.field,name:l10n_fr_rib.rib_bank_code_field +msgid "bank_code" +msgstr "código bancario" + +#. module: l10n_fr_rib +#: model:res.partner.bank.type.field,name:l10n_fr_rib.rib_key_field +msgid "key" +msgstr "clave" + +#. module: l10n_fr_rib +#: model:res.partner.bank.type.field,name:l10n_fr_rib.rib_rib_acc_number_field +msgid "rib_acc_number" +msgstr "Número CC" + +#. module: l10n_fr_rib +#: help:res.partner.bank,key:0 +msgid "" +"The key is a number allowing to check the correctness of the other codes." +msgstr "" +"La clave es un número que permite comprobar si el resto de códigos son " +"correctos." + +#. module: l10n_fr_rib +#: field:res.partner.bank,key:0 +msgid "Key" +msgstr "Clave" + +#. module: l10n_fr_rib +#: code:addons/l10n_fr_rib/bank.py:53 +#: code:addons/l10n_fr_rib/bank.py:58 +#, python-format +msgid "Error" +msgstr "Error" + +#. module: l10n_fr_rib +#: model:res.partner.bank.type,format_layout:l10n_fr_rib.bank_rib +msgid "%(bank_name)s: %(bank_code)s %(office)s %(rib_acc_number)s %(key)s" +msgstr "%(bank_name)s: %(bank_code)s %(office)s %(rib_acc_number)s %(key)s" + +#. module: l10n_fr_rib +#: constraint:res.partner.bank:0 +msgid "The RIB and/or IBAN is not valid" +msgstr "La CC y/o IBAN no es válida" + +#. module: l10n_fr_rib +#: model:ir.model,name:l10n_fr_rib.model_res_bank +msgid "Bank" +msgstr "Banco" + +#. module: l10n_fr_rib +#: model:res.partner.bank.type.field,name:l10n_fr_rib.rib_acc_number_field +msgid "acc_number" +msgstr "Número cuenta" diff --git a/addons/l10n_gr/l10n_gr_wizard.xml b/addons/l10n_gr/l10n_gr_wizard.xml index 6919eb199b6..52aaa88fdab 100644 --- a/addons/l10n_gr/l10n_gr_wizard.xml +++ b/addons/l10n_gr/l10n_gr_wizard.xml @@ -1,5 +1,5 @@ - + open diff --git a/addons/l10n_in/l10n_in_wizard.xml b/addons/l10n_in/l10n_in_wizard.xml index bcbd9a43f27..704ac4daf38 100644 --- a/addons/l10n_in/l10n_in_wizard.xml +++ b/addons/l10n_in/l10n_in_wizard.xml @@ -1,6 +1,6 @@ - + open diff --git a/addons/l10n_in_hr_payroll/i18n/es.po b/addons/l10n_in_hr_payroll/i18n/es.po new file mode 100644 index 00000000000..52f443733fb --- /dev/null +++ b/addons/l10n_in_hr_payroll/i18n/es.po @@ -0,0 +1,942 @@ +# 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-08-17 06:46+0000\n" +"PO-Revision-Date: 2012-11-12 17:08+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-13 05:17+0000\n" +"X-Generator: Launchpad (build 16251)\n" + +#. module: l10n_in_hr_payroll +#: report:salary.detail.byyear:0 +msgid "E-mail Address" +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:payment.advice.report,employee_bank_no:0 +msgid "Employee Bank Account" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:payment.advice.report:0 +msgid "Payment Advices which are in draft state" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:salary.detail.byyear:0 +msgid "Title" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:payroll.advice:0 +msgid "Payment Advice from" +msgstr "" + +#. module: l10n_in_hr_payroll +#: model:ir.model,name:l10n_in_hr_payroll.model_yearly_salary_detail +msgid "Hr Salary Employee By Category Report" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:salary.detail.byyear:0 +msgid "Employees Salary Details" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:salary.detail.byyear:0 +msgid "Allowances with Basic:" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:salary.detail.byyear:0 +msgid "Department" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:salary.detail.byyear:0 +msgid "Deductions:" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:payroll.advice:0 +msgid "A/C no." +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:hr.contract,driver_salay:0 +msgid "Driver Salary" +msgstr "" + +#. module: l10n_in_hr_payroll +#: model:ir.actions.act_window,name:l10n_in_hr_payroll.action_yearly_salary_detail +#: model:ir.actions.report.xml,name:l10n_in_hr_payroll.yearly_salary +#: model:ir.ui.menu,name:l10n_in_hr_payroll.menu_yearly_salary_detail +msgid "Yearly Salary by Employee" +msgstr "" + +#. module: l10n_in_hr_payroll +#: model:ir.actions.act_window,name:l10n_in_hr_payroll.act_hr_emp_payslip_list +msgid "Payslips" +msgstr "" + +#. module: l10n_in_hr_payroll +#: selection:payment.advice.report,month:0 +msgid "March" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:paylip.details.in:0 +msgid "(" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.payroll.advice:0 +#: field:hr.payroll.advice,company_id:0 +#: field:hr.payroll.advice.line,company_id:0 +#: view:payment.advice.report:0 +#: field:payment.advice.report,company_id:0 +msgid "Company" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:payroll.advice:0 +msgid "The Manager" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.payroll.advice:0 +msgid "Letter Details" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:paylip.details.in:0 +msgid "," +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.payroll.advice:0 +msgid "Set to Draft" +msgstr "" + +#. module: l10n_in_hr_payroll +#: help:hr.employee,number_of_year:0 +msgid "Total years of work experience" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:payroll.advice:0 +msgid "to" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:payroll.advice:0 +msgid "Total :" +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:hr.payslip.run,available_advice:0 +msgid "Made Payment Advice?" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:payment.advice.report:0 +msgid "Advices which are paid using NEFT transfer" +msgstr "" + +#. module: l10n_in_hr_payroll +#: help:hr.contract,tds:0 +msgid "Amount for Tax Deduction at Source" +msgstr "" + +#. module: l10n_in_hr_payroll +#: model:ir.model,name:l10n_in_hr_payroll.model_hr_payslip +msgid "Pay Slip" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:payment.advice.report:0 +#: field:payment.advice.report,day:0 +msgid "Day" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:payment.advice.report:0 +msgid "Month of Payment Advices" +msgstr "" + +#. module: l10n_in_hr_payroll +#: constraint:hr.payslip:0 +msgid "Payslip 'Date From' must be before 'Date To'." +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:hr.payroll.advice,batch_id:0 +msgid "Batch" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:paylip.details.in:0 +msgid "Code" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.payroll.advice:0 +msgid "Other Information" +msgstr "" + +#. module: l10n_in_hr_payroll +#: selection:hr.payroll.advice,state:0 +#: selection:payment.advice.report,state:0 +msgid "Cancelled" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:payroll.advice:0 +msgid "For" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:paylip.details.in:0 +msgid "Details by Salary Rule Category:" +msgstr "" + +#. module: l10n_in_hr_payroll +#: help:hr.contract,voluntary_provident_fund:0 +msgid "VPF computed as percentage(%)" +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:hr.payroll.advice,number:0 +#: report:paylip.details.in:0 +msgid "Reference" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.payroll.advice:0 +#: view:payment.advice.report:0 +msgid "Group By..." +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:hr.contract,medical_insurance:0 +msgid "Medical Insurance" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:paylip.details.in:0 +msgid "Identification No" +msgstr "" + +#. module: l10n_in_hr_payroll +#: selection:hr.payroll.advice,state:0 +#: selection:payment.advice.report,state:0 +msgid "Confirmed" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:salary.detail.byyear:0 +#: report:salary.employee.bymonth:0 +msgid "From" +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:hr.payroll.advice.line,bysal:0 +#: field:payment.advice.report,bysal:0 +#: report:payroll.advice:0 +msgid "By Salary" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.payroll.advice:0 +#: view:payment.advice.report:0 +msgid "Confirm" +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:hr.payroll.advice,chaque_nos:0 +#: field:payment.advice.report,cheque_nos:0 +msgid "Cheque Numbers" +msgstr "" + +#. module: l10n_in_hr_payroll +#: constraint:res.company:0 +msgid "Error! You can not create recursive companies." +msgstr "" + +#. module: l10n_in_hr_payroll +#: model:ir.actions.act_window,name:l10n_in_hr_payroll.action_salary_employee_month +#: model:ir.actions.report.xml,name:l10n_in_hr_payroll.hr_salary_employee_bymonth +#: model:ir.ui.menu,name:l10n_in_hr_payroll.menu_salary_employee_month +msgid "Yearly Salary by Head" +msgstr "" + +#. module: l10n_in_hr_payroll +#: code:addons/l10n_in_hr_payroll/l10n_in_hr_payroll.py:184 +#, python-format +msgid "You can not confirm Payment advice without advice lines." +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:hr.payroll.advice,state:0 +#: field:payment.advice.report,state:0 +msgid "State" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:payroll.advice:0 +msgid "Yours Sincerely" +msgstr "" + +#. module: l10n_in_hr_payroll +#: help:hr.contract,medical_insurance:0 +msgid "Deduction towards company provided medical insurance" +msgstr "" + +#. module: l10n_in_hr_payroll +#: model:ir.model,name:l10n_in_hr_payroll.model_hr_payroll_advice_line +msgid "Bank Advice Lines" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:paylip.details.in:0 +msgid "Email" +msgstr "" + +#. module: l10n_in_hr_payroll +#: help:hr.payslip.run,available_advice:0 +msgid "" +"If this box is checked which means that Payment Advice exists for current " +"batch" +msgstr "" + +#. module: l10n_in_hr_payroll +#: code:addons/l10n_in_hr_payroll/l10n_in_hr_payroll.py:158 +#: code:addons/l10n_in_hr_payroll/l10n_in_hr_payroll.py:184 +#: code:addons/l10n_in_hr_payroll/l10n_in_hr_payroll.py:240 +#: code:addons/l10n_in_hr_payroll/l10n_in_hr_payroll.py:257 +#, python-format +msgid "Error !" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.salary.employee.month:0 +#: view:yearly.salary.detail:0 +msgid "Print" +msgstr "" + +#. module: l10n_in_hr_payroll +#: model:ir.model,name:l10n_in_hr_payroll.model_hr_payslip_run +msgid "Payslip Batches" +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:hr.payroll.advice.line,debit_credit:0 +#: report:payroll.advice:0 +msgid "C/D" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:salary.employee.bymonth:0 +msgid "Yearly Salary Details" +msgstr "" + +#. module: l10n_in_hr_payroll +#: model:ir.actions.report.xml,name:l10n_in_hr_payroll.payroll_advice +msgid "Print Advice" +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:hr.payroll.advice,line_ids:0 +msgid "Employee Salary" +msgstr "" + +#. module: l10n_in_hr_payroll +#: selection:payment.advice.report,month:0 +msgid "July" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:res.company:0 +msgid "Configuration" +msgstr "" + +#. module: l10n_in_hr_payroll +#: model:ir.actions.act_window,name:l10n_in_hr_payroll.action_view_hr_bank_advice_tree +#: model:ir.ui.menu,name:l10n_in_hr_payroll.hr_menu_payment_advice +msgid "Payment Advices" +msgstr "" + +#. module: l10n_in_hr_payroll +#: model:ir.actions.act_window,name:l10n_in_hr_payroll.action_payment_advice_report_all +#: model:ir.ui.menu,name:l10n_in_hr_payroll.menu_reporting_payment_advice +#: view:payment.advice.report:0 +msgid "Advices Analysis" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.salary.employee.month:0 +msgid "" +"This wizard will print report which displays employees break-up of Net Head " +"for a specified dates." +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:hr.payroll.advice.line,ifsc:0 +msgid "IFSC" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:paylip.details.in:0 +msgid "Date To" +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:hr.contract,tds:0 +msgid "TDS" +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:hr.employee,join_date:0 +msgid "Join Date" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.payroll.advice:0 +msgid "Confirm Advices" +msgstr "" + +#. module: l10n_in_hr_payroll +#: constraint:hr.contract:0 +msgid "Error! Contract start-date must be less than contract end-date." +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:res.company,dearness_allowance:0 +msgid "Dearness Allowance" +msgstr "" + +#. module: l10n_in_hr_payroll +#: selection:payment.advice.report,month:0 +msgid "August" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.contract:0 +msgid "Deduction" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.payroll.advice:0 +msgid "Search Payment advice" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:payroll.advice:0 +msgid "SI. No." +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:payment.advice.report:0 +msgid "Payment Advices which are in confirm state" +msgstr "" + +#. module: l10n_in_hr_payroll +#: selection:payment.advice.report,month:0 +msgid "December" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.payroll.advice:0 +msgid "Confirm Sheet" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:payment.advice.report:0 +#: field:payment.advice.report,month:0 +msgid "Month" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:salary.detail.byyear:0 +msgid "Employee Code" +msgstr "" + +#. module: l10n_in_hr_payroll +#: selection:hr.contract,city_type:0 +msgid "Non Metro" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.salary.employee.month:0 +#: view:yearly.salary.detail:0 +msgid "or" +msgstr "" + +#. module: l10n_in_hr_payroll +#: model:ir.model,name:l10n_in_hr_payroll.model_hr_salary_employee_month +msgid "Hr Salary Employee By Month Report" +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:hr.salary.employee.month,category_id:0 +msgid "Category" +msgstr "" + +#. module: l10n_in_hr_payroll +#: code:addons/l10n_in_hr_payroll/l10n_in_hr_payroll.py:240 +#, python-format +msgid "" +"Payment advice already exists for %s, 'Set to Draft' to create a new advice." +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.payslip.run:0 +msgid "To Advice" +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:hr.employee,number_of_year:0 +msgid "No. of Years of Service" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:paylip.details.in:0 +msgid "Note" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:paylip.details.in:0 +msgid "Salary Rule Category" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.payroll.advice:0 +#: selection:hr.payroll.advice,state:0 +#: view:payment.advice.report:0 +#: selection:payment.advice.report,state:0 +msgid "Draft" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:paylip.details.in:0 +msgid "Date From" +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:hr.contract,voluntary_provident_fund:0 +msgid "Voluntary Provident Fund" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:salary.detail.byyear:0 +msgid "Employee Name" +msgstr "" + +#. module: l10n_in_hr_payroll +#: model:ir.model,name:l10n_in_hr_payroll.model_payment_advice_report +msgid "Payment Advice Analysis" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.payroll.advice:0 +#: view:payment.advice.report:0 +msgid "Status" +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:hr.contract,city_type:0 +msgid "Type of City" +msgstr "" + +#. module: l10n_in_hr_payroll +#: help:res.company,dearness_allowance:0 +msgid "Check this box if your company provide Dearness Allowance to employee" +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:hr.payroll.advice.line,ifsc_code:0 +#: field:payment.advice.report,ifsc_code:0 +#: report:payroll.advice:0 +msgid "IFSC Code" +msgstr "" + +#. module: l10n_in_hr_payroll +#: selection:payment.advice.report,month:0 +msgid "June" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:payment.advice.report:0 +#: field:payment.advice.report,nbr:0 +msgid "# Payment Lines" +msgstr "" + +#. module: l10n_in_hr_payroll +#: model:ir.actions.report.xml,name:l10n_in_hr_payroll.payslip_details_report +msgid "PaySlip Details" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.payroll.advice:0 +msgid "Payment Lines" +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:hr.payroll.advice,date:0 +#: field:payment.advice.report,date:0 +msgid "Date" +msgstr "" + +#. module: l10n_in_hr_payroll +#: selection:payment.advice.report,month:0 +msgid "November" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:payment.advice.report:0 +msgid "Extended Filters..." +msgstr "" + +#. module: l10n_in_hr_payroll +#: model:ir.actions.act_window,help:l10n_in_hr_payroll.action_payment_advice_report_all +msgid "This report performs analysis on Payment Advices" +msgstr "" + +#. module: l10n_in_hr_payroll +#: selection:payment.advice.report,month:0 +msgid "October" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:paylip.details.in:0 +#: report:salary.detail.byyear:0 +msgid "Designation" +msgstr "" + +#. module: l10n_in_hr_payroll +#: selection:payment.advice.report,month:0 +msgid "January" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:yearly.salary.detail:0 +msgid "Pay Head Employee Breakup" +msgstr "" + +#. module: l10n_in_hr_payroll +#: model:ir.model,name:l10n_in_hr_payroll.model_res_company +msgid "Companies" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:paylip.details.in:0 +#: report:payroll.advice:0 +msgid "Authorized Signature" +msgstr "" + +#. module: l10n_in_hr_payroll +#: model:ir.model,name:l10n_in_hr_payroll.model_hr_contract +msgid "Contract" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.payroll.advice.line:0 +msgid "Advice Lines" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:payroll.advice:0 +msgid "To," +msgstr "" + +#. module: l10n_in_hr_payroll +#: help:hr.contract,driver_salay:0 +msgid "Check this box if you provide allowance for driver" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.payroll.advice:0 +#: field:hr.payroll.advice.line,advice_id:0 +#: field:hr.payslip,advice_id:0 +#: model:ir.model,name:l10n_in_hr_payroll.model_hr_payroll_advice +msgid "Bank Advice" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:salary.detail.byyear:0 +msgid "Other No." +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.payroll.advice:0 +msgid "Draft Advices" +msgstr "" + +#. module: l10n_in_hr_payroll +#: help:hr.payroll.advice,neft:0 +msgid "Check this box if your company use online transfer for salary" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:salary.detail.byyear:0 +msgid "To" +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:payment.advice.report,number:0 +msgid "Number" +msgstr "" + +#. module: l10n_in_hr_payroll +#: selection:payment.advice.report,month:0 +msgid "September" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.payroll.advice:0 +#: view:hr.salary.employee.month:0 +#: view:yearly.salary.detail:0 +msgid "Cancel" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:payment.advice.report:0 +msgid "Day of Payment Advices" +msgstr "" + +#. module: l10n_in_hr_payroll +#: constraint:hr.employee:0 +msgid "Error! You cannot create recursive hierarchy of Employee(s)." +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:yearly.salary.detail:0 +msgid "" +"This wizard will print report which display a pay head employee breakup for " +"a specified dates." +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.payslip.run:0 +msgid "Payslip Batches ready to be Adviced" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:paylip.details.in:0 +msgid "Pay Slip Details" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:payment.advice.report:0 +msgid "Total Salary" +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:hr.payroll.advice.line,employee_id:0 +#: model:ir.model,name:l10n_in_hr_payroll.model_hr_employee +#: view:payment.advice.report:0 +#: field:payment.advice.report,employee_id:0 +msgid "Employee" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.payroll.advice:0 +msgid "Compute Advice" +msgstr "" + +#. module: l10n_in_hr_payroll +#: help:hr.employee,join_date:0 +msgid "Joining date of employee" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:payroll.advice:0 +msgid "Dear Sir/Madam," +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:hr.payroll.advice,note:0 +msgid "Description" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:paylip.details.in:0 +msgid ")" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:res.company:0 +msgid "Payroll" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:payment.advice.report:0 +msgid "NEFT" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:paylip.details.in:0 +#: report:salary.detail.byyear:0 +msgid "Address" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.payroll.advice:0 +#: field:hr.payroll.advice,bank_id:0 +#: view:payment.advice.report:0 +#: field:payment.advice.report,bank_id:0 +#: report:payroll.advice:0 +#: report:salary.detail.byyear:0 +msgid "Bank" +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:hr.salary.employee.month,end_date:0 +#: field:yearly.salary.detail,date_to:0 +msgid "End Date" +msgstr "" + +#. module: l10n_in_hr_payroll +#: selection:payment.advice.report,month:0 +msgid "February" +msgstr "" + +#. module: l10n_in_hr_payroll +#: sql_constraint:res.company:0 +msgid "The company name must be unique !" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.payroll.advice:0 +#: field:hr.payroll.advice,name:0 +#: report:paylip.details.in:0 +#: field:payment.advice.report,name:0 +#: report:salary.employee.bymonth:0 +msgid "Name" +msgstr "" + +#. module: l10n_in_hr_payroll +#: selection:hr.contract,city_type:0 +msgid "Metro" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.salary.employee.month:0 +#: field:hr.salary.employee.month,employee_ids:0 +#: view:yearly.salary.detail:0 +#: field:yearly.salary.detail,employee_ids:0 +msgid "Employees" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:paylip.details.in:0 +msgid "Bank Account" +msgstr "" + +#. module: l10n_in_hr_payroll +#: selection:payment.advice.report,month:0 +msgid "April" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:payroll.advice:0 +msgid "Name of the Employe" +msgstr "" + +#. module: l10n_in_hr_payroll +#: code:addons/l10n_in_hr_payroll/l10n_in_hr_payroll.py:158 +#: code:addons/l10n_in_hr_payroll/l10n_in_hr_payroll.py:257 +#, python-format +msgid "Please define bank account for the %s employee" +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:hr.salary.employee.month,start_date:0 +#: field:yearly.salary.detail,date_from:0 +msgid "Start Date" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.contract:0 +msgid "Allowance" +msgstr "" + +#. module: l10n_in_hr_payroll +#: help:hr.payroll.advice,bank_id:0 +msgid "Select the Bank from which the salary is going to be paid" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.salary.employee.month:0 +msgid "Employee Pay Head Breakup" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:salary.detail.byyear:0 +msgid "Phone No." +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:paylip.details.in:0 +msgid "Credit" +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:hr.payroll.advice.line,name:0 +#: report:payroll.advice:0 +msgid "Bank Account No." +msgstr "" + +#. module: l10n_in_hr_payroll +#: help:hr.payroll.advice,date:0 +msgid "Advice Date is used to search Payslips" +msgstr "" + +#. module: l10n_in_hr_payroll +#: selection:payment.advice.report,month:0 +msgid "May" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:hr.payslip.run:0 +msgid "Create Advice" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:payment.advice.report:0 +#: field:payment.advice.report,year:0 +msgid "Year" +msgstr "" + +#. module: l10n_in_hr_payroll +#: field:hr.payroll.advice,neft:0 +#: field:payment.advice.report,neft:0 +msgid "NEFT Transaction" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:paylip.details.in:0 +#: report:salary.detail.byyear:0 +#: report:salary.employee.bymonth:0 +msgid "Total" +msgstr "" + +#. module: l10n_in_hr_payroll +#: report:payroll.advice:0 +msgid "form period" +msgstr "" + +#. module: l10n_in_hr_payroll +#: view:payment.advice.report:0 +msgid "Year of Payment Advices" +msgstr "" diff --git a/addons/l10n_it/data/account.tax.code.template.csv b/addons/l10n_it/data/account.tax.code.template.csv index 290396052f4..d6d6667f43b 100644 --- a/addons/l10n_it/data/account.tax.code.template.csv +++ b/addons/l10n_it/data/account.tax.code.template.csv @@ -60,6 +60,3 @@ IVC21det40,template_ivacode_pagata_21det40,IVA a credito 21% detraibile 40%,temp IVC21Idet40,template_impcode_pagata_21det40,IVA a credito 21% detraibile 40% (imponibile),template_impcode_pagata IVC21det50,template_ivacode_pagata_21det50,IVA a credito 21% detraibile 50%,template_ivacode_pagata IVC21Idet50,template_impcode_pagata_21det50,IVA a credito 21% detraibile 50% (imponibile),template_impcode_pagata -Rit,template_ra,Ritenute d'acconto,vat_code_chart_root -RitD20,template_ritcode_20,Ritenute a debito 20%,template_ra -RitD20I,template_ritimpcode_20,Ritenute a debito 20% (imponibile),template_ra diff --git a/addons/l10n_it/data/account.tax.template.csv b/addons/l10n_it/data/account.tax.template.csv index 36c4f99db79..641f495dd72 100644 --- a/addons/l10n_it/data/account.tax.template.csv +++ b/addons/l10n_it/data/account.tax.template.csv @@ -62,4 +62,3 @@ id,description,chart_template_id:id,name,sequence,amount,parent_id:id,child_depe 21I5,21I5,l10n_it_chart_template_generic,IVA al 21% detraibile al 50%,,0.21,,True,percent,,,purchase,template_impcode_pagata_21det50,,template_impcode_pagata_21det50,,,,False,-1,-1 21I5b,21I5b,l10n_it_chart_template_generic,IVA al 21% detraibile al 50% (I),1,0.5,21I5,False,percent,,,purchase,,,,,,,False,, 21I5a,21I5a,l10n_it_chart_template_generic,IVA al 21% detraibile al 50% (D),2,0,21I5,False,balance,1601,1601,purchase,,template_ivacode_pagata_21det50,,template_ivacode_pagata_21det50,,,False,, -rit-20,rit-20,l10n_it_chart_template_generic,Ritenuta d'acconto al 20% (debito),,-0.2,,False,percent,2602,2602,purchase,template_ritimpcode_20,template_ritcode_20,template_ritimpcode_20,template_ritcode_20,-1,1,False,1,-1 diff --git a/addons/l10n_it/l10n_chart_it_generic.xml b/addons/l10n_it/l10n_chart_it_generic.xml index 792bd21a2fc..dde7a77516a 100644 --- a/addons/l10n_it/l10n_chart_it_generic.xml +++ b/addons/l10n_it/l10n_chart_it_generic.xml @@ -1,5 +1,5 @@ - + open diff --git a/addons/l10n_lu/l10n_lu_wizard.xml b/addons/l10n_lu/l10n_lu_wizard.xml index 6919eb199b6..52aaa88fdab 100644 --- a/addons/l10n_lu/l10n_lu_wizard.xml +++ b/addons/l10n_lu/l10n_lu_wizard.xml @@ -1,5 +1,5 @@ - + open diff --git a/addons/l10n_ma/l10n_ma_wizard.xml b/addons/l10n_ma/l10n_ma_wizard.xml index 6919eb199b6..52aaa88fdab 100644 --- a/addons/l10n_ma/l10n_ma_wizard.xml +++ b/addons/l10n_ma/l10n_ma_wizard.xml @@ -1,5 +1,5 @@ - + open diff --git a/addons/l10n_multilang/i18n/es.po b/addons/l10n_multilang/i18n/es.po new file mode 100644 index 00000000000..4ba54992c5b --- /dev/null +++ b/addons/l10n_multilang/i18n/es.po @@ -0,0 +1,177 @@ +# 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 01:06+0000\n" +"PO-Revision-Date: 2012-11-08 14:29+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-09 04:39+0000\n" +"X-Generator: Launchpad (build 16250)\n" + +#. module: l10n_multilang +#: model:ir.model,name:l10n_multilang.model_account_fiscal_position_template +msgid "Template for Fiscal Position" +msgstr "Plantilla para posición fiscal" + +#. module: l10n_multilang +#: sql_constraint:account.account:0 +msgid "The code of the account must be unique per company !" +msgstr "¡El código de la cuenta debe ser único por compañía!" + +#. module: l10n_multilang +#: constraint:account.account.template:0 +msgid "" +"Configuration Error!\n" +"You can not define children to an account with internal type different of " +"\"View\"! " +msgstr "" +"Error de configuración!\n" +"¡No puede definir hijos para una cuenta con el tipo interno distinto de " +"\"Vista\"! " + +#. module: l10n_multilang +#: model:ir.model,name:l10n_multilang.model_account_analytic_journal +msgid "Analytic Journal" +msgstr "Diario analítico" + +#. module: l10n_multilang +#: constraint:account.account.template:0 +msgid "Error ! You can not create recursive account templates." +msgstr "¡Error! No puede crear plantillas de cuentas recursivas." + +#. module: l10n_multilang +#: model:ir.model,name:l10n_multilang.model_account_journal +msgid "Journal" +msgstr "Diario" + +#. module: l10n_multilang +#: model:ir.model,name:l10n_multilang.model_account_chart_template +msgid "Templates for Account Chart" +msgstr "Plantillas para el plan contable" + +#. module: l10n_multilang +#: sql_constraint:account.tax:0 +msgid "The description must be unique per company!" +msgstr "¡La descripción debe ser única por compañia!" + +#. module: l10n_multilang +#: constraint:account.tax.code.template:0 +msgid "Error ! You can not create recursive Tax Codes." +msgstr "¡Error! No puede crear códigos de impuestos recursivos." + +#. module: l10n_multilang +#: model:ir.model,name:l10n_multilang.model_account_tax_template +msgid "account.tax.template" +msgstr "Plantilla de impuestos" + +#. module: l10n_multilang +#: model:ir.model,name:l10n_multilang.model_account_tax +msgid "account.tax" +msgstr "Impuesto" + +#. module: l10n_multilang +#: model:ir.model,name:l10n_multilang.model_account_account +msgid "Account" +msgstr "Cuenta" + +#. module: l10n_multilang +#: model:ir.model,name:l10n_multilang.model_wizard_multi_charts_accounts +msgid "wizard.multi.charts.accounts" +msgstr "Asistente de plan de cuentas" + +#. module: l10n_multilang +#: constraint:account.journal:0 +msgid "" +"Configuration error! The currency chosen should be shared by the default " +"accounts too." +msgstr "" +"¡Error de configuración! La moneda elegida debería ser también la misma en " +"las cuentas por defecto" + +#. module: l10n_multilang +#: model:ir.model,name:l10n_multilang.model_account_account_template +msgid "Templates for Accounts" +msgstr "Plantillas para cuentas" + +#. module: l10n_multilang +#: help:account.chart.template,spoken_languages:0 +msgid "" +"State here the languages for which the translations of templates could be " +"loaded at the time of installation of this localization module and copied in " +"the final object when generating them from templates. You must provide the " +"language codes separated by ';'" +msgstr "" +"Indique aquí los idiomas para los que las traducciones de las plantillas " +"pueden ser cargadas en el momento de la instalación de este módulo de " +"localización y copiados en el objeto final cuando se generen desde las " +"plantillas. Debe proveer los códigos de idioma separados por ';'." + +#. module: l10n_multilang +#: constraint:account.account:0 +msgid "Error ! You can not create recursive accounts." +msgstr "¡Error! No se pueden crear cuentas recursivas." + +#. module: l10n_multilang +#: constraint:account.account:0 +msgid "" +"Configuration Error! \n" +"You can not select an account type with a deferral method different of " +"\"Unreconciled\" for accounts with internal type \"Payable/Receivable\"! " +msgstr "" +"¡Error de configuración! \n" +"¡No puede seleccionar un tipo de cuenta con un método de cierre diferente de " +"\"Reconciliado\" para las cuentas con tipo interno \"A pagar/A cobrar\"! " + +#. module: l10n_multilang +#: sql_constraint:account.journal:0 +msgid "The name of the journal must be unique per company !" +msgstr "¡El nombre del diaro debe ser único por compañía!" + +#. module: l10n_multilang +#: model:ir.model,name:l10n_multilang.model_account_analytic_account +msgid "Analytic Account" +msgstr "Cuenta analítica" + +#. module: l10n_multilang +#: sql_constraint:account.journal:0 +msgid "The code of the journal must be unique per company !" +msgstr "¡El código del diario debe ser único por compañía!" + +#. module: l10n_multilang +#: model:ir.model,name:l10n_multilang.model_account_fiscal_position +msgid "Fiscal Position" +msgstr "Posición fiscal" + +#. module: l10n_multilang +#: constraint:account.account:0 +msgid "" +"Configuration Error! \n" +"You can not define children to an account with internal type different of " +"\"View\"! " +msgstr "" +"¡Error de configuración! \n" +"¡No puede definir hijos en una cuenta con tipo interno distinto a \"Vista\"! " + +#. module: l10n_multilang +#: constraint:account.analytic.account:0 +msgid "Error! You can not create recursive analytic accounts." +msgstr "¡Error! No puede crear cuentas analíticas recursivas." + +#. module: l10n_multilang +#: model:ir.model,name:l10n_multilang.model_account_tax_code_template +msgid "Tax Code Template" +msgstr "Plantilla códigos de impuestos" + +#. module: l10n_multilang +#: field:account.chart.template,spoken_languages:0 +msgid "Spoken Languages" +msgstr "Idiomas hablados" diff --git a/addons/l10n_mx/l10n_chart_mx_wizard.xml b/addons/l10n_mx/l10n_chart_mx_wizard.xml index 6919eb199b6..52aaa88fdab 100644 --- a/addons/l10n_mx/l10n_chart_mx_wizard.xml +++ b/addons/l10n_mx/l10n_chart_mx_wizard.xml @@ -1,5 +1,5 @@ - + open diff --git a/addons/l10n_nl/account_chart_netherlands.xml b/addons/l10n_nl/account_chart_netherlands.xml index 96100d9a442..f736c6f4895 100644 --- a/addons/l10n_nl/account_chart_netherlands.xml +++ b/addons/l10n_nl/account_chart_netherlands.xml @@ -1424,7 +1424,7 @@ - + Btw af te dragen laag 1601 other @@ -1432,7 +1432,7 @@ - + Btw af te dragen hoog 1602 other @@ -1448,7 +1448,7 @@ - + Btw te vorderen laag 1611 other @@ -1456,7 +1456,7 @@ - + Btw te vorderen hoog 1612 other @@ -3909,7 +3909,7 @@ 1a - Leveringen/diensten belast met 19% (BTW) + Leveringen/diensten belast met 21% (BTW) 1b @@ -4021,7 +4021,7 @@ 1a - Leveringen/diensten belast met 19% (omzet) + Leveringen/diensten belast met 21% (omzet) 1b @@ -4149,22 +4149,22 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge 6% BTW percent - - + + sale - + Verkopen/omzet hoog - 19% BTW - + 21% BTW + percent - - + + @@ -4176,10 +4176,10 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge Verkopen/omzet overig variabel BTW - + percent - - + + @@ -4194,20 +4194,20 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge 6% BTW percent - - + + purchase - + BTW te vorderen hoog (inkopen) - 19% BTW - + 21% BTW + percent - - + + purchase @@ -4216,10 +4216,10 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge BTW te vorderen overig (inkopen) variabel BTW - + percent - - + + purchase @@ -4240,8 +4240,8 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge BTW af te dragen verlegd (inkopen) - 19% BTW verlegd - + 21% BTW verlegd + percent @@ -4277,8 +4277,8 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge BTW te vorderen verlegd (inkopen) - 19% BTW verlegd - + 21% BTW verlegd + percent @@ -4330,8 +4330,8 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge percent - - + + purchase @@ -4342,43 +4342,43 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge percent - - + + purchase - + Inkopen import binnen EU hoog - 19% BTW import binnen EU - + 21% BTW import binnen EU + percent purchase - + Inkopen import binnen EU hoog(1) percent - - - + + + purchase - + Inkopen import binnen EU hoog(2) percent - - - + + + purchase @@ -4400,8 +4400,8 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge percent - - + + purchase @@ -4412,8 +4412,8 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge percent - - + + purchase @@ -4463,8 +4463,8 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge percent - - + + purchase @@ -4475,8 +4475,8 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge percent - - + + purchase @@ -4485,7 +4485,7 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge Inkopen import buiten EU hoog BTW import buiten EU - + percent @@ -4498,8 +4498,8 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge percent - - + + purchase @@ -4510,8 +4510,8 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge percent - - + + purchase @@ -4520,7 +4520,7 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge Inkopen import buiten EU overig BTW import buiten EU - + percent @@ -4533,8 +4533,8 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge percent - - + + purchase @@ -4545,8 +4545,8 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge percent - - + + purchase diff --git a/addons/l10n_nl/l10n_nl_wizard.xml b/addons/l10n_nl/l10n_nl_wizard.xml index 6919eb199b6..52aaa88fdab 100644 --- a/addons/l10n_nl/l10n_nl_wizard.xml +++ b/addons/l10n_nl/l10n_nl_wizard.xml @@ -1,5 +1,5 @@ - + open diff --git a/addons/l10n_pe/l10n_pe_wizard.xml b/addons/l10n_pe/l10n_pe_wizard.xml index 3c94d3db7a8..82e7671b4d3 100644 --- a/addons/l10n_pe/l10n_pe_wizard.xml +++ b/addons/l10n_pe/l10n_pe_wizard.xml @@ -1,6 +1,6 @@ - + open diff --git a/addons/l10n_pl/l10n_chart_pl_wizard.xml b/addons/l10n_pl/l10n_chart_pl_wizard.xml index 6919eb199b6..52aaa88fdab 100644 --- a/addons/l10n_pl/l10n_chart_pl_wizard.xml +++ b/addons/l10n_pl/l10n_chart_pl_wizard.xml @@ -1,5 +1,5 @@ - + open diff --git a/addons/l10n_ro/l10n_chart_ro_wizard.xml b/addons/l10n_ro/l10n_chart_ro_wizard.xml index 6919eb199b6..52aaa88fdab 100644 --- a/addons/l10n_ro/l10n_chart_ro_wizard.xml +++ b/addons/l10n_ro/l10n_chart_ro_wizard.xml @@ -1,5 +1,5 @@ - + open diff --git a/addons/l10n_syscohada/l10n_syscohada_wizard.xml b/addons/l10n_syscohada/l10n_syscohada_wizard.xml index 792bd21a2fc..dde7a77516a 100644 --- a/addons/l10n_syscohada/l10n_syscohada_wizard.xml +++ b/addons/l10n_syscohada/l10n_syscohada_wizard.xml @@ -1,5 +1,5 @@ - + open diff --git a/addons/l10n_th/account_data.xml b/addons/l10n_th/account_data.xml index d730caa8404..9ae3d9e3364 100644 --- a/addons/l10n_th/account_data.xml +++ b/addons/l10n_th/account_data.xml @@ -641,11 +641,12 @@ sale + - + + open - diff --git a/addons/l10n_tr/l10n_tr_wizard.xml b/addons/l10n_tr/l10n_tr_wizard.xml index 59668347bed..66b9d37221b 100644 --- a/addons/l10n_tr/l10n_tr_wizard.xml +++ b/addons/l10n_tr/l10n_tr_wizard.xml @@ -1,6 +1,6 @@ - + open diff --git a/addons/l10n_uk/l10n_uk_wizard.xml b/addons/l10n_uk/l10n_uk_wizard.xml index 1b8d528c0e8..8a6f1a5a83c 100644 --- a/addons/l10n_uk/l10n_uk_wizard.xml +++ b/addons/l10n_uk/l10n_uk_wizard.xml @@ -1,6 +1,6 @@ - + open diff --git a/addons/l10n_us/l10n_us_wizard.xml b/addons/l10n_us/l10n_us_wizard.xml index 6919eb199b6..52aaa88fdab 100644 --- a/addons/l10n_us/l10n_us_wizard.xml +++ b/addons/l10n_us/l10n_us_wizard.xml @@ -1,5 +1,5 @@ - + open diff --git a/addons/l10n_uy/l10n_uy_wizard.xml b/addons/l10n_uy/l10n_uy_wizard.xml index 49cbeca89f9..3dea57dc5c2 100644 --- a/addons/l10n_uy/l10n_uy_wizard.xml +++ b/addons/l10n_uy/l10n_uy_wizard.xml @@ -1,6 +1,6 @@ - + open diff --git a/addons/l10n_ve/l10n_chart_ve_wizard.xml b/addons/l10n_ve/l10n_chart_ve_wizard.xml index 6919eb199b6..52aaa88fdab 100644 --- a/addons/l10n_ve/l10n_chart_ve_wizard.xml +++ b/addons/l10n_ve/l10n_chart_ve_wizard.xml @@ -1,5 +1,5 @@ - + open 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 + + + + + + + + + +

- +
+ +
+ +
+ + +
-
@@ -83,7 +84,7 @@
- +
diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py index bd0ade31b68..9fd476d9d7f 100644 --- a/addons/mail/mail_message.py +++ b/addons/mail/mail_message.py @@ -25,6 +25,7 @@ import tools from email.header import decode_header from openerp import SUPERUSER_ID from openerp.osv import osv, orm, fields +from openerp.tools import html_email_clean from openerp.tools.translate import _ _logger = logging.getLogger(__name__) @@ -50,8 +51,9 @@ class mail_message(osv.Model): _description = 'Message' _inherit = ['ir.needaction_mixin'] _order = 'id desc' + _rec_name = 'record_name' - _message_read_limit = 10 + _message_read_limit = 30 _message_read_fields = ['id', 'parent_id', 'model', 'res_id', 'body', 'subject', 'date', 'to_read', 'email_from', 'type', 'vote_user_ids', 'attachment_ids', 'author_id', 'partner_ids', 'record_name', 'favorite_user_ids'] _message_record_name_length = 18 @@ -120,21 +122,26 @@ class mail_message(osv.Model): "message, comment for other messages such as user replies"), 'email_from': fields.char('From', help="Email address of the sender. This field is set when no matching partner is found for incoming emails."), - 'author_id': fields.many2one('res.partner', 'Author', + 'author_id': fields.many2one('res.partner', 'Author', select=1, + ondelete='set null', help="Author of the message. If not set, email_from may hold an email address that did not match any partner."), 'partner_ids': fields.many2many('res.partner', string='Recipients'), 'notified_partner_ids': fields.many2many('res.partner', 'mail_notification', - 'message_id', 'partner_id', 'Recipients'), + 'message_id', 'partner_id', 'Notified partners', + help='Partners that have a notification pushing this message in their mailboxes'), 'attachment_ids': fields.many2many('ir.attachment', 'message_attachment_rel', 'message_id', 'attachment_id', 'Attachments'), - 'parent_id': fields.many2one('mail.message', 'Parent Message', select=True, ondelete='set null', help="Initial thread message."), + 'parent_id': fields.many2one('mail.message', 'Parent Message', select=True, + ondelete='set null', help="Initial thread message."), 'child_ids': fields.one2many('mail.message', 'parent_id', 'Child Messages'), 'model': fields.char('Related Document Model', size=128, select=1), 'res_id': fields.integer('Related Document ID', select=1), 'record_name': fields.function(_get_record_name, type='char', store=True, string='Message Record Name', help="Name get of the related document."), - 'notification_ids': fields.one2many('mail.notification', 'message_id', 'Notifications'), + 'notification_ids': fields.one2many('mail.notification', 'message_id', + string='Notifications', + help='Technical field holding the message notifications. Use notified_partner_ids to access notified partners.'), 'subject': fields.char('Subject'), 'date': fields.datetime('Date'), 'message_id': fields.char('Message-Id', help='Message unique identifier', select=1, readonly=1), @@ -142,7 +149,8 @@ class mail_message(osv.Model): 'to_read': fields.function(_get_to_read, fnct_search=_search_to_read, type='boolean', string='To read', help='Functional field to search for messages the current user has to read'), - 'subtype_id': fields.many2one('mail.message.subtype', 'Subtype'), + 'subtype_id': fields.many2one('mail.message.subtype', 'Subtype', + ondelete='set null', select=1,), 'vote_user_ids': fields.many2many('res.users', 'mail_vote', 'message_id', 'user_id', string='Votes', help='Users that voted for this message'), @@ -200,67 +208,99 @@ class mail_message(osv.Model): # Message loading for web interface #------------------------------------------------------ - def _message_get_dict(self, cr, uid, message, context=None): - """ Return a dict representation of the message. This representation is - used in the JS client code, to display the messages. + def _message_read_dict_postprocess(self, cr, uid, messages, message_tree, context=None): + """ Post-processing on values given by message_read. This method will + handle partners in batch to avoid doing numerous queries. - :param dict message: read result of a mail.message + :param list messages: list of message, as get_dict result + :param dict message_tree: {[msg.id]: msg browse record} """ - # TDE note: this method should be optimized, to lessen the number of queries, will be done ASAP - is_author = False - if message['author_id']: - is_author = message['author_id'][0] == self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0] - author_id = message['author_id'] - elif message['email_from']: - author_id = (0, message['email_from']) + res_partner_obj = self.pool.get('res.partner') + ir_attachment_obj = self.pool.get('ir.attachment') + pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0] - has_voted = False - if uid in message.get('vote_user_ids'): - has_voted = True + # 1. Aggregate partners (author_id and partner_ids) and attachments + partner_ids = set() + attachment_ids = set() + for key, message in message_tree.iteritems(): + if message.author_id: + partner_ids |= set([message.author_id.id]) + if message.partner_ids: + partner_ids |= set([partner.id for partner in message.partner_ids]) + if message.attachment_ids: + attachment_ids |= set([attachment.id for attachment in message.attachment_ids]) - is_favorite = False - if uid in message.get('favorite_user_ids'): - is_favorite = True + # Filter author_ids uid can see + # partner_ids = self.pool.get('res.partner').search(cr, uid, [('id', 'in', partner_ids)], context=context) + partners = res_partner_obj.name_get(cr, uid, list(partner_ids), context=context) + partner_tree = dict((partner[0], partner) for partner in partners) - is_private = True - if message.get('model') and message.get('res_id'): - is_private = False + # 2. Attachments + attachments = ir_attachment_obj.read(cr, uid, list(attachment_ids), ['id', 'datas_fname'], context=context) + attachments_tree = dict((attachment['id'], {'id': attachment['id'], 'filename': attachment['datas_fname']}) for attachment in attachments) - try: - attachment_ids = [{'id': attach[0], 'name': attach[1]} for attach in self.pool.get('ir.attachment').name_get(cr, uid, message['attachment_ids'], context=context)] - except (orm.except_orm, osv.except_osv): - attachment_ids = [] - - # TDE note: should we send partner_ids ? - # TDE note: shouldn't we separated followers and other partners ? costly to compute maybe , - try: - partner_ids = self.pool.get('res.partner').name_get(cr, uid, message['partner_ids'], context=context) - except (orm.except_orm, osv.except_osv): + # 3. Update message dictionaries + for message_dict in messages: + message_id = message_dict.get('id') + message = message_tree[message_id] + if message.author_id: + author = partner_tree[message.author_id.id] + else: + author = (0, message.email_from) partner_ids = [] + for partner in message.partner_ids: + if partner.id in partner_tree: + partner_ids.append(partner_tree[partner.id]) + attachment_ids = [] + for attachment in message.attachment_ids: + if attachment.id in attachments_tree: + attachment_ids.append(attachments_tree[attachment.id]) + message_dict.update({ + 'is_author': pid == author[0], + 'author_id': author, + 'partner_ids': partner_ids, + 'attachment_ids': attachment_ids, + }) + return True - return { - 'id': message['id'], - 'type': message['type'], - 'attachment_ids': attachment_ids, - 'body': message['body'], - 'model': message['model'], - 'res_id': message['res_id'], - 'record_name': message['record_name'], - 'subject': message['subject'], - 'date': message['date'], - 'author_id': author_id, - 'is_author': is_author, - 'partner_ids': partner_ids, - 'parent_id': False, - 'vote_nb': len(message['vote_user_ids']), - 'has_voted': has_voted, - 'is_private': is_private, - 'is_favorite': is_favorite, - 'to_read': message['to_read'], - } + def _message_read_dict(self, cr, uid, message, parent_id=False, context=None): + """ Return a dict representation of the message. This representation is + used in the JS client code, to display the messages. Partners and + attachments related stuff will be done in post-processing in batch. - def _message_read_add_expandables(self, cr, uid, message_list, read_messages, - thread_level=0, message_loaded_ids=[], domain=[], parent_id=False, context=None, limit=None): + :param dict message: mail.message browse record + """ + # private message: no model, no res_id + is_private = False + if not message.model or not message.res_id: + is_private = True + # votes and favorites: res.users ids, no prefetching should be done + vote_nb = len(message.vote_user_ids) + has_voted = uid in [user.id for user in message.vote_user_ids] + is_favorite = uid in [user.id for user in message.favorite_user_ids] + + return {'id': message.id, + 'type': message.type, + 'body': html_email_clean(message.body), + 'model': message.model, + 'res_id': message.res_id, + 'record_name': message.record_name, + 'subject': message.subject, + 'date': message.date, + 'to_read': message.to_read, + 'parent_id': parent_id, + 'is_private': is_private, + 'author_id': False, + 'is_author': False, + 'partner_ids': [], + 'vote_nb': vote_nb, + 'has_voted': has_voted, + 'is_favorite': is_favorite, + 'attachment_ids': [], + } + + def _message_read_add_expandables(self, cr, uid, messages, message_tree, parent_tree, + message_unload_ids=[], thread_level=0, domain=[], parent_id=False, context=None): """ Create expandables for message_read, to load new messages. 1. get the expandable for new threads if display is flat (thread_level == 0): @@ -275,96 +315,82 @@ class mail_message(osv.Model): for each hole in the child list based on message displayed, create an expandable - :param list message_list:list of message structure for the Chatter + :param list messages: list of message structure for the Chatter widget to which expandables are added - :param dict read_messages: dict [id]: read result of the messages to - easily have access to their values, given their ID + :param dict message_tree: dict [id]: browse record of this message + :param dict parent_tree: dict [parent_id]: [child_ids] + :param list message_unload_ids: list of message_ids we do not want + to load :return bool: True """ - def _get_expandable(domain, message_nb, parent_id, id, model): + def _get_expandable(domain, message_nb, parent_id, max_limit): return { 'domain': domain, 'nb_messages': message_nb, 'type': 'expandable', 'parent_id': parent_id, - 'id': id, - # TDE note: why do we need model sometimes, and sometimes not ??? - 'model': model, + 'max_limit': max_limit, } - # all_not_loaded_ids = [] - id_list = sorted(read_messages.keys()) - if not id_list: - return message_list + if not messages: + return True + message_ids = sorted(message_tree.keys()) # 1. get the expandable for new threads if thread_level == 0: - exp_domain = domain + [('id', '<', min(message_loaded_ids + id_list))] + exp_domain = domain + [('id', '<', min(message_unload_ids + message_ids))] else: - exp_domain = domain + ['!', ('id', 'child_of', message_loaded_ids + id_list)] + exp_domain = domain + ['!', ('id', 'child_of', message_unload_ids + parent_tree.keys())] ids = self.search(cr, uid, exp_domain, context=context, limit=1) if ids: - message_list.append(_get_expandable(exp_domain, -1, parent_id, -1, None)) + # inside a thread: prepend + if parent_id: + messages.insert(0, _get_expandable(exp_domain, -1, parent_id, True)) + # new threads: append + else: + messages.append(_get_expandable(exp_domain, -1, parent_id, True)) # 2. get the expandables for new messages inside threads if display is not flat if thread_level == 0: return True - for message_id in id_list: - message = read_messages[message_id] + for message_id in message_ids: + message = message_tree[message_id] - # message is not a thread header (has a parent_id) - # TDE note: parent_id is false is there is a parent we can not see -> ok - if message.get('parent_id'): + # generate only for thread header messages (TDE note: parent_id may be False is uid cannot see parent_id, seems ok) + if message.parent_id: continue - # TDE note: check search is correctly implemented in mail.message - not_loaded_ids = self.search(cr, uid, [ - ('id', 'child_of', message['id']), - ('id', 'not in', message_loaded_ids), - ], context=context, limit=self._message_read_more_limit) - if not not_loaded_ids: + # check there are message for expandable + child_ids = set([child.id for child in message.child_ids]) - set(message_unload_ids) + child_ids = sorted(list(child_ids), reverse=True) + if not child_ids: continue - # all_not_loaded_ids += not_loaded_ids - # group childs not read - id_min, id_max, nb = max(not_loaded_ids), 0, 0 - for not_loaded_id in not_loaded_ids: - if not read_messages.get(not_loaded_id): + # make groups of unread messages + id_min, id_max, nb = max(child_ids), 0, 0 + for child_id in child_ids: + if not child_id in message_ids: nb += 1 - if id_min > not_loaded_id: - id_min = not_loaded_id - if id_max < not_loaded_id: - id_max = not_loaded_id + if id_min > child_id: + id_min = child_id + if id_max < child_id: + id_max = child_id elif nb > 0: exp_domain = [('id', '>=', id_min), ('id', '<=', id_max), ('id', 'child_of', message_id)] - message_list.append(_get_expandable(exp_domain, nb, message_id, id_min, message.get('model'))) - id_min, id_max, nb = max(not_loaded_ids), 0, 0 + messages.append(_get_expandable(exp_domain, nb, message_id, False)) + id_min, id_max, nb = max(child_ids), 0, 0 else: - id_min, id_max, nb = max(not_loaded_ids), 0, 0 + id_min, id_max, nb = max(child_ids), 0, 0 if nb > 0: exp_domain = [('id', '>=', id_min), ('id', '<=', id_max), ('id', 'child_of', message_id)] - message_list.append(_get_expandable(exp_domain, nb, message_id, id_min, message.get('model'))) - - # message_loaded_ids = list(set(message_loaded_ids + read_messages.keys() + all_not_loaded_ids)) + idx = [msg.get('id') for msg in messages].index(message_id) + 1 + # messages.append(_get_expandable(exp_domain, nb, message_id, id_min)) + messages.insert(idx, _get_expandable(exp_domain, nb, message_id, False)) return True - def _get_parent(self, cr, uid, message, context=None): - """ Tools method that tries to get the parent of a mail.message. If - no parent, or if uid has no access right on the parent, False - is returned. - - :param dict message: read result of a mail.message - """ - if not message['parent_id']: - return False - parent_id = message['parent_id'][0] - try: - return self.read(cr, uid, parent_id, self._message_read_fields, context=context) - except (orm.except_orm, osv.except_osv): - return False - - def message_read(self, cr, uid, ids=None, domain=None, message_unload_ids=None, thread_level=0, context=None, parent_id=False, limit=None): + def message_read(self, cr, uid, ids=None, domain=None, message_unload_ids=None, + thread_level=0, context=None, parent_id=False, limit=None): """ Read messages from mail.message, and get back a list of structured messages to be displayed as discussion threads. If IDs is set, fetch these records. Otherwise use the domain to fetch messages. @@ -388,46 +414,56 @@ class mail_message(osv.Model): ancestors and expandables :return list: list of message structure for the Chatter widget """ - # print 'message_read', ids, domain, message_unload_ids, thread_level, context, parent_id, limit assert thread_level in [0, 1], 'message_read() thread_level should be 0 (flat) or 1 (1 level of thread); given %s.' % thread_level domain = domain if domain is not None else [] message_unload_ids = message_unload_ids if message_unload_ids is not None else [] if message_unload_ids: domain += [('id', 'not in', message_unload_ids)] limit = limit or self._message_read_limit - read_messages = {} + message_tree = {} message_list = [] + parent_tree = {} # no specific IDS given: fetch messages according to the domain, add their parents if uid has access to if ids is None: ids = self.search(cr, uid, domain, context=context, limit=limit) - for message in self.read(cr, uid, ids, self._message_read_fields, context=context): - message_id = message['id'] - # if not in tree and not in message_loaded list - if not message_id in read_messages and not message_id in message_unload_ids: - read_messages[message_id] = message - message_list.append(self._message_get_dict(cr, uid, message, context=context)) + # fetch parent if threaded, sort messages + for message in self.browse(cr, uid, ids, context=context): + message_id = message.id + if message_id in message_tree: + continue + message_tree[message_id] = message - # get the older ancestor the user can read, update its ancestor field - if not thread_level: - message_list[-1]['parent_id'] = parent_id - continue - parent = self._get_parent(cr, uid, message, context=context) - while parent and parent.get('id') != parent_id: - message_list[-1]['parent_id'] = parent.get('id') - message = parent - parent = self._get_parent(cr, uid, message, context=context) - # if in thread: add its ancestor to the list of messages - if not message['id'] in read_messages and not message['id'] in message_unload_ids: - read_messages[message['id']] = message - message_list.append(self._message_get_dict(cr, uid, message, context=context)) + # find parent_id + if thread_level == 0: + tree_parent_id = parent_id + else: + tree_parent_id = message_id + parent = message + while parent.parent_id and parent.parent_id.id != parent_id: + parent = parent.parent_id + tree_parent_id = parent.id + if not parent.id in message_tree: + message_tree[parent.id] = parent + # newest messages first + parent_tree.setdefault(tree_parent_id, []) + if tree_parent_id != message_id: + parent_tree[tree_parent_id].append(self._message_read_dict(cr, uid, message_tree[message_id], parent_id=tree_parent_id, context=context)) + + if thread_level: + for key, message_id_list in parent_tree.iteritems(): + message_id_list.sort(key=lambda item: item['id']) + message_id_list.insert(0, self._message_read_dict(cr, uid, message_tree[key], context=context)) + + parent_list = parent_tree.items() + parent_list = sorted(parent_list, key=lambda item: max([msg.get('id') for msg in item[1]]) if item[1] else item[0], reverse=True) + message_list = [message for (key, msg_list) in parent_list for message in msg_list] # get the child expandable messages for the tree - message_list = sorted(message_list, key=lambda k: k['id']) - self._message_read_add_expandables(cr, uid, message_list, read_messages, thread_level=thread_level, - message_loaded_ids=message_unload_ids, domain=domain, parent_id=parent_id, context=context, limit=limit) - + self._message_read_dict_postprocess(cr, uid, message_list, message_tree, context=context) + self._message_read_add_expandables(cr, uid, message_list, message_tree, parent_tree, + thread_level=thread_level, message_unload_ids=message_unload_ids, domain=domain, parent_id=parent_id, context=context) return message_list # TDE Note: do we need this ? @@ -461,7 +497,6 @@ class mail_message(osv.Model): - otherwise: remove the id """ # Rules do not apply to administrator - # print '_search', uid, args if uid == SUPERUSER_ID: return super(mail_message, self)._search(cr, uid, args, offset=offset, limit=limit, order=order, context=context, count=count, access_rights_uid=access_rights_uid) @@ -599,6 +634,8 @@ class mail_message(osv.Model): def create(self, cr, uid, values, context=None): if not values.get('message_id') and values.get('res_id') and values.get('model'): values['message_id'] = tools.generate_tracking_message_id('%(res_id)s-%(model)s' % values) + elif not values.get('message_id'): + values['message_id'] = tools.generate_tracking_message_id('private') newid = super(mail_message, self).create(cr, uid, values, context) self._notify(cr, SUPERUSER_ID, newid, context=context) return newid @@ -729,7 +766,7 @@ class mail_message(osv.Model): ], context=context) fol_objs = fol_obj.read(cr, uid, fol_ids, ['partner_id'], context=context) partners_to_notify |= set(fol['partner_id'][0] for fol in fol_objs) - # when writing to a wall + # remove me from notified partners, unless the message is written on my own wall if message.get('author_id') and message.get('model') == "res.partner" and message.get('res_id') == message.get('author_id')[0]: partners_to_notify |= set([message.get('author_id')[0]]) elif message.get('author_id'): diff --git a/addons/mail/mail_message_view.xml b/addons/mail/mail_message_view.xml index 93b35797fd5..7d2e9483773 100644 --- a/addons/mail/mail_message_view.xml +++ b/addons/mail/mail_message_view.xml @@ -59,21 +59,26 @@ + + - + +
@@ -84,7 +89,6 @@ form tree,form - {'search_default_to_read_message':True} diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index f3203336615..ee1a015396b 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -74,17 +74,17 @@ class mail_thread(osv.AbstractModel): - message_unread: has uid unread message for the document - message_summary: html snippet summarizing the Chatter for kanban views """ res = dict((id, dict(message_unread=False, message_summary='')) for id in ids) + user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0] - # search for unread messages, by reading directly mail.notification, as SUPERUSER - notif_obj = self.pool.get('mail.notification') - notif_ids = notif_obj.search(cr, SUPERUSER_ID, [ - ('partner_id.user_ids', 'in', [uid]), - ('message_id.res_id', 'in', ids), - ('message_id.model', '=', self._name), - ('read', '=', False) - ], context=context) - for notif in notif_obj.browse(cr, SUPERUSER_ID, notif_ids, context=context): - res[notif.message_id.res_id]['message_unread'] = True + # search for unread messages, directly in SQL to improve performances + cr.execute(""" SELECT m.res_id FROM mail_message m + RIGHT JOIN mail_notification n + ON (n.message_id = m.id AND n.partner_id = %s AND n.read = False) + WHERE m.model = %s AND m.res_id in %s""", + (user_pid, self._name, tuple(ids),)) + msg_ids = [result[0] for result in cr.fetchall()] + for msg_id in msg_ids: + res[msg_id]['message_unread'] = True for thread in self.browse(cr, uid, ids, context=context): cls = res[thread.id]['message_unread'] and ' class="oe_kanban_mail_new"' or '' @@ -319,10 +319,12 @@ class mail_thread(osv.AbstractModel): """ assert isinstance(message, Message), 'message must be an email.message.Message at this point' message_id = message.get('Message-Id') + references = decode_header(message, 'References') + in_reply_to = decode_header(message, 'In-Reply-To') # 1. Verify if this is a reply to an existing thread - references = decode_header(message, 'References') or decode_header(message, 'In-Reply-To') - ref_match = references and tools.reference_re.search(references) + thread_references = references or in_reply_to + ref_match = thread_references and tools.reference_re.search(thread_references) if ref_match: thread_id = int(ref_match.group(1)) model = ref_match.group(2) or model @@ -333,6 +335,14 @@ class mail_thread(osv.AbstractModel): message_id, model, thread_id, custom_values, uid) return [(model, thread_id, custom_values, uid)] + # Verify this is a reply to a private message + message_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', '=', in_reply_to)], limit=1, context=context) + if message_ids: + message = self.pool.get('mail.message').browse(cr, uid, message_ids[0], context=context) + _logger.debug('Routing mail with Message-Id %s: direct reply to a private message: %s, custom_values: %s, uid: %s', + message_id, message.id, custom_values, uid) + return [(False, 0, custom_values, uid)] + # 2. Look for a matching mail.alias entry # Delivered-To is a safe bet in most modern MTAs, but we have to fallback on To + Cc values # for all the odd MTAs out there, as there is no standard header for the envelope's `rcpt_to` value. @@ -376,14 +386,19 @@ class mail_thread(osv.AbstractModel): def message_process(self, cr, uid, model, message, custom_values=None, save_original=False, strip_attachments=False, thread_id=None, context=None): - """Process an incoming RFC2822 email message, relying on - ``mail.message.parse()`` for the parsing operation, - and ``message_route()`` to figure out the target model. + """ Process an incoming RFC2822 email message, relying on + ``mail.message.parse()`` for the parsing operation, + and ``message_route()`` to figure out the target model. - Once the target model is known, its ``message_new`` method - is called with the new message (if the thread record did not exist) + Once the target model is known, its ``message_new`` method + is called with the new message (if the thread record did not exist) or its ``message_update`` method (if it did). + There is a special case where the target model is False: a reply + to a private message. In this case, we skip the message_new / + message_update step, to just post a new message using mail_thread + message_post. + :param string model: the fallback model to use if the message does not match any of the currently configured mail aliases (may be None if a matching alias is supposed to be present) @@ -425,15 +440,19 @@ class mail_thread(osv.AbstractModel): for model, thread_id, custom_values, user_id in routes: if self._name != model: context.update({'thread_model': model}) - model_pool = self.pool.get(model) - assert thread_id and hasattr(model_pool, 'message_update') or hasattr(model_pool, 'message_new'), \ - "Undeliverable mail with Message-Id %s, model %s does not accept incoming emails" % \ - (msg['message_id'], model) - if thread_id and hasattr(model_pool, 'message_update'): - model_pool.message_update(cr, user_id, [thread_id], msg, context=context) + if model: + model_pool = self.pool.get(model) + assert thread_id and hasattr(model_pool, 'message_update') or hasattr(model_pool, 'message_new'), \ + "Undeliverable mail with Message-Id %s, model %s does not accept incoming emails" % \ + (msg['message_id'], model) + if thread_id and hasattr(model_pool, 'message_update'): + model_pool.message_update(cr, user_id, [thread_id], msg, context=context) + else: + thread_id = model_pool.message_new(cr, user_id, msg, custom_values, context=context) else: - thread_id = model_pool.message_new(cr, user_id, msg, custom_values, context=context) - model_pool.message_post(cr, uid, [thread_id], context=context, **msg) + assert thread_id == 0, "Posting a message without model should be with a null res_id, to create a private message." + model_pool = self.pool.get('mail.thread') + model_pool.message_post_user_api(cr, uid, [thread_id], context=context, content_subtype='html', **msg) return thread_id def message_new(self, cr, uid, msg_dict, custom_values=None, context=None): @@ -501,7 +520,7 @@ class mail_thread(osv.AbstractModel): body = tools.ustr(body, encoding, errors='replace') if message.get_content_type() == 'text/plain': # text/plain ->
-                body = tools.append_content_to_html(u'', body)
+                body = tools.append_content_to_html(u'', body, preserve=True)
         else:
             alternative = (message.get_content_type() == 'multipart/alternative')
             for part in message.walk():
@@ -516,7 +535,7 @@ class mail_thread(osv.AbstractModel):
                 # 2) text/plain -> 
                 if part.get_content_type() == 'text/plain' and (not alternative or not body):
                     body = tools.append_content_to_html(body, tools.ustr(part.get_payload(decode=True),
-                                                                         encoding, errors='replace'))
+                                                                         encoding, errors='replace'), preserve=True)
                 # 3) text/html -> raw
                 elif part.get_content_type() == 'text/html':
                     html = tools.ustr(part.get_payload(decode=True), encoding, errors='replace')
@@ -556,7 +575,6 @@ class mail_thread(osv.AbstractModel):
         """
         msg_dict = {
             'type': 'email',
-            'subtype': 'mail.mt_comment',
             'author_id': False,
         }
         if not isinstance(message, Message):
@@ -588,7 +606,7 @@ class mail_thread(osv.AbstractModel):
             else:
                 msg_dict['email_from'] = message.get('from')
         partner_ids = self._message_find_partners(cr, uid, message, ['From', 'To', 'Cc'], context=context)
-        msg_dict['partner_ids'] = partner_ids
+        msg_dict['partner_ids'] = [(4, partner_id) for partner_id in partner_ids]
 
         if 'Date' in message:
             date_hdr = decode(message.get('Date'))
@@ -629,7 +647,8 @@ class mail_thread(osv.AbstractModel):
             mail.message ID. Extra keyword arguments will be used as default
             column values for the new mail.message record.
             Auto link messages for same id and object
-            :param int thread_id: thread ID to post into, or list with one ID
+            :param int thread_id: thread ID to post into, or list with one ID;
+                if False/0, mail.message model will also be set as False
             :param str body: body of the message, usually raw HTML that will
                 be sanitized
             :param str subject: optional subject
@@ -639,10 +658,13 @@ class mail_thread(osv.AbstractModel):
                 ``(name,content)``, where content is NOT base64 encoded
             :return: ID of newly created mail.message
         """
-        context = context or {}
-        attachments = attachments or []
+        if context is None:
+            context = {}
+        if attachments is None:
+            attachments = {}
+
         assert (not thread_id) or isinstance(thread_id, (int, long)) or \
-            (isinstance(thread_id, (list, tuple)) and len(thread_id) == 1), "Invalid thread_id"
+            (isinstance(thread_id, (list, tuple)) and len(thread_id) == 1), "Invalid thread_id; should be 0, False, an ID or a list with one ID"
         if isinstance(thread_id, (list, tuple)):
             thread_id = thread_id and thread_id[0]
         mail_message = self.pool.get('mail.message')
@@ -682,7 +704,6 @@ class mail_thread(osv.AbstractModel):
             # avoid loops when finding ancestors
             processed_list = []
             if message_ids:
-                _counter, _counter_max = 0, 200
                 message = mail_message.browse(cr, SUPERUSER_ID, message_ids[0], context=context)
                 while (message.parent_id and message.parent_id.id not in processed_list):
                     processed_list.append(message.parent_id.id)
@@ -707,18 +728,45 @@ class mail_thread(osv.AbstractModel):
 
         return mail_message.create(cr, uid, values, context=context)
 
-    def message_post_api(self, cr, uid, thread_id, body='', subject=False, parent_id=False, attachment_ids=None, context=None):
-        """ Wrapper on message_post, used only in Chatter (JS). The purpose is
-            to handle attachments.
-            # TDE FIXME: body is plaintext: convert it into html
+    def message_post_user_api(self, cr, uid, thread_id, body='', subject=False, parent_id=False,
+                                attachment_ids=None, context=None, content_subtype='plaintext', **kwargs):
+        """ Wrapper on message_post, used for user input :
+            - mail gateway
+            - quick reply in Chatter (refer to mail.js), not
+                the mail.compose.message wizard
+            The purpose is to perform some pre- and post-processing:
+            - if body is plaintext: convert it into html
+            - if parent_id: handle reply to a previous message by adding the
+                parent partners to the message
+            - type and subtype: comment and mail.mt_comment by default
+            - attachment_ids: supposed not attached to any document; attach them
+                to the related document. Should only be set by Chatter.
         """
-        new_message_id = self.message_post(cr, uid, thread_id=thread_id, body=body, subject=subject, type='comment',
-                        subtype='mail.mt_comment', parent_id=parent_id, context=context)
+        ir_attachment = self.pool.get('ir.attachment')
+        mail_message = self.pool.get('mail.message')
 
-        # HACK FIXME: Chatter: attachments linked to the document (not done JS-side), load the message
+        # 1. Pre-processing: body, partner_ids, type and subtype
+        if content_subtype == 'plaintext':
+            body = tools.plaintext2html(body)
+
+        partner_ids = kwargs.pop('partner_ids', [])
+        if parent_id:
+            parent_message = self.pool.get('mail.message').browse(cr, uid, parent_id, context=context)
+            partner_ids += [(4, partner.id) for partner in parent_message.partner_ids]
+            # TDE FIXME HACK: mail.thread -> private message
+            if self._name == 'mail.thread' and parent_message.author_id.id:
+                partner_ids.append((4, parent_message.author_id.id))
+
+        message_type = kwargs.pop('type', 'comment')
+        message_subtype = kwargs.pop('subtype', 'mail.mt_comment')
+
+        # 2. Post message
+        new_message_id = self.message_post(cr, uid, thread_id=thread_id, body=body, subject=subject, type=message_type,
+                        subtype=message_subtype, parent_id=parent_id, context=context, partner_ids=partner_ids, **kwargs)
+
+        # 3. Post-processing
+        # HACK TDE FIXME: Chatter: attachments linked to the document (not done JS-side), load the message
         if attachment_ids:
-            ir_attachment = self.pool.get('ir.attachment')
-            mail_message = self.pool.get('mail.message')
             filtered_attachment_ids = ir_attachment.search(cr, SUPERUSER_ID, [
                 ('res_model', '=', 'mail.compose.message'),
                 ('res_id', '=', 0),
diff --git a/addons/mail/mail_thread_view.xml b/addons/mail/mail_thread_view.xml
index 07b24725b05..3a37218c813 100644
--- a/addons/mail/mail_thread_view.xml
+++ b/addons/mail/mail_thread_view.xml
@@ -4,48 +4,126 @@
         
             Inbox
             mail.wall
-            
+            mail.message
+            {
+              'default_model': 'res.users', 
+              'default_res_id': uid,
+            }
+            
+            
+                

+ Good Job! Your inbox is empty. +

+ Your inbox contains private messages or emails sent to you + as well as information related to documents or people you + follow. +

+
To: me mail.wall - + mail.message + { + 'default_model': 'res.users', + 'default_res_id': uid, + 'search_default_message_unread': True + } + + +

+ No private message. +

+ This list contains messages sent to you. +

+
- Favorites + Todo mail.wall - + mail.message + { + 'default_model': 'res.users', + 'default_res_id': uid, + 'search_default_message_unread': True + } + + +

+ No todo! +

+ When you process messages in your inbox, you can mark some + as todo. From this menu, you can process all your todo. +

+
Archives mail.wall - + { + 'default_model': 'res.users', + 'default_res_id': uid, + 'search_default_message_read': True + } + + +

+ No message found. +

+
Sent mail.wall - + { + 'default_model': 'res.users', + 'default_res_id': uid + } + + +

+ No message sent yet. +

+ Click on the top-right icon to compose a message. This + message will be sent by email if it's an internal contact. +

+
- - + @@ -61,7 +139,7 @@ - Favorites + Todo diff --git a/addons/mail/res_partner.py b/addons/mail/res_partner.py index 6b13fe6ad20..060adc291de 100644 --- a/addons/mail/res_partner.py +++ b/addons/mail/res_partner.py @@ -42,4 +42,23 @@ class res_partner_mail(osv.Model): 'notification_email_send': lambda *args: 'comment' } + def message_post(self, cr, uid, thread_id, body='', subject=None, type='notification', + subtype=None, parent_id=False, attachments=None, context=None, **kwargs): + """ Override related to res.partner. In case of email message, set it as + private: + - add the target partner in the message partner_ids + - set thread_id as None, because this will trigger the 'private' + aspect of the message (model=False, res_id=False) + """ + if isinstance(thread_id, (list, tuple)): + thread_id = thread_id[0] + if type == 'email': + partner_ids = kwargs.get('partner_ids', []) + if thread_id not in partner_ids: + partner_ids.append((4, thread_id)) + kwargs['partner_ids'] = partner_ids + thread_id = False + return super(res_partner_mail, self).message_post(cr, uid, thread_id, body=body, subject=subject, + type=type, subtype=subtype, parent_id=parent_id, attachments=attachments, context=context, **kwargs) + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/mail/res_users.py b/addons/mail/res_users.py index 1b6388111ba..20c0715c148 100644 --- a/addons/mail/res_users.py +++ b/addons/mail/res_users.py @@ -91,8 +91,10 @@ class res_users(osv.Model): return user_id def _create_welcome_message(self, cr, uid, user, context=None): - company_name = user.company_id.name if user.company_id else _('the company') - body = _('%s has joined %s.') % (user.name, company_name) + if not self.has_group(cr, uid, 'base.group_user'): + return False + company_name = user.company_id.name if user.company_id else '' + body = _('%s has joined the %s network.') % (user.name, company_name) # TODO change SUPERUSER_ID into user.id but catch errors return self.pool.get('res.partner').message_post(cr, SUPERUSER_ID, [user.partner_id.id], body=body, context=context) @@ -111,7 +113,8 @@ class res_users(osv.Model): alias_pool.unlink(cr, uid, alias_ids, context=context) return res - def message_post_api(self, cr, uid, thread_id, body='', subject=False, parent_id=False, attachment_ids=None, context=None): + def message_post_user_api(self, cr, uid, thread_id, body='', subject=False, parent_id=False, + attachment_ids=None, context=None, content_subtype='plaintext', **kwargs): """ Redirect the posting of message on res.users to the related partner. This is done because when giving the context of Chatter on the various mailboxes, we do not have access to the current partner_id. @@ -122,8 +125,8 @@ class res_users(osv.Model): if isinstance(thread_id, (list, tuple)): thread_id = thread_id[0] partner_id = self.pool.get('res.users').read(cr, uid, thread_id, ['partner_id'], context=context)['partner_id'][0] - return self.pool.get('res.partner').message_post_api(cr, uid, partner_id, body=body, subject=subject, - parent_id=parent_id, attachment_ids=attachment_ids, context=context) + return self.pool.get('res.partner').message_post_user_api(cr, uid, partner_id, body=body, subject=subject, + parent_id=parent_id, attachment_ids=attachment_ids, context=context, content_subtype=content_subtype, **kwargs) def message_post(self, cr, uid, thread_id, context=None, **kwargs): """ Redirect the posting of message on res.users to the related partner. @@ -143,6 +146,7 @@ class res_users(osv.Model): return self.pool.get('res.partner').message_update(cr, uid, [partner_id], msg_dict, update_vals=update_vals, context=context) + class res_users_mail_group(osv.Model): """ Update of res.users class - if adding groups to an user, check mail.groups linked to this user diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv index 52c6c605109..b87c323b53d 100644 --- a/addons/mail/security/ir.model.access.csv +++ b/addons/mail/security/ir.model.access.csv @@ -7,12 +7,13 @@ access_mail_mail_system,mail.mail.system,model_mail_mail,base.group_system,1,1,1 access_mail_followers_all,mail.followers.all,model_mail_followers,,1,0,0,0 access_mail_followers_system,mail.followers.system,model_mail_followers,base.group_system,1,1,1,1 access_mail_notification_all,mail.notification.all,model_mail_notification,,1,0,0,0 -access_mail_notification_aystem,mail.notification.system,model_mail_notification,base.group_system,1,1,1,1 +access_mail_notification_group_user,mail.notification.user,model_mail_notification,base.group_user,1,1,1,0 +access_mail_notification_system,mail.notification.system,model_mail_notification,base.group_system,1,1,1,1 access_mail_group_all,mail.group.all,model_mail_group,,1,0,0,0 access_mail_group_user,mail.group.user,model_mail_group,base.group_user,1,1,1,1 access_mail_alias_all,mail.alias.all,model_mail_alias,,1,0,0,0 -access_mail_alias_user,mail.alias,model_mail_alias,base.group_user,1,1,1,0 -access_mail_alias_system,mail.alias,model_mail_alias,base.group_system,1,1,1,1 +access_mail_alias_user,mail.alias.user,model_mail_alias,base.group_user,1,1,1,0 +access_mail_alias_system,mail.alias.system,model_mail_alias,base.group_system,1,1,1,1 access_mail_message_subtype_all,mail.message.subtype.all,model_mail_message_subtype,,1,0,0,0 access_mail_vote_all,mail.vote.all,model_mail_vote,,1,1,1,1 -access_mail_favorite_all,mail.favorite.all,model_mail_favorite,,1,1,1,1 \ No newline at end of file +access_mail_favorite_all,mail.favorite.all,model_mail_favorite,,1,1,1,1 diff --git a/addons/mail/security/mail_security.xml b/addons/mail/security/mail_security.xml index 3f2b7bf3e9a..525425c2aa0 100644 --- a/addons/mail/security/mail_security.xml +++ b/addons/mail/security/mail_security.xml @@ -10,7 +10,7 @@ ['|', '|', ('public', '=', 'public'), ('message_follower_ids', 'in', [user.partner_id.id]), '&', ('public','=','groups'), ('group_public_id','in', [g.id for g in user.groups_id])] - + mail.followers: read and write its own entries [('partner_id', '=', user.partner_id.id)] @@ -18,6 +18,14 @@ + + mail.notification: read and write its own entries + + [('partner_id', '=', user.partner_id.id)] + + + + - + +
@@ -38,7 +39,8 @@
-