[UPD] update colot template and logic UI
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user