瀏覽代碼

feat(helpdesk_extras): Corregir traducciones en formularios dinámicos del sitio web

- Agregar contexto de idioma del sitio web al generar formularios dinámicamente
- Corregir traducción de labels de campos usando get_description() con contexto de idioma
- Corregir traducción de opciones de selection usando _description_selection()
- Corregir traducción de campos many2one usando contexto de idioma en search_read
- Corregir traducción de '-- Select --' agregando comentario odoo-python en .po
- Actualizar traducciones de business_impact en ir.model.fields.selection
- Crear migración 18.0.1.0.10 para cargar traducciones de selection automáticamente
- Actualizar versión del módulo a 18.0.1.0.10
odoo 2 月之前
父節點
當前提交
0183c79fd8

+ 1 - 1
helpdesk_extras/__manifest__.py

@@ -1,6 +1,6 @@
 {
     "name": "Helpdesk Extras",
-    "version": "18.0.1.0.7",
+    "version": "18.0.1.0.10",
     "category": "Services/Helpdesk",
     "summary": "Funcionalidades extras para Helpdesk - Compartir equipos y widget de horas",
     "description": """

+ 4 - 4
helpdesk_extras/data/helpdesk_affected_module_data.xml

@@ -10,9 +10,9 @@
         </record>
         <record id="helpdesk_extras.module_account_accountant" model="helpdesk.affected.module">
             <field name="code">account_accountant</field>
-            <field name="name">Invoicing (Enterprise)</field>
+            <field name="name">Invoicing</field>
             <field name="is_main_application">false</field>
-            <field name="description">Invoices, Payments, Follow-ups &amp; Bank synchronization (Enterprise)</field>
+            <field name="description">Invoices, Payments, Follow-ups &amp; Bank synchronization</field>
             <field name="active">True</field>
         </record>
         <record id="helpdesk_extras.module_accountant" model="helpdesk.affected.module">
@@ -46,7 +46,7 @@
         </record>
         <record id="helpdesk_extras.module_analytic_enterprise" model="helpdesk.affected.module">
             <field name="code">analytic_enterprise</field>
-            <field name="name">Analytic Accounting Enterprise</field>
+            <field name="name">Analytic Accounting</field>
             <field name="is_main_application">true</field>
             <field name="active">True</field>
         </record>
@@ -606,7 +606,7 @@
         </record>
         <record id="helpdesk_extras.module_web_enterprise" model="helpdesk.affected.module">
             <field name="code">web_enterprise</field>
-            <field name="name">Web Enterprise</field>
+            <field name="name">Web</field>
             <field name="is_main_application">false</field>
             <field name="active">True</field>
         </record>

+ 659 - 0
helpdesk_extras/i18n/es_MX.po

@@ -477,3 +477,662 @@ msgstr "Alto"
 #: selection:helpdesk.ticket,business_impact:2
 msgid "Normal"
 msgstr "Normal"
+
+#. module: helpdesk_extras
+#: model:ir.model.fields,field_description:helpdesk_extras.field_helpdesk_ticket__request_type_id
+msgid "Request Type"
+msgstr "Tipo de Solicitud"
+
+#. module: helpdesk_extras
+#: model:ir.model.fields,field_description:helpdesk_extras.field_helpdesk_ticket__request_type_code
+msgid "Request Type Code"
+msgstr "Código de Tipo de Solicitud"
+
+#. module: helpdesk_extras
+#: model:ir.model.fields,field_description:helpdesk_extras.field_helpdesk_ticket__affected_module_id
+msgid "Affected Module"
+msgstr "Módulo Afectado"
+
+#. module: helpdesk_extras
+#: model:ir.model.fields,field_description:helpdesk_extras.field_helpdesk_ticket__affected_user_email
+msgid "Affected User Email"
+msgstr "Email del Usuario Afectado"
+
+#. module: helpdesk_extras
+#: model:ir.model.fields,field_description:helpdesk_extras.field_helpdesk_ticket__business_impact
+msgid "Business Impact"
+msgstr "Impacto de Negocio"
+
+#. module: helpdesk_extras
+#: model:ir.model.fields,field_description:helpdesk_extras.field_helpdesk_ticket__reproduce_steps
+msgid "Steps to Reproduce"
+msgstr "Pasos para Reproducir"
+
+#. module: helpdesk_extras
+#: model:ir.model.fields,field_description:helpdesk_extras.field_helpdesk_ticket__business_goal
+msgid "Business Goal"
+msgstr "Objetivo de Negocio"
+
+#. module: helpdesk_extras
+#: model:ir.model.fields,field_description:helpdesk_extras.field_helpdesk_ticket__attachment_ids
+msgid "Attachments"
+msgstr "Adjuntos"
+
+#. module: helpdesk_extras
+#: model:ir.model.fields,field_description:helpdesk_extras.field_helpdesk_ticket__estimated_hours
+msgid "Estimated Hours"
+msgstr "Horas Estimadas"
+
+#. module: helpdesk_extras
+#: model:ir.model.fields,field_description:helpdesk_extras.field_helpdesk_ticket__approval_status
+msgid "Approval Status"
+msgstr "Estado de Aprobación"
+
+#. module: helpdesk_extras
+#: selection:helpdesk.ticket,approval_status:helpdesk_extras
+msgid "N/A"
+msgstr "N/A"
+
+#. module: helpdesk_extras
+#: selection:helpdesk.ticket,approval_status:helpdesk_extras
+msgid "Waiting for Approval"
+msgstr "Esperando Aprobación"
+
+#. module: helpdesk_extras
+#: selection:helpdesk.ticket,approval_status:helpdesk_extras
+msgid "Approved"
+msgstr "Aprobado"
+
+#. module: helpdesk_extras
+#: selection:helpdesk.ticket,approval_status:helpdesk_extras
+msgid "Rejected"
+msgstr "Rechazado"
+
+#. module: helpdesk_extras
+#: model:ir.model.fields,field_description:helpdesk_extras.field_helpdesk_ticket__client_authorization
+msgid "Client Authorization"
+msgstr "Autorización del Cliente"
+
+#. module: helpdesk_extras
+#: model:ir.model.fields,field_description:helpdesk_extras.field_helpdesk_ticket__has_template
+msgid "Has Template"
+msgstr "Tiene Plantilla"
+
+#. module: helpdesk_extras
+#. odoo-python
+#: code:addons/helpdesk_extras/models/helpdesk_team.py:896
+#: code:addons/helpdesk_extras/models/helpdesk_team.py:1083
+msgid "-- Select --"
+msgstr "-- Seleccionar --"
+
+#. module: helpdesk_extras
+#: model_terms:ir.ui.view,arch_db:helpdesk_extras.helpdesk_ticket_view_form_inherit_helpdesk_extras
+msgid "Request Information"
+msgstr "Información de Solicitud"
+
+#. module: helpdesk_extras
+#: model_terms:ir.ui.view,arch_db:helpdesk_extras.helpdesk_ticket_view_form_inherit_helpdesk_extras
+msgid "Details"
+msgstr "Detalles"
+
+#. module: helpdesk_extras
+#: model_terms:ir.ui.view,arch_db:helpdesk_extras.helpdesk_ticket_view_form_inherit_helpdesk_extras
+msgid "Approval & Billing"
+msgstr "Aprobación y Facturación"
+
+#. module: helpdesk_extras
+#: model:ir.model.fields,field_description:helpdesk_extras.field_helpdesk_affected_module__name
+msgid "Name"
+msgstr "Nombre"
+
+#. module: helpdesk_extras
+#: model:ir.model.fields,field_description:helpdesk_extras.field_helpdesk_affected_module__code
+msgid "Code"
+msgstr "Código"
+
+#. module: helpdesk_extras
+#: model:ir.model.fields,field_description:helpdesk_extras.field_helpdesk_affected_module__active
+msgid "Active"
+msgstr "Activo"
+
+#. module: helpdesk_extras
+#: model:ir.model.fields,field_description:helpdesk_extras.field_helpdesk_affected_module__is_main_application
+msgid "Main Application"
+msgstr "Aplicación Principal"
+
+#. module: helpdesk_extras
+#: model:ir.model.fields,field_description:helpdesk_extras.field_helpdesk_affected_module__description
+msgid "Description"
+msgstr "Descripción"
+
+#. module: helpdesk_extras
+#: model_terms:ir.ui.view,arch_db:helpdesk_extras.helpdesk_affected_module_view_search
+msgid "Active"
+msgstr "Activo"
+
+#. module: helpdesk_extras
+#: model_terms:ir.ui.view,arch_db:helpdesk_extras.helpdesk_affected_module_view_search
+msgid "Inactive"
+msgstr "Inactivo"
+
+#. module: helpdesk_extras
+#: model_terms:ir.ui.view,arch_db:helpdesk_extras.helpdesk_affected_module_view_search
+msgid "Main Applications"
+msgstr "Aplicaciones Principales"
+
+#. module: helpdesk_extras
+#: model:ir.actions.act_window,name:helpdesk_extras.helpdesk_affected_module_action
+msgid "Affected Modules"
+msgstr "Módulos Afectados"
+
+#. module: helpdesk_extras
+#: model:ir.ui.menu,name:helpdesk_extras.helpdesk_affected_module_menu
+msgid "Affected Modules"
+msgstr "Módulos Afectados"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_accountant
+msgid "Accounting"
+msgstr "Contabilidad"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_ai
+msgid "AI Base"
+msgstr "Base de IA"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_ai_app
+msgid "AI"
+msgstr "IA"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_analytic
+msgid "Analytic Accounting"
+msgstr "Contabilidad Analítica"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_analytic_enterprise
+msgid "Analytic Accounting Enterprise"
+msgstr "Contabilidad Analítica"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_appointment
+msgid "Appointments"
+msgstr "Citas"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_approvals
+msgid "Approvals"
+msgstr "Aprobaciones"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_barcodes
+msgid "Barcode"
+msgstr "Código de Barras"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_base
+msgid "Base"
+msgstr "Base"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_calendar
+msgid "Calendar"
+msgstr "Calendario"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_certificate
+msgid "Certificate"
+msgstr "Certificado"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_cloud_storage
+msgid "Cloud Storage"
+msgstr "Almacenamiento en la Nube"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_contacts
+msgid "Contacts"
+msgstr "Contactos"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_crm
+msgid "CRM"
+msgstr "CRM"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_loyalty
+msgid "Coupons & Loyalty"
+msgstr "Cupones y Fidelidad"
+
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_hr_attendance
+msgid "Attendances"
+msgstr "Asistencias"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_web_cohort
+msgid "Cohort View"
+msgstr "Vista de cohorte"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_portal
+msgid "Customer Portal"
+msgstr "Portal del cliente"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_rating
+msgid "Customer Rating"
+msgstr "Valoración del cliente"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_board
+msgid "Dashboards"
+msgstr "Tableros"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_databases
+msgid "Databases"
+msgstr "Bases de datos"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_delivery
+msgid "Delivery Costs"
+msgstr "Gastos de envío"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_mail
+msgid "Discuss"
+msgstr "Conversaciones"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_documents
+msgid "Documents"
+msgstr "Documentos"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_esg
+msgid "ESG"
+msgstr "ASG"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_mass_mailing
+msgid "Email Marketing"
+msgstr "Marketing por correo electrónico"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_hr_contract
+msgid "Employee Contracts"
+msgstr "Contratos de los empleados"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_hr
+msgid "Employees"
+msgstr "Empleados"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_equity
+msgid "Equity"
+msgstr "Patrimonio"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_event
+msgid "Events Organization"
+msgstr "Organización de eventos"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_hr_expense
+msgid "Expenses"
+msgstr "Gastos"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_industry_fsm
+msgid "Field Service"
+msgstr "Servicio de campo"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_fleet
+msgid "Fleet"
+msgstr "Flota"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_frontdesk
+msgid "Frontdesk"
+msgstr "Recepción"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_gamification
+msgid "Gamification"
+msgstr "Ludificación"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_web_grid
+msgid "Grid View"
+msgstr "Vista de cuadrícula"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_helpdesk
+msgid "Helpdesk"
+msgstr "Servicio de asistencia"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_bus
+msgid "IM Bus"
+msgstr "Bus IM"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_iot
+msgid "Internet of Things"
+msgstr "Internet de las cosas"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_stock
+msgid "Inventory"
+msgstr "Inventarios"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_account
+msgid "Invoicing"
+msgstr "Facturación"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_digest
+msgid "KPI Digests"
+msgstr "Resúmenes de KPI"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_knowledge
+msgid "Knowledge"
+msgstr "Información"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_link_tracker
+msgid "Link Tracker"
+msgstr "Rastreador de enlaces"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_im_livechat
+msgid "Live Chat"
+msgstr "Chat en directo"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_lunch
+msgid "Lunch"
+msgstr "Comida"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_maintenance
+msgid "Maintenance"
+msgstr "Mantenimiento"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_mrp
+msgid "Manufacturing"
+msgstr "Fabricación"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_web_map
+msgid "Map View"
+msgstr "Vista del mapa"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_marketing_automation
+msgid "Marketing Automation"
+msgstr "Automatización de marketing"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_room
+msgid "Meeting Rooms"
+msgstr "Sala de reuniones"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_membership
+msgid "Members"
+msgstr "Miembros"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_web_mobile
+msgid "Mobile"
+msgstr "Móvil"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_onboarding
+msgid "Onboarding Toolbox"
+msgstr "Caja de herramientas para la incorporación"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_partnership
+msgid "Partnership / Membership"
+msgstr "Asociación / Afiliación"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_payment
+msgid "Payment Engine"
+msgstr "Motor de pago"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_hr_payroll
+msgid "Payroll"
+msgstr "Nómina"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_phone_validation
+msgid "Phone Numbers Validation"
+msgstr "Validación de números de teléfono"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_planning
+msgid "Planning"
+msgstr "Planificación"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_point_of_sale
+msgid "Point of Sale"
+msgstr "Punto de venta"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_privacy_lookup
+msgid "Privacy"
+msgstr "Privacidad"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_product
+msgid "Products & Pricelists"
+msgstr "Productos y listas de precios"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_project
+msgid "Project"
+msgstr "Proyecto"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_purchase
+msgid "Purchase"
+msgstr "Compra"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_quality_control
+msgid "Quality"
+msgstr "Calidad"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_quality
+msgid "Quality Base"
+msgstr "Base de calidad"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_rpc
+msgid "RPC endpoints"
+msgstr "Puntos de conexión RPC"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_hr_recruitment
+msgid "Recruitment"
+msgstr "Reclutamiento"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_repair
+msgid "Repairs"
+msgstr "Reparaciones"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_resource
+msgid "Resource"
+msgstr "Recurso"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_sms
+msgid "SMS gateway"
+msgstr "Puerta de enlace SMS"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_sale
+msgid "Sales"
+msgstr "Ventas"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_sales_team
+msgid "Sales Teams"
+msgstr "Equipos de ventas"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_sign
+msgid "Sign"
+msgstr "Firmar"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_snailmail
+msgid "Snail Mail"
+msgstr "Correo postal"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_social
+msgid "Social Marketing"
+msgstr "Marketing social"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_spreadsheet
+msgid "Spreadsheet"
+msgstr "Hoja de cálculo"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_web_studio
+msgid "Studio"
+msgstr "Studio"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_survey
+msgid "Surveys"
+msgstr "Encuestas"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_hr_timesheet
+msgid "Task Logs"
+msgstr "Registros de tareas"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_hr_holidays
+msgid "Time Off"
+msgstr "Ausencia"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_timer
+msgid "Timer"
+msgstr "Temporizador"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_timesheet_grid
+msgid "Timesheets"
+msgstr "Partes de horas"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_web_tour
+msgid "Tours"
+msgstr "Recorridos"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_transifex
+msgid "Transifex integration"
+msgstr "Integración en Transifex"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_utm
+msgid "UTM Trackers"
+msgstr "Rastreadores UTM"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_uom
+msgid "Units of measure"
+msgstr "Unidades de medida"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_web_unsplash
+msgid "Unsplash Image Library"
+msgstr "Biblioteca de imágenes de Unsplash"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_voip
+msgid "VoIP"
+msgstr "VoIP"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_stock_account
+msgid "WMS Accounting"
+msgstr "Contabilidad del SGA"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_web
+msgid "Web"
+msgstr "Web"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_web_editor
+msgid "Web Editor"
+msgstr "Editor web"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_web_enterprise
+msgid "Web Enterprise"
+msgstr "Web"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_web_gantt
+msgid "Web Gantt"
+msgstr "Diagrama Gantt web"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_web_hierarchy
+msgid "Web Hierarchy"
+msgstr "Jerarquía web"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_website
+msgid "Website"
+msgstr "Sitio web"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_whatsapp
+msgid "WhatsApp Messaging"
+msgstr "Mensajes de WhatsApp"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_worksheet
+msgid "Worksheet"
+msgstr "Hoja de trabajo"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_account_accountant
+msgid "Invoicing (Enterprise)"
+msgstr "Facturación"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_preventa
+msgid "Preventa"
+msgstr "Preventa"
+
+#. module: helpdesk_extras
+#: model:helpdesk.affected.module,name:helpdesk_extras.module_sale_management
+msgid "Sales Management"
+msgstr "Gestión de Ventas"
+

+ 55 - 0
helpdesk_extras/migrations/18.0.1.0.10/post-migration.py

@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+import logging
+from odoo import api, SUPERUSER_ID
+
+_logger = logging.getLogger(__name__)
+
+
+def migrate(cr, version):
+    """Cargar traducciones de selection fields en ir.model.fields.selection"""
+    env = api.Environment(cr, SUPERUSER_ID, {})
+    
+    # Traducciones de business_impact
+    translations = {
+        'business_impact': {
+            '0': 'Crítico',
+            '1': 'Alto',
+            '2': 'Normal',
+        }
+    }
+    
+    # Traducciones de approval_status
+    translations['approval_status'] = {
+        'draft': 'N/A',
+        'waiting': 'Esperando Aprobación',
+        'approved': 'Aprobado',
+        'rejected': 'Rechazado',
+    }
+    
+    _logger.info("Cargando traducciones de selection fields...")
+    
+    for field_name, field_translations in translations.items():
+        # Buscar el campo en ir.model.fields
+        field_record = env['ir.model.fields'].search([
+            ('model', '=', 'helpdesk.ticket'),
+            ('name', '=', field_name)
+        ], limit=1)
+        
+        if not field_record:
+            _logger.warning(f"Campo {field_name} no encontrado en ir.model.fields")
+            continue
+        
+        # Actualizar cada opción de selection
+        updated_count = 0
+        for sel in field_record.selection_ids:
+            if sel.value in field_translations:
+                translated_name = field_translations[sel.value]
+                # Actualizar en español
+                sel.with_context(lang='es_MX').write({'name': translated_name})
+                updated_count += 1
+                _logger.info(f"  {field_name}.{sel.value}: '{sel.name}' → '{translated_name}'")
+        
+        _logger.info(f"✅ {updated_count} opciones actualizadas para {field_name}")
+    
+    cr.commit()
+    _logger.info("✅ Traducciones de selection fields cargadas exitosamente")

+ 52 - 0
helpdesk_extras/migrations/18.0.1.0.9/post-migration.py

@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+import logging
+from odoo import api, SUPERUSER_ID
+
+_logger = logging.getLogger(__name__)
+
+
+def migrate(cr, version):
+    """Actualizar traducciones para quitar 'Enterprise' de los nombres de módulos"""
+    env = api.Environment(cr, SUPERUSER_ID, {})
+    
+    # Actualizar traducciones en ir.translation
+    Translation = env['ir.translation']
+    
+    # Buscar traducciones problemáticas
+    translations = Translation.search([
+        ('name', '=', 'helpdesk.affected.module,name'),
+        ('lang', '=', 'es_MX'),
+        ('value', 'ilike', 'Enterprise'),
+    ])
+    
+    _logger.info(f"Encontradas {len(translations)} traducciones con 'Enterprise' para actualizar")
+    
+    updates = {
+        'Web de Enterprise': 'Web',
+        'Web Enterprise': 'Web',
+        'Contabilidad Analítica Enterprise': 'Contabilidad Analítica',
+        'Analytic Accounting Enterprise': 'Contabilidad Analítica',
+        'Facturación (Enterprise)': 'Facturación',
+        'Facturación Enterprise': 'Facturación',
+        'Invoicing (Enterprise)': 'Facturación',
+    }
+    
+    updated_count = 0
+    for trans in translations:
+        old_value = trans.value
+        new_value = updates.get(old_value)
+        
+        if not new_value:
+            # Limpieza genérica
+            new_value = old_value.replace('Enterprise', '').replace('enterprise', '')
+            new_value = new_value.replace(' de ', ' ').replace(' (', '').replace(')', '')
+            new_value = ' '.join(new_value.split()).strip()
+        
+        if old_value != new_value:
+            trans.write({'value': new_value})
+            updated_count += 1
+            _logger.info(f"Actualizado: '{old_value}' → '{new_value}'")
+    
+    cr.commit()
+    _logger.info(f"✅ {updated_count} traducciones actualizadas exitosamente")
+

+ 11 - 11
helpdesk_extras/models/helpdesk_affected_module.py

@@ -11,34 +11,34 @@ class HelpdeskAffectedModule(models.Model):
     _order = 'name'
 
     name = fields.Char(
-        string='Name',
+        string=_('Name'),
         required=True,
         translate=True,
-        help="Module name (translatable)"
+        help=_("Module name (translatable)")
     )
     code = fields.Char(
-        string='Code',
+        string=_('Code'),
         required=True,
         index=True,
-        help="Technical code of the module (e.g., 'account', 'sale')"
+        help=_("Technical code of the module (e.g., 'account', 'sale')")
     )
     active = fields.Boolean(
-        string='Active',
+        string=_('Active'),
         default=True,
-        help="If unchecked, this module will be hidden from selection"
+        help=_("If unchecked, this module will be hidden from selection")
     )
     is_main_application = fields.Boolean(
-        string='Main Application',
+        string=_('Main Application'),
         default=False,
-        help="Check if this is a main Odoo application (not an extension module)"
+        help=_("Check if this is a main Odoo application (not an extension module)")
     )
     description = fields.Text(
-        string='Description',
-        help="Module description"
+        string=_('Description'),
+        help=_("Module description")
     )
 
     _sql_constraints = [
-        ('code_unique', 'UNIQUE(code)', 'The module code must be unique!'),
+        ('code_unique', 'UNIQUE(code)', _('The module code must be unique!')),
     ]
 
     def name_get(self):

+ 168 - 43
helpdesk_extras/models/helpdesk_team.py

@@ -404,6 +404,25 @@ class HelpdeskTeamExtras(models.Model):
         if not default_form:
             return
 
+        # Get website language for translations
+        # Try to get current website, fallback to default website or system language
+        website = self.env['website'].get_current_website()
+        if website:
+            lang = website.default_lang_id.code if website.default_lang_id else 'en_US'
+        else:
+            # Fallback: try to get default website
+            try:
+                default_website = self.env.ref('website.default_website', raise_if_not_found=False)
+                if default_website and default_website.default_lang_id:
+                    lang = default_website.default_lang_id.code
+                else:
+                    lang = self.env.context.get('lang', 'en_US')
+            except Exception:
+                lang = self.env.context.get('lang', 'en_US')
+        
+        # Create environment with website language for translations
+        env_lang = self.env(context=dict(self.env.context, lang=lang))
+
         # Get template fields sorted by sequence
         template_fields = self.template_id.field_ids.sorted('sequence')
         
@@ -466,7 +485,7 @@ class HelpdeskTeamExtras(models.Model):
         template_fields_html = []
         for tf in template_fields:
             try:
-                field_html, field_id_counter = self._build_template_field_html(tf, field_id_counter)
+                field_html, field_id_counter = self._build_template_field_html(tf, field_id_counter, env_lang=env_lang)
                 if field_html:
                     template_fields_html.append(field_html)
                     _logger.info(f"Built HTML for field {tf.field_id.name if tf.field_id else 'Unknown'}")
@@ -538,18 +557,19 @@ class HelpdeskTeamExtras(models.Model):
         # Restore default arch
         self.website_form_view_id.sudo().arch = default_form.arch
 
-    def _build_template_field_html(self, template_field, field_id_counter=0):
+    def _build_template_field_html(self, template_field, field_id_counter=0, env_lang=None):
         """Build HTML string for a template field exactly as Odoo's form builder does
         
         Args:
             template_field: helpdesk.template.field record
             field_id_counter: int, counter for generating unique field IDs (incremented and returned)
+            env_lang: Environment with language context for translations
         
         Returns:
             tuple: (html_string, updated_counter)
         """
         # Build the XML element first using existing method
-        field_el, field_id_counter = self._build_template_field_xml(template_field, field_id_counter)
+        field_el, field_id_counter = self._build_template_field_xml(template_field, field_id_counter, env_lang=env_lang)
         if field_el is None:
             return None, field_id_counter
         
@@ -557,12 +577,13 @@ class HelpdeskTeamExtras(models.Model):
         html_str = etree.tostring(field_el, encoding='unicode', pretty_print=True)
         return html_str, field_id_counter
 
-    def _build_template_field_xml(self, template_field, field_id_counter=0):
+    def _build_template_field_xml(self, template_field, field_id_counter=0, env_lang=None):
         """Build XML element for a template field exactly as Odoo's form builder does
         
         Args:
             template_field: helpdesk.template.field record
             field_id_counter: int, counter for generating unique field IDs (incremented and returned)
+            env_lang: Environment with language context for translations
         
         Returns:
             tuple: (field_element, updated_counter)
@@ -570,8 +591,32 @@ class HelpdeskTeamExtras(models.Model):
         field = template_field.field_id
         field_name = field.name
         field_type = field.ttype
-        # Use custom label if provided, otherwise use field's default label
-        field_label = template_field.label_custom or field.field_description or field.name
+        
+        # Use environment with language context if provided, otherwise use self.env
+        env = env_lang if env_lang else self.env
+        
+        # Use custom label if provided, otherwise use field's default label with language context
+        if template_field.label_custom:
+            field_label = template_field.label_custom
+        else:
+            # Get field description in the correct language using the model's field
+            model_name = field.model_id.model
+            try:
+                model = env[model_name]
+                model_field = model._fields.get(field_name)
+                if model_field:
+                    # Use get_description() method which returns translated string
+                    # This method respects the language context
+                    field_desc = model_field.get_description(env)
+                    field_label = field_desc.get('string', '') if isinstance(field_desc, dict) else (field_desc or field.field_description or field.name)
+                    if not field_label:
+                        field_label = field.field_description or field.name
+                else:
+                    field_label = field.field_description or field.name
+            except Exception:
+                # Fallback to field description or name
+                field_label = field.field_description or field.name
+        
         required = template_field.required
         sequence = template_field.sequence
 
@@ -719,20 +764,33 @@ class HelpdeskTeamExtras(models.Model):
                 if selection_options:
                     options_list = selection_options
                 else:
-                    # Get from model field definition
+                    # Get from model field definition with language context
                     model_name = field.model_id.model
-                    model = self.env[model_name]
+                    model = env[model_name]
                     options_list = []
                     if hasattr(model, field_name):
                         model_field = model._fields.get(field_name)
                         if model_field and hasattr(model_field, 'selection'):
-                            selection = model_field.selection
-                            if callable(selection):
-                                selection = selection(model)
-                            if isinstance(selection, (list, tuple)):
-                                options_list = selection
+                            # Use _description_selection method which handles translation correctly
+                            try:
+                                if hasattr(model_field, '_description_selection'):
+                                    # This method returns translated options when env has lang context
+                                    selection = model_field._description_selection(env)
+                                    if isinstance(selection, (list, tuple)):
+                                        options_list = selection
+                                else:
+                                    # Fallback: use get_field_selection from ir.model.fields
+                                    options_list = env['ir.model.fields'].get_field_selection(model_name, field_name)
+                            except Exception:
+                                # Fallback: get selection directly
+                                selection = model_field.selection
+                                if callable(selection):
+                                    selection = selection(model)
+                                if isinstance(selection, (list, tuple)):
+                                    options_list = selection
                         elif field.selection:
                             try:
+                                # Evaluate selection string if it's stored as string
                                 selection = eval(field.selection) if isinstance(field.selection, str) else field.selection
                                 if isinstance(selection, (list, tuple)):
                                     options_list = selection
@@ -771,12 +829,12 @@ class HelpdeskTeamExtras(models.Model):
                     'data-name': field_name,
                     'data-display': 'horizontal'
                 })
