[UPD] add dokumentasi fucntion

This commit is contained in:
Madanil
2026-10-20 08:57:07 +07:00
parent 62e545276f
commit c0a185478b
14 changed files with 203 additions and 23 deletions

View File

@@ -1,4 +1,7 @@
# -*- coding: utf-8 -*-
"""Package models: semua model Odoo untuk modul Freshservice.
Urutan import penting karena ticket bergantung pada stage_log dan conversation.
"""
from . import gmn_res_config_settings_v01
from . import gmn_freshservice_ticket_stage_log_v01
from . import gmn_freshservice_ticket_conversation_v01

View File

@@ -1,8 +1,14 @@
# -*- coding: utf-8 -*-
"""Model untuk menyimpan riwayat percakapan (chat/pesan/reply) dari tiket Freshservice.
Setiap baris adalah satu pesan, baik dari user maupun dari agen IT.
"""
from odoo import models, fields
class FreshserviceTicketConversation(models.Model):
_name = 'freshservice.ticket.conversation'
"""Menyimpan pesan/balasan pada tiket.
Dipakai untuk menampilkan isi chat di tab percakapan dan di file Excel.
"""
_description = 'Percakapan & Balasan Tiket Freshservice'
_order = 'created_at asc, id asc'
@@ -32,5 +38,9 @@ class FreshserviceTicketConversation(models.Model):
created_at = fields.Datetime(string='Waktu Kirim')
def _compute_sender_type(self):
"""Tentukan siapa yang kirim pesan:
- 'customer' jika incoming=True (pesan dari user)
- 'agent' jika incoming=False (balasan dari agen IT)
"""
for rec in self:
rec.sender_type = 'customer' if rec.incoming else 'agent'

View File

@@ -1,4 +1,7 @@
# -*- coding: utf-8 -*-
"""Model Stage Log: menyimpan riwayat perubahan status tiket beserta durasi.
Termasuk helper function untuk hitung jam kerja, hari kerja, dan format tampilan.
"""
from datetime import timedelta, datetime, time
from odoo import models, fields, api
@@ -240,6 +243,11 @@ def format_duration_human(hours_float):
return f"{days} Hari"
class FreshserviceTicketStageLog(models.Model):
"""Satu baris = satu fase/status di tiket.
Contoh: Open → In Progress → Pending User → Resolved.
Setiap baris punya waktu mulai, waktu selesai, siapa pelakunya,
dan siapa yang bertanggung jawab (Agent/User/Third Party).
"""
_name = 'freshservice.ticket.stage.log'
_description = 'Audit Trail Riwayat Status & Durasi Tiket'
_order = 'start_date asc, id asc'
@@ -345,11 +353,15 @@ class FreshserviceTicketStageLog(models.Model):
@api.depends('end_date')
def _compute_is_active(self):
"""Stage dianggap masih aktif kalau belum ada end_date (belum selesai)."""
for rec in self:
rec.is_active = not bool(rec.end_date)
@api.depends('start_date', 'end_date', 'is_active')
def _compute_working_metrics(self):
"""Hitung jam kerja efektif (08:30-17:30 WIB, Senin-Jumat)
dan konversi ke hari kerja (1 hari = 8 jam).
"""
for rec in self:
if rec.start_date:
wh = compute_working_hours(self.env, rec.start_date, rec.end_date)
@@ -363,6 +375,9 @@ class FreshserviceTicketStageLog(models.Model):
@api.depends('start_date', 'end_date')
def _compute_duration(self):
"""Hitung durasi dalam hari kalender dan jam total.
Kalau stage masih aktif, hitung sampai waktu sekarang.
"""
for rec in self:
if rec.start_date:
days = compute_calendar_days(self.env, rec.start_date, rec.end_date)

View File

@@ -1,4 +1,7 @@
# -*- coding: utf-8 -*-
"""Model utama Freshservice Ticket.
Menyimpan data tiket, metrik SLA, dan logika sinkronisasi dari Freshservice API.
"""
import re
import logging
from datetime import datetime, timedelta, timezone
@@ -10,6 +13,11 @@ from .gmn_freshservice_ticket_stage_log_v01 import format_duration_human, format
_logger = logging.getLogger(__name__)
class FreshserviceTicket(models.Model):
"""Model tiket Freshservice di Odoo.
Menyimpan info tiket (judul, status, prioritas, pemohon, agen),
metrik SLA (first response, resolution time, breakdown per tanggung jawab),
dan relasi ke stage log (audit trail) serta percakapan.
"""
_name = 'freshservice.ticket'
_description = 'Freshservice Ticket with Audit Trail'
_inherit = ['mail.thread', 'mail.activity.mixin']
@@ -212,6 +220,12 @@ class FreshserviceTicket(models.Model):
'stage_log_ids.duration_days', 'stage_log_ids.duration_hours', 'stage_log_ids.working_hours', 'stage_log_ids.is_active'
)
def _compute_time_metrics(self):
"""Hitung semua metrik waktu tiket:
- First Response Time (berapa lama sampai agen pertama kali merespon)
- Breakdown jam kerja per tanggung jawab (Agent/User/Meeting/Third Party)
- Resolution Time (total waktu sampai tiket selesai)
- Status tanggung jawab saat ini (siapa yang harus action)
"""
for rec in self:
# 1. Hitung First Response
if rec.ticket_created_at and rec.first_responded_at:
@@ -302,7 +316,9 @@ class FreshserviceTicket(models.Model):
}
def action_assign_to_me(self):
"""Menugaskan tiket ini ke user Odoo yang sedang login"""
"""Tombol 'Assign ke Saya': tugaskan tiket ini ke user yang sedang login.
Otomatis catat di chatter siapa yang ambil alih tiket.
"""
self.ensure_one()
current_user = self.env.user
vals = {'user_id': current_user.id}
@@ -348,10 +364,12 @@ class FreshserviceTicket(models.Model):
return False, ""
def action_sync_from_freshservice(self):
"""
Sinkronisasi data dari Freshservice (Mock Engine atau Live API sesuai konfigurasi).
Memetakan payload JSON ke Model Tiket, Audit Trail Logs, dan Percakapan.
Mendukung concurrency row-lock untuk keamanan multi-user.
"""Tarik data lengkap tiket dari Freshservice API.
Proses:
1. Ambil detail tiket (subject, status, requester, dll)
2. Ambil audit trail activities lalu rekonstruksi jadi stage log
3. Ambil percakapan (reply/note)
Pakai row-lock supaya aman kalau 2 user sync bersamaan.
"""
self.ensure_one()
@@ -959,7 +977,9 @@ class FreshserviceTicket(models.Model):
@api.model
def _extract_ticket_ids_from_domain(self, domain):
"""Ambil angka ticket ID dari domain pencarian agar bisa fetch on-demand."""
"""Cari angka-angka ticket ID dari domain pencarian Odoo.
Contoh: domain [('external_ticket_id', '=', '992')] → return ['992']
"""
ticket_ids = []
if not domain:
return ticket_ids
@@ -982,7 +1002,9 @@ class FreshserviceTicket(models.Model):
@api.model
def _fetch_and_create_ticket_by_external_id(self, ticket_id):
"""Ambil tiket tertentu dari Freshservice jika belum ada di Odoo."""
"""Cari tiket di Odoo berdasarkan ID Freshservice.
Kalau belum ada, buat record baru lalu langsung sync dari API.
"""
if ticket_id in (None, ''):
return self.browse()
@@ -1016,10 +1038,8 @@ class FreshserviceTicket(models.Model):
@api.model
def action_open_and_fetch_tickets(self, tiket_id = None):
"""
Action menu: Otomatis get tiket terbaru dari Freshservice saat membuka menu 'Daftar Tiket',
lalu langsung membuka tampilan list tiket secara real-time!
Batas jumlah tiket disesuaikan dengan konfigurasi di Settings.
"""Dipanggil saat user klik menu 'Daftar Tiket'.
Otomatis tarik tiket terbaru dari Freshservice, lalu buka halaman list.
"""
try:
self._auto_sync_recent_tickets(limit=None, raise_if_empty=False, tiket_id=tiket_id)
@@ -1038,6 +1058,9 @@ class FreshserviceTicket(models.Model):
@api.model
def name_search(self, name='', args=None, operator='ilike', limit=100):
"""Override name_search: kalau user ketik nomor tiket yang belum ada di Odoo,
otomatis fetch dari Freshservice dulu baru tampilkan.
"""
if name and isinstance(name, str):
ticket_ids = re.findall(r'\d+', name.strip())
for ticket_id in ticket_ids:
@@ -1047,9 +1070,9 @@ class FreshserviceTicket(models.Model):
@api.model
def web_search_read(self, domain, specification, offset=0, limit=None, order=None, count_limit=None):
"""
Otomatis fetch tiket terbaru dari Freshservice saat pengguna membuka / me-refresh (F5) halaman list tiket.
Batas jumlah tiket disesuaikan dengan konfigurasi di Settings.
"""Override web_search_read: otomatis sync tiket terbaru dari Freshservice
setiap kali user buka atau refresh halaman list tiket.
Juga cek kalau ada nomor tiket di pencarian yang belum ada di Odoo.
"""
if offset == 0 and not self.env.context.get('skip_auto_fetch'):
try:
@@ -1067,9 +1090,8 @@ class FreshserviceTicket(models.Model):
@api.readonly
def web_read(self, specification: dict[str, dict]) -> list[dict]:
"""
Ketika pengguna membuka form view (detail tiket), jika detail audit trail
belum di-fetch, otomatis fetch detail tiket tersebut saja secara on-demand.
"""Override web_read: saat user buka detail tiket (form view),
kalau audit trail belum pernah di-sync, otomatis fetch dari Freshservice.
"""
if len(self) == 1 and not self.env.context.get('skip_auto_detail_fetch'):
# Form view meminta detail fields seperti stage_log_ids, conversation_ids, atau audit trail
@@ -1090,8 +1112,8 @@ class FreshserviceTicket(models.Model):
return super().web_read(specification)
def action_sync_recent_tickets(self):
"""
Tombol manual 'Tarik Tiket Terbaru Freshservice' di header tabel jika user ingin refresh paksa.
"""Tombol manual 'Tarik Tiket Terbaru' di halaman list.
Dipanggil kalau user mau refresh paksa daftar tiket dari Freshservice.
"""
synced_count = self._auto_sync_recent_tickets(limit=None, raise_if_empty=True)
return {

View File

@@ -1,9 +1,16 @@
# -*- coding: utf-8 -*-
"""Konfigurasi Settings untuk koneksi Freshservice.
User bisa set URL, API Key, dan limit sync tiket dari menu Settings Odoo.
"""
from odoo import models, fields, api
from odoo.exceptions import UserError
import requests
class ResConfigSettings(models.TransientModel):
"""Halaman Settings untuk mengatur koneksi ke Freshservice API.
Bisa diakses dari menu Settings > Freshservice.
"""
_inherit = 'res.config.settings'
freshservice_url = fields.Char(
@@ -30,7 +37,9 @@ class ResConfigSettings(models.TransientModel):
)
def action_test_freshservice_connection(self):
"""Uji konektivitas ke server Freshservice"""
"""Tombol 'Test Connection' di halaman Settings.
Coba panggil API Freshservice untuk cek apakah URL dan API Key valid.
"""
self.ensure_one()
url = (self.freshservice_url or "").rstrip("/")
if not url: