[ADD] iitial commit gmn fresh service

This commit is contained in:
Madanil
2026-10-19 08:07:51 +07:00
commit ebdc2ee304
38 changed files with 4111 additions and 0 deletions

View File

@@ -0,0 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- LIST VIEW STAGE LOGS -->
<record id="view_freshservice_ticket_stage_log_tree" model="ir.ui.view">
<field name="name">freshservice.ticket.stage.log.tree</field>
<field name="model">freshservice.ticket.stage.log</field>
<field name="arch" type="xml">
<list string="Audit Trail &amp; Durasi Status"
decoration-danger="responsibility == 'internal' and is_active"
decoration-warning="responsibility == 'customer' and is_active"
decoration-muted="not is_active or responsibility in ('on_hold','third_party','internal_discussion','scheduled_meeting','external_activity','resolved_idle')">
<field name="ticket_id"/>
<field name="stage_name" widget="badge"/>
<field name="responsibility"
widget="badge"
decoration-danger="responsibility == 'internal'"
decoration-warning="responsibility == 'customer'"
decoration-muted="responsibility in ('on_hold','third_party','internal_discussion','scheduled_meeting','external_activity','resolved_idle')"/>
<field name="actor_name"/>
<field name="actor_type"/>
<field name="start_date" string="Waktu Mulai (UTC)"/>
<field name="end_date" string="Waktu Selesai (UTC)"/>
<field name="working_duration_display" string="Jam Kerja (08:30-17:30)"/>
<field name="working_days_effective" string="Hari Kerja Efektif"/>
<field name="duration_display" string="Durasi Kalender"/>
<field name="is_active" widget="boolean_toggle"/>
</list>
</field>
</record>
<!-- FORM VIEW STAGE LOGS -->
<record id="view_freshservice_ticket_stage_log_form" model="ir.ui.view">
<field name="name">freshservice.ticket.stage.log.form</field>
<field name="model">freshservice.ticket.stage.log</field>
<field name="arch" type="xml">
<form string="Detail Audit Trail">
<sheet>
<group>
<group>
<field name="ticket_id" readonly="1"/>
<field name="stage_name" readonly="1"/>
<field name="responsibility" readonly="1"/>
<field name="actor_name" readonly="1"/>
<field name="actor_type" readonly="1"/>
</group>
<group>
<field name="start_date" readonly="1" string="Waktu Mulai (UTC)"/>
<field name="end_date" readonly="1" string="Waktu Selesai (UTC)"/>
<field name="working_duration_display" readonly="1" string="Jam Kerja (08:30-17:30 WIB)"/>
<field name="working_days_effective" readonly="1" string="Hari Kerja Efektif"/>
<field name="duration_display" readonly="1" string="Durasi Kalender"/>
<field name="duration_days" readonly="1"/>
<!-- Jam di-hide -->
<field name="duration_hours" invisible="1"/>
<field name="is_active" readonly="1"/>
</group>
</group>
<notebook>
<page string="Deskripsi Aktivitas">
<field name="activity_content" readonly="1"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!-- SEARCH VIEW STAGE LOGS -->
<record id="view_freshservice_ticket_stage_log_search" model="ir.ui.view">
<field name="name">freshservice.ticket.stage.log.search</field>
<field name="model">freshservice.ticket.stage.log</field>
<field name="arch" type="xml">
<search string="Cari Log Aktivitas">
<field name="ticket_id"/>
<field name="stage_name"/>
<field name="actor_name"/>
<filter name="filter_internal" string="Waiting on Us (Internal)" domain="[('responsibility', '=', 'internal')]"/>
<filter name="filter_customer" string="Waiting on User (Customer)" domain="[('responsibility', '=', 'customer')]"/>
<filter name="filter_on_hold" string="On Hold (SLA Paused)" domain="[('responsibility', '=', 'on_hold')]"/>
<filter name="filter_third_party" string="Waiting on Third Party (SLA Paused)" domain="[('responsibility', '=', 'third_party')]"/>
<filter name="filter_internal_discussion" string="Internal Discussion (SLA Paused)" domain="[('responsibility', '=', 'internal_discussion')]"/>
<filter name="filter_scheduled_meeting" string="Tunggu Bersama (Scheduled Meeting)" domain="[('responsibility', '=', 'scheduled_meeting')]"/>
<filter name="filter_external_activity" string="External Activity (SLA Paused)" domain="[('responsibility', '=', 'external_activity')]"/>
<filter name="filter_resolved_idle" string="Resolved (Ticket Closed/Idle)" domain="[('responsibility', '=', 'resolved_idle')]"/>
<filter name="filter_sla_paused" string="Semua SLA Paused" domain="[('responsibility', 'in', ['on_hold','third_party','internal_discussion','scheduled_meeting','external_activity'])]"/>
<filter name="filter_active" string="Sedang Berjalan (Aktif)" domain="[('is_active', '=', True)]"/>
<separator/>
<group>
<filter name="group_by_ticket" string="Tiket" context="{'group_by': 'ticket_id'}"/>
<filter name="group_by_responsibility" string="Tanggung Jawab" context="{'group_by': 'responsibility'}"/>
<filter name="group_by_stage" string="Status / Stage" context="{'group_by': 'stage_name'}"/>
<filter name="group_by_actor" string="Pelaku (Actor)" context="{'group_by': 'actor_name'}"/>
</group>
</search>
</field>
</record>
<!-- ACTION STAGE LOGS -->
<record id="action_freshservice_ticket_stage_log" model="ir.actions.act_window">
<field name="name">Audit Trail Riwayat Status</field>
<field name="res_model">freshservice.ticket.stage.log</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_freshservice_ticket_stage_log_search"/>
<field name="context">{'search_default_group_by_ticket': 1}</field>
</record>
</odoo>

