Compare commits
No commits in common. "acd67d3816a3dc1b1e9667e9c4987850b37797d4" and "6e889696775f350ce6a41898cc4a94fccac7ee88" have entirely different histories.
acd67d3816
...
6e88969677
16 changed files with 45 additions and 219 deletions
|
@ -66,5 +66,3 @@ SERVALA_ODOO_DB=''
|
||||||
SERVALA_ODOO_URL=''
|
SERVALA_ODOO_URL=''
|
||||||
SERVALA_ODOO_USERNAME=''
|
SERVALA_ODOO_USERNAME=''
|
||||||
SERVALA_ODOO_PASSWORD=''
|
SERVALA_ODOO_PASSWORD=''
|
||||||
# Helpdesk team ID for support tickets in Odoo. Defaults to 5.
|
|
||||||
SERVALA_ODOO_HELPDESK_TEAM_ID='5'
|
|
||||||
|
|
|
@ -58,7 +58,6 @@ class Organization(ServalaModelMixin, models.Model):
|
||||||
details = "{base}details/"
|
details = "{base}details/"
|
||||||
services = "{base}services/"
|
services = "{base}services/"
|
||||||
instances = "{base}instances/"
|
instances = "{base}instances/"
|
||||||
support = "{base}support/"
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def slug(self):
|
def slug(self):
|
||||||
|
|
|
@ -93,24 +93,3 @@ class User(ServalaModelMixin, PermissionsMixin, AbstractBaseUser):
|
||||||
)
|
)
|
||||||
if result:
|
if result:
|
||||||
return result[0]
|
return result[0]
|
||||||
|
|
||||||
def get_or_create_odoo_contact(self, organization):
|
|
||||||
if (
|
|
||||||
not organization.billing_entity
|
|
||||||
or not organization.billing_entity.odoo_company_id
|
|
||||||
):
|
|
||||||
return None
|
|
||||||
|
|
||||||
if existing_contact := self.get_odoo_contact(organization):
|
|
||||||
return existing_contact["id"]
|
|
||||||
|
|
||||||
partner_data = {
|
|
||||||
"name": f"{self.first_name} {self.last_name}".strip() or self.email,
|
|
||||||
"email": self.email,
|
|
||||||
"company_type": "person",
|
|
||||||
"type": "contact",
|
|
||||||
"parent_id": organization.billing_entity.odoo_company_id,
|
|
||||||
}
|
|
||||||
|
|
||||||
contact_id = odoo.CLIENT.execute("res.partner", "create", [partner_data])
|
|
||||||
return contact_id
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
from .organization import OrganizationForm
|
from .organization import OrganizationForm
|
||||||
from .profile import UserProfileForm
|
from .profile import UserProfileForm
|
||||||
from .support import SupportForm
|
|
||||||
|
|
||||||
__all__ = ["OrganizationForm", "UserProfileForm", "SupportForm"]
|
__all__ = ["OrganizationForm", "UserProfileForm"]
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
from django import forms
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
class SupportForm(forms.Form):
|
|
||||||
message = forms.CharField(
|
|
||||||
label=_("Message"),
|
|
||||||
widget=forms.Textarea(attrs={"rows": 8}),
|
|
||||||
help_text=_("Please describe your issue or question in detail."),
|
|
||||||
)
|
|
|
@ -1,13 +1,9 @@
|
||||||
{% extends "error_base.html" %}
|
{% extends "error_base.html" %}
|
||||||
{% block title %}
|
|
||||||
403 - Access Forbidden
|
{% block title %}403 - Access Forbidden{% endblock title %}
|
||||||
{% endblock title %}
|
|
||||||
{% block error_alt %}
|
{% block error_alt %}Access Forbidden{% endblock error_alt %}
|
||||||
Access Forbidden
|
|
||||||
{% endblock error_alt %}
|
{% block error_title %}Access Forbidden{% endblock error_title %}
|
||||||
{% block error_title %}
|
|
||||||
Access Forbidden
|
{% block error_message %}You are not authorized to access this page.{% endblock error_message %}
|
||||||
{% endblock error_title %}
|
|
||||||
{% block error_message %}
|
|
||||||
You are not authorized to access this page.
|
|
||||||
{% endblock error_message %}
|
|
|
@ -1,13 +1,9 @@
|
||||||
{% extends "error_base.html" %}
|
{% extends "error_base.html" %}
|
||||||
{% block title %}
|
|
||||||
404 - Page Not Found
|
{% block title %}404 - Page Not Found{% endblock title %}
|
||||||
{% endblock title %}
|
|
||||||
{% block error_alt %}
|
{% block error_alt %}Not Found{% endblock error_alt %}
|
||||||
Not Found
|
|
||||||
{% endblock error_alt %}
|
{% block error_title %}Page Not Found{% endblock error_title %}
|
||||||
{% block error_title %}
|
|
||||||
Page Not Found
|
{% block error_message %}The page you are looking for could not be found.{% endblock error_message %}
|
||||||
{% endblock error_title %}
|
|
||||||
{% block error_message %}
|
|
||||||
The page you are looking for could not be found.
|
|
||||||
{% endblock error_message %}
|
|
|
@ -1,13 +1,9 @@
|
||||||
{% extends "error_base.html" %}
|
{% extends "error_base.html" %}
|
||||||
{% block title %}
|
|
||||||
500 - Server Error
|
{% block title %}500 - Server Error{% endblock title %}
|
||||||
{% endblock title %}
|
|
||||||
{% block error_alt %}
|
{% block error_alt %}Server Error{% endblock error_alt %}
|
||||||
Server Error
|
|
||||||
{% endblock error_alt %}
|
{% block error_title %}Server Error{% endblock error_title %}
|
||||||
{% block error_title %}
|
|
||||||
Server Error
|
{% block error_message %}The website is currently unavailable. Please try again later or contact support.{% endblock error_message %}
|
||||||
{% endblock error_title %}
|
|
||||||
{% block error_message %}
|
|
||||||
The website is currently unavailable. Please try again later or contact support.
|
|
||||||
{% endblock error_message %}
|
|
|
@ -4,11 +4,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>
|
<title>{% block title %}Error{% endblock title %} - Servala</title>
|
||||||
{% block title %}
|
|
||||||
Error
|
|
||||||
{% endblock title %}
|
|
||||||
- Servala</title>
|
|
||||||
<link rel="shortcut icon"
|
<link rel="shortcut icon"
|
||||||
href="data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2033%2034'%20fill-rule='evenodd'%20stroke-linejoin='round'%20stroke-miterlimit='2'%20xmlns:v='https://vecta.io/nano'%3e%3cpath%20d='M3%2027.472c0%204.409%206.18%205.552%2013.5%205.552%207.281%200%2013.5-1.103%2013.5-5.513s-6.179-5.552-13.5-5.552c-7.281%200-13.5%201.103-13.5%205.513z'%20fill='%23435ebe'%20fill-rule='nonzero'/%3e%3ccircle%20cx='16.5'%20cy='8.8'%20r='8.8'%20fill='%2341bbdd'/%3e%3c/svg%3e"
|
href="data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2033%2034'%20fill-rule='evenodd'%20stroke-linejoin='round'%20stroke-miterlimit='2'%20xmlns:v='https://vecta.io/nano'%3e%3cpath%20d='M3%2027.472c0%204.409%206.18%205.552%2013.5%205.552%207.281%200%2013.5-1.103%2013.5-5.513s-6.179-5.552-13.5-5.552c-7.281%200-13.5%201.103-13.5%205.513z'%20fill='%23435ebe'%20fill-rule='nonzero'/%3e%3ccircle%20cx='16.5'%20cy='8.8'%20r='8.8'%20fill='%2341bbdd'/%3e%3c/svg%3e"
|
||||||
type="image/x-icon">
|
type="image/x-icon">
|
||||||
|
@ -26,20 +22,12 @@
|
||||||
alt="Sir Vala - {% block error_alt %}Error{% endblock error_alt %}"
|
alt="Sir Vala - {% block error_alt %}Error{% endblock error_alt %}"
|
||||||
style="max-width: 300px;
|
style="max-width: 300px;
|
||||||
margin-bottom: 2rem">
|
margin-bottom: 2rem">
|
||||||
<h1 class="error-title">
|
<h1 class="error-title">{% block error_title %}Error{% endblock error_title %}</h1>
|
||||||
{% block error_title %}
|
<p class="fs-5 text-gray-600">{% block error_message %}An error occurred.{% endblock error_message %}</p>
|
||||||
Error
|
|
||||||
{% endblock error_title %}
|
|
||||||
</h1>
|
|
||||||
<p class="fs-5 text-gray-600">
|
|
||||||
{% block error_message %}
|
|
||||||
An error occurred.
|
|
||||||
{% endblock error_message %}
|
|
||||||
</p>
|
|
||||||
<a href="/" class="btn btn-lg btn-outline-primary mt-3">Go Home</a>
|
<a href="/" class="btn btn-lg btn-outline-primary mt-3">Go Home</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -1,43 +0,0 @@
|
||||||
{% extends "frontend/base.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block html_title %}
|
|
||||||
{% block page_title %}
|
|
||||||
{% translate "Support" %}
|
|
||||||
{% endblock page_title %}
|
|
||||||
{% endblock html_title %}
|
|
||||||
{% block content %}
|
|
||||||
<section class="section">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-9">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5>{% translate "Get Support" %}</h5>
|
|
||||||
<p class="text-muted">
|
|
||||||
{% translate "Need help? Submit your question or issue below and our support team will get back to you." %}
|
|
||||||
</p>
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
{% translate "Submit Support Request" as form_submit_label %}
|
|
||||||
{% include "includes/form.html" with form=form %}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h4>{% translate "Support Information" %}</h4>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<p class="small text-muted">
|
|
||||||
{% translate "When you submit a support request, it will be sent to our support team who will respond via email." %}
|
|
||||||
</p>
|
|
||||||
<p class="small text-muted">{% translate "For urgent issues, please contact us directly." %}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{% endblock content %}
|
|
|
@ -32,13 +32,6 @@
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="menu-item">
|
|
||||||
<a href="{{ request.organization.urls.support }}" class='menu-link'>
|
|
||||||
<span>
|
|
||||||
<i class="bi bi-question-circle"></i>
|
|
||||||
{% translate "Support" %}</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from django.conf import settings
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
from django.views.generic import RedirectView
|
from django.views.generic import RedirectView
|
||||||
|
|
||||||
|
@ -60,13 +61,22 @@ urlpatterns = [
|
||||||
views.ServiceInstanceDeleteView.as_view(),
|
views.ServiceInstanceDeleteView.as_view(),
|
||||||
name="organization.instance.delete",
|
name="organization.instance.delete",
|
||||||
),
|
),
|
||||||
path(
|
|
||||||
"support/",
|
|
||||||
views.SupportView.as_view(),
|
|
||||||
name="organization.support",
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
path("", RedirectView.as_view(pattern_name="frontend:profile"), name="index"),
|
path("", RedirectView.as_view(pattern_name="frontend:profile"), name="index"),
|
||||||
|
# Error page URLs available in all environments
|
||||||
|
path(
|
||||||
|
"error404/",
|
||||||
|
views.custom_404,
|
||||||
|
{"exception": Exception("Test 404")},
|
||||||
|
name="error_404",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"error403/",
|
||||||
|
views.custom_403,
|
||||||
|
{"exception": Exception("Test 403")},
|
||||||
|
name="error_403",
|
||||||
|
),
|
||||||
|
path("error500/", views.custom_500, name="error_500"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -14,7 +14,6 @@ from .service import (
|
||||||
ServiceListView,
|
ServiceListView,
|
||||||
ServiceOfferingDetailView,
|
ServiceOfferingDetailView,
|
||||||
)
|
)
|
||||||
from .support import SupportView
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"IndexView",
|
"IndexView",
|
||||||
|
@ -30,7 +29,6 @@ __all__ = [
|
||||||
"ServiceListView",
|
"ServiceListView",
|
||||||
"ServiceOfferingDetailView",
|
"ServiceOfferingDetailView",
|
||||||
"ProfileView",
|
"ProfileView",
|
||||||
"SupportView",
|
|
||||||
"custom_404",
|
"custom_404",
|
||||||
"custom_403",
|
"custom_403",
|
||||||
"custom_500",
|
"custom_500",
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.shortcuts import redirect
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from django.views.generic import FormView
|
|
||||||
|
|
||||||
from servala.core.odoo import CLIENT
|
|
||||||
from servala.frontend.forms.support import SupportForm
|
|
||||||
from servala.frontend.views.mixins import OrganizationViewMixin
|
|
||||||
|
|
||||||
|
|
||||||
class SupportView(OrganizationViewMixin, FormView):
|
|
||||||
form_class = SupportForm
|
|
||||||
template_name = "frontend/organizations/support.html"
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
|
||||||
message = form.cleaned_data["message"]
|
|
||||||
organization = self.organization
|
|
||||||
user = self.request.user
|
|
||||||
|
|
||||||
try:
|
|
||||||
partner_id = user.get_or_create_odoo_contact(organization)
|
|
||||||
if not partner_id:
|
|
||||||
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.
|
|
||||||
# Also, we want to be very sure that support requests work, especially for
|
|
||||||
# organizations where something in the creation process may have gone wrong,
|
|
||||||
# so if the ID does not exist, we omit it entirely.
|
|
||||||
if organization.odoo_sale_order_id:
|
|
||||||
ticket_data["sale_order_id"] = organization.odoo_sale_order_id
|
|
||||||
|
|
||||||
CLIENT.execute("helpdesk.ticket", "create", [ticket_data])
|
|
||||||
messages.success(
|
|
||||||
self.request,
|
|
||||||
_(
|
|
||||||
"Your support request has been submitted successfully. We will contact you shortly."
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
messages.error(
|
|
||||||
self.request,
|
|
||||||
_(
|
|
||||||
"There was an error submitting your support request. Please try again or contact us directly."
|
|
||||||
),
|
|
||||||
)
|
|
||||||
print(f"Error creating helpdesk ticket: {e}")
|
|
||||||
|
|
||||||
return redirect(self.request.path)
|
|
|
@ -131,7 +131,6 @@ ODOO = {
|
||||||
"DB": os.environ.get("SERVALA_ODOO_DB"),
|
"DB": os.environ.get("SERVALA_ODOO_DB"),
|
||||||
"USERNAME": os.environ.get("SERVALA_ODOO_USERNAME"),
|
"USERNAME": os.environ.get("SERVALA_ODOO_USERNAME"),
|
||||||
"PASSWORD": os.environ.get("SERVALA_ODOO_PASSWORD"),
|
"PASSWORD": os.environ.get("SERVALA_ODOO_PASSWORD"),
|
||||||
"HELPDESK_TEAM_ID": int(os.environ.get("SERVALA_ODOO_HELPDESK_TEAM_ID", "5")),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django.urls import path
|
||||||
from django.urls.conf import include
|
from django.urls.conf import include
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from servala.frontend import urls, views
|
from servala.frontend import urls
|
||||||
|
|
||||||
admin.site.site_title = _("Servala Admin")
|
admin.site.site_title = _("Servala Admin")
|
||||||
admin.site.site_header = _("Servala Management")
|
admin.site.site_header = _("Servala Management")
|
||||||
|
@ -26,21 +26,6 @@ urlpatterns = [
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
urlpatterns += [
|
|
||||||
path(
|
|
||||||
"error404/",
|
|
||||||
views.custom_404,
|
|
||||||
{"exception": Exception("Test 404")},
|
|
||||||
name="error_404",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"error403/",
|
|
||||||
views.custom_403,
|
|
||||||
{"exception": Exception("Test 403")},
|
|
||||||
name="error_403",
|
|
||||||
),
|
|
||||||
path("error500/", views.custom_500, name="error_500"),
|
|
||||||
]
|
|
||||||
|
|
||||||
# Custom error handlers
|
# Custom error handlers
|
||||||
handler404 = "servala.frontend.views.custom_404"
|
handler404 = "servala.frontend.views.custom_404"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue