Extract odoo helpdesk ticket creation
All checks were successful
Tests / test (push) Successful in 28s

This commit is contained in:
Tobias Kunze 2025-11-12 11:52:54 +01:00 committed by Tobias Brunner
parent 61f1065bc6
commit e4c64c4a17
Signed by: tobru
SSH key fingerprint: SHA256:kOXg1R6c11XW3/Pt9dbLdQvOJGFAy+B2K6v6PtRWBGQ
5 changed files with 70 additions and 46 deletions

View file

@ -21,7 +21,7 @@ from servala.core.models import (
User, User,
) )
from servala.core.models.service import Service, ServiceInstance, ServiceOffering from servala.core.models.service import Service, ServiceInstance, ServiceOffering
from servala.core.odoo import CLIENT from servala.core.odoo import create_helpdesk_ticket
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -269,9 +269,7 @@ The Servala Team"""
organization = None organization = None
try: try:
# Look for instances with this name in the service offering's context # Look for instances with this name in the service offering's context
# TODO: we do not currently match instance IDs from exoscale yet, this service_instance = (
# will likely not work at all yet
instances = (
ServiceInstance.objects.filter( ServiceInstance.objects.filter(
name=instance_id, name=instance_id,
context__service_offering=service_offering, context__service_offering=service_offering,
@ -280,7 +278,7 @@ The Servala Team"""
.first() .first()
) )
if instances: if service_instance:
organization = service_instance.organization organization = service_instance.organization
except Exception: except Exception:
pass pass
@ -327,13 +325,11 @@ The Servala Team"""
description_parts.append(f" - {full_name} ({user_link}) - {role}") description_parts.append(f" - {full_name} ({user_link}) - {role}")
description = "\n".join(description_parts) description = "\n".join(description_parts)
ticket_data = {
"name": f"Exoscale OSB {action} - {service.name} - {instance_id}",
"team_id": settings.ODOO["HELPDESK_TEAM_ID"],
"description": description,
}
CLIENT.execute("helpdesk.ticket", "create", [ticket_data]) create_helpdesk_ticket(
title=f"Exoscale OSB {action} - {service.name} - {instance_id}",
description=description,
)
logger.info( logger.info(
f"Created {action} helpdesk ticket for instance {instance_id}, service {service.name}" f"Created {action} helpdesk ticket for instance {instance_id}, service {service.name}"
) )

View file

@ -207,3 +207,19 @@ def get_invoice_addresses(user):
return invoice_addresses or [] return invoice_addresses or []
except Exception: except Exception:
return [] return []
def create_helpdesk_ticket(title, description, partner_id=None, sale_order_id=None):
ticket_data = {
"name": title,
"team_id": settings.ODOO["HELPDESK_TEAM_ID"],
"description": description,
}
if partner_id:
ticket_data["partner_id"] = partner_id
if sale_order_id:
ticket_data["sale_order_id"] = sale_order_id
return CLIENT.execute("helpdesk.ticket", "create", [ticket_data])

View file

@ -1,11 +1,10 @@
from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import FormView from django.views.generic import FormView
from servala.core.odoo import CLIENT from servala.core.odoo import create_helpdesk_ticket
from servala.frontend.forms.support import SupportForm from servala.frontend.forms.support import SupportForm
from servala.frontend.views.mixins import OrganizationViewMixin from servala.frontend.views.mixins import OrganizationViewMixin
@ -24,21 +23,16 @@ class SupportView(OrganizationViewMixin, FormView):
if not partner_id: if not partner_id:
raise Exception("Could not get or create Odoo contact for user") raise Exception("Could not get or create Odoo contact for user")
ticket_data = {
"name": f"Servala Support - Organization {organization.name}",
"team_id": settings.ODOO["HELPDESK_TEAM_ID"],
"partner_id": partner_id,
"description": message,
}
# All orgs should have a sale order ID, but legacy ones might not have it. # All orgs should have a sale order ID, but legacy ones might not have it.
# Also, we want to be very sure that support requests work, especially for # Also, we want to be very sure that support requests work, especially for
# organizations where something in the creation process may have gone wrong, # organizations where something in the creation process may have gone wrong,
# so if the ID does not exist, we omit it entirely. # so if the ID does not exist, we omit it entirely.
if organization.odoo_sale_order_id: create_helpdesk_ticket(
ticket_data["sale_order_id"] = organization.odoo_sale_order_id title=f"Servala Support - Organization {organization.name}",
description=message,
CLIENT.execute("helpdesk.ticket", "create", [ticket_data]) partner_id=partner_id,
sale_order_id=organization.odoo_sale_order_id or None,
)
messages.success( messages.success(
self.request, self.request,
_( _(

View file

@ -8,6 +8,7 @@ overrides/adds settings specific to testing.
from servala.settings import * # noqa: F403, F401 from servala.settings import * # noqa: F403, F401
SECRET_KEY = "test-secret-key-for-testing-only-do-not-use-in-production" SECRET_KEY = "test-secret-key-for-testing-only-do-not-use-in-production"
SALT_KEY = SECRET_KEY
PASSWORD_HASHERS = [ PASSWORD_HASHERS = [
"django.contrib.auth.hashers.MD5PasswordHasher", "django.contrib.auth.hashers.MD5PasswordHasher",
] ]

View file

@ -614,7 +614,8 @@ def test_delete_creates_ticket_with_admin_links(
test_service_offering, test_service_offering,
instance_id, instance_id,
): ):
mock_api_client = mocker.patch("servala.api.views.CLIENT") # Mock the create_helpdesk_ticket function
mock_create_ticket = mocker.patch("servala.api.views.create_helpdesk_ticket")
response = osb_client.delete( response = osb_client.delete(
f"/api/osb/v2/service_instances/{instance_id}" f"/api/osb/v2/service_instances/{instance_id}"
@ -623,13 +624,15 @@ def test_delete_creates_ticket_with_admin_links(
assert response.status_code == 200 assert response.status_code == 200
ticket_call = mock_api_client.execute.call_args_list[-1] # Verify the ticket was created with admin URL
ticket_data = ticket_call[0][2][0] mock_create_ticket.assert_called_once()
call_kwargs = mock_create_ticket.call_args[1]
assert "admin/core/serviceoffering" in ticket_data["description"] # Check that the description contains an admin URL
assert f"/{test_service_offering.pk}/" in ticket_data["description"] assert "admin/core/serviceoffering" in call_kwargs["description"]
assert f"/{test_service_offering.pk}/" in call_kwargs["description"]
assert ( assert (
ticket_data["name"] call_kwargs["title"]
== f"Exoscale OSB Offboard - {test_service.name} - {instance_id}" == f"Exoscale OSB Offboard - {test_service.name} - {instance_id}"
) )
@ -644,7 +647,9 @@ def test_patch_creates_ticket_with_user_admin_links(
instance_id, instance_id,
org_owner, org_owner,
): ):
mock_api_client = mocker.patch("servala.api.views.CLIENT") # Mock the create_helpdesk_ticket function
mock_create_ticket = mocker.patch("servala.api.views.create_helpdesk_ticket")
payload = { payload = {
"service_id": test_service.osb_service_id, "service_id": test_service.osb_service_id,
"plan_id": test_service_offering.osb_plan_id, "plan_id": test_service_offering.osb_plan_id,
@ -666,13 +671,17 @@ def test_patch_creates_ticket_with_user_admin_links(
) )
assert response.status_code == 200 assert response.status_code == 200
ticket_call = mock_api_client.execute.call_args_list[-1]
ticket_data = ticket_call[0][2][0] # Verify the ticket was created with admin URLs
assert "admin/core/serviceoffering" in ticket_data["description"] mock_create_ticket.assert_called_once()
assert "admin/core/user" in ticket_data["description"] call_kwargs = mock_create_ticket.call_args[1]
assert f"/{org_owner.pk}/" in ticket_data["description"]
# Check that the description contains admin URLs
assert "admin/core/serviceoffering" in call_kwargs["description"]
assert "admin/core/user" in call_kwargs["description"]
assert f"/{org_owner.pk}/" in call_kwargs["description"]
assert ( assert (
ticket_data["name"] call_kwargs["title"]
== f"Exoscale OSB Suspend - {test_service.name} - {instance_id}" == f"Exoscale OSB Suspend - {test_service.name} - {instance_id}"
) )
@ -686,7 +695,9 @@ def test_ticket_includes_organization_and_instance_when_found(
test_service_offering, test_service_offering,
organization, organization,
): ):
mock_api_client = mocker.patch("servala.api.views.CLIENT") # Mock the create_helpdesk_ticket function
mock_create_ticket = mocker.patch("servala.api.views.create_helpdesk_ticket")
service_definition = ServiceDefinition.objects.create( service_definition = ServiceDefinition.objects.create(
name="Test Definition", name="Test Definition",
service=test_service, service=test_service,
@ -719,11 +730,17 @@ def test_ticket_includes_organization_and_instance_when_found(
) )
assert response.status_code == 200 assert response.status_code == 200
ticket_call = mock_api_client.execute.call_args_list[-1]
ticket_data = ticket_call[0][2][0] # Verify the ticket was created with all admin URLs
assert f"Organization: {organization.name}" in ticket_data["description"] mock_create_ticket.assert_called_once()
assert "admin/core/organization" in ticket_data["description"] call_kwargs = mock_create_ticket.call_args[1]
assert f"/{organization.pk}/" in ticket_data["description"]
assert f"Instance: {service_instance.name}" in ticket_data["description"] # Check organization is included
assert "admin/core/serviceinstance" in ticket_data["description"] assert f"Organization: {organization.name}" in call_kwargs["description"]
assert f"/{service_instance.pk}/" in ticket_data["description"] assert "admin/core/organization" in call_kwargs["description"]
assert f"/{organization.pk}/" in call_kwargs["description"]
# Check instance is included
assert f"Instance: {service_instance.name}" in call_kwargs["description"]
assert "admin/core/serviceinstance" in call_kwargs["description"]
assert f"/{service_instance.pk}/" in call_kwargs["description"]