-                # Get selection options (same as radio)
+                # Get selection options (same as radio) with language context
                 if selection_options:
                     options_list = selection_options
                 else:
                     model_name = field.model_id.model
-                    model = self.env[model_name]
+                    model = env[model_name]
                     options_list = []
                     if hasattr(model, field_name):
                         model_field = model._fields.get(field_name)
@@ -831,11 +889,31 @@ class HelpdeskTeamExtras(models.Model):
                     input_el.set('placeholder', template_field.placeholder)
                 if required:
                     input_el.set('required', '1')
-                # Add default option
+                # Add default option - translate using website language context
                 default_option = etree.SubElement(input_el, 'option', {
                     'value': ''
                 })
-                default_option.text = '-- Select --'
+                # Get translation using the environment's language context
+                # Load translations explicitly and get translated text
+                lang = env.lang or 'en_US'
+                try:
+                    from odoo.tools.translate import get_translation, code_translations
+                    # Force load translations by accessing them (this triggers _load_python_translations)
+                    translations = code_translations.get_python_translations('helpdesk_extras', lang)
+                    translated_text = get_translation('helpdesk_extras', lang, '-- Select --', ())
+                    # If translation is the same as source, it means translation not found or not loaded
+                    if translated_text == '-- Select --' and lang != 'en_US':
+                        # Check if translation exists in loaded translations
+                        translated_text = translations.get('-- Select --', '-- Select --')
+                    default_option.text = translated_text
+                except Exception:
+                    # Fallback: use direct translation mapping based on language
+                    translations_map = {
+                        'es_MX': '-- Seleccionar --',
+                        'es_ES': '-- Seleccionar --',
+                        'es': '-- Seleccionar --',
+                    }
+                    default_option.text = translations_map.get(lang, '-- Select --')
                 
                 # Populate selection options
                 if selection_options:
