Complete workplace ticket interactions

This commit is contained in:
metatroncubeswdev 2026-08-31 15:25:50 -04:00
parent 21958eca15
commit f118a777d3
5 changed files with 156 additions and 3 deletions

View File

@ -1,3 +1,5 @@
import base64
from odoo import fields, http
from odoo.http import request
@ -86,6 +88,7 @@ class McsCorporateWebsite(http.Controller):
"status_key": ticket.state,
"customer": ticket.partner_id.name or ticket.customer_name,
"assigned_team": dict(ticket._fields["service_area"].selection).get(ticket.service_area),
"assigned_agent": ticket.user_id.name or "",
"created": ticket.create_date,
"updated": ticket.write_date,
"updated_label": fields.Datetime.to_string(ticket.write_date) if ticket.write_date else "",
@ -286,6 +289,41 @@ class McsCorporateWebsite(http.Controller):
messages = ticket.message_ids.sorted(lambda item: item.date)
return self._workplace_render("mcs_corporate_website.workplace_ticket_detail", {"ticket": ticket, "messages": messages})
@http.route(["/workplace/tickets/<int:ticket_id>/reply"], type="http", auth="user", website=True, methods=["POST"])
def workplace_ticket_reply(self, ticket_id, **post):
role = self._role()
ticket = request.env["mcs.support.ticket"].sudo().search(self._ticket_domain(role) + [("id", "=", ticket_id)], limit=1)
if not ticket:
return request.not_found()
body = (post.get("reply") or "").strip()
attachments = self._attach_uploaded_files(ticket)
if body or attachments:
ticket.message_post(
body=body or "Attachment added.",
attachment_ids=attachments.ids,
message_type="comment",
subtype_xmlid="mail.mt_comment",
)
return request.redirect("/workplace/tickets/%s" % ticket.id)
@http.route(["/workplace/tickets/<int:ticket_id>/close"], type="http", auth="user", website=True, methods=["POST"])
def workplace_ticket_close(self, ticket_id, **post):
role = self._role()
ticket = request.env["mcs.support.ticket"].sudo().search(self._ticket_domain(role) + [("id", "=", ticket_id)], limit=1)
if not ticket:
return request.not_found()
ticket.action_close()
return request.redirect("/workplace/tickets/%s" % ticket.id)
@http.route(["/workplace/tickets/<int:ticket_id>/reopen"], type="http", auth="user", website=True, methods=["POST"])
def workplace_ticket_reopen(self, ticket_id, **post):
role = self._role()
ticket = request.env["mcs.support.ticket"].sudo().search(self._ticket_domain(role) + [("id", "=", ticket_id)], limit=1)
if not ticket:
return request.not_found()
ticket.action_reopen()
return request.redirect("/workplace/tickets/%s" % ticket.id)
@http.route(["/workplace/knowledge"], type="http", auth="user", website=True)
def workplace_knowledge(self, **kwargs):
return self._workplace_render("mcs_corporate_website.workplace_knowledge", {"articles": self._knowledge(self._role())})
@ -311,8 +349,26 @@ class McsCorporateWebsite(http.Controller):
"description": (post.get("description") or "").strip(),
}
)
self._attach_uploaded_files(ticket)
return request.redirect("/support/thanks/%s" % ticket.access_token)
def _attach_uploaded_files(self, ticket):
Attachment = request.env["ir.attachment"].sudo()
attachments = Attachment.browse()
for upload in request.httprequest.files.getlist("attachments"):
if not upload or not upload.filename:
continue
attachments |= Attachment.create(
{
"name": upload.filename,
"datas": base64.b64encode(upload.stream.read()),
"res_model": "mcs.support.ticket",
"res_id": ticket.id,
"mimetype": upload.mimetype,
}
)
return attachments
@http.route(["/support/thanks/<string:token>"], type="http", auth="public", website=True)
def support_thanks(self, token, **kwargs):
ticket = request.env["mcs.support.ticket"].sudo().search([("access_token", "=", token)], limit=1)

View File

@ -15,6 +15,7 @@ class McsSupportTicket(models.Model):
customer_email = fields.Char(required=True)
customer_phone = fields.Char()
partner_id = fields.Many2one("res.partner", string="Customer", tracking=True)
user_id = fields.Many2one("res.users", string="Assigned Agent", tracking=True)
company_id = fields.Many2one(
"res.company",
default=lambda self: self.env.company,
@ -95,3 +96,6 @@ class McsSupportTicket(models.Model):
def action_close(self):
self.write({"state": "closed"})
def action_reopen(self):
self.write({"state": "in_progress"})

View File

@ -671,6 +671,11 @@
box-shadow: 0 10px 24px rgba(28, 78, 216, 0.2);
}
.mcs-wp-btn--secondary {
background: #334155;
box-shadow: none;
}
.mcs-table-wrap {
width: 100%;
overflow-x: auto;
@ -796,6 +801,18 @@
}
}
.mcs-upload {
border: 1px dashed #B8C1D1;
border-radius: 14px;
padding: 16px;
background: #F8FAFC;
span {
color: var(--text-secondary);
font-weight: 700;
}
}
.mcs-search {
width: min(420px, 100%);
border: 1px solid var(--border);
@ -803,6 +820,80 @@
padding: 12px 14px;
}
body:has(form[action="/web/login"]) {
background: #F6F7F9;
#wrapwrap {
min-height: 100vh;
background:
linear-gradient(90deg, #101114 0%, #101114 48%, #F6F7F9 48%, #F6F7F9 100%);
}
main {
min-height: calc(100vh - 76px);
display: grid;
place-items: center;
padding: 48px 24px;
}
main::before {
content: "Your workplace. Connected.\\A Attendance, support and collaboration in one place.";
white-space: pre-line;
position: fixed;
left: 8vw;
top: 38vh;
max-width: 440px;
color: #fff;
font-size: 3.1rem;
line-height: 1.05;
font-weight: 820;
letter-spacing: 0;
}
form[action="/web/login"] {
width: min(430px, 100%);
margin-left: 42vw;
padding: 34px;
border: 1px solid #E6E8EC;
border-radius: 14px;
background: #fff;
box-shadow: 0 24px 70px rgba(17, 24, 39, 0.14);
.form-control {
min-height: 46px;
border-radius: 10px;
}
.btn-primary {
width: 100%;
min-height: 46px;
border-radius: 10px;
background: #1C4ED8;
border-color: #1C4ED8;
font-weight: 800;
}
}
}
@media (max-width: 991.98px) {
body:has(form[action="/web/login"]) {
#wrapwrap {
background: #F6F7F9;
}
main::before {
position: static;
color: #17181B;
font-size: 2.2rem;
margin-bottom: 22px;
}
form[action="/web/login"] {
margin-left: 0;
}
}
}
.mcs-timeline {
display: grid;
gap: 12px;

View File

@ -39,6 +39,7 @@
<field name="customer_email"/>
<field name="customer_phone"/>
<field name="partner_id"/>
<field name="user_id"/>
</group>
<group>
<field name="service_area"/>
@ -66,6 +67,7 @@
<field name="subject"/>
<field name="customer_name"/>
<field name="customer_email"/>
<field name="user_id"/>
<filter string="Open" name="open" domain="[('state', 'not in', ['resolved', 'closed'])]"/>
<filter string="Urgent" name="urgent" domain="[('priority', '=', '2')]"/>
<group expand="0" string="Group By">

View File

@ -196,7 +196,7 @@
<label>Description<textarea name="description" required="required"/></label>
<label>Contact Person<input name="customer_name" t-att-value="wp_user.name" required="required"/></label>
<label>Email<input type="email" name="customer_email" t-att-value="wp_user.email" required="required"/></label>
<label>Attachments<input type="file" name="attachments" multiple="multiple"/></label>
<label class="mcs-upload">Attachments<input type="file" name="attachments" multiple="multiple"/><span>Drop files here or choose attachments</span></label>
<button class="mcs-wp-btn" type="submit">Create Ticket</button>
</form>
<aside class="mcs-wp-card"><h2>Support hours</h2><p>Standard support requests are reviewed during business hours. Urgent production issues should include affected system, business impact, and escalation contact.</p><div class="mcs-list-row"><span>Expected response</span><strong>1 business day</strong></div><div class="mcs-list-row"><span>Emergency</span><strong>Mark Urgent</strong></div></aside>
@ -208,8 +208,8 @@
<t t-call="mcs_corporate_website.workplace_layout">
<section class="mcs-wp-page-head"><div><p t-esc="ticket.name"/><h1 t-esc="ticket.subject"/></div><span class="mcs-status" t-esc="ticket.state"/></section>
<section class="mcs-wp-two">
<article class="mcs-wp-card"><div class="mcs-card-head"><h2>Conversation</h2></div><div class="mcs-timeline"><div class="mcs-message"><strong t-esc="ticket.customer_name"/><p t-esc="ticket.description"/></div><t t-foreach="messages" t-as="message"><div class="mcs-message mcs-message--support"><strong t-esc="message.author_id.name"/><p t-out="message.body"/></div></t></div><form class="mcs-reply"><textarea placeholder="Write a reply"/><button class="mcs-wp-btn" type="button">Send Reply</button></form></article>
<aside class="mcs-wp-card"><h2>Ticket details</h2><div class="mcs-list-row"><span>Status</span><strong t-esc="ticket.state"/></div><div class="mcs-list-row"><span>Priority</span><strong t-esc="ticket.priority"/></div><div class="mcs-list-row"><span>Assigned Team</span><strong t-esc="ticket.service_area"/></div><div class="mcs-list-row"><span>Created</span><strong><span t-field="ticket.create_date"/></strong></div></aside>
<article class="mcs-wp-card"><div class="mcs-card-head"><h2>Conversation</h2></div><div class="mcs-timeline"><div class="mcs-message"><strong t-esc="ticket.customer_name"/><p t-esc="ticket.description"/></div><t t-foreach="messages" t-as="message"><div class="mcs-message mcs-message--support"><strong t-esc="message.author_id.name"/><p t-out="message.body"/></div></t></div><form t-att-action="'/workplace/tickets/%s/reply' % ticket.id" method="post" enctype="multipart/form-data" class="mcs-reply"><input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/><textarea name="reply" placeholder="Write a reply"/><input type="file" name="attachments" multiple="multiple"/><button class="mcs-wp-btn" type="submit">Send Reply</button></form></article>
<aside class="mcs-wp-card"><h2>Ticket details</h2><div class="mcs-list-row"><span>Status</span><strong t-esc="ticket.state"/></div><div class="mcs-list-row"><span>Priority</span><strong t-esc="ticket.priority"/></div><div class="mcs-list-row"><span>Assigned Team</span><strong t-esc="ticket.service_area"/></div><div class="mcs-list-row"><span>Assigned Agent</span><strong t-esc="ticket.user_id.name or '-'"/></div><div class="mcs-list-row"><span>Created</span><strong><span t-field="ticket.create_date"/></strong></div><form t-att-action="'/workplace/tickets/%s/close' % ticket.id" method="post"><input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/><button class="mcs-wp-btn" type="submit">Close Ticket</button></form><form t-att-action="'/workplace/tickets/%s/reopen' % ticket.id" method="post" class="mt-2"><input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/><button class="mcs-wp-btn mcs-wp-btn--secondary" type="submit">Reopen Ticket</button></form></aside>
</section>
</t>
</template>