diff --git a/addons/crm/i18n/es_MX.po.moved b/addons/crm/i18n/es_MX.po.moved
deleted file mode 100644
index bba19b80731..00000000000
--- a/addons/crm/i18n/es_MX.po.moved
+++ /dev/null
@@ -1,3672 +0,0 @@
-# Translation of OpenERP Server.
-# This file contains the translation of the following modules:
-# * crm
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: OpenERP Server 6.0.2\n"
-"Report-Msgid-Bugs-To: support@openerp.com\n"
-"POT-Creation-Date: 2011-07-06 16:24+0000\n"
-"PO-Revision-Date: 2011-07-06 16:24+0000\n"
-"Last-Translator: <>\n"
-"Language-Team: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Plural-Forms: \n"
-
-#. module: crm
-#: view:crm.lead.report:0
-msgid "# Leads"
-msgstr "# Iniciativas"
-
-#. module: crm
-#: view:crm.lead:0
-#: selection:crm.lead,type:0
-#: selection:crm.lead.report,type:0
-msgid "Lead"
-msgstr "Iniciativa"
-
-#. module: crm
-#: model:crm.case.categ,name:crm.categ_oppor3
-msgid "Need Services"
-msgstr "Necesita servicios"
-
-#. module: crm
-#: selection:crm.meeting,rrule_type:0
-msgid "Monthly"
-msgstr "Mensual"
-
-#. module: crm
-#: view:crm.opportunity2phonecall:0
-msgid "Schedule a PhoneCall"
-msgstr "Planificar llamada telefónica"
-
-#. module: crm
-#: model:ir.model,name:crm.model_crm_case_stage
-msgid "Stage of case"
-msgstr "Etapa del caso"
-
-#. module: crm
-#: view:crm.meeting:0
-msgid "Visibility"
-msgstr "Visibilidad"
-
-#. module: crm
-#: field:crm.lead,title:0
-msgid "Title"
-msgstr "Título"
-
-#. module: crm
-#: field:crm.meeting,show_as:0
-msgid "Show as"
-msgstr "Mostrar como"
-
-#. module: crm
-#: field:crm.meeting,day:0
-#: selection:crm.meeting,select1:0
-msgid "Date of month"
-msgstr "Día del mes"
-
-#. module: crm
-#: view:crm.lead:0
-#: view:crm.phonecall:0
-msgid "Today"
-msgstr "Hoy"
-
-#. module: crm
-#: view:crm.merge.opportunity:0
-msgid "Select Opportunities"
-msgstr "Seleccionar oportunidades"
-
-#. module: crm
-#: view:crm.meeting:0
-#: view:crm.phonecall2opportunity:0
-#: view:crm.phonecall2phonecall:0
-#: view:crm.send.mail:0
-msgid " "
-msgstr " "
-
-#. module: crm
-#: view:crm.lead.report:0
-#: field:crm.phonecall.report,delay_close:0
-msgid "Delay to close"
-msgstr "Demora cierre"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Previous Stage"
-msgstr "Etapa anterior"
-
-#. module: crm
-#: code:addons/crm/wizard/crm_add_note.py:26
-#, python-format
-msgid "Can not add note!"
-msgstr "¡No se puede añadir una nota!"
-
-#. module: crm
-#: field:crm.case.stage,name:0
-msgid "Stage Name"
-msgstr "Nombre de etapa"
-
-#. module: crm
-#: view:crm.lead.report:0
-#: field:crm.lead.report,day:0
-#: view:crm.phonecall.report:0
-#: field:crm.phonecall.report,day:0
-msgid "Day"
-msgstr "Día"
-
-#. module: crm
-#: sql_constraint:crm.case.section:0
-msgid "The code of the sales team must be unique !"
-msgstr "¡El código del equipo de ventas debe ser único!"
-
-#. module: crm
-#: code:addons/crm/wizard/crm_lead_to_opportunity.py:95
-#, python-format
-msgid "Lead '%s' has been converted to an opportunity."
-msgstr "La inicaitiva '%s' ha sido convertida en oportunidad"
-
-#. module: crm
-#: code:addons/crm/crm_lead.py:228
-#, python-format
-msgid "The lead '%s' has been closed."
-msgstr "La iniciativa '%s' ha sido cerrada"
-
-#. module: crm
-#: selection:crm.meeting,freq:0
-msgid "No Repeat"
-msgstr "No repetir"
-
-#. module: crm
-#: code:addons/crm/wizard/crm_lead_to_opportunity.py:135
-#: code:addons/crm/wizard/crm_lead_to_opportunity.py:260
-#: code:addons/crm/wizard/crm_lead_to_partner.py:55
-#: code:addons/crm/wizard/crm_phonecall_to_partner.py:52
-#, python-format
-msgid "Warning !"
-msgstr "Advertencia !"
-
-#. module: crm
-#: selection:crm.meeting,rrule_type:0
-msgid "Yearly"
-msgstr "Anual"
-
-#. module: crm
-#: field:crm.segmentation.line,name:0
-msgid "Rule Name"
-msgstr "Nombre de regla"
-
-#. module: crm
-#: view:crm.case.resource.type:0
-#: view:crm.lead:0
-#: field:crm.lead,type_id:0
-#: view:crm.lead.report:0
-#: field:crm.lead.report,type_id:0
-#: model:ir.model,name:crm.model_crm_case_resource_type
-msgid "Campaign"
-msgstr "Campaña"
-
-#. module: crm
-#: selection:crm.lead2opportunity.partner,action:0
-msgid "Do not create a partner"
-msgstr "No crear una empresa"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Search Opportunities"
-msgstr "Busqueda de oportunidades"
-
-#. module: crm
-#: code:addons/crm/wizard/crm_merge_opportunities.py:46
-#, python-format
-msgid "Opportunity must have Partner assigned before merging with other Opportunity."
-msgstr "La oportunidad debe de tener una empresa asignada antes de fusionarla con otra oportunidad."
-
-#. module: crm
-#: code:addons/crm/wizard/crm_merge_opportunities.py:46
-#: code:addons/crm/wizard/crm_merge_opportunities.py:53
-#, python-format
-msgid "Warning!"
-msgstr "¡Aviso!"
-
-#. module: crm
-#: code:addons/crm/wizard/crm_send_email.py:72
-#: code:addons/crm/wizard/crm_send_email.py:169
-#: code:addons/crm/wizard/crm_send_email.py:270
-#, python-format
-msgid "Can not send mail!"
-msgstr "¡No se pudo enviar el correo!"
-
-#. module: crm
-#: field:crm.lead,partner_id:0
-#: view:crm.lead.report:0
-#: field:crm.lead.report,partner_id:0
-#: field:crm.lead2opportunity,partner_id:0
-#: field:crm.lead2opportunity.partner,partner_id:0
-#: field:crm.lead2partner,partner_id:0
-#: view:crm.meeting:0
-#: field:crm.meeting,partner_id:0
-#: field:crm.partner2opportunity,partner_id:0
-#: view:crm.phonecall:0
-#: field:crm.phonecall,partner_id:0
-#: view:crm.phonecall.report:0
-#: field:crm.phonecall.report,partner_id:0
-#: field:crm.phonecall2opportunity,partner_id:0
-#: field:crm.phonecall2partner,partner_id:0
-#: model:ir.model,name:crm.model_res_partner
-#: model:process.node,name:crm.process_node_partner0
-msgid "Partner"
-msgstr "Empresa"
-
-#. module: crm
-#: field:crm.meeting,organizer:0
-#: field:crm.meeting,organizer_id:0
-msgid "Organizer"
-msgstr "Organizador"
-
-#. module: crm
-#: view:crm.phonecall:0
-#: view:crm.phonecall2phonecall:0
-#: model:ir.actions.act_window,name:crm.phonecall_to_phonecall_act
-#: view:res.partner:0
-msgid "Schedule Other Call"
-msgstr "Planificar otra llamada"
-
-#. module: crm
-#: help:crm.meeting,edit_all:0
-msgid "Edit all Occurrences of recurrent Meeting."
-msgstr "Editar todas las ocurrencias de la reunión recurrente."
-
-#. module: crm
-#: code:addons/crm/wizard/crm_opportunity_to_phonecall.py:134
-#: code:addons/crm/wizard/crm_phonecall_to_phonecall.py:89
-#: model:crm.case.categ,name:crm.categ_meet3
-#: view:crm.phonecall:0
-#: model:ir.ui.menu,name:crm.menu_crm_config_phonecall
-#: view:res.partner:0
-#, python-format
-msgid "Phone Call"
-msgstr "Llamada de telefono"
-
-#. module: crm
-#: field:crm.lead,optout:0
-msgid "Opt-Out"
-msgstr "No acepta recibir emails"
-
-#. module: crm
-#: code:addons/crm/crm_opportunity.py:108
-#, python-format
-msgid "The opportunity '%s' has been marked as lost."
-msgstr "La oportunidad '%s' ha sido marcada como perdida."
-
-#. module: crm
-#: model:ir.actions.act_window,help:crm.action_report_crm_lead
-msgid "Leads Analysis allows you to check different CRM related information. Check for treatment delays, number of responses given and emails sent. You can sort out your leads analysis by different groups to get accurate grained analysis."
-msgstr "El análisis de iniciativas le permite verificar información relacionada con el CRM. Puede verificar los retrasos, el número de respuestas realizadas y el número de emails enviados. Puede ordenar el análisis de sus iniciativas según diferentes grupos para obtener un análisis reagrupado preciso."
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Send New Email"
-msgstr "Enviar nuevo email"
-
-#. module: crm
-#: field:crm.segmentation,segmentation_line:0
-msgid "Criteria"
-msgstr "Criterios"
-
-#. module: crm
-#: field:crm.case.stage,section_ids:0
-msgid "Sections"
-msgstr "Secciones"
-
-#. module: crm
-#: view:crm.merge.opportunity:0
-msgid "_Merge"
-msgstr "_Fusionar"
-
-#. module: crm
-#: view:crm.lead.report:0
-#: model:ir.actions.act_window,name:crm.action_report_crm_lead
-#: model:ir.ui.menu,name:crm.menu_report_crm_leads_tree
-msgid "Leads Analysis"
-msgstr "Análisis de iniciativas"
-
-#. module: crm
-#: view:crm.lead2opportunity.action:0
-msgid "If you select Merge with existing Opportunity, the lead details(with the communication history) will be merged with existing Opportunity of Selected partner."
-msgstr "Si selecciona fusionar con oportunidad existente, los detalles de la iniciativa (con el histórico de la comunicación) serán fusionados con la oportunidad existente de la empresa seleccionada"
-
-#. module: crm
-#: selection:crm.meeting,class:0
-msgid "Public"
-msgstr "Público"
-
-#. module: crm
-#: model:ir.actions.act_window,name:crm.crm_case_resource_type_act
-#: model:ir.ui.menu,name:crm.menu_crm_case_resource_type_act
-msgid "Campaigns"
-msgstr "Campañas"
-
-#. module: crm
-#: model:ir.actions.act_window,name:crm.crm_lead_categ_action
-#: model:ir.ui.menu,name:crm.menu_crm_case_phonecall-act
-#: model:ir.ui.menu,name:crm.menu_crm_lead_categ
-msgid "Categories"
-msgstr "Categorías"
-
-#. module: crm
-#: selection:crm.meeting,end_type:0
-msgid "Forever"
-msgstr "Siempre"
-
-#. module: crm
-#: help:crm.lead,optout:0
-msgid "If opt-out is checked, this contact has refused to receive emails or unsubscribed to a campaign."
-msgstr "Si opt-out está marcado, este contacto ha rehusado recibir correos electrónicos o ha eliminado su suscripción a una campaña."
-
-#. module: crm
-#: model:process.transition,name:crm.process_transition_leadpartner0
-msgid "Prospect Partner"
-msgstr "Socio prospecto"
-
-#. module: crm
-#: field:crm.lead,contact_name:0
-msgid "Contact Name"
-msgstr "Nombre de contacto"
-
-#. module: crm
-#: selection:crm.lead2opportunity.partner,action:0
-#: selection:crm.lead2partner,action:0
-#: selection:crm.phonecall2partner,action:0
-msgid "Link to an existing partner"
-msgstr "Enlace al socio existente"
-
-#. module: crm
-#: view:crm.lead:0
-#: view:crm.meeting:0
-#: field:crm.phonecall,partner_contact:0
-msgid "Contact"
-msgstr "Contacto"
-
-#. module: crm
-#: view:crm.installer:0
-msgid "Enhance your core CRM Application with additional functionalities."
-msgstr "Mejore su aplicación CRM básica con funcionalidades adicionales."
-
-#. module: crm
-#: field:crm.case.stage,on_change:0
-msgid "Change Probability Automatically"
-msgstr "Cambiar la probabilidad automáticamente"
-
-#. module: crm
-#: field:base.action.rule,regex_history:0
-msgid "Regular Expression on Case History"
-msgstr "Expresiones Regulares en el Historial del Caso"
-
-#. module: crm
-#: code:addons/crm/crm_lead.py:209
-#, python-format
-msgid "The lead '%s' has been opened."
-msgstr "La iniciativa '%s' ha sido abierta."
-
-#. module: crm
-#: model:process.transition,name:crm.process_transition_opportunitymeeting0
-msgid "Opportunity Meeting"
-msgstr "Oportunidad de Reunión"
-
-#. module: crm
-#: help:crm.lead.report,delay_close:0
-#: help:crm.phonecall.report,delay_close:0
-msgid "Number of Days to close the case"
-msgstr "Número de días para cerrar el caso"
-
-#. module: crm
-#: model:process.node,note:crm.process_node_opportunities0
-msgid "When a real project/opportunity is detected"
-msgstr "Cuando un proyecto/oportunidad real es detectado"
-
-#. module: crm
-#: field:crm.installer,crm_fundraising:0
-msgid "Fundraising"
-msgstr "Recaudación de fondos"
-
-#. module: crm
-#: view:res.partner:0
-#: field:res.partner,opportunity_ids:0
-msgid "Leads and Opportunities"
-msgstr "Iniciativas y oportunidades"
-
-#. module: crm
-#: view:crm.send.mail:0
-msgid "_Send"
-msgstr "_Enviar"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Communication"
-msgstr "Comunicación"
-
-#. module: crm
-#: field:crm.case.section,change_responsible:0
-msgid "Change Responsible"
-msgstr "Cambiar responsable"
-
-#. module: crm
-#: field:crm.merge.opportunity,state:0
-msgid "Set State To"
-msgstr "Cambiar estado a"
-
-#. module: crm
-#: model:ir.actions.act_window,help:crm.crm_case_categ_phone_outgoing0
-msgid "Outbound Calls list all the calls to be done by your sales team. A salesman can record the information about the call in the form view. This information will be stored in the partner form to trace every contact you have with a customer. You can also import a .CSV file with a list of calls to be done by your sales team."
-msgstr "Llamadas salientes muestra todas las llamadas realizadas por su equipo de ventas. Un vendedor puede grabar la información sobre la llamada en la vista de formulario. Esta información se almacenará en el formulario de empresa para rastrear cada contacto que tenga con un cliente. También puede importar un archivo .CSV con una lista de llamadas a realizar por su equipo de ventas."
-
-#. module: crm
-#: model:ir.model,name:crm.model_crm_lead2opportunity_action
-msgid "Convert/Merge Opportunity"
-msgstr "Convertir/Fusionar oportunidad"
-
-#. module: crm
-#: field:crm.lead,write_date:0
-msgid "Update Date"
-msgstr "Actualizar fecha"
-
-#. module: crm
-#: view:crm.lead2opportunity.action:0
-#: field:crm.lead2opportunity.action,name:0
-msgid "Select Action"
-msgstr "Seleccionar acción"
-
-#. module: crm
-#: field:base.action.rule,trg_categ_id:0
-#: view:crm.lead:0
-#: field:crm.lead,categ_id:0
-#: view:crm.lead.report:0
-#: field:crm.lead.report,categ_id:0
-#: field:crm.opportunity2phonecall,categ_id:0
-#: field:crm.phonecall,categ_id:0
-#: field:crm.phonecall.report,categ_id:0
-#: field:crm.phonecall2phonecall,categ_id:0
-msgid "Category"
-msgstr "Categoría"
-
-#. module: crm
-#: view:crm.lead.report:0
-msgid "#Opportunities"
-msgstr "# Oportunidades"
-
-#. module: crm
-#: model:crm.case.resource.type,name:crm.type_oppor2
-msgid "Campaign 1"
-msgstr "Campaña 1"
-
-#. module: crm
-#: model:crm.case.resource.type,name:crm.type_oppor1
-msgid "Campaign 2"
-msgstr "Campaña 2"
-
-#. module: crm
-#: view:crm.meeting:0
-msgid "Privacy"
-msgstr "Privada"
-
-#. module: crm
-#: view:crm.lead.report:0
-msgid "Opportunity Analysis"
-msgstr "Análisis de oportunidades"
-
-#. module: crm
-#: help:crm.meeting,location:0
-msgid "Location of Event"
-msgstr "Lacalización del evento"
-
-#. module: crm
-#: field:crm.meeting,rrule:0
-msgid "Recurrent Rule"
-msgstr "Regla recurrente"
-
-#. module: crm
-#: help:crm.installer,fetchmail:0
-msgid "Allows you to receive E-Mails from POP/IMAP server."
-msgstr "Le permite recibir e-mails desde un servidor POP/IMAP"
-
-#. module: crm
-#: model:process.transition,note:crm.process_transition_opportunitymeeting0
-msgid "Normal or phone meeting for opportunity"
-msgstr "Oportunidad de reunion normal ó telefonico."
-
-#. module: crm
-#: model:process.node,note:crm.process_node_leads0
-msgid "Very first contact with new prospect"
-msgstr "Primer contacto con nueva prospección"
-
-#. module: crm
-#: code:addons/crm/crm_lead.py:278
-#: code:addons/crm/wizard/crm_lead_to_opportunity.py:197
-#: code:addons/crm/wizard/crm_lead_to_opportunity.py:231
-#: code:addons/crm/wizard/crm_lead_to_opportunity.py:299
-#: view:crm.lead2opportunity:0
-#: view:crm.partner2opportunity:0
-#: model:ir.actions.act_window,name:crm.action_crm_lead2opportunity
-#: model:ir.actions.act_window,name:crm.action_view_crm_partner2opportunity
-#: model:ir.actions.act_window,name:crm.crm_partner2opportunity
-#, python-format
-msgid "Create Opportunity"
-msgstr "Crear oportunidad"
-
-#. module: crm
-#: view:crm.installer:0
-msgid "Configure"
-msgstr "Configurar"
-
-#. module: crm
-#: code:addons/crm/crm.py:378
-#: view:crm.lead:0
-#: view:res.partner:0
-#, python-format
-msgid "Escalate"
-msgstr "Escalado"
-
-#. module: crm
-#: model:ir.module.module,shortdesc:crm.module_meta_information
-msgid "Customer & Supplier Relationship Management"
-msgstr "Gestión de las relaciones con los clientes y proveedores"
-
-#. module: crm
-#: selection:crm.lead.report,month:0
-#: selection:crm.meeting,month_list:0
-#: selection:crm.phonecall.report,month:0
-msgid "June"
-msgstr "Junio"
-
-#. module: crm
-#: selection:crm.segmentation,state:0
-msgid "Not Running"
-msgstr "No ejecutado"
-
-#. module: crm
-#: view:crm.send.mail:0
-#: model:ir.actions.act_window,name:crm.action_crm_reply_mail
-msgid "Reply to last Mail"
-msgstr "Responder al último e-mail"
-
-#. module: crm
-#: field:crm.lead,email:0
-msgid "E-Mail"
-msgstr "E-mail"
-
-#. module: crm
-#: field:crm.installer,wiki_sale_faq:0
-msgid "Sale FAQ"
-msgstr "FAQ de ventas"
-
-#. module: crm
-#: model:ir.model,name:crm.model_crm_send_mail_attachment
-msgid "crm.send.mail.attachment"
-msgstr "crm.enviar.mail.adjunto"
-
-#. module: crm
-#: selection:crm.lead.report,month:0
-#: selection:crm.meeting,month_list:0
-#: selection:crm.phonecall.report,month:0
-msgid "October"
-msgstr "Octubre"
-
-#. module: crm
-#: help:crm.meeting,email_from:0
-#: help:crm.phonecall,email_from:0
-msgid "These people will receive email."
-msgstr "Estas personas recibirán un email."
-
-#. module: crm
-#: view:crm.meeting:0
-#: field:crm.meeting,name:0
-msgid "Summary"
-msgstr "Resumen"
-
-#. module: crm
-#: view:crm.segmentation:0
-msgid "State of Mind Computation"
-msgstr "Cálculo grado de satisfacción"
-
-#. module: crm
-#: help:crm.case.section,change_responsible:0
-msgid "Thick this box if you want that on escalation, the responsible of this sale team automatically becomes responsible of the lead/opportunity escaladed"
-msgstr "Marque esta opción si quiere que el responsable del equipo de ventas sea automáticamente responsable de la iniciativa/oportunidad, en caso de ser escalada."
-
-#. module: crm
-#: help:crm.installer,outlook:0
-#: help:crm.installer,thunderbird:0
-msgid "Allows you to link your e-mail to OpenERP's documents. You can attach it to any existing one in OpenERP or create a new one."
-msgstr "Permite enlazar su e-mail a la gestión documental de OpenERP. Puede adjuntarlo a cualquier documento ya existente en OpenERP o crear uno de nuevo."
-
-#. module: crm
-#: view:crm.case.categ:0
-msgid "Case Category"
-msgstr "Categoría del caso"
-
-#. module: crm
-#: help:crm.segmentation,som_interval_default:0
-msgid "Default state of mind for period preceeding the 'Max Interval' computation. This is the starting state of mind by default if the partner has no event."
-msgstr "Grado de satisfacción por defecto para el período que precede el cálculo de 'Intervalo máx.'. Este es el grado de satisfacción inicial por defecto si la empresa no tiene ningún evento."
-
-#. module: crm
-#: selection:crm.meeting,end_type:0
-msgid "End date"
-msgstr "Fecha de fin"
-
-#. module: crm
-#: constraint:base.action.rule:0
-msgid "Error: The mail is not well formated"
-msgstr "Error: El email no está bien formateado"
-
-#. module: crm
-#: view:crm.phonecall.report:0
-msgid "#Phone calls"
-msgstr "#Llamadas telefónicas"
-
-#. module: crm
-#: help:crm.segmentation,categ_id:0
-msgid "The partner category that will be added to partners that match the segmentation criterions after computation."
-msgstr "La categoría de empresas que será añadida a las empresas que cumplan los criterios de segmentación después del cálculo."
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Communication history"
-msgstr "Historial de comunicación"
-
-#. module: crm
-#: help:crm.phonecall,canal_id:0
-msgid "The channels represent the different communication modes available with the customer. With each commercial opportunity, you can indicate the canall which is this opportunity source."
-msgstr "Los canales representan los diferentes modos de comunicación posibles con el cliente. En cada oportunidad comercial, puede indicar el canal que ha sido el origen de esta oportunidad."
-
-#. module: crm
-#: code:addons/crm/crm_meeting.py:93
-#, python-format
-msgid "The meeting '%s' has been confirmed."
-msgstr "La reunión '%s' ha sido confirmada"
-
-#. module: crm
-#: field:crm.case.section,user_id:0
-msgid "Responsible User"
-msgstr "Usuario responsable"
-
-#. module: crm
-#: code:addons/crm/wizard/crm_phonecall_to_partner.py:53
-#, python-format
-msgid "A partner is already defined on this phonecall."
-msgstr "Una empresa ya esta definida para esta llamada."
-
-#. module: crm
-#: help:crm.case.section,reply_to:0
-msgid "The email address put in the 'Reply-To' of all emails sent by OpenERP about cases in this sales team"
-msgstr "La dirección de correo electrónico usada como \"Responder a\" de todos los correos electrónicos enviados por OpenERP para los casos de este equipo de ventas."
-
-#. module: crm
-#: view:res.users:0
-msgid "Current Activity"
-msgstr "Actividad actual"
-
-#. module: crm
-#: help:crm.meeting,exrule:0
-msgid "Defines a rule or repeating pattern of time to exclude from the recurring rule."
-msgstr "Define una regla o patrón de repetición de tiempo a excluir de la regla recurrente."
-
-#. module: crm
-#: field:crm.case.section,resource_calendar_id:0
-msgid "Working Time"
-msgstr "Horario de trabajo"
-
-#. module: crm
-#: view:crm.segmentation.line:0
-msgid "Partner Segmentation Lines"
-msgstr "Líneas de segmentación de empresa"
-
-#. module: crm
-#: view:crm.lead:0
-#: view:crm.meeting:0
-msgid "Details"
-msgstr "Detalles"
-
-#. module: crm
-#: help:crm.installer,crm_caldav:0
-msgid "Helps you to synchronize the meetings with other calendar clients and mobiles."
-msgstr "Le permite sincronizar las reuniones con otros clientes de calendario y móviles."
-
-#. module: crm
-#: selection:crm.meeting,freq:0
-msgid "Years"
-msgstr "Años"
-
-#. module: crm
-#: help:crm.installer,crm_claim:0
-msgid "Manages the suppliers and customers claims, including your corrective or preventive actions."
-msgstr "Gestiona las reclamaciones de clientes y proveedores, incluyendo acciones correctivas o preventivas"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Leads Form"
-msgstr "Formulario de iniciativas"
-
-#. module: crm
-#: view:crm.segmentation:0
-#: model:ir.model,name:crm.model_crm_segmentation
-msgid "Partner Segmentation"
-msgstr "Segmentación de empresa"
-
-#. module: crm
-#: model:ir.actions.act_window,help:crm.crm_case_categ_meet
-msgid "The meeting calendar is shared between the sales teams and fully integrated with other applications such as the employee holidays or the business opportunities. You can also synchronize meetings with your mobile phone using the caldav interface.\n"
-" "
-msgstr "Las citas del calendario estan compartidas entre el equipo de ventas e integradas con otras aplicaciones como los dias festivos de los empleados o las oportunidades de negocio. Usted puede tambien sincronizar citas con su telefono movil usando la interfaz caldav.\n"
-" "
-
-#. module: crm
-#: field:crm.lead.report,probable_revenue:0
-msgid "Probable Revenue"
-msgstr "Ingreso estimado"
-
-#. module: crm
-#: help:crm.segmentation,name:0
-msgid "The name of the segmentation."
-msgstr "El nombre de la segmentación."
-
-#. module: crm
-#: field:crm.case.stage,probability:0
-#: field:crm.lead,probability:0
-msgid "Probability (%)"
-msgstr "Probabilidad (%)"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Leads Generation"
-msgstr "Generación de iniciativas"
-
-#. module: crm
-#: view:board.board:0
-#: model:ir.ui.menu,name:crm.menu_board_statistics_dash
-msgid "Statistics Dashboard"
-msgstr "Tablero de estadísticas"
-
-#. module: crm
-#: code:addons/crm/wizard/crm_lead_to_opportunity.py:88
-#: code:addons/crm/wizard/crm_lead_to_opportunity.py:98
-#: code:addons/crm/wizard/crm_partner_to_opportunity.py:101
-#: code:addons/crm/wizard/crm_phonecall_to_opportunity.py:117
-#: view:crm.lead:0
-#: selection:crm.lead,type:0
-#: selection:crm.lead.report,type:0
-#: field:crm.lead2opportunity,name:0
-#: field:crm.meeting,opportunity_id:0
-#: field:crm.phonecall,opportunity_id:0
-#, python-format
-msgid "Opportunity"
-msgstr "Oportunidad"
-
-#. module: crm
-#: model:crm.case.resource.type,name:crm.type_lead7
-msgid "Television"
-msgstr "Televisión"
-
-#. module: crm
-#: field:crm.installer,crm_caldav:0
-msgid "Calendar Synchronizing"
-msgstr "Sincronización del calendario"
-
-#. module: crm
-#: view:crm.segmentation:0
-msgid "Stop Process"
-msgstr "Parar el proceso"
-
-#. module: crm
-#: view:crm.phonecall:0
-msgid "Search Phonecalls"
-msgstr "Buscar llamadas"
-
-#. module: crm
-#: view:crm.lead2opportunity.partner:0
-#: view:crm.lead2partner:0
-#: view:crm.phonecall2partner:0
-msgid "Continue"
-msgstr "Siguiente"
-
-#. module: crm
-#: field:crm.segmentation,som_interval:0
-msgid "Days per Period"
-msgstr "Días por período"
-
-#. module: crm
-#: field:crm.meeting,byday:0
-msgid "By day"
-msgstr "Por día"
-
-#. module: crm
-#: field:base.action.rule,act_section_id:0
-msgid "Set Team to"
-msgstr "Establecer equipo a"
-
-#. module: crm
-#: view:calendar.attendee:0
-#: field:calendar.attendee,categ_id:0
-msgid "Event Type"
-msgstr "Tipo de evento"
-
-#. module: crm
-#: model:ir.model,name:crm.model_crm_installer
-msgid "crm.installer"
-msgstr "crm.instalador"
-
-#. module: crm
-#: field:crm.segmentation,exclusif:0
-msgid "Exclusive"
-msgstr "Exclusivo"
-
-#. module: crm
-#: code:addons/crm/crm_opportunity.py:91
-#, python-format
-msgid "The opportunity '%s' has been won."
-msgstr "La oportunidad '%s' ha sido ganada"
-
-#. module: crm
-#: help:crm.meeting,alarm_id:0
-msgid "Set an alarm at this time, before the event occurs"
-msgstr "Configure una alarma en este momento, antes de que ocurra el evento"
-
-#. module: crm
-#: field:crm.lead.report,create_date:0
-#: field:crm.phonecall.report,create_date:0
-msgid "Create Date"
-msgstr "Fecha creación"
-
-#. module: crm
-#: field:crm.lead,ref2:0
-msgid "Reference 2"
-msgstr "Referencia 2"
-
-#. module: crm
-#: view:crm.segmentation:0
-msgid "Sales Purchase"
-msgstr "Compra Ventas"
-
-#. module: crm
-#: view:crm.case.stage:0
-#: field:crm.case.stage,requirements:0
-msgid "Requirements"
-msgstr "Requerimientos"
-
-#. module: crm
-#: help:crm.meeting,exdate:0
-msgid "This property defines the list of date/time exceptions for a recurring calendar component."
-msgstr "Esta propiedad define la lista de excepciones de fecha/hora para un evento de calendario recurrente."
-
-#. module: crm
-#: view:crm.phonecall2opportunity:0
-msgid "Convert To Opportunity "
-msgstr "Convertir a oportunidad "
-
-#. module: crm
-#: help:crm.case.stage,sequence:0
-msgid "Gives the sequence order when displaying a list of case stages."
-msgstr "Indica el orden de secuencia cuando se muestra un lista de etapas de casos."
-
-#. module: crm
-#: view:crm.lead:0
-#: field:crm.merge.opportunity,opportunity_ids:0
-#: model:ir.actions.act_window,name:crm.crm_case_category_act_oppor11
-#: model:ir.ui.menu,name:crm.menu_crm_case_opp
-#: model:process.node,name:crm.process_node_opportunities0
-msgid "Opportunities"
-msgstr "Oportunidades"
-
-#. module: crm
-#: field:crm.segmentation,categ_id:0
-msgid "Partner Category"
-msgstr "Categoría de empresa"
-
-#. module: crm
-#: view:crm.add.note:0
-#: model:ir.actions.act_window,name:crm.action_crm_add_note
-msgid "Add Note"
-msgstr "Añadir nota"
-
-#. module: crm
-#: field:crm.lead,is_supplier_add:0
-msgid "Supplier"
-msgstr "Proveedor"
-
-#. module: crm
-#: help:crm.send.mail,reply_to:0
-msgid "Reply-to of the Sales team defined on this case"
-msgstr "\"Responder a\" del equipo de ventas definido en este caso"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Mark Won"
-msgstr "Marcar ganado"
-
-#. module: crm
-#: selection:crm.segmentation.line,expr_name:0
-msgid "Purchase Amount"
-msgstr "Importe de compra"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Mark Lost"
-msgstr "Marcar perdido"
-
-#. module: crm
-#: selection:crm.lead.report,month:0
-#: selection:crm.meeting,month_list:0
-#: selection:crm.phonecall.report,month:0
-msgid "March"
-msgstr "Marzo"
-
-#. module: crm
-#: code:addons/crm/crm_lead.py:230
-#, python-format
-msgid "The opportunity '%s' has been closed."
-msgstr "La oportunidad '%s' ha sido cerrada"
-
-#. module: crm
-#: field:crm.lead,day_open:0
-msgid "Days to Open"
-msgstr "Días para abrir"
-
-#. module: crm
-#: view:crm.meeting:0
-msgid "Show Time as"
-msgstr "Mostrar hora como"
-
-#. module: crm
-#: code:addons/crm/crm_lead.py:264
-#: view:crm.phonecall2partner:0
-#, python-format
-msgid "Create Partner"
-msgstr "Crear empresa"
-
-#. module: crm
-#: selection:crm.segmentation.line,expr_operator:0
-msgid "<"
-msgstr "<"
-
-#. module: crm
-#: field:crm.lead,mobile:0
-#: field:crm.phonecall,partner_mobile:0
-msgid "Mobile"
-msgstr "Móvil"
-
-#. module: crm
-#: field:crm.meeting,end_type:0
-msgid "Way to end reccurency"
-msgstr "Forma de terminar recurrencia"
-
-#. module: crm
-#: code:addons/crm/wizard/crm_merge_opportunities.py:53
-#, python-format
-msgid "There are no other 'Open' or 'Pending' Opportunities for the partner '%s'."
-msgstr "No existen más oportunidades 'Abiertas' o 'Pendientes' para la empresa '%s'."
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Next Stage"
-msgstr "Siguiente etapa"
-
-#. module: crm
-#: view:board.board:0
-msgid "My Meetings"
-msgstr "Mis reuniones"
-
-#. module: crm
-#: field:crm.lead,ref:0
-msgid "Reference"
-msgstr "Referencia"
-
-#. module: crm
-#: field:crm.lead,optin:0
-msgid "Opt-In"
-msgstr "Acepta recibir emails"
-
-#. module: crm
-#: code:addons/crm/crm_opportunity.py:208
-#: code:addons/crm/crm_phonecall.py:185
-#: code:addons/crm/wizard/crm_phonecall_to_meeting.py:55
-#: code:addons/crm/wizard/crm_phonecall_to_meeting.py:137
-#: view:crm.meeting:0
-#: model:ir.actions.act_window,name:crm.act_crm_opportunity_crm_meeting_new
-#: model:ir.actions.act_window,name:crm.crm_case_categ_meet
-#: model:ir.ui.menu,name:crm.menu_crm_case_categ_meet
-#: model:ir.ui.menu,name:crm.menu_meeting_sale
-#: view:res.partner:0
-#: field:res.partner,meeting_ids:0
-#, python-format
-msgid "Meetings"
-msgstr "Reuniones"
-
-#. module: crm
-#: view:crm.meeting:0
-msgid "Choose day where repeat the meeting"
-msgstr "Eligir día en el que repetir la cita"
-
-#. module: crm
-#: field:crm.lead,date_action_next:0
-#: field:crm.lead,title_action:0
-#: field:crm.meeting,date_action_next:0
-#: field:crm.phonecall,date_action_next:0
-msgid "Next Action"
-msgstr "Acción siguiente"
-
-#. module: crm
-#: field:crm.meeting,end_date:0
-msgid "Repeat Until"
-msgstr "Repetir hasta"
-
-#. module: crm
-#: field:crm.meeting,date_deadline:0
-msgid "Deadline"
-msgstr "Fecha límite"
-
-#. module: crm
-#: help:crm.meeting,active:0
-msgid "If the active field is set to true, it will allow you to hide the event alarm information without removing it."
-msgstr "Si el campo activo se establece en verdadero, se omitirá la alarma del evento, sin embargo no se eliminará."
-
-#. module: crm
-#: code:addons/crm/wizard/crm_phonecall_to_opportunity.py:57
-#, python-format
-msgid "Closed/Cancelled Phone Call Could not convert into Opportunity"
-msgstr "Las llamadas telefónicas cerradas/canceladas no podrían ser convertidas en oportunidades"
-
-#. module: crm
-#: view:crm.segmentation:0
-msgid "Partner Segmentations"
-msgstr "Segmentaciones de empresa"
-
-#. module: crm
-#: view:crm.meeting:0
-#: field:crm.meeting,user_id:0
-#: view:crm.phonecall:0
-#: field:crm.phonecall,user_id:0
-#: view:res.partner:0
-msgid "Responsible"
-msgstr "Responsable"
-
-#. module: crm
-#: view:res.partner:0
-msgid "Previous"
-msgstr "Anterior"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Statistics"
-msgstr "Estadísticas"
-
-#. module: crm
-#: view:crm.meeting:0
-#: field:crm.send.mail,email_from:0
-msgid "From"
-msgstr "De"
-
-#. module: crm
-#: view:crm.lead2opportunity.action:0
-#: view:res.partner:0
-msgid "Next"
-msgstr "Siguiente"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Stage:"
-msgstr "Etapa:"
-
-#. module: crm
-#: model:crm.case.stage,name:crm.stage_lead5
-#: model:crm.case.stage,name:crm.stage_opportunity5
-#: view:crm.lead:0
-msgid "Won"
-msgstr "Ganado"
-
-#. module: crm
-#: field:crm.lead.report,delay_expected:0
-msgid "Overpassed Deadline"
-msgstr "Fecha límite sobrepasada"
-
-#. module: crm
-#: model:crm.case.section,name:crm.section_sales_department
-msgid "Sales Department"
-msgstr "Departamento de ventas"
-
-#. module: crm
-#: field:crm.send.mail,html:0
-msgid "HTML formatting?"
-msgstr "Formato HTML?"
-
-#. module: crm
-#: field:crm.case.stage,type:0
-#: field:crm.lead,type:0
-#: field:crm.lead.report,type:0
-#: view:crm.meeting:0
-#: view:crm.phonecall:0
-#: view:crm.phonecall.report:0
-#: view:res.partner:0
-msgid "Type"
-msgstr "Tipo"
-
-#. module: crm
-#: view:crm.segmentation:0
-msgid "Compute Segmentation"
-msgstr "Calcular la segmentación"
-
-#. module: crm
-#: selection:crm.lead,priority:0
-#: selection:crm.lead.report,priority:0
-#: selection:crm.phonecall,priority:0
-#: selection:crm.phonecall.report,priority:0
-msgid "Lowest"
-msgstr "Muy bajo"
-
-#. module: crm
-#: view:crm.add.note:0
-#: view:crm.send.mail:0
-#: field:crm.send.mail.attachment,binary:0
-msgid "Attachment"
-msgstr "Adjunto"
-
-#. module: crm
-#: selection:crm.lead.report,month:0
-#: selection:crm.meeting,month_list:0
-#: selection:crm.phonecall.report,month:0
-msgid "August"
-msgstr "Agosto"
-
-#. module: crm
-#: view:crm.lead:0
-#: field:crm.lead,create_date:0
-#: field:crm.lead.report,creation_date:0
-#: field:crm.meeting,create_date:0
-#: field:crm.phonecall,create_date:0
-#: field:crm.phonecall.report,creation_date:0
-msgid "Creation Date"
-msgstr "Fecha creación"
-
-#. module: crm
-#: model:crm.case.categ,name:crm.categ_oppor5
-msgid "Need a Website Design"
-msgstr "Necesita un diseño de sitio web"
-
-#. module: crm
-#: field:crm.meeting,recurrent_uid:0
-msgid "Recurrent ID"
-msgstr "ID recurrente"
-
-#. module: crm
-#: view:crm.lead:0
-#: view:crm.meeting:0
-#: field:crm.send.mail,subject:0
-#: view:res.partner:0
-msgid "Subject"
-msgstr "Asunto"
-
-#. module: crm
-#: field:crm.meeting,tu:0
-msgid "Tue"
-msgstr "Mar"
-
-#. module: crm
-#: code:addons/crm/crm_lead.py:300
-#: view:crm.case.stage:0
-#: view:crm.lead:0
-#: field:crm.lead,stage_id:0
-#: view:crm.lead.report:0
-#: field:crm.lead.report,stage_id:0
-#, python-format
-msgid "Stage"
-msgstr "Etapa"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "History Information"
-msgstr "Histórico información"
-
-#. module: crm
-#: field:base.action.rule,act_mail_to_partner:0
-msgid "Mail to Partner"
-msgstr "Correo a la empresa"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Mailings"
-msgstr "Correos"
-
-#. module: crm
-#: field:crm.meeting,class:0
-msgid "Mark as"
-msgstr "Marcar como"
-
-#. module: crm
-#: field:crm.meeting,count:0
-msgid "Repeat"
-msgstr "Repetir"
-
-#. module: crm
-#: help:crm.meeting,rrule_type:0
-msgid "Let the event automatically repeat at that interval"
-msgstr "Permite que el evento se repita en ese intervalo"
-
-#. module: crm
-#: view:base.action.rule:0
-msgid "Condition Case Fields"
-msgstr "Campos de condiciones de casos"
-
-#. module: crm
-#: model:ir.module.module,description:crm.module_meta_information
-msgid "The generic OpenERP Customer Relationship Management\n"
-"system enables a group of people to intelligently and efficiently manage\n"
-"leads, opportunities, meeting, phonecall etc.\n"
-"It manages key tasks such as communication, identification, prioritization,\n"
-"assignment, resolution and notification.\n"
-"\n"
-"OpenERP ensures that all cases are successfully tracked by users, customers and\n"
-"suppliers. It can automatically send reminders, escalate the request, trigger\n"
-"specific methods and lots of other actions based on your own enterprise rules.\n"
-"\n"
-"The greatest thing about this system is that users don't need to do anything\n"
-"special. They can just send email to the request tracker. OpenERP will take\n"
-"care of thanking them for their message, automatically routing it to the\n"
-"appropriate staff, and make sure all future correspondence gets to the right\n"
-"place.\n"
-"\n"
-"The CRM module has a email gateway for the synchronisation interface\n"
-"between mails and OpenERP.\n"
-"Create dashboard for CRM that includes:\n"
-" * My Leads (list)\n"
-" * Leads by Stage (graph)\n"
-" * My Meetings (list)\n"
-" * Sales Pipeline by Stage (graph)\n"
-" * My Cases (list)\n"
-" * Jobs Tracking (graph)\n"
-""
-msgstr "El sistema de Gestion de Relaciones con Cliente en OpenERP \n"
-"le permite a un grupo de personas \n"
-"leads, opportunities, meeting, phonecall etc.\n"
-"It manages key tasks such as communication, identification, prioritization,\n"
-"assignment, resolution and notification.\n"
-"\n"
-"OpenERP ensures that all cases are successfully tracked by users, customers and\n"
-"suppliers. It can automatically send reminders, escalate the request, trigger\n"
-"specific methods and lots of other actions based on your own enterprise rules.\n"
-"\n"
-"The greatest thing about this system is that users don't need to do anything\n"
-"special. They can just send email to the request tracker. OpenERP will take\n"
-"care of thanking them for their message, automatically routing it to the\n"
-"appropriate staff, and make sure all future correspondence gets to the right\n"
-"place.\n"
-"\n"
-"The CRM module has a email gateway for the synchronisation interface\n"
-"between mails and OpenERP.\n"
-"Create dashboard for CRM that includes:\n"
-" * My Leads (list)\n"
-" * Leads by Stage (graph)\n"
-" * My Meetings (list)\n"
-" * Sales Pipeline by Stage (graph)\n"
-" * My Cases (list)\n"
-" * Jobs Tracking (graph)\n"
-""
-
-#. module: crm
-#: view:crm.case.section:0
-#: field:crm.case.section,stage_ids:0
-#: view:crm.case.stage:0
-#: model:ir.actions.act_window,name:crm.crm_case_stage_act
-#: model:ir.actions.act_window,name:crm.crm_lead_stage_act
-#: model:ir.actions.act_window,name:crm.crm_opportunity_stage_act
-#: model:ir.ui.menu,name:crm.menu_crm_lead_stage_act
-#: model:ir.ui.menu,name:crm.menu_crm_opportunity_stage_act
-msgid "Stages"
-msgstr "Etapas"
-
-#. module: crm
-#: field:crm.lead,planned_revenue:0
-#: field:crm.lead2opportunity,planned_revenue:0
-#: field:crm.partner2opportunity,planned_revenue:0
-#: field:crm.phonecall2opportunity,planned_revenue:0
-msgid "Expected Revenue"
-msgstr "Ingreso estimado"
-
-#. module: crm
-#: model:ir.actions.act_window,help:crm.crm_phonecall_categ_action
-msgid "Create specific phone call categories to better define the type of calls tracked in the system."
-msgstr "Crear categorías específicas de llamada telefónica para definir mejor el tipo de llamadas en el sistema de seguimiento."
-
-#. module: crm
-#: selection:crm.lead.report,month:0
-#: selection:crm.meeting,month_list:0
-#: selection:crm.phonecall.report,month:0
-msgid "September"
-msgstr "Setiembre"
-
-#. module: crm
-#: field:crm.segmentation,partner_id:0
-msgid "Max Partner ID processed"
-msgstr "Máx ID de empresa procesado"
-
-#. module: crm
-#: model:ir.actions.act_window,name:crm.action_report_crm_phonecall
-#: model:ir.ui.menu,name:crm.menu_report_crm_phonecalls_tree
-msgid "Phone Calls Analysis"
-msgstr "Análisis de llamadas"
-
-#. module: crm
-#: field:crm.lead.report,opening_date:0
-#: field:crm.phonecall.report,opening_date:0
-msgid "Opening Date"
-msgstr "Fecha de apertura"
-
-#. module: crm
-#: help:crm.phonecall,duration:0
-msgid "Duration in Minutes"
-msgstr "Duración en minutos"
-
-#. module: crm
-#: help:crm.installer,crm_helpdesk:0
-msgid "Manages a Helpdesk service."
-msgstr "Gestiona un servicio de soporte."
-
-#. module: crm
-#: field:crm.partner2opportunity,name:0
-msgid "Opportunity Name"
-msgstr "Nombre oportunidad"
-
-#. module: crm
-#: help:crm.case.section,active:0
-msgid "If the active field is set to true, it will allow you to hide the sales team without removing it."
-msgstr "Si el campo activo se marca, permite ocultar el equipo de ventas sin eliminarlo."
-
-#. module: crm
-#: view:crm.lead.report:0
-#: view:crm.phonecall.report:0
-msgid " Year "
-msgstr " Año "
-
-#. module: crm
-#: field:crm.meeting,edit_all:0
-msgid "Edit All"
-msgstr "Editar todo"
-
-#. module: crm
-#: field:crm.meeting,fr:0
-msgid "Fri"
-msgstr "Vie"
-
-#. module: crm
-#: model:ir.model,name:crm.model_crm_lead
-msgid "crm.lead"
-msgstr "crm.iniciativa"
-
-#. module: crm
-#: field:crm.meeting,write_date:0
-msgid "Write Date"
-msgstr "Fecha escritura"
-
-#. module: crm
-#: view:crm.meeting:0
-msgid "End of Recurrency"
-msgstr "Fin de recurrencia"
-
-#. module: crm
-#: view:crm.meeting:0
-msgid "Reminder"
-msgstr "Recordatorio"
-
-#. module: crm
-#: help:crm.segmentation,sales_purchase_active:0
-msgid "Check if you want to use this tab as part of the segmentation rule. If not checked, the criteria beneath will be ignored"
-msgstr "Márquela si quiere utilizar esta pestaña como parte de la regla de segmentación. Si no se marca, los criterios que contenga serán ignorados"
-
-#. module: crm
-#: view:crm.lead2opportunity.partner:0
-#: view:crm.lead2partner:0
-#: view:crm.phonecall:0
-#: view:crm.phonecall2partner:0
-#: model:ir.actions.act_window,name:crm.action_crm_lead2partner
-#: model:ir.actions.act_window,name:crm.action_crm_phonecall2partner
-#: view:res.partner:0
-msgid "Create a Partner"
-msgstr "Crear una empresa"
-
-#. module: crm
-#: field:crm.segmentation,state:0
-msgid "Execution Status"
-msgstr "Estado ejecución"
-
-#. module: crm
-#: selection:crm.meeting,week_list:0
-msgid "Monday"
-msgstr "Lunes"
-
-#. module: crm
-#: field:crm.lead,day_close:0
-msgid "Days to Close"
-msgstr "Días para el cierre"
-
-#. module: crm
-#: field:crm.add.note,attachment_ids:0
-#: field:crm.case.section,complete_name:0
-#: field:crm.send.mail,attachment_ids:0
-msgid "unknown"
-msgstr "desconocido"
-
-#. module: crm
-#: field:crm.lead,id:0
-#: field:crm.meeting,id:0
-#: field:crm.phonecall,id:0
-msgid "ID"
-msgstr "ID"
-
-#. module: crm
-#: model:ir.model,name:crm.model_crm_partner2opportunity
-msgid "Partner To Opportunity"
-msgstr "Empresa a oportunidad"
-
-#. module: crm
-#: view:crm.meeting:0
-#: field:crm.meeting,date:0
-#: field:crm.opportunity2phonecall,date:0
-#: view:crm.phonecall:0
-#: field:crm.phonecall,date:0
-#: field:crm.phonecall2phonecall,date:0
-#: view:res.partner:0
-msgid "Date"
-msgstr "Fecha"
-
-#. module: crm
-#: view:crm.lead:0
-#: view:crm.lead.report:0
-#: view:crm.meeting:0
-#: view:crm.phonecall.report:0
-msgid "Extended Filters..."
-msgstr "Filtros extendidos..."
-
-#. module: crm
-#: field:crm.phonecall2opportunity,name:0
-msgid "Opportunity Summary"
-msgstr "Resumen oportunidad"
-
-#. module: crm
-#: view:crm.phonecall.report:0
-msgid "Search"
-msgstr "Buscar"
-
-#. module: crm
-#: view:board.board:0
-msgid "Opportunities by Categories"
-msgstr "Oportunidades por categorías"
-
-#. module: crm
-#: view:crm.meeting:0
-msgid "Choose day in the month where repeat the meeting"
-msgstr "Elija el día del mes en que se repetirá la cita."
-
-#. module: crm
-#: view:crm.lead:0
-#: view:res.partner:0
-msgid "History"
-msgstr "Historial"
-
-#. module: crm
-#: model:ir.actions.act_window,help:crm.crm_segmentation_tree-act
-msgid "Create specific partner categories which you can assign to your partners to better manage your interactions with them. The segmentation tool is able to assign categories to partners according to criteria you set."
-msgstr "Cree categorías de empresa específicas para gestionar mejor sus interacciones con ellas. La herramienta de segmentación es capaz de asignar categorías a empresas de acuerdo a los criterios que establezca."
-
-#. module: crm
-#: field:crm.case.section,code:0
-msgid "Code"
-msgstr "Código"
-
-#. module: crm
-#: field:crm.case.section,child_ids:0
-msgid "Child Teams"
-msgstr "Equipos hijos"
-
-#. module: crm
-#: view:crm.lead:0
-#: field:crm.lead,state:0
-#: view:crm.lead.report:0
-#: field:crm.lead.report,state:0
-#: view:crm.meeting:0
-#: field:crm.meeting,state:0
-#: field:crm.phonecall,state:0
-#: view:crm.phonecall.report:0
-#: field:crm.phonecall.report,state:0
-msgid "State"
-msgstr "Estado"
-
-#. module: crm
-#: model:crm.case.resource.type,name:crm.type_lead1
-msgid "Telesales"
-msgstr "Telesales"
-
-#. module: crm
-#: field:crm.meeting,freq:0
-msgid "Frequency"
-msgstr "Frecuencia"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "References"
-msgstr "Referencias"
-
-#. module: crm
-#: code:addons/crm/crm.py:392
-#: view:crm.lead:0
-#: view:crm.lead2opportunity:0
-#: view:crm.lead2opportunity.action:0
-#: view:crm.lead2opportunity.partner:0
-#: view:crm.lead2partner:0
-#: view:crm.phonecall:0
-#: view:crm.phonecall2partner:0
-#: view:res.partner:0
-#, python-format
-msgid "Cancel"
-msgstr "Cancelar"
-
-#. module: crm
-#: model:ir.model,name:crm.model_res_users
-msgid "res.users"
-msgstr "res.usuarios"
-
-#. module: crm
-#: model:ir.model,name:crm.model_crm_merge_opportunity
-msgid "Merge two Opportunities"
-msgstr "Fusionar dos oportunidades"
-
-#. module: crm
-#: selection:crm.meeting,end_type:0
-msgid "Fix amout of times"
-msgstr "Cantidad fija de veces"
-
-#. module: crm
-#: view:crm.lead:0
-#: view:crm.meeting:0
-#: view:crm.phonecall:0
-msgid "Current"
-msgstr "Actual"
-
-#. module: crm
-#: field:crm.meeting,exrule:0
-msgid "Exception Rule"
-msgstr "Exception de regla"
-
-#. module: crm
-#: help:base.action.rule,act_mail_to_partner:0
-msgid "Check this if you want the rule to send an email to the partner."
-msgstr "Verifica esto si tu quieres enviar la norma en un correo electronico a el socio"
-
-#. module: crm
-#: model:ir.actions.act_window,name:crm.crm_phonecall_categ_action
-msgid "Phonecall Categories"
-msgstr "Categorías de llamadas"
-
-#. module: crm
-#: view:crm.meeting:0
-msgid "Invite People"
-msgstr "Invitar personas"
-
-#. module: crm
-#: constraint:crm.case.section:0
-msgid "Error ! You cannot create recursive Sales team."
-msgstr "¡Error! No puede crear equipos de ventas recursivos."
-
-#. module: crm
-#: view:crm.meeting:0
-msgid "Search Meetings"
-msgstr "Buscar reuniones"
-
-#. module: crm
-#: selection:crm.segmentation.line,expr_name:0
-msgid "Sale Amount"
-msgstr "Importe de venta"
-
-#. module: crm
-#: code:addons/crm/wizard/crm_send_email.py:141
-#, python-format
-msgid "Unable to send mail. Please check SMTP is configured properly."
-msgstr "Imposible enviar el correo electrónico. Verifique que la configuración SMTP sea correcta."
-
-#. module: crm
-#: selection:crm.segmentation.line,expr_operator:0
-msgid "="
-msgstr "="
-
-#. module: crm
-#: selection:crm.meeting,state:0
-msgid "Unconfirmed"
-msgstr "No confirmado"
-
-#. module: crm
-#: model:ir.actions.act_window,help:crm.action_report_crm_opportunity
-msgid "Opportunities Analysis gives you an instant access to your opportunities with information such as the expected revenue, planned cost, missed deadlines or the number of interactions per opportunity. This report is mainly used by the sales manager in order to do the periodic review with the teams of the sales pipeline."
-msgstr "El análisis de oportunidades le da acceso instantáneo a sus oportunidades con información como el ingreso previsto, coste planeado, fechas límite incumplidas o el número de interacciones por oportunidad. Este informe lo utiliza principalmente el responsable de ventas para hacer una revisión periódica del proceso de ventas con los equipos."
-
-#. module: crm
-#: field:crm.case.categ,name:0
-#: field:crm.installer,name:0
-#: field:crm.lead,name:0
-#: field:crm.segmentation,name:0
-#: field:crm.send.mail.attachment,name:0
-msgid "Name"
-msgstr "Nombre"
-
-#. module: crm
-#: field:crm.meeting,alarm_id:0
-#: field:crm.meeting,base_calendar_alarm_id:0
-msgid "Alarm"
-msgstr "Alarma"
-
-#. module: crm
-#: model:ir.actions.act_window,help:crm.crm_lead_stage_act
-msgid "Add specific stages to leads and opportunities allowing your sales to better organise their sales pipeline. Stages will allow them to easily track how a specific lead or opportunity is positioned in the sales cycle."
-msgstr "Agregar etapas específicas de iniciativas y oportunidades para organizar mejor su flujo de ventas. Estas etapas permitirán un fácil seguimiento de iniciativas u oportunidades en relación al ciclo de ventas."
-
-#. module: crm
-#: view:crm.lead.report:0
-#: view:crm.phonecall.report:0
-msgid "My Case(s)"
-msgstr "Mi(s) caso(s)"
-
-#. module: crm
-#: field:crm.lead,birthdate:0
-msgid "Birthdate"
-msgstr "Fecha de nacimiento"
-
-#. module: crm
-#: view:crm.meeting:0
-msgid "The"
-msgstr "El"
-
-#. module: crm
-#: field:crm.send.mail.attachment,wizard_id:0
-msgid "Wizard"
-msgstr "Asistente"
-
-#. module: crm
-#: help:crm.lead,section_id:0
-msgid "Sales team to which this case belongs to. Defines responsible user and e-mail address for the mail gateway."
-msgstr "El equipo de ventas al que pertenece este caso. Define el usuario responsable y la dirección de correo electrónico para la pasarela de correo."
-
-#. module: crm
-#: view:crm.lead:0
-#: view:crm.phonecall:0
-msgid "Creation"
-msgstr "Creación"
-
-#. module: crm
-#: selection:crm.lead,priority:0
-#: selection:crm.lead.report,priority:0
-#: selection:crm.phonecall,priority:0
-#: selection:crm.phonecall.report,priority:0
-msgid "High"
-msgstr "Alto"
-
-#. module: crm
-#: model:process.node,note:crm.process_node_partner0
-msgid "Convert to prospect to business partner"
-msgstr "Convertir prospección a empresa"
-
-#. module: crm
-#: view:crm.phonecall2opportunity:0
-msgid "_Convert"
-msgstr "_Convertir"
-
-#. module: crm
-#: model:ir.actions.act_window,help:crm.action_view_attendee_form
-msgid "With Meeting Invitations you can create and manage the meeting invitations sent/to be sent to your colleagues/partners. You can not only invite OpenERP users, but also external parties, such as a customer."
-msgstr "Con invitaciones a reuniones puede crear y gesionar las invitaciones de reuniones enviadas / por enviar a sus compañeros de trabajo / empresas. La invitación no debe ser únicamente para usuarios de OpenERP, puede ser igualmente para terceros externos, como un cliente."
-
-#. module: crm
-#: selection:crm.meeting,week_list:0
-msgid "Saturday"
-msgstr "Sábado"
-
-#. module: crm
-#: selection:crm.meeting,byday:0
-msgid "Fifth"
-msgstr "Quinto"
-
-#. module: crm
-#: view:crm.phonecall2phonecall:0
-msgid "_Schedule"
-msgstr "_Calendario"
-
-#. module: crm
-#: field:crm.lead.report,delay_close:0
-msgid "Delay to Close"
-msgstr "Tiempo restante para el cierre"
-
-#. module: crm
-#: field:crm.meeting,we:0
-msgid "Wed"
-msgstr "Mier"
-
-#. module: crm
-#: model:crm.case.categ,name:crm.categ_oppor6
-msgid "Potential Reseller"
-msgstr "Distribuidor potencial"
-
-#. module: crm
-#: field:crm.lead.report,planned_revenue:0
-msgid "Planned Revenue"
-msgstr "Ingresos previstos"
-
-#. module: crm
-#: view:crm.lead:0
-#: view:crm.lead.report:0
-#: view:crm.meeting:0
-#: view:crm.phonecall:0
-#: view:crm.phonecall.report:0
-msgid "Group By..."
-msgstr "Agrupar por..."
-
-#. module: crm
-#: help:crm.lead,partner_id:0
-msgid "Optional linked partner, usually after conversion of the lead"
-msgstr "Empresa relacionada opcional, normalmente después de la conversión de la iniciativa"
-
-#. module: crm
-#: view:crm.meeting:0
-msgid "Invitation details"
-msgstr "Detalles de la invitación"
-
-#. module: crm
-#: field:crm.case.section,parent_id:0
-msgid "Parent Team"
-msgstr "Equipo padre"
-
-#. module: crm
-#: field:crm.lead,date_action:0
-msgid "Next Action Date"
-msgstr "Fecha de la próxima acción"
-
-#. module: crm
-#: selection:crm.segmentation,state:0
-msgid "Running"
-msgstr "En proceso"
-
-#. module: crm
-#: selection:crm.meeting,freq:0
-msgid "Hours"
-msgstr "Horas"
-
-#. module: crm
-#: field:crm.lead,zip:0
-msgid "Zip"
-msgstr "Código postal"
-
-#. module: crm
-#: code:addons/crm/crm_lead.py:213
-#, python-format
-msgid "The case '%s' has been opened."
-msgstr "El caso '%s' ha sido abierto"
-
-#. module: crm
-#: view:crm.installer:0
-msgid "title"
-msgstr "título"
-
-#. module: crm
-#: model:crm.case.categ,name:crm.categ_phone1
-#: model:ir.actions.act_window,name:crm.crm_case_categ_phone_incoming0
-#: model:ir.ui.menu,name:crm.menu_crm_case_phone_inbound
-msgid "Inbound"
-msgstr "Entrante"
-
-#. module: crm
-#: help:crm.case.stage,probability:0
-msgid "This percentage depicts the default/average probability of the Case for this stage to be a success"
-msgstr "Este porcentaje representa la probabilidad por defecto / media para que los casos de esta etapa sean un éxito."
-
-#. module: crm
-#: view:crm.phonecall.report:0
-#: model:ir.actions.act_window,name:crm.act_crm_opportunity_crm_phonecall_new
-msgid "Phone calls"
-msgstr "Llamadas telefónicas"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Communication History"
-msgstr "Historial de comunicaciones"
-
-#. module: crm
-#: selection:crm.meeting,show_as:0
-msgid "Free"
-msgstr "Libre"
-
-#. module: crm
-#: view:crm.installer:0
-msgid "Synchronization"
-msgstr "Sincronización"
-
-#. module: crm
-#: field:crm.case.section,allow_unlink:0
-msgid "Allow Delete"
-msgstr "Permitir eliminar"
-
-#. module: crm
-#: field:crm.meeting,mo:0
-msgid "Mon"
-msgstr "Lun"
-
-#. module: crm
-#: selection:crm.lead,priority:0
-#: selection:crm.lead.report,priority:0
-#: selection:crm.phonecall,priority:0
-#: selection:crm.phonecall.report,priority:0
-msgid "Highest"
-msgstr "Muy alto"
-
-#. module: crm
-#: model:ir.actions.act_window,help:crm.crm_case_categ_phone_incoming0
-msgid "The Inbound Calls tool allows you to log your inbound calls on the fly. Each call you get will appear on the partner form to trace every contact you have with a partner. From the phone call form, you can trigger a request for another call, a meeting or an opportunity."
-msgstr "La herramienta de llamadas entrantes le permite seguir el rastro de sus llamadas entrantes en tiempo real. Cada llamada que reciba, aparecerá en el formulario de la empresa para dejar el rastro de cada contacto que tiene en una empresa. Desde el formulario de llamada telefónica, puede lanzar una solicitud para otra llamada, una reunión o una oportunidad"
-
-#. module: crm
-#: help:crm.meeting,recurrency:0
-msgid "Recurrent Meeting"
-msgstr "Reunión periódica"
-
-#. module: crm
-#: view:crm.case.section:0
-#: view:crm.lead:0
-#: field:crm.lead,description:0
-msgid "Notes"
-msgstr "Notas"
-
-#. module: crm
-#: selection:crm.meeting,freq:0
-msgid "Days"
-msgstr "Días"
-
-#. module: crm
-#: field:crm.segmentation.line,expr_value:0
-msgid "Value"
-msgstr "Valor"
-
-#. module: crm
-#: view:crm.lead:0
-#: view:crm.lead.report:0
-msgid "Opportunity by Categories"
-msgstr "Oportunidades por categorías"
-
-#. module: crm
-#: view:crm.lead:0
-#: field:crm.lead,partner_name:0
-msgid "Customer Name"
-msgstr "Nombre del cliente"
-
-#. module: crm
-#: model:ir.model,name:crm.model_crm_phonecall2opportunity
-msgid "Phonecall To Opportunity"
-msgstr "Llamada telefónica a oportunidad"
-
-#. module: crm
-#: field:crm.case.section,reply_to:0
-msgid "Reply-To"
-msgstr "Responder a"
-
-#. module: crm
-#: view:crm.case.section:0
-msgid "Select Stages for this Sales Team"
-msgstr "Seleccionar etapas para este equipo de ventas"
-
-#. module: crm
-#: view:board.board:0
-msgid "Opportunities by Stage"
-msgstr "Oportunidades por etapa"
-
-#. module: crm
-#: view:crm.meeting:0
-msgid "Recurrency Option"
-msgstr "Opción de recurrencia"
-
-#. module: crm
-#: model:process.transition,note:crm.process_transition_leadpartner0
-msgid "Prospect is converting to business partner"
-msgstr "El prospecto se convierte a partner"
-
-#. module: crm
-#: view:crm.lead2opportunity:0
-#: view:crm.partner2opportunity:0
-#: model:ir.actions.act_window,name:crm.phonecall2opportunity_act
-msgid "Convert To Opportunity"
-msgstr "Convertir en oportunidad"
-
-#. module: crm
-#: view:crm.phonecall:0
-#: view:crm.phonecall.report:0
-#: view:res.partner:0
-msgid "Held"
-msgstr "Ocupado"
-
-#. module: crm
-#: view:crm.lead:0
-#: view:crm.phonecall:0
-#: view:res.partner:0
-msgid "Reset to Draft"
-msgstr "Cambiar a borrador"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Extra Info"
-msgstr "Información extra"
-
-#. module: crm
-#: view:crm.merge.opportunity:0
-#: model:ir.actions.act_window,name:crm.action_merge_opportunities
-#: model:ir.actions.act_window,name:crm.merge_opportunity_act
-msgid "Merge Opportunities"
-msgstr "Fusionar oportunidades"
-
-#. module: crm
-#: model:crm.case.resource.type,name:crm.type_lead5
-msgid "Google Adwords"
-msgstr "Google Adwords"
-
-#. module: crm
-#: model:ir.model,name:crm.model_crm_phonecall
-msgid "crm.phonecall"
-msgstr "crm.llamadateléfono"
-
-#. module: crm
-#: model:crm.case.resource.type,name:crm.type_lead3
-msgid "Mail Campaign 2"
-msgstr "Campaña mail 2"
-
-#. module: crm
-#: model:crm.case.resource.type,name:crm.type_lead2
-msgid "Mail Campaign 1"
-msgstr "Mail Campaign 1"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Create"
-msgstr "Crear"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Dates"
-msgstr "Fechas"
-
-#. module: crm
-#: code:addons/crm/crm.py:492
-#, python-format
-msgid "Send"
-msgstr "Enviar"
-
-#. module: crm
-#: view:crm.lead:0
-#: field:crm.lead,priority:0
-#: view:crm.lead.report:0
-#: field:crm.lead.report,priority:0
-#: field:crm.phonecall,priority:0
-#: view:crm.phonecall.report:0
-#: field:crm.phonecall.report,priority:0
-msgid "Priority"
-msgstr "Prioridad"
-
-#. module: crm
-#: field:crm.segmentation,sales_purchase_active:0
-msgid "Use The Sales Purchase Rules"
-msgstr "Utiliza las reglas de compra ventas"
-
-#. module: crm
-#: model:ir.model,name:crm.model_crm_lead2opportunity_partner
-msgid "Lead To Opportunity Partner"
-msgstr "Iniciativa a Oportunidad"
-
-#. module: crm
-#: field:crm.meeting,location:0
-msgid "Location"
-msgstr "Ubicación"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Reply"
-msgstr "Responder"
-
-#. module: crm
-#: selection:crm.meeting,freq:0
-msgid "Weeks"
-msgstr "Semanas"
-
-#. module: crm
-#: model:process.node,note:crm.process_node_meeting0
-msgid "Schedule a normal or phone meeting"
-msgstr "Programar una reunión normal o telefónica"
-
-#. module: crm
-#: code:addons/crm/crm.py:375
-#, python-format
-msgid "Error !"
-msgstr "¡Error!"
-
-#. module: crm
-#: model:ir.actions.act_window,help:crm.crm_meeting_categ_action
-msgid "Create different meeting categories to better organize and classify your meetings."
-msgstr "Cree diferentes categorías de reuniones para organizarlas y clasificarlas mejor."
-
-#. module: crm
-#: model:ir.model,name:crm.model_crm_segmentation_line
-msgid "Segmentation line"
-msgstr "Línea de segmentación"
-
-#. module: crm
-#: view:crm.opportunity2phonecall:0
-#: view:crm.phonecall2phonecall:0
-msgid "Planned Date"
-msgstr "Fecha prevista"
-
-#. module: crm
-#: field:crm.meeting,base_calendar_url:0
-msgid "Caldav URL"
-msgstr "URL de caldav"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Expected Revenues"
-msgstr "Ingresos esperados"
-
-#. module: crm
-#: model:crm.case.resource.type,name:crm.type_lead6
-msgid "Google Adwords 2"
-msgstr "Google Adwords 2"
-
-#. module: crm
-#: help:crm.lead,type:0
-#: help:crm.lead.report,type:0
-msgid "Type is used to separate Leads and Opportunities"
-msgstr "El tipo es utilizado para separar iniciativas y oportunidades"
-
-#. module: crm
-#: view:crm.phonecall2partner:0
-msgid "Are you sure you want to create a partner based on this Phonecall ?"
-msgstr "¿Está seguro que quiere crear una empresa basada en esta llamada telefónica?"
-
-#. module: crm
-#: selection:crm.lead.report,month:0
-#: selection:crm.meeting,month_list:0
-#: selection:crm.phonecall.report,month:0
-msgid "July"
-msgstr "Julio"
-
-#. module: crm
-#: model:ir.actions.act_window,help:crm.crm_case_section_act
-msgid "Define a Sales Team to organize your different salesmen or sales departments into separate teams. Each team will work in its own list of opportunities, sales orders, etc. Each user can set a default team in his user preferences. The opportunities and sales order displayed, will automatically be filtered according to his team."
-msgstr "Defina un equipo de ventas para organizar a sus diferentes vendedores o departamentos de ventas en equipos separados. Cada equipo trabajará en su propia lista de oportunidades, pedidos de venta, etc. Cada usuario puede configurar un equipo predeterminado en sus preferencias de usuario. Las oportunidades y pedidos de venta mostrados se filtrarán automáticamente de acuerdo a su equipo."
-
-#. module: crm
-#: help:crm.meeting,count:0
-msgid "Repeat x times"
-msgstr "Repetir x veces"
-
-#. module: crm
-#: model:ir.actions.act_window,name:crm.crm_case_section_act
-#: model:ir.model,name:crm.model_crm_case_section
-#: model:ir.ui.menu,name:crm.menu_crm_case_section_act
-msgid "Sales Teams"
-msgstr "Equipos de ventas"
-
-#. module: crm
-#: model:ir.model,name:crm.model_crm_lead2partner
-msgid "Lead to Partner"
-msgstr "Inicaitiva a cliente"
-
-#. module: crm
-#: field:crm.segmentation.line,segmentation_id:0
-#: model:ir.actions.act_window,name:crm.crm_segmentation-act
-msgid "Segmentation"
-msgstr "Segmentación"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Team"
-msgstr "Equipo"
-
-#. module: crm
-#: field:crm.installer,outlook:0
-msgid "MS-Outlook"
-msgstr "MS-Outlook"
-
-#. module: crm
-#: view:crm.phonecall:0
-#: view:crm.phonecall.report:0
-#: view:res.partner:0
-msgid "Not Held"
-msgstr "No ocupado"
-
-#. module: crm
-#: field:crm.lead.report,probability:0
-msgid "Probability"
-msgstr "Probabilidad"
-
-#. module: crm
-#: view:crm.lead.report:0
-#: field:crm.lead.report,month:0
-#: field:crm.meeting,month_list:0
-#: view:crm.phonecall.report:0
-#: field:crm.phonecall.report,month:0
-msgid "Month"
-msgstr "Mes"
-
-#. module: crm
-#: view:crm.lead:0
-#: model:ir.actions.act_window,name:crm.crm_case_category_act_leads_all
-#: model:ir.ui.menu,name:crm.menu_crm_case_categ0_act_leads
-#: model:process.node,name:crm.process_node_leads0
-msgid "Leads"
-msgstr "Iniciativas"
-
-#. module: crm
-#: model:ir.actions.act_window,help:crm.crm_case_category_act_leads_all
-msgid "Leads allow you to manage and keep track of all initial contacts with a prospect or partner showing interest in your products or services. A lead is usually the first step in your sales cycle. Once qualified, a lead may be converted into a business opportunity, while creating the related partner for further detailed tracking of any linked activities. You can import a database of prospects, keep track of your business cards or integrate your website's contact form with the OpenERP Leads. Leads can be connected to the email gateway: new emails may create leads, each of them automatically gets the history of the conversation with the prospect."
-msgstr "Las iniciativas le permiten gestionar y realizar un seguimiento de todos los contactos iniciales con un cliente potencial o socio que muestre interés en sus productos o servicios. Una iniciativa es generalmente el primer paso en su ciclo de ventas. Una vez identificada, una iniciativa se puede convertir en una oportunidad de negocio, creándose el socio correspondiente para el seguimiento detallado de cualquier actividad relacionada. Usted puede importar una base de datos de posibles clientes, realizar el seguimiento de sus tarjetas de visita o integrar el formulario de contacto de su sitio web con las iniciativas de OpenERP. Las iniciativas pueden ser conectadas a una pasarela de correo electrónico: los nuevos correos crearán nuevas iniciativas y cada una de ellas obtendrá automáticamente el historial de la conversación con el cliente potencial."
-
-#. module: crm
-#: selection:crm.lead2opportunity.partner,action:0
-#: selection:crm.lead2partner,action:0
-#: selection:crm.phonecall2partner,action:0
-msgid "Create a new partner"
-msgstr "Crear una nueva empresa"
-
-#. module: crm
-#: view:crm.meeting:0
-#: view:res.partner:0
-msgid "Start Date"
-msgstr "Fecha inicio"
-
-#. module: crm
-#: selection:crm.phonecall,state:0
-#: view:crm.phonecall.report:0
-msgid "Todo"
-msgstr "Por hacer"
-
-#. module: crm
-#: view:crm.meeting:0
-msgid "Delegate"
-msgstr "Delegar"
-
-#. module: crm
-#: view:crm.meeting:0
-msgid "Decline"
-msgstr "Rechazar"
-
-#. module: crm
-#: help:crm.lead,optin:0
-msgid "If opt-in is checked, this contact has accepted to receive emails."
-msgstr "Si opt-in está marcado, este contacto ha aceptado recibir correos electrónicos."
-
-#. module: crm
-#: view:crm.meeting:0
-msgid "Reset to Unconfirmed"
-msgstr "Restablecer a no confirmado"
-
-#. module: crm
-#: code:addons/crm/wizard/crm_add_note.py:40
-#: view:crm.add.note:0
-#, python-format
-msgid "Note"
-msgstr "Nota"
-
-#. module: crm
-#: constraint:res.users:0
-msgid "The chosen company is not in the allowed companies for this user"
-msgstr "La compañía seleccionada no está en las compañías permitidas para este usuario"
-
-#. module: crm
-#: selection:crm.lead,priority:0
-#: selection:crm.lead.report,priority:0
-#: selection:crm.phonecall,priority:0
-#: selection:crm.phonecall.report,priority:0
-msgid "Low"
-msgstr "Bajo"
-
-#. module: crm
-#: selection:crm.add.note,state:0
-#: field:crm.lead,date_closed:0
-#: selection:crm.lead,state:0
-#: view:crm.lead.report:0
-#: selection:crm.lead.report,state:0
-#: field:crm.meeting,date_closed:0
-#: selection:crm.merge.opportunity,state:0
-#: field:crm.phonecall,date_closed:0
-#: selection:crm.phonecall.report,state:0
-#: selection:crm.send.mail,state:0
-msgid "Closed"
-msgstr "Cerrado"
-
-#. module: crm
-#: view:crm.installer:0
-msgid "Plug-In"
-msgstr "Conector"
-
-#. module: crm
-#: model:crm.case.categ,name:crm.categ_meet2
-msgid "Internal Meeting"
-msgstr "Reunión interna"
-
-#. module: crm
-#: code:addons/crm/crm.py:411
-#: selection:crm.add.note,state:0
-#: view:crm.lead:0
-#: selection:crm.lead,state:0
-#: view:crm.lead.report:0
-#: selection:crm.lead.report,state:0
-#: selection:crm.merge.opportunity,state:0
-#: selection:crm.phonecall,state:0
-#: selection:crm.phonecall.report,state:0
-#: selection:crm.send.mail,state:0
-#, python-format
-msgid "Pending"
-msgstr "Pendiente"
-
-#. module: crm
-#: model:crm.case.categ,name:crm.categ_meet1
-msgid "Customer Meeting"
-msgstr "Reunión de cliente"
-
-#. module: crm
-#: view:crm.lead:0
-#: field:crm.lead,email_cc:0
-msgid "Global CC"
-msgstr "CC Global"
-
-#. module: crm
-#: view:crm.phonecall:0
-#: model:ir.actions.act_window,name:crm.crm_case_categ_phone0
-#: model:ir.ui.menu,name:crm.menu_crm_case_phone
-#: view:res.partner:0
-msgid "Phone Calls"
-msgstr "Llamadas telefónicas"
-
-#. module: crm
-#: help:crm.lead.report,delay_open:0
-#: help:crm.phonecall.report,delay_open:0
-msgid "Number of Days to open the case"
-msgstr "Número de días para abrir el caso"
-
-#. module: crm
-#: field:crm.lead,phone:0
-#: field:crm.phonecall,partner_phone:0
-msgid "Phone"
-msgstr "Teléfono"
-
-#. module: crm
-#: field:crm.case.section,active:0
-#: field:crm.lead,active:0
-#: view:crm.lead.report:0
-#: field:crm.meeting,active:0
-#: field:crm.phonecall,active:0
-msgid "Active"
-msgstr "Activo"
-
-#. module: crm
-#: code:addons/crm/crm_lead.py:306
-#, python-format
-msgid "The stage of opportunity '%s' has been changed to '%s'."
-msgstr "La fase de la oportunidad '%s' ha cambiado a '%s'."
-
-#. module: crm
-#: selection:crm.segmentation.line,operator:0
-msgid "Mandatory Expression"
-msgstr "Expresión obligatoria"
-
-#. module: crm
-#: selection:crm.segmentation.line,expr_operator:0
-msgid ">"
-msgstr ">"
-
-#. module: crm
-#: view:crm.meeting:0
-msgid "Uncertain"
-msgstr "Incierto"
-
-#. module: crm
-#: field:crm.send.mail,email_cc:0
-msgid "CC"
-msgstr "CC"
-
-#. module: crm
-#: view:crm.send.mail:0
-#: model:ir.actions.act_window,name:crm.action_crm_send_mail
-msgid "Send Mail"
-msgstr "Enviar correo"
-
-#. module: crm
-#: selection:crm.meeting,freq:0
-msgid "Months"
-msgstr "Meses"
-
-#. module: crm
-#: help:crm.installer,wiki_sale_faq:0
-msgid "Helps you manage wiki pages for Frequently Asked Questions on Sales Application."
-msgstr "Le ayuda a organizar páginas wiki para preguntas frecuentes sobre la aplicación de ventas"
-
-#. module: crm
-#: help:crm.installer,crm_fundraising:0
-msgid "This may help associations in their fundraising process and tracking."
-msgstr "Puede ayudar a las asociaciones en su proceso de obtención de fondos y seguimiento."
-
-#. module: crm
-#: field:crm.lead2opportunity.partner,action:0
-#: field:crm.lead2partner,action:0
-#: field:crm.phonecall2partner,action:0
-msgid "Action"
-msgstr "Acción"
-
-#. module: crm
-#: field:crm.installer,crm_claim:0
-msgid "Claims"
-msgstr "Reclamaciones"
-
-#. module: crm
-#: field:crm.segmentation,som_interval_decrease:0
-msgid "Decrease (0>1)"
-msgstr "Disminuir (0>1)"
-
-#. module: crm
-#: view:crm.add.note:0
-#: view:crm.lead:0
-#: view:crm.send.mail:0
-msgid "Attachments"
-msgstr "Datos adjuntos"
-
-#. module: crm
-#: selection:crm.meeting,rrule_type:0
-msgid "Weekly"
-msgstr "Semanal"
-
-#. module: crm
-#: view:crm.lead.report:0
-#: model:ir.actions.act_window,name:crm.action_report_crm_opportunity
-#: model:ir.ui.menu,name:crm.menu_report_crm_opportunities_tree
-msgid "Opportunities Analysis"
-msgstr "Análisis de oportunidades"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Misc"
-msgstr "Varios"
-
-#. module: crm
-#: model:crm.case.categ,name:crm.categ_oppor8
-#: view:crm.meeting:0
-msgid "Other"
-msgstr "Otro"
-
-#. module: crm
-#: view:crm.meeting:0
-#: selection:crm.meeting,state:0
-#: selection:crm.phonecall,state:0
-msgid "Done"
-msgstr "Hecho"
-
-#. module: crm
-#: help:crm.meeting,interval:0
-msgid "Repeat every (Days/Week/Month/Year)"
-msgstr "Repetir cada (días/semana/mes/año)"
-
-#. module: crm
-#: field:crm.segmentation,som_interval_max:0
-msgid "Max Interval"
-msgstr "Intervalo máx"
-
-#. module: crm
-#: view:crm.opportunity2phonecall:0
-msgid "_Schedule Call"
-msgstr "_Programar llamada"
-
-#. module: crm
-#: code:addons/crm/crm.py:326
-#: selection:crm.add.note,state:0
-#: view:crm.lead:0
-#: selection:crm.lead,state:0
-#: selection:crm.lead.report,state:0
-#: selection:crm.merge.opportunity,state:0
-#: view:crm.phonecall:0
-#: selection:crm.phonecall.report,state:0
-#: selection:crm.send.mail,state:0
-#: view:res.partner:0
-#, python-format
-msgid "Open"
-msgstr "Abierto"
-
-#. module: crm
-#: selection:crm.meeting,week_list:0
-msgid "Tuesday"
-msgstr "Martes"
-
-#. module: crm
-#: field:crm.lead,city:0
-msgid "City"
-msgstr "Ciudad"
-
-#. module: crm
-#: selection:crm.meeting,show_as:0
-msgid "Busy"
-msgstr "Ocupado"
-
-#. module: crm
-#: field:crm.meeting,interval:0
-msgid "Repeat every"
-msgstr "Repetir cada"
-
-#. module: crm
-#: field:crm.installer,crm_helpdesk:0
-msgid "Helpdesk"
-msgstr "Asistencia/Ayuda"
-
-#. module: crm
-#: field:crm.meeting,recurrency:0
-msgid "Recurrent"
-msgstr "Recurrente"
-
-#. module: crm
-#: code:addons/crm/crm.py:397
-#, python-format
-msgid "The case '%s' has been cancelled."
-msgstr "El caso '%s' ha sido cancelado"
-
-#. module: crm
-#: field:crm.installer,sale_crm:0
-msgid "Opportunity to Quotation"
-msgstr "Oportunidad a presupuesto"
-
-#. module: crm
-#: model:ir.model,name:crm.model_crm_send_mail
-msgid "Send new email"
-msgstr "Enviar nuevo email"
-
-#. module: crm
-#: view:board.board:0
-#: model:ir.actions.act_window,name:crm.act_my_oppor
-msgid "My Open Opportunities"
-msgstr "Mis oportunidades abiertas"
-
-#. module: crm
-#: model:ir.actions.act_window,name:crm.open_board_statistical_dash
-msgid "CRM - Statistics Dashboard"
-msgstr "CRM - Tablero de estadísticas"
-
-#. module: crm
-#: help:crm.meeting,rrule:0
-msgid "Defines a rule or repeating pattern for recurring events\n"
-"e.g.: Every other month on the last Sunday of the month for 10 occurrences: FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=-1SU"
-msgstr "Define una regla o patrón repetitivo para eventos recurrentes.\n"
-"Por ejemplo: Para 10 ocurrencias cada último domingo de cada dos meses : FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=-1SU"
-
-#. module: crm
-#: field:base.action.rule,trg_max_history:0
-msgid "Maximum Communication History"
-msgstr "Máximo historial de comunicaciones"
-
-#. module: crm
-#: view:crm.lead2opportunity.partner:0
-#: view:crm.lead2partner:0
-msgid "Are you sure you want to create a partner based on this lead ?"
-msgstr "¿Está seguro que quiere crear una empresa basada en esta iniciativa?"
-
-#. module: crm
-#: view:crm.meeting:0
-#: field:crm.meeting,categ_id:0
-msgid "Meeting Type"
-msgstr "Tipo de reunión"
-
-#. module: crm
-#: code:addons/crm/wizard/crm_lead_to_opportunity.py:314
-#, python-format
-msgid "Merge with Existing Opportunity"
-msgstr "Fusinar con oportunidad existente"
-
-#. module: crm
-#: help:crm.lead,state:0
-#: help:crm.phonecall,state:0
-msgid "The state is set to 'Draft', when a case is created. \n"
-"If the case is in progress the state is set to 'Open'. \n"
-"When the case is over, the state is set to 'Done'. \n"
-"If the case needs to be reviewed then the state is set to 'Pending'."
-msgstr "El estado se establece a 'Borrador', cuando se crea un caso. \n"
-"Si el caso está en progreso el estado se establece a 'Abierto'. \n"
-"Cuando el caso se cierra, el estado se establece a 'Realizado'. \n"
-"Si el caso necesita ser revisado entonces en estado se establece a 'Pendiente'."
-
-#. module: crm
-#: view:crm.meeting:0
-#: view:res.partner:0
-msgid "End Date"
-msgstr "Fecha final"
-
-#. module: crm
-#: selection:crm.meeting,byday:0
-msgid "Third"
-msgstr "Tercero"
-
-#. module: crm
-#: help:crm.segmentation,som_interval_max:0
-msgid "The computation is made on all events that occured during this interval, the past X periods."
-msgstr "El cálculo se realiza en todos los eventos que ocurran durante este intervalo, los X períodos anteriores."
-
-#. module: crm
-#: view:board.board:0
-msgid "My Win/Lost Ratio for the Last Year"
-msgstr "Mi coeficiente ganado/perdido del año anterior"
-
-#. module: crm
-#: field:crm.installer,thunderbird:0
-msgid "Thunderbird"
-msgstr "Thunderbird"
-
-#. module: crm
-#: view:crm.lead.report:0
-msgid "# of Emails"
-msgstr "Nº de e-mails"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Search Leads"
-msgstr "Buscar iniciativas"
-
-#. module: crm
-#: view:crm.lead.report:0
-#: view:crm.phonecall.report:0
-#: field:crm.phonecall.report,delay_open:0
-msgid "Delay to open"
-msgstr "Retraso de apertura"
-
-#. module: crm
-#: view:crm.meeting:0
-msgid "Recurrency period"
-msgstr "Periodo de recurrencia"
-
-#. module: crm
-#: field:crm.meeting,week_list:0
-msgid "Weekday"
-msgstr "Día de la semana"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Referrer"
-msgstr "Referenciado por"
-
-#. module: crm
-#: model:ir.model,name:crm.model_crm_lead2opportunity
-msgid "Lead To Opportunity"
-msgstr "Iniciativa a opportunidad"
-
-#. module: crm
-#: model:ir.model,name:crm.model_calendar_attendee
-msgid "Attendee information"
-msgstr "Información asistentes"
-
-#. module: crm
-#: view:crm.segmentation:0
-msgid "Segmentation Test"
-msgstr "Prueba de segmentación"
-
-#. module: crm
-#: view:crm.segmentation:0
-msgid "Continue Process"
-msgstr "Continuar el proceso"
-
-#. module: crm
-#: view:crm.installer:0
-msgid "Configure Your CRM Application"
-msgstr "Configure su aplicación CRM"
-
-#. module: crm
-#: model:ir.model,name:crm.model_crm_phonecall2partner
-msgid "Phonecall to Partner"
-msgstr "Llamada telefónica a empresa"
-
-#. module: crm
-#: help:crm.lead,partner_name:0
-msgid "The name of the future partner that will be created while converting the into opportunity"
-msgstr "El nombre del futuro cliente que se creará cuando se convierta en oportunidad."
-
-#. module: crm
-#: field:crm.opportunity2phonecall,user_id:0
-#: field:crm.phonecall2phonecall,user_id:0
-msgid "Assign To"
-msgstr "Asignar a"
-
-#. module: crm
-#: field:crm.add.note,state:0
-#: field:crm.send.mail,state:0
-msgid "Set New State To"
-msgstr "Establecer nuevo estado a"
-
-#. module: crm
-#: field:crm.lead,date_action_last:0
-#: field:crm.meeting,date_action_last:0
-#: field:crm.phonecall,date_action_last:0
-msgid "Last Action"
-msgstr "Última acción"
-
-#. module: crm
-#: field:crm.meeting,duration:0
-#: field:crm.phonecall,duration:0
-#: field:crm.phonecall.report,duration:0
-msgid "Duration"
-msgstr "Duración"
-
-#. module: crm
-#: field:crm.send.mail,reply_to:0
-msgid "Reply To"
-msgstr "Responder a"
-
-#. module: crm
-#: view:board.board:0
-#: model:ir.actions.act_window,name:crm.open_board_crm
-#: model:ir.ui.menu,name:crm.menu_board_crm
-msgid "Sales Dashboard"
-msgstr "Tablero de ventas"
-
-#. module: crm
-#: code:addons/crm/wizard/crm_lead_to_partner.py:56
-#, python-format
-msgid "A partner is already defined on this lead."
-msgstr "Una empresa ya ha sido definida en esta iniciativa."
-
-#. module: crm
-#: field:crm.lead.report,nbr:0
-#: field:crm.phonecall.report,nbr:0
-msgid "# of Cases"
-msgstr "# de casos"
-
-#. module: crm
-#: help:crm.meeting,section_id:0
-#: help:crm.phonecall,section_id:0
-msgid "Sales team to which Case belongs to."
-msgstr "Equipo de ventas al cual pertence el caso"
-
-#. module: crm
-#: selection:crm.meeting,week_list:0
-msgid "Sunday"
-msgstr "Domingo"
-
-#. module: crm
-#: selection:crm.meeting,byday:0
-msgid "Fourth"
-msgstr "Cuarto"
-
-#. module: crm
-#: selection:crm.add.note,state:0
-#: selection:crm.merge.opportunity,state:0
-#: selection:crm.send.mail,state:0
-msgid "Unchanged"
-msgstr "Sin cambios"
-
-#. module: crm
-#: model:ir.actions.act_window,name:crm.crm_segmentation_tree-act
-#: model:ir.ui.menu,name:crm.menu_crm_segmentation-act
-msgid "Partners Segmentation"
-msgstr "Segmentación de empresas"
-
-#. module: crm
-#: field:crm.lead,fax:0
-msgid "Fax"
-msgstr "Fax"
-
-#. module: crm
-#: model:ir.actions.act_window,help:crm.crm_case_category_act_oppor11
-msgid "With opportunities you can manage and keep track of your sales pipeline by creating specific customer- or prospect-related sales documents to follow up potential sales. Information such as expected revenue, opportunity stage, expected closing date, communication history and much more can be stored. Opportunities can be connected to the email gateway: new emails may create opportunities, each of them automatically gets the history of the conversation with the customer.\n"
-"\n"
-"You and your team(s) will be able to plan meetings and phone calls from opportunities, convert them into quotations, manage related documents, track all customer related activities, and much more."
-msgstr "Con las oportunidades puede gestionar y guardar el registro de su canal de ventas creando documentos específicos de venta por cliente - o potencial cliente - para el seguimiento de sus ventas potenciales. La información sobre ingresos esperados, etapa de la oportunidad, fecha estimada de cierre, histórico de las comunicaciones y muchos otros datos puede ser registrada. Las oportunidades pueden ser conectadas a la pasarela de correo electrónico: nuevos emails pueden crear oportunidades, y cada uno de ellos obtiene automáticamente el historial de la conversación con el cliente."
-
-#. module: crm
-#: view:crm.meeting:0
-msgid "Assignment"
-msgstr "Asignación"
-
-#. module: crm
-#: field:crm.lead,company_id:0
-#: view:crm.lead.report:0
-#: field:crm.lead.report,company_id:0
-#: field:crm.phonecall,company_id:0
-#: view:crm.phonecall.report:0
-#: field:crm.phonecall.report,company_id:0
-msgid "Company"
-msgstr "Compañía"
-
-#. module: crm
-#: selection:crm.meeting,week_list:0
-msgid "Friday"
-msgstr "Viernes"
-
-#. module: crm
-#: field:crm.meeting,allday:0
-msgid "All Day"
-msgstr "Todo el día"
-
-#. module: crm
-#: field:crm.segmentation.line,operator:0
-msgid "Mandatory / Optional"
-msgstr "Obligatorio / Opcional"
-
-#. module: crm
-#: model:ir.actions.act_window,name:crm.action_view_attendee_form
-#: model:ir.ui.menu,name:crm.menu_attendee_invitations
-msgid "Meeting Invitations"
-msgstr "Invitaciones a reunión"
-
-#. module: crm
-#: field:crm.case.categ,object_id:0
-msgid "Object Name"
-msgstr "Nombre del objeto"
-
-#. module: crm
-#: help:crm.lead,email_from:0
-msgid "E-mail address of the contact"
-msgstr "e-mail del contacto"
-
-#. module: crm
-#: field:crm.lead,referred:0
-msgid "Referred by"
-msgstr "Referenciado por"
-
-#. module: crm
-#: view:crm.lead:0
-#: model:ir.model,name:crm.model_crm_add_note
-msgid "Add Internal Note"
-msgstr "Añadir nota interna"
-
-#. module: crm
-#: code:addons/crm/crm_lead.py:304
-#, python-format
-msgid "The stage of lead '%s' has been changed to '%s'."
-msgstr "La etapa de la iniciativa '%s' ha sido cambiada por '%s'"
-
-#. module: crm
-#: selection:crm.meeting,byday:0
-msgid "Last"
-msgstr "Último"
-
-#. module: crm
-#: field:crm.lead,message_ids:0
-#: field:crm.meeting,message_ids:0
-#: field:crm.phonecall,message_ids:0
-msgid "Messages"
-msgstr "Mensajes"
-
-#. module: crm
-#: help:crm.case.stage,on_change:0
-msgid "Change Probability on next and previous stages."
-msgstr "Cambiar probabilidad para las etapas siguientes y anteriores."
-
-#. module: crm
-#: code:addons/crm/crm.py:455
-#: code:addons/crm/crm.py:457
-#: code:addons/crm/crm_action_rule.py:66
-#: code:addons/crm/wizard/crm_send_email.py:141
-#, python-format
-msgid "Error!"
-msgstr "¡Error!"
-
-#. module: crm
-#: field:crm.opportunity2phonecall,name:0
-#: field:crm.phonecall2phonecall,name:0
-msgid "Call summary"
-msgstr "Resumen de la llamada"
-
-#. module: crm
-#: selection:crm.add.note,state:0
-#: selection:crm.lead,state:0
-#: selection:crm.lead.report,state:0
-#: selection:crm.meeting,state:0
-#: selection:crm.merge.opportunity,state:0
-#: selection:crm.phonecall,state:0
-#: selection:crm.phonecall.report,state:0
-#: selection:crm.send.mail,state:0
-msgid "Cancelled"
-msgstr "Cancelado"
-
-#. module: crm
-#: field:crm.add.note,body:0
-msgid "Note Body"
-msgstr "Contenido de la nota"
-
-#. module: crm
-#: view:board.board:0
-msgid "My Planned Revenues by Stage"
-msgstr "Mis ingresos previstos por etapa"
-
-#. module: crm
-#: field:crm.lead.report,date_closed:0
-#: field:crm.phonecall.report,date_closed:0
-msgid "Close Date"
-msgstr "Fecha cierre"
-
-#. module: crm
-#: view:crm.lead.report:0
-#: view:crm.phonecall.report:0
-msgid " Month "
-msgstr " Mes "
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Links"
-msgstr "Enlaces"
-
-#. module: crm
-#: model:ir.actions.act_window,help:crm.crm_lead_categ_action
-msgid "Create specific categories that fit your company's activities to better classify and analyse your leads and opportunities. Such categories could for instance reflect your product structure or the different types of sales you do."
-msgstr "Crear categorías específicas que se adapten a las actividades de su compañía para clasificar y analizar mejor sus iniciativas y oportunidades. Estas categorías podrían, por ejemplo, reflejar la estructura de sus productos o de los distintos tipos de ventas que realice."
-
-#. module: crm
-#: help:crm.segmentation,som_interval_decrease:0
-msgid "If the partner has not purchased (or bought) during a period, decrease the state of mind by this factor. It's a multiplication"
-msgstr "Si la empresa no ha comprado durante un período, disminuir el grado de satisfacción por este factor. Es una multiplicación."
-
-#. module: crm
-#: model:ir.actions.act_window,help:crm.action_report_crm_phonecall
-msgid "From this report, you can analyse the performance of your sales team, based on their phone calls. You can group or filter the information according to several criteria and drill down the information, by adding more groups in the report."
-msgstr "Desde este informe, puede analizar el rendimiento de su equipo de ventas basándose en sus llamadas telefónicas. Puede agrupar o filtrar la información de acuerdo a varios criterios y profundizar en la información añadiendo más grupos al informe."
-
-#. module: crm
-#: view:crm.case.section:0
-msgid "Mailgateway"
-msgstr "Pasarela de correo"
-
-#. module: crm
-#: help:crm.lead,user_id:0
-msgid "By Default Salesman is Administrator when create New User"
-msgstr "Por defecto el vendedor es administrador cuando se crea un nuevo usuario"
-
-#. module: crm
-#: view:crm.lead.report:0
-msgid "# Mails"
-msgstr "Nº de correos"
-
-#. module: crm
-#: code:addons/crm/wizard/crm_phonecall_to_opportunity.py:57
-#, python-format
-msgid "Warning"
-msgstr "Aviso"
-
-#. module: crm
-#: field:crm.phonecall,name:0
-#: view:res.partner:0
-msgid "Call Summary"
-msgstr "Resumen de llamadas"
-
-#. module: crm
-#: field:crm.segmentation.line,expr_operator:0
-msgid "Operator"
-msgstr "Operador"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Schedule/Log Call"
-msgstr "Planificar/Registrar llamada"
-
-#. module: crm
-#: field:crm.installer,fetchmail:0
-msgid "Fetch Emails"
-msgstr "Buscar emails"
-
-#. module: crm
-#: selection:crm.meeting,state:0
-msgid "Confirmed"
-msgstr "Confirmado"
-
-#. module: crm
-#: help:crm.send.mail,email_cc:0
-msgid "These addresses will receive a copy of this email. To modify the permanent CC list, edit the global CC field of this case"
-msgstr "Estas direcciones recibirán una copia de este correo electrónico. Para modificar la lista CC permanente, edite el campo CC global de este caso."
-
-#. module: crm
-#: view:crm.meeting:0
-msgid "Confirm"
-msgstr "Confirmar"
-
-#. module: crm
-#: field:crm.meeting,su:0
-msgid "Sun"
-msgstr "Dom"
-
-#. module: crm
-#: field:crm.phonecall.report,section_id:0
-msgid "Section"
-msgstr "Sección"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Total of Planned Revenue"
-msgstr "Total ingresos previstos"
-
-#. module: crm
-#: code:addons/crm/crm.py:375
-#, python-format
-msgid "You can not escalate, You are already at the top level regarding your sales-team category."
-msgstr "No puede escalar, ya está en el nivel más alto en su categoría de equipo de ventas."
-
-#. module: crm
-#: selection:crm.segmentation.line,operator:0
-msgid "Optional Expression"
-msgstr "Expresión opcional"
-
-#. module: crm
-#: selection:crm.meeting,select1:0
-msgid "Day of month"
-msgstr "Día del mes"
-
-#. module: crm
-#: field:crm.lead2opportunity,probability:0
-msgid "Success Rate (%)"
-msgstr "Tasa de éxito (%)"
-
-#. module: crm
-#: model:crm.case.stage,name:crm.stage_lead1
-#: model:crm.case.stage,name:crm.stage_opportunity1
-msgid "New"
-msgstr "Nuevo"
-
-#. module: crm
-#: view:crm.meeting:0
-msgid "Mail TO"
-msgstr "Enviar correo a"
-
-#. module: crm
-#: view:crm.lead:0
-#: field:crm.lead,email_from:0
-#: field:crm.meeting,email_from:0
-#: field:crm.phonecall,email_from:0
-msgid "Email"
-msgstr "Email"
-
-#. module: crm
-#: view:crm.lead:0
-#: field:crm.lead,channel_id:0
-#: view:crm.lead.report:0
-#: field:crm.lead.report,channel_id:0
-#: field:crm.phonecall,canal_id:0
-msgid "Channel"
-msgstr "Canal"
-
-#. module: crm
-#: model:ir.actions.act_window,name:crm.opportunity2phonecall_act
-msgid "Schedule Call"
-msgstr "Planificar llamada"
-
-#. module: crm
-#: code:addons/crm/wizard/crm_lead_to_opportunity.py:135
-#: code:addons/crm/wizard/crm_lead_to_opportunity.py:260
-#, python-format
-msgid "Closed/Cancelled Leads Could not convert into Opportunity"
-msgstr "Las iniciativas cerradas/canceladas no podrían ser convertidas en oportunidades"
-
-#. module: crm
-#: help:crm.segmentation,exclusif:0
-msgid "Check if the category is limited to partners that match the segmentation criterions. \n"
-"If checked, remove the category from partners that doesn't match segmentation criterions"
-msgstr "Marque esta opción si la categoría está limitada a empresas que coincidan con los criterios de segmentación. \n"
-"Si está marcada, elimina la categoría de aquellas empresas que no coincidan con los criterios de segmentación."
-
-#. module: crm
-#: field:crm.meeting,exdate:0
-msgid "Exception Date/Times"
-msgstr "Fecha/horas excepción"
-
-#. module: crm
-#: selection:crm.meeting,class:0
-msgid "Confidential"
-msgstr "Confidencial"
-
-#. module: crm
-#: field:crm.lead,state_id:0
-msgid "Fed. State"
-msgstr "Provincia"
-
-#. module: crm
-#: model:process.transition,note:crm.process_transition_leadopportunity0
-msgid "Creating business opportunities from Leads"
-msgstr "Creando oportunidades de negocio desde iniciativas"
-
-#. module: crm
-#: help:crm.send.mail,html:0
-msgid "Select this if you want to send email with HTML formatting."
-msgstr "Seleccione esta opción si desea enviar emails con formato HTML"
-
-#. module: crm
-#: model:crm.case.categ,name:crm.categ_oppor4
-msgid "Need Information"
-msgstr "Necesita información"
-
-#. module: crm
-#: model:process.transition,name:crm.process_transition_leadopportunity0
-msgid "Prospect Opportunity"
-msgstr "Oportunidad de prospección"
-
-#. module: crm
-#: view:crm.installer:0
-#: model:ir.actions.act_window,name:crm.action_crm_installer
-msgid "CRM Application Configuration"
-msgstr "Configuración de la aplicación CRM"
-
-#. module: crm
-#: field:base.action.rule,act_categ_id:0
-msgid "Set Category to"
-msgstr "Establecer categoría"
-
-#. module: crm
-#: view:crm.case.section:0
-msgid "Configuration"
-msgstr "Configuración"
-
-#. module: crm
-#: field:crm.meeting,th:0
-msgid "Thu"
-msgstr "Jue"
-
-#. module: crm
-#: view:crm.add.note:0
-#: view:crm.merge.opportunity:0
-#: view:crm.opportunity2phonecall:0
-#: view:crm.partner2opportunity:0
-#: view:crm.phonecall2opportunity:0
-#: view:crm.phonecall2phonecall:0
-#: view:crm.send.mail:0
-msgid "_Cancel"
-msgstr "_Cancelar"
-
-#. module: crm
-#: view:crm.lead.report:0
-#: view:crm.phonecall.report:0
-msgid " Month-1 "
-msgstr " Mes-1 "
-
-#. module: crm
-#: help:crm.installer,sale_crm:0
-msgid "This module relates sale from opportunity cases in the CRM."
-msgstr "Este módulo relaciona ventas con oportunidades en el CRM."
-
-#. module: crm
-#: selection:crm.meeting,rrule_type:0
-msgid "Daily"
-msgstr "Diario"
-
-#. module: crm
-#: model:crm.case.stage,name:crm.stage_lead2
-#: model:crm.case.stage,name:crm.stage_opportunity2
-msgid "Qualification"
-msgstr "Calificación"
-
-#. module: crm
-#: view:crm.case.stage:0
-msgid "Stage Definition"
-msgstr "Definición de etapa"
-
-#. module: crm
-#: selection:crm.meeting,byday:0
-msgid "First"
-msgstr "Primero"
-
-#. module: crm
-#: selection:crm.lead.report,month:0
-#: selection:crm.meeting,month_list:0
-#: selection:crm.phonecall.report,month:0
-msgid "December"
-msgstr "Diciembre"
-
-#. module: crm
-#: field:crm.installer,config_logo:0
-msgid "Image"
-msgstr "Imagen"
-
-#. module: crm
-#: view:base.action.rule:0
-msgid "Condition on Communication History"
-msgstr "Condición en el historial de comunicaciones"
-
-#. module: crm
-#: help:crm.segmentation,som_interval:0
-msgid "A period is the average number of days between two cycle of sale or purchase for this segmentation. \n"
-"It's mainly used to detect if a partner has not purchased or buy for a too long time, \n"
-"so we suppose that his state of mind has decreased because he probably bought goods to another supplier. \n"
-"Use this functionality for recurring businesses."
-msgstr "Un periodo es el número medio de días entre dos ciclos de ventas o compras para esta segmentación. \n"
-"Se utiliza principalmente para detectar si una empresa no ha comprado por un largo periodo de tiempo, \n"
-"por lo que suponemos que su grado de satisfacción ha disminuido porqué seguramente compró bienes a otro proveedor. \n"
-"Utilice esta funcionalidad para negocios recurrentes."
-
-#. module: crm
-#: view:crm.send.mail:0
-msgid "_Send Reply"
-msgstr "_Enviar respuesta"
-
-#. module: crm
-#: field:crm.meeting,vtimezone:0
-msgid "Timezone"
-msgstr "Zona horaria"
-
-#. module: crm
-#: field:crm.lead2opportunity.partner,msg:0
-#: field:crm.lead2partner,msg:0
-#: view:crm.send.mail:0
-msgid "Message"
-msgstr "Mensaje"
-
-#. module: crm
-#: field:crm.meeting,sa:0
-msgid "Sat"
-msgstr "Sáb"
-
-#. module: crm
-#: view:crm.lead:0
-#: field:crm.lead,user_id:0
-#: view:crm.lead.report:0
-#: view:crm.phonecall.report:0
-msgid "Salesman"
-msgstr "Comercial"
-
-#. module: crm
-#: field:crm.lead,date_deadline:0
-msgid "Expected Closing"
-msgstr "Cierre previsto"
-
-#. module: crm
-#: model:ir.model,name:crm.model_crm_opportunity2phonecall
-msgid "Opportunity to Phonecall"
-msgstr "Oportunidad a llamada telefónica"
-
-#. module: crm
-#: help:crm.case.section,allow_unlink:0
-msgid "Allows to delete non draft cases"
-msgstr "Permite eliminar los casos en estado no borrador."
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Schedule Meeting"
-msgstr "Programar reunión"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Partner Name"
-msgstr "Nombre empresa"
-
-#. module: crm
-#: model:crm.case.categ,name:crm.categ_phone2
-#: model:ir.actions.act_window,name:crm.crm_case_categ_phone_outgoing0
-#: model:ir.ui.menu,name:crm.menu_crm_case_phone_outbound
-msgid "Outbound"
-msgstr "Saliente"
-
-#. module: crm
-#: field:crm.lead,date_open:0
-#: field:crm.phonecall,date_open:0
-msgid "Opened"
-msgstr "Abierto"
-
-#. module: crm
-#: view:crm.case.section:0
-#: field:crm.case.section,member_ids:0
-msgid "Team Members"
-msgstr "Miembros del equipo"
-
-#. module: crm
-#: view:crm.lead:0
-#: view:crm.meeting:0
-#: view:crm.phonecall:0
-#: view:res.partner:0
-msgid "Contacts"
-msgstr "Contactos"
-
-#. module: crm
-#: model:crm.case.categ,name:crm.categ_oppor1
-msgid "Interest in Computer"
-msgstr "Interesado en ordenadores"
-
-#. module: crm
-#: view:crm.meeting:0
-msgid "Invitation Detail"
-msgstr "Detalle de la invitación"
-
-#. module: crm
-#: field:crm.segmentation,som_interval_default:0
-msgid "Default (0=None)"
-msgstr "Por defecto (0=Ninguno)"
-
-#. module: crm
-#: help:crm.lead,email_cc:0
-msgid "These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"
-msgstr "Estas direcciones de correo serán añadidas al campo CC para todos los correos entrantes y salientes de este registro antes de ser enviados. Separe las diferentes direcciones de correo con una coma."
-
-#. module: crm
-#: field:crm.partner2opportunity,probability:0
-#: field:crm.phonecall2opportunity,probability:0
-msgid "Success Probability"
-msgstr "Probabilidad de éxito"
-
-#. module: crm
-#: code:addons/crm/crm.py:426
-#: selection:crm.add.note,state:0
-#: selection:crm.lead,state:0
-#: selection:crm.lead.report,state:0
-#: selection:crm.merge.opportunity,state:0
-#: selection:crm.phonecall,state:0
-#: selection:crm.phonecall.report,state:0
-#: selection:crm.send.mail,state:0
-#, python-format
-msgid "Draft"
-msgstr "Borrador"
-
-#. module: crm
-#: model:ir.actions.act_window,name:crm.crm_case_section_act_tree
-msgid "Cases by Sales Team"
-msgstr "Casos por equipo de ventas"
-
-#. module: crm
-#: field:crm.meeting,attendee_ids:0
-msgid "Attendees"
-msgstr "Asistentes"
-
-#. module: crm
-#: view:crm.meeting:0
-#: view:crm.phonecall:0
-#: model:ir.model,name:crm.model_crm_meeting
-#: model:process.node,name:crm.process_node_meeting0
-#: model:res.request.link,name:crm.request_link_meeting
-msgid "Meeting"
-msgstr "Reunión"
-
-#. module: crm
-#: model:ir.model,name:crm.model_crm_case_categ
-msgid "Category of Case"
-msgstr "Categoría de caso"
-
-#. module: crm
-#: view:crm.lead:0
-#: view:crm.phonecall:0
-msgid "7 Days"
-msgstr "7 días"
-
-#. module: crm
-#: view:board.board:0
-msgid "Planned Revenue by Stage and User"
-msgstr "Ingresos previsto por etapa y usuario"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Communication & History"
-msgstr "Comunicación e Historial"
-
-#. module: crm
-#: model:ir.model,name:crm.model_crm_lead_report
-msgid "CRM Lead Report"
-msgstr "Informe de iniciativas CRM"
-
-#. module: crm
-#: field:crm.installer,progress:0
-msgid "Configuration Progress"
-msgstr "Progreso de la configuración"
-
-#. module: crm
-#: selection:crm.lead,priority:0
-#: selection:crm.lead.report,priority:0
-#: selection:crm.phonecall,priority:0
-#: selection:crm.phonecall.report,priority:0
-msgid "Normal"
-msgstr "Normal"
-
-#. module: crm
-#: field:crm.lead,street2:0
-msgid "Street2"
-msgstr "Calle2"
-
-#. module: crm
-#: model:ir.actions.act_window,name:crm.crm_meeting_categ_action
-#: model:ir.ui.menu,name:crm.menu_crm_case_meeting-act
-msgid "Meeting Categories"
-msgstr "Categorías de reuniones"
-
-#. module: crm
-#: view:crm.lead2opportunity.partner:0
-#: view:crm.lead2partner:0
-#: view:crm.phonecall2partner:0
-msgid "You may have to verify that this partner does not exist already."
-msgstr "Debería verificar que esta empresa no exista ya."
-
-#. module: crm
-#: field:crm.lead.report,delay_open:0
-msgid "Delay to Open"
-msgstr "Retraso de apertura"
-
-#. module: crm
-#: field:crm.lead.report,user_id:0
-#: field:crm.phonecall.report,user_id:0
-msgid "User"
-msgstr "Usuario"
-
-#. module: crm
-#: selection:crm.lead.report,month:0
-#: selection:crm.meeting,month_list:0
-#: selection:crm.phonecall.report,month:0
-msgid "November"
-msgstr "Noviembre"
-
-#. module: crm
-#: code:addons/crm/crm_action_rule.py:67
-#, python-format
-msgid "No E-Mail ID Found for your Company address!"
-msgstr "¡No se ha encontrado Email en la dirección de su compañía!"
-
-#. module: crm
-#: view:crm.lead.report:0
-msgid "Opportunities By Stage"
-msgstr "Oportunidades por etapa"
-
-#. module: crm
-#: model:ir.actions.act_window,name:crm.crm_case_categ_phone_create_partner
-msgid "Schedule Phone Call"
-msgstr "Planificar llamada telefónica"
-
-#. module: crm
-#: selection:crm.lead.report,month:0
-#: selection:crm.meeting,month_list:0
-#: selection:crm.phonecall.report,month:0
-msgid "January"
-msgstr "Enero"
-
-#. module: crm
-#: model:ir.actions.act_window,help:crm.crm_opportunity_stage_act
-msgid "Create specific stages that will help your sales better organise their sales pipeline by maintaining them to their sales opportunities. It will allow them to easily track how is positioned a specific opportunity in the sales cycle."
-msgstr "Crear pasos específicos que ayudarán a su departamento de ventas a organizar mejor el flujo de venta gestionando sus oportunidades de venta. Esto les permitirá seguir fácilmente como está posicionada una oportunidad en el ciclo de ventas."
-
-#. module: crm
-#: model:process.process,name:crm.process_process_contractprocess0
-msgid "Contract"
-msgstr "Contrato"
-
-#. module: crm
-#: model:crm.case.resource.type,name:crm.type_lead4
-msgid "Twitter Ads"
-msgstr "Anuncios Twiter"
-
-#. module: crm
-#: code:addons/crm/wizard/crm_add_note.py:26
-#: code:addons/crm/wizard/crm_send_email.py:72
-#: code:addons/crm/wizard/crm_send_email.py:169
-#: code:addons/crm/wizard/crm_send_email.py:270
-#, python-format
-msgid "Error"
-msgstr "Error"
-
-#. module: crm
-#: view:crm.lead.report:0
-msgid "Planned Revenues"
-msgstr "Ingresos esperados"
-
-#. module: crm
-#: model:crm.case.categ,name:crm.categ_oppor7
-msgid "Need Consulting"
-msgstr "Necesita consultoría"
-
-#. module: crm
-#: model:ir.model,name:crm.model_crm_phonecall2phonecall
-msgid "Phonecall To Phonecall"
-msgstr "Llamada telefónica a llamada telefónica"
-
-#. module: crm
-#: code:addons/crm/crm_lead.py:232
-#, python-format
-msgid "The case '%s' has been closed."
-msgstr "El caso '%s' ha sido cerrado"
-
-#. module: crm
-#: field:crm.lead,partner_address_id:0
-#: field:crm.meeting,partner_address_id:0
-#: field:crm.phonecall,partner_address_id:0
-msgid "Partner Contact"
-msgstr "Contacto empresa"
-
-#. module: crm
-#: field:crm.meeting,recurrent_id:0
-msgid "Recurrent ID date"
-msgstr "ID fecha recurrente"
-
-#. module: crm
-#: 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 de usuario!"
-
-#. module: crm
-#: code:addons/crm/wizard/crm_merge_opportunities.py:100
-#, python-format
-msgid "Merged into Opportunity: %s"
-msgstr "Fusionado con la oportunidad '%s'"
-
-#. module: crm
-#: code:addons/crm/crm.py:347
-#: view:crm.lead:0
-#: view:res.partner:0
-#, python-format
-msgid "Close"
-msgstr "Cerrado"
-
-#. module: crm
-#: view:crm.lead:0
-#: view:crm.phonecall:0
-#: view:res.partner:0
-msgid "Categorization"
-msgstr "Categorización"
-
-#. module: crm
-#: model:ir.model,name:crm.model_base_action_rule
-msgid "Action Rules"
-msgstr "Reglas acción"
-
-#. module: crm
-#: field:crm.meeting,rrule_type:0
-msgid "Recurrency"
-msgstr "Recurrencia"
-
-#. module: crm
-#: field:crm.meeting,phonecall_id:0
-msgid "Phonecall"
-msgstr "Llamada telefónica"
-
-#. module: crm
-#: selection:crm.meeting,week_list:0
-msgid "Thursday"
-msgstr "Jueves"
-
-#. module: crm
-#: view:crm.meeting:0
-#: field:crm.send.mail,email_to:0
-msgid "To"
-msgstr "Para"
-
-#. module: crm
-#: selection:crm.meeting,class:0
-msgid "Private"
-msgstr "Privado"
-
-#. module: crm
-#: field:crm.lead,function:0
-msgid "Function"
-msgstr "Función"
-
-#. module: crm
-#: view:crm.add.note:0
-msgid "_Add"
-msgstr "_Añadir"
-
-#. module: crm
-#: selection:crm.segmentation.line,expr_name:0
-msgid "State of Mind"
-msgstr "Grado de satisfacción"
-
-#. module: crm
-#: field:crm.case.section,note:0
-#: view:crm.meeting:0
-#: field:crm.meeting,description:0
-#: view:crm.phonecall:0
-#: field:crm.phonecall,description:0
-#: view:crm.segmentation:0
-#: field:crm.segmentation,description:0
-#: view:res.partner:0
-msgid "Description"
-msgstr "Descripción"
-
-#. module: crm
-#: field:base.action.rule,trg_section_id:0
-#: field:crm.case.categ,section_id:0
-#: field:crm.case.resource.type,section_id:0
-#: view:crm.case.section:0
-#: field:crm.case.section,name:0
-#: field:crm.lead,section_id:0
-#: view:crm.lead.report:0
-#: field:crm.lead.report,section_id:0
-#: field:crm.meeting,section_id:0
-#: field:crm.opportunity2phonecall,section_id:0
-#: view:crm.phonecall:0
-#: field:crm.phonecall,section_id:0
-#: view:crm.phonecall.report:0
-#: field:crm.phonecall2phonecall,section_id:0
-#: field:res.partner,section_id:0
-#: field:res.users,context_section_id:0
-msgid "Sales Team"
-msgstr "Equipo de ventas"
-
-#. module: crm
-#: selection:crm.lead.report,month:0
-#: selection:crm.meeting,month_list:0
-#: selection:crm.phonecall.report,month:0
-msgid "May"
-msgstr "Mayo"
-
-#. module: crm
-#: model:crm.case.categ,name:crm.categ_oppor2
-msgid "Interest in Accessories"
-msgstr "Interesado en accesorios"
-
-#. module: crm
-#: code:addons/crm/crm_lead.py:211
-#, python-format
-msgid "The opportunity '%s' has been opened."
-msgstr "La oportunidad '%s' ha sido abierta"
-
-#. module: crm
-#: field:crm.lead.report,email:0
-msgid "# Emails"
-msgstr "Nº de emails"
-
-#. module: crm
-#: field:crm.lead,street:0
-msgid "Street"
-msgstr "Calle"
-
-#. module: crm
-#: view:crm.lead.report:0
-msgid "Opportunities by User and Team"
-msgstr "Oportunidades por usuario y equipo"
-
-#. module: crm
-#: field:crm.case.section,working_hours:0
-msgid "Working Hours"
-msgstr "Horario de trabajo"
-
-#. module: crm
-#: view:crm.lead:0
-#: field:crm.lead,is_customer_add:0
-msgid "Customer"
-msgstr "Cliente"
-
-#. module: crm
-#: selection:crm.lead.report,month:0
-#: selection:crm.meeting,month_list:0
-#: selection:crm.phonecall.report,month:0
-msgid "February"
-msgstr "Febrero"
-
-#. module: crm
-#: view:crm.phonecall:0
-#: model:ir.actions.act_window,name:crm.crm_case_categ_meet_create_partner
-#: view:res.partner:0
-msgid "Schedule a Meeting"
-msgstr "Planificar una reunión"
-
-#. module: crm
-#: model:crm.case.stage,name:crm.stage_lead6
-#: model:crm.case.stage,name:crm.stage_opportunity6
-#: view:crm.lead:0
-msgid "Lost"
-msgstr "Perdido"
-
-#. module: crm
-#: field:crm.lead,country_id:0
-#: view:crm.lead.report:0
-#: field:crm.lead.report,country_id:0
-msgid "Country"
-msgstr "País"
-
-#. module: crm
-#: view:crm.lead:0
-#: selection:crm.lead2opportunity.action,name:0
-#: view:crm.phonecall:0
-#: view:res.partner:0
-msgid "Convert to Opportunity"
-msgstr "Convertir a oportunidad"
-
-#. module: crm
-#: selection:crm.meeting,week_list:0
-msgid "Wednesday"
-msgstr "Miércoles"
-
-#. module: crm
-#: selection:crm.lead.report,month:0
-#: selection:crm.meeting,month_list:0
-#: selection:crm.phonecall.report,month:0
-msgid "April"
-msgstr "Abril"
-
-#. module: crm
-#: field:crm.case.resource.type,name:0
-msgid "Campaign Name"
-msgstr "Nombre de la campaña"
-
-#. module: crm
-#: model:ir.model,name:crm.model_crm_phonecall_report
-msgid "Phone calls by user and section"
-msgstr "Llamadas telefónicas por usuario y sección"
-
-#. module: crm
-#: selection:crm.lead2opportunity.action,name:0
-msgid "Merge with existing Opportunity"
-msgstr "Fusionado con oportunidad existente"
-
-#. module: crm
-#: field:crm.meeting,select1:0
-msgid "Option"
-msgstr "Opción"
-
-#. module: crm
-#: model:crm.case.stage,name:crm.stage_lead4
-#: model:crm.case.stage,name:crm.stage_opportunity4
-msgid "Negotiation"
-msgstr "Negociación"
-
-#. module: crm
-#: view:crm.lead:0
-msgid "Exp.Closing"
-msgstr "Cierre previsto"
-
-#. module: crm
-#: field:crm.case.stage,sequence:0
-#: field:crm.meeting,sequence:0
-msgid "Sequence"
-msgstr "Secuencia"
-
-#. module: crm
-#: field:crm.send.mail,body:0
-msgid "Message Body"
-msgstr "Cuerpo del mensaje"
-
-#. module: crm
-#: view:crm.meeting:0
-msgid "Accept"
-msgstr "Aceptar"
-
-#. module: crm
-#: field:crm.segmentation.line,expr_name:0
-msgid "Control Variable"
-msgstr "Variable de control"
-
-#. module: crm
-#: selection:crm.meeting,byday:0
-msgid "Second"
-msgstr "Segundo"
-
-#. module: crm
-#: model:crm.case.stage,name:crm.stage_lead3
-#: model:crm.case.stage,name:crm.stage_opportunity3
-msgid "Proposition"
-msgstr "Propuesta"
-
-#. module: crm
-#: field:res.partner,phonecall_ids:0
-msgid "Phonecalls"
-msgstr "Llamadas telefónicas"
-
-#. module: crm
-#: view:crm.lead.report:0
-#: field:crm.lead.report,name:0
-#: view:crm.phonecall.report:0
-#: field:crm.phonecall.report,name:0
-msgid "Year"
-msgstr "Año"
-
-#. module: crm
-#: model:crm.case.resource.type,name:crm.type_lead8
-msgid "Newsletter"
-msgstr "Boletín de noticias"
-
diff --git a/addons/point_of_sale/__openerp__.py b/addons/point_of_sale/__openerp__.py
index d08a8163be6..e0722f12395 100644
--- a/addons/point_of_sale/__openerp__.py
+++ b/addons/point_of_sale/__openerp__.py
@@ -83,16 +83,16 @@ Main features:
'application': True,
# Web client
'js': [
- 'static/lib/backbone/backbone-0.9.2.js',
'static/lib/mousewheel/jquery.mousewheel-3.0.6.js',
- 'static/src/js/pos_models.js',
- 'static/src/js/pos_basewidget.js',
- 'static/src/js/pos_keyboard_widget.js',
- 'static/src/js/pos_scrollbar_widget.js',
- 'static/src/js/pos_widgets.js',
- 'static/src/js/pos_devices.js',
- 'static/src/js/pos_screens.js',
- 'static/src/js/pos_main.js'
+ 'static/src/js/db.js',
+ 'static/src/js/models.js',
+ 'static/src/js/widget_base.js',
+ 'static/src/js/widget_keyboard.js',
+ 'static/src/js/widget_scrollbar.js',
+ 'static/src/js/widgets.js',
+ 'static/src/js/devices.js',
+ 'static/src/js/screens.js',
+ 'static/src/js/main.js'
],
'css': [
'static/src/css/pos.css',
diff --git a/addons/point_of_sale/account_bank_statement.py b/addons/point_of_sale/account_bank_statement.py
index 5b989edb53d..eb7d791ebf9 100644
--- a/addons/point_of_sale/account_bank_statement.py
+++ b/addons/point_of_sale/account_bank_statement.py
@@ -34,8 +34,8 @@ class account_journal(osv.osv):
'self_checkout_payment_method' : fields.boolean('Self Checkout Payment Method'),
}
_defaults = {
- 'opening_control' : True,
- 'closing_control' : True,
+ 'opening_control' : False,
+ 'closing_control' : False,
'self_checkout_payment_method' : False,
}
diff --git a/addons/point_of_sale/controllers/__init__.py b/addons/point_of_sale/controllers/__init__.py
new file mode 100644
index 00000000000..b5f0bcc9ec6
--- /dev/null
+++ b/addons/point_of_sale/controllers/__init__.py
@@ -0,0 +1,3 @@
+import main
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
diff --git a/addons/point_of_sale/controllers.py b/addons/point_of_sale/controllers/main.py
similarity index 52%
rename from addons/point_of_sale/controllers.py
rename to addons/point_of_sale/controllers/main.py
index 33a96dacf84..425a7e3d7f8 100644
--- a/addons/point_of_sale/controllers.py
+++ b/addons/point_of_sale/controllers/main.py
@@ -1,14 +1,71 @@
# -*- coding: utf-8 -*-
import logging
+import simplejson
+import os
+import openerp
try:
import openerp.addons.web.common.http as openerpweb
+ from openerp.addons.web.controllers.main import manifest_list, module_boot, html_template
except ImportError:
import web.common.http as openerpweb
class PointOfSaleController(openerpweb.Controller):
_cp_path = '/pos'
+ @openerpweb.httprequest
+ def app(self, req, s_action=None, **kw):
+ js = "\n ".join('' % i for i in manifest_list(req, None, 'js'))
+ css = "\n ".join('' % i for i in manifest_list(req, None, 'css'))
+
+ cookie = req.httprequest.cookies.get("instance0|session_id")
+ session_id = cookie.replace("%22","")
+ template = html_template.replace('
+
+
+
diff --git a/addons/point_of_sale/doc/barcode_test_sheet4.pdf b/addons/point_of_sale/doc/barcode_test_sheet4.pdf
new file mode 100644
index 00000000000..aff24aefd9d
Binary files /dev/null and b/addons/point_of_sale/doc/barcode_test_sheet4.pdf differ
diff --git a/addons/point_of_sale/doc/barcode_test_sheet4.svg b/addons/point_of_sale/doc/barcode_test_sheet4.svg
new file mode 100644
index 00000000000..671d862519c
--- /dev/null
+++ b/addons/point_of_sale/doc/barcode_test_sheet4.svg
@@ -0,0 +1,1210 @@
+
+
+
+
diff --git a/addons/point_of_sale/doc/barcode_test_sheet_new.svg b/addons/point_of_sale/doc/barcode_test_sheet_new.svg
new file mode 100644
index 00000000000..57dce3dc013
--- /dev/null
+++ b/addons/point_of_sale/doc/barcode_test_sheet_new.svg
@@ -0,0 +1,506 @@
+
+
+
+
diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py
index 690d371e9db..f4d32985688 100644
--- a/addons/point_of_sale/point_of_sale.py
+++ b/addons/point_of_sale/point_of_sale.py
@@ -22,6 +22,7 @@
import pdb
import openerp
import addons
+import openerp.addons.product.product
import time
from datetime import datetime
@@ -59,16 +60,9 @@ class pos_config(osv.osv):
help="Accounting journal used to post sales entries."),
'iface_self_checkout' : fields.boolean('Self Checkout Mode',
help="Check this if this point of sale should open by default in a self checkout mode. If unchecked, OpenERP uses the normal cashier mode by default."),
- 'iface_websql' : fields.boolean('WebSQL (Faster but Chrome Only)',
- help="If have more than 200 products, it's highly suggested to use WebSQL "\
- "to store the data in the browser, instead of localStore mechanism. "\
- "It's more efficient but works on the Chrome browser only."
- ),
- 'iface_led' : fields.boolean('Help Notification'),
'iface_cashdrawer' : fields.boolean('Cashdrawer Interface'),
'iface_payment_terminal' : fields.boolean('Payment Terminal Interface'),
'iface_electronic_scale' : fields.boolean('Electronic Scale Interface'),
- 'iface_barscan' : fields.boolean('BarScan Interface'),
'iface_vkeyboard' : fields.boolean('Virtual KeyBoard Interface'),
'iface_print_via_proxy' : fields.boolean('Print via Proxy'),
@@ -77,7 +71,7 @@ class pos_config(osv.osv):
help="This sequence is automatically created by OpenERP but you can change it "\
"to customize the reference numbers of your orders."),
'session_ids': fields.one2many('pos.session', 'config_id', 'Sessions'),
- 'group_by' : fields.boolean('Group By', help="Check this if you want to group the Journal Items by Product while closing a Session"),
+ 'group_by' : fields.boolean('Group Journal Items', help="Check this if you want to group the Journal Items by Product while closing a Session"),
}
def name_get(self, cr, uid, ids, context=None):
@@ -93,7 +87,7 @@ class pos_config(osv.osv):
result.append((record.id, record.name+' ('+_('not used')+')'))
continue
session = record.session_ids[0]
- result.append((record.id, record.name + ' ('+session.user_id.name+', '+states[session.state]+')'))
+ result.append((record.id, record.name + ' ('+session.user_id.name+')')) #, '+states[session.state]+')'))
return result
@@ -143,8 +137,6 @@ class pos_config(osv.osv):
obj.sequence_id.unlink()
return super(pos_config, self).unlink(cr, uid, ids, context=context)
-pos_config()
-
class pos_session(osv.osv):
_name = 'pos.session'
_order = 'id desc'
@@ -459,8 +451,6 @@ class pos_session(osv.osv):
'context' : context,
}
-pos_session()
-
class pos_order(osv.osv):
_name = "pos.order"
_description = "Point of Sale"
@@ -1079,8 +1069,6 @@ class pos_order(osv.osv):
self.create_account_move(cr, uid, ids, context=context)
return True
-pos_order()
-
class account_bank_statement(osv.osv):
_inherit = 'account.bank.statement'
_columns= {
@@ -1179,8 +1167,6 @@ class pos_order_line(osv.osv):
})
return super(pos_order_line, self).copy_data(cr, uid, id, default, context=context)
-pos_order_line()
-
class pos_category(osv.osv):
_name = 'pos.category'
_description = "Point of Sale Category"
@@ -1230,6 +1216,11 @@ class pos_category(osv.osv):
'parent_id': fields.many2one('pos.category','Parent Category', select=True),
'child_id': fields.one2many('pos.category', 'parent_id', string='Children Categories'),
'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of product categories."),
+
+ # NOTE: there is no 'default image', because by default we don't show thumbnails for categories. However if we have a thumbnail
+ # for at least one category, then we display a default image on the other, so that the buttons have consistent styling.
+ # In this case, the default image is set by the js code.
+
'image': fields.binary("Image",
help="This field holds the image used for the category. "\
"The image is base64 encoded, and PIL-supported. "\
@@ -1252,21 +1243,40 @@ class pos_category(osv.osv):
"Use this field anywhere a small image is required."),
}
- def _get_default_image(self, cr, uid, context=None):
- image_path = openerp.modules.get_module_resource('point_of_sale', 'images', 'default_category_photo.png')
- return tools.image_resize_image_big(open(image_path, 'rb').read().encode('base64'))
-
- _defaults = {
- 'image': _get_default_image,
- }
-
-pos_category()
-
import io, StringIO
+class ean_wizard(osv.osv_memory):
+ _name = 'pos.ean_wizard'
+ _columns = {
+ 'ean13_pattern': fields.char('Ean13 Pattern', size=32, required=True, translate=True),
+ }
+ def sanitize_ean13(self, cr, uid, ids, context):
+ for r in self.browse(cr,uid,ids):
+ ean13 = openerp.addons.product.product.sanitize_ean13(r.ean13_pattern)
+ m = context.get('active_model')
+ m_id = context.get('active_id')
+ self.pool.get(m).write(cr,uid,[m_id],{'ean13':ean13})
+ return { 'type' : 'ir.actions.act_window_close' }
+
class product_product(osv.osv):
_inherit = 'product.product'
+
+ #def _get_small_image(self, cr, uid, ids, prop, unknow_none, context=None):
+ # result = {}
+ # for obj in self.browse(cr, uid, ids, context=context):
+ # if not obj.product_image:
+ # result[obj.id] = False
+ # continue
+
+ # image_stream = io.BytesIO(obj.product_image.decode('base64'))
+ # img = Image.open(image_stream)
+ # img.thumbnail((120, 100), Image.ANTIALIAS)
+ # img_stream = StringIO.StringIO()
+ # img.save(img_stream, "JPEG")
+ # result[obj.id] = img_stream.getvalue().encode('base64')
+ # return result
+
_columns = {
'income_pdt': fields.boolean('Point of Sale Cash In', help="This is a product you can use to put cash into a statement for the point of sale backend."),
'expense_pdt': fields.boolean('Point of Sale Cash Out', help="This is a product you can use to take cash from a statement for the point of sale backend, exemple: money lost, transfer to bank, etc."),
@@ -1278,7 +1288,16 @@ class product_product(osv.osv):
'to_weight' : False,
}
-product_product()
-
+ def edit_ean(self, cr, uid, ids, context):
+ return {
+ 'name': "Edit Ean",
+ 'type': 'ir.actions.act_window',
+ 'view_type': 'form',
+ 'view_mode': 'form',
+ 'res_model': 'pos.ean_wizard',
+ 'target' : 'new',
+ 'view_id': False,
+ 'context':context,
+ }
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/point_of_sale/point_of_sale_demo.xml b/addons/point_of_sale/point_of_sale_demo.xml
index 6836d0e7c4d..db8caaadf3b 100644
--- a/addons/point_of_sale/point_of_sale_demo.xml
+++ b/addons/point_of_sale/point_of_sale_demo.xml
@@ -1,12 +1,40 @@
+
+ 0410100000006
+
+ 0410200000005
-
+
+
+ John Smith (Cashier)
+ jsmith
+ 0410300000004
+
+
+
+ John Doe (Cashier)
+ jdoe
+ 0410400000003
+
+
+
+ Joe Bloggs (Client)
+ jbloggs
+ 0420100000005
+
+
+
+ Bob Soap (Client)
+ bsoap
+ 0420200000004
+
+
@@ -154,6 +182,7 @@
1.98Boni OrangesTrue
+ 2100002000003
@@ -248,6 +277,7 @@
5.10FishingTrue
+ 2300001000008
@@ -316,6 +346,7 @@
1.28OnionsTrue
+ 2100001000004
@@ -389,6 +420,7 @@
0.51Coca-Cola Regular 33cl
+ 5449000000996/9j/4AAQSkZJRgABAAEBLAEsAAD/2wCEAAoHBwgHBgoICAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4+JS5ESUM8SDc9PjsBCwoKDg0OHBYWHCgoKCgoOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O//AABEIAGQAOAMBEQACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/APZSQoJJwByTQBCt1HIxVOSOvtQBBe36WkDyySLGqAse5wKB2M1fEqHDYkZCu7ds4xQFi/Zaql7brPCVdGzjBweDjvQFix9ujVgsmULdM9/oelAiwrB1DKcgjINAFTVL6DT9PluLggIqnI9amUlFXZrQoyrTUY7s5OLWpJbINIs1ozjO6ICRfy4IrGOIT3R3VsslGTUJJ/g/8jPkngaXzZ9Wif2nDrn8wRWqq031OZ4LEL7D+Wo0ahp6uP8AiYaf5f8Ac81cflt/pT9pDuifqtf+SX3MVby2kuPMg1aBPRYSzlfYYGKTq0+5SwWJ/kf3WNy4muLTTUkJkvGlQspYCNcYHXqT1/8A1UqlTlV0h4fC+1m1J2szY8NanHquiQTLtWRB5cqD+Fhwf8fxopT543FjsK8PWcenT0OU8dXUlx4gtNK8wJF5aytlsbiWI/p+PHpXPipNySPZyWlGGHnVtd3t+A6bakaqvQDArJhC7d2Ub1AkJ3gcrke4qZaG9F3locBfMr3blVGAalI+joxtBHVeFirWwIUD6CnHc8fMk1I76JkudJj3n/Uttb6dv6V2RtKHofNyTp13bqZWgXMVp4xbT7NgIZomeRAcjI6GppSSqWR242nKpg/aT3TsjG+IJ2+NLY562i/+hNWeJ+M7sj1wUv8AF+iOTGq3trd7VuJDGH5TOQRmsEe08NTnDZXsdHrtzLcaLbm2eJmMzCBVJHmxMMgjnOQwYEZJycdxW9SKcVY8bBJQry5r7a+TWmvk1Zp9vmZFjoNlqGhwXEt6LK7mkdUeUZicg8KT/C1KMFKCd7M66+ZVqOIlFR5opLbdefmixPpuqeH7Ge4VZ4kDKoUhXVSe+7HzAjoeOeo6ZU6TimzOljKOMqRi2np5p/d0f9INK1u9udL1qKe6dybVWUdMfOAcY/3qVOXuy9C8ThKcK1Fxj9r9H/kaPw+H/FXRe1q9PC/GYZ4/9jf+JFj4h/8AI52h/wCnVf8A0JqrE/GjPIv9yn/i/RHGSQNPfCNMbnfaM+5rnWrse4pqFO76I39X1TSdL1A6GbQ3OnQoI5+fmEo6uno3r64ronOMXy20PHw2GxFel7fm5Zt3Xa3Z9127Fy2tZlsprvSpIdas3wZrdx88o/2l7SD+8OuOmetxi7Nx1X9ficdWrH2ihVTpyWz6L0f8r7dPTavqlzYf8Ii8dn/aUQFwgNtOTtgP905/h7jryO1TUcfZO1zbCU6v11OfI/deq6+fr38jn9PJR7he0tuy/lhv/Za5oPf0PZrK6i+0l/l+p1Xw7GfFg9rVv5itsL8Z5We/7p/28S/Ec7PF1m3/AE7L/wChNV4n40TkCvhJr+9+hyDTJFeq8jFVWQEkdRzXNH4j2nFyptLsXte0uOXVJwkPkSPKQGVyyFzyA2c7d2chuhyOnONqsLs4MFipRoxu7pL52XVd7dVuvuvL4UiSVEax2LqFqWeSA5WS4XqArZGPQjp3welOgrrTdGGaScW1P4JWs+i9VZ+qf5Brl/4hutFf7S9wLRZQk0U8IV0bquWAG4e/HPXtkqSqOOuw8DQwdOuuVLmtdNO6a66X0fkZNm25tw/55t/6Ca51uepU0XzX5nWfDZt/it/a1b+a1vhfj+R5Gfq2FX+L/MsfEqPzPFOnrnG63A/8eNaYle+jPh+XLhaj8/0PPrknz5Of4j/Oudn0kPhXodLpUyeIrKKx+1my1i2TZbXAcqJ0/uMR3Hatov2itezPExcHg6jny81OTu1vZ90NstMaCJbGY2+n6rBIzYu0AW4B6bZOoI6cGiELK2zM6+JU5c6vODX2Xt6r/NEGq6vq2paNKj3Mnk28qpc28gBKNztO7GSuR3OQcdampOco7muEwuHo101FXabT799Nr+n4GXo5LX8cQ/jyv5g1lBXaPSxNlSb7HW/C47vFEv8A16v/AOhLW2G+P5HlcR6YVf4v0ZpfEhf+Km0pvWIj/wAerTEfGjkyF/7NV9Tzi5/4+JM/3j/OuZn1EPhXoafhq1mu5b6KGW3jLWpXNwPlyWULhv4WyQQfUVdKLd/Q87M6saag2m/e6b7O+nVW3RuJrerafbNZ+INMj1JIRl0mH7xF/vZIIYe4/E1sqk0rSVzyZYPD1Z8+HqOF+23p3T8vuMzVvEdpe6VJp+kaQllA7LJOwxlsHgce+KzqVU42irHbg8vqU6yqVajk9kUfC8XneI7KPrmT+hqKKvNHdmMuXCzfkdP8K+fFEx7C0b/0JK0w3x/I83iP/do/4v0Zp/EzP9u6QV67G7e4q8T8SOTh+3sK1/L9Tzi9QR3cqhg2G6iuaW59PSleCZo+GEuPt8skLwCOOL9+lxnZIhIBBwOnIOe2M1VK/McGaSh7NJp3b0tunvp/l12Oy0W4t5fM+yalA1rEGVrS+Ic2zYI+V8/Mvb0wetddNp7PTz6HzuMhONueD5nb3o6c3quj/U5e401IfAQvFaJXmvizYP3lG5QF9ccn8a55w/dX8z2KWIcsx5NbKH52eongGEzeLrQ44jDuf++SP5kVOGV6iNs7ny4Kfnb8zc+FA/4qS79rVv8A0Na0wy/efI5OJH/s8P8AF+jND4mNjxBpABx8jc/jV4j4kc+QL9xV+R59er5upugO3e4GW7Z71yvVn0dJ8tFPyNa10uXStUeay1FWktUdnjERMnysEZWjz05z16c1pGDjK6Z5lbExxFFKcNJNa301V00/61NaaDwta3gv5RcWnygSwxLuhcsuSmMZXr0OOlbctNO+x50amPqQ5FaXZvRqz38/VXOYvLoz+HrKGMkRW9xMAp9DtIz+ZrCbvBep69Cny4mbe7jH9U/0Oj+GNsDf318w4ghCA+7HP/staYRe82cHEdT91CHd/l/w5rfC3TZba/kvJAdtzbNs9MB1zVYaOt/Iw4hxMZpQX2WvyY74lRyHxLpUgU7PKOGxnkNk/pijEfGgyGUfq1VeZ57qY238w56965pbn0lB3pI6W1jm1+KLWNIl8vXLJQJ4sj9+AMBxnjJHBHf+eyTn70d0eHWccJJ0aqvSls+3l/kEGseG2upptV0y4srxlZLiGIHy5SeuVPQ559jzmqjOm3qrEVMJjVFKlNSj0b3XzOdtoTNpGpOisIYXikBPY5Kgfkx/KsbXiz1JS5a9NPdpr8L/AKHoHhi0/sXwBc3kg2yTwyTn6bcKP6/jXVQjyUmz57Mav1nMowWyaX46mv4Jkid7ZIoyPL06Pe/GCSE/wP5GrpWtH0OLMIy9rVb61P8AP/gHTappNnrFqba8i3qDlWBwyH1B7GrnBSVmcmHxFShLmgzzTxJ8OprWRri3vg6N084Yx9SOK5pYZ9GfQ4XiNRjacPu/yf8Amc1BpuoaTdJdW9zGssZyro+B/wCPYzUKjUi7o6amc4HEQcZ3s+6/yN3/AISa11CMJ4g0S2uZFGPNhljyfwzkfnW2sviieW1Sou9DEWXZ3/y/QjnuLXVUh0uysU0zS/NEk77wWkx9M/5xSlTcvdSsi6eMp0G6sqvPO1lo7I7prBPEejvp8BNvauFQyDBwoI4XqO2K3lFSjY8nD4mVGsqlrtfmbOi6HYaDZi1sItinlmJyzn1JpU6cYKyFisXVxM+abuaB6mrZziEBhggEHsaAM668PaPekm406Bye+3H8qVkO7M5vAPhlm3HTh9PMb/GiwcxetPDGh2JBt9MgUjoSu4/rmiyC7NQAKAFAAHQCmIUUIAAA/9k=
@@ -1022,3 +1054,4 @@
+
diff --git a/addons/point_of_sale/point_of_sale_view.xml b/addons/point_of_sale/point_of_sale_view.xml
index 47a41260378..6487eebbe4f 100644
--- a/addons/point_of_sale/point_of_sale_view.xml
+++ b/addons/point_of_sale/point_of_sale_view.xml
@@ -55,10 +55,14 @@
-
+
-
-
+
+
@@ -630,6 +634,9 @@
+
+
+
@@ -773,10 +780,10 @@
-
+
-
-
+
+
@@ -791,14 +798,11 @@
-
-
-
@@ -877,7 +881,7 @@
class="oe_highlight"/>
-
+
@@ -888,7 +892,7 @@
- Point of Sale Session:
+ Session:
@@ -1057,5 +1061,20 @@
parent="menu_point_of_sale"
id="menu_pos_session_opening" sequence="0"/>
+
+ pos.ean_wizard
+ pos.ean_wizard
+ form
+
+
+
+
+
+
diff --git a/addons/point_of_sale/report/pos_sales_user.py b/addons/point_of_sale/report/pos_sales_user.py
index e49528441b9..6ac2e1892c1 100644
--- a/addons/point_of_sale/report/pos_sales_user.py
+++ b/addons/point_of_sale/report/pos_sales_user.py
@@ -37,14 +37,14 @@ class pos_sales_user(report_sxw.rml_parse):
dt1 = form['date_start'] + ' 00:00:00'
dt2 = form['date_end'] + ' 23:59:59'
data={}
- self.cr.execute("select po.name as pos,po.date_order,ru.name as user,po.state,rc.name " \
- "from pos_order as po,res_users as ru,res_company as rc " \
+ self.cr.execute("select po.name as pos,po.date_order,rp.name as user,po.state,rc.name " \
+ "from pos_order as po,res_users as ru,res_company as rc, res_partner as rp " \
"where po.date_order >= %s and po.date_order <= %s " \
- "and po.company_id=rc.id and po.user_id=ru.id and po.user_id IN %s " \
+ "and po.company_id=rc.id and po.user_id=ru.id and po.user_id IN %s and ru.partner_id = rp.id" \
,(dt1,dt2,tuple(form['user_id'])))
return self.cr.dictfetchall()
report_sxw.report_sxw('report.pos.sales.user', 'pos.order', 'addons/point_of_sale/report/pos_sales_user.rml', parser=pos_sales_user,header='internal')
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
\ No newline at end of file
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/point_of_sale/res_users.py b/addons/point_of_sale/res_users.py
index d64f05d1391..f1c613d5f06 100644
--- a/addons/point_of_sale/res_users.py
+++ b/addons/point_of_sale/res_users.py
@@ -1,38 +1,8 @@
#!/usr/bin/env python
from osv import osv, fields
import math
+import openerp.addons.product.product
-def is_pair(x):
- return not x%2
-# This code is a duplicate of product#check_ean function
-def check_ean(eancode):
- if not eancode:
- return True
- if len(eancode) <> 13:
- return False
- try:
- int(eancode)
- except:
- return False
- oddsum=0
- evensum=0
- total=0
- eanvalue=eancode
- reversevalue = eanvalue[::-1]
- finalean=reversevalue[1:]
-
- for i in range(len(finalean)):
- if is_pair(i):
- oddsum += int(finalean[i])
- else:
- evensum += int(finalean[i])
- total=(oddsum * 3) + evensum
-
- check = int(10 - math.ceil(total % 10.0)) %10
-
- if check != int(eancode[-1]):
- return False
- return True
class res_users(osv.osv):
_inherit = 'res.users'
@@ -43,10 +13,22 @@ class res_users(osv.osv):
def _check_ean(self, cr, uid, ids, context=None):
return all(
- check_ean(user.ean13) == True
+ openerp.addons.product.product.check_ean(user.ean13) == True
for user in self.browse(cr, uid, ids, context=context)
)
+ def edit_ean(self, cr, uid, ids, context):
+ return {
+ 'name': "Edit Ean",
+ 'type': 'ir.actions.act_window',
+ 'view_type': 'form',
+ 'view_mode': 'form',
+ 'res_model': 'pos.ean_wizard',
+ 'target' : 'new',
+ 'view_id': False,
+ 'context':context,
+ }
+
_constraints = [
(_check_ean, "Error: Invalid ean code", ['ean13'],),
]
diff --git a/addons/point_of_sale/res_users_view.xml b/addons/point_of_sale/res_users_view.xml
index f561ccf9b80..2a510b1d575 100644
--- a/addons/point_of_sale/res_users_view.xml
+++ b/addons/point_of_sale/res_users_view.xml
@@ -20,8 +20,11 @@
-
-
+
+
+
+
+
diff --git a/addons/point_of_sale/security/ir.model.access.csv b/addons/point_of_sale/security/ir.model.access.csv
index 06673fc3da8..74b4bbf5753 100644
--- a/addons/point_of_sale/security/ir.model.access.csv
+++ b/addons/point_of_sale/security/ir.model.access.csv
@@ -60,7 +60,8 @@ access_product_uom_manager,product.uom manager,product.model_product_uom,group_p
access_res_partner_manager,res.partner manager,base.model_res_partner,group_pos_manager,1,0,0,0
access_product_category_manager,product.category manager,product.model_product_category,group_pos_manager,1,1,1,1
access_product_pricelist_manager,product.pricelist manager,product.model_product_pricelist,group_pos_manager,1,0,0,0
-access_product_category_pos_manager,pos.category manager,model_pos_category,group_pos_manager,1,1,1,"1"""
-access_product_category_pos_user,pos.category user,model_pos_category,group_pos_user,1,0,0,"0"""
+access_product_category_pos_manager,pos.category manager,model_pos_category,group_pos_manager,1,1,1,1
+access_product_category_pos_user,pos.category user,model_pos_category,group_pos_user,1,0,0,0
access_pos_session_user,pos.session user,model_pos_session,group_pos_user,1,1,1,0
access_pos_config_user,pos.config user,model_pos_config,group_pos_user,1,1,1,0
+access_ir_sequence_manager,ir.sequence manager,base.model_ir_sequence,group_pos_manager,1,1,1,1
diff --git a/addons/point_of_sale/static/lib/backbone/backbone-0.5.3.js b/addons/point_of_sale/static/lib/backbone/backbone-0.5.3.js
deleted file mode 100644
index 8958c798466..00000000000
--- a/addons/point_of_sale/static/lib/backbone/backbone-0.5.3.js
+++ /dev/null
@@ -1,1158 +0,0 @@
-// Backbone.js 0.5.3
-// (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.
-// Backbone may be freely distributed under the MIT license.
-// For all details and documentation:
-// http://documentcloud.github.com/backbone
-
-(function(){
-
- // Initial Setup
- // -------------
-
- // Save a reference to the global object.
- var root = this;
-
- // Save the previous value of the `Backbone` variable.
- var previousBackbone = root.Backbone;
-
- // The top-level namespace. All public Backbone classes and modules will
- // be attached to this. Exported for both CommonJS and the browser.
- var Backbone;
- if (typeof exports !== 'undefined') {
- Backbone = exports;
- } else {
- Backbone = root.Backbone = {};
- }
-
- // Current version of the library. Keep in sync with `package.json`.
- Backbone.VERSION = '0.5.3';
-
- // Require Underscore, if we're on the server, and it's not already present.
- var _ = root._;
- if (!_ && (typeof require !== 'undefined')) _ = require('underscore')._;
-
- // For Backbone's purposes, jQuery or Zepto owns the `$` variable.
- var $ = root.jQuery || root.Zepto;
-
- // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
- // to its previous owner. Returns a reference to this Backbone object.
- Backbone.noConflict = function() {
- root.Backbone = previousBackbone;
- return this;
- };
-
- // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option will
- // fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and set a
- // `X-Http-Method-Override` header.
- Backbone.emulateHTTP = false;
-
- // Turn on `emulateJSON` to support legacy servers that can't deal with direct
- // `application/json` requests ... will encode the body as
- // `application/x-www-form-urlencoded` instead and will send the model in a
- // form param named `model`.
- Backbone.emulateJSON = false;
-
- // Backbone.Events
- // -----------------
-
- // A module that can be mixed in to *any object* in order to provide it with
- // custom events. You may `bind` or `unbind` a callback function to an event;
- // `trigger`-ing an event fires all callbacks in succession.
- //
- // var object = {};
- // _.extend(object, Backbone.Events);
- // object.bind('expand', function(){ alert('expanded'); });
- // object.trigger('expand');
- //
- Backbone.Events = {
-
- // Bind an event, specified by a string name, `ev`, to a `callback` function.
- // Passing `"all"` will bind the callback to all events fired.
- bind : function(ev, callback, context) {
- var calls = this._callbacks || (this._callbacks = {});
- var list = calls[ev] || (calls[ev] = []);
- list.push([callback, context]);
- return this;
- },
-
- // Remove one or many callbacks. If `callback` is null, removes all
- // callbacks for the event. If `ev` is null, removes all bound callbacks
- // for all events.
- unbind : function(ev, callback) {
- var calls;
- if (!ev) {
- this._callbacks = {};
- } else if (calls = this._callbacks) {
- if (!callback) {
- calls[ev] = [];
- } else {
- var list = calls[ev];
- if (!list) return this;
- for (var i = 0, l = list.length; i < l; i++) {
- if (list[i] && callback === list[i][0]) {
- list[i] = null;
- break;
- }
- }
- }
- }
- return this;
- },
-
- // Trigger an event, firing all bound callbacks. Callbacks are passed the
- // same arguments as `trigger` is, apart from the event name.
- // Listening for `"all"` passes the true event name as the first argument.
- trigger : function(eventName) {
- var list, calls, ev, callback, args;
- var both = 2;
- if (!(calls = this._callbacks)) return this;
- while (both--) {
- ev = both ? eventName : 'all';
- if (list = calls[ev]) {
- for (var i = 0, l = list.length; i < l; i++) {
- if (!(callback = list[i])) {
- list.splice(i, 1); i--; l--;
- } else {
- args = both ? Array.prototype.slice.call(arguments, 1) : arguments;
- callback[0].apply(callback[1] || this, args);
- }
- }
- }
- }
- return this;
- }
-
- };
-
- // Backbone.Model
- // --------------
-
- // Create a new model, with defined attributes. A client id (`cid`)
- // is automatically generated and assigned for you.
- Backbone.Model = function(attributes, options) {
- var defaults;
- attributes || (attributes = {});
- if (defaults = this.defaults) {
- if (_.isFunction(defaults)) defaults = defaults.call(this);
- attributes = _.extend({}, defaults, attributes);
- }
- this.attributes = {};
- this._escapedAttributes = {};
- this.cid = _.uniqueId('c');
- this.set(attributes, {silent : true});
- this._changed = false;
- this._previousAttributes = _.clone(this.attributes);
- if (options && options.collection) this.collection = options.collection;
- this.initialize(attributes, options);
- };
-
- // Attach all inheritable methods to the Model prototype.
- _.extend(Backbone.Model.prototype, Backbone.Events, {
-
- // A snapshot of the model's previous attributes, taken immediately
- // after the last `"change"` event was fired.
- _previousAttributes : null,
-
- // Has the item been changed since the last `"change"` event?
- _changed : false,
-
- // The default name for the JSON `id` attribute is `"id"`. MongoDB and
- // CouchDB users may want to set this to `"_id"`.
- idAttribute : 'id',
-
- // Initialize is an empty function by default. Override it with your own
- // initialization logic.
- initialize : function(){},
-
- // Return a copy of the model's `attributes` object.
- toJSON : function() {
- return _.clone(this.attributes);
- },
-
- // Get the value of an attribute.
- get : function(attr) {
- return this.attributes[attr];
- },
-
- // Get the HTML-escaped value of an attribute.
- escape : function(attr) {
- var html;
- if (html = this._escapedAttributes[attr]) return html;
- var val = this.attributes[attr];
- return this._escapedAttributes[attr] = escapeHTML(val == null ? '' : '' + val);
- },
-
- // Returns `true` if the attribute contains a value that is not null
- // or undefined.
- has : function(attr) {
- return this.attributes[attr] != null;
- },
-
- // Set a hash of model attributes on the object, firing `"change"` unless you
- // choose to silence it.
- set : function(attrs, options) {
-
- // Extract attributes and options.
- options || (options = {});
- if (!attrs) return this;
- if (attrs.attributes) attrs = attrs.attributes;
- var now = this.attributes, escaped = this._escapedAttributes;
-
- // Run validation.
- if (!options.silent && this.validate && !this._performValidation(attrs, options)) return false;
-
- // Check for changes of `id`.
- if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
-
- // We're about to start triggering change events.
- var alreadyChanging = this._changing;
- this._changing = true;
-
- // Update attributes.
- for (var attr in attrs) {
- var val = attrs[attr];
- if (!_.isEqual(now[attr], val)) {
- now[attr] = val;
- delete escaped[attr];
- this._changed = true;
- if (!options.silent) this.trigger('change:' + attr, this, val, options);
- }
- }
-
- // Fire the `"change"` event, if the model has been changed.
- if (!alreadyChanging && !options.silent && this._changed) this.change(options);
- this._changing = false;
- return this;
- },
-
- // Remove an attribute from the model, firing `"change"` unless you choose
- // to silence it. `unset` is a noop if the attribute doesn't exist.
- unset : function(attr, options) {
- if (!(attr in this.attributes)) return this;
- options || (options = {});
- var value = this.attributes[attr];
-
- // Run validation.
- var validObj = {};
- validObj[attr] = void 0;
- if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false;
-
- // Remove the attribute.
- delete this.attributes[attr];
- delete this._escapedAttributes[attr];
- if (attr == this.idAttribute) delete this.id;
- this._changed = true;
- if (!options.silent) {
- this.trigger('change:' + attr, this, void 0, options);
- this.change(options);
- }
- return this;
- },
-
- // Clear all attributes on the model, firing `"change"` unless you choose
- // to silence it.
- clear : function(options) {
- options || (options = {});
- var attr;
- var old = this.attributes;
-
- // Run validation.
- var validObj = {};
- for (attr in old) validObj[attr] = void 0;
- if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false;
-
- this.attributes = {};
- this._escapedAttributes = {};
- this._changed = true;
- if (!options.silent) {
- for (attr in old) {
- this.trigger('change:' + attr, this, void 0, options);
- }
- this.change(options);
- }
- return this;
- },
-
- // Fetch the model from the server. If the server's representation of the
- // model differs from its current attributes, they will be overriden,
- // triggering a `"change"` event.
- fetch : function(options) {
- options || (options = {});
- var model = this;
- var success = options.success;
- options.success = function(resp, status, xhr) {
- if (!model.set(model.parse(resp, xhr), options)) return false;
- if (success) success(model, resp);
- };
- options.error = wrapError(options.error, model, options);
- return (this.sync || Backbone.sync).call(this, 'read', this, options);
- },
-
- // Set a hash of model attributes, and sync the model to the server.
- // If the server returns an attributes hash that differs, the model's
- // state will be `set` again.
- save : function(attrs, options) {
- options || (options = {});
- if (attrs && !this.set(attrs, options)) return false;
- var model = this;
- var success = options.success;
- options.success = function(resp, status, xhr) {
- if (!model.set(model.parse(resp, xhr), options)) return false;
- if (success) success(model, resp, xhr);
- };
- options.error = wrapError(options.error, model, options);
- var method = this.isNew() ? 'create' : 'update';
- return (this.sync || Backbone.sync).call(this, method, this, options);
- },
-
- // Destroy this model on the server if it was already persisted. Upon success, the model is removed
- // from its collection, if it has one.
- destroy : function(options) {
- options || (options = {});
- if (this.isNew()) return this.trigger('destroy', this, this.collection, options);
- var model = this;
- var success = options.success;
- options.success = function(resp) {
- model.trigger('destroy', model, model.collection, options);
- if (success) success(model, resp);
- };
- options.error = wrapError(options.error, model, options);
- return (this.sync || Backbone.sync).call(this, 'delete', this, options);
- },
-
- // Default URL for the model's representation on the server -- if you're
- // using Backbone's restful methods, override this to change the endpoint
- // that will be called.
- url : function() {
- var base = getUrl(this.collection) || this.urlRoot || urlError();
- if (this.isNew()) return base;
- return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
- },
-
- // **parse** converts a response into the hash of attributes to be `set` on
- // the model. The default implementation is just to pass the response along.
- parse : function(resp, xhr) {
- return resp;
- },
-
- // Create a new model with identical attributes to this one.
- clone : function() {
- return new this.constructor(this);
- },
-
- // A model is new if it has never been saved to the server, and lacks an id.
- isNew : function() {
- return this.id == null;
- },
-
- // Call this method to manually fire a `change` event for this model.
- // Calling this will cause all objects observing the model to update.
- change : function(options) {
- this.trigger('change', this, options);
- this._previousAttributes = _.clone(this.attributes);
- this._changed = false;
- },
-
- // Determine if the model has changed since the last `"change"` event.
- // If you specify an attribute name, determine if that attribute has changed.
- hasChanged : function(attr) {
- if (attr) return this._previousAttributes[attr] != this.attributes[attr];
- return this._changed;
- },
-
- // Return an object containing all the attributes that have changed, or false
- // if there are no changed attributes. Useful for determining what parts of a
- // view need to be updated and/or what attributes need to be persisted to
- // the server.
- changedAttributes : function(now) {
- now || (now = this.attributes);
- var old = this._previousAttributes;
- var changed = false;
- for (var attr in now) {
- if (!_.isEqual(old[attr], now[attr])) {
- changed = changed || {};
- changed[attr] = now[attr];
- }
- }
- return changed;
- },
-
- // Get the previous value of an attribute, recorded at the time the last
- // `"change"` event was fired.
- previous : function(attr) {
- if (!attr || !this._previousAttributes) return null;
- return this._previousAttributes[attr];
- },
-
- // Get all of the attributes of the model at the time of the previous
- // `"change"` event.
- previousAttributes : function() {
- return _.clone(this._previousAttributes);
- },
-
- // Run validation against a set of incoming attributes, returning `true`
- // if all is well. If a specific `error` callback has been passed,
- // call that instead of firing the general `"error"` event.
- _performValidation : function(attrs, options) {
- var error = this.validate(attrs);
- if (error) {
- if (options.error) {
- options.error(this, error, options);
- } else {
- this.trigger('error', this, error, options);
- }
- return false;
- }
- return true;
- }
-
- });
-
- // Backbone.Collection
- // -------------------
-
- // Provides a standard collection class for our sets of models, ordered
- // or unordered. If a `comparator` is specified, the Collection will maintain
- // its models in sort order, as they're added and removed.
- Backbone.Collection = function(models, options) {
- options || (options = {});
- if (options.comparator) this.comparator = options.comparator;
- _.bindAll(this, '_onModelEvent', '_removeReference');
- this._reset();
- if (models) this.reset(models, {silent: true});
- this.initialize.apply(this, arguments);
- };
-
- // Define the Collection's inheritable methods.
- _.extend(Backbone.Collection.prototype, Backbone.Events, {
-
- // The default model for a collection is just a **Backbone.Model**.
- // This should be overridden in most cases.
- model : Backbone.Model,
-
- // Initialize is an empty function by default. Override it with your own
- // initialization logic.
- initialize : function(){},
-
- // The JSON representation of a Collection is an array of the
- // models' attributes.
- toJSON : function() {
- return this.map(function(model){ return model.toJSON(); });
- },
-
- // Add a model, or list of models to the set. Pass **silent** to avoid
- // firing the `added` event for every new model.
- add : function(models, options) {
- if (_.isArray(models)) {
- for (var i = 0, l = models.length; i < l; i++) {
- this._add(models[i], options);
- }
- } else {
- this._add(models, options);
- }
- return this;
- },
-
- // Remove a model, or a list of models from the set. Pass silent to avoid
- // firing the `removed` event for every model removed.
- remove : function(models, options) {
- if (_.isArray(models)) {
- for (var i = 0, l = models.length; i < l; i++) {
- this._remove(models[i], options);
- }
- } else {
- this._remove(models, options);
- }
- return this;
- },
-
- // Get a model from the set by id.
- get : function(id) {
- if (id == null) return null;
- return this._byId[id.id != null ? id.id : id];
- },
-
- // Get a model from the set by client id.
- getByCid : function(cid) {
- return cid && this._byCid[cid.cid || cid];
- },
-
- // Get the model at the given index.
- at: function(index) {
- return this.models[index];
- },
-
- // Force the collection to re-sort itself. You don't need to call this under normal
- // circumstances, as the set will maintain sort order as each item is added.
- sort : function(options) {
- options || (options = {});
- if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
- this.models = this.sortBy(this.comparator);
- if (!options.silent) this.trigger('reset', this, options);
- return this;
- },
-
- // Pluck an attribute from each model in the collection.
- pluck : function(attr) {
- return _.map(this.models, function(model){ return model.get(attr); });
- },
-
- // When you have more items than you want to add or remove individually,
- // you can reset the entire set with a new list of models, without firing
- // any `added` or `removed` events. Fires `reset` when finished.
- reset : function(models, options) {
- models || (models = []);
- options || (options = {});
- this.each(this._removeReference);
- this._reset();
- this.add(models, {silent: true});
- if (!options.silent) this.trigger('reset', this, options);
- return this;
- },
-
- // Fetch the default set of models for this collection, resetting the
- // collection when they arrive. If `add: true` is passed, appends the
- // models to the collection instead of resetting.
- fetch : function(options) {
- options || (options = {});
- var collection = this;
- var success = options.success;
- options.success = function(resp, status, xhr) {
- collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
- if (success) success(collection, resp);
- };
- options.error = wrapError(options.error, collection, options);
- return (this.sync || Backbone.sync).call(this, 'read', this, options);
- },
-
- // Create a new instance of a model in this collection. After the model
- // has been created on the server, it will be added to the collection.
- // Returns the model, or 'false' if validation on a new model fails.
- create : function(model, options) {
- var coll = this;
- options || (options = {});
- model = this._prepareModel(model, options);
- if (!model) return false;
- var success = options.success;
- options.success = function(nextModel, resp, xhr) {
- coll.add(nextModel, options);
- if (success) success(nextModel, resp, xhr);
- };
- model.save(null, options);
- return model;
- },
-
- // **parse** converts a response into a list of models to be added to the
- // collection. The default implementation is just to pass it through.
- parse : function(resp, xhr) {
- return resp;
- },
-
- // Proxy to _'s chain. Can't be proxied the same way the rest of the
- // underscore methods are proxied because it relies on the underscore
- // constructor.
- chain: function () {
- return _(this.models).chain();
- },
-
- // Reset all internal state. Called when the collection is reset.
- _reset : function(options) {
- this.length = 0;
- this.models = [];
- this._byId = {};
- this._byCid = {};
- },
-
- // Prepare a model to be added to this collection
- _prepareModel: function(model, options) {
- if (!(model instanceof Backbone.Model)) {
- var attrs = model;
- model = new this.model(attrs, {collection: this});
- if (model.validate && !model._performValidation(attrs, options)) model = false;
- } else if (!model.collection) {
- model.collection = this;
- }
- return model;
- },
-
- // Internal implementation of adding a single model to the set, updating
- // hash indexes for `id` and `cid` lookups.
- // Returns the model, or 'false' if validation on a new model fails.
- _add : function(model, options) {
- options || (options = {});
- model = this._prepareModel(model, options);
- if (!model) return false;
- var already = this.getByCid(model);
- if (already) throw new Error(["Can't add the same model to a set twice", already.id]);
- this._byId[model.id] = model;
- this._byCid[model.cid] = model;
- var index = options.at != null ? options.at :
- this.comparator ? this.sortedIndex(model, this.comparator) :
- this.length;
- this.models.splice(index, 0, model);
- model.bind('all', this._onModelEvent);
- this.length++;
- if (!options.silent) model.trigger('add', model, this, options);
- return model;
- },
-
- // Internal implementation of removing a single model from the set, updating
- // hash indexes for `id` and `cid` lookups.
- _remove : function(model, options) {
- options || (options = {});
- model = this.getByCid(model) || this.get(model);
- if (!model) return null;
- delete this._byId[model.id];
- delete this._byCid[model.cid];
- this.models.splice(this.indexOf(model), 1);
- this.length--;
- if (!options.silent) model.trigger('remove', model, this, options);
- this._removeReference(model);
- return model;
- },
-
- // Internal method to remove a model's ties to a collection.
- _removeReference : function(model) {
- if (this == model.collection) {
- delete model.collection;
- }
- model.unbind('all', this._onModelEvent);
- },
-
- // Internal method called every time a model in the set fires an event.
- // Sets need to update their indexes when models change ids. All other
- // events simply proxy through. "add" and "remove" events that originate
- // in other collections are ignored.
- _onModelEvent : function(ev, model, collection, options) {
- if ((ev == 'add' || ev == 'remove') && collection != this) return;
- if (ev == 'destroy') {
- this._remove(model, options);
- }
- if (model && ev === 'change:' + model.idAttribute) {
- delete this._byId[model.previous(model.idAttribute)];
- this._byId[model.id] = model;
- }
- this.trigger.apply(this, arguments);
- }
-
- });
-
- // Underscore methods that we want to implement on the Collection.
- var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', 'detect',
- 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
- 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', 'toArray', 'size',
- 'first', 'rest', 'last', 'without', 'indexOf', 'lastIndexOf', 'isEmpty', 'groupBy'];
-
- // Mix in each Underscore method as a proxy to `Collection#models`.
- _.each(methods, function(method) {
- Backbone.Collection.prototype[method] = function() {
- return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
- };
- });
-
- // Backbone.Router
- // -------------------
-
- // Routers map faux-URLs to actions, and fire events when routes are
- // matched. Creating a new one sets its `routes` hash, if not set statically.
- Backbone.Router = function(options) {
- options || (options = {});
- if (options.routes) this.routes = options.routes;
- this._bindRoutes();
- this.initialize.apply(this, arguments);
- };
-
- // Cached regular expressions for matching named param parts and splatted
- // parts of route strings.
- var namedParam = /:([\w\d]+)/g;
- var splatParam = /\*([\w\d]+)/g;
- var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
-
- // Set up all inheritable **Backbone.Router** properties and methods.
- _.extend(Backbone.Router.prototype, Backbone.Events, {
-
- // Initialize is an empty function by default. Override it with your own
- // initialization logic.
- initialize : function(){},
-
- // Manually bind a single named route to a callback. For example:
- //
- // this.route('search/:query/p:num', 'search', function(query, num) {
- // ...
- // });
- //
- route : function(route, name, callback) {
- Backbone.history || (Backbone.history = new Backbone.History);
- if (!_.isRegExp(route)) route = this._routeToRegExp(route);
- Backbone.history.route(route, _.bind(function(fragment) {
- var args = this._extractParameters(route, fragment);
- callback.apply(this, args);
- this.trigger.apply(this, ['route:' + name].concat(args));
- }, this));
- },
-
- // Simple proxy to `Backbone.history` to save a fragment into the history.
- navigate : function(fragment, triggerRoute) {
- Backbone.history.navigate(fragment, triggerRoute);
- },
-
- // Bind all defined routes to `Backbone.history`. We have to reverse the
- // order of the routes here to support behavior where the most general
- // routes can be defined at the bottom of the route map.
- _bindRoutes : function() {
- if (!this.routes) return;
- var routes = [];
- for (var route in this.routes) {
- routes.unshift([route, this.routes[route]]);
- }
- for (var i = 0, l = routes.length; i < l; i++) {
- this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
- }
- },
-
- // Convert a route string into a regular expression, suitable for matching
- // against the current location hash.
- _routeToRegExp : function(route) {
- route = route.replace(escapeRegExp, "\\$&")
- .replace(namedParam, "([^\/]*)")
- .replace(splatParam, "(.*?)");
- return new RegExp('^' + route + '$');
- },
-
- // Given a route, and a URL fragment that it matches, return the array of
- // extracted parameters.
- _extractParameters : function(route, fragment) {
- return route.exec(fragment).slice(1);
- }
-
- });
-
- // Backbone.History
- // ----------------
-
- // Handles cross-browser history management, based on URL fragments. If the
- // browser does not support `onhashchange`, falls back to polling.
- Backbone.History = function() {
- this.handlers = [];
- _.bindAll(this, 'checkUrl');
- };
-
- // Cached regex for cleaning hashes.
- var hashStrip = /^#*/;
-
- // Cached regex for detecting MSIE.
- var isExplorer = /msie [\w.]+/;
-
- // Has the history handling already been started?
- var historyStarted = false;
-
- // Set up all inheritable **Backbone.History** properties and methods.
- _.extend(Backbone.History.prototype, {
-
- // The default interval to poll for hash changes, if necessary, is
- // twenty times a second.
- interval: 50,
-
- // Get the cross-browser normalized URL fragment, either from the URL,
- // the hash, or the override.
- getFragment : function(fragment, forcePushState) {
- if (fragment == null) {
- if (this._hasPushState || forcePushState) {
- fragment = window.location.pathname;
- var search = window.location.search;
- if (search) fragment += search;
- if (fragment.indexOf(this.options.root) == 0) fragment = fragment.substr(this.options.root.length);
- } else {
- fragment = window.location.hash;
- }
- }
- return decodeURIComponent(fragment.replace(hashStrip, ''));
- },
-
- // Start the hash change handling, returning `true` if the current URL matches
- // an existing route, and `false` otherwise.
- start : function(options) {
-
- // Figure out the initial configuration. Do we need an iframe?
- // Is pushState desired ... is it available?
- if (historyStarted) throw new Error("Backbone.history has already been started");
- this.options = _.extend({}, {root: '/'}, this.options, options);
- this._wantsPushState = !!this.options.pushState;
- this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);
- var fragment = this.getFragment();
- var docMode = document.documentMode;
- var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
- if (oldIE) {
- this.iframe = $('').hide().appendTo('body')[0].contentWindow;
- this.navigate(fragment);
- }
-
- // Depending on whether we're using pushState or hashes, and whether
- // 'onhashchange' is supported, determine how we check the URL state.
- if (this._hasPushState) {
- $(window).bind('popstate', this.checkUrl);
- } else if ('onhashchange' in window && !oldIE) {
- $(window).bind('hashchange', this.checkUrl);
- } else {
- setInterval(this.checkUrl, this.interval);
- }
-
- // Determine if we need to change the base url, for a pushState link
- // opened by a non-pushState browser.
- this.fragment = fragment;
- historyStarted = true;
- var loc = window.location;
- var atRoot = loc.pathname == this.options.root;
- if (this._wantsPushState && !this._hasPushState && !atRoot) {
- this.fragment = this.getFragment(null, true);
- window.location.replace(this.options.root + '#' + this.fragment);
- // Return immediately as browser will do redirect to new url
- return true;
- } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
- this.fragment = loc.hash.replace(hashStrip, '');
- window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
- }
-
- if (!this.options.silent) {
- return this.loadUrl();
- }
- },
-
- // Add a route to be tested when the fragment changes. Routes added later may
- // override previous routes.
- route : function(route, callback) {
- this.handlers.unshift({route : route, callback : callback});
- },
-
- // Checks the current URL to see if it has changed, and if it has,
- // calls `loadUrl`, normalizing across the hidden iframe.
- checkUrl : function(e) {
- var current = this.getFragment();
- if (current == this.fragment && this.iframe) current = this.getFragment(this.iframe.location.hash);
- if (current == this.fragment || current == decodeURIComponent(this.fragment)) return false;
- if (this.iframe) this.navigate(current);
- this.loadUrl() || this.loadUrl(window.location.hash);
- },
-
- // Attempt to load the current URL fragment. If a route succeeds with a
- // match, returns `true`. If no defined routes matches the fragment,
- // returns `false`.
- loadUrl : function(fragmentOverride) {
- var fragment = this.fragment = this.getFragment(fragmentOverride);
- var matched = _.any(this.handlers, function(handler) {
- if (handler.route.test(fragment)) {
- handler.callback(fragment);
- return true;
- }
- });
- return matched;
- },
-
- // Save a fragment into the hash history. You are responsible for properly
- // URL-encoding the fragment in advance. This does not trigger
- // a `hashchange` event.
- navigate : function(fragment, triggerRoute) {
- var frag = (fragment || '').replace(hashStrip, '');
- if (this.fragment == frag || this.fragment == decodeURIComponent(frag)) return;
- if (this._hasPushState) {
- var loc = window.location;
- if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
- this.fragment = frag;
- window.history.pushState({}, document.title, loc.protocol + '//' + loc.host + frag);
- } else {
- window.location.hash = this.fragment = frag;
- if (this.iframe && (frag != this.getFragment(this.iframe.location.hash))) {
- this.iframe.document.open().close();
- this.iframe.location.hash = frag;
- }
- }
- if (triggerRoute) this.loadUrl(fragment);
- }
-
- });
-
- // Backbone.View
- // -------------
-
- // Creating a Backbone.View creates its initial element outside of the DOM,
- // if an existing element is not provided...
- Backbone.View = function(options) {
- this.cid = _.uniqueId('view');
- this._configure(options || {});
- this._ensureElement();
- this.delegateEvents();
- this.initialize.apply(this, arguments);
- };
-
- // Element lookup, scoped to DOM elements within the current view.
- // This should be prefered to global lookups, if you're dealing with
- // a specific view.
- var selectorDelegate = function(selector) {
- return $(selector, this.el);
- };
-
- // Cached regex to split keys for `delegate`.
- var eventSplitter = /^(\S+)\s*(.*)$/;
-
- // List of view options to be merged as properties.
- var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
-
- // Set up all inheritable **Backbone.View** properties and methods.
- _.extend(Backbone.View.prototype, Backbone.Events, {
-
- // The default `tagName` of a View's element is `"div"`.
- tagName : 'div',
-
- // Attach the `selectorDelegate` function as the `$` property.
- $ : selectorDelegate,
-
- // Initialize is an empty function by default. Override it with your own
- // initialization logic.
- initialize : function(){},
-
- // **render** is the core function that your view should override, in order
- // to populate its element (`this.el`), with the appropriate HTML. The
- // convention is for **render** to always return `this`.
- render : function() {
- return this;
- },
-
- // Remove this view from the DOM. Note that the view isn't present in the
- // DOM by default, so calling this method may be a no-op.
- remove : function() {
- $(this.el).remove();
- return this;
- },
-
- // For small amounts of DOM Elements, where a full-blown template isn't
- // needed, use **make** to manufacture elements, one at a time.
- //
- // var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
- //
- make : function(tagName, attributes, content) {
- var el = document.createElement(tagName);
- if (attributes) $(el).attr(attributes);
- if (content) $(el).html(content);
- return el;
- },
-
- // Set callbacks, where `this.callbacks` is a hash of
- //
- // *{"event selector": "callback"}*
- //
- // {
- // 'mousedown .title': 'edit',
- // 'click .button': 'save'
- // }
- //
- // pairs. Callbacks will be bound to the view, with `this` set properly.
- // Uses event delegation for efficiency.
- // Omitting the selector binds the event to `this.el`.
- // This only works for delegate-able events: not `focus`, `blur`, and
- // not `change`, `submit`, and `reset` in Internet Explorer.
- delegateEvents : function(events) {
- if (!(events || (events = this.events))) return;
- if (_.isFunction(events)) events = events.call(this);
- $(this.el).unbind('.delegateEvents' + this.cid);
- for (var key in events) {
- var method = this[events[key]];
- if (!method) throw new Error('Event "' + events[key] + '" does not exist');
- var match = key.match(eventSplitter);
- var eventName = match[1], selector = match[2];
- method = _.bind(method, this);
- eventName += '.delegateEvents' + this.cid;
- if (selector === '') {
- $(this.el).bind(eventName, method);
- } else {
- $(this.el).delegate(selector, eventName, method);
- }
- }
- },
-
- // Performs the initial configuration of a View with a set of options.
- // Keys with special meaning *(model, collection, id, className)*, are
- // attached directly to the view.
- _configure : function(options) {
- if (this.options) options = _.extend({}, this.options, options);
- for (var i = 0, l = viewOptions.length; i < l; i++) {
- var attr = viewOptions[i];
- if (options[attr]) this[attr] = options[attr];
- }
- this.options = options;
- },
-
- // Ensure that the View has a DOM element to render into.
- // If `this.el` is a string, pass it through `$()`, take the first
- // matching element, and re-assign it to `el`. Otherwise, create
- // an element from the `id`, `className` and `tagName` proeprties.
- _ensureElement : function() {
- if (!this.el) {
- var attrs = this.attributes || {};
- if (this.id) attrs.id = this.id;
- if (this.className) attrs['class'] = this.className;
- this.el = this.make(this.tagName, attrs);
- } else if (_.isString(this.el)) {
- this.el = $(this.el).get(0);
- }
- }
-
- });
-
- // The self-propagating extend function that Backbone classes use.
- var extend = function (protoProps, classProps) {
- var child = inherits(this, protoProps, classProps);
- child.extend = this.extend;
- return child;
- };
-
- // Set up inheritance for the model, collection, and view.
- Backbone.Model.extend = Backbone.Collection.extend =
- Backbone.Router.extend = Backbone.View.extend = extend;
-
- // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
- var methodMap = {
- 'create': 'POST',
- 'update': 'PUT',
- 'delete': 'DELETE',
- 'read' : 'GET'
- };
-
- // Backbone.sync
- // -------------
-
- // Override this function to change the manner in which Backbone persists
- // models to the server. You will be passed the type of request, and the
- // model in question. By default, uses makes a RESTful Ajax request
- // to the model's `url()`. Some possible customizations could be:
- //
- // * Use `setTimeout` to batch rapid-fire updates into a single request.
- // * Send up the models as XML instead of JSON.
- // * Persist models via WebSockets instead of Ajax.
- //
- // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
- // as `POST`, with a `_method` parameter containing the true HTTP method,
- // as well as all requests with the body as `application/x-www-form-urlencoded` instead of
- // `application/json` with the model in a param named `model`.
- // Useful when interfacing with server-side languages like **PHP** that make
- // it difficult to read the body of `PUT` requests.
- Backbone.sync = function(method, model, options) {
- var type = methodMap[method];
-
- // Default JSON-request options.
- var params = _.extend({
- type: type,
- dataType: 'json'
- }, options);
-
- // Ensure that we have a URL.
- if (!params.url) {
- params.url = getUrl(model) || urlError();
- }
-
- // Ensure that we have the appropriate request data.
- if (!params.data && model && (method == 'create' || method == 'update')) {
- params.contentType = 'application/json';
- params.data = JSON.stringify(model.toJSON());
- }
-
- // For older servers, emulate JSON by encoding the request into an HTML-form.
- if (Backbone.emulateJSON) {
- params.contentType = 'application/x-www-form-urlencoded';
- params.data = params.data ? {model : params.data} : {};
- }
-
- // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
- // And an `X-HTTP-Method-Override` header.
- if (Backbone.emulateHTTP) {
- if (type === 'PUT' || type === 'DELETE') {
- if (Backbone.emulateJSON) params.data._method = type;
- params.type = 'POST';
- params.beforeSend = function(xhr) {
- xhr.setRequestHeader('X-HTTP-Method-Override', type);
- };
- }
- }
-
- // Don't process data on a non-GET request.
- if (params.type !== 'GET' && !Backbone.emulateJSON) {
- params.processData = false;
- }
-
- // Make the request.
- return $.ajax(params);
- };
-
- // Helpers
- // -------
-
- // Shared empty constructor function to aid in prototype-chain creation.
- var ctor = function(){};
-
- // Helper function to correctly set up the prototype chain, for subclasses.
- // Similar to `goog.inherits`, but uses a hash of prototype properties and
- // class properties to be extended.
- var inherits = function(parent, protoProps, staticProps) {
- var child;
-
- // The constructor function for the new subclass is either defined by you
- // (the "constructor" property in your `extend` definition), or defaulted
- // by us to simply call `super()`.
- if (protoProps && protoProps.hasOwnProperty('constructor')) {
- child = protoProps.constructor;
- } else {
- child = function(){ return parent.apply(this, arguments); };
- }
-
- // Inherit class (static) properties from parent.
- _.extend(child, parent);
-
- // Set the prototype chain to inherit from `parent`, without calling
- // `parent`'s constructor function.
- ctor.prototype = parent.prototype;
- child.prototype = new ctor();
-
- // Add prototype properties (instance properties) to the subclass,
- // if supplied.
- if (protoProps) _.extend(child.prototype, protoProps);
-
- // Add static properties to the constructor function, if supplied.
- if (staticProps) _.extend(child, staticProps);
-
- // Correctly set child's `prototype.constructor`.
- child.prototype.constructor = child;
-
- // Set a convenience property in case the parent's prototype is needed later.
- child.__super__ = parent.prototype;
-
- return child;
- };
-
- // Helper function to get a URL from a Model or Collection as a property
- // or as a function.
- var getUrl = function(object) {
- if (!(object && object.url)) return null;
- return _.isFunction(object.url) ? object.url() : object.url;
- };
-
- // Throw an error when a URL is needed, and none is supplied.
- var urlError = function() {
- throw new Error('A "url" property or function must be specified');
- };
-
- // Wrap an optional error callback with a fallback error event.
- var wrapError = function(onError, model, options) {
- return function(resp) {
- if (onError) {
- onError(model, resp, options);
- } else {
- model.trigger('error', model, resp, options);
- }
- };
- };
-
- // Helper function to escape a string for HTML rendering.
- var escapeHTML = function(string) {
- return string.replace(/&(?!\w+;|#\d+;|#x[\da-f]+;)/gi, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/');
- };
-
-}).call(this);
\ No newline at end of file
diff --git a/addons/point_of_sale/static/lib/backbone/backbone-0.9.2.js b/addons/point_of_sale/static/lib/backbone/backbone-0.9.2.js
deleted file mode 100644
index 3373c952bfa..00000000000
--- a/addons/point_of_sale/static/lib/backbone/backbone-0.9.2.js
+++ /dev/null
@@ -1,1431 +0,0 @@
-// Backbone.js 0.9.2
-
-// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
-// Backbone may be freely distributed under the MIT license.
-// For all details and documentation:
-// http://backbonejs.org
-
-(function(){
-
- // Initial Setup
- // -------------
-
- // Save a reference to the global object (`window` in the browser, `global`
- // on the server).
- var root = this;
-
- // Save the previous value of the `Backbone` variable, so that it can be
- // restored later on, if `noConflict` is used.
- var previousBackbone = root.Backbone;
-
- // Create a local reference to slice/splice.
- var slice = Array.prototype.slice;
- var splice = Array.prototype.splice;
-
- // The top-level namespace. All public Backbone classes and modules will
- // be attached to this. Exported for both CommonJS and the browser.
- var Backbone;
- if (typeof exports !== 'undefined') {
- Backbone = exports;
- } else {
- Backbone = root.Backbone = {};
- }
-
- // Current version of the library. Keep in sync with `package.json`.
- Backbone.VERSION = '0.9.2';
-
- // Require Underscore, if we're on the server, and it's not already present.
- var _ = root._;
- if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
-
- // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
- var $ = root.jQuery || root.Zepto || root.ender;
-
- // Set the JavaScript library that will be used for DOM manipulation and
- // Ajax calls (a.k.a. the `$` variable). By default Backbone will use: jQuery,
- // Zepto, or Ender; but the `setDomLibrary()` method lets you inject an
- // alternate JavaScript library (or a mock library for testing your views
- // outside of a browser).
- Backbone.setDomLibrary = function(lib) {
- $ = lib;
- };
-
- // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
- // to its previous owner. Returns a reference to this Backbone object.
- Backbone.noConflict = function() {
- root.Backbone = previousBackbone;
- return this;
- };
-
- // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
- // will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and
- // set a `X-Http-Method-Override` header.
- Backbone.emulateHTTP = false;
-
- // Turn on `emulateJSON` to support legacy servers that can't deal with direct
- // `application/json` requests ... will encode the body as
- // `application/x-www-form-urlencoded` instead and will send the model in a
- // form param named `model`.
- Backbone.emulateJSON = false;
-
- // Backbone.Events
- // -----------------
-
- // Regular expression used to split event strings
- var eventSplitter = /\s+/;
-
- // A module that can be mixed in to *any object* in order to provide it with
- // custom events. You may bind with `on` or remove with `off` callback functions
- // to an event; trigger`-ing an event fires all callbacks in succession.
- //
- // var object = {};
- // _.extend(object, Backbone.Events);
- // object.on('expand', function(){ alert('expanded'); });
- // object.trigger('expand');
- //
- var Events = Backbone.Events = {
-
- // Bind one or more space separated events, `events`, to a `callback`
- // function. Passing `"all"` will bind the callback to all events fired.
- on: function(events, callback, context) {
-
- var calls, event, node, tail, list;
- if (!callback) return this;
- events = events.split(eventSplitter);
- calls = this._callbacks || (this._callbacks = {});
-
- // Create an immutable callback list, allowing traversal during
- // modification. The tail is an empty object that will always be used
- // as the next node.
- while (event = events.shift()) {
- list = calls[event];
- node = list ? list.tail : {};
- node.next = tail = {};
- node.context = context;
- node.callback = callback;
- calls[event] = {tail: tail, next: list ? list.next : node};
- }
-
- return this;
- },
-
- // Remove one or many callbacks. If `context` is null, removes all callbacks
- // with that function. If `callback` is null, removes all callbacks for the
- // event. If `events` is null, removes all bound callbacks for all events.
- off: function(events, callback, context) {
- var event, calls, node, tail, cb, ctx;
-
- // No events, or removing *all* events.
- if (!(calls = this._callbacks)) return;
- if (!(events || callback || context)) {
- delete this._callbacks;
- return this;
- }
-
- // Loop through the listed events and contexts, splicing them out of the
- // linked list of callbacks if appropriate.
- events = events ? events.split(eventSplitter) : _.keys(calls);
- while (event = events.shift()) {
- node = calls[event];
- delete calls[event];
- if (!node || !(callback || context)) continue;
- // Create a new list, omitting the indicated callbacks.
- tail = node.tail;
- while ((node = node.next) !== tail) {
- cb = node.callback;
- ctx = node.context;
- if ((callback && cb !== callback) || (context && ctx !== context)) {
- this.on(event, cb, ctx);
- }
- }
- }
-
- return this;
- },
-
- // Trigger one or many events, firing all bound callbacks. Callbacks are
- // passed the same arguments as `trigger` is, apart from the event name
- // (unless you're listening on `"all"`, which will cause your callback to
- // receive the true name of the event as the first argument).
- trigger: function(events) {
- var event, node, calls, tail, args, all, rest;
- if (!(calls = this._callbacks)) return this;
- all = calls.all;
- events = events.split(eventSplitter);
- rest = slice.call(arguments, 1);
-
- // For each event, walk through the linked list of callbacks twice,
- // first to trigger the event, then to trigger any `"all"` callbacks.
- while (event = events.shift()) {
- if (node = calls[event]) {
- tail = node.tail;
- while ((node = node.next) !== tail) {
- node.callback.apply(node.context || this, rest);
- }
- }
- if (node = all) {
- tail = node.tail;
- args = [event].concat(rest);
- while ((node = node.next) !== tail) {
- node.callback.apply(node.context || this, args);
- }
- }
- }
-
- return this;
- }
-
- };
-
- // Aliases for backwards compatibility.
- Events.bind = Events.on;
- Events.unbind = Events.off;
-
- // Backbone.Model
- // --------------
-
- // Create a new model, with defined attributes. A client id (`cid`)
- // is automatically generated and assigned for you.
- var Model = Backbone.Model = function(attributes, options) {
- var defaults;
- attributes || (attributes = {});
- if (options && options.parse) attributes = this.parse(attributes);
- if (defaults = getValue(this, 'defaults')) {
- attributes = _.extend({}, defaults, attributes);
- }
- if (options && options.collection) this.collection = options.collection;
- this.attributes = {};
- this._escapedAttributes = {};
- this.cid = _.uniqueId('c');
- this.changed = {};
- this._silent = {};
- this._pending = {};
- this.set(attributes, {silent: true});
- // Reset change tracking.
- this.changed = {};
- this._silent = {};
- this._pending = {};
- this._previousAttributes = _.clone(this.attributes);
- this.initialize.apply(this, arguments);
- };
-
- // Attach all inheritable methods to the Model prototype.
- _.extend(Model.prototype, Events, {
-
- // A hash of attributes whose current and previous value differ.
- changed: null,
-
- // A hash of attributes that have silently changed since the last time
- // `change` was called. Will become pending attributes on the next call.
- _silent: null,
-
- // A hash of attributes that have changed since the last `'change'` event
- // began.
- _pending: null,
-
- // The default name for the JSON `id` attribute is `"id"`. MongoDB and
- // CouchDB users may want to set this to `"_id"`.
- idAttribute: 'id',
-
- // Initialize is an empty function by default. Override it with your own
- // initialization logic.
- initialize: function(){},
-
- // Return a copy of the model's `attributes` object.
- toJSON: function(options) {
- return _.clone(this.attributes);
- },
-
- // Get the value of an attribute.
- get: function(attr) {
- return this.attributes[attr];
- },
-
- // Get the HTML-escaped value of an attribute.
- escape: function(attr) {
- var html;
- if (html = this._escapedAttributes[attr]) return html;
- var val = this.get(attr);
- return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
- },
-
- // Returns `true` if the attribute contains a value that is not null
- // or undefined.
- has: function(attr) {
- return this.get(attr) != null;
- },
-
- // Set a hash of model attributes on the object, firing `"change"` unless
- // you choose to silence it.
- set: function(key, value, options) {
- var attrs, attr, val;
-
- // Handle both `"key", value` and `{key: value}` -style arguments.
- if (_.isObject(key) || key == null) {
- attrs = key;
- options = value;
- } else {
- attrs = {};
- attrs[key] = value;
- }
-
- // Extract attributes and options.
- options || (options = {});
- if (!attrs) return this;
- if (attrs instanceof Model) attrs = attrs.attributes;
- if (options.unset) for (attr in attrs) attrs[attr] = void 0;
-
- // Run validation.
- if (!this._validate(attrs, options)) return false;
-
- // Check for changes of `id`.
- if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
-
- var changes = options.changes = {};
- var now = this.attributes;
- var escaped = this._escapedAttributes;
- var prev = this._previousAttributes || {};
-
- // For each `set` attribute...
- for (attr in attrs) {
- val = attrs[attr];
-
- // If the new and current value differ, record the change.
- if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
- delete escaped[attr];
- (options.silent ? this._silent : changes)[attr] = true;
- }
-
- // Update or delete the current value.
- options.unset ? delete now[attr] : now[attr] = val;
-
- // If the new and previous value differ, record the change. If not,
- // then remove changes for this attribute.
- if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
- this.changed[attr] = val;
- if (!options.silent) this._pending[attr] = true;
- } else {
- delete this.changed[attr];
- delete this._pending[attr];
- }
- }
-
- // Fire the `"change"` events.
- if (!options.silent) this.change(options);
- return this;
- },
-
- // Remove an attribute from the model, firing `"change"` unless you choose
- // to silence it. `unset` is a noop if the attribute doesn't exist.
- unset: function(attr, options) {
- (options || (options = {})).unset = true;
- return this.set(attr, null, options);
- },
-
- // Clear all attributes on the model, firing `"change"` unless you choose
- // to silence it.
- clear: function(options) {
- (options || (options = {})).unset = true;
- return this.set(_.clone(this.attributes), options);
- },
-
- // Fetch the model from the server. If the server's representation of the
- // model differs from its current attributes, they will be overriden,
- // triggering a `"change"` event.
- fetch: function(options) {
- options = options ? _.clone(options) : {};
- var model = this;
- var success = options.success;
- options.success = function(resp, status, xhr) {
- if (!model.set(model.parse(resp, xhr), options)) return false;
- if (success) success(model, resp);
- };
- options.error = Backbone.wrapError(options.error, model, options);
- return (this.sync || Backbone.sync).call(this, 'read', this, options);
- },
-
- // Set a hash of model attributes, and sync the model to the server.
- // If the server returns an attributes hash that differs, the model's
- // state will be `set` again.
- save: function(key, value, options) {
- var attrs, current;
-
- // Handle both `("key", value)` and `({key: value})` -style calls.
- if (_.isObject(key) || key == null) {
- attrs = key;
- options = value;
- } else {
- attrs = {};
- attrs[key] = value;
- }
- options = options ? _.clone(options) : {};
-
- // If we're "wait"-ing to set changed attributes, validate early.
- if (options.wait) {
- if (!this._validate(attrs, options)) return false;
- current = _.clone(this.attributes);
- }
-
- // Regular saves `set` attributes before persisting to the server.
- var silentOptions = _.extend({}, options, {silent: true});
- if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
- return false;
- }
-
- // After a successful server-side save, the client is (optionally)
- // updated with the server-side state.
- var model = this;
- var success = options.success;
- options.success = function(resp, status, xhr) {
- var serverAttrs = model.parse(resp, xhr);
- if (options.wait) {
- delete options.wait;
- serverAttrs = _.extend(attrs || {}, serverAttrs);
- }
- if (!model.set(serverAttrs, options)) return false;
- if (success) {
- success(model, resp);
- } else {
- model.trigger('sync', model, resp, options);
- }
- };
-
- // Finish configuring and sending the Ajax request.
- options.error = Backbone.wrapError(options.error, model, options);
- var method = this.isNew() ? 'create' : 'update';
- var xhr = (this.sync || Backbone.sync).call(this, method, this, options);
- if (options.wait) this.set(current, silentOptions);
- return xhr;
- },
-
- // Destroy this model on the server if it was already persisted.
- // Optimistically removes the model from its collection, if it has one.
- // If `wait: true` is passed, waits for the server to respond before removal.
- destroy: function(options) {
- options = options ? _.clone(options) : {};
- var model = this;
- var success = options.success;
-
- var triggerDestroy = function() {
- model.trigger('destroy', model, model.collection, options);
- };
-
- if (this.isNew()) {
- triggerDestroy();
- return false;
- }
-
- options.success = function(resp) {
- if (options.wait) triggerDestroy();
- if (success) {
- success(model, resp);
- } else {
- model.trigger('sync', model, resp, options);
- }
- };
-
- options.error = Backbone.wrapError(options.error, model, options);
- var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);
- if (!options.wait) triggerDestroy();
- return xhr;
- },
-
- // Default URL for the model's representation on the server -- if you're
- // using Backbone's restful methods, override this to change the endpoint
- // that will be called.
- url: function() {
- var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
- if (this.isNew()) return base;
- return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
- },
-
- // **parse** converts a response into the hash of attributes to be `set` on
- // the model. The default implementation is just to pass the response along.
- parse: function(resp, xhr) {
- return resp;
- },
-
- // Create a new model with identical attributes to this one.
- clone: function() {
- return new this.constructor(this.attributes);
- },
-
- // A model is new if it has never been saved to the server, and lacks an id.
- isNew: function() {
- return this.id == null;
- },
-
- // Call this method to manually fire a `"change"` event for this model and
- // a `"change:attribute"` event for each changed attribute.
- // Calling this will cause all objects observing the model to update.
- change: function(options) {
- options || (options = {});
- var changing = this._changing;
- this._changing = true;
-
- // Silent changes become pending changes.
- for (var attr in this._silent) this._pending[attr] = true;
-
- // Silent changes are triggered.
- var changes = _.extend({}, options.changes, this._silent);
- this._silent = {};
- for (var attr in changes) {
- this.trigger('change:' + attr, this, this.get(attr), options);
- }
- if (changing) return this;
-
- // Continue firing `"change"` events while there are pending changes.
- while (!_.isEmpty(this._pending)) {
- this._pending = {};
- this.trigger('change', this, options);
- // Pending and silent changes still remain.
- for (var attr in this.changed) {
- if (this._pending[attr] || this._silent[attr]) continue;
- delete this.changed[attr];
- }
- this._previousAttributes = _.clone(this.attributes);
- }
-
- this._changing = false;
- return this;
- },
-
- // Determine if the model has changed since the last `"change"` event.
- // If you specify an attribute name, determine if that attribute has changed.
- hasChanged: function(attr) {
- if (!arguments.length) return !_.isEmpty(this.changed);
- return _.has(this.changed, attr);
- },
-
- // Return an object containing all the attributes that have changed, or
- // false if there are no changed attributes. Useful for determining what
- // parts of a view need to be updated and/or what attributes need to be
- // persisted to the server. Unset attributes will be set to undefined.
- // You can also pass an attributes object to diff against the model,
- // determining if there *would be* a change.
- changedAttributes: function(diff) {
- if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
- var val, changed = false, old = this._previousAttributes;
- for (var attr in diff) {
- if (_.isEqual(old[attr], (val = diff[attr]))) continue;
- (changed || (changed = {}))[attr] = val;
- }
- return changed;
- },
-
- // Get the previous value of an attribute, recorded at the time the last
- // `"change"` event was fired.
- previous: function(attr) {
- if (!arguments.length || !this._previousAttributes) return null;
- return this._previousAttributes[attr];
- },
-
- // Get all of the attributes of the model at the time of the previous
- // `"change"` event.
- previousAttributes: function() {
- return _.clone(this._previousAttributes);
- },
-
- // Check if the model is currently in a valid state. It's only possible to
- // get into an *invalid* state if you're using silent changes.
- isValid: function() {
- return !this.validate(this.attributes);
- },
-
- // Run validation against the next complete set of model attributes,
- // returning `true` if all is well. If a specific `error` callback has
- // been passed, call that instead of firing the general `"error"` event.
- _validate: function(attrs, options) {
- if (options.silent || !this.validate) return true;
- attrs = _.extend({}, this.attributes, attrs);
- var error = this.validate(attrs, options);
- if (!error) return true;
- if (options && options.error) {
- options.error(this, error, options);
- } else {
- this.trigger('error', this, error, options);
- }
- return false;
- }
-
- });
-
- // Backbone.Collection
- // -------------------
-
- // Provides a standard collection class for our sets of models, ordered
- // or unordered. If a `comparator` is specified, the Collection will maintain
- // its models in sort order, as they're added and removed.
- var Collection = Backbone.Collection = function(models, options) {
- options || (options = {});
- if (options.model) this.model = options.model;
- if (options.comparator) this.comparator = options.comparator;
- this._reset();
- this.initialize.apply(this, arguments);
- if (models) this.reset(models, {silent: true, parse: options.parse});
- };
-
- // Define the Collection's inheritable methods.
- _.extend(Collection.prototype, Events, {
-
- // The default model for a collection is just a **Backbone.Model**.
- // This should be overridden in most cases.
- model: Model,
-
- // Initialize is an empty function by default. Override it with your own
- // initialization logic.
- initialize: function(){},
-
- // The JSON representation of a Collection is an array of the
- // models' attributes.
- toJSON: function(options) {
- return this.map(function(model){ return model.toJSON(options); });
- },
-
- // Add a model, or list of models to the set. Pass **silent** to avoid
- // firing the `add` event for every new model.
- add: function(models, options) {
- var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
- options || (options = {});
- models = _.isArray(models) ? models.slice() : [models];
-
- // Begin by turning bare objects into model references, and preventing
- // invalid models or duplicate models from being added.
- for (i = 0, length = models.length; i < length; i++) {
- if (!(model = models[i] = this._prepareModel(models[i], options))) {
- throw new Error("Can't add an invalid model to a collection");
- }
- cid = model.cid;
- id = model.id;
- if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
- dups.push(i);
- continue;
- }
- cids[cid] = ids[id] = model;
- }
-
- // Remove duplicates.
- i = dups.length;
- while (i--) {
- models.splice(dups[i], 1);
- }
-
- // Listen to added models' events, and index models for lookup by
- // `id` and by `cid`.
- for (i = 0, length = models.length; i < length; i++) {
- (model = models[i]).on('all', this._onModelEvent, this);
- this._byCid[model.cid] = model;
- if (model.id != null) this._byId[model.id] = model;
- }
-
- // Insert models into the collection, re-sorting if needed, and triggering
- // `add` events unless silenced.
- this.length += length;
- index = options.at != null ? options.at : this.models.length;
- splice.apply(this.models, [index, 0].concat(models));
- if (this.comparator) this.sort({silent: true});
- if (options.silent) return this;
- for (i = 0, length = this.models.length; i < length; i++) {
- if (!cids[(model = this.models[i]).cid]) continue;
- options.index = i;
- model.trigger('add', model, this, options);
- }
- return this;
- },
-
- // Remove a model, or a list of models from the set. Pass silent to avoid
- // firing the `remove` event for every model removed.
- remove: function(models, options) {
- var i, l, index, model;
- options || (options = {});
- models = _.isArray(models) ? models.slice() : [models];
- for (i = 0, l = models.length; i < l; i++) {
- model = this.getByCid(models[i]) || this.get(models[i]);
- if (!model) continue;
- delete this._byId[model.id];
- delete this._byCid[model.cid];
- index = this.indexOf(model);
- this.models.splice(index, 1);
- this.length--;
- if (!options.silent) {
- options.index = index;
- model.trigger('remove', model, this, options);
- }
- this._removeReference(model);
- }
- return this;
- },
-
- // Add a model to the end of the collection.
- push: function(model, options) {
- model = this._prepareModel(model, options);
- this.add(model, options);
- return model;
- },
-
- // Remove a model from the end of the collection.
- pop: function(options) {
- var model = this.at(this.length - 1);
- this.remove(model, options);
- return model;
- },
-
- // Add a model to the beginning of the collection.
- unshift: function(model, options) {
- model = this._prepareModel(model, options);
- this.add(model, _.extend({at: 0}, options));
- return model;
- },
-
- // Remove a model from the beginning of the collection.
- shift: function(options) {
- var model = this.at(0);
- this.remove(model, options);
- return model;
- },
-
- // Get a model from the set by id.
- get: function(id) {
- if (id == null) return void 0;
- return this._byId[id.id != null ? id.id : id];
- },
-
- // Get a model from the set by client id.
- getByCid: function(cid) {
- return cid && this._byCid[cid.cid || cid];
- },
-
- // Get the model at the given index.
- at: function(index) {
- return this.models[index];
- },
-
- // Return models with matching attributes. Useful for simple cases of `filter`.
- where: function(attrs) {
- if (_.isEmpty(attrs)) return [];
- return this.filter(function(model) {
- for (var key in attrs) {
- if (attrs[key] !== model.get(key)) return false;
- }
- return true;
- });
- },
-
- // Force the collection to re-sort itself. You don't need to call this under
- // normal circumstances, as the set will maintain sort order as each item
- // is added.
- sort: function(options) {
- options || (options = {});
- if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
- var boundComparator = _.bind(this.comparator, this);
- if (this.comparator.length == 1) {
- this.models = this.sortBy(boundComparator);
- } else {
- this.models.sort(boundComparator);
- }
- if (!options.silent) this.trigger('reset', this, options);
- return this;
- },
-
- // Pluck an attribute from each model in the collection.
- pluck: function(attr) {
- return _.map(this.models, function(model){ return model.get(attr); });
- },
-
- // When you have more items than you want to add or remove individually,
- // you can reset the entire set with a new list of models, without firing
- // any `add` or `remove` events. Fires `reset` when finished.
- reset: function(models, options) {
- models || (models = []);
- options || (options = {});
- for (var i = 0, l = this.models.length; i < l; i++) {
- this._removeReference(this.models[i]);
- }
- this._reset();
- this.add(models, _.extend({silent: true}, options));
- if (!options.silent) this.trigger('reset', this, options);
- return this;
- },
-
- // Fetch the default set of models for this collection, resetting the
- // collection when they arrive. If `add: true` is passed, appends the
- // models to the collection instead of resetting.
- fetch: function(options) {
- options = options ? _.clone(options) : {};
- if (options.parse === undefined) options.parse = true;
- var collection = this;
- var success = options.success;
- options.success = function(resp, status, xhr) {
- collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
- if (success) success(collection, resp);
- };
- options.error = Backbone.wrapError(options.error, collection, options);
- return (this.sync || Backbone.sync).call(this, 'read', this, options);
- },
-
- // Create a new instance of a model in this collection. Add the model to the
- // collection immediately, unless `wait: true` is passed, in which case we
- // wait for the server to agree.
- create: function(model, options) {
- var coll = this;
- options = options ? _.clone(options) : {};
- model = this._prepareModel(model, options);
- if (!model) return false;
- if (!options.wait) coll.add(model, options);
- var success = options.success;
- options.success = function(nextModel, resp, xhr) {
- if (options.wait) coll.add(nextModel, options);
- if (success) {
- success(nextModel, resp);
- } else {
- nextModel.trigger('sync', model, resp, options);
- }
- };
- model.save(null, options);
- return model;
- },
-
- // **parse** converts a response into a list of models to be added to the
- // collection. The default implementation is just to pass it through.
- parse: function(resp, xhr) {
- return resp;
- },
-
- // Proxy to _'s chain. Can't be proxied the same way the rest of the
- // underscore methods are proxied because it relies on the underscore
- // constructor.
- chain: function () {
- return _(this.models).chain();
- },
-
- // Reset all internal state. Called when the collection is reset.
- _reset: function(options) {
- this.length = 0;
- this.models = [];
- this._byId = {};
- this._byCid = {};
- },
-
- // Prepare a model or hash of attributes to be added to this collection.
- _prepareModel: function(model, options) {
- options || (options = {});
- if (!(model instanceof Model)) {
- var attrs = model;
- options.collection = this;
- model = new this.model(attrs, options);
- if (!model._validate(model.attributes, options)) model = false;
- } else if (!model.collection) {
- model.collection = this;
- }
- return model;
- },
-
- // Internal method to remove a model's ties to a collection.
- _removeReference: function(model) {
- if (this == model.collection) {
- delete model.collection;
- }
- model.off('all', this._onModelEvent, this);
- },
-
- // Internal method called every time a model in the set fires an event.
- // Sets need to update their indexes when models change ids. All other
- // events simply proxy through. "add" and "remove" events that originate
- // in other collections are ignored.
- _onModelEvent: function(event, model, collection, options) {
- if ((event == 'add' || event == 'remove') && collection != this) return;
- if (event == 'destroy') {
- this.remove(model, options);
- }
- if (model && event === 'change:' + model.idAttribute) {
- delete this._byId[model.previous(model.idAttribute)];
- this._byId[model.id] = model;
- }
- this.trigger.apply(this, arguments);
- }
-
- });
-
- // Underscore methods that we want to implement on the Collection.
- var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
- 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
- 'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
- 'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
- 'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
-
- // Mix in each Underscore method as a proxy to `Collection#models`.
- _.each(methods, function(method) {
- Collection.prototype[method] = function() {
- return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
- };
- });
-
- // Backbone.Router
- // -------------------
-
- // Routers map faux-URLs to actions, and fire events when routes are
- // matched. Creating a new one sets its `routes` hash, if not set statically.
- var Router = Backbone.Router = function(options) {
- options || (options = {});
- if (options.routes) this.routes = options.routes;
- this._bindRoutes();
- this.initialize.apply(this, arguments);
- };
-
- // Cached regular expressions for matching named param parts and splatted
- // parts of route strings.
- var namedParam = /:\w+/g;
- var splatParam = /\*\w+/g;
- var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
-
- // Set up all inheritable **Backbone.Router** properties and methods.
- _.extend(Router.prototype, Events, {
-
- // Initialize is an empty function by default. Override it with your own
- // initialization logic.
- initialize: function(){},
-
- // Manually bind a single named route to a callback. For example:
- //
- // this.route('search/:query/p:num', 'search', function(query, num) {
- // ...
- // });
- //
- route: function(route, name, callback) {
- Backbone.history || (Backbone.history = new History);
- if (!_.isRegExp(route)) route = this._routeToRegExp(route);
- if (!callback) callback = this[name];
- Backbone.history.route(route, _.bind(function(fragment) {
- var args = this._extractParameters(route, fragment);
- callback && callback.apply(this, args);
- this.trigger.apply(this, ['route:' + name].concat(args));
- Backbone.history.trigger('route', this, name, args);
- }, this));
- return this;
- },
-
- // Simple proxy to `Backbone.history` to save a fragment into the history.
- navigate: function(fragment, options) {
- Backbone.history.navigate(fragment, options);
- },
-
- // Bind all defined routes to `Backbone.history`. We have to reverse the
- // order of the routes here to support behavior where the most general
- // routes can be defined at the bottom of the route map.
- _bindRoutes: function() {
- if (!this.routes) return;
- var routes = [];
- for (var route in this.routes) {
- routes.unshift([route, this.routes[route]]);
- }
- for (var i = 0, l = routes.length; i < l; i++) {
- this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
- }
- },
-
- // Convert a route string into a regular expression, suitable for matching
- // against the current location hash.
- _routeToRegExp: function(route) {
- route = route.replace(escapeRegExp, '\\$&')
- .replace(namedParam, '([^\/]+)')
- .replace(splatParam, '(.*?)');
- return new RegExp('^' + route + '$');
- },
-
- // Given a route, and a URL fragment that it matches, return the array of
- // extracted parameters.
- _extractParameters: function(route, fragment) {
- return route.exec(fragment).slice(1);
- }
-
- });
-
- // Backbone.History
- // ----------------
-
- // Handles cross-browser history management, based on URL fragments. If the
- // browser does not support `onhashchange`, falls back to polling.
- var History = Backbone.History = function() {
- this.handlers = [];
- _.bindAll(this, 'checkUrl');
- };
-
- // Cached regex for cleaning leading hashes and slashes .
- var routeStripper = /^[#\/]/;
-
- // Cached regex for detecting MSIE.
- var isExplorer = /msie [\w.]+/;
-
- // Has the history handling already been started?
- History.started = false;
-
- // Set up all inheritable **Backbone.History** properties and methods.
- _.extend(History.prototype, Events, {
-
- // The default interval to poll for hash changes, if necessary, is
- // twenty times a second.
- interval: 50,
-
- // Gets the true hash value. Cannot use location.hash directly due to bug
- // in Firefox where location.hash will always be decoded.
- getHash: function(windowOverride) {
- var loc = windowOverride ? windowOverride.location : window.location;
- var match = loc.href.match(/#(.*)$/);
- return match ? match[1] : '';
- },
-
- // Get the cross-browser normalized URL fragment, either from the URL,
- // the hash, or the override.
- getFragment: function(fragment, forcePushState) {
- if (fragment == null) {
- if (this._hasPushState || forcePushState) {
- fragment = window.location.pathname;
- var search = window.location.search;
- if (search) fragment += search;
- } else {
- fragment = this.getHash();
- }
- }
- if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);
- return fragment.replace(routeStripper, '');
- },
-
- // Start the hash change handling, returning `true` if the current URL matches
- // an existing route, and `false` otherwise.
- start: function(options) {
- if (History.started) throw new Error("Backbone.history has already been started");
- History.started = true;
-
- // Figure out the initial configuration. Do we need an iframe?
- // Is pushState desired ... is it available?
- this.options = _.extend({}, {root: '/'}, this.options, options);
- this._wantsHashChange = this.options.hashChange !== false;
- this._wantsPushState = !!this.options.pushState;
- this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);
- var fragment = this.getFragment();
- var docMode = document.documentMode;
- var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
-
- if (oldIE) {
- this.iframe = $('').hide().appendTo('body')[0].contentWindow;
- this.navigate(fragment);
- }
-
- // Depending on whether we're using pushState or hashes, and whether
- // 'onhashchange' is supported, determine how we check the URL state.
- if (this._hasPushState) {
- $(window).bind('popstate', this.checkUrl);
- } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
- $(window).bind('hashchange', this.checkUrl);
- } else if (this._wantsHashChange) {
- this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
- }
-
- // Determine if we need to change the base url, for a pushState link
- // opened by a non-pushState browser.
- this.fragment = fragment;
- var loc = window.location;
- var atRoot = loc.pathname == this.options.root;
-
- // If we've started off with a route from a `pushState`-enabled browser,
- // but we're currently in a browser that doesn't support it...
- if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
- this.fragment = this.getFragment(null, true);
- window.location.replace(this.options.root + '#' + this.fragment);
- // Return immediately as browser will do redirect to new url
- return true;
-
- // Or if we've started out with a hash-based route, but we're currently
- // in a browser where it could be `pushState`-based instead...
- } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
- this.fragment = this.getHash().replace(routeStripper, '');
- window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
- }
-
- if (!this.options.silent) {
- return this.loadUrl();
- }
- },
-
- // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
- // but possibly useful for unit testing Routers.
- stop: function() {
- $(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
- clearInterval(this._checkUrlInterval);
- History.started = false;
- },
-
- // Add a route to be tested when the fragment changes. Routes added later
- // may override previous routes.
- route: function(route, callback) {
- this.handlers.unshift({route: route, callback: callback});
- },
-
- // Checks the current URL to see if it has changed, and if it has,
- // calls `loadUrl`, normalizing across the hidden iframe.
- checkUrl: function(e) {
- var current = this.getFragment();
- if (current == this.fragment && this.iframe) current = this.getFragment(this.getHash(this.iframe));
- if (current == this.fragment) return false;
- if (this.iframe) this.navigate(current);
- this.loadUrl() || this.loadUrl(this.getHash());
- },
-
- // Attempt to load the current URL fragment. If a route succeeds with a
- // match, returns `true`. If no defined routes matches the fragment,
- // returns `false`.
- loadUrl: function(fragmentOverride) {
- var fragment = this.fragment = this.getFragment(fragmentOverride);
- var matched = _.any(this.handlers, function(handler) {
- if (handler.route.test(fragment)) {
- handler.callback(fragment);
- return true;
- }
- });
- return matched;
- },
-
- // Save a fragment into the hash history, or replace the URL state if the
- // 'replace' option is passed. You are responsible for properly URL-encoding
- // the fragment in advance.
- //
- // The options object can contain `trigger: true` if you wish to have the
- // route callback be fired (not usually desirable), or `replace: true`, if
- // you wish to modify the current URL without adding an entry to the history.
- navigate: function(fragment, options) {
- if (!History.started) return false;
- if (!options || options === true) options = {trigger: options};
- var frag = (fragment || '').replace(routeStripper, '');
- if (this.fragment == frag) return;
-
- // If pushState is available, we use it to set the fragment as a real URL.
- if (this._hasPushState) {
- if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
- this.fragment = frag;
- window.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, frag);
-
- // If hash changes haven't been explicitly disabled, update the hash
- // fragment to store history.
- } else if (this._wantsHashChange) {
- this.fragment = frag;
- this._updateHash(window.location, frag, options.replace);
- if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) {
- // Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.
- // When replace is true, we don't want this.
- if(!options.replace) this.iframe.document.open().close();
- this._updateHash(this.iframe.location, frag, options.replace);
- }
-
- // If you've told us that you explicitly don't want fallback hashchange-
- // based history, then `navigate` becomes a page refresh.
- } else {
- window.location.assign(this.options.root + fragment);
- }
- if (options.trigger) this.loadUrl(fragment);
- },
-
- // Update the hash location, either replacing the current entry, or adding
- // a new one to the browser history.
- _updateHash: function(location, fragment, replace) {
- if (replace) {
- location.replace(location.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment);
- } else {
- location.hash = fragment;
- }
- }
- });
-
- // Backbone.View
- // -------------
-
- // Creating a Backbone.View creates its initial element outside of the DOM,
- // if an existing element is not provided...
- var View = Backbone.View = function(options) {
- this.cid = _.uniqueId('view');
- this._configure(options || {});
- this._ensureElement();
- this.initialize.apply(this, arguments);
- this.delegateEvents();
- };
-
- // Cached regex to split keys for `delegate`.
- var delegateEventSplitter = /^(\S+)\s*(.*)$/;
-
- // List of view options to be merged as properties.
- var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
-
- // Set up all inheritable **Backbone.View** properties and methods.
- _.extend(View.prototype, Events, {
-
- // The default `tagName` of a View's element is `"div"`.
- tagName: 'div',
-
- // jQuery delegate for element lookup, scoped to DOM elements within the
- // current view. This should be prefered to global lookups where possible.
- $: function(selector) {
- return this.$el.find(selector);
- },
-
- // Initialize is an empty function by default. Override it with your own
- // initialization logic.
- initialize: function(){},
-
- // **render** is the core function that your view should override, in order
- // to populate its element (`this.el`), with the appropriate HTML. The
- // convention is for **render** to always return `this`.
- render: function() {
- return this;
- },
-
- // Remove this view from the DOM. Note that the view isn't present in the
- // DOM by default, so calling this method may be a no-op.
- remove: function() {
- this.$el.remove();
- return this;
- },
-
- // For small amounts of DOM Elements, where a full-blown template isn't
- // needed, use **make** to manufacture elements, one at a time.
- //
- // var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
- //
- make: function(tagName, attributes, content) {
- var el = document.createElement(tagName);
- if (attributes) $(el).attr(attributes);
- if (content) $(el).html(content);
- return el;
- },
-
- // Change the view's element (`this.el` property), including event
- // re-delegation.
- setElement: function(element, delegate) {
- if (this.$el) this.undelegateEvents();
- this.$el = (element instanceof $) ? element : $(element);
- this.el = this.$el[0];
- if (delegate !== false) this.delegateEvents();
- return this;
- },
-
- // Set callbacks, where `this.events` is a hash of
- //
- // *{"event selector": "callback"}*
- //
- // {
- // 'mousedown .title': 'edit',
- // 'click .button': 'save'
- // 'click .open': function(e) { ... }
- // }
- //
- // pairs. Callbacks will be bound to the view, with `this` set properly.
- // Uses event delegation for efficiency.
- // Omitting the selector binds the event to `this.el`.
- // This only works for delegate-able events: not `focus`, `blur`, and
- // not `change`, `submit`, and `reset` in Internet Explorer.
- delegateEvents: function(events) {
- if (!(events || (events = getValue(this, 'events')))) return;
- this.undelegateEvents();
- for (var key in events) {
- var method = events[key];
- if (!_.isFunction(method)) method = this[events[key]];
- if (!method) throw new Error('Method "' + events[key] + '" does not exist');
- var match = key.match(delegateEventSplitter);
- var eventName = match[1], selector = match[2];
- method = _.bind(method, this);
- eventName += '.delegateEvents' + this.cid;
- if (selector === '') {
- this.$el.bind(eventName, method);
- } else {
- this.$el.delegate(selector, eventName, method);
- }
- }
- },
-
- // Clears all callbacks previously bound to the view with `delegateEvents`.
- // You usually don't need to use this, but may wish to if you have multiple
- // Backbone views attached to the same DOM element.
- undelegateEvents: function() {
- this.$el.unbind('.delegateEvents' + this.cid);
- },
-
- // Performs the initial configuration of a View with a set of options.
- // Keys with special meaning *(model, collection, id, className)*, are
- // attached directly to the view.
- _configure: function(options) {
- if (this.options) options = _.extend({}, this.options, options);
- for (var i = 0, l = viewOptions.length; i < l; i++) {
- var attr = viewOptions[i];
- if (options[attr]) this[attr] = options[attr];
- }
- this.options = options;
- },
-
- // Ensure that the View has a DOM element to render into.
- // If `this.el` is a string, pass it through `$()`, take the first
- // matching element, and re-assign it to `el`. Otherwise, create
- // an element from the `id`, `className` and `tagName` properties.
- _ensureElement: function() {
- if (!this.el) {
- var attrs = getValue(this, 'attributes') || {};
- if (this.id) attrs.id = this.id;
- if (this.className) attrs['class'] = this.className;
- this.setElement(this.make(this.tagName, attrs), false);
- } else {
- this.setElement(this.el, false);
- }
- }
-
- });
-
- // The self-propagating extend function that Backbone classes use.
- var extend = function (protoProps, classProps) {
- var child = inherits(this, protoProps, classProps);
- child.extend = this.extend;
- return child;
- };
-
- // Set up inheritance for the model, collection, and view.
- Model.extend = Collection.extend = Router.extend = View.extend = extend;
-
- // Backbone.sync
- // -------------
-
- // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
- var methodMap = {
- 'create': 'POST',
- 'update': 'PUT',
- 'delete': 'DELETE',
- 'read': 'GET'
- };
-
- // Override this function to change the manner in which Backbone persists
- // models to the server. You will be passed the type of request, and the
- // model in question. By default, makes a RESTful Ajax request
- // to the model's `url()`. Some possible customizations could be:
- //
- // * Use `setTimeout` to batch rapid-fire updates into a single request.
- // * Send up the models as XML instead of JSON.
- // * Persist models via WebSockets instead of Ajax.
- //
- // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
- // as `POST`, with a `_method` parameter containing the true HTTP method,
- // as well as all requests with the body as `application/x-www-form-urlencoded`
- // instead of `application/json` with the model in a param named `model`.
- // Useful when interfacing with server-side languages like **PHP** that make
- // it difficult to read the body of `PUT` requests.
- Backbone.sync = function(method, model, options) {
- var type = methodMap[method];
-
- // Default options, unless specified.
- options || (options = {});
-
- // Default JSON-request options.
- var params = {type: type, dataType: 'json'};
-
- // Ensure that we have a URL.
- if (!options.url) {
- params.url = getValue(model, 'url') || urlError();
- }
-
- // Ensure that we have the appropriate request data.
- if (!options.data && model && (method == 'create' || method == 'update')) {
- params.contentType = 'application/json';
- params.data = JSON.stringify(model.toJSON());
- }
-
- // For older servers, emulate JSON by encoding the request into an HTML-form.
- if (Backbone.emulateJSON) {
- params.contentType = 'application/x-www-form-urlencoded';
- params.data = params.data ? {model: params.data} : {};
- }
-
- // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
- // And an `X-HTTP-Method-Override` header.
- if (Backbone.emulateHTTP) {
- if (type === 'PUT' || type === 'DELETE') {
- if (Backbone.emulateJSON) params.data._method = type;
- params.type = 'POST';
- params.beforeSend = function(xhr) {
- xhr.setRequestHeader('X-HTTP-Method-Override', type);
- };
- }
- }
-
- // Don't process data on a non-GET request.
- if (params.type !== 'GET' && !Backbone.emulateJSON) {
- params.processData = false;
- }
-
- // Make the request, allowing the user to override any Ajax options.
- return $.ajax(_.extend(params, options));
- };
-
- // Wrap an optional error callback with a fallback error event.
- Backbone.wrapError = function(onError, originalModel, options) {
- return function(model, resp) {
- resp = model === originalModel ? resp : model;
- if (onError) {
- onError(originalModel, resp, options);
- } else {
- originalModel.trigger('error', originalModel, resp, options);
- }
- };
- };
-
- // Helpers
- // -------
-
- // Shared empty constructor function to aid in prototype-chain creation.
- var ctor = function(){};
-
- // Helper function to correctly set up the prototype chain, for subclasses.
- // Similar to `goog.inherits`, but uses a hash of prototype properties and
- // class properties to be extended.
- var inherits = function(parent, protoProps, staticProps) {
- var child;
-
- // The constructor function for the new subclass is either defined by you
- // (the "constructor" property in your `extend` definition), or defaulted
- // by us to simply call the parent's constructor.
- if (protoProps && protoProps.hasOwnProperty('constructor')) {
- child = protoProps.constructor;
- } else {
- child = function(){ parent.apply(this, arguments); };
- }
-
- // Inherit class (static) properties from parent.
- _.extend(child, parent);
-
- // Set the prototype chain to inherit from `parent`, without calling
- // `parent`'s constructor function.
- ctor.prototype = parent.prototype;
- child.prototype = new ctor();
-
- // Add prototype properties (instance properties) to the subclass,
- // if supplied.
- if (protoProps) _.extend(child.prototype, protoProps);
-
- // Add static properties to the constructor function, if supplied.
- if (staticProps) _.extend(child, staticProps);
-
- // Correctly set child's `prototype.constructor`.
- child.prototype.constructor = child;
-
- // Set a convenience property in case the parent's prototype is needed later.
- child.__super__ = parent.prototype;
-
- return child;
- };
-
- // Helper function to get a value from a Backbone object as a property
- // or as a function.
- var getValue = function(object, prop) {
- if (!(object && object[prop])) return null;
- return _.isFunction(object[prop]) ? object[prop]() : object[prop];
- };
-
- // Throw an error when a URL is needed, and none is supplied.
- var urlError = function() {
- throw new Error('A "url" property or function must be specified');
- };
-
-}).call(this);
diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css
index 114e6ccee9e..a9baf041942 100644
--- a/addons/point_of_sale/static/src/css/pos.css
+++ b/addons/point_of_sale/static/src/css/pos.css
@@ -31,6 +31,7 @@
background: -ms-linear-gradient(top,rgba(0,0,0,0.09),rgba(0,0,0,0));
background: linear-gradient(top,rgba(0,0,0,0.09),rgba(0,0,0,0));
}
+
.point-of-sale .darker-shadow-top{
position: absolute;
top:0;
@@ -42,6 +43,7 @@
background: -ms-linear-gradient(top,rgba(0,0,0,0.15),rgba(0,0,0,0));
background: linear-gradient(top,rgba(0,0,0,0.15),rgba(0,0,0,0));
}
+
/* ********* The black loading screen ********* */
.point-of-sale .loader{
@@ -156,11 +158,11 @@
}
.point-of-sale #rightheader button {
- color: black;
- height:29px;
- margin:2px;
+ color: #273072;
+ height:27px;
+ margin:3px;
margin-right:0px;
- border: 1px solid black;
+ border: 1px solid #353A7E;
background: #7f82ac;
background: -webkit-gradient(linear, left top, left bottom, from(#b2b3d7), to(#7f82ac));
background: -moz-linear-gradient(#b2b3d7, #7f82ac);
@@ -187,6 +189,33 @@
font-weight: 900;
}
+/* c) The session buttons */
+
+.point-of-sale #rightheader .header-button{
+ float:right;
+ height:32px;
+ padding-left:10px;
+ padding-right:10px;
+ border-right: 1px solid #3a3a3a;
+ border-left: 1px solid #3a3a3a;
+ color:#DDD;
+ line-height:32px;
+ text-align:center;
+ cursor: pointer;
+
+ -webkit-transition-property: background;
+ -webkit-transition-duration: 0.2s;
+ -webkit-transition-timing-function: ease-out;
+}
+.point-of-sale #rightheader .header-button:last-child{
+ border-left: 1px solid #3a3a3a;
+}
+.point-of-sale #rightheader .header-button:hover{
+ background: rgba(0,0,0,0.2);
+ text-shadow: #000 0px 0px 3px;
+ color:#EEE;
+}
+
/* c) The notifications indicator */
.point-of-sale .oe_pos_synch-notification{
@@ -234,7 +263,8 @@
left:0;
width:440px;
top:0px;
- bottom:105px;
+ /*bottom:105px;*/
+ bottom:0;
border-right: solid 1px #CECBCB;
background-color: white;
}
@@ -318,7 +348,8 @@
.point-of-sale #rightpane {
position: absolute;
top: 0;
- bottom: 105px;
+ /*bottom: 105px;*/
+ bottom:0;
left: 440px;
right: 0;
vertical-align: top;
@@ -454,6 +485,42 @@
cursor: pointer;
}
+.point-of-sale .category-simple-button{
+ position: relative;
+ display: inline-block;
+ font-size: 14px;
+ margin-right:10px;
+ padding:5px;
+
+ cursor: pointer;
+ border: 1px solid #cacaca;
+ border-radius: 4px;
+
+ background: #e2e2e2;
+ background: -webkit-linear-gradient(#f0f0f0, #e2e2e2);
+ background: -moz-linear-gradient(#f0f0f0, #e2e2e2);
+ background: -ms-linear-gradient(#f0f0f0, #e2e2e2);
+ background: linear-gradient(#f0f0f0, #e2e2e2);
+ -webkit-box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
+ -moz-box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
+ box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
+}
+.point-of-sale .category-simple-button:hover {
+ color: white;
+ background: #7f82ac;
+ border: 1px solid #7f82ac;
+ background: -webkit-linear-gradient(#9d9fc5, #7f82ac);
+ background: -moz-linear-gradient(#9d9fc5, #7f82ac);
+ background: -ms-linear-gradient(#9d9fc5, #7f82ac);
+ background: linear-gradient(#9d9fc5, #7f82ac);
+
+ -webkit-transition-property: background, border;
+ -webkit-transition-duration: 0.2s;
+ -webkit-transition-timing-function: ease-out;
+}
+
+
+
.point-of-sale .category-button .category-img {
position: relative;
width: 120px;
@@ -463,7 +530,8 @@
}
.point-of-sale .category-button .category-img img {
- height: 100px;
+ max-height: 100px;
+ max-width: 120px;
}
.point-of-sale .category-button .category-name {
@@ -490,6 +558,7 @@
position:relative;
vertical-align: top;
display: inline-block;
+ line-height: 100px;
font-size: 11px;
margin: 5px;
width: 120px;
@@ -497,9 +566,9 @@
background:#fff;
border: 1px solid #fff;
border-radius: 3px;
- -webkit-box-shadow: 0px 1px 8px rgba(0,0,0,0.2);
- -moz-box-shadow: 0px 1px 8px rgba(0,0,0,0.2);
- box-shadow: 0px 1px 8px rgba(0,0,0,0.2);
+ -webkit-box-shadow: 0px 1px 8px rgba(127,130,172,0.4);
+ -moz-box-shadow: 0px 1px 8px rgba(127,130,172,0.4);
+ box-shadow: 0px 1px 8px rgba(127,130,172,0.4);
}
.point-of-sale .product .product-img {
@@ -511,7 +580,8 @@
}
.point-of-sale .product .product-img img {
- height: 100px;
+ max-height: 100px;
+ max-width: 120px;
}
.point-of-sale .product .price-tag {
@@ -520,6 +590,7 @@
right: 2px;
vertical-align: top;
color: white;
+ line-height: 14px;
background: #7f82ac;
padding: 2px 5px;
border-radius: 3px;
@@ -533,6 +604,7 @@
box-sizing: border-box;
bottom:0;
top:auto;
+ line-height: 14px;
width:100%;
background: -webkit-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
background: -moz-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
@@ -598,8 +670,16 @@
display: inline-block;
font-size: 1.5em;
}
+.point-of-sale .greyed-out{
+ color: #AAA;
+}
+.point-of-sale .pos-step-container input{
+ font-size: 1em;
+}
+
.point-of-sale .pos-payment-container {
text-align: left;
+ min-width: 500px;
}
.point-of-sale .pos-payment-container .left-block{
display: inline-block;
@@ -648,8 +728,17 @@
width: 300px;
background-color: white;
margin: 20px;
- padding: 10px;
+ padding: 15px;
+ padding-bottom:30px;
display: inline-block;
+ font-family: "Inconsolata";
+ -webkit-box-shadow: 0px 5px 16px rgba(0,0,0, 0.3);
+ -moz-box-shadow: 0px 5px 16px rgba(0,0,0, 0.3);
+ box-shadow: 0px 5px 16px rgba(0,0,0, 0.3);
+}
+.point-of-sale .pos-sale-ticket .emph{
+ font-size: 20px;
+ margin:5px;
}
.point-of-sale .pos-sale-ticket table {
width: 100%;
@@ -660,18 +749,18 @@
}
@media print {
- #oe_header, #oe_menu, .point-of-sale #topheader, .point-of-sale #leftpane {
- display: none;
+ .point-of-sale #topheader, .point-of-sale #leftpane {
+ display: none !important;
}
.point-of-sale #content {
- top: 0px;
+ top: 0px !important;
}
.point-of-sale #rightpane {
- left: 0px;
+ left: 0px !important;
background-color: white;
}
#receipt-screen header {
- display: none;
+ display: none !important;
}
#receipt-screen {
text-align: left;
@@ -761,6 +850,28 @@
font-family: "Inconsolata";
}
+/* e) The Welcome Screen */
+.point-of-sale .goodbye-message{
+ position: absolute;
+ left:50%;
+ top:30%;
+ width:500px;
+ height:400px;
+ margin-left: -250px;
+ margin-top: -200px;
+ padding:10px;
+ padding-top:20px;
+ text-align:center;
+ font-size:20px;
+ font-weight:bold;
+ background-color: #F0EEEE;
+ border: 1px solid #E0DDDD;
+ -webkit-box-shadow: 0px 10px 20px rgba(0,0,0, 0.3);
+ -moz-box-shadow: 0px 10px 20px rgba(0,0,0, 0.3);
+ -ms-box-shadow: 0px 10px 20px rgba(0,0,0, 0.3);
+ z-index:1150;
+}
+
/* ********* The OrderWidget ********* */
.point-of-sale .order-container{
@@ -846,6 +957,10 @@
-moz-transition: background 50ms ease-in-out;
transition: background 50ms ease-in-out;
}
+.point-of-sale .order .orderline.empty:hover{
+ background: transparent;
+ cursor: default;
+}
.point-of-sale .order .orderline.selected{
background: rgba(140,143,183,0.2);
@@ -933,9 +1048,9 @@
background: -moz-linear-gradient(#f0f0f0, #e2e2e2);
background: -ms-linear-gradient(#f0f0f0, #e2e2e2);
background: linear-gradient(#f0f0f0, #e2e2e2);
- -webkit-box-shadow: 0px 2px 2px rgba(0,0,0, 0.3);
- -moz-box-shadow: 0px 2px 2px rgba(0,0,0, 0.3);
- box-shadow: 0px 2px 2px rgba(0,0,0, 0.3);
+ -webkit-box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
+ -moz-box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
+ box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
}
.point-of-sale .pos-actionbar .button .label{
margin-top: 37px;
@@ -957,6 +1072,23 @@
-webkit-transition-timing-function: ease-out;
}
+.point-of-sale .pos-actionbar .button.disabled{
+ color:#AAA;
+}
+.point-of-sale .pos-actionbar .button.disabled:hover{
+ border: 1px solid #cacaca;
+ border-radius: 4px;
+
+ background: #e2e2e2;
+ background: -webkit-linear-gradient(#f0f0f0, #e2e2e2);
+ background: -moz-linear-gradient(#f0f0f0, #e2e2e2);
+ background: -ms-linear-gradient(#f0f0f0, #e2e2e2);
+ background: linear-gradient(#f0f0f0, #e2e2e2);
+ -webkit-box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
+ -moz-box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
+ box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
+}
+
.point-of-sale .pos-actionbar .button.rightalign{
float:right;
}
diff --git a/addons/point_of_sale/static/src/img/default_category_photo.png b/addons/point_of_sale/static/src/img/default_category_photo.png
new file mode 100644
index 00000000000..25af75ee815
Binary files /dev/null and b/addons/point_of_sale/static/src/img/default_category_photo.png differ
diff --git a/addons/point_of_sale/static/src/img/icons/bancontact.svg b/addons/point_of_sale/static/src/img/icons/bancontact.svg
new file mode 100644
index 00000000000..70604078014
--- /dev/null
+++ b/addons/point_of_sale/static/src/img/icons/bancontact.svg
@@ -0,0 +1,1114 @@
+
+
\ No newline at end of file
diff --git a/addons/point_of_sale/static/src/img/icons/scan.svg b/addons/point_of_sale/static/src/img/icons/scan.svg
new file mode 100644
index 00000000000..4d753acf638
--- /dev/null
+++ b/addons/point_of_sale/static/src/img/icons/scan.svg
@@ -0,0 +1,984 @@
+
+
\ No newline at end of file
diff --git a/addons/point_of_sale/static/src/js/TODO.txt b/addons/point_of_sale/static/src/js/TODO.txt
new file mode 100644
index 00000000000..edadbe188ea
--- /dev/null
+++ b/addons/point_of_sale/static/src/js/TODO.txt
@@ -0,0 +1,62 @@
+v Affichage Catégories doivent respecter le poids
+v Affichage Catégories par poids doivent avoir une hiérarchie plate
+v Le Onscreen keyboard ne fonctionne plus
+v Scrolling dans la sélection produit et dans
+ la liste de courses
+v Réductions
+v Redesign liste de courses
+v bugs scans produits par prix par poids
+v si pas photo photo par defaut
+v WebSQL
+v Redesign header, bouton retour aux backend en mode caissière
+v bouton exit en mode caissière doit retourner au backend.
+v posting orders
+v bug ajout ligne payment à zero + curseur dedans + suppression + design plus grand
+v si case print via proxy cochée, alors on skip l'ecran receipt
+v bouton exit self checkout
+v demarrage en mode self-checkout si self-checkout
+v produits poid code barre non reconnus
+v activer popup client aux écrans self-checkout
+v discount pas plus grand que 100
+v numpad dans l'écran payment pour pouvoir entrer le montant
+v différence de total. Methode d'arrondis
+
+- login alternatif
+- user preferences : désactiver la balance etc.
+- numpad state parfois sans state
+- numpad state en mode pesée ?? le cacher ?
+- Redesign du receipt
+- retirer le receipt screen
+- le numpad en mode paymenet marche pas
+- l'écran de payement est hyper laid
+- l'écran de pesée est hyper laid
+x générer les données de printing
+x popups d'erreur certainement buggés
+
+TODO AUG 20
+-----------
+
+* GREEN STATUS
+ - make it green !
+
+* TRUNK
+ - Connection status tooltip
+ v Remove receipt screen
+ - Finish the receipt JSON generation
+ - Modifie le widget de liste course
+ v supprimer l'écran
+ v bloquer sur l'impression
+ v L'impression est foireuse
+
+* CLIENT
+ - create a new branch
+ - Self-checkout welcome screen
+ - removal of products for the root category
+ - Terminal de payement
+ - Code à barres
+ - Vidanges
+ - Design
+ - Backport
+ - CSS pour écran résistif
+ - Test sur matos
+
diff --git a/addons/point_of_sale/static/src/js/db.js b/addons/point_of_sale/static/src/js/db.js
new file mode 100644
index 00000000000..77f7307b48f
--- /dev/null
+++ b/addons/point_of_sale/static/src/js/db.js
@@ -0,0 +1,235 @@
+function openerp_pos_db(instance, module){
+
+ /* PosLS is a LocalStorage based implementation of the point of sale database,
+ it performs better for few products, but does not scale beyond 500 products.
+ */
+ module.PosLS = instance.web.Class.extend({
+ name: 'openerp_pos_ls', //the prefix of the localstorage data
+ limit: 100, // the maximum number of results returned by a search
+ init: function(options){
+ options = options || {};
+ this.name = options.name || this.name;
+ this.limit = options.limit || this.limit;
+
+ //cache the data in memory to avoid roundtrips to the localstorage
+ this.cache = {};
+
+ this.category_by_id = {};
+ this.root_category_id = 0;
+ this.category_products = {};
+ this.category_ancestors = {};
+ this.category_childs = {};
+ this.category_parent = {};
+ this.category_search_string = {};
+ },
+ /* returns the category object from its id. If you pass a list of id as parameters, you get
+ * a list of category objects.
+ */
+ get_category_by_id: function(categ_id){
+ if(categ_id instanceof Array){
+ var list = [];
+ for(var i = 0, len = categ_id.length; i < len; i++){
+ var cat = this.category_by_id[categ_id[i]];
+ if(cat){
+ list.push(cat);
+ }else{
+ console.error("get_category_by_id: no category has id:",categ_id[i]);
+ }
+ }
+ return list;
+ }else{
+ return this.category_by_id[categ_id];
+ }
+ },
+ /* returns a list of the category's child categories ids, or an empty list
+ * if a category has no childs */
+ get_category_childs_ids: function(categ_id){
+ return this.category_childs[categ_id] || [];
+ },
+ /* returns a list of all ancestors (parent, grand-parent, etc) categories ids
+ * starting from the root category to the direct parent */
+ get_category_ancestors_ids: function(categ_id){
+ return this.category_ancestors[categ_id] || [];
+ },
+ /* returns the parent category's id of a category, or the root_category_id if no parent.
+ * the root category is parent of itself. */
+ get_category_parent_id: function(categ_id){
+ return this.category_parent[categ_id] || this.root_category_id;
+ },
+ /* adds categories definitions to the database. categories is a list of categories objects as
+ * returned by the openerp server. Categories must be inserted before the products or the
+ * product/ categories association may (will) not work properly */
+ add_categories: function(categories){
+ var self = this;
+ if(!this.category_by_id[this.root_category_id]){
+ this.category_by_id[this.root_category_id] = {
+ id : this.root_category_id,
+ name : 'Root',
+ };
+ }
+ for(var i=0, len = categories.length; i < len; i++){
+ this.category_by_id[categories[i].id] = categories[i];
+ }
+ for(var i=0, len = categories.length; i < len; i++){
+ var cat = categories[i];
+ var parent_id = cat.parent_id[0] || this.root_category_id;
+ this.category_parent[cat.id] = cat.parent_id[0];
+ if(!this.category_childs[parent_id]){
+ this.category_childs[parent_id] = [];
+ }
+ this.category_childs[parent_id].push(cat.id);
+ }
+ function make_ancestors(cat_id, ancestors){
+ self.category_ancestors[cat_id] = ancestors;
+
+ ancestors = ancestors.slice(0);
+ ancestors.push(cat_id);
+
+ var childs = self.category_childs[cat_id] || [];
+ for(var i=0, len = childs.length; i < len; i++){
+ make_ancestors(childs[i], ancestors);
+ }
+ }
+ make_ancestors(this.root_category_id, []);
+ },
+ /* loads a record store from the database. returns default if nothing is found */
+ load: function(store,deft){
+ if(this.cache[store] !== undefined){
+ return this.cache[store];
+ }
+ var data = localStorage[this.name + '_' + store];
+ if(data !== undefined){
+ data = JSON.parse(data);
+ this.cache[store] = data;
+ return data;
+ }else{
+ return deft;
+ }
+ },
+ /* saves a record store to the database */
+ save: function(store,data){
+ localStorage[this.name + '_' + store] = JSON.stringify(data);
+ this.cache[store] = data;
+ },
+ _product_search_string: function(product){
+ var str = '' + product.id + ':' + product.name;
+ if(product.ean13){
+ str += '|' + product.ean13;
+ }
+ return str + '\n';
+ },
+ add_products: function(products){
+ var stored_products = this.load('products',{});
+ var stored_categories = this.load('categories',{});
+
+ if(!products instanceof Array){
+ products = [products];
+ }
+ for(var i = 0, len = products.length; i < len; i++){
+ var product = products[i];
+ var search_string = this._product_search_string(product);
+ var categ_id = product.pos_categ_id[0];
+ if(!stored_categories[categ_id]){
+ stored_categories[categ_id] = [];
+ }
+ stored_categories[categ_id].push(product.id);
+
+ if(this.category_search_string[categ_id] === undefined){
+ this.category_search_string[categ_id] = '';
+ }
+ this.category_search_string[categ_id] += search_string;
+
+ var ancestors = this.get_category_ancestors_ids(categ_id) || [];
+
+ for(var j = 0; j < ancestors.length; j++){
+ var ancestor = ancestors[j];
+ if(! stored_categories[ancestor]){
+ stored_categories[ancestor] = [];
+ }
+ stored_categories[ancestor].push(product.id);
+
+ if( this.category_search_string[ancestor] === undefined){
+ this.category_search_string[ancestor] = '';
+ }
+ this.category_search_string[ancestor] += search_string;
+ }
+ stored_products[product.id] = product;
+ }
+ this.save('products',stored_products);
+ this.save('categories',stored_categories);
+ },
+ /* removes all the data from the database. TODO : being able to selectively remove data */
+ clear: function(stores){
+ for(var i = 0, len = arguments.length; i < len; i++){
+ localStorage.removeItem(this.name + '_' + arguments[i]);
+ }
+ },
+ /* this internal methods returns the count of properties in an object. */
+ _count_props : function(obj){
+ var count = 0;
+ for(var prop in obj){
+ if(obj.hasOwnProperty(prop)){
+ count++;
+ }
+ }
+ return count;
+ },
+ get_product_by_id: function(id){
+ return this.load('products',{})[id];
+ },
+ get_product_by_ean13: function(ean13){
+ var products = this.load('products',{});
+ for(var i in products){
+ if( products[i] && products[i].ean13 === ean13){
+ return products[i];
+ }
+ }
+ return undefined;
+ },
+ get_product_by_category: function(category_id){
+ var stored_categories = this.load('categories',{});
+ var stored_products = this.load('products',{});
+ var product_ids = stored_categories[category_id];
+ var list = [];
+ for(var i = 0, len = Math.min(product_ids.length,this.limit); i < len; i++){
+ list.push(stored_products[product_ids[i]]);
+ }
+ return list;
+ },
+ /* returns a list of products with :
+ * - a category that is or is a child of category_id,
+ * - a name, package or ean13 containing the query (case insensitive)
+ */
+ search_product_in_category: function(category_id, query){
+ var re = RegExp("([0-9]+):.*?"+query,"gi");
+ var results = [];
+ for(var i = 0; i < this.limit; i++){
+ r = re.exec(this.category_search_string[category_id]);
+ if(r){
+ var id = Number(r[1]);
+ results.push(this.get_product_by_id(id));
+ }else{
+ break;
+ }
+ }
+ return results;
+ },
+ add_order: function(order){
+ var last_id = this.load('last_order_id',0);
+ var orders = this.load('orders',[]);
+ orders.push({id: last_id + 1, data: order});
+ this.save('last_order_id',last_id+1);
+ this.save('orders',orders);
+ },
+ remove_order: function(order_id){
+ var orders = this.load('orders',[]);
+ orders = _.filter(orders, function(order){
+ return order.id !== order_id;
+ });
+ this.save('orders',orders);
+ },
+ get_orders: function(){
+ return this.load('orders',[]);
+ },
+ });
+}
diff --git a/addons/point_of_sale/static/src/js/pos_devices.js b/addons/point_of_sale/static/src/js/devices.js
similarity index 82%
rename from addons/point_of_sale/static/src/js/pos_devices.js
rename to addons/point_of_sale/static/src/js/devices.js
index 2857fc39937..a6be16b3835 100644
--- a/addons/point_of_sale/static/src/js/pos_devices.js
+++ b/addons/point_of_sale/static/src/js/devices.js
@@ -17,7 +17,9 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
delay_payment: function(){ this.activate(); this.payment_status = 'waiting_for_payment'; },
}))();
- //window.debug_devices = debug_devices;
+ if(jQuery.deparam(jQuery.param.querystring()).debug !== undefined){
+ window.debug_devices = debug_devices;
+ }
// this object interfaces with the local proxy to communicate to the various hardware devices
// connected to the Point of Sale. As the communication only goes from the POS to the proxy,
@@ -42,21 +44,26 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
success_callback = success_callback || function(){};
error_callback = error_callback || function(){};
- if(debug_devices && debug_devices.active){
+
+ if(jQuery.deparam(jQuery.param.querystring()).debug !== undefined){
console.log('PROXY:',name,params);
- }else{
+ }
+
+ if(!(debug_devices && debug_devices.active)){
this.connection.rpc('/pos/'+name, params || {}, success_callback, error_callback);
}
},
//a product has been scanned and recognized with success
- scan_item_success: function(){
- this.message('scan_item_success');
+ // ean is a parsed ean object
+ scan_item_success: function(ean){
+ this.message('scan_item_success',ean);
},
- //a product has been scanned but not recognized
- scan_item_error_unrecognized: function(){
- this.message('scan_item_error_unrecognized');
+ // a product has been scanned but not recognized
+ // ean is a parsed ean object
+ scan_item_error_unrecognized: function(ean){
+ this.message('scan_item_error_unrecognized',ean);
},
//the client is asking for help
@@ -222,11 +229,11 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
this.action_callback_stack = [];
- this.price_prefix_set = attributes.price_prefix_set || {'02':'', '22':'', '24':'', '26':'', '28':''};
- this.weight_prefix_set = attributes.weight_prefix_set || {'21':'','23':'','27':'','29':'','25':''};
- this.client_prefix_set = attributes.weight_prefix_set || {'42':''};
- this.cashier_prefix_set = attributes.weight_prefix_set || {'40':''};
- this.discount_prefix_set = attributes.weight_prefix_set || {'44':''};
+ this.weight_prefix_set = attributes.weight_prefix_set || {'21':''};
+ this.discount_prefix_set = attributes.discount_prefix_set || {'22':''};
+ this.price_prefix_set = attributes.price_prefix_set || {'23':''};
+ this.cashier_prefix_set = attributes.cashier_prefix_set || {'041':''};
+ this.client_prefix_set = attributes.client_prefix_set || {'042':''};
},
save_callbacks: function(){
var callbacks = {};
@@ -270,41 +277,37 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
this.action_callback[action] = undefined;
}
},
-
-
+ // returns the checksum of the ean, or -1 if the ean has not the correct length, ean must be a string
+ ean_checksum: function(ean){
+ var code = ean.split('');
+ if(code.length !== 13){
+ return -1;
+ }
+ var oddsum = 0, evensum = 0, total = 0;
+ code = code.reverse().splice(1);
+ for(var i = 0; i < code.length; i++){
+ if(i % 2 == 0){
+ oddsum += Number(code[i]);
+ }else{
+ evensum += Number(code[i]);
+ }
+ }
+ total = oddsum * 3 + evensum;
+ return Number((10 - total % 10) % 10);
+ },
// returns true if the ean is a valid EAN codebar number by checking the control digit.
// ean must be a string
check_ean: function(ean){
- var code = ean.split('');
- for(var i = 0; i < code.length; i++){
- code[i] = Number(code[i]);
+ return this.ean_checksum(ean) === Number(ean[ean.length-1]);
+ },
+ // returns a valid zero padded ean13 from an ean prefix. the ean prefix must be a string.
+ sanitize_ean:function(ean){
+ ean = ean.substr(0,13);
+
+ for(var n = 0, count = (13 - ean.length); n < count; n++){
+ ean = ean + '0';
}
- var st1 = code.slice();
- var st2 = st1.slice(0,st1.length-1).reverse();
- // some EAN13 barcodes have a length of 12, as they start by 0
- while (st2.length < 12) {
- st2.push(0);
- }
- var countSt3 = 1;
- var st3 = 0;
- $.each(st2, function() {
- if (countSt3%2 === 1) {
- st3 += this;
- }
- countSt3 ++;
- });
- st3 *= 3;
- var st4 = 0;
- var countSt4 = 1;
- $.each(st2, function() {
- if (countSt4%2 === 0) {
- st4 += this;
- }
- countSt4 ++;
- });
- var st5 = st3 + st4;
- var cd = (10 - (st5%10)) % 10;
- return code[code.length-1] === cd;
+ return ean.substr(0,12) + this.ean_checksum(ean);
},
// attempts to interpret an ean (string encoding an ean)
@@ -327,41 +330,47 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
type:'unknown', //
prefix:'',
ean:ean,
+ base_ean: ean,
id:'',
value: 0,
unit: 'none',
};
- var prefix2 = ean.substring(0,2);
+ console.log('ean',ean);
- if(!this.check_ean(ean)){
+ function match_prefix(prefix_set, type){
+ for(prefix in prefix_set){
+ if(ean.substring(0,prefix.length) === prefix){
+ parse_result.prefix = prefix;
+ parse_result.type = type;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ if (!this.check_ean(ean)){
parse_result.type = 'error';
- }else if (prefix2 in this.price_prefix_set){
- parse_result.type = 'price';
- parse_result.prefix = prefix2;
+ } else if( match_prefix(this.price_prefix_set,'price')){
parse_result.id = ean.substring(0,7);
+ parse_result.base_ean = this.sanitize_ean(ean.substring(0,7));
parse_result.value = Number(ean.substring(7,12))/100.0;
parse_result.unit = 'euro';
- } else if (prefix2 in this.weight_prefix_set){
- parse_result.type = 'weight';
- parse_result.prefix = prefix2;
+ } else if( match_prefix(this.weight_prefix_set,'weight')){
parse_result.id = ean.substring(0,7);
parse_result.value = Number(ean.substring(7,12))/1000.0;
+ parse_result.base_ean = this.sanitize_ean(ean.substring(0,7));
parse_result.unit = 'Kg';
- }else if (prefix2 in this.client_prefix_set){
- parse_result.type = 'client';
- parse_result.prefix = prefix2;
+ } else if( match_prefix(this.client_prefix_set,'client')){
parse_result.id = ean.substring(0,7);
- }else if (prefix2 in this.cashier_prefix_set){
- parse_result.type = 'cashier';
- parse_result.prefix = prefix2;
+ parse_result.unit = 'Kg';
+ } else if( match_prefix(this.cashier_prefix_set,'cashier')){
parse_result.id = ean.substring(0,7);
- }else if (prefix2 in this.discount_prefix_set){
- parse_result.type = 'discount';
- parse_result.prefix = prefix2;
+ } else if( match_prefix(this.discount_prefix_set,'discount')){
parse_result.id = ean.substring(0,7);
+ parse_result.base_ean = this.sanitize_ean(ean.substring(0,7));
parse_result.value = Number(ean.substring(7,12))/100.0;
parse_result.unit = '%';
- }else{
+ } else {
parse_result.type = 'unit';
parse_result.prefix = '';
parse_result.id = ean;
@@ -402,7 +411,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
var parse_result = self.parse_ean(codeNumbers.join(''));
if (parse_result.type === 'error') { //most likely a checksum error, raise warning
- console.error('ERROR: barcode checksum error:',parse_result);
+ console.warn('WARNING: barcode checksum error:',parse_result);
}else if(parse_result.type in {'unit':'', 'weight':'', 'price':''}){ //ean is associated to a product
if(self.action_callback['product']){
self.action_callback['product'](parse_result);
diff --git a/addons/point_of_sale/static/src/js/pos_main.js b/addons/point_of_sale/static/src/js/main.js
similarity index 92%
rename from addons/point_of_sale/static/src/js/pos_main.js
rename to addons/point_of_sale/static/src/js/main.js
index 71ac69c6067..b9263b91314 100644
--- a/addons/point_of_sale/static/src/js/pos_main.js
+++ b/addons/point_of_sale/static/src/js/main.js
@@ -5,6 +5,8 @@ openerp.point_of_sale = function(instance) {
var module = instance.point_of_sale;
+ openerp_pos_db(instance,module); // import db.js
+
openerp_pos_models(instance,module); // import pos_models.js
openerp_pos_basewidget(instance,module); // import pos_basewidget.js
diff --git a/addons/point_of_sale/static/src/js/pos_models.js b/addons/point_of_sale/static/src/js/models.js
similarity index 58%
rename from addons/point_of_sale/static/src/js/pos_models.js
rename to addons/point_of_sale/static/src/js/models.js
index 8a1506769bd..a1f6def6bf3 100644
--- a/addons/point_of_sale/static/src/js/pos_models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -1,51 +1,6 @@
function openerp_pos_models(instance, module){ //module is instance.point_of_sale
var QWeb = instance.web.qweb;
- module.LocalStorageDAO = instance.web.Class.extend({
- add_operation: function(operation) {
- var self = this;
- return $.async_when().pipe(function() {
- var tmp = self._get('oe_pos_operations', []);
- var last_id = self._get('oe_pos_operations_sequence', 1);
- tmp.push({'id': last_id, 'data': operation});
- self._set('oe_pos_operations', tmp);
- self._set('oe_pos_operations_sequence', last_id + 1);
- });
- },
- remove_operation: function(id) {
- var self = this;
- return $.async_when().pipe(function() {
- var tmp = self._get('oe_pos_operations', []);
- tmp = _.filter(tmp, function(el) {
- return el.id !== id;
- });
- self._set('oe_pos_operations', tmp);
- });
- },
- get_operations: function() {
- var self = this;
- return $.async_when().pipe(function() {
- return self._get('oe_pos_operations', []);
- });
- },
- _get: function(key, default_) {
- var txt = localStorage['oe_pos_dao_'+key];
- if (! txt)
- return default_;
- return JSON.parse(txt);
- },
- _set: function(key, value) {
- localStorage['oe_pos_dao_'+key] = JSON.stringify(value);
- },
- reset_stored_data: function(){
- for(key in localStorage){
- if(key.indexOf('oe_pos_dao_') === 0){
- delete localStorage[key];
- }
- }
- },
- });
-
var fetch = function(model, fields, domain, ctx){
return new instance.web.Model(model).query(fields).filter(domain).context(ctx).all()
};
@@ -58,29 +13,20 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
// this is done asynchronously, a ready deferred alows the GUI to wait interactively
// for the loading to be completed
// There is a single instance of the PosModel for each Front-End instance, it is usually called
- // 'pos' and is available to almost all widgets.
+ // 'pos' and is available to all widgets extending PosWidget.
module.PosModel = Backbone.Model.extend({
initialize: function(session, attributes) {
Backbone.Model.prototype.initialize.call(this, attributes);
var self = this;
- this.dao = new module.LocalStorageDAO(); // used to store the order's data on the Hard Drive
+ this.session = session;
this.ready = $.Deferred(); // used to notify the GUI that the PosModel has loaded all resources
this.flush_mutex = new $.Mutex(); // used to make sure the orders are sent to the server once at time
- //this.build_tree = _.bind(this.build_tree, this); // ???
- this.session = session;
- this.categories = {};
- this.root_category = null;
- this.weightable_categories = []; // a flat list of all categories that directly contain weightable products
- this.barcode_reader = new module.BarcodeReader({'pos': this}); // used to read barcodes
- this.proxy = new module.ProxyDevice(); // used to communicate to the hardware devices via a local proxy
- // pos settings
- this.use_scale = false;
- this.use_proxy_printer = false;
- this.use_virtual_keyboard = false;
- this.use_websql = false;
- this.use_barcode_scanner = false;
+ this.barcode_reader = new module.BarcodeReader({'pos': this}); // used to read barcodes
+ this.proxy = new module.ProxyDevice(); // used to communicate to the hardware devices via a local proxy
+ this.db = new module.PosLS(); // a database used to store the products and categories
+ this.db.clear('products','categories');
// default attributes values. If null, it will be loaded below.
this.set({
@@ -98,12 +44,12 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
'products': new module.ProductCollection(),
'cashRegisters': null,
- 'product_list': null, // the list of all products, does not change.
'bank_statements': null,
'taxes': null,
'pos_session': null,
'pos_config': null,
- 'categories': null,
+ 'units': null,
+ 'units_by_id': null,
'selectedOrder': undefined,
});
@@ -113,10 +59,9 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
// We fetch the backend data on the server asynchronously. this is done only when the pos user interface is launched,
// Any change on this data made on the server is thus not reflected on the point of sale until it is relaunched.
- var user_def = fetch('res.users',['name','company_id'],[['id','=',this.session.uid]])
+ var loaded = fetch('res.users',['name','company_id'],[['id','=',this.session.uid]])
.pipe(function(users){
- var user = users[0];
- self.set('user',user);
+ self.set('user',users[0]);
return fetch('res.company',
[
@@ -124,171 +69,114 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
'email',
'website',
'company_registry',
- //TODO contact_address
'vat',
'name',
- 'phone'
+ 'phone',
+ 'partner_id',
],
- [['id','=',user.company_id[0]]])
+ [['id','=',users[0].company_id[0]]]);
}).pipe(function(companies){
- var company = companies[0];
- self.set('company',company);
+ self.set('company',companies[0]);
- return fetch('res.currency',['symbol','position'],[['id','=',company.currency_id[0]]]);
- }).pipe(function (currencies){
+ return fetch('res.partner',['contact_address'],[['id','=',companies[0].partner_id[0]]]);
+ }).pipe(function(company_partners){
+ self.get('company').contact_address = company_partners[0].contact_address;
+
+ return fetch('res.currency',['symbol','position'],[['id','=',self.get('company').currency_id[0]]]);
+ }).pipe(function(currencies){
self.set('currency',currencies[0]);
- });
- var cat_def = fetch('pos.category', ['id','name', 'parent_id', 'child_id', 'image_medium'])
- .pipe(function(result){
- return self.set({'categories': result});
- });
-
- var uom_def = fetch( //unit of measure
- 'product.uom',
- null,
- null
- ).then(function(result){
- self.set({'units': result});
+ return fetch('product.uom', null, null);
+ }).pipe(function(units){
+ self.set('units',units);
var units_by_id = {};
- for(var i = 0, len = result.length; i < len; i++){
- units_by_id[result[i].id] = result[i];
+ for(var i = 0, len = units.length; i < len; i++){
+ units_by_id[units[i].id] = units[i];
}
- self.set({'units_by_id':units_by_id});
- });
-
- var pack_def = fetch(
- 'product.packaging',
- null,
- null
- ).then(function(packaging){
- self.set('product.packaging',packaging);
- });
-
- var users_def = fetch(
- 'res.users',
- ['name','ean13'],
- [['ean13', '!=', false]]
- ).then(function(result){
- self.set({'user_list':result});
- });
-
- var tax_def = fetch('account.tax', ['amount','price_include','type'])
- .then(function(result){
- self.set({'taxes': result});
- });
-
- var session_def = fetch( // loading the PoS Session.
- 'pos.session',
- ['id', 'journal_ids','name','user_id','config_id','start_at','stop_at'],
- [['state', '=', 'opened'], ['user_id', '=', this.session.uid]]
- ).pipe(function(result) {
-
- // some data are associated with the pos session, like the pos config and bank statements.
- // we must have a valid session before we can read those.
+ self.set('units_by_id',units_by_id);
- var session_data_def = new $.Deferred();
+ return fetch('product.packaging', null, null);
+ }).pipe(function(packagings){
+ self.set('product.packaging',packagings);
- if( result.length !== 0 ) {
- var pos_session = result[0];
+ return fetch('res.users', ['name','ean13'], [['ean13', '!=', false]]);
+ }).pipe(function(users){
+ self.set('user_list',users);
- self.set({'pos_session': pos_session});
+ return fetch('account.tax', ['amount', 'price_include', 'type']);
+ }).pipe(function(taxes){
+ self.set('taxes', taxes);
- var pos_config_def = fetch(
- 'pos.config',
- ['name','journal_ids','shop_id','journal_id',
- 'iface_self_checkout', 'iface_websql', 'iface_led', 'iface_cashdrawer',
- 'iface_payment_terminal', 'iface_electronic_scale', 'iface_barscan', 'iface_vkeyboard',
- 'iface_print_via_proxy','iface_cashdrawer','state','sequence_id','session_ids'],
- [['id','=', pos_session.config_id[0]]]
- ).pipe(function(result){
- var pos_config = result[0]
-
- self.set({'pos_config': pos_config});
- self.use_scale = pos_config.iface_electronic_scale || false;
- self.use_proxy_printer = pos_config.iface_print_via_proxy || false;
- self.use_virtual_keyboard = pos_config.iface_vkeyboard || false;
- self.use_websql = pos_config.iface_websql || false;
- self.use_barcode_scanner = pos_config.iface_barscan || false;
- self.use_selfcheckout = pos_config.iface_self_checkout || false;
- self.use_cashbox = pos_config.iface_cashdrawer || false;
+ return fetch(
+ 'pos.session',
+ ['id', 'journal_ids','name','user_id','config_id','start_at','stop_at'],
+ [['state', '=', 'opened'], ['user_id', '=', self.session.uid]]
+ );
+ }).pipe(function(sessions){
+ self.set('pos_session', sessions[0]);
- return shop_def = fetch('sale.shop',[], [['id','=',pos_config.shop_id[0]]])
- }).pipe(function(shops){
- self.set('shop',shops[0]);
- return fetch(
- 'product.product',
- //context {pricelist: shop.pricelist_id[0]}
- ['name', 'list_price','price','pos_categ_id', 'taxes_id','image_medium', 'ean13', 'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type'],
- [['pos_categ_id','!=', false]],
- {pricelist: shops[0].pricelist_id[0]} // context for price
- );
- }).pipe( function(product_list){
- self.set({'product_list': product_list});
- });
+ return fetch(
+ 'pos.config',
+ ['name','journal_ids','shop_id','journal_id',
+ 'iface_self_checkout', 'iface_led', 'iface_cashdrawer',
+ 'iface_payment_terminal', 'iface_electronic_scale', 'iface_barscan', 'iface_vkeyboard',
+ 'iface_print_via_proxy','iface_cashdrawer','state','sequence_id','session_ids'],
+ [['id','=', self.get('pos_session').config_id[0]]]
+ );
+ }).pipe(function(configs){
+ var pos_config = configs[0];
+ self.set('pos_config', pos_config);
+ self.iface_electronic_scale = !!pos_config.iface_electronic_scale;
+ self.iface_print_via_proxy = !!pos_config.iface_print_via_proxy;
+ self.iface_vkeyboard = !!pos_config.iface_vkeyboard;
+ self.iface_self_checkout = !!pos_config.iface_self_checkout;
+ self.iface_cashdrawer = !!pos_config.iface_cashdrawer;
- var bank_def = fetch(
- 'account.bank.statement',
- ['account_id','currency','journal_id','state','name','user_id','pos_session_id'],
- [['state','=','open'],['pos_session_id', '=', pos_session.id]]
- ).then(function(result){
- self.set({'bank_statements':result});
- });
+ return fetch('sale.shop',[],[['id','=',pos_config.shop_id[0]]]);
+ }).pipe(function(shops){
+ self.set('shop',shops[0]);
- var journal_def = fetch(
- 'account.journal',
- undefined,
- [['user_id','=',pos_session.user_id[0]]]
- ).then(function(result){
- self.set({'journals':result});
- });
+ return fetch('pos.category', ['id','name','parent_id','child_id','image'])
+ }).pipe(function(categories){
+ self.db.add_categories(categories);
- // associate the bank statements with their journals.
- var bank_process_def = $.when(bank_def, journal_def)
- .then(function(){
- var bank_statements = self.get('bank_statements');
- var journals = self.get('journals');
- for(var i = 0, ilen = bank_statements.length; i < ilen; i++){
- for(var j = 0, jlen = journals.length; j < jlen; j++){
- if(bank_statements[i].journal_id[0] === journals[j].id){
- bank_statements[i].journal = journals[j];
- bank_statements[i].self_checkout_payment_method = journals[j].self_checkout_payment_method;
- }
- }
- }
- });
+ return fetch(
+ 'product.product',
+ ['name', 'list_price','price','pos_categ_id', 'taxes_id', 'ean13', 'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type'],
+ [['pos_categ_id','!=', false]],
+ {pricelist: self.get('shop').pricelist_id[0]} // context for price
+ );
+ }).pipe(function(products){
+ self.db.add_products(products);
- session_data_def = $.when(pos_config_def,bank_def,journal_def,bank_process_def);
+ return fetch(
+ 'account.bank.statement',
+ ['account_id','currency','journal_id','state','name','user_id','pos_session_id'],
+ [['state','=','open'],['pos_session_id', '=', self.get('pos_session').id]]
+ );
+ }).pipe(function(bank_statements){
+ self.set('bank_statements', bank_statements);
- }else{
- session_data_def.reject();
- }
- return session_data_def;
- });
-
- // associate the products with their categories
- var prod_process_def = $.when(cat_def, session_def)
- .pipe(function(){
- var product_list = self.get('product_list');
- var categories = self.get('categories');
- var cat_by_id = {};
- for(var i = 0; i < categories.length; i++){
- cat_by_id[categories[i].id] = categories[i];
- }
- //set the parent in the category
- for(var i = 0; i < categories.length; i++){
- categories[i].parent_category = cat_by_id[categories[i].parent_id[0]];
- }
- for(var i = 0; i < product_list.length; i++){
- product_list[i].pos_category = cat_by_id[product_list[i].pos_categ_id[0]];
+ return fetch('account.journal', undefined, [['user_id','=', self.get('pos_session').user_id[0]]]);
+ }).pipe(function(journals){
+ self.set('journals',journals);
+
+ // associate the bank statements with their journals.
+ var bank_statements = self.get('bank_statements');
+ for(var i = 0, ilen = bank_statements.length; i < ilen; i++){
+ for(var j = 0, jlen = journals.length; j < jlen; j++){
+ if(bank_statements[i].journal_id[0] === journals[j].id){
+ bank_statements[i].journal = journals[j];
+ bank_statements[i].self_checkout_payment_method = journals[j].self_checkout_payment_method;
+ }
+ }
}
+ self.set({'cashRegisters' : new module.CashRegisterCollection(self.get('bank_statements'))});
});
// when all the data has loaded, we compute some stuff, and declare the Pos ready to be used.
- $.when(pack_def, cat_def, user_def, users_def, uom_def, session_def, tax_def, prod_process_def, user_def, this.flush())
+ $.when(loaded)
.then(function(){
- self.build_categories();
- self.set({'cashRegisters' : new module.CashRegisterCollection(self.get('bank_statements'))});
//self.log_loaded_data(); //Uncomment if you want to log the data to the console for easier debugging
self.ready.resolve();
},function(){
@@ -302,7 +190,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
log_loaded_data: function(){
console.log('PosModel data has been loaded:');
console.log('PosModel: categories:',this.get('categories'));
- console.log('PosModel: product_list:',this.get('product_list'));
console.log('PosModel: units:',this.get('units'));
console.log('PosModel: bank_statements:',this.get('bank_statements'));
console.log('PosModel: journals:',this.get('journals'));
@@ -316,7 +203,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
console.log('PosModel: user_list:',this.get('user_list'));
console.log('PosModel: user:',this.get('user'));
console.log('PosModel.session:',this.session);
- console.log('PosModel.categories:',this.categories);
console.log('PosModel end of data log.');
},
@@ -324,7 +210,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
// order and a valid selected order
on_removed_order: function(removed_order){
if( this.get('orders').isEmpty()){
- this.add_and_select_order(new module.Order({ pos: this }));
+ this.add_new_order();
}
if( this.get('selectedOrder') === removed_order){
this.set({ selectedOrder: this.get('orders').last() });
@@ -333,175 +219,70 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
// saves the order locally and try to send it to the backend. 'record' is a bizzarely defined JSON version of the Order
push_order: function(record) {
- var self = this;
- return this.dao.add_operation(record).pipe(function(){
- return self.flush();
- });
+ this.db.add_order(record);
+ this.flush();
},
- add_and_select_order: function(newOrder) {
- (this.get('orders')).add(newOrder);
- return this.set({
- selectedOrder: newOrder
- });
+ //creates a new empty order and sets it as the current order
+ add_new_order: function(){
+ var order = new module.Order({pos:this});
+ this.get('orders').add(order);
+ this.set('selectedOrder', order);
},
-
- // attemps to send all pending orders ( stored in the DAO ) to the server.
- // it will do it one by one, and remove the successfully sent ones from the DAO once
- // it has been confirmed that they have been received.
+
+ // attemps to send all pending orders ( stored in the pos_db ) to the server,
+ // and remove the successfully sent ones from the db once
+ // it has been confirmed that they have been sent correctly.
flush: function() {
//this makes sure only one _int_flush is called at the same time
return this.flush_mutex.exec(_.bind(function() {
- return this._int_flush();
+ return this._flush(0);
}, this));
},
- _int_flush : function() {
+ // attempts to send an order of index 'index' in the list of order to send. The index
+ // is used to skip orders that failed. do not call this method outside the mutex provided
+ // by flush()
+ _flush: function(index){
var self = this;
+ var orders = this.db.get_orders();
+ self.set('nbr_pending_operations',orders.length);
- this.dao.get_operations().pipe(function(operations) {
- // operations are really Orders that are converted to json.
- // they are saved to disk and then we attempt to send them to the backend so that they can
- // be applied.
- // since the network is not reliable we potentially have many 'pending operations' that have not been sent.
- self.set( {'nbr_pending_operations':operations.length} );
- if(operations.length === 0){
- return $.when();
- }
- var order = operations[0];
-
- // we prevent the default error handler and assume errors
- // are a normal use case, except we stop the current iteration
-
- return (new instance.web.Model('pos.order')).get_func('create_from_ui')([order])
- .fail(function(unused, event){
- // wtf ask niv
- event.preventDefault();
- })
- .pipe(function(){
- // success: remove the successfully sent operation, and try to send the next one
- self.dao.remove_operation(operations[0].id).pipe(function(){
- return self._int_flush();
- });
- }, function(){
- // in case of error we just sit there and do nothing. wtf ask niv
- return $.when();
- });
- });
+ var order = orders[index];
+ if(!order){
+ return;
+ }
+ //try to push an order to the server
+ (new instance.web.Model('pos.order')).get_func('create_from_ui')([order])
+ .fail(function(unused, event){
+ //don't show error popup if it fails
+ event.preventDefault();
+ console.error('Failed to send order:',order);
+ self._flush(index+1);
+ })
+ .done(function(){
+ //remove from db if success
+ self.db.remove_order(order.id);
+ self._flush(index);
+ });
},
- // this adds several properties to the categories in order to make it easier to diplay them
- // fields added include the list of product relevant to each category, list of child categories,
- // list of ancestors, etc.
- build_categories : function(){
- var categories = this.get('categories');
- var products = this.get('product_list');
+ scan_product: function(parsed_ean){
+ var self = this;
+ var product = this.db.get_product_by_ean13(parsed_ean.base_ean);
+ var selectedOrder = this.get('selectedOrder');
- //append the content of array2 into array1
- function append(array1, array2){
- for(var i = 0, len = array2.length; i < len; i++){
- array1.push(array2[i]);
- }
+ if(!product){
+ return false;
}
- function appendSet(set1, set2){
- for(key in set2){
- set1[key] = set2[key];
- }
+ if(parsed_ean.type === 'price'){
+ selectedOrder.addProduct(new module.Product(product), {price:parsed_ean.value});
+ }else if(parsed_ean.type === 'weight'){
+ selectedOrder.addProduct(new module.Product(product), {quantity:parsed_ean.value, merge:false});
+ }else{
+ selectedOrder.addProduct(new module.Product(product));
}
-
- var categories_by_id = {};
- for(var i = 0; i < categories.length; i++){
- categories_by_id[categories[i].id] = categories[i];
- }
- this.categories_by_id = categories_by_id;
-
- var root_category = {
- name : 'Root',
- id : 0,
- parent : null,
- childrens : [],
- };
-
- // add parent and childrens field to categories, find root_categories
- for(var i = 0; i < categories.length; i++){
- var cat = categories[i];
-
- cat.parent = categories_by_id[cat.parent_id[0]];
- if(!cat.parent){
- root_category.childrens.push(cat);
- cat.parent = root_category;
- }
-
- cat.childrens = [];
- for(var j = 0; j < cat.child_id.length; j++){
- cat.childrens.push(categories_by_id[ cat.child_id[j] ]);
- }
- }
-
- categories.push(root_category);
-
- // set some default fields for next steps
- for(var i = 0; i < categories.length; i++){
- var cat = categories[i];
-
- cat.product_list = []; //list of all products in the category
- cat.product_set = {}; // [product.id] === true if product is in category
- cat.weightable_product_list = [];
- cat.weightable_product_set = {};
- cat.weightable = false; //true if directly contains weightable products
- }
-
- this.root_category = root_category;
-
- //we add the products to the categories.
- for(var i = 0, len = products.length; i < len; i++){
- var product = products[i];
- var cat = categories_by_id[product.pos_categ_id[0]];
- if(cat){
- cat.product_list.push(product);
- cat.product_set[product.id] = true;
- if(product.to_weight){
- cat.weightable_product_list.push(product);
- cat.weightable_product_set[product.id] = true;
- cat.weightable = true;
- }
- }
- }
-
- // we build a flat list of all categories that directly contains weightable products
- this.weightable_categories = [];
- for(var i = 0, len = categories.length; i < len; i++){
- var cat = categories[i];
- if(cat.weightable){
- this.weightable_categories.push(cat);
- }
- }
-
- // add ancestor field to categories, contains the list of parents of parents, from root to parent
- function make_ancestors(cat, ancestors){
- cat.ancestors = ancestors.slice(0);
- ancestors.push(cat);
-
- for(var i = 0; i < cat.childrens.length; i++){
- make_ancestors(cat.childrens[i], ancestors.slice(0));
- }
- }
-
- //add the products of the subcategories to the parent categories
- function make_products(cat){
- for(var i = 0; i < cat.childrens.length; i++){
- make_products(cat.childrens[i]);
-
- append(cat.product_list, cat.childrens[i].product_list);
- append(cat.weightable_product_list, cat.childrens[i].weightable_product_list);
-
- appendSet(cat.product_set, cat.childrens[i].product_set);
- appendSet(cat.weightable_product_set, cat.childrens[i].weightable_product_set);
- }
- }
-
- make_ancestors(root_category,[]);
- make_products(root_category);
+ return true;
},
});
@@ -608,15 +389,10 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
return false;
}else if(this.get_discount() > 0){ // we don't merge discounted orderlines
return false;
- }else if(this.get_product_type() === 'unit'){
- return true;
- }else if(this.get_product_type() === 'weight'){
- return true;
- }else if(this.get_product_type() === 'price'){
- return this.get_product().get('list_price') === orderline.get_product().get('list_price');
- }else{
- console.error('point_of_sale/pos_models.js/Orderline.can_be_merged_with() : unknown product type:',this.get('product_type'));
+ }else if(this.price !== orderline.price){
return false;
+ }else{
+ return true;
}
},
merge: function(orderline){
@@ -882,6 +658,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
change: this.getChange(),
name : this.getName(),
client: client ? client.name : null ,
+ invoice_id: null, //TODO
cashier: cashier ? cashier.name : null,
date: {
year: date.getFullYear(),
@@ -895,7 +672,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
email: company.email,
website: company.website,
company_registry: company.company_registry,
- contact_address: null, //TODO
+ contact_address: company.contact_address,
vat: company.vat,
name: company.name,
phone: company.phone,
diff --git a/addons/point_of_sale/static/src/js/pos_screens.js b/addons/point_of_sale/static/src/js/screens.js
similarity index 84%
rename from addons/point_of_sale/static/src/js/pos_screens.js
rename to addons/point_of_sale/static/src/js/screens.js
index 0515eb9807b..738eadb08c5 100644
--- a/addons/point_of_sale/static/src/js/pos_screens.js
+++ b/addons/point_of_sale/static/src/js/screens.js
@@ -152,14 +152,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.pos_widget.screen_selector.show_popup('help');
},
- logout_button_action: function(){
- this.pos_widget.screen_selector.set_user_mode('client');
- },
-
- close_button_action: function(){
- this.pos_widget.try_close();
- },
-
barcode_product_screen: 'products', //if defined, this screen will be loaded when a product is scanned
barcode_product_error_popup: 'error', //if defined, this popup will be loaded when there's an error in the popup
@@ -167,14 +159,16 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
// it will add the product to the order and go to barcode_product_screen. Or show barcode_product_error_popup if
// there's an error.
barcode_product_action: function(ean){
- if(this.pos_widget.scan_product(ean)){
- this.pos.proxy.scan_item_success();
- if(this.barcode_product_screen){
- this.pos_widget.screen_selector.set_current_screen(this.barcode_product_screen);
+ var self = this;
+ if(self.pos.scan_product(ean)){
+ self.pos.proxy.scan_item_success(ean);
+ if(self.barcode_product_screen){
+ self.pos_widget.screen_selector.set_current_screen(self.barcode_product_screen);
}
}else{
- if(this.barcode_product_error_popup){
- this.pos_widget.screen_selector.show_popup(this.barcode_product_error_popup);
+ self.pos.proxy.scan_item_error_unrecognized(ean);
+ if(self.barcode_product_error_popup && self.pos_widget.screen_selector.get_user_mode() !== 'cashier'){
+ self.pos_widget.screen_selector.show_popup(self.barcode_product_error_popup);
}
}
},
@@ -194,6 +188,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
return true;
}
}
+ this.pos.proxy.scan_item_unrecognized(ean);
return false;
},
@@ -207,9 +202,11 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
if(users[i].ean13 === ean.ean){
this.pos.get('selectedOrder').set_client(users[i]);
this.pos_widget.username.refresh();
+ this.pos.proxy.scan_item_success(ean);
return true;
}
}
+ this.pos.proxy.scan_item_unrecognized(ean);
return false;
//TODO start the transaction
},
@@ -217,29 +214,78 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
// what happens when a discount barcode is scanned : the default behavior
// is to set the discount on the last order.
barcode_discount_action: function(ean){
+ this.pos.proxy.scan_item_success(ean);
var last_orderline = this.pos.get('selectedOrder').getLastOrderline();
if(last_orderline){
last_orderline.set_discount(ean.value)
}
},
+ // shows an action bar on the screen. The actionbar is automatically shown when you add a button
+ // with add_action_button()
+ show_action_bar: function(){
+ this.pos_widget.action_bar.show();
+ this.$element.css({'bottom':'105px'});
+ },
+
+ // hides the action bar. The actionbar is automatically hidden when it is empty
+ hide_action_bar: function(){
+ this.pos_widget.action_bar.hide();
+ this.$element.css({'bottom':'0px'});
+ },
+
+ // adds a new button to the action bar. The button definition takes three parameters, all optional :
+ // - label: the text below the button
+ // - icon: a small icon that will be shown
+ // - click: a callback that will be executed when the button is clicked.
+ // the method returns a reference to the button widget, and automatically show the actionbar.
+ add_action_button: function(button_def){
+ this.show_action_bar();
+ return this.pos_widget.action_bar.add_new_button(button_def);
+ },
+
// this method shows the screen and sets up all the widget related to this screen. Extend this method
// if you want to alter the behavior of the screen.
show: function(){
+ var self = this;
+
this.hidden = false;
if(this.$element){
this.$element.show();
}
+ if(this.pos_widget.action_bar.get_button_count() > 0){
+ this.show_action_bar();
+ }else{
+ this.hide_action_bar();
+ }
+
+ // we add the help button by default. we do this because the buttons are cleared on each refresh so that
+ // the button stay local to each screen
+ this.pos_widget.left_action_bar.add_new_button({
+ label: 'help',
+ icon: '/point_of_sale/static/src/img/icons/png48/help.png',
+ click: function(){ self.help_button_action(); },
+ });
+
var self = this;
var cashier_mode = this.pos_widget.screen_selector.get_user_mode() === 'cashier';
this.pos_widget.set_numpad_visible(this.show_numpad && cashier_mode);
this.pos_widget.set_leftpane_visible(this.show_leftpane);
+ this.pos_widget.set_left_action_bar_visible(this.show_leftpane && !cashier_mode);
this.pos_widget.set_cashier_controls_visible(cashier_mode);
- this.pos_widget.action_bar.set_element_visible('help-button', !cashier_mode, function(){ self.help_button_action(); });
- this.pos_widget.action_bar.set_element_visible('logout-button', cashier_mode && this.pos.use_selfcheckout, function(){ self.logout_button_action(); });
- this.pos_widget.action_bar.set_element_visible('close-button', cashier_mode, function(){ self.close_button_action(); });
+
+ if(cashier_mode && this.pos.iface_self_checkout){
+ this.pos_widget.client_button.show();
+ }else{
+ this.pos_widget.client_button.hide();
+ }
+ if(cashier_mode){
+ this.pos_widget.close_button.show();
+ }else{
+ this.pos_widget.close_button.hide();
+ }
this.pos_widget.username.set_user_mode(this.pos_widget.screen_selector.get_user_mode());
@@ -251,16 +297,14 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
});
},
-
// this method is called when the screen is closed to make place for a new screen. this is a good place
// to put your cleanup stuff as it is guaranteed that for each show() there is one and only one close()
close: function(){
if(this.pos.barcode_reader){
this.pos.barcode_reader.reset_action_callbacks();
}
- if(this.pos_widget.action_bar){
- this.pos_widget.action_bar.destroy_buttons();
- }
+ this.pos_widget.action_bar.destroy_buttons();
+ this.pos_widget.left_action_bar.destroy_buttons();
},
// this methods hides the screen. It's not a good place to put your cleanup stuff as it is called on the
@@ -349,6 +393,9 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
module.ScaleInviteScreenWidget = module.ScreenWidget.extend({
template:'ScaleInviteScreenWidget',
+ next_screen:'scale',
+ previous_screen:'products',
+
show: function(){
this._super();
var self = this;
@@ -359,21 +406,19 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
var weight = self.pos.proxy.weighting_read_kg();
if(weight > 0.001){
clearInterval(this.intervalID);
- self.pos_widget.screen_selector.set_current_screen('scale');
+ self.pos_widget.screen_selector.set_current_screen(self.next_screen);
}
},500);
- this.pos_widget.action_bar.add_new_button(
- {
+ this.add_action_button({
label: 'back',
icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
click: function(){
clearInterval(this.intervalID);
self.pos.proxy.weighting_end();
- self.pos_widget.screen_selector.set_current_screen('products');
+ self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
}
- }
- );
+ });
},
close: function(){
this._super();
@@ -383,25 +428,30 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
module.ScaleScreenWidget = module.ScreenWidget.extend({
template:'ScaleScreenWidget',
+
+ next_screen: 'products',
+ previous_screen: 'products',
+
show: function(){
this._super();
this.renderElement();
var self = this;
- this.pos_widget.action_bar.add_new_button({
+
+ this.add_action_button({
label: 'back',
icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
click: function(){
- self.pos_widget.screen_selector.set_current_screen('products');
+ self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
}
});
- this.validate_button = this.pos_widget.action_bar.add_new_button({
+ this.validate_button = this.add_action_button({
label: 'Validate',
icon: '/point_of_sale/static/src/img/icons/png48/validate.png',
click: function(){
self.order_product();
- self.pos_widget.screen_selector.set_current_screen('products');
+ self.pos_widget.screen_selector.set_current_screen(self.next_screen);
},
});
@@ -419,7 +469,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this._super();
this.$('.product-picture').click(function(){
self.order_product();
- self.pos_widget.screen_selector.set_current_screen('products');
+ self.pos_widget.screen_selector.set_current_screen(self.next_screen);
});
},
get_product: function(){
@@ -444,7 +494,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
},
get_product_image: function(){
var product = this.get_product();
- return product ? product.get('image_medium') : undefined;
+ return product ? product.get('image') : undefined;
},
get_product_weight: function(){
return this.weight || 0;
@@ -458,6 +508,10 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
module.ClientPaymentScreenWidget = module.ScreenWidget.extend({
template:'ClientPaymentScreenWidget',
+
+ next_screen: 'welcome',
+ previous_screen: 'products',
+
show: function(){
this._super();
var self = this;
@@ -486,7 +540,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
self.pos.push_order(currentOrder.exportAsJSON()).then(function() {
currentOrder.destroy();
self.pos.proxy.transaction_end();
- self.pos_widget.screen_selector.set_current_screen('welcome');
+ self.pos_widget.screen_selector.set_current_screen(self.next_screen);
});
}else if(payment === 'payment_rejected'){
clearInterval(this.intervalID);
@@ -494,17 +548,15 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
}
},500);
- this.pos_widget.action_bar.add_new_button(
- {
+ this.add_action_button({
label: 'back',
icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
- click: function(){ //TODO Go to ask for weighting screen
+ click: function(){
clearInterval(this.intervalID);
self.pos.proxy.payment_canceled();
- self.pos_widget.screen_selector.set_current_screen('products');
+ self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
}
- }
- );
+ });
},
close: function(){
this._super();
@@ -515,23 +567,32 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
module.WelcomeScreenWidget = module.ScreenWidget.extend({
template:'WelcomeScreenWidget',
+ next_screen: 'products',
+
show_numpad: false,
show_leftpane: false,
barcode_client_action: function(ean){
this._super(ean);
- this.pos_widget.screen_selector.set_current_screen('products');
+ this.pos_widget.screen_selector.set_current_screen(this.next_screen);
},
show: function(){
this._super();
var self = this;
+ $('.goodbye-message').css({opacity:1}).show();
+ setTimeout(function(){
+ $('.goodbye-message').animate({opacity:0},500,'swing',function(){$('.goodbye-message').hide();});
+ },3000);
},
});
module.ProductScreenWidget = module.ScreenWidget.extend({
template:'ProductScreenWidget',
+ scale_screen: 'scale_invite',
+ client_next_screen: 'client_payment',
+
show_numpad: true,
show_leftpane: true,
@@ -542,8 +603,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.product_list_widget = new module.ProductListWidget(this,{
click_product_action: function(product){
- if(product.get('to_weight') && self.pos.use_scale){
- self.pos_widget.screen_selector.set_current_screen('scale_invite', {product: product});
+ if(product.get('to_weight') && self.pos.iface_electronic_scale){
+ self.pos_widget.screen_selector.set_current_screen(self.scale_screen, {product: product});
}else{
self.pos.get('selectedOrder').addProduct(product);
}
@@ -559,16 +620,16 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.product_categories_widget.reset_category();
this.pos_widget.order_widget.set_numpad_state(this.pos_widget.numpad.state);
- if(this.pos.use_virtual_keyboard){
+ if(this.pos.iface_vkeyboard){
this.pos_widget.onscreen_keyboard.connect();
}
if(this.pos_widget.screen_selector.current_mode === 'client'){
- this.pos_widget.action_bar.add_new_button({
+ this.add_action_button({
label: 'pay',
icon: '/point_of_sale/static/src/img/icons/png48/go-next.png',
click: function(){
- self.pos_widget.screen_selector.set_current_screen('client_payment');
+ self.pos_widget.screen_selector.set_current_screen(self.client_next_screen);
}
});
}
@@ -604,17 +665,19 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this._super();
var self = this;
- this.pos_widget.action_bar.add_new_button({
+ this.add_action_button({
label: 'Print',
icon: '/point_of_sale/static/src/img/icons/png48/printer.png',
click: function(){ self.print(); },
});
- this.pos_widget.action_bar.add_new_button({
+ this.add_action_button({
label: 'Next Order',
icon: '/point_of_sale/static/src/img/icons/png48/go-next.png',
click: function() { self.finishOrder(); },
});
+
+ window.print();
},
print: function() {
window.print();
@@ -643,6 +706,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
module.PaymentScreenWidget = module.ScreenWidget.extend({
template: 'PaymentScreenWidget',
+ back_screen: 'products',
+ next_screen: 'receipt',
init: function(parent, options) {
this._super(parent,options);
this.model = options.model;
@@ -654,21 +719,21 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this._super();
var self = this;
- if(this.pos.use_cashbox){
+ if(this.pos.iface_cashdrawer){
this.pos.proxy.open_cashbox();
}
this.set_numpad_state(this.pos_widget.numpad.state);
- this.back_button = this.pos_widget.action_bar.add_new_button({
+ this.back_button = this.add_action_button({
label: 'Back',
icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
click: function(){
- self.pos_widget.screen_selector.set_current_screen('products');
+ self.pos_widget.screen_selector.set_current_screen(self.back_screen);
},
});
- this.validate_button = this.pos_widget.action_bar.add_new_button({
+ this.validate_button = this.add_action_button({
label: 'Validate',
icon: '/point_of_sale/static/src/img/icons/png48/validate.png',
click: function(){
@@ -682,24 +747,18 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.pos_widget.payment_screen.set_numpad_state(null);
},
back: function() {
- this.pos_widget.screen_selector.set_current_screen('products');
+ this.pos_widget.screen_selector.set_current_screen(self.back_screen);
},
validateCurrentOrder: function() {
- var self = this;
var currentOrder = this.pos.get('selectedOrder');
- this.validate_button.$element.attr('disabled','disabled'); //FIXME is the css actually using this attr ?
-
this.pos.push_order(currentOrder.exportAsJSON())
- .then(function() {
- self.validate_button.$element.removeAttr('disabled');
- if(self.pos.use_proxy_printer){
- self.pos.proxy.print_receipt(currentOrder.export_for_printing());
- self.pos.get('selectedOrder').destroy(); //finish order and go back to scan screen
- }else{
- self.pos_widget.screen_selector.set_current_screen('receipt');
- }
- });
+ if(this.pos.iface_print_via_proxy){
+ this.pos.proxy.print_receipt(currentOrder.export_for_printing());
+ this.pos.get('selectedOrder').destroy(); //finish order and go back to scan screen
+ }else{
+ this.pos_widget.screen_selector.set_current_screen(this.next_screen);
+ }
},
bindPaymentLineEvents: function() {
this.currentPaymentLines = (this.pos.get('selectedOrder')).get('paymentLines');
diff --git a/addons/point_of_sale/static/src/js/pos_basewidget.js b/addons/point_of_sale/static/src/js/widget_base.js
similarity index 100%
rename from addons/point_of_sale/static/src/js/pos_basewidget.js
rename to addons/point_of_sale/static/src/js/widget_base.js
diff --git a/addons/point_of_sale/static/src/js/pos_keyboard_widget.js b/addons/point_of_sale/static/src/js/widget_keyboard.js
similarity index 100%
rename from addons/point_of_sale/static/src/js/pos_keyboard_widget.js
rename to addons/point_of_sale/static/src/js/widget_keyboard.js
diff --git a/addons/point_of_sale/static/src/js/pos_scrollbar_widget.js b/addons/point_of_sale/static/src/js/widget_scrollbar.js
similarity index 99%
rename from addons/point_of_sale/static/src/js/pos_scrollbar_widget.js
rename to addons/point_of_sale/static/src/js/widget_scrollbar.js
index d4f6903c435..7c765028231 100644
--- a/addons/point_of_sale/static/src/js/pos_scrollbar_widget.js
+++ b/addons/point_of_sale/static/src/js/widget_scrollbar.js
@@ -65,6 +65,7 @@ function openerp_pos_scrollbar(instance, module){ //module is instance.point_of_
this.auto_hide(false);
this.$element.bind('mousewheel',function(event,delta){
self.scroll(delta*self.wheel_step);
+ return false;
});
this.$element.bind('click',function(event){
var vpos = event.pageY - self.$element.offset().top;
diff --git a/addons/point_of_sale/static/src/js/pos_widgets.js b/addons/point_of_sale/static/src/js/widgets.js
similarity index 75%
rename from addons/point_of_sale/static/src/js/pos_widgets.js
rename to addons/point_of_sale/static/src/js/widgets.js
index 9a27be29381..c3bd73ca35a 100644
--- a/addons/point_of_sale/static/src/js/pos_widgets.js
+++ b/addons/point_of_sale/static/src/js/widgets.js
@@ -1,6 +1,57 @@
function openerp_pos_widgets(instance, module){ //module is instance.point_of_sale
var QWeb = instance.web.qweb;
+ // The ImageCache is used to hide the latency of the application cache on-disk access in chrome
+ // that causes annoying flickering on product pictures. Why the hell a simple access to
+ // the application cache involves such latency is beyond me, hopefully one day this can be
+ // removed.
+ module.ImageCache = instance.web.Class.extend({
+ init: function(options){
+ options = options || {};
+ this.max_size = options.max_size || 500;
+
+ this.cache = {};
+ this.access_time = {};
+ this.size = 0;
+ },
+ get_image_uncached: function(url){
+ var img = new Image();
+ img.src = url;
+ return img;
+ },
+ // returns a DOM Image object from an url, and cache the last 500 (by default) results
+ get_image: function(url){
+ var cached = this.cache[url];
+ if(cached){
+ this.access_time[url] = (new Date()).getTime();
+ return cached;
+ }else{
+ var img = new Image();
+ img.src = url;
+ while(this.size >= this.max_size){
+ var oldestUrl = null;
+ var oldestTime = (new Date()).getTime();
+ for(var url in this.cache){
+ var time = this.access_time[url];
+ if(time <= oldestTime){
+ oldestTime = time;
+ oldestUrl = url;
+ }
+ }
+ if(oldestUrl){
+ delete this.cache[oldestUrl];
+ delete this.access_time[oldestUrl];
+ }
+ this.size--;
+ }
+ this.cache[url] = img;
+ this.access_time[url] = (new Date()).getTime();
+ this.size++;
+ return img;
+ }
+ },
+ });
+
module.NumpadWidget = module.PosBaseWidget.extend({
template:'NumpadWidget',
init: function(parent, options) {
@@ -112,7 +163,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
template:'OrderWidget',
init: function(parent, options) {
this._super(parent,options);
- this.compact = false;
+ this.display_mode = options.display_mode || 'numpad'; // 'maximized' | 'actionbar' | 'numpad'
this.set_numpad_state(options.numpadState);
this.pos.bind('change:selectedOrder', this.change_selected_order, this);
this.bind_orderline_events();
@@ -165,8 +216,12 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
var self = this;
this._super();
- if(!this.compact){
+ if(this.display_mode === 'maximized'){
$('.point-of-sale .order-container').css({'bottom':'0px'});
+ }else if(this.display_mode === 'actionbar'){
+ $('.point-of-sale .order-container').css({'bottom':'105px'});
+ }else if(this.display_mode !== 'numpad'){
+ console.error('ERROR: OrderWidget renderElement(): wrong display_mode:',this.display_mode);
}
var $content = this.$('.orderlines');
@@ -211,9 +266,9 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
var total = order ? order.getTotal() : 0;
this.$('.summary .value.total').html(this.format_currency(total));
},
- set_compact: function(compact){
- if(this.compact !== compact){
- this.compact = compact;
+ set_display_mode: function(mode){
+ if(this.display_mode !== mode){
+ this.display_mode = mode;
this.renderElement();
}
},
@@ -225,20 +280,16 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this._super(parent,options);
this.model = options.model;
this.model.attributes.weight = options.weight;
- this.next_screen = options.next_screen;
- this.click_product_action = options.click_product_action;
+ this.next_screen = options.next_screen; //when a product is clicked, this screen is set
+ this.click_product_action = options.click_product_action;
},
- add_to_order: function(event) {
- /* Preserve the category URL */
- event.preventDefault();
- return (this.pos.get('selectedOrder')).addProduct(this.model);
- },
- set_weight: function(weight){
- this.model.attributes.weight = weight;
- this.renderElement();
+ // returns the url of the product thumbnail
+ get_image_url: function() {
+ return '/web/binary/image?session_id='+instance.session.session_id+'&model=product.product&field=image&id='+this.model.get('id');
},
renderElement: function() {
this._super();
+ this.$('img').replaceWith(this.pos_widget.image_cache.get_image(this.get_image_url()));
var self = this;
$("a", this.$element).click(function(e){
if(self.click_product_action){
@@ -313,6 +364,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
module.ActionButtonWidget = instance.web.Widget.extend({
template:'ActionButtonWidget',
+ icon_template:'ActionButtonWidgetWithIcon',
init: function(parent, options){
this._super(parent, options);
this.label = options.label || 'button';
@@ -320,7 +372,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this.click_action = options.click;
if(options.icon){
this.icon = options.icon;
- this.template = 'ActionButtonWidgetWithIcon';
+ this.template = this.icon_template;
}
},
renderElement: function(){
@@ -359,21 +411,25 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this.button_list = [];
return this;
},
+ get_button_count: function(){
+ return this.button_list.length;
+ },
add_new_button: function(button_options){
- if(arguments.length == 1){
- var button = new module.ActionButtonWidget(this,button_options);
- this.button_list.push(button);
- button.appendTo($('.pos-actionbar-button-list'));
- return button;
- }else{
- for(var i = 0; i < arguments.length; i++){
- this.add_new_button(arguments[i]);
- }
- }
- return undefined;
+ var button = new module.ActionButtonWidget(this,button_options);
+ this.button_list.push(button);
+ button.appendTo(this.$('.pos-actionbar-button-list'));
+ return button;
+ },
+ show:function(){
+ this.$element.show();
+ },
+ hide:function(){
+ this.$element.hide();
},
});
+ module.CategoryButton = module.PosBaseWidget.extend({
+ });
module.ProductCategoriesWidget = module.PosBaseWidget.extend({
template: 'ProductCategoriesWidget',
init: function(parent, options){
@@ -389,41 +445,53 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
// changes the category. if undefined, sets to root category
set_category : function(category){
+ var db = this.pos.db;
if(!category){
- this.category = this.pos.root_category;
+ this.category = db.get_category_by_id(db.root_category_id);
}else{
this.category = category;
}
-
this.breadcrumb = [];
- for(var i = 1; i < this.category.ancestors.length; i++){
- this.breadcrumb.push(this.category.ancestors[i]);
+ var ancestors_ids = db.get_category_ancestors_ids(this.category.id);
+ for(var i = 1; i < ancestors_ids.length; i++){
+ this.breadcrumb.push(db.get_category_by_id(ancestors_ids[i]));
}
- if(this.category !== this.pos.root_category){
+ if(this.category.id !== db.root_category_id){
this.breadcrumb.push(this.category);
}
- if(this.product_type === 'weightable'){
- this.subcategories = [];
- for(var i = 0; i < this.category.childrens.length; i++){
- if(this.category.childrens[i].weightable_product_list.length > 0){
- this.subcategories.push( this.category.childrens[i]);
- }
- }
- }else{
- this.subcategories = this.category.childrens || [];
- }
+ this.subcategories = db.get_category_by_id(db.get_category_childs_ids(this.category.id));
+ },
+
+ get_image_url: function(category){
+ return '/web/binary/image?session_id='+instance.session.session_id+'&model=pos.category&field=image&id='+category.id;
},
renderElement: function(){
var self = this;
this._super();
- _.each(this.subcategories, function(category){
- var button = QWeb.render('CategoryButton',{category:category});
- button = _.str.trim(button);
- $(button).appendTo(this.$('.category-list')).click(function(event){
+ var hasimages = false; //if none of the subcategories have images, we don't display buttons with icons
+ _.each(this.subcategories, function(category){
+ if(category.image){
+ hasimages = true;
+ }
+ });
+
+ _.each(this.subcategories, function(category){
+ if(hasimages){
+ var button = QWeb.render('CategoryButton',{category:category});
+ var button = _.str.trim(button);
+ var button = $(button);
+ button.find('img').replaceWith(self.pos_widget.image_cache.get_image(self.get_image_url(category)));
+ }else{
+ var button = QWeb.render('CategorySimpleButton',{category:category});
+ button = _.str.trim(button); // we remove whitespace between buttons to fix spacing
+ var button = $(button);
+ }
+
+ button.appendTo(this.$('.category-list')).click(function(event){
var id = category.id;
- var cat = self.pos.categories_by_id[id];
+ var cat = self.pos.db.get_category_by_id(id);
self.set_category(cat);
self.renderElement();
self.search_and_categories(cat);
@@ -432,7 +500,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
// breadcrumb click actions
this.$(".oe-pos-categories-list a").click(function(event){
var id = $(event.target).data("category-id");
- var category = self.pos.categories_by_id[id];
+ var category = self.pos.db.get_category_by_id(id);
self.set_category(category);
self.renderElement();
self.search_and_categories(category);
@@ -455,47 +523,30 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
// filters the products, and sets up the search callbacks
search_and_categories: function(category){
var self = this;
-
- var all_products = this.pos.get('product_list');
- var all_packages = this.pos.get('product.packaging');
// find all products belonging to the current category
- var products = [];
- if(this.product_type === 'weightable'){
- products = all_products.filter( function(product){
- return self.category.weightable_product_set[product.id];
- });
- }else{
- products = all_products.filter( function(product){
- return self.category.product_set[product.id];
- });
- }
+ var products = this.pos.db.get_product_by_category(this.category.id);
+ self.pos.get('products').reset(products);
- // product lists watch for reset events on 'products' to re-render.
- // FIXME that means all productlist widget re-render... even the hidden ones !
- this.pos.get('products').reset(products);
-
- // find all the products whose name match the query in the searchbox
+ // filter the products according to the search string
this.$('.searchbox input').keyup(function(){
- var results, search_str;
- search_str = $(this).val().toLowerCase();
- if(search_str){
- results = products.filter( function(p){
- return p.name.toLowerCase().indexOf(search_str) != -1 ||
- (p.ean13 && p.ean13.indexOf(search_str) != -1);
- });
- self.$element.find('.search-clear').fadeIn();
+ query = $(this).val().toLowerCase();
+ if(query){
+ var products = self.pos.db.search_product_in_category(self.category.id, query);
+ self.pos.get('products').reset(products);
+ self.$('.search-clear').fadeIn();
}else{
- results = products;
- self.$element.find('.search-clear').fadeOut();
+ var products = self.pos.db.get_product_by_category(self.category.id);
+ self.pos.get('products').reset(products);
+ self.$('.search-clear').fadeOut();
}
- self.pos.get('products').reset(results);
- });
- this.$('.searchbox input').click(function(){
});
+ this.$('.searchbox input').click(function(){}); //Why ???
+
//reset the search when clicking on reset
this.$('.search-clear').click(function(){
+ var products = self.pos.db.get_product_by_category(self.category.id);
self.pos.get('products').reset(products);
self.$('.searchbox input').val('').focus();
self.$('.search-clear').fadeOut();
@@ -519,11 +570,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
self.renderElement();
});
},
- set_weight: function(weight){
- for(var i = 0; i < this.product_list.length; i++){
- this.product_list[i].set_weight(weight);
- }
- },
renderElement: function() {
var self = this;
this._super();
@@ -586,14 +632,35 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
},
});
+ module.HeaderButtonWidget = module.PosBaseWidget.extend({
+ template: 'HeaderButtonWidget',
+ init: function(parent, options){
+ options = options || {};
+ this._super(parent, options);
+ this.action = options.action;
+ this.label = options.label;
+ },
+ renderElement: function(){
+ var self = this;
+ this._super();
+ if(this.action){
+ this.$element.click(function(){ self.action(); });
+ }
+ },
+ show: function(){ this.$element.show(); },
+ hide: function(){ this.$element.hide(); },
+
+ });
+
+
// ---------- Main Point of Sale Widget ----------
// this is used to notify the user that data is being synchronized on the network
module.SynchNotificationWidget = module.PosBaseWidget.extend({
template: "SynchNotificationWidget",
- init: function(parent,options) {
+ init: function(parent, options){
options = options || {};
- this._super(parent,options);
+ this._super(parent, options);
},
renderElement: function() {
var self = this;
@@ -632,9 +699,19 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this.pos_widget = this; //So that pos_widget's childs have pos_widget set automatically
this.numpad_visible = true;
+ this.left_action_bar_visible = true;
this.leftpane_visible = true;
this.leftpane_width = '440px';
this.cashier_controls_visible = true;
+ this.image_cache = new module.ImageCache(); // for faster products image display
+
+ /*
+ //Epileptic mode
+ setInterval(function(){
+ $('body').css({'-webkit-filter':'hue-rotate('+Math.random()*360+'deg)' });
+ },100);
+ */
+
},
start: function() {
@@ -643,7 +720,9 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
self.build_currency_template();
self.renderElement();
- self.$('.neworder-button').click(_.bind(self.create_new_order, self));
+ self.$('.neworder-button').click(function(){
+ self.pos.add_new_order();
+ });
//when a new order is created, add an order button widget
self.pos.get('orders').bind('add', function(new_order){
@@ -659,6 +738,10 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
self.build_widgets();
+ self.screen_selector.set_default_screen();
+
+ self.pos.barcode_reader.connect();
+
instance.webclient.set_content_full_screen(true);
if (!self.pos.get('pos_session')) {
@@ -667,9 +750,14 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
self.screen_selector.show_popup('error', 'Sorry, we could not find any PoS Configuration for this session');
}
- self.$('.loader').animate({opacity:0},3000,'swing',function(){$('.loader').hide();});
+ self.$('.loader').animate({opacity:0},1500,'swing',function(){self.$('.loader').hide();});
self.$('.loader img').hide();
+ if(jQuery.deparam(jQuery.param.querystring()).debug !== undefined){
+ window.pos = self.pos;
+ window.pos_widget = self.pos_widget;
+ }
+
},function(){ // error when loading models data from the backend
self.$('.loader img').hide();
return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_session_opening']], ['res_id'])
@@ -682,8 +770,11 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
}, self));
});
},
-
+
+ // This method instantiates all the screens, widgets, etc. If you want to add new screens change the
+ // startup screen, etc, override this method.
build_widgets: function() {
+ var self = this;
// -------- Screens ---------
@@ -725,13 +816,16 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
// -------- Misc ---------
this.notification = new module.SynchNotificationWidget(this,{});
- this.notification.replace(this.$('.placeholder-SynchNotificationWidget'));
+ this.notification.appendTo(this.$('#rightheader'));
this.username = new module.UsernameWidget(this,{});
this.username.replace(this.$('.placeholder-UsernameWidget'));
this.action_bar = new module.ActionBarWidget(this);
- this.action_bar.appendTo($(".point-of-sale #content"));
+ this.action_bar.appendTo($(".point-of-sale #rightpane"));
+
+ this.left_action_bar = new module.ActionBarWidget(this);
+ this.left_action_bar.appendTo($(".point-of-sale #leftpane"));
this.paypad = new module.PaypadWidget(this, {});
this.paypad.replace($('#placeholder-PaypadWidget'));
@@ -746,6 +840,19 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
'keyboard_model': 'simple'
});
this.onscreen_keyboard.appendTo($(".point-of-sale #content"));
+
+ this.close_button = new module.HeaderButtonWidget(this,{
+ label:'Close',
+ action: function(){ self.try_close(); },
+ });
+ this.close_button.appendTo(this.$('#rightheader'));
+
+ this.client_button = new module.HeaderButtonWidget(this,{
+ label:'Self-Checkout',
+ action: function(){ self.screen_selector.set_user_mode('client'); },
+ });
+ this.client_button.appendTo(this.$('#rightheader'));
+
// -------- Screen Selector ---------
@@ -768,59 +875,11 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
},
default_client_screen: 'welcome',
default_cashier_screen: 'products',
- default_mode: this.pos.use_selfcheckout ? 'client' : 'cashier',
+ default_mode: this.pos.iface_self_checkout ? 'client' : 'cashier',
});
- this.screen_selector.set_default_screen();
-
- this.pos.barcode_reader.connect();
},
- //FIXME this method is probably not at the right place ...
- scan_product: function(parsed_ean){
- var selectedOrder = this.pos.get('selectedOrder');
- var scannedProductModel = this.get_product_by_ean(parsed_ean);
- if (!scannedProductModel){
- return false;
- } else {
- if(parsed_ean.type === 'price'){
- selectedOrder.addProduct(new module.Product(scannedProductModel), { price:parsed_ean.value});
- }else if(parsed_ean.type === 'weight'){
- selectedOrder.addProduct(new module.Product(scannedProductModel), { quantity:parsed_ean.value, merge:false});
- }else{
- selectedOrder.addProduct(new module.Product(scannedProductModel));
- }
- return true;
- }
- },
-
- get_product_by_ean: function(parsed_ean) {
- var allProducts = this.pos.get('product_list');
- var allPackages = this.pos.get('product.packaging');
- var scannedProductModel = undefined;
-
- if (parsed_ean.type === 'price' || parsed_ean.type === 'weight') {
- var itemCode = parsed_ean.id;
- var scannedPackaging = _.detect(allPackages, function(pack) {
- return pack.ean && pack.ean.substring(0,7) === itemCode;
- });
- if (scannedPackaging) {
- scannedProductModel = _.detect(allProducts, function(pc) { return pc.id === scannedPackaging.product_id[0];});
- }else{
- scannedProductModel = _.detect(allProducts, function(pc) { return pc.ean13 && (pc.ean13.substring(0,7) === parsed_ean.id);});
- }
- } else if(parsed_ean.type === 'unit'){
- scannedProductModel = _.detect(allProducts, function(pc) { return pc.ean13 === parsed_ean.ean;}); //TODO DOES NOT SCALE
- }
- return scannedProductModel;
- },
- // creates a new order, and add it to the list of orders.
- create_new_order: function() {
- var new_order;
- new_order = new module.Order({ pos: this.pos });
- this.pos.get('orders').add(new_order);
- this.pos.set({ selectedOrder: new_order });
- },
changed_pending_operations: function () {
var self = this;
this.synch_notification.on_change_nbr_pending(self.pos.get('nbr_pending_operations').length);
@@ -830,16 +889,35 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
if(visible !== this.numpad_visible){
this.numpad_visible = visible;
if(visible){
+ this.set_left_action_bar_visible(false);
this.numpad.show();
this.paypad.show();
- this.order_widget.set_compact(true);
+ this.order_widget.set_display_mode('numpad');
}else{
this.numpad.hide();
this.paypad.hide();
- this.order_widget.set_compact(false);
+ if(this.order_widget.display_mode === 'numpad'){
+ this.order_widget.set_display_mode('maximized');
+ }
}
}
},
+ set_left_action_bar_visible: function(visible){
+ if(visible !== this.left_action_bar_visible){
+ this.left_action_bar_visible = visible;
+ if(visible){
+ this.set_numpad_visible(false);
+ this.left_action_bar.show();
+ this.order_widget.set_display_mode('actionbar');
+ }else{
+ this.left_action_bar.hide();
+ if(this.order_widget.display_mode === 'actionbar'){
+ this.order_widget.set_display_mode('maximized');
+ }
+ }
+ }
+ },
+
//shows or hide the leftpane (contains the list of orderlines, the numpad, the paypad, etc.)
set_leftpane_visible: function(visible){
if(visible !== this.leftpane_visible){
@@ -874,12 +952,14 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
});
},
close: function() {
+ var self = this;
this.pos.barcode_reader.disconnect();
return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_client_pos_menu']], ['res_id']).pipe(
_.bind(function(res) {
return this.rpc('/web/action/load', {'action_id': res[0]['res_id']}).pipe(_.bind(function(result) {
var action = result.result;
action.context = _.extend(action.context || {}, {'cancel_action': {type: 'ir.actions.client', tag: 'reload'}});
+ //self.destroy();
this.do_action(action);
}, this));
}, this));
diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml
index 9bdac93d990..81f929d50e2 100644
--- a/addons/point_of_sale/static/src/xml/pos.xml
+++ b/addons/point_of_sale/static/src/xml/pos.xml
@@ -15,7 +15,7 @@
-
+
@@ -44,6 +44,12 @@
+
+
+
+
+
+
There are pending operations that could not be saved into the database, are you sure you want to exit?