Forráskód Böngészése

Actualización módulo helpdesk_extras - Verificación y actualización de campo affected_user_id

odoo 2 hónapja
szülő
commit
222b50dd12

+ 3 - 1
helpdesk_extras/__manifest__.py

@@ -1,6 +1,6 @@
 {
     "name": "Helpdesk Extras",
-    "version": "18.0.1.0.3",
+    "version": "18.0.1.0.7",
     "category": "Services/Helpdesk",
     "summary": "Funcionalidades extras para Helpdesk - Compartir equipos y widget de horas",
     "description": """
@@ -27,6 +27,7 @@ Funcionalidades adicionales para el módulo de Helpdesk:
         "data/helpdesk_request_type_data.xml",
         "data/helpdesk_form_data.xml",
         "data/helpdesk_workflow_template_data.xml",
+        "data/helpdesk_affected_module_data.xml",
         "wizard/helpdesk_team_share_wizard_views.xml",
         "wizard/helpdesk_workflow_template_apply_wizard_views.xml",
         "views/helpdesk_request_type_views.xml",
@@ -34,6 +35,7 @@ Funcionalidades adicionales para el módulo de Helpdesk:
         "views/helpdesk_workflow_template_views.xml",
         "views/helpdesk_team_views.xml",
         "views/helpdesk_ticket_views.xml",
+        "views/helpdesk_affected_module_views.xml",
         "views/helpdesk_portal_templates.xml",
         "views/website_helpdesk_form.xml",
         "views/snippets/s_helpdesk_hours.xml",

+ 42 - 9
helpdesk_extras/controllers/website_helpdesk_hours.py

@@ -18,15 +18,19 @@ class WebsiteHelpdeskHours(http.Controller):
         """
         Calculate available hours for the authenticated portal user's partner.
 
-        Returns:
-            dict: {
-                'total_available': float,  # Total hours available
-                'hours_used': float,       # Hours already delivered/used
-                'prepaid_hours': float,    # Hours from prepaid orders (not delivered)
-                'credit_hours': float,      # Hours calculated from available credit
-                'credit_available': float, # Available credit amount
-                'highest_price': float,    # Highest price unit for hours
-            }
+            Returns:
+                dict: {
+                    'total_available': float,  # Total hours available
+                    'hours_used': float,       # Hours already delivered/used
+                    'prepaid_hours': float,    # Hours from prepaid orders (not delivered)
+                    'prepaid_hours_used': float,  # Hours used from prepaid
+                    'prepaid_hours_total': float,  # Total prepaid hours (available + used)
+                    'credit_hours': float,      # Hours calculated from available credit
+                    'credit_hours_used': float,  # Hours used from credit
+                    'credit_hours_total': float,  # Total credit hours (available + used)
+                    'credit_available': float, # Available credit amount
+                    'highest_price': float,    # Highest price unit for hours
+                }
         """
         try:
             # Get contact information early for use in all return cases
@@ -47,7 +51,11 @@ class WebsiteHelpdeskHours(http.Controller):
                     "total_available": 0.0,
                     "hours_used": 0.0,
                     "prepaid_hours": 0.0,
+                    "prepaid_hours_used": 0.0,
+                    "prepaid_hours_total": 0.0,
                     "credit_hours": 0.0,
+                    "credit_hours_used": 0.0,
+                    "credit_hours_total": 0.0,
                     "credit_available": 0.0,
                     "highest_price": 0.0,
                     "whatsapp_number": whatsapp_number,
@@ -67,7 +75,11 @@ class WebsiteHelpdeskHours(http.Controller):
                     "total_available": 0.0,
                     "hours_used": 0.0,
                     "prepaid_hours": 0.0,
+                    "prepaid_hours_used": 0.0,
+                    "prepaid_hours_total": 0.0,
                     "credit_hours": 0.0,
+                    "credit_hours_used": 0.0,
+                    "credit_hours_total": 0.0,
                     "credit_available": 0.0,
                     "highest_price": 0.0,
                     "whatsapp_number": whatsapp_number,
@@ -95,7 +107,11 @@ class WebsiteHelpdeskHours(http.Controller):
                     "total_available": 0.0,
                     "hours_used": 0.0,
                     "prepaid_hours": 0.0,
+                    "prepaid_hours_used": 0.0,
+                    "prepaid_hours_total": 0.0,
                     "credit_hours": 0.0,
+                    "credit_hours_used": 0.0,
+                    "credit_hours_total": 0.0,
                     "credit_available": 0.0,
                     "highest_price": 0.0,
                     "whatsapp_number": whatsapp_number,
@@ -288,13 +304,26 @@ class WebsiteHelpdeskHours(http.Controller):
             # Available hours = paid invoice hours only (minus used hours)
             prepaid_hours = max(0.0, paid_hours - hours_used)
             
+            # Calculate used hours separately for prepaid and credit
+            # Hours are consumed from prepaid first, then from credit
+            prepaid_hours_used = min(hours_used, paid_hours)
+            credit_hours_used = max(0.0, hours_used - paid_hours)
+            
+            # Calculate totals (available + used)
+            prepaid_hours_total = prepaid_hours + prepaid_hours_used  # Should equal paid_hours
+            credit_hours_total = credit_hours + credit_hours_used
+            
             total_available = prepaid_hours + credit_hours
 
             return {
                 "total_available": round(total_available, 2),
                 "hours_used": round(hours_used, 2),
                 "prepaid_hours": round(prepaid_hours, 2),
+                "prepaid_hours_used": round(prepaid_hours_used, 2),
+                "prepaid_hours_total": round(prepaid_hours_total, 2),
                 "credit_hours": round(credit_hours, 2),
+                "credit_hours_used": round(credit_hours_used, 2),
+                "credit_hours_total": round(credit_hours_total, 2),
                 "credit_available": round(credit_available, 2),
                 "highest_price": round(highest_price, 2),
                 "whatsapp_number": whatsapp_number,
@@ -329,7 +358,11 @@ class WebsiteHelpdeskHours(http.Controller):
                 "total_available": 0.0,
                 "hours_used": 0.0,
                 "prepaid_hours": 0.0,
+                "prepaid_hours_used": 0.0,
+                "prepaid_hours_total": 0.0,
                 "credit_hours": 0.0,
+                "credit_hours_used": 0.0,
+                "credit_hours_total": 0.0,
                 "credit_available": 0.0,
                 "highest_price": 0.0,
                 "whatsapp_number": whatsapp_number,

+ 688 - 0
helpdesk_extras/data/helpdesk_affected_module_data.xml

@@ -0,0 +1,688 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+    <data noupdate="1">
+        <record id="helpdesk_extras.module_account" model="helpdesk.affected.module">
+            <field name="code">account</field>
+            <field name="name">Invoicing</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Invoices, Payments, Follow-ups &amp; Bank Synchronization</field>
+            <field name="active">True</field>
+        </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="is_main_application">false</field>
+            <field name="description">Invoices, Payments, Follow-ups &amp; Bank synchronization (Enterprise)</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_accountant" model="helpdesk.affected.module">
+            <field name="code">accountant</field>
+            <field name="name">Accounting</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Manage financial and analytic accounting</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_ai" model="helpdesk.affected.module">
+            <field name="code">ai</field>
+            <field name="name">AI Base</field>
+            <field name="is_main_application">false</field>
+            <field name="description">Base module for AI features</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_ai_app" model="helpdesk.affected.module">
+            <field name="code">ai_app</field>
+            <field name="name">AI</field>
+            <field name="is_main_application">true</field>
+            <field name="description">
+        A powerful suite of AI tools and agents
+        integrated directly into your Odoo environment.</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_analytic" model="helpdesk.affected.module">
+            <field name="code">analytic</field>
+            <field name="name">Analytic Accounting</field>
+            <field name="is_main_application">true</field>
+            <field name="active">True</field>
+        </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="is_main_application">true</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_appointment" model="helpdesk.affected.module">
+            <field name="code">appointment</field>
+            <field name="name">Appointments</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Allow people to book meetings in your agenda</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_approvals" model="helpdesk.affected.module">
+            <field name="code">approvals</field>
+            <field name="name">Approvals</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Create and validate approvals requests</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_barcodes" model="helpdesk.affected.module">
+            <field name="code">barcodes</field>
+            <field name="name">Barcode</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Scan and Parse Barcodes</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_base" model="helpdesk.affected.module">
+            <field name="code">base</field>
+            <field name="name">Base</field>
+            <field name="is_main_application">true</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_board" model="helpdesk.affected.module">
+            <field name="code">board</field>
+            <field name="name">Dashboards</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Build your own dashboards</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_bus" model="helpdesk.affected.module">
+            <field name="code">bus</field>
+            <field name="name">IM Bus</field>
+            <field name="is_main_application">true</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_calendar" model="helpdesk.affected.module">
+            <field name="code">calendar</field>
+            <field name="name">Calendar</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Schedule employees' meetings</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_certificate" model="helpdesk.affected.module">
+            <field name="code">certificate</field>
+            <field name="name">Certificate</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Manage certificate</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_cloud_storage" model="helpdesk.affected.module">
+            <field name="code">cloud_storage</field>
+            <field name="name">Cloud Storage</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Store chatter attachments in the cloud</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_contacts" model="helpdesk.affected.module">
+            <field name="code">contacts</field>
+            <field name="name">Contacts</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Centralize your address book</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_crm" model="helpdesk.affected.module">
+            <field name="code">crm</field>
+            <field name="name">CRM</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Track leads and close opportunities</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_databases" model="helpdesk.affected.module">
+            <field name="code">databases</field>
+            <field name="name">Databases</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Manage a fleet of Odoo databases</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_delivery" model="helpdesk.affected.module">
+            <field name="code">delivery</field>
+            <field name="name">Delivery Costs</field>
+            <field name="is_main_application">true</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_digest" model="helpdesk.affected.module">
+            <field name="code">digest</field>
+            <field name="name">KPI Digests</field>
+            <field name="is_main_application">true</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_documents" model="helpdesk.affected.module">
+            <field name="code">documents</field>
+            <field name="name">Documents</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Collect, organize and share documents.</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_equity" model="helpdesk.affected.module">
+            <field name="code">equity</field>
+            <field name="name">Equity</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Manage securities, transactions, and cap tables.</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_esg" model="helpdesk.affected.module">
+            <field name="code">esg</field>
+            <field name="name">ESG</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Calculate and report your company's Environmental, Social, and Governance impact.</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_event" model="helpdesk.affected.module">
+            <field name="code">event</field>
+            <field name="name">Events Organization</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Trainings, Conferences, Meetings, Exhibitions, Registrations</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_fleet" model="helpdesk.affected.module">
+            <field name="code">fleet</field>
+            <field name="name">Fleet</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Manage your fleet and track car costs</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_frontdesk" model="helpdesk.affected.module">
+            <field name="code">frontdesk</field>
+            <field name="name">Frontdesk</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Visitor management system</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_gamification" model="helpdesk.affected.module">
+            <field name="code">gamification</field>
+            <field name="name">Gamification</field>
+            <field name="is_main_application">true</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_helpdesk" model="helpdesk.affected.module">
+            <field name="code">helpdesk</field>
+            <field name="name">Helpdesk</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Track, prioritize, and solve customer tickets</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_hr" model="helpdesk.affected.module">
+            <field name="code">hr</field>
+            <field name="name">Employees</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Centralize employee information</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_hr_attendance" model="helpdesk.affected.module">
+            <field name="code">hr_attendance</field>
+            <field name="name">Attendances</field>
+            <field name="is_main_application">false</field>
+            <field name="description">Track employee attendance</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_hr_contract" model="helpdesk.affected.module">
+            <field name="code">hr_contract</field>
+            <field name="name">Employee Contracts</field>
+            <field name="is_main_application">false</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_hr_expense" model="helpdesk.affected.module">
+            <field name="code">hr_expense</field>
+            <field name="name">Expenses</field>
+            <field name="is_main_application">false</field>
+            <field name="description">Submit, validate and reinvoice employee expenses</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_hr_holidays" model="helpdesk.affected.module">
+            <field name="code">hr_holidays</field>
+            <field name="name">Time Off</field>
+            <field name="is_main_application">false</field>
+            <field name="description">Allocate PTOs and follow leaves requests</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_hr_payroll" model="helpdesk.affected.module">
+            <field name="code">hr_payroll</field>
+            <field name="name">Payroll</field>
+            <field name="is_main_application">false</field>
+            <field name="description">Manage your employee payroll records</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_hr_recruitment" model="helpdesk.affected.module">
+            <field name="code">hr_recruitment</field>
+            <field name="name">Recruitment</field>
+            <field name="is_main_application">false</field>
+            <field name="description">Track your recruitment pipeline</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_hr_timesheet" model="helpdesk.affected.module">
+            <field name="code">hr_timesheet</field>
+            <field name="name">Task Logs</field>
+            <field name="is_main_application">false</field>
+            <field name="description">Track employee time on tasks</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_im_livechat" model="helpdesk.affected.module">
+            <field name="code">im_livechat</field>
+            <field name="name">Live Chat</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Chat with your website visitors</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_industry_fsm" model="helpdesk.affected.module">
+            <field name="code">industry_fsm</field>
+            <field name="name">Field Service</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Schedule and track onsite operations, time and material</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_iot" model="helpdesk.affected.module">
+            <field name="code">iot</field>
+            <field name="name">Internet of Things</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Basic models and helpers to support Internet of Things.</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_knowledge" model="helpdesk.affected.module">
+            <field name="code">knowledge</field>
+            <field name="name">Knowledge</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Centralize, manage, share and grow your knowledge library</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_link_tracker" model="helpdesk.affected.module">
+            <field name="code">link_tracker</field>
+            <field name="name">Link Tracker</field>
+            <field name="is_main_application">true</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_loyalty" model="helpdesk.affected.module">
+            <field name="code">loyalty</field>
+            <field name="name">Coupons &amp; Loyalty</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Use discounts, gift card, eWallets and loyalty programs in different sales channels</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_lunch" model="helpdesk.affected.module">
+            <field name="code">lunch</field>
+            <field name="name">Lunch</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Handle lunch orders of your employees</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_mail" model="helpdesk.affected.module">
+            <field name="code">mail</field>
+            <field name="name">Discuss</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Chat, mail gateway and private channels</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_maintenance" model="helpdesk.affected.module">
+            <field name="code">maintenance</field>
+            <field name="name">Maintenance</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Track equipment and manage maintenance requests</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_marketing_automation" model="helpdesk.affected.module">
+            <field name="code">marketing_automation</field>
+            <field name="name">Marketing Automation</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Build automated mailing campaigns</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_mass_mailing" model="helpdesk.affected.module">
+            <field name="code">mass_mailing</field>
+            <field name="name">Email Marketing</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Design, send and track emails</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_membership" model="helpdesk.affected.module">
+            <field name="code">membership</field>
+            <field name="name">Members</field>
+            <field name="is_main_application">true</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_mrp" model="helpdesk.affected.module">
+            <field name="code">mrp</field>
+            <field name="name">Manufacturing</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Manufacturing Orders &amp; BOMs</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_onboarding" model="helpdesk.affected.module">
+            <field name="code">onboarding</field>
+            <field name="name">Onboarding Toolbox</field>
+            <field name="is_main_application">true</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_partnership" model="helpdesk.affected.module">
+            <field name="code">partnership</field>
+            <field name="name">Partnership / Membership</field>
+            <field name="is_main_application">true</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_payment" model="helpdesk.affected.module">
+            <field name="code">payment</field>
+            <field name="name">Payment Engine</field>
+            <field name="is_main_application">true</field>
+            <field name="description">The payment engine used by payment provider modules.</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_phone_validation" model="helpdesk.affected.module">
+            <field name="code">phone_validation</field>
+            <field name="name">Phone Numbers Validation</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Validate and format phone numbers</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_planning" model="helpdesk.affected.module">
+            <field name="code">planning</field>
+            <field name="name">Planning</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Manage your employees' schedule</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_point_of_sale" model="helpdesk.affected.module">
+            <field name="code">point_of_sale</field>
+            <field name="name">Point of Sale</field>
+            <field name="is_main_application">true</field>
+            <field name="description">User-friendly PoS interface for shops and restaurants</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_portal" model="helpdesk.affected.module">
+            <field name="code">portal</field>
+            <field name="name">Customer Portal</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Customer Portal</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_preventa" model="helpdesk.affected.module">
+            <field name="code">preventa</field>
+            <field name="name">Preventa</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Gestión de preventa para categorías de productos</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_privacy_lookup" model="helpdesk.affected.module">
+            <field name="code">privacy_lookup</field>
+            <field name="name">Privacy</field>
+            <field name="is_main_application">false</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_product" model="helpdesk.affected.module">
+            <field name="code">product</field>
+            <field name="name">Products &amp; Pricelists</field>
+            <field name="is_main_application">true</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_project" model="helpdesk.affected.module">
+            <field name="code">project</field>
+            <field name="name">Project</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Organize and plan your projects</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_purchase" model="helpdesk.affected.module">
+            <field name="code">purchase</field>
+            <field name="name">Purchase</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Purchase orders, tenders and agreements</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_quality" model="helpdesk.affected.module">
+            <field name="code">quality</field>
+            <field name="name">Quality Base</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Basic Feature for Quality</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_quality_control" model="helpdesk.affected.module">
+            <field name="code">quality_control</field>
+            <field name="name">Quality</field>
+            <field name="is_main_application">false</field>
+            <field name="description">Control the quality of your products</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_rating" model="helpdesk.affected.module">
+            <field name="code">rating</field>
+            <field name="name">Customer Rating</field>
+            <field name="is_main_application">true</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_repair" model="helpdesk.affected.module">
+            <field name="code">repair</field>
+            <field name="name">Repairs</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Repair damaged products</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_resource" model="helpdesk.affected.module">
+            <field name="code">resource</field>
+            <field name="name">Resource</field>
+            <field name="is_main_application">true</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_room" model="helpdesk.affected.module">
+            <field name="code">room</field>
+            <field name="name">Meeting Rooms</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Manage Meeting Rooms</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_rpc" model="helpdesk.affected.module">
+            <field name="code">rpc</field>
+            <field name="name">RPC endpoints</field>
+            <field name="is_main_application">true</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_sale" model="helpdesk.affected.module">
+            <field name="code">sale</field>
+            <field name="name">Sales</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Sales internal machinery</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_sale_management" model="helpdesk.affected.module">
+            <field name="code">sale_management</field>
+            <field name="name">Sales Management</field>
+            <field name="is_main_application">false</field>
+            <field name="description">From quotations to invoices</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_sales_team" model="helpdesk.affected.module">
+            <field name="code">sales_team</field>
+            <field name="name">Sales Teams</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Sales Teams</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_sign" model="helpdesk.affected.module">
+            <field name="code">sign</field>
+            <field name="name">Sign</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Send documents to sign online and handle filled copies</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_sms" model="helpdesk.affected.module">
+            <field name="code">sms</field>
+            <field name="name">SMS gateway</field>
+            <field name="is_main_application">true</field>
+            <field name="description">SMS Text Messaging</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_snailmail" model="helpdesk.affected.module">
+            <field name="code">snailmail</field>
+            <field name="name">Snail Mail</field>
+            <field name="is_main_application">true</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_social" model="helpdesk.affected.module">
+            <field name="code">social</field>
+            <field name="name">Social Marketing</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Manage your social media and website visitors</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_spreadsheet" model="helpdesk.affected.module">
+            <field name="code">spreadsheet</field>
+            <field name="name">Spreadsheet</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Spreadsheet</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_stock" model="helpdesk.affected.module">
+            <field name="code">stock</field>
+            <field name="name">Inventory</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Manage your stock and logistics activities</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_stock_account" model="helpdesk.affected.module">
+            <field name="code">stock_account</field>
+            <field name="name">WMS Accounting</field>
+            <field name="is_main_application">false</field>
+            <field name="description">Inventory, Logistic, Valuation, Accounting</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_survey" model="helpdesk.affected.module">
+            <field name="code">survey</field>
+            <field name="name">Surveys</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Send your surveys or share them live.</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_timer" model="helpdesk.affected.module">
+            <field name="code">timer</field>
+            <field name="name">Timer</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Record time</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_timesheet_grid" model="helpdesk.affected.module">
+            <field name="code">timesheet_grid</field>
+            <field name="name">Timesheets</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Track employee time on tasks</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_transifex" model="helpdesk.affected.module">
+            <field name="code">transifex</field>
+            <field name="name">Transifex integration</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Add a link to edit a translation in Transifex</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_uom" model="helpdesk.affected.module">
+            <field name="code">uom</field>
+            <field name="name">Units of measure</field>
+            <field name="is_main_application">true</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_utm" model="helpdesk.affected.module">
+            <field name="code">utm</field>
+            <field name="name">UTM Trackers</field>
+            <field name="is_main_application">true</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_voip" model="helpdesk.affected.module">
+            <field name="code">voip</field>
+            <field name="name">VoIP</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Make and receive phone calls from within Odoo.</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_web" model="helpdesk.affected.module">
+            <field name="code">web</field>
+            <field name="name">Web</field>
+            <field name="is_main_application">true</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_web_cohort" model="helpdesk.affected.module">
+            <field name="code">web_cohort</field>
+            <field name="name">Cohort View</field>
+            <field name="is_main_application">false</field>
+            <field name="description">Basic Cohort view for odoo</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_web_editor" model="helpdesk.affected.module">
+            <field name="code">web_editor</field>
+            <field name="name">Web Editor</field>
+            <field name="is_main_application">false</field>
+            <field name="active">True</field>
+        </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="is_main_application">false</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_web_gantt" model="helpdesk.affected.module">
+            <field name="code">web_gantt</field>
+            <field name="name">Web Gantt</field>
+            <field name="is_main_application">false</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_web_grid" model="helpdesk.affected.module">
+            <field name="code">web_grid</field>
+            <field name="name">Grid View</field>
+            <field name="is_main_application">false</field>
+            <field name="description">Basic 2D Grid view for odoo</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_web_hierarchy" model="helpdesk.affected.module">
+            <field name="code">web_hierarchy</field>
+            <field name="name">Web Hierarchy</field>
+            <field name="is_main_application">false</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_web_map" model="helpdesk.affected.module">
+            <field name="code">web_map</field>
+            <field name="name">Map View</field>
+            <field name="is_main_application">false</field>
+            <field name="description">Defines the map view for odoo enterprise</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_web_mobile" model="helpdesk.affected.module">
+            <field name="code">web_mobile</field>
+            <field name="name">Mobile</field>
+            <field name="is_main_application">false</field>
+            <field name="description">Odoo Mobile Core module</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_web_studio" model="helpdesk.affected.module">
+            <field name="code">web_studio</field>
+            <field name="name">Studio</field>
+            <field name="is_main_application">false</field>
+            <field name="description">Create and customize your Odoo apps</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_web_tour" model="helpdesk.affected.module">
+            <field name="code">web_tour</field>
+            <field name="name">Tours</field>
+            <field name="is_main_application">false</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_web_unsplash" model="helpdesk.affected.module">
+            <field name="code">web_unsplash</field>
+            <field name="name">Unsplash Image Library</field>
+            <field name="is_main_application">false</field>
+            <field name="description">Find free high-resolution images from Unsplash</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_website" model="helpdesk.affected.module">
+            <field name="code">website</field>
+            <field name="name">Website</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Enterprise website builder</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_whatsapp" model="helpdesk.affected.module">
+            <field name="code">whatsapp</field>
+            <field name="name">WhatsApp Messaging</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Text your Contacts on WhatsApp</field>
+            <field name="active">True</field>
+        </record>
+        <record id="helpdesk_extras.module_worksheet" model="helpdesk.affected.module">
+            <field name="code">worksheet</field>
+            <field name="name">Worksheet</field>
+            <field name="is_main_application">true</field>
+            <field name="description">Create customizable worksheet</field>
+            <field name="active">True</field>
+        </record>
+    </data>
+</odoo>

+ 2 - 1
helpdesk_extras/data/helpdesk_form_data.xml

@@ -6,10 +6,11 @@
         <value eval="[
             'request_type_id',
             'affected_module_id',
+            'affected_user_id',
             'business_impact',
             'reproduce_steps',
             'business_goal',
-            'client_authorization',
+            'client_authorization'
         ]"/>
     </function>
 </odoo>

+ 127 - 0
helpdesk_extras/migrations/18.0.1.0.4/post-migration.py

@@ -0,0 +1,127 @@
+from odoo import api, SUPERUSER_ID
+
+def migrate(cr, version):
+    env = api.Environment(cr, SUPERUSER_ID, {})
+    
+    # Load translations for affected modules
+    modules_translations = {
+        'account': {'en_US': 'Invoicing', 'es_ES': 'Invoicing'},
+        'account_accountant': {'en_US': 'Invoicing', 'es_ES': 'Facturación / Contabilidad'},
+        'accountant': {'en_US': 'Accounting', 'es_ES': 'Accounting'},
+        'ai': {'en_US': 'AI', 'es_ES': 'AI'},
+        'ai_app': {'en_US': 'AI', 'es_ES': 'AI'},
+        'analytic': {'en_US': 'Analytic Accounting', 'es_ES': 'Contabilidad analítica'},
+        'analytic_enterprise': {'en_US': 'Analytic Accounting Enterprise', 'es_ES': 'Contabilidad analítica empresarial'},
+        'appointment': {'en_US': 'Appointments', 'es_ES': 'Citas'},
+        'approvals': {'en_US': 'Approvals', 'es_ES': 'Aprobaciones'},
+        'barcodes': {'en_US': 'Barcode', 'es_ES': 'Barcode'},
+        'base': {'en_US': 'Base', 'es_ES': 'Base'},
+        'board': {'en_US': 'Dashboards', 'es_ES': 'Tableros'},
+        'bus': {'en_US': 'IM Bus', 'es_ES': 'Bus IM'},
+        'calendar': {'en_US': 'Calendar', 'es_ES': 'Calendario'},
+        'certificate': {'en_US': 'Certificate', 'es_ES': 'Certificado'},
+        'cloud_storage': {'en_US': 'Cloud Storage', 'es_ES': 'Almacenamiento en la nube'},
+        'contacts': {'en_US': 'Contacts', 'es_ES': 'Contacts'},
+        'crm': {'en_US': 'CRM', 'es_ES': 'CRM'},
+        'databases': {'en_US': 'Databases', 'es_ES': 'Databases'},
+        'delivery': {'en_US': 'Delivery Costs', 'es_ES': 'Gastos de envío'},
+        'digest': {'en_US': 'KPI Digests', 'es_ES': 'Resúmenes de KPI'},
+        'documents': {'en_US': 'Documents', 'es_ES': 'Documentos'},
+        'equity': {'en_US': 'Equity', 'es_ES': 'Equity'},
+        'esg': {'en_US': 'ESG', 'es_ES': 'ESG'},
+        'event': {'en_US': 'Events Organization', 'es_ES': 'Organización de eventos'},
+        'fleet': {'en_US': 'Fleet', 'es_ES': 'Flota'},
+        'frontdesk': {'en_US': 'Frontdesk', 'es_ES': 'Recepción'},
+        'gamification': {'en_US': 'Gamification', 'es_ES': 'Ludificación'},
+        'helpdesk': {'en_US': 'Helpdesk', 'es_ES': 'Servicio de asistencia'},
+        'hr': {'en_US': 'Employees', 'es_ES': 'Employees'},
+        'hr_attendance': {'en_US': 'Attendances', 'es_ES': 'Asistencias'},
+        'hr_contract': {'en_US': 'Employee Contracts', 'es_ES': 'Contratos de los empleados'},
+        'hr_expense': {'en_US': 'Expenses', 'es_ES': 'Gastos'},
+        'hr_holidays': {'en_US': 'Time Off', 'es_ES': 'Ausencias'},
+        'hr_payroll': {'en_US': 'Payroll', 'es_ES': 'Nómina'},
+        'hr_recruitment': {'en_US': 'Recruitment', 'es_ES': 'Reclutamiento'},
+        'hr_timesheet': {'en_US': 'Task Logs', 'es_ES': 'Registros de tareas'},
+        'im_livechat': {'en_US': 'Live Chat', 'es_ES': 'Chat en directo'},
+        'industry_fsm': {'en_US': 'Field Service', 'es_ES': 'Servicio de campo'},
+        'iot': {'en_US': 'Internet of Things', 'es_ES': 'Internet de las cosas'},
+        'knowledge': {'en_US': 'Knowledge', 'es_ES': 'Información'},
+        'link_tracker': {'en_US': 'Link Tracker', 'es_ES': 'Link Tracker'},
+        'loyalty': {'en_US': 'Coupons & Loyalty', 'es_ES': 'Cupones y fidelidad'},
+        'lunch': {'en_US': 'Lunch', 'es_ES': 'Comida'},
+        'mail': {'en_US': 'Discuss', 'es_ES': 'Conversaciones'},
+        'maintenance': {'en_US': 'Maintenance', 'es_ES': 'Mantenimiento'},
+        'marketing_automation': {'en_US': 'Marketing Automation', 'es_ES': 'Automatización de marketing'},
+        'mass_mailing': {'en_US': 'Email Marketing', 'es_ES': 'Marketing por correo electrónico'},
+        'membership': {'en_US': 'Members', 'es_ES': 'Miembros'},
+        'mrp': {'en_US': 'Manufacturing', 'es_ES': 'Manufacturing'},
+        'onboarding': {'en_US': 'Onboarding Toolbox', 'es_ES': 'Caja de herramientas para la incorporación'},
+        'partnership': {'en_US': 'Partnership / Membership', 'es_ES': 'Partnership / Membership'},
+        'payment': {'en_US': 'Payment Engine', 'es_ES': 'Motor de pago'},
+        'phone_validation': {'en_US': 'Phone Numbers Validation', 'es_ES': 'Validación de números de teléfono'},
+        'planning': {'en_US': 'Planning', 'es_ES': 'Planificación'},
+        'point_of_sale': {'en_US': 'Point of Sale', 'es_ES': 'Punto de venta'},
+        'portal': {'en_US': 'Customer Portal', 'es_ES': 'Customer Portal'},
+        'preventa': {'en_US': 'Preventa', 'es_ES': 'Preventa'},
+        'privacy_lookup': {'en_US': 'Privacy', 'es_ES': 'Privacidad'},
+        'product': {'en_US': 'Products & Pricelists', 'es_ES': 'Productos y listas de precios'},
+        'project': {'en_US': 'Project', 'es_ES': 'Proyecto'},
+        'purchase': {'en_US': 'Purchase', 'es_ES': 'Purchase'},
+        'quality': {'en_US': 'Quality Base', 'es_ES': 'Base de calidad '},
+        'quality_control': {'en_US': 'Quality', 'es_ES': 'Calidad'},
+        'rating': {'en_US': 'Customer Rating', 'es_ES': 'Valoración del cliente'},
+        'repair': {'en_US': 'Repairs', 'es_ES': 'Reparaciones'},
+        'resource': {'en_US': 'Resource', 'es_ES': 'Recurso'},
+        'room': {'en_US': 'Meeting Rooms', 'es_ES': 'Sala de reuniones'},
+        'rpc': {'en_US': 'RPC endpoints', 'es_ES': 'RPC endpoints'},
+        'sale': {'en_US': 'Sales', 'es_ES': 'Sales'},
+        'sale_management': {'en_US': 'Sales', 'es_ES': 'Sales'},
+        'sales_team': {'en_US': 'Sales Teams', 'es_ES': 'Sales Teams'},
+        'sign': {'en_US': 'Sign', 'es_ES': 'Firma electrónica'},
+        'sms': {'en_US': 'SMS gateway', 'es_ES': 'Puerta de enlace SMS'},
+        'snailmail': {'en_US': 'Snail Mail', 'es_ES': 'Correo postal'},
+        'social': {'en_US': 'Social Marketing', 'es_ES': 'Marketing social'},
+        'spreadsheet': {'en_US': 'Spreadsheet', 'es_ES': 'Spreadsheet'},
+        'stock': {'en_US': 'Inventory', 'es_ES': 'Inventory'},
+        'stock_account': {'en_US': 'WMS Accounting', 'es_ES': 'Contabilidad del SGA'},
+        'survey': {'en_US': 'Surveys', 'es_ES': 'Surveys'},
+        'timer': {'en_US': 'Timer', 'es_ES': 'Temporizador'},
+        'timesheet_grid': {'en_US': 'Timesheets', 'es_ES': 'Partes de horas'},
+        'transifex': {'en_US': 'Transifex integration', 'es_ES': 'Integración en Transifex'},
+        'uom': {'en_US': 'Units of measure', 'es_ES': 'Unidades de medida'},
+        'utm': {'en_US': 'UTM Trackers', 'es_ES': 'Rastreadores UTM'},
+        'voip': {'en_US': 'VoIP', 'es_ES': 'VoIP'},
+        'web': {'en_US': 'Web', 'es_ES': 'Web'},
+        'web_cohort': {'en_US': 'Cohort View', 'es_ES': 'Vista de cohorte'},
+        'web_editor': {'en_US': 'Web Editor', 'es_ES': 'Editor web'},
+        'web_enterprise': {'en_US': 'Web Enterprise', 'es_ES': 'Web de Enterprise'},
+        'web_gantt': {'en_US': 'Web Gantt', 'es_ES': 'Diagrama Gantt web'},
+        'web_grid': {'en_US': 'Grid View', 'es_ES': 'Vista de cuadrícula'},
+        'web_hierarchy': {'en_US': 'Web Hierarchy', 'es_ES': 'Jerarquía web'},
+        'web_map': {'en_US': 'Map View', 'es_ES': 'Vista del mapa'},
+        'web_mobile': {'en_US': 'Mobile', 'es_ES': 'Mobile'},
+        'web_studio': {'en_US': 'Studio', 'es_ES': 'Studio'},
+        'web_tour': {'en_US': 'Tours', 'es_ES': 'Recorridos'},
+        'web_unsplash': {'en_US': 'Unsplash Image Library', 'es_ES': 'Biblioteca de imágenes de Unsplash'},
+        'website': {'en_US': 'Website', 'es_ES': 'Website'},
+        'whatsapp': {'en_US': 'WhatsApp Messaging', 'es_ES': 'Mensajes de WhatsApp'},
+        'worksheet': {'en_US': 'Worksheet', 'es_ES': 'Hoja de trabajo'},
+    }
+    
+    # Ensure Spanish language is installed
+    es_lang = env['res.lang'].search([('code', '=', 'es_ES')], limit=1)
+    if not es_lang:
+        env['res.lang']._activate_lang('es_ES')
+    
+    # Get all affected modules
+    affected_modules = env['helpdesk.affected.module'].search([])
+    
+    for module in affected_modules:
+        if module.code in modules_translations:
+            translations = modules_translations[module.code]
+            # Set English name first (base language) - this ensures en_US is set
+            module.with_context(lang='en_US').write({'name': translations['en_US']})
+            # Update Spanish translation using write with context
+            if 'es_ES' in translations and translations['es_ES']:
+                # Use write with context to set translation
+                module.with_context(lang='es_ES').write({'name': translations['es_ES']})

+ 127 - 0
helpdesk_extras/migrations/18.0.1.0.5/post-migration.py

@@ -0,0 +1,127 @@
+from odoo import api, SUPERUSER_ID
+
+def migrate(cr, version):
+    env = api.Environment(cr, SUPERUSER_ID, {})
+    
+    # Load translations for affected modules
+    modules_translations = {
+        'account': {'en_US': 'Invoicing', 'es_ES': 'Invoicing'},
+        'account_accountant': {'en_US': 'Invoicing', 'es_ES': 'Facturación / Contabilidad'},
+        'accountant': {'en_US': 'Accounting', 'es_ES': 'Accounting'},
+        'ai': {'en_US': 'AI', 'es_ES': 'AI'},
+        'ai_app': {'en_US': 'AI', 'es_ES': 'AI'},
+        'analytic': {'en_US': 'Analytic Accounting', 'es_ES': 'Contabilidad analítica'},
+        'analytic_enterprise': {'en_US': 'Analytic Accounting Enterprise', 'es_ES': 'Contabilidad analítica empresarial'},
+        'appointment': {'en_US': 'Appointments', 'es_ES': 'Citas'},
+        'approvals': {'en_US': 'Approvals', 'es_ES': 'Aprobaciones'},
+        'barcodes': {'en_US': 'Barcode', 'es_ES': 'Barcode'},
+        'base': {'en_US': 'Base', 'es_ES': 'Base'},
+        'board': {'en_US': 'Dashboards', 'es_ES': 'Tableros'},
+        'bus': {'en_US': 'IM Bus', 'es_ES': 'Bus IM'},
+        'calendar': {'en_US': 'Calendar', 'es_ES': 'Calendario'},
+        'certificate': {'en_US': 'Certificate', 'es_ES': 'Certificado'},
+        'cloud_storage': {'en_US': 'Cloud Storage', 'es_ES': 'Almacenamiento en la nube'},
+        'contacts': {'en_US': 'Contacts', 'es_ES': 'Contacts'},
+        'crm': {'en_US': 'CRM', 'es_ES': 'CRM'},
+        'databases': {'en_US': 'Databases', 'es_ES': 'Databases'},
+        'delivery': {'en_US': 'Delivery Costs', 'es_ES': 'Gastos de envío'},
+        'digest': {'en_US': 'KPI Digests', 'es_ES': 'Resúmenes de KPI'},
+        'documents': {'en_US': 'Documents', 'es_ES': 'Documentos'},
+        'equity': {'en_US': 'Equity', 'es_ES': 'Equity'},
+        'esg': {'en_US': 'ESG', 'es_ES': 'ESG'},
+        'event': {'en_US': 'Events Organization', 'es_ES': 'Organización de eventos'},
+        'fleet': {'en_US': 'Fleet', 'es_ES': 'Flota'},
+        'frontdesk': {'en_US': 'Frontdesk', 'es_ES': 'Recepción'},
+        'gamification': {'en_US': 'Gamification', 'es_ES': 'Ludificación'},
+        'helpdesk': {'en_US': 'Helpdesk', 'es_ES': 'Servicio de asistencia'},
+        'hr': {'en_US': 'Employees', 'es_ES': 'Employees'},
+        'hr_attendance': {'en_US': 'Attendances', 'es_ES': 'Asistencias'},
+        'hr_contract': {'en_US': 'Employee Contracts', 'es_ES': 'Contratos de los empleados'},
+        'hr_expense': {'en_US': 'Expenses', 'es_ES': 'Gastos'},
+        'hr_holidays': {'en_US': 'Time Off', 'es_ES': 'Ausencias'},
+        'hr_payroll': {'en_US': 'Payroll', 'es_ES': 'Nómina'},
+        'hr_recruitment': {'en_US': 'Recruitment', 'es_ES': 'Reclutamiento'},
+        'hr_timesheet': {'en_US': 'Task Logs', 'es_ES': 'Registros de tareas'},
+        'im_livechat': {'en_US': 'Live Chat', 'es_ES': 'Chat en directo'},
+        'industry_fsm': {'en_US': 'Field Service', 'es_ES': 'Servicio de campo'},
+        'iot': {'en_US': 'Internet of Things', 'es_ES': 'Internet de las cosas'},
+        'knowledge': {'en_US': 'Knowledge', 'es_ES': 'Información'},
+        'link_tracker': {'en_US': 'Link Tracker', 'es_ES': 'Link Tracker'},
+        'loyalty': {'en_US': 'Coupons & Loyalty', 'es_ES': 'Cupones y fidelidad'},
+        'lunch': {'en_US': 'Lunch', 'es_ES': 'Comida'},
+        'mail': {'en_US': 'Discuss', 'es_ES': 'Conversaciones'},
+        'maintenance': {'en_US': 'Maintenance', 'es_ES': 'Mantenimiento'},
+        'marketing_automation': {'en_US': 'Marketing Automation', 'es_ES': 'Automatización de marketing'},
+        'mass_mailing': {'en_US': 'Email Marketing', 'es_ES': 'Marketing por correo electrónico'},
+        'membership': {'en_US': 'Members', 'es_ES': 'Miembros'},
+        'mrp': {'en_US': 'Manufacturing', 'es_ES': 'Manufacturing'},
+        'onboarding': {'en_US': 'Onboarding Toolbox', 'es_ES': 'Caja de herramientas para la incorporación'},
+        'partnership': {'en_US': 'Partnership / Membership', 'es_ES': 'Partnership / Membership'},
+        'payment': {'en_US': 'Payment Engine', 'es_ES': 'Motor de pago'},
+        'phone_validation': {'en_US': 'Phone Numbers Validation', 'es_ES': 'Validación de números de teléfono'},
+        'planning': {'en_US': 'Planning', 'es_ES': 'Planificación'},
+        'point_of_sale': {'en_US': 'Point of Sale', 'es_ES': 'Punto de venta'},
+        'portal': {'en_US': 'Customer Portal', 'es_ES': 'Customer Portal'},
+        'preventa': {'en_US': 'Preventa', 'es_ES': 'Preventa'},
+        'privacy_lookup': {'en_US': 'Privacy', 'es_ES': 'Privacidad'},
+        'product': {'en_US': 'Products & Pricelists', 'es_ES': 'Productos y listas de precios'},
+        'project': {'en_US': 'Project', 'es_ES': 'Proyecto'},
+        'purchase': {'en_US': 'Purchase', 'es_ES': 'Purchase'},
+        'quality': {'en_US': 'Quality Base', 'es_ES': 'Base de calidad '},
+        'quality_control': {'en_US': 'Quality', 'es_ES': 'Calidad'},
+        'rating': {'en_US': 'Customer Rating', 'es_ES': 'Valoración del cliente'},
+        'repair': {'en_US': 'Repairs', 'es_ES': 'Reparaciones'},
+        'resource': {'en_US': 'Resource', 'es_ES': 'Recurso'},
+        'room': {'en_US': 'Meeting Rooms', 'es_ES': 'Sala de reuniones'},
+        'rpc': {'en_US': 'RPC endpoints', 'es_ES': 'RPC endpoints'},
+        'sale': {'en_US': 'Sales', 'es_ES': 'Sales'},
+        'sale_management': {'en_US': 'Sales', 'es_ES': 'Sales'},
+        'sales_team': {'en_US': 'Sales Teams', 'es_ES': 'Sales Teams'},
+        'sign': {'en_US': 'Sign', 'es_ES': 'Firma electrónica'},
+        'sms': {'en_US': 'SMS gateway', 'es_ES': 'Puerta de enlace SMS'},
+        'snailmail': {'en_US': 'Snail Mail', 'es_ES': 'Correo postal'},
+        'social': {'en_US': 'Social Marketing', 'es_ES': 'Marketing social'},
+        'spreadsheet': {'en_US': 'Spreadsheet', 'es_ES': 'Spreadsheet'},
+        'stock': {'en_US': 'Inventory', 'es_ES': 'Inventory'},
+        'stock_account': {'en_US': 'WMS Accounting', 'es_ES': 'Contabilidad del SGA'},
+        'survey': {'en_US': 'Surveys', 'es_ES': 'Surveys'},
+        'timer': {'en_US': 'Timer', 'es_ES': 'Temporizador'},
+        'timesheet_grid': {'en_US': 'Timesheets', 'es_ES': 'Partes de horas'},
+        'transifex': {'en_US': 'Transifex integration', 'es_ES': 'Integración en Transifex'},
+        'uom': {'en_US': 'Units of measure', 'es_ES': 'Unidades de medida'},
+        'utm': {'en_US': 'UTM Trackers', 'es_ES': 'Rastreadores UTM'},
+        'voip': {'en_US': 'VoIP', 'es_ES': 'VoIP'},
+        'web': {'en_US': 'Web', 'es_ES': 'Web'},
+        'web_cohort': {'en_US': 'Cohort View', 'es_ES': 'Vista de cohorte'},
+        'web_editor': {'en_US': 'Web Editor', 'es_ES': 'Editor web'},
+        'web_enterprise': {'en_US': 'Web Enterprise', 'es_ES': 'Web de Enterprise'},
+        'web_gantt': {'en_US': 'Web Gantt', 'es_ES': 'Diagrama Gantt web'},
+        'web_grid': {'en_US': 'Grid View', 'es_ES': 'Vista de cuadrícula'},
+        'web_hierarchy': {'en_US': 'Web Hierarchy', 'es_ES': 'Jerarquía web'},
+        'web_map': {'en_US': 'Map View', 'es_ES': 'Vista del mapa'},
+        'web_mobile': {'en_US': 'Mobile', 'es_ES': 'Mobile'},
+        'web_studio': {'en_US': 'Studio', 'es_ES': 'Studio'},
+        'web_tour': {'en_US': 'Tours', 'es_ES': 'Recorridos'},
+        'web_unsplash': {'en_US': 'Unsplash Image Library', 'es_ES': 'Biblioteca de imágenes de Unsplash'},
+        'website': {'en_US': 'Website', 'es_ES': 'Website'},
+        'whatsapp': {'en_US': 'WhatsApp Messaging', 'es_ES': 'Mensajes de WhatsApp'},
+        'worksheet': {'en_US': 'Worksheet', 'es_ES': 'Hoja de trabajo'},
+    }
+    
+    # Ensure Spanish language is installed
+    es_lang = env['res.lang'].search([('code', '=', 'es_ES')], limit=1)
+    if not es_lang:
+        env['res.lang']._activate_lang('es_ES')
+    
+    # Get all affected modules
+    affected_modules = env['helpdesk.affected.module'].search([])
+    
+    for module in affected_modules:
+        if module.code in modules_translations:
+            translations = modules_translations[module.code]
+            # Set English name first (base language) - this ensures en_US is set
+            module.with_context(lang='en_US').write({'name': translations['en_US']})
+            # Update Spanish translation using write with context
+            if 'es_ES' in translations and translations['es_ES']:
+                # Use write with context to set translation
+                module.with_context(lang='es_ES').write({'name': translations['es_ES']})

+ 81 - 0
helpdesk_extras/migrations/18.0.1.0.6/post-migration.py

@@ -0,0 +1,81 @@
+from odoo import api, SUPERUSER_ID
+import logging
+
+_logger = logging.getLogger(__name__)
+
+def migrate(cr, version):
+    env = api.Environment(cr, SUPERUSER_ID, {})
+    
+    # Eliminar la restricción de clave foránea problemática
+    # El campo visibility_condition_m2o_id es polimórfico y puede referenciar diferentes modelos
+    # por lo que no debe tener una restricción de clave foránea fija
+    try:
+        cr.execute("""
+            ALTER TABLE helpdesk_template_field 
+            DROP CONSTRAINT IF EXISTS helpdesk_template_field_visibility_condition_m2o_id_fkey
+        """)
+        _logger.info("Restricción de clave foránea eliminada")
+    except Exception as e:
+        _logger.warning(f"No se pudo eliminar la restricción (puede que ya no exista): {e}")
+    
+    # Migrar valores antiguos de ir.module.module a helpdesk.affected.module
+    # Buscar campos de template que usen affected_module_id como dependencia
+    cr.execute("""
+        SELECT 
+            htf.id,
+            htf.visibility_condition,
+            htf.visibility_condition_m2o_id,
+            imf.id as field_id,
+            imf.relation as relation_model
+        FROM helpdesk_template_field htf
+        JOIN ir_model_fields imf ON htf.visibility_dependency = imf.id
+        WHERE imf.name = 'affected_module_id'
+          AND imf.model = 'helpdesk.ticket'
+          AND (htf.visibility_condition_m2o_id IS NOT NULL 
+               OR (htf.visibility_condition IS NOT NULL AND htf.visibility_condition ~ '^[0-9]+$'))
+    """)
+    
+    records_to_migrate = cr.fetchall()
+    migrated_count = 0
+    
+    if records_to_migrate:
+        # Migrar cada registro
+        for record_id, condition, m2o_id, field_id, relation_model in records_to_migrate:
+            try:
+                old_id = m2o_id or (int(condition) if condition and condition.isdigit() else None)
+                
+                if old_id and relation_model == 'ir.module.module':
+                    # Buscar el módulo antiguo
+                    old_module = env['ir.module.module'].browse(old_id)
+                    if old_module.exists():
+                        module_code = old_module.name
+                        # Buscar el equivalente en el nuevo catálogo
+                        new_module = env['helpdesk.affected.module'].search([
+                            ('code', '=', module_code)
+                        ], limit=1)
+                        
+                        if new_module:
+                            # Actualizar el ID
+                            new_id = new_module.id
+                            cr.execute("""
+                                UPDATE helpdesk_template_field
+                                SET visibility_condition_m2o_id = %s,
+                                    visibility_condition = %s
+                                WHERE id = %s
+                            """, (new_id, str(new_id), record_id))
+                            migrated_count += 1
+                        else:
+                            # Si no existe en el nuevo catálogo, limpiar el valor
+                            cr.execute("""
+                                UPDATE helpdesk_template_field
+                                SET visibility_condition_m2o_id = NULL,
+                                    visibility_condition = NULL
+                                WHERE id = %s
+                            """, (record_id,))
+                            migrated_count += 1
+            except Exception as e:
+                _logger.warning(f"Error migrando registro {record_id}: {e}")
+                continue
+    
+    cr.commit()
+    _logger.info(f"Migración completada: {migrated_count} registros procesados de {len(records_to_migrate)} encontrados")

+ 40 - 0
helpdesk_extras/migrations/18.0.1.0.7/post-migration.py

@@ -0,0 +1,40 @@
+from odoo import api, SUPERUSER_ID
+import logging
+
+_logger = logging.getLogger(__name__)
+
+def migrate(cr, version):
+    env = api.Environment(cr, SUPERUSER_ID, {})
+    
+    # Corregir duplicados: asegurar que solo las aplicaciones principales tengan is_main_application=True
+    # y que no haya duplicados por nombre
+    
+    # 1. Corregir módulo 'ai' - no debe ser aplicación principal
+    ai_module = env['helpdesk.affected.module'].search([('code', '=', 'ai')], limit=1)
+    if ai_module:
+        ai_module.write({
+            'name': 'AI Base',
+            'is_main_application': False
+        })
+        _logger.info("Módulo 'ai' actualizado: marcado como no principal")
+    
+    # 2. Corregir account_accountant - no debe ser aplicación principal
+    account_accountant = env['helpdesk.affected.module'].search([('code', '=', 'account_accountant')], limit=1)
+    if account_accountant and account_accountant.is_main_application:
+        account_accountant.write({
+            'name': 'Invoicing (Enterprise)',
+            'is_main_application': False
+        })
+        _logger.info("Módulo 'account_accountant' actualizado: marcado como no principal")
+    
+    # 3. Corregir sale_management - no debe ser aplicación principal
+    sale_management = env['helpdesk.affected.module'].search([('code', '=', 'sale_management')], limit=1)
+    if sale_management and sale_management.is_main_application:
+        sale_management.write({
+            'name': 'Sales Management',
+            'is_main_application': False
+        })
+        _logger.info("Módulo 'sale_management' actualizado: marcado como no principal")
+    
+    cr.commit()
+    _logger.info("Migración de duplicados completada")

+ 53 - 0
helpdesk_extras/migrations/18.0.1.0.8/post-migration.py

@@ -0,0 +1,53 @@
+from odoo import api, SUPERUSER_ID
+import logging
+
+_logger = logging.getLogger(__name__)
+
+def migrate(cr, version):
+    env = api.Environment(cr, SUPERUSER_ID, {})
+    
+    # Verificar si el campo ya existe
+    cr.execute("""
+        SELECT id FROM ir_model_fields 
+        WHERE model = 'helpdesk.ticket' AND name = 'affected_user_email'
+    """)
+    existing = cr.fetchone()
+    
+    if existing:
+        _logger.info("Campo affected_user_email ya existe en ir.model.fields")
+        # Asegurar que no esté en la lista negra
+        cr.execute("""
+            UPDATE ir_model_fields 
+            SET website_form_blacklisted = false
+            WHERE id = %s
+        """, (existing[0],))
+        _logger.info("Campo actualizado: website_form_blacklisted = false")
+    else:
+        # Obtener el modelo
+        cr.execute("""
+            SELECT id FROM ir_model WHERE model = 'helpdesk.ticket'
+        """)
+        model_result = cr.fetchone()
+        
+        if model_result:
+            model_id = model_result[0]
+            # Crear el campo usando el ORM
+            try:
+                field_vals = {
+                    'model_id': model_id,
+                    'name': 'affected_user_email',
+                    'field_description': 'Affected User',
+                    'ttype': 'char',
+                    'model': 'helpdesk.ticket',
+                    'help': 'Email address of the affected user (from another Odoo instance)',
+                    'website_form_blacklisted': False,
+                    'state': 'base',
+                }
+                field = env['ir.model.fields'].sudo().create(field_vals)
+                _logger.info(f"Campo affected_user_email creado con ID: {field.id}")
+            except Exception as e:
+                _logger.error(f"Error al crear campo con ORM: {e}")
+        else:
+            _logger.error("Modelo helpdesk.ticket no encontrado")
+    
+    cr.commit()

+ 1 - 0
helpdesk_extras/models/__init__.py

@@ -8,3 +8,4 @@ from . import helpdesk_template
 from . import helpdesk_workflow_template
 from . import helpdesk_workflow_template_stage
 from . import helpdesk_workflow_template_sla
+from . import helpdesk_affected_module

+ 51 - 0
helpdesk_extras/models/helpdesk_affected_module.py

@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models, _
+
+
+class HelpdeskAffectedModule(models.Model):
+    _name = 'helpdesk.affected.module'
+    _description = 'Affected Module Catalog'
+    _rec_name = 'name'
+    _order = 'name'
+
+    name = fields.Char(
+        string='Name',
+        required=True,
+        translate=True,
+        help="Module name (translatable)"
+    )
+    code = fields.Char(
+        string='Code',
+        required=True,
+        index=True,
+        help="Technical code of the module (e.g., 'account', 'sale')"
+    )
+    active = fields.Boolean(
+        string='Active',
+        default=True,
+        help="If unchecked, this module will be hidden from selection"
+    )
+    is_main_application = fields.Boolean(
+        string='Main Application',
+        default=False,
+        help="Check if this is a main Odoo application (not an extension module)"
+    )
+    description = fields.Text(
+        string='Description',
+        help="Module description"
+    )
+
+    _sql_constraints = [
+        ('code_unique', 'UNIQUE(code)', 'The module code must be unique!'),
+    ]
+
+    def name_get(self):
+        """Override name_get to show translated name"""
+        # Odoo automatically handles translation based on context lang
+        # when field has translate=True, so we just return the name
+        result = []
+        for record in self:
+            result.append((record.id, record.name))
+        return result

+ 4 - 5
helpdesk_extras/models/helpdesk_team.py

@@ -1022,15 +1022,14 @@ class HelpdeskTeamExtras(models.Model):
                             })
                             option.text = req_type.name
                     elif field_name == 'affected_module_id':
-                        modules = self.env['ir.module.module'].sudo().search([
-                            ('state', '=', 'installed'),
-                            ('application', '=', True)
-                        ], order='shortdesc')
+                        modules = self.env['helpdesk.affected.module'].sudo().search([
+                            ('active', '=', True)
+                        ], order='name')
                         for module in modules:
                             option = etree.SubElement(input_el, 'option', {
                                 'value': str(module.id)
                             })
-                            option.text = module.shortdesc or module.name
+                            option.text = module.name
         elif field_type in ('date', 'datetime'):
             # Date/Datetime field - NUEVO: soporte para fechas
             date_wrapper = etree.SubElement(input_div, 'div', {

+ 15 - 4
helpdesk_extras/models/helpdesk_ticket.py

@@ -23,13 +23,24 @@ class HelpdeskTicket(models.Model):
         help="Code of the request type for conditional logic"
     )
     affected_module_id = fields.Many2one(
-        'ir.module.module',
+        'helpdesk.affected.module',
         string='Affected Module',
-        required=False,  # Not required at DB level to allow null if module is uninstalled
-        domain=[('state', '=', 'installed'), ('application', '=', True)],
-        ondelete='set null',
+        required=False,
+        domain=[('active', '=', True)],
         help="Odoo module where the issue or improvement occurs"
     )
+    affected_user_id = fields.Many2one(
+        'res.users',
+        string='Affected User',
+        required=False,
+        tracking=True,
+        help="User affected by this ticket (from this Odoo instance)"
+    )
+    affected_user_email = fields.Char(
+        string='Affected User Email',
+        tracking=True,
+        help="Email address of the affected user (from another Odoo instance or external)"
+    )
     business_impact = fields.Selection(
         [
             ('0', 'Critical'),

+ 3 - 0
helpdesk_extras/security/ir.model.access.csv

@@ -23,3 +23,6 @@ access_helpdesk_workflow_template_sla_internal,helpdesk.workflow.template.sla.in
 access_helpdesk_workflow_template_sla_manager,helpdesk.workflow.template.sla.manager,helpdesk_extras.model_helpdesk_workflow_template_sla,helpdesk.group_helpdesk_manager,1,1,1,1
 access_helpdesk_workflow_template_apply_wizard_user,helpdesk.workflow.template.apply.wizard.user,helpdesk_extras.model_helpdesk_workflow_template_apply_wizard,helpdesk.group_helpdesk_user,1,1,1,1
 access_helpdesk_workflow_template_apply_wizard_manager,helpdesk.workflow.template.apply.wizard.manager,helpdesk_extras.model_helpdesk_workflow_template_apply_wizard,helpdesk.group_helpdesk_manager,1,1,1,1
+access_helpdesk_affected_module_user,helpdesk.affected.module.user,helpdesk_extras.model_helpdesk_affected_module,helpdesk.group_helpdesk_user,1,0,0,0
+access_helpdesk_affected_module_manager,helpdesk.affected.module.manager,helpdesk_extras.model_helpdesk_affected_module,helpdesk.group_helpdesk_manager,1,1,1,1
+access_helpdesk_affected_module_portal,helpdesk.affected.module.portal,helpdesk_extras.model_helpdesk_affected_module,base.group_portal,1,0,0,0

+ 81 - 0
helpdesk_extras/views/helpdesk_affected_module_views.xml

@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+    <!-- Affected Module Catalog List View -->
+    <record id="helpdesk_affected_module_view_tree" model="ir.ui.view">
+        <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">
+                <field name="code"/>
+                <field name="name"/>
+                <field name="is_main_application"/>
+                <field name="active" widget="boolean_toggle"/>
+            </list>
+        </field>
+    </record>
+
+    <!-- Affected Module Catalog Form View -->
+    <record id="helpdesk_affected_module_view_form" model="ir.ui.view">
+        <field name="name">helpdesk.affected.module.form</field>
+        <field name="model">helpdesk.affected.module</field>
+        <field name="arch" type="xml">
+            <form string="Affected Module">
+                <sheet>
+                    <group>
+                        <group>
+                            <field name="code" required="1"/>
+                            <field name="name" required="1"/>
+                            <field name="is_main_application"/>
+                            <field name="active"/>
+                        </group>
+                    </group>
+                    <group>
+                        <field name="description" placeholder="Module description..."/>
+                    </group>
+                </sheet>
+            </form>
+        </field>
+    </record>
+
+    <!-- Affected Module Catalog Search View -->
+    <record id="helpdesk_affected_module_view_search" model="ir.ui.view">
+        <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">
+                <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)]"/>
+            </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="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
+            </p>
+            <p>
+                Manage the catalog of Odoo modules that can be affected in helpdesk tickets.
+            </p>
+        </field>
+    </record>
+
+    <!-- Menu Item for Affected Module Catalog -->
+    <menuitem id="helpdesk_affected_module_menu"
+              name="Affected Modules"
+              parent="helpdesk.helpdesk_menu_config"
+              action="helpdesk_affected_module_action"
+              sequence="25"
+              groups="helpdesk.group_helpdesk_manager"/>
+
+</odoo>

+ 1 - 1
helpdesk_extras/views/helpdesk_portal_templates.xml

@@ -29,7 +29,7 @@
             </div>
             <div t-if="ticket.affected_module_id" class="row mb-4">
                 <strong class="col-lg-3">Affected Module</strong>
-                <span class="col-lg-9" t-field="ticket.affected_module_id.shortdesc"/>
+                <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>

+ 16 - 1
helpdesk_extras/views/helpdesk_ticket_views.xml

@@ -6,6 +6,7 @@
         <field name="name">helpdesk.ticket.form.inherit.helpdesk.extras</field>
         <field name="inherit_id" ref="helpdesk.helpdesk_ticket_view_form"/>
         <field name="model">helpdesk.ticket</field>
+        <field name="priority">20</field>
         <field name="arch" type="xml">
             <!-- Add Extras page with all custom fields -->
             <xpath expr="//notebook" position="inside">
@@ -14,6 +15,8 @@
                         <group string="Request Information">
                             <field name="request_type_id" required="1"/>
                             <field name="affected_module_id"/>
+                            <field name="affected_user_id" widget="many2one_avatar_user"/>
+                            <field name="affected_user_email" widget="email" placeholder="email@example.com"/>
                             <field name="business_impact"/>
                         </group>
                         <group string="Details">
@@ -31,7 +34,7 @@
                         <field name="approval_status"/>
                     </group>
                     <group string="Attachments">
-                        <field name="attachment_ids" widget="many2many_binary"/>
+                        <field name="attachment_ids"/>
                     </group>
                     <group string="Template Information" invisible="not has_template">
                         <field name="team_id" invisible="1"/>
@@ -62,4 +65,16 @@
         </field>
     </record>
 
+    <!-- Extend helpdesk.ticket search view -->
+    <record id="helpdesk_ticket_view_search_inherit_helpdesk_extras" model="ir.ui.view">
+        <field name="name">helpdesk.ticket.search.inherit.helpdesk.extras</field>
+        <field name="inherit_id" ref="helpdesk.helpdesk_tickets_view_search_base"/>
+        <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"/>
+            </xpath>
+        </field>
+    </record>
+
 </odoo>

+ 25 - 3
theme_m22tc/controllers/helpdesk_portal.py

@@ -99,8 +99,13 @@ class CustomerPortal(HelpdeskCustomerPortal):
                         return {
                             'used': 0.0,
                             'available': 0.0,
+                            'total': 0.0,
                             'prepaid_hours': 0.0,
+                            'prepaid_hours_used': 0.0,
+                            'prepaid_hours_total': 0.0,
                             'credit_hours': 0.0,
+                            'credit_hours_used': 0.0,
+                            'credit_hours_total': 0.0,
                             'credit_available': 0.0,
                             'highest_price': 0.0,
                             'percentage': 0.0,
@@ -111,19 +116,31 @@ class CustomerPortal(HelpdeskCustomerPortal):
                     total_available = float(hours_data.get('total_available', 0.0) or 0.0)
                     hours_used = float(hours_data.get('hours_used', 0.0) or 0.0)
                     prepaid_hours = float(hours_data.get('prepaid_hours', 0.0) or 0.0)
+                    prepaid_hours_used = float(hours_data.get('prepaid_hours_used', 0.0) or 0.0)
+                    prepaid_hours_total = float(hours_data.get('prepaid_hours_total', 0.0) or 0.0)
                     credit_hours = float(hours_data.get('credit_hours', 0.0) or 0.0)
+                    credit_hours_used = float(hours_data.get('credit_hours_used', 0.0) or 0.0)
+                    credit_hours_total = float(hours_data.get('credit_hours_total', 0.0) or 0.0)
                     credit_available = float(hours_data.get('credit_available', 0.0) or 0.0)
                     highest_price = float(hours_data.get('highest_price', 0.0) or 0.0)
                     
-                    # Calculate percentage (used / (used + available)) - same as widget
-                    total_with_used = total_available + hours_used
-                    percentage = (hours_used / total_with_used * 100) if total_with_used > 0 else 0.0
+                    # Calculate total hours (prepaid total + credit total)
+                    # This is the sum of all hours: available + used
+                    total_hours = prepaid_hours_total + credit_hours_total
+                    
+                    # Calculate percentage (used / total) - using the correct total
+                    percentage = (hours_used / total_hours * 100) if total_hours > 0 else 0.0
                     
                     result = {
                         'used': round(hours_used, 2),
                         'available': round(total_available, 2),
+                        'total': round(total_hours, 2),  # Total hours (available + used)
                         'prepaid_hours': round(prepaid_hours, 2),
+                        'prepaid_hours_used': round(prepaid_hours_used, 2),
+                        'prepaid_hours_total': round(prepaid_hours_total, 2),
                         'credit_hours': round(credit_hours, 2),
+                        'credit_hours_used': round(credit_hours_used, 2),
+                        'credit_hours_total': round(credit_hours_total, 2),
                         'credit_available': round(credit_available, 2),
                         'highest_price': round(highest_price, 2),
                         'percentage': round(percentage, 1),
@@ -143,8 +160,13 @@ class CustomerPortal(HelpdeskCustomerPortal):
         return {
             'used': 0.0,
             'available': 0.0,
+            'total': 0.0,
             'prepaid_hours': 0.0,
+            'prepaid_hours_used': 0.0,
+            'prepaid_hours_total': 0.0,
             'credit_hours': 0.0,
+            'credit_hours_used': 0.0,
+            'credit_hours_total': 0.0,
             'credit_available': 0.0,
             'highest_price': 0.0,
             'percentage': 0.0,

+ 18 - 0
theme_m22tc/data/ir_asset.xml

@@ -41,5 +41,23 @@
             <field name="bundle">web.assets_frontend</field>
             <field name="path">theme_m22tc/static/src/js/m22_bottom_sheet.js</field>
         </record>
+
+        <!-- No external dependencies needed - pure JavaScript + Tailwind -->
+
+        <!-- Helpdesk Form Select Enhancement JS -->
+        <record id="m22tc_helpdesk_select_js" model="ir.asset">
+            <field name="name">M22 Tech Helpdesk Select Enhancement JS</field>
+            <field name="key">theme_m22tc.m22tc_helpdesk_select_js</field>
+            <field name="bundle">web.assets_frontend</field>
+            <field name="path">theme_m22tc/static/src/js/m22_helpdesk_select.js</field>
+        </record>
+
+        <!-- Helpdesk Select Styles SCSS -->
+        <record id="m22tc_helpdesk_select_scss" model="ir.asset">
+            <field name="name">M22 Tech Helpdesk Select Styles SCSS</field>
+            <field name="key">theme_m22tc.m22tc_helpdesk_select_scss</field>
+            <field name="bundle">web.assets_frontend</field>
+            <field name="path">theme_m22tc/static/src/scss/m22tc_helpdesk_select.scss</field>
+        </record>
     </data>
 </odoo>

+ 42 - 0
theme_m22tc/i18n/es.po

@@ -0,0 +1,42 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * theme_m22tc
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 18.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2025-12-11 01:00:00+0000\n"
+"PO-Revision-Date: 2025-12-11 01:00:00+0000\n"
+"Last-Translator: M22 Tech\n"
+"Language-Team: Spanish\n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. module: theme_m22tc
+#: model_terms:ir.ui.view,arch_db:theme_m22tc.m22_helpdesk_select
+msgid "Select an option..."
+msgstr "Selecciona una opción..."
+
+#. module: theme_m22tc
+#: model_terms:ir.ui.view,arch_db:theme_m22tc.m22_helpdesk_select
+msgid "No results found"
+msgstr "No se encontraron resultados"
+
+#. module: theme_m22tc
+#: model_terms:ir.ui.view,arch_db:theme_m22tc.m22_helpdesk_select
+msgid "Search..."
+msgstr "Buscar..."
+
+#. module: theme_m22tc
+#: model_terms:ir.ui.view,arch_db:theme_m22tc.m22_helpdesk_select
+msgid "Alpine.js library not loaded. Skipping select enhancement."
+msgstr "La biblioteca Alpine.js no está cargada. Se omite la mejora de selects."
+
+#. module: theme_m22tc
+#: model_terms:ir.ui.view,arch_db:theme_m22tc.m22_helpdesk_select
+msgid "Error initializing combobox:"
+msgstr "Error al inicializar el combobox:"

+ 42 - 0
theme_m22tc/i18n/es_MX.po

@@ -0,0 +1,42 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * theme_m22tc
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 18.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2025-12-11 01:00:00+0000\n"
+"PO-Revision-Date: 2025-12-11 01:00:00+0000\n"
+"Last-Translator: M22 Tech\n"
+"Language-Team: Spanish (Mexico)\n"
+"Language: es_MX\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. module: theme_m22tc
+#: model_terms:ir.ui.view,arch_db:theme_m22tc.m22_helpdesk_select
+msgid "Select an option..."
+msgstr "Selecciona una opción..."
+
+#. module: theme_m22tc
+#: model_terms:ir.ui.view,arch_db:theme_m22tc.m22_helpdesk_select
+msgid "No results found"
+msgstr "No se encontraron resultados"
+
+#. module: theme_m22tc
+#: model_terms:ir.ui.view,arch_db:theme_m22tc.m22_helpdesk_select
+msgid "Search..."
+msgstr "Buscar..."
+
+#. module: theme_m22tc
+#: model_terms:ir.ui.view,arch_db:theme_m22tc.m22_helpdesk_select
+msgid "Alpine.js library not loaded. Skipping select enhancement."
+msgstr "La biblioteca Alpine.js no está cargada. Se omite la mejora de selects."
+
+#. module: theme_m22tc
+#: model_terms:ir.ui.view,arch_db:theme_m22tc.m22_helpdesk_select
+msgid "Error initializing combobox:"
+msgstr "Error al inicializar el combobox:"

+ 357 - 0
theme_m22tc/static/src/js/m22_helpdesk_select.js

@@ -0,0 +1,357 @@
+/** @odoo-module **/
+
+import { _t } from "@web/core/l10n/translation";
+import publicWidget from "@web/legacy/js/public/public_widget";
+
+/**
+ * M22 Helpdesk Select Enhancement Widget
+ * 
+ * Transforms standard select dropdowns in helpdesk forms into modern
+ * searchable selects using vanilla JavaScript + Tailwind CSS.
+ * No external dependencies - pure modern JavaScript.
+ */
+publicWidget.registry.M22HelpdeskSelect = publicWidget.Widget.extend({
+    selector: '#helpdesk_ticket_form, form[id*="helpdesk"], form[data-model_name="helpdesk.ticket"]',
+    
+    /**
+     * @override
+     */
+    start: function () {
+        const self = this;
+        return this._super.apply(this, arguments).then(function () {
+            // Wait a bit for form fields to be fully rendered
+            setTimeout(function () {
+                self._initializeSelects();
+            }, 300);
+        });
+    },
+
+    /**
+     * @override
+     */
+    destroy: function () {
+        // Cleanup click outside handlers
+        const comboboxes = this.el.querySelectorAll('.m22-combobox-wrapper');
+        comboboxes.forEach(wrapper => {
+            if (wrapper._clickOutsideHandler) {
+                document.removeEventListener('click', wrapper._clickOutsideHandler);
+                delete wrapper._clickOutsideHandler;
+            }
+        });
+        this._super.apply(this, arguments);
+    },
+
+    //--------------------------------------------------------------------------
+    // Private
+    //--------------------------------------------------------------------------
+
+    /**
+     * Initialize combobox on all select elements in the form
+     * @private
+     */
+    _initializeSelects: function () {
+        // Find all select elements with class s_website_form_input (many2one fields)
+        // Exclude selects that are already initialized
+        const selects = this.el.querySelectorAll('select.s_website_form_input:not(.m22-combobox-processed)');
+        
+        if (!selects.length) {
+            return;
+        }
+
+        selects.forEach(select => {
+            try {
+                select.classList.add('m22-combobox-processed');
+                this._transformSelectToCombobox(select);
+            } catch (error) {
+                console.error(_t('Error initializing combobox:'), error);
+            }
+        });
+    },
+
+    /**
+     * Transform a select element into a modern combobox
+     * @param {HTMLElement} select
+     * @private
+     */
+    _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);
+
+        // Get current value
+        const currentValue = select.value;
+        const currentOption = options.find(opt => opt.value === currentValue);
+        const placeholder = select.querySelector('option[disabled]')?.textContent?.trim() || _t('Select an option...');
+        const searchPlaceholder = _t('Search...');
+        const noResultsText = _t('No results found');
+
+        // Create wrapper div
+        const wrapper = document.createElement('div');
+        wrapper.className = 'relative m22-combobox-wrapper';
+        
+        // Create state object
+        const state = {
+            open: false,
+            search: '',
+            selected: currentOption || null,
+            options: options.filter(opt => !opt.disabled),
+            placeholder: placeholder,
+            searchPlaceholder: searchPlaceholder,
+            noResultsText: noResultsText,
+            
+            get filteredOptions() {
+                if (!this.search) return this.options;
+                const query = this.search.toLowerCase();
+                return this.options.filter(opt => 
+                    opt.label.toLowerCase().includes(query)
+                );
+            },
+            
+            get hasResults() {
+                return this.filteredOptions.length > 0;
+            }
+        };
+
+        // Build HTML structure with Tailwind classes
+        wrapper.innerHTML = `
+            <!-- Hidden original select for form submission -->
+            <select name="${select.name}" id="${select.id}" class="hidden" ${select.required ? 'required' : ''}>
+                ${options.map(opt => 
+                    `<option value="${opt.value}" ${opt.value === currentValue ? 'selected' : ''} ${opt.disabled ? 'disabled' : ''}>${opt.label}</option>`
+                ).join('')}
+            </select>
+            
+            <!-- Combobox button -->
+            <button 
+                type="button"
+                class="m22-combobox-button w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-left shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 sm:text-sm ${state.selected ? 'text-gray-900' : 'text-gray-500'}"
+            >
+                <span class="m22-combobox-placeholder block truncate ${state.selected ? 'hidden' : ''}">${placeholder}</span>
+                <span class="m22-combobox-value block truncate ${!state.selected ? 'hidden' : ''}">${state.selected ? state.selected.label : ''}</span>
+                <span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
+                    <svg class="m22-combobox-arrow h-5 w-5 text-gray-400 transition-transform" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+                        <path fill-rule="evenodd" d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z" clip-rule="evenodd" />
+                    </svg>
+                </span>
+            </button>
+            
+            <!-- Dropdown panel -->
+            <div 
+                class="m22-combobox-dropdown absolute z-[9999] mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm hidden"
+            >
+                <!-- Search input -->
+                <div class="sticky top-0 z-10 bg-white px-3 py-2 border-b border-gray-200">
+                    <input 
+                        type="text"
+                        class="m22-combobox-search w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
+                        placeholder="${searchPlaceholder}"
+                    />
+                </div>
+                
+                <!-- Options list -->
+                <div class="m22-combobox-options">
+                    ${state.options.map(opt => `
+                        <div
+                            class="m22-combobox-option relative cursor-pointer select-none px-4 py-2 text-gray-900 hover:bg-blue-50 transition-colors ${state.selected && state.selected.value === opt.value ? 'bg-blue-100' : ''}"
+                            data-value="${opt.value}"
+                        >
+                            <span class="block truncate">${this._escapeHtml(opt.label)}</span>
+                            ${state.selected && state.selected.value === opt.value ? `
+                                <span class="absolute inset-y-0 right-0 flex items-center pr-4 text-blue-600">
+                                    <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+                                        <path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z" clip-rule="evenodd" />
+                                    </svg>
+                                </span>
+                            ` : ''}
+                        </div>
+                    `).join('')}
+                </div>
+                
+                <!-- No results -->
+                <div class="m22-combobox-no-results px-4 py-2 text-sm text-gray-500 hidden">
+                    ${noResultsText}
+                </div>
+            </div>
+        `;
+
+        // Get references to elements
+        const button = wrapper.querySelector('.m22-combobox-button');
+        const dropdown = wrapper.querySelector('.m22-combobox-dropdown');
+        const searchInput = wrapper.querySelector('.m22-combobox-search');
+        const optionsContainer = wrapper.querySelector('.m22-combobox-options');
+        const noResults = wrapper.querySelector('.m22-combobox-no-results');
+        const placeholderSpan = wrapper.querySelector('.m22-combobox-placeholder');
+        const valueSpan = wrapper.querySelector('.m22-combobox-value');
+        const arrow = wrapper.querySelector('.m22-combobox-arrow');
+        const hiddenSelect = wrapper.querySelector('select');
+
+        // Methods
+        const updateDisplay = () => {
+            if (state.selected) {
+                placeholderSpan.classList.add('hidden');
+                valueSpan.textContent = state.selected.label;
+                valueSpan.classList.remove('hidden');
+                button.classList.remove('text-gray-500');
+                button.classList.add('text-gray-900');
+            } else {
+                placeholderSpan.classList.remove('hidden');
+                valueSpan.classList.add('hidden');
+                button.classList.remove('text-gray-900');
+                button.classList.add('text-gray-500');
+            }
+        };
+
+        const renderOptions = () => {
+            const filtered = state.filteredOptions;
+            
+            if (filtered.length === 0 && state.search) {
+                optionsContainer.classList.add('hidden');
+                noResults.classList.remove('hidden');
+            } else {
+                optionsContainer.classList.remove('hidden');
+                noResults.classList.add('hidden');
+                
+                // Update options highlighting
+                optionsContainer.querySelectorAll('.m22-combobox-option').forEach(optionEl => {
+                    const optionValue = optionEl.dataset.value;
+                    const isSelected = state.selected && state.selected.value === optionValue;
+                    
+                    optionEl.classList.toggle('bg-blue-100', isSelected);
+                    
+                    // Update checkmark
+                    const checkmark = optionEl.querySelector('.absolute');
+                    if (isSelected && !checkmark) {
+                        const check = document.createElement('span');
+                        check.className = 'absolute inset-y-0 right-0 flex items-center pr-4 text-blue-600';
+                        check.innerHTML = `
+                            <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+                                <path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z" clip-rule="evenodd" />
+                            </svg>
+                        `;
+                        optionEl.appendChild(check);
+                    } else if (!isSelected && checkmark) {
+                        checkmark.remove();
+                    }
+                    
+                    // Show/hide based on search
+                    const option = state.options.find(o => o.value === optionValue);
+                    if (option) {
+                        const matches = !state.search || option.label.toLowerCase().includes(state.search.toLowerCase());
+                        optionEl.style.display = matches ? '' : 'none';
+                    }
+                });
+            }
+        };
+
+        const selectOption = (option) => {
+            state.selected = option;
+            state.search = '';
+            state.open = false;
+            
+            // Update hidden select
+            hiddenSelect.value = option.value;
+            
+            // Update display
+            updateDisplay();
+            renderOptions();
+            closeDropdown();
+            
+            // Trigger change event
+            const event = new Event('change', { bubbles: true });
+            hiddenSelect.dispatchEvent(event);
+        };
+
+        const openDropdown = () => {
+            state.open = true;
+            dropdown.classList.remove('hidden');
+            arrow.classList.add('rotate-180');
+            searchInput.focus();
+            renderOptions();
+        };
+
+        const closeDropdown = () => {
+            state.open = false;
+            dropdown.classList.add('hidden');
+            arrow.classList.remove('rotate-180');
+            state.search = '';
+            searchInput.value = '';
+            renderOptions();
+        };
+
+        // Event listeners
+        button.addEventListener('click', (e) => {
+            e.stopPropagation();
+            if (state.open) {
+                closeDropdown();
+            } else {
+                openDropdown();
+            }
+        });
+
+        searchInput.addEventListener('input', (e) => {
+            state.search = e.target.value;
+            renderOptions();
+        });
+
+        searchInput.addEventListener('keydown', (e) => {
+            if (e.key === 'Escape') {
+                closeDropdown();
+            } else if (e.key === 'Enter' && state.filteredOptions.length > 0) {
+                e.preventDefault();
+                selectOption(state.filteredOptions[0]);
+            }
+        });
+
+        optionsContainer.addEventListener('click', (e) => {
+            const optionEl = e.target.closest('.m22-combobox-option');
+            if (optionEl) {
+                const value = optionEl.dataset.value;
+                const option = state.options.find(o => o.value === value);
+                if (option) {
+                    selectOption(option);
+                }
+            }
+        });
+
+        // Click outside handler
+        const clickOutsideHandler = (e) => {
+            if (state.open && !wrapper.contains(e.target)) {
+                closeDropdown();
+            }
+        };
+        document.addEventListener('click', clickOutsideHandler);
+        wrapper._clickOutsideHandler = clickOutsideHandler;
+
+        // Keyboard navigation
+        optionsContainer.addEventListener('keydown', (e) => {
+            if (e.key === 'Escape') {
+                closeDropdown();
+                button.focus();
+            }
+        });
+
+        // Replace select with wrapper
+        select.parentNode.insertBefore(wrapper, select);
+        select.style.display = 'none';
+        wrapper.appendChild(select);
+
+        // Initial render
+        updateDisplay();
+        renderOptions();
+    },
+
+    /**
+     * Escape HTML to prevent XSS
+     * @param {string} text
+     * @returns {string}
+     * @private
+     */
+    _escapeHtml: function(text) {
+        const div = document.createElement('div');
+        div.textContent = text;
+        return div.innerHTML;
+    }
+});

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

@@ -0,0 +1,63 @@
+/**
+ * M22 Tech Helpdesk Select Enhancement Styles
+ * 
+ * Minimal styles for vanilla JavaScript + Tailwind combobox
+ * Most styling is handled by Tailwind utility classes
+ */
+
+// Ensure form containers don't clip dropdowns
+#helpdesk_ticket_form,
+form[id*="helpdesk"],
+form[data-model_name="helpdesk.ticket"] {
+    
+    // Combobox wrapper
+    .m22-combobox-wrapper {
+        // Ensure dropdown is visible above other elements
+        position: relative;
+        z-index: 1;
+        
+        // Dropdown panel
+        .m22-combobox-dropdown {
+            z-index: 9999 !important;
+        }
+        
+        // Arrow rotation animation
+        .m22-combobox-arrow {
+            transition: transform 0.2s ease-in-out;
+            
+            &.rotate-180 {
+                transform: rotate(180deg);
+            }
+        }
+    }
+    
+    // Ensure select fields match form layout
+    .s_website_form_field {
+        .m22-combobox-wrapper {
+            width: 100%;
+        }
+    }
+}
+
+// Prevent clipping on form containers
+#helpdesk_section,
+.s_website_form {
+    .s_website_form_rows,
+    .s_website_form_field {
+        overflow: visible;
+    }
+}
+
+// Responsive adjustments
+@media (max-width: 575.98px) {
+    #helpdesk_ticket_form,
+    form[id*="helpdesk"],
+    form[data-model_name="helpdesk.ticket"] {
+        .m22-combobox-wrapper {
+            .m22-combobox-dropdown {
+                z-index: 99999 !important;
+                max-height: 50vh !important;
+            }
+        }
+    }
+}

+ 12 - 15
theme_m22tc/views/helpdesk_dashboard.xml

@@ -29,8 +29,8 @@
                                         <div>
                                             <h6 class="text-muted mb-1 small text-uppercase">Tiempo</h6>
                                             <h3 class="mb-0">
-                                                <span t-esc="round(dashboard_data['time_stats']['used'], 1)"/>h
-                                                <small class="text-muted">/ <span t-esc="round(dashboard_data['time_stats']['available'], 1)"/>h</small>
+                                                <span t-esc="int(dashboard_data['time_stats']['used'])"/>h
+                                                <small class="text-muted">/ <span t-esc="int(dashboard_data['time_stats']['total'])"/>h</small>
                                             </h3>
                                         </div>
                                         <div class="m22-dashboard-icon">
@@ -52,26 +52,23 @@
                                         </small>
                                     </div>
                                     <!-- Desglose: Prepago y Crédito -->
-                                    <t t-if="dashboard_data['time_stats']['prepaid_hours'] > 0 or dashboard_data['time_stats']['credit_hours'] > 0 or dashboard_data['time_stats']['credit_available'] > 0">
+                                    <t t-if="dashboard_data['time_stats']['prepaid_hours_total'] > 0 or dashboard_data['time_stats']['credit_hours_total'] > 0">
                                         <div class="mt-3 pt-2 border-top">
-                                            <t t-if="dashboard_data['time_stats']['prepaid_hours'] > 0">
+                                            <t t-if="dashboard_data['time_stats']['prepaid_hours_total'] > 0">
                                                 <div class="d-flex justify-content-between small mb-1">
                                                     <span class="text-muted">Prepago:</span>
-                                                    <span class="fw-semibold"><span t-esc="round(dashboard_data['time_stats']['prepaid_hours'], 1)"/>h</span>
-                                                </div>
-                                            </t>
-                                            <t t-if="dashboard_data['time_stats']['credit_hours'] > 0">
-                                                <div class="d-flex justify-content-between small mb-1">
-                                                    <span class="text-muted">Crédito:</span>
-                                                    <span class="fw-semibold"><span t-esc="round(dashboard_data['time_stats']['credit_hours'], 1)"/>h</span>
+                                                    <span class="fw-semibold">
+                                                        <span t-esc="int(dashboard_data['time_stats']['prepaid_hours_used'])"/>h
+                                                        <small class="text-muted">/ <span t-esc="int(dashboard_data['time_stats']['prepaid_hours_total'])"/>h</small>
+                                                    </span>
                                                 </div>
                                             </t>
-                                            <t t-if="dashboard_data['time_stats']['credit_available'] > 0">
+                                            <t t-if="dashboard_data['time_stats']['credit_hours_total'] > 0">
                                                 <div class="d-flex justify-content-between small">
-                                                    <span class="text-muted">Crédito disponible:</span>
+                                                    <span class="text-muted">Crédito:</span>
                                                     <span class="fw-semibold">
-                                                        <span t-esc="request.env.company.currency_id.symbol"/>
-                                                        <span t-esc="round(dashboard_data['time_stats']['credit_available'], 2)"/>
+                                                        <span t-esc="int(dashboard_data['time_stats']['credit_hours_used'])"/>h
+                                                        <small class="text-muted">/ <span t-esc="int(dashboard_data['time_stats']['credit_hours_total'])"/>h</small>
                                                     </span>
                                                 </div>
                                             </t>