View File

@@ -0,0 +1,334 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- FORM VIEW -->
<record id="view_freshservice_ticket_form" model="ir.ui.view">
<field name="name">freshservice.ticket.form</field>
<field name="model">freshservice.ticket</field>
<field name="arch" type="xml">
<form string="Freshservice Ticket Audit Trail">
<header>
<button name="action_sync_from_freshservice"
type="object"
string="Tarik Data Freshservice"
class="btn-primary"
icon="fa-refresh"/>
<button name="action_open_bottleneck_wizard"
type="object"
string="Laporan Bottleneck (Excel)"
class="btn-outline-success"
icon="fa-file-excel-o"/>
<!-- <button name="action_assign_to_me"
type="object"
string="Tugaskan ke Saya"
class="btn-outline-primary"
icon="fa-user-check"/>
<button name="action_toggle_mock_mode"
type="object"
string="Ganti Mode (Mock / Live)"
class="btn-secondary"
icon="fa-exchange"/> -->
<field name="status" widget="statusbar" statusbar_visible="open,pending,resolved,closed"/>
</header>
<sheet>
<field name="is_detail_synced" invisible="1"/>
<widget name="web_ribbon" title="SIMULASI MOCK DATA" bg_color="text-bg-info" invisible="not is_mock_data"/>
<div class="oe_title">
<label for="name" string="Judul / Masalah Tiket"/>
<h1>
<field name="name" placeholder="Contoh: SAP Client error di finance..."/>
</h1>
<div class="d-flex align-items-center gap-2 mt-2">
<span class="badge text-bg-secondary fs-6">ID Freshservice: #<field name="external_ticket_id" class="oe_inline"/></span>
<field name="current_responsibility" widget="badge"
decoration-danger="current_responsibility == 'internal'"
decoration-warning="current_responsibility == 'customer'"
decoration-success="current_responsibility == 'closed'"/>
</div>
</div>
<!-- KPI METRIC CARDS SECTION -->
<div class="row mt-4 mb-4">
<div class="col-md-3">
<div class="card text-center p-3 shadow-sm border-primary">
<small class="text-muted fw-bold text-uppercase">First Response</small>
<h4 class="mb-0 text-primary mt-1">
<field name="first_response_time_display" readonly="1"/>
</h4>
<small class="text-secondary mt-1">
Oleh: <field name="first_responder_name" readonly="1"/>
</small>
</div>
</div>
<div class="col-md-3">
<div class="card text-center p-3 shadow-sm border-danger">
<small class="text-muted fw-bold text-uppercase">Agent</small>
<h4 class="mb-0 text-danger mt-1">
<field name="total_agent_time_display" readonly="1"/>
</h4>
<small class="text-secondary mt-1">Waiting on Us</small>
</div>
</div>
<div class="col-md-3">
<div class="card text-center p-3 shadow-sm border-warning">
<small class="text-muted fw-bold text-uppercase">User</small>
<h4 class="mb-0 text-warning mt-1">
<field name="total_customer_time_display" readonly="1"/>
</h4>
<small class="text-secondary mt-1">Waiting on Customer</small>
</div>
</div>
<div class="col-md-3">
<div class="card text-center p-3 shadow-sm border-success">
<small class="text-muted fw-bold text-uppercase">Waktu Selesai</small>
<h4 class="mb-0 text-success mt-1">
<field name="resolution_time_display" readonly="1"/>
</h4>
<small class="text-secondary mt-1">Total Resolution</small>
</div>
</div>
</div>
<group>
<group string="Informasi Pelapor &amp; Penugasan">
<field name="requester_name"/>
<field name="requester_email"/>
<field name="responder_name"/>
<field name="department_name"/>
</group>
<group string="Parameter Tiket">
<field name="priority" widget="priority"/>
<field name="ticket_created_at" readonly="1"/>
<field name="due_by" readonly="1"/>
<field name="is_mock_data" widget="boolean_toggle"/>
</group>
</group>
<notebook>
<page string="Detail Flow &amp; Durasi Status" name="audit_trail">
<div class="alert alert-info py-2" role="alert">
<strong>Log Transparansi SLA:</strong> Tabel ini menghitung secara objektif berapa lama tiket berada di tangan agen internal vs menunggu konfirmasi user di setiap pergantian status.
</div>
<field name="stage_log_ids" readonly="1">
<list decoration-danger="responsibility == 'internal' and is_active"
decoration-warning="responsibility == 'customer' and is_active"
decoration-muted="not is_active or responsibility in ('on_hold','third_party','internal_discussion','scheduled_meeting','external_activity','resolved_idle')">
<field name="stage_name" widget="badge"/>
<field name="responsibility" widget="badge"
decoration-danger="responsibility == 'internal'"
decoration-warning="responsibility == 'customer'"
decoration-muted="responsibility in ('on_hold','third_party','internal_discussion','scheduled_meeting','external_activity','resolved_idle')"/>
<field name="actor_name"/>
<field name="actor_type"/>
<field name="start_date" string="Waktu Mulai (UTC)"/>
<field name="end_date" string="Waktu Selesai (UTC)"/>
<field name="working_duration_display" string="Jam Kerja (08:30-17:30)"/>
<field name="working_days_effective" string="Hari Kerja Efektif"/>
<field name="activity_content"/>
<field name="is_active" widget="boolean_toggle"/>
</list>
</field>
</page>
<page string="Percakapan &amp; Balasan" name="conversations">
<field name="conversation_ids" readonly="1">
<list>
<field name="created_at"/>
<field name="sender_type" widget="badge"
decoration-info="sender_type == 'agent'"
decoration-success="sender_type == 'customer'"/>
<field name="user_name"/>
<field name="body_text"/>
</list>
</field>
</page>
<page string="Deskripsi Masalah" name="description">
<field name="description"/>
</page>
<page string="Timestamp Teknis" name="technical_timestamps">
<group>
<group string="Waktu Kejadian">
<field name="ticket_created_at" readonly="1"/>
<field name="first_responded_at" readonly="1"/>
<field name="resolved_at" readonly="1"/>
<field name="last_detail_sync" readonly="1"/>
</group>
<group string="Metrik SLA (Hari)">
<field name="first_response_time_days" readonly="1"/>
<field name="total_agent_time_days" readonly="1"/>
<field name="total_customer_time_days" readonly="1"/>
<field name="resolution_time_days" readonly="1"/>
<!-- Field jam disembunyikan (hide) -->
<field name="first_response_time_hours" invisible="1"/>
<field name="total_agent_time_hours" invisible="1"/>
<field name="total_customer_time_hours" invisible="1"/>
<field name="resolution_time_hours" invisible="1"/>
</group>
</group>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- LIST VIEW -->
<record id="view_freshservice_ticket_tree" model="ir.ui.view">
<field name="name">freshservice.ticket.tree</field>
<field name="model">freshservice.ticket</field>
<field name="arch" type="xml">
<list string="Daftar Tiket Freshservice"
decoration-danger="current_responsibility == 'internal'"
decoration-warning="current_responsibility == 'customer'"
decoration-muted="status in ('resolved', 'closed')">
<header>
<button name="action_sync_recent_tickets"
string="Tarik Tiket Terbaru Freshservice"
type="object"
class="btn-primary"
icon="fa-cloud-download"/>
</header>
<field name="external_ticket_id"/>
<field name="name"/>
<field name="status" widget="badge"
decoration-info="status == 'open'"
decoration-warning="status == 'pending'"
decoration-success="status == 'resolved'"
decoration-muted="status == 'closed'"/>
<field name="current_responsibility" widget="badge"
decoration-danger="current_responsibility == 'internal'"
decoration-warning="current_responsibility == 'customer'"
decoration-success="current_responsibility == 'closed'"/>
<field name="total_agent_time_display"/>
<field name="total_customer_time_display"/>
<field name="first_response_time_display"/>
<field name="responder_name"/>
<field name="requester_name"/>
<field name="ticket_created_at"/>
<field name="is_detail_synced" widget="boolean_toggle" optional="hide" string="Detail Di-fetch"/>
<field name="is_mock_data" widget="boolean_toggle" optional="show"/>
</list>
</field>
</record>
<!-- KANBAN VIEW -->
<record id="view_freshservice_ticket_kanban" model="ir.ui.view">
<field name="name">freshservice.ticket.kanban</field>
<field name="model">freshservice.ticket</field>
<field name="arch" type="xml">
<kanban default_group_by="status" class="o_kanban_small_column">
<field name="id"/>
<field name="name"/>
<field name="external_ticket_id"/>
<field name="priority"/>
<field name="current_responsibility"/>
<field name="total_agent_time_display"/>
<field name="total_customer_time_display"/>
<field name="responder_name"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click p-3 rounded">
<div class="d-flex justify-content-between">
<span class="badge text-bg-light fw-bold">#<field name="external_ticket_id"/></span>
<field name="priority" widget="priority"/>
</div>
<div class="fw-bold mt-2">
<field name="name"/>
</div>
<div class="d-flex justify-content-between align-items-center text-muted small mt-1">
<span>Agen: <field name="responder_name"/></span>
<field name="user_id" widget="many2one_avatar_user"/>
</div>
<div class="d-flex justify-content-between align-items-center mt-3 pt-2 border-top">
<span class="badge text-bg-danger">Agen: <field name="total_agent_time_display"/></span>
<span class="badge text-bg-warning">User: <field name="total_customer_time_display"/></span>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<!-- PIVOT VIEW -->
<record id="view_freshservice_ticket_pivot" model="ir.ui.view">
<field name="name">freshservice.ticket.pivot</field>
<field name="model">freshservice.ticket</field>
<field name="arch" type="xml">
<pivot string="Analisis Durasi Tiket SLA (Hari)">
<field name="responder_name" type="row"/>
<field name="current_responsibility" type="col"/>
<field name="total_agent_time_days" type="measure"/>
<field name="total_customer_time_days" type="measure"/>
<field name="first_response_time_days" type="measure"/>
<field name="resolution_time_days" type="measure"/>
</pivot>
</field>
</record>
<!-- GRAPH VIEW -->
<record id="view_freshservice_ticket_graph" model="ir.ui.view">
<field name="name">freshservice.ticket.graph</field>
<field name="model">freshservice.ticket</field>
<field name="arch" type="xml">
<graph string="Perbandingan Durasi Agen vs User (Hari)" type="bar">
<field name="responder_name"/>
<field name="total_agent_time_days" type="measure"/>
<field name="total_customer_time_days" type="measure"/>
</graph>
</field>
</record>
<!-- SEARCH VIEW -->
<record id="view_freshservice_ticket_search" model="ir.ui.view">
<field name="name">freshservice.ticket.search</field>
<field name="model">freshservice.ticket</field>
<field name="arch" type="xml">
<search string="Cari Tiket">
<field name="name" filter_domain="['|', ('name', 'ilike', self), ('external_ticket_id', 'ilike', self)]"/>
<field name="external_ticket_id"/>
<field name="user_id"/>
<field name="requester_name"/>
<field name="responder_name"/>
<filter name="filter_my_tickets" string="Tiket Saya" domain="[('user_id', '=', uid)]"/>
<filter name="filter_unassigned" string="Belum Ditugaskan" domain="[('user_id', '=', False)]"/>
<separator/>
<filter name="filter_waiting_us" string="Waiting on Us (Internal)" domain="[('current_responsibility', '=', 'internal')]"/>
<filter name="filter_waiting_user" string="Waiting on Customer (User)" domain="[('current_responsibility', '=', 'customer')]"/>
<separator/>
<filter name="filter_open_progress" string="Tiket Aktif (Belum Selesai)" domain="[('status', 'in', ['open', 'pending'])]"/>
<filter name="filter_resolved" string="Selesai (Resolved/Closed)" domain="[('status', 'in', ['resolved', 'closed'])]"/>
<separator/>
<filter name="filter_mock" string="Data Mock" domain="[('is_mock_data', '=', True)]"/>
<filter name="filter_live" string="Data Live API" domain="[('is_mock_data', '=', False)]"/>
<group>
<filter name="group_by_status" string="Status" context="{'group_by': 'status'}"/>
<filter name="group_by_responsibility" string="Tanggung Jawab" context="{'group_by': 'current_responsibility'}"/>
<filter name="group_by_responder" string="Agen (IT)" context="{'group_by': 'responder_name'}"/>
<filter name="group_by_requester" string="Pemohon (User)" context="{'group_by': 'requester_name'}"/>
</group>
</search>
</field>
</record>
<!-- ACTIONS -->
<record id="action_freshservice_ticket" model="ir.actions.act_window">
<field name="name">Daftar Tiket</field>
<field name="res_model">freshservice.ticket</field>
<field name="view_mode">list,form,kanban,pivot,graph</field>
<field name="search_view_id" ref="view_freshservice_ticket_search"/>
<field name="context">{'search_default_filter_open_progress': 0}</field>
<field name="limit">200</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Belum ada tiket Freshservice yang dimuat!
</p>
<p>
Klik tombol <strong>"Tarik Tiket Terbaru Freshservice"</strong> pada header tabel untuk menarik daftar tiket dari Freshservice secara otomatis.
</p>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- TOP LEVEL ROOT MENU -->
<menuitem id="menu_freshservice_root"
name="Ticket Audit"
web_icon="fa fa-clock-o,#34495e,#ffffff"
sequence="20"/>
<!-- SERVER ACTION: Buka Menu Sekaligus Auto-Fetch Data Terbaru Freshservice -->
<record id="action_server_open_freshservice_tickets" model="ir.actions.server">
<field name="name">Buka &amp; Auto-Fetch Tiket Freshservice</field>
<field name="model_id" ref="model_freshservice_ticket"/>
<field name="state">code</field>
<field name="code">
action = model.action_open_and_fetch_tickets()
</field>
</record>
<!-- MAIN MENUS -->
<menuitem id="menu_freshservice_tickets"
name="Daftar Tiket"
parent="menu_freshservice_root"
action="action_server_open_freshservice_tickets"
sequence="10"/>
<menuitem id="menu_freshservice_stage_logs"
name="Audit Trail Riwayat Status"
parent="menu_freshservice_root"
action="action_freshservice_ticket_stage_log"
sequence="20"/>
<menuitem id="menu_freshservice_bottleneck_wizard"
name="Laporan Bottleneck (Excel)"
parent="menu_freshservice_root"
action="action_freshservice_ticket_bottleneck_wizard"
sequence="30"/>
<!-- CONFIGURATION MENU -->
<menuitem id="menu_freshservice_config_parent"
name="Konfigurasi"
parent="menu_freshservice_root"
sequence="90"/>
<menuitem id="menu_freshservice_config_settings"
name="Pengaturan Integrasi"
parent="menu_freshservice_config_parent"
action="action_freshservice_config_settings"
sequence="10"/>
</odoo>

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="res_config_settings_view_form_freshservice" model="ir.ui.view">
<field name="name">res.config.settings.view.form.freshservice</field>
<field name="model">res.config.settings</field>
<field name="priority" eval="90"/>
<field name="inherit_id" ref="base.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//form" position="inside">
<app data-string="Freshservice Audit" string="Freshservice Audit" name="freshservice_ticket_audit">
<block title="Konfigurasi Integrasi Freshservice" name="freshservice_settings_block">
<setting id="freshservice_url_setting" help="URL Portal Freshservice perusahaan Anda">
<field name="freshservice_url"/>
</setting>
<setting id="freshservice_api_key_setting" help="API Key dari profil Freshservice (wajib diisi untuk Live API)">
<field name="freshservice_api_key" password="True"/>
</setting>
<setting id="freshservice_sync_limit_setting" help="Batas jumlah tiket terbaru yang ditarik saat membuka daftar tiket (default: 200)">
<field name="freshservice_sync_limit"/>
</setting>
<setting id="freshservice_mock_setting" help="Gunakan mock data sesuai format resmi Freshservice API untuk testing tanpa API Key asli">
<field name="freshservice_use_mock"/>
<div class="mt8">
<button name="action_test_freshservice_connection"
type="object"
string="Tes Koneksi / Cek Status"
class="btn-primary"
icon="fa-plug"/>
</div>
</setting>
</block>
</app>
</xpath>
</field>
</record>
<record id="action_freshservice_config_settings" model="ir.actions.act_window">
<field name="name">Pengaturan Freshservice</field>
<field name="res_model">res.config.settings</field>
<field name="view_mode">form</field>
<field name="target">main</field>
<field name="context">{'module': 'freshservice_ticket_audit'}</field>
</record>
</odoo>