@@ -848,25 +926,52 @@ class HelpdeskTeamExtras(models.Model):
                         if template_field.default_value and str(template_field.default_value) == str(option_value):
                             option.set('selected', 'selected')
                 else:
-                    # Get from model field definition
+                    # Get from model field definition with language context
                     model_name = field.model_id.model
-                    model = self.env[model_name]
+                    model = env[model_name]
                     if hasattr(model, field_name):
                         model_field = model._fields.get(field_name)
                         if model_field and hasattr(model_field, 'selection'):
-                            selection = model_field.selection
-                            if callable(selection):
-                                selection = selection(model)
-                            if isinstance(selection, (list, tuple)):
-                                for option_value, option_label in selection:
-                                    option = etree.SubElement(input_el, 'option', {
-                                        'value': str(option_value)
-                                    })
-                                    option.text = option_label
-                                    if template_field.default_value and str(template_field.default_value) == str(option_value):
-                                        option.set('selected', 'selected')
+                            # Use _description_selection method which handles translation correctly
+                            try:
+                                if hasattr(model_field, '_description_selection'):
+                                    # This method returns translated options when env has lang context
+                                    selection = model_field._description_selection(env)
+                                    if isinstance(selection, (list, tuple)):
+                                        for option_value, option_label in selection:
+                                            option = etree.SubElement(input_el, 'option', {
+                                                'value': str(option_value)
+                                            })
+                                            option.text = option_label
+                                            if template_field.default_value and str(template_field.default_value) == str(option_value):
+                                                option.set('selected', 'selected')
+                                else:
+                                    # Fallback: use get_field_selection from ir.model.fields
+                                    selection = env['ir.model.fields'].get_field_selection(model_name, field_name)
+                                    if isinstance(selection, (list, tuple)):
+                                        for option_value, option_label in selection:
+                                            option = etree.SubElement(input_el, 'option', {
+                                                'value': str(option_value)
+                                            })
+                                            option.text = option_label
+                                            if template_field.default_value and str(template_field.default_value) == str(option_value):
+                                                option.set('selected', 'selected')
+                            except Exception:
+                                # Fallback: get selection directly
+                                selection = model_field.selection
+                                if callable(selection):
+                                    selection = selection(model)
+                                if isinstance(selection, (list, tuple)):
+                                    for option_value, option_label in selection:
+                                        option = etree.SubElement(input_el, 'option', {
+                                            'value': str(option_value)
+                                        })
+                                        option.text = option_label
+                                        if template_field.default_value and str(template_field.default_value) == str(option_value):
+                                            option.set('selected', 'selected')
                         elif field.selection:
                             try:
