[UPD] update colot template and logic UI

This commit is contained in:
Madanil
2026-10-19 10:15:38 +07:00
parent a44219a8f2
commit ef5ba7f159
15 changed files with 203 additions and 116 deletions

View File

@@ -25,7 +25,7 @@ class FreshserviceTicketConversation(models.Model):
sender_type = fields.Selection([
('agent', 'IT Support (Agent)'),
('customer', 'User (Customer)'),
('customer', 'User (Requester)'),
], string='Tipe Pengirim', compute='_compute_sender_type', store=True)
body_text = fields.Text(string='Isi Pesan')

View File

@@ -260,7 +260,7 @@ class FreshserviceTicketStageLog(models.Model):
('on_hold', 'On Hold (SLA is Paused)'),
('third_party', 'Waiting on Third Party (SLA is Paused)'),
('internal_discussion', 'Waiting on Internal Discussion (SLA is Paused)'),
('scheduled_meeting', 'Tunggu Bersama (Scheduled Meeting - SLA is Paused)'),
('scheduled_meeting', 'Scheduled Meeting (Waiting Together - SLA is Paused)'),
('external_activity', 'External Activity (SLA is Paused)'),
('resolved_idle', 'Resolved (Ticket Closed/Idle)'),
], string='Tanggung Jawab', required=True, default='internal', index=True)

View File

@@ -64,7 +64,7 @@ class FreshserviceTicket(models.Model):
first_responded_at = fields.Datetime(string='Waktu First Response')
resolved_at = fields.Datetime(string='Waktu Selesai (Resolved)')
is_mock_data = fields.Boolean(string='Menggunakan Data Mock', default=True, readonly=True)
is_mock_data = fields.Boolean(string='Menggunakan Data Mock', default=False, readonly=True)
is_detail_synced = fields.Boolean(
string='Detail Lengkap Tersinkron',
default=False,
@@ -134,6 +134,40 @@ class FreshserviceTicket(models.Model):
store=True
)
shared_wait_time_days = fields.Integer(
string='Scheduled Meeting (Days)',
compute='_compute_time_metrics',
store=True
)
shared_wait_time_hours = fields.Float(
string='Scheduled Meeting (Hours)',
compute='_compute_time_metrics',
store=True,
digits=(12, 2)
)
shared_wait_time_display = fields.Char(
string='Scheduled Meeting',
compute='_compute_time_metrics',
store=True
)
third_party_time_days = fields.Integer(
string='Waiting on Third Party (Hari)',
compute='_compute_time_metrics',
store=True
)
third_party_time_hours = fields.Float(
string='Waiting on Third Party (Jam)',
compute='_compute_time_metrics',
store=True,
digits=(12, 2)
)
third_party_time_display = fields.Char(
string='Waiting on Third Party',
compute='_compute_time_metrics',
store=True
)
resolution_time_days = fields.Integer(
string='Waktu Penyelesaian (Hari)',
compute='_compute_time_metrics',
@@ -153,7 +187,7 @@ class FreshserviceTicket(models.Model):
current_responsibility = fields.Selection([
('internal', 'Waiting on Us (Internal)'),
('customer', 'Waiting on User (Customer)'),
('customer', 'Waiting on User (Requester)'),
('closed', 'Tiket Selesai'),
], string='Status Tanggung Jawab Saat Ini', compute='_compute_time_metrics', store=True)
@@ -171,7 +205,7 @@ class FreshserviceTicket(models.Model):
@api.depends(
'ticket_created_at', 'first_responded_at', 'resolved_at', 'status', 'is_detail_synced',
'stage_log_ids.duration_days', 'stage_log_ids.duration_hours', 'stage_log_ids.is_active'
'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):
for rec in self:
@@ -190,10 +224,15 @@ class FreshserviceTicket(models.Model):
rec.first_response_time_display = "Belum Ada Respon"
# 2. Hitung total SLA berdasarkan jam kerja efektif, bukan durasi kalender.
agent_hours = sum(l.working_hours for l in rec.stage_log_ids if l.responsibility == 'internal')
# Mengikuti Aturan 9: Internal Hold & Internal Discussion dibebankan ke Agen (Waiting on Us).
agent_hours = sum(l.working_hours for l in rec.stage_log_ids if l.responsibility in ('internal', 'on_hold', 'internal_discussion'))
cust_hours = sum(l.working_hours for l in rec.stage_log_ids if l.responsibility == 'customer')
shared_wait_hours = sum(l.working_hours for l in rec.stage_log_ids if l.responsibility == 'scheduled_meeting')
third_party_hours = sum(l.working_hours for l in rec.stage_log_ids if l.responsibility == 'third_party')
agent_days = round(agent_hours / 8.0, 2)
cust_days = round(cust_hours / 8.0, 2)
shared_wait_days = round(shared_wait_hours / 8.0, 2)
third_party_days = round(third_party_hours / 8.0, 2)
# Resolved/closed dan aktivitas SLA-paused tidak dibebankan ke user/IT.
total_working_hours = sum(l.working_hours for l in rec.stage_log_ids)
@@ -207,6 +246,14 @@ class FreshserviceTicket(models.Model):
rec.total_customer_time_days = round(cust_days)
rec.total_customer_time_display = format_duration_days(round(cust_days)) if (rec.is_detail_synced or rec.stage_log_ids) else "-"
rec.shared_wait_time_hours = round(shared_wait_hours, 2)
rec.shared_wait_time_days = round(shared_wait_days)
rec.shared_wait_time_display = format_duration_days(round(shared_wait_days)) if (rec.is_detail_synced or rec.stage_log_ids) else "-"
rec.third_party_time_hours = round(third_party_hours, 2)
rec.third_party_time_days = round(third_party_days)
rec.third_party_time_display = format_duration_days(round(third_party_days)) if (rec.is_detail_synced or rec.stage_log_ids) else "-"
# 3. Hitung Resolution Time
if rec.ticket_created_at and rec.resolved_at:
delta_res = rec.resolved_at - rec.ticket_created_at
@@ -289,9 +336,9 @@ class FreshserviceTicket(models.Model):
return
get_param = self.env['ir.config_parameter'].sudo().get_param
base_url = get_param('freshservice_ticket_audit.freshservice_url', 'https://servicedesk.lyman.co.id')
api_key = get_param('freshservice_ticket_audit.freshservice_api_key', '')
use_mock = get_param('freshservice_ticket_audit.freshservice_use_mock', 'True') in ('True', '1', True)
base_url = get_param('gmn_freshservice.freshservice_url', 'https://servicedesk.lyman.co.id')
api_key = get_param('gmn_freshservice.freshservice_api_key', '')
use_mock = False
client = FreshserviceClient(base_url=base_url, api_key=api_key, use_mock=use_mock)
payload = client.fetch_ticket_full_data(self.external_ticket_id)
@@ -657,7 +704,7 @@ class FreshserviceTicket(models.Model):
'riwayat percakapan lama dipertahankan.', self.external_ticket_id,
)
mode_text = "MOCK ENGINE (Simulasi Format Resmi)" if use_mock else "LIVE FRESHSERVICE API"
mode_text = "LIVE FRESHSERVICE API"
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
@@ -669,25 +716,6 @@ class FreshserviceTicket(models.Model):
}
}
def action_toggle_mock_mode(self):
"""Toggle instan mode antara Mock dan Live API"""
param_obj = self.env['ir.config_parameter'].sudo()
current = param_obj.get_param('freshservice_ticket_audit.freshservice_use_mock', 'True') in ('True', '1', True)
new_val = not current
param_obj.set_param('freshservice_ticket_audit.freshservice_use_mock', str(new_val))
status_label = "MOCK DATA (Simulasi)" if new_val else "LIVE API FRESHSERVICE"
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': 'Mode Berhasil Diubah',
'message': f'Sistem sekarang beralih ke: {status_label}. Klik tombol "Tarik Data Freshservice" untuk menyegarkan data.',
'type': 'info',
'sticky': False,
}
}
# =========================================================================
# AUTO-FETCH SAAT BUKA HALAMAN LIST TIKET & MULTI-USER SYNCHRONIZATION
# =========================================================================
@@ -711,14 +739,14 @@ class FreshserviceTicket(models.Model):
param_obj = self.env['ir.config_parameter'].sudo()
if not limit:
try:
limit = int(param_obj.get_param('freshservice_ticket_audit.freshservice_sync_limit', 200) or 200)
limit = int(param_obj.get_param('gmn_freshservice.freshservice_sync_limit', 200) or 200)
except (ValueError, TypeError):
limit = 200
if limit <= 0:
limit = 200
now_ts = fields.Datetime.now().timestamp()
last_sync = param_obj.get_param('freshservice_ticket_audit.last_list_sync_time')
last_sync = param_obj.get_param('gmn_freshservice.last_list_sync_time')
if last_sync:
try:
# Throttling: Jangan panggil ulang jika baru saja di-fetch dalam 5 detik terakhir
@@ -726,14 +754,14 @@ class FreshserviceTicket(models.Model):
return 0
except Exception:
pass
param_obj.set_param('freshservice_ticket_audit.last_list_sync_time', str(now_ts))
param_obj.set_param('gmn_freshservice.last_list_sync_time', str(now_ts))
base_url = param_obj.get_param('freshservice_ticket_audit.freshservice_url', 'https://servicedesk.lyman.co.id')
api_key = param_obj.get_param('freshservice_ticket_audit.freshservice_api_key', '')
use_mock = param_obj.get_param('freshservice_ticket_audit.freshservice_use_mock', 'True') in ('True', '1', True)
base_url = param_obj.get_param('gmn_freshservice.freshservice_url', 'https://servicedesk.lyman.co.id')
api_key = param_obj.get_param('gmn_freshservice.freshservice_api_key', '')
use_mock = False
try:
client = FreshserviceClient(base_url=base_url, api_key=api_key, use_mock=use_mock)
client = FreshserviceClient(base_url=base_url, api_key=api_key, use_mock=False)
tickets_list = client.fetch_recent_tickets(limit=limit, tiket_id=tiket_id)
except Exception as e:
_logger.warning("Auto-fetch Freshservice gagal: %s", e)
@@ -884,10 +912,10 @@ class FreshserviceTicket(models.Model):
except Exception as e:
_logger.warning("Auto fetch saat buka menu tiket: %s", e)
action = self.env['ir.actions.act_window']._for_xml_id('freshservice_ticket_audit.action_freshservice_ticket')
action = self.env['ir.actions.act_window']._for_xml_id('gmn_freshservice.action_freshservice_ticket')
param_obj = self.env['ir.config_parameter'].sudo()
try:
sync_limit = int(param_obj.get_param('freshservice_ticket_audit.freshservice_sync_limit', 200) or 200)
sync_limit = int(param_obj.get_param('gmn_freshservice.freshservice_sync_limit', 200) or 200)
if sync_limit > 0:
action['limit'] = sync_limit
except Exception:

View File

@@ -8,24 +8,25 @@ class ResConfigSettings(models.TransientModel):
freshservice_url = fields.Char(
string='Freshservice URL',
config_parameter='freshservice_ticket_audit.freshservice_url',
config_parameter='gmn_freshservice.freshservice_url',
default='https://ptglobalmultimedianetwork.freshservice.com'
)
freshservice_api_key = fields.Char(
string='Freshservice API Key',
config_parameter='freshservice_ticket_audit.freshservice_api_key'
config_parameter='gmn_freshservice.freshservice_api_key'
)
freshservice_sync_limit = fields.Integer(
string='Limit Sinkronisasi Tiket',
config_parameter='freshservice_ticket_audit.freshservice_sync_limit',
config_parameter='gmn_freshservice.freshservice_sync_limit',
default=200,
help='Batas jumlah tiket terbaru yang ditarik dari Freshservice saat membuka halaman list tiket (default 200).'
)
freshservice_use_mock = fields.Boolean(
string='Gunakan Mock Data Engine',
config_parameter='freshservice_ticket_audit.freshservice_use_mock',
# default=True,
help='Jika dicentang, sistem menggunakan simulasi format JSON resmi Freshservice tanpa perlu API Key asli.'
config_parameter='gmn_freshservice.freshservice_use_mock',
default=False,
readonly=True,
help='Mode mock dinonaktifkan. Integrasi selalu menggunakan Live Freshservice API.'
)
def action_test_freshservice_connection(self):