diff --git a/helpdesk_mgmt/controllers/main.py b/helpdesk_mgmt/controllers/main.py index 4c43176d0c..58f912d748 100644 --- a/helpdesk_mgmt/controllers/main.py +++ b/helpdesk_mgmt/controllers/main.py @@ -25,7 +25,9 @@ def support_ticket_close(self, **kw): .sudo() .search([("id", "=", values["ticket_id"])]) ) - ticket.stage_id = values.get("stage_id") + stage = http.request.env["helpdesk.ticket.stage"].browse(values.get("stage_id")) + if stage.close_from_portal: # protect against invalid target stage request + ticket.stage_id = values.get("stage_id") return werkzeug.utils.redirect("/my/ticket/" + str(ticket.id)) diff --git a/helpdesk_mgmt/controllers/myaccount.py b/helpdesk_mgmt/controllers/myaccount.py index 7772bb306b..e821f09fd8 100644 --- a/helpdesk_mgmt/controllers/myaccount.py +++ b/helpdesk_mgmt/controllers/myaccount.py @@ -111,7 +111,7 @@ def portal_my_ticket(self, ticket_id=None, access_token=None, **kw): def _ticket_get_page_view_values(self, ticket, **kwargs): closed_stages = request.env["helpdesk.ticket.stage"].search( - [("closed", "=", True)] + [("close_from_portal", "=", True)] ) values = { "page_name": "ticket", diff --git a/helpdesk_mgmt/data/helpdesk_data.xml b/helpdesk_mgmt/data/helpdesk_data.xml index 5e43f8eea7..d1458303b0 100644 --- a/helpdesk_mgmt/data/helpdesk_data.xml +++ b/helpdesk_mgmt/data/helpdesk_data.xml @@ -179,6 +179,7 @@ Done False True + True True @@ -188,6 +189,17 @@ Cancelled False True + True + True + + + + + 6 + Rejected + False + True + False True diff --git a/helpdesk_mgmt/models/helpdesk_ticket_stage.py b/helpdesk_mgmt/models/helpdesk_ticket_stage.py index 96255c72f2..2a31fabae5 100644 --- a/helpdesk_mgmt/models/helpdesk_ticket_stage.py +++ b/helpdesk_mgmt/models/helpdesk_ticket_stage.py @@ -1,4 +1,4 @@ -from odoo import fields, models +from odoo import api, fields, models class HelpdeskTicketStage(models.Model): @@ -12,6 +12,10 @@ class HelpdeskTicketStage(models.Model): active = fields.Boolean(default=True) unattended = fields.Boolean(string="Unattended") closed = fields.Boolean(string="Closed") + close_from_portal = fields.Boolean( + help="Display button in portal ticket form to allow closing ticket " + "with this stage as target." + ) mail_template_id = fields.Many2one( comodel_name="mail.template", string="Email Template", @@ -31,3 +35,8 @@ class HelpdeskTicketStage(models.Model): string="Company", default=lambda self: self.env.company, ) + + @api.onchange("closed") + def _onchange_closed(self): + if not self.closed: + self.close_from_portal = False diff --git a/helpdesk_mgmt/tests/test_helpdesk_portal.py b/helpdesk_mgmt/tests/test_helpdesk_portal.py index e9954cde9e..c985dba63e 100644 --- a/helpdesk_mgmt/tests/test_helpdesk_portal.py +++ b/helpdesk_mgmt/tests/test_helpdesk_portal.py @@ -69,6 +69,26 @@ def _submit_ticket(self, **values): resp = self.url_open("/submitted/ticket", data=data) self.assertEqual(resp.status_code, 200) + def _count_close_buttons(self, resp) -> int: + """Count close buttons in a form by counting forms with that target.""" + return resp.text.count('action="/ticket/close"') + + def _call_close_ticket(self, ticket, stage): + """Call /ticket/close with the specified target stage, check redirect.""" + resp = self.url_open( + "/ticket/close", + data={ + "csrf_token": http.WebRequest.csrf_token(self), + "stage_id": stage.id, + "ticket_id": ticket.id, + }, + allow_redirects=False, + ) + self.assertEqual(resp.status_code, 302) + self.assertTrue(resp.is_redirect) # http://127.0.0.1:8069/my/ticket/ + self.assertTrue(resp.headers["Location"].endswith(f"/my/ticket/{ticket.id}")) + return resp + @odoo.tests.tagged("post_install", "-at_install") class TestHelpdeskPortal(TestHelpdeskPortalBase): @@ -93,3 +113,24 @@ def test_submit_ticket_02(self): "

" + "
".join(self.new_ticket_desc_lines) + "

", tickets.mapped("description"), ) + + def test_close_ticket(self): + """Close a ticket from the portal.""" + self.assertFalse(self.portal_ticket.closed) + self.authenticate("test-portal", "test-portal") + resp = self.url_open(f"/my/ticket/{self.portal_ticket.id}") + self.assertEqual(self._count_close_buttons(resp), 2) # 2 close stages in data/ + stage = self.env.ref("helpdesk_mgmt.helpdesk_ticket_stage_done") + self._call_close_ticket(self.portal_ticket, stage) + self.assertTrue(self.portal_ticket.closed) + self.assertEqual(self.portal_ticket.stage_id, stage) + resp = self.url_open(f"/my/ticket/{self.portal_ticket.id}") + self.assertEqual(self._count_close_buttons(resp), 0) # no close buttons now + + def test_close_ticket_invalid_stage(self): + """Attempt to close a ticket from the portal with an invalid target stage.""" + self.authenticate("test-portal", "test-portal") + stage = self.env.ref("helpdesk_mgmt.helpdesk_ticket_stage_awaiting") + self._call_close_ticket(self.portal_ticket, stage) + self.assertFalse(self.portal_ticket.closed) + self.assertNotEqual(self.portal_ticket.stage_id, stage) diff --git a/helpdesk_mgmt/views/helpdesk_ticket_stage_views.xml b/helpdesk_mgmt/views/helpdesk_ticket_stage_views.xml index aee2ae90ac..b71e9fe981 100644 --- a/helpdesk_mgmt/views/helpdesk_ticket_stage_views.xml +++ b/helpdesk_mgmt/views/helpdesk_ticket_stage_views.xml @@ -49,6 +49,10 @@ +