[UPD] add dokumentasi fucntion
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user