+                                # Evaluate selection string if it's stored as string
                                 selection = eval(field.selection) if isinstance(field.selection, str) else field.selection
                                 if isinstance(selection, (list, tuple)):
                                     for option_value, option_label in selection:
@@ -908,11 +1013,11 @@ class HelpdeskTeamExtras(models.Model):
                     'data-name': field_name,
                     'data-display': 'horizontal'
                 })
-                # Load records from relation
+                # Load records from relation with language context
                 relation = field.relation
                 if relation and relation != 'ir.attachment':
                     try:
-                        records = self.env[relation].sudo().search_read(
+                        records = env[relation].sudo().search_read(
                             [], ['display_name'], limit=1000
                         )
                         for record in records:
@@ -951,7 +1056,7 @@ class HelpdeskTeamExtras(models.Model):
                 relation = field.relation
                 if relation and relation != 'ir.attachment':
                     try:
-                        records = self.env[relation].sudo().search_read(
+                        records = env[relation].sudo().search_read(
                             [], ['display_name'], limit=1000
                         )
                         default_values = template_field.default_value.split(',') if template_field.default_value else []
@@ -993,16 +1098,36 @@ class HelpdeskTeamExtras(models.Model):
                 if required:
                     input_el.set('required', '1')
                 
-                # Add default option
+                # Add default option - translate using website language context
                 default_option = etree.SubElement(input_el, 'option', {'value': ''})
-                default_option.text = '-- Select --'
+                # Get translation using the environment's language context
+                # Load translations explicitly and get translated text
+                lang = env.lang or 'en_US'
+                try:
+                    from odoo.tools.translate import get_translation, code_translations
+                    # Force load translations by accessing them (this triggers _load_python_translations)
+                    translations = code_translations.get_python_translations('helpdesk_extras', lang)
+                    translated_text = get_translation('helpdesk_extras', lang, '-- Select --', ())
+                    # If translation is the same as source, it means translation not found or not loaded
+                    if translated_text == '-- Select --' and lang != 'en_US':
+                        # Check if translation exists in loaded translations
+                        translated_text = translations.get('-- Select --', '-- Select --')
+                    default_option.text = translated_text
+                except Exception:
+                    # Fallback: use direct translation mapping based on language
+                    translations_map = {
+                        'es_MX': '-- Seleccionar --',
+                        'es_ES': '-- Seleccionar --',
+                        'es': '-- Seleccionar --',
+                    }
+                    default_option.text = translations_map.get(lang, '-- Select --')
             
-            # Load records dynamically from relation
+            # Load records dynamically from relation with language context
             relation = field.relation
             if relation and relation != 'ir.attachment':
                 try:
-                    # Try to get records from the relation model
-                    records = self.env[relation].sudo().search_read(
+                    # Try to get records from the relation model with language context
+                    records = env[relation].sudo().search_read(
                         [], ['display_name'], limit=1000
                     )
                     for record in records:
@@ -1013,16 +1138,16 @@ class HelpdeskTeamExtras(models.Model):
                             if template_field.default_value and str(template_field.default_value) == str(record['id']):
                                 option.set('selected', 'selected')
                 except Exception:
-                    # If relation doesn't exist or access denied, try specific cases
+                    # If relation doesn't exist or access denied, try specific cases with language context
                     if field_name == 'request_type_id':
-                        request_types = self.env['helpdesk.request.type'].sudo().search([('active', '=', True)])
+                        request_types = env['helpdesk.request.type'].sudo().search([('active', '=', True)])
                         for req_type in request_types:
                             option = etree.SubElement(input_el, 'option', {
                                 'value': str(req_type.id)
                             })
                             option.text = req_type.name
                     elif field_name == 'affected_module_id':
-                        modules = self.env['helpdesk.affected.module'].sudo().search([
+                        modules = env['helpdesk.affected.module'].sudo().search([
                             ('active', '=', True)
                         ], order='name')
                         for module in modules:
@@ -1082,11 +1207,11 @@ class HelpdeskTeamExtras(models.Model):
                     'data-name': field_name,
                     'data-display': 'horizontal'
                 })
-                # Try to load records from relation
+                # Try to load records from relation with language context
                 relation = field.relation
                 if relation:
                     try:
-                        records = self.env[relation].sudo().search_read(
+                        records = env[relation].sudo().search_read(
                             [], ['display_name'], limit=100
                         )
                         for record in records:

+ 32 - 32
helpdesk_extras/models/helpdesk_ticket.py

@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 # Part of Odoo. See LICENSE file for full copyright and licensing details.
 
-from odoo import api, fields, models
+from odoo import _, api, fields, models
 
 
 class HelpdeskTicket(models.Model):
@@ -9,82 +9,82 @@ class HelpdeskTicket(models.Model):
 
     request_type_id = fields.Many2one(
         'helpdesk.request.type',
-        string='Request Type',
+        string=_('Request Type'),
         required=True,
         tracking=True,
-        help="Type of ticket (e.g., Incident, Improvement)",
+        help=_("Type of ticket (e.g., Incident, Improvement)"),
         default=lambda self: self._default_request_type_id()
     )
     request_type_code = fields.Char(
         related='request_type_id.code',
-        string='Request Type Code',
+        string=_('Request Type Code'),
         store=True,
         readonly=True,
-        help="Code of the request type for conditional logic"
+        help=_("Code of the request type for conditional logic")
     )
     affected_module_id = fields.Many2one(
         'helpdesk.affected.module',
-        string='Affected Module',
+        string=_('Affected Module'),
         required=False,
         domain=[('active', '=', True)],
-        help="Odoo module where the issue or improvement occurs"
+        help=_("Odoo module where the issue or improvement occurs")
     )
     affected_user_email = fields.Char(
-        string='Affected User Email',
+        string=_('Affected User Email'),
         tracking=True,
-        help="Email address of the affected user (from another Odoo instance or external)"
+        help=_("Email address of the affected user (from another Odoo instance or external)")
     )
     business_impact = fields.Selection(
         [
-            ('0', 'Critical'),
-            ('1', 'High'),
-            ('2', 'Normal'),
+            ('0', _('Critical')),
+            ('1', _('High')),
+            ('2', _('Normal')),
         ],
-        string='Business Impact',
+        string=_('Business Impact'),
         default='2',
         tracking=True,
-        help="Urgency reported by the client"
+        help=_("Urgency reported by the client")
     )
     reproduce_steps = fields.Html(
-        string='Steps to Reproduce',
-        help="Detailed steps to reproduce the issue (only for Incidents)"
+        string=_('Steps to Reproduce'),
+        help=_("Detailed steps to reproduce the issue (only for Incidents)")
     )
     business_goal = fields.Html(
-        string='Business Goal',
-        help="Business objective for this improvement (only for Improvements)"
+        string=_('Business Goal'),
+        help=_("Business objective for this improvement (only for Improvements)")
     )
     client_authorization = fields.Boolean(
-        string='Client Authorization',
+        string=_('Client Authorization'),
         default=False,
-        help="Checkbox from web form indicating client authorization"
+        help=_("Checkbox from web form indicating client authorization")
     )
     estimated_hours = fields.Float(
-        string='Estimated Hours',
-        help="Hours quoted after analysis"
+        string=_('Estimated Hours'),
+        help=_("Hours quoted after analysis")
     )
     approval_status = fields.Selection(
         [
-            ('draft', 'N/A'),
-            ('waiting', 'Waiting for Approval'),
-            ('approved', 'Approved'),
-            ('rejected', 'Rejected'),
+            ('draft', _('N/A')),
+            ('waiting', _('Waiting for Approval')),
+            ('approved', _('Approved')),
+            ('rejected', _('Rejected')),
         ],
-        string='Approval Status',
+        string=_('Approval Status'),
         default='draft',
         tracking=True,
-        help="Status of the approval workflow"
+        help=_("Status of the approval workflow")
     )
     has_template = fields.Boolean(
-        string='Has Template',
+        string=_('Has Template'),
         compute='_compute_has_template',
-        help="Indicates if the team has a template assigned"
+        help=_("Indicates if the team has a template assigned")
     )
     attachment_ids = fields.One2many(
         'ir.attachment',
         'res_id',
-        string='Attachments',
+        string=_('Attachments'),
         domain=[('res_model', '=', 'helpdesk.ticket')],
-        help="Files attached to this ticket"
+        help=_("Files attached to this ticket")
     )
 
     @api.depends('team_id.template_id')

+ 81 - 0
helpdesk_extras/scripts/update_translations.py

@@ -0,0 +1,81 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Script para actualizar traducciones y quitar 'Enterprise' de los nombres de módulos
+Ejecutar: python3 scripts/update_translations.py
+"""
+import sys
+import os
+
+# Agregar ruta de Odoo
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../odoo'))
+
+import odoo
+from odoo import api, SUPERUSER_ID
+
+def update_translations():
+    """Actualizar traducciones para quitar Enterprise"""
+    # Configurar Odoo
+    odoo.tools.config.parse_config(['-c', '../../../odoo.conf', '-d', 'm22_techconsulting_dev'])
+    
+    with odoo.api.Environment.manage():
+        env = api.Environment(odoo.registry('m22_techconsulting_dev'), SUPERUSER_ID, {})
+        
+        try:
+            Translation = env['ir.translation']
+            
+            # Buscar traducciones problemáticas
+            translations = Translation.search([
+                ('name', '=', 'helpdesk.affected.module,name'),
+                ('lang', '=', 'es_MX'),
+            ])
+            
+            print(f"🔍 Encontradas {len(translations)} traducciones para helpdesk.affected.module")
+            
+            # Filtrar las que contienen Enterprise
+            problematic = [t for t in translations if 'Enterprise' in t.value or 'enterprise' in t.value.lower()]
+            
+            if not problematic:
+                print("✅ No hay traducciones con 'Enterprise'")
+                return
+            
+            print(f"🔄 Actualizando {len(problematic)} traducciones...")
+            
+            updates = {
+                'Web de Enterprise': 'Web',
+                'Web Enterprise': 'Web',
+                'Contabilidad Analítica Enterprise': 'Contabilidad Analítica',
+                'Analytic Accounting Enterprise': 'Contabilidad Analítica',
+                'Facturación (Enterprise)': 'Facturación',
+                'Facturación Enterprise': 'Facturación',
+                'Invoicing (Enterprise)': 'Facturación',
+            }
+            
+            updated_count = 0
+            for trans in problematic:
+                old_value = trans.value
+                new_value = updates.get(old_value)
+                
+                if not new_value:
+                    # Limpieza genérica
+                    new_value = old_value.replace('Enterprise', '').replace('enterprise', '')
+                    new_value = new_value.replace(' de ', ' ').replace(' (', '').replace(')', '')
+                    new_value = ' '.join(new_value.split()).strip()
+                
+                if old_value != new_value:
+                    trans.write({'value': new_value})
+                    updated_count += 1
+                    print(f"✅ '{old_value}' → '{new_value}'")
+            
+            env.cr.commit()
+            print(f"\n✅ {updated_count} traducciones actualizadas exitosamente")
+            
+        except Exception as e:
+            print(f"❌ Error: {e}")
+            import traceback
+            traceback.print_exc()
+            env.cr.rollback()
+
+if __name__ == '__main__':
+    update_translations()
+

+ 11 - 11
helpdesk_extras/views/helpdesk_affected_module_views.xml

@@ -6,7 +6,7 @@
         <field name="name">helpdesk.affected.module.list</field>
         <field name="model">helpdesk.affected.module</field>
         <field name="arch" type="xml">
-            <list string="Affected Modules" default_order="name">
+            <list default_order="name">
                 <field name="code"/>
                 <field name="name"/>
                 <field name="is_main_application"/>
@@ -20,7 +20,7 @@
         <field name="name">helpdesk.affected.module.form</field>
         <field name="model">helpdesk.affected.module</field>
         <field name="arch" type="xml">
-            <form string="Affected Module">
+            <form>
                 <sheet>
                     <group>
                         <group>
@@ -31,7 +31,7 @@
                         </group>
                     </group>
                     <group>
-                        <field name="description" placeholder="Module description..."/>
+                        <field name="description"/>
                     </group>
                 </sheet>
             </form>
@@ -43,36 +43,36 @@
         <field name="name">helpdesk.affected.module.search</field>
         <field name="model">helpdesk.affected.module</field>
         <field name="arch" type="xml">
-            <search string="Search Affected Modules">
+            <search>
                 <field name="code"/>
                 <field name="name"/>
-                <filter string="Active" name="active" domain="[('active', '=', True)]"/>
-                <filter string="Inactive" name="inactive" domain="[('active', '=', False)]"/>
-                <filter string="Main Applications" name="main_app" domain="[('is_main_application', '=', True)]"/>
+                <filter name="active" domain="[('active', '=', True)]"/>
+                <filter name="inactive" domain="[('active', '=', False)]"/>
+                <filter name="main_app" domain="[('is_main_application', '=', True)]"/>
             </search>
         </field>
     </record>
 
     <!-- Action for Affected Module Catalog -->
     <record id="helpdesk_affected_module_action" model="ir.actions.act_window">
-        <field name="name">Affected Modules</field>
+        <field name="name">Módulos Afectados</field>
         <field name="res_model">helpdesk.affected.module</field>
         <field name="view_mode">list,form</field>
         <field name="domain">[('active', '=', True), ('is_main_application', '=', True)]</field>
         <field name="context">{'search_default_active': 1, 'search_default_main_app': 1}</field>
         <field name="help" type="html">
             <p class="o_view_nocontent_smiling_face">
-                Create your first affected module
+                Crea tu primer módulo afectado
             </p>
             <p>
-                Manage the catalog of Odoo modules that can be affected in helpdesk tickets.
+                Gestiona el catálogo de módulos de Odoo que pueden ser afectados en tickets de helpdesk.
             </p>
         </field>
     </record>
 
     <!-- Menu Item for Affected Module Catalog -->
     <menuitem id="helpdesk_affected_module_menu"
-              name="Affected Modules"
+              name="Módulos Afectados"
               parent="helpdesk.helpdesk_menu_config"
               action="helpdesk_affected_module_action"
               sequence="25"

+ 12 - 12
helpdesk_extras/views/helpdesk_portal_templates.xml

@@ -24,40 +24,40 @@
         <!-- Add new fields after "Reported on" -->
         <xpath expr="//div[@name='description']" position="before">
             <div t-if="ticket.request_type_id" class="row mb-4">
-                <strong class="col-lg-3">Request Type</strong>
+                <strong class="col-lg-3">Tipo de Solicitud</strong>
                 <span class="col-lg-9" t-field="ticket.request_type_id.name"/>
             </div>
             <div t-if="ticket.affected_module_id" class="row mb-4">
-                <strong class="col-lg-3">Affected Module</strong>
+                <strong class="col-lg-3">Módulo Afectado</strong>
                 <span class="col-lg-9" t-field="ticket.affected_module_id.name"/>
             </div>
             <div t-if="ticket.business_impact" class="row mb-4">
-                <strong class="col-lg-3">Business Impact</strong>
+                <strong class="col-lg-3">Impacto de Negocio</strong>
                 <span class="col-lg-9">
-                    <t t-if="ticket.business_impact == '0'">Critical</t>
-                    <t t-elif="ticket.business_impact == '1'">High</t>
+                    <t t-if="ticket.business_impact == '0'">Crítico</t>
+                    <t t-elif="ticket.business_impact == '1'">Alto</t>
                     <t t-elif="ticket.business_impact == '2'">Normal</t>
                 </span>
             </div>
             <div t-if="ticket.reproduce_steps and ticket.request_type_code == 'incident'" class="row mb-4">
-                <strong class="col-lg-3">Steps to Reproduce</strong>
+                <strong class="col-lg-3">Pasos para Reproducir</strong>
                 <div class="col-lg-9" t-field="ticket.reproduce_steps"/>
             </div>
             <div t-if="ticket.business_goal and ticket.request_type_code == 'improvement'" class="row mb-4">
-                <strong class="col-lg-3">Business Goal</strong>
+                <strong class="col-lg-3">Objetivo de Negocio</strong>
                 <div class="col-lg-9" t-field="ticket.business_goal"/>
             </div>
             <div t-if="ticket.estimated_hours" class="row mb-4">
-                <strong class="col-lg-3">Estimated Hours</strong>
+                <strong class="col-lg-3">Horas Estimadas</strong>
                 <span class="col-lg-9" t-field="ticket.estimated_hours" t-options='{"widget": "float"}'/>
             </div>
             <div t-if="ticket.approval_status" class="row mb-4">
-                <strong class="col-lg-3">Approval Status</strong>
+                <strong class="col-lg-3">Estado de Aprobación</strong>
                 <span class="col-lg-9">
                     <t t-if="ticket.approval_status == 'draft'">N/A</t>
-                    <t t-elif="ticket.approval_status == 'waiting'">Waiting for Approval</t>
-                    <t t-elif="ticket.approval_status == 'approved'">Approved</t>
-                    <t t-elif="ticket.approval_status == 'rejected'">Rejected</t>
+                    <t t-elif="ticket.approval_status == 'waiting'">Esperando Aprobación</t>
+                    <t t-elif="ticket.approval_status == 'approved'">Aprobado</t>
+                    <t t-elif="ticket.approval_status == 'rejected'">Rechazado</t>
                 </span>
             </div>
         </xpath>

+ 2 - 2
helpdesk_extras/views/helpdesk_ticket_views.xml

@@ -32,7 +32,7 @@
                         <field name="estimated_hours"/>
                         <field name="approval_status"/>
                     </group>
-                    <group string="Attachments">
+                    <group>
                         <field name="attachment_ids"/>
                     </group>
                     <group string="Template Information" invisible="not has_template">
@@ -71,7 +71,7 @@
         <field name="model">helpdesk.ticket</field>
         <field name="arch" type="xml">
             <xpath expr="//field[@name='partner_id']" position="after">
-                <field name="affected_module_id" string="Affected Module"/>
+                <field name="affected_module_id"/>
             </xpath>
         </field>
     </record>

+ 43 - 6
theme_m22tc/static/src/js/m22_helpdesk_select.js

@@ -75,11 +75,15 @@ publicWidget.registry.M22HelpdeskSelect = publicWidget.Widget.extend({
      */
     _transformSelectToCombobox: function (select) {
         // Get options from select
-        const options = Array.from(select.options).map(opt => ({
-            value: opt.value,
-            label: opt.textContent.trim(),
-            disabled: opt.disabled
-        })).filter(opt => opt.value || opt.label);
+        // Filter out placeholder/disabled options that are typically used as placeholders
+        const options = Array.from(select.options)
+            .map(opt => ({
+                value: opt.value,
+                label: opt.textContent.trim(),
+                disabled: opt.disabled,
+                isPlaceholder: opt.disabled || !opt.value || opt.value === ''
+            }))
+            .filter(opt => opt.value || opt.label); // Keep only options with value or label
 
         // Get current value
         const currentValue = select.value;
@@ -97,7 +101,7 @@ publicWidget.registry.M22HelpdeskSelect = publicWidget.Widget.extend({
             open: false,
             search: '',
             selected: currentOption || null,
-            options: options.filter(opt => !opt.disabled),
+            options: options.filter(opt => !opt.disabled && !opt.isPlaceholder),
             placeholder: placeholder,
             searchPlaceholder: searchPlaceholder,
             noResultsText: noResultsText,
@@ -290,14 +294,44 @@ publicWidget.registry.M22HelpdeskSelect = publicWidget.Widget.extend({
 
         const openDropdown = () => {
             state.open = true;
+            // Add class to wrapper for CSS z-index management
+            wrapper.classList.add('m22-combobox-open');
             dropdown.classList.remove('hidden');
             arrow.classList.add('rotate-180');
+            
+            // Close other open dropdowns to prevent overlap
+            const allWrappers = document.querySelectorAll('.m22-combobox-wrapper.m22-combobox-open');
+            allWrappers.forEach(otherWrapper => {
+                if (otherWrapper !== wrapper) {
+                    otherWrapper.classList.remove('m22-combobox-open');
+                    const otherDropdown = otherWrapper.querySelector('.m22-combobox-dropdown');
+                    const otherArrow = otherWrapper.querySelector('.m22-combobox-arrow');
+                    if (otherDropdown) {
+                        otherDropdown.classList.add('hidden');
+                    }
+                    if (otherArrow) {
+                        otherArrow.classList.remove('rotate-180');
+                    }
+                    // Reset state if accessible
+                    if (otherWrapper._comboboxState) {
+                        otherWrapper._comboboxState.open = false;
+                        otherWrapper._comboboxState.search = '';
+                        const otherSearchInput = otherWrapper.querySelector('.m22-combobox-search');
+                        if (otherSearchInput) {
+                            otherSearchInput.value = '';
+                        }
+                    }
+                }
+            });
+            
             searchInput.focus();
             renderOptions();
         };
 
         const closeDropdown = () => {
             state.open = false;
+            // Remove open class from wrapper
+            wrapper.classList.remove('m22-combobox-open');
             dropdown.classList.add('hidden');
             arrow.classList.remove('rotate-180');
             state.search = '';
@@ -340,6 +374,9 @@ publicWidget.registry.M22HelpdeskSelect = publicWidget.Widget.extend({
             }
         });
 
+        // Store state reference for external access (used when closing other dropdowns)
+        wrapper._comboboxState = state;
+        
         // Click outside handler
         const clickOutsideHandler = (e) => {
             if (state.open && !wrapper.contains(e.target)) {

+ 10 - 0
theme_m22tc/static/src/scss/m22tc_helpdesk_select.scss

@@ -16,11 +16,21 @@ form[data-model_name="helpdesk.ticket"] {
         position: relative;
         z-index: 1;
         
+        // When dropdown is open, increase z-index to appear above other dropdowns
+        &.m22-combobox-open {
+            z-index: 10000;
+        }
+        
         // Dropdown panel
         .m22-combobox-dropdown {
             z-index: 9999 !important;
         }
         
+        // When wrapper is open, dropdown should have even higher z-index
+        &.m22-combobox-open .m22-combobox-dropdown {
+            z-index: 10001 !important;
+        }
+        
         // Arrow rotation animation
         .m22-combobox-arrow {
             transition: transform 0.2s ease-in-out;