#
🚨 Temuan Bottleneck Utama:
# {primary_bn_text}
@@ -751,10 +832,12 @@ class FreshserviceTicketBottleneckWizard(models.TransientModel):
def _build_ranking_sheets(
self, workbook, tickets, title_fmt, subtitle_fmt, section_fmt, th_fmt,
td_fmt, td_wrap_fmt, td_center_fmt, td_num_fmt, td_dec_fmt, td_pct_fmt, td_date_fmt,
- bn_badge_fmt, normal_badge_fmt, kpi_box_label, kpi_box_val, status_formats, sla_paused_fmt=None
+ bn_badge_fmt, normal_badge_fmt, kpi_box_label, kpi_box_val, status_formats,
+ sla_paused_fmt=None, scheduled_meeting_fmt=None
):
"""Membuat sheet ranking bottleneck untuk multi-tiket (bahan meeting evaluasi tim)."""
- ws = workbook.add_worksheet('Ranking Bottleneck Tiket')
+ scheduled_meeting_fmt = scheduled_meeting_fmt or sla_paused_fmt or td_center_fmt
+ ws = workbook.add_worksheet('Ticket Bottleneck Ranking')
ws.set_column('A:A', 5)
ws.set_column('B:B', 8)
ws.set_column('C:C', 14)
@@ -773,20 +856,20 @@ class FreshserviceTicketBottleneckWizard(models.TransientModel):
ws.set_column('P:P', 16)
ws.set_column('Q:Q', 16)
- ws.merge_range('B2:Q2', 'LAPORAN RANKING BOTTLENECK & EVALUASI SLA TIKET (BAHAN MEETING)', title_fmt)
+ ws.merge_range('B2:Q2', 'BOTTLENECK RANKING & SLA EVALUATION REPORT', title_fmt)
ws.write(
'B3',
- f'Dicetak: {datetime.now().strftime("%d-%m-%Y %H:%M:%S")} | Dicetak oleh: {self.env.user.name}',
+ f'Printed: {datetime.now().strftime("%d-%m-%Y %H:%M:%S")} | Printed by: {self.env.user.name}',
subtitle_fmt
)
headers = [
- 'Rank', 'ID Freshservice', 'Judul Tiket', 'Pemohon (User)', 'Departemen', 'Agen IT',
+ 'Rank', 'Freshservice ID', 'Ticket Subject', 'Requester (User)', 'Department', 'Agent Agent',
'Status',
- 'Total Hari Kalender', 'Total Jam Kerja (08:30-17:30)', 'Total Hari Kerja Efektif',
- 'Waktu IT (Hari Kalender)', 'Waktu IT (Jam Kerja)',
- 'Waktu User (Hari Kalender)', 'Waktu User (Jam Kerja)',
- 'Bottleneck Utama (Stage)', 'Durasi Bottleneck (Hari Kalender)', 'Durasi Bottleneck (Jam Kerja)'
+ 'Total Calendar Days', 'Total Working Hours (08:30-17:30)', 'Total Effective Workdays',
+ 'Agent Time (Calendar Days)', 'Agent Time (Working Hours)',
+ 'User Time (Calendar Days)', 'User Time (Working Hours)',
+ 'Primary Bottleneck (Stage)', 'Bottleneck Duration (Calendar Days)', 'Bottleneck Duration (Working Hours)'
]
for col_idx, h in enumerate(headers, start=1):
ws.write(4, col_idx, h, th_fmt)
@@ -805,7 +888,7 @@ class FreshserviceTicketBottleneckWizard(models.TransientModel):
top_wh = 0.0
if top_stage:
top_stage_name = top_stage[0].stage_name or '-'
- top_resp = 'Internal (IT)' if top_stage[0].responsibility == 'internal' else 'Customer (User)'
+ top_resp = 'Agent Active Work' if top_stage[0].responsibility == 'internal' else 'Waiting on Requester'
top_days = top_stage[0].duration_days
top_wh = compute_working_hours(self.env, top_stage[0].start_date, top_stage[0].end_date)
@@ -815,16 +898,16 @@ class FreshserviceTicketBottleneckWizard(models.TransientModel):
t_wh_cust = 0.0
for s in t.stage_log_ids:
s_wh = compute_working_hours(self.env, s.start_date, s.end_date)
- if s.responsibility == 'internal':
+ resp = s.override_responsibility or s.responsibility
+ if resp in ('internal', 'it_hold', 'on_hold', 'internal_discussion'):
t_wh_agent += s_wh
- elif s.responsibility == 'customer':
+ elif resp in ('customer', 'user_after_auto_resolve', 'needs_verification'):
t_wh_cust += s_wh
- # on_hold: SLA paused, tidak dihitung di IT maupun User
t_wd_eff = round(t_wh_total / 8.0, 2)
if rank_idx <= 5:
- slowest_tickets.append(f"#{t.external_ticket_id} ({t.name[:25]}... - {t.resolution_time_days} Hari / {t_wh_total:.1f} Jam Kerja)")
+ slowest_tickets.append(f"#{t.external_ticket_id} ({t.name[:25]}... - {t.resolution_time_days} Days / {t_wh_total:.1f} Working Hours)")
ws.write(row, 1, f"#{rank_idx}", bn_badge_fmt if rank_idx <= 3 else td_center_fmt)
ws.write(row, 2, f"#{t.external_ticket_id}", td_center_fmt)
@@ -847,7 +930,7 @@ class FreshserviceTicketBottleneckWizard(models.TransientModel):
row += 1
# SHEET 2: DETAIL STAGE LOG SEMUA TIKET
- ws2 = workbook.add_worksheet('Detail Stage Log Semua Tiket')
+ ws2 = workbook.add_worksheet('All Tickets Stage Logs')
ws2.set_column('A:A', 5)
ws2.set_column('B:B', 14)
ws2.set_column('C:C', 22)
@@ -862,13 +945,13 @@ class FreshserviceTicketBottleneckWizard(models.TransientModel):
ws2.set_column('L:L', 32)
ws2.set_column('M:M', 50)
- ws2.merge_range('B2:M2', 'RINCIAN SELURUH TRANSISI STAGE & DURASI TIKET', title_fmt)
+ ws2.merge_range('B2:M2', 'ALL TICKET STAGE TRANSITIONS & DURATIONS', title_fmt)
headers2 = [
- 'ID Tiket', 'Waktu Mulai (WIB)', 'Waktu Selesai (WIB)', 'Stage / Status',
- 'Tanggung Jawab', 'Pengguna',
- 'Hari Kalender', 'Jam Kalender',
- 'Jam Kerja (08:30-17:30)', 'Hari Kerja Efektif',
- 'Keterangan Aktivitas', 'Isi Balasan / Pesan'
+ 'Ticket ID', 'Start Time (WIB)', 'End Time (WIB)', 'Stage / Status',
+ 'Responsibility', 'Actor',
+ 'Calendar Days', 'Calendar Hours',
+ 'Working Hours (08:30-17:30)', 'Effective Workdays',
+ 'Activity Details', 'Reply / Message Content'
]
for col_idx, h in enumerate(headers2, start=1):
ws2.write(3, col_idx, h, th_fmt)
@@ -877,17 +960,23 @@ class FreshserviceTicketBottleneckWizard(models.TransientModel):
for t in sorted_tickets:
for log in t.stage_log_ids.sorted('start_date'):
if log.responsibility == 'internal':
- resp_label = 'Internal (Waiting on Us)'
+ resp_label = 'Agent Active Work'
elif log.responsibility == 'customer':
- resp_label = 'Customer (Waiting on User)'
- elif log.responsibility == 'on_hold':
- resp_label = 'On Hold (SLA is Paused)'
+ resp_label = 'Waiting on Requester'
+ elif log.responsibility in ('on_hold', 'it_hold', 'internal_discussion'):
+ resp_label = 'Agent Hold & Discussion'
elif log.responsibility == 'third_party':
- resp_label = 'Waiting on Third Party (SLA is Paused)'
+ resp_label = 'Waiting on Third Party'
elif log.responsibility == 'scheduled_meeting':
- resp_label = 'Scheduled Meeting (Waiting Together - SLA is Paused)'
+ resp_label = 'Scheduled Meeting (Shared)'
+ elif log.responsibility == 'user_after_auto_resolve':
+ resp_label = 'Waiting on User After Auto-Resolve'
+ elif log.responsibility == 'needs_verification':
+ resp_label = 'Waiting on User Verification'
+ elif log.responsibility == 'resolved_idle':
+ resp_label = 'Resolved / Idle Time'
else:
- resp_label = 'Waiting on Internal Discussion (SLA is Paused)'
+ resp_label = 'Waiting on Requester'
msg_body = self._get_stage_conversation_body(log, t)
raw_act_main = log.activity_content or ''
if 'Isi Pesan:' in raw_act_main:
@@ -900,7 +989,7 @@ class FreshserviceTicketBottleneckWizard(models.TransientModel):
from datetime import timedelta as _td2
def _wib(dt):
if not dt:
- return 'Aktif'
+ return 'Active'
try:
return (dt + _td2(hours=7)).strftime('%Y-%m-%d %H:%M')
except Exception:
@@ -910,8 +999,7 @@ class FreshserviceTicketBottleneckWizard(models.TransientModel):
ws2.write(row2, 2, _wib(log.start_date), td_center_fmt)
ws2.write(row2, 3, _wib(log.end_date), td_center_fmt)
ws2.write(row2, 4, log.stage_name or '-', td_fmt)
- resp_fmt = bn_badge_fmt if log.responsibility in ('internal', 'on_hold', 'internal_discussion') else (normal_badge_fmt if log.responsibility == 'customer' else (scheduled_meeting_fmt if log.responsibility == 'scheduled_meeting' and scheduled_meeting_fmt else (sla_paused_fmt or td_center_fmt)))
- # Waiting on User memakai normal_badge_fmt orange/kuning.
+ resp_fmt = bn_badge_fmt if log.responsibility in ('internal', 'on_hold', 'it_hold', 'internal_discussion') else (normal_badge_fmt if log.responsibility in ('customer', 'user_after_auto_resolve', 'needs_verification') else (scheduled_meeting_fmt if log.responsibility == 'scheduled_meeting' and scheduled_meeting_fmt else (sla_paused_fmt or td_center_fmt)))
ws2.write(row2, 5, resp_label, resp_fmt)
ws2.write(row2, 6, log.actor_name or '-', td_fmt)
ws2.write(row2, 7, calc_log_days, td_num_fmt)
@@ -925,15 +1013,16 @@ class FreshserviceTicketBottleneckWizard(models.TransientModel):
avg_days = sum(sorted_tickets.mapped('resolution_time_days')) / max(1, len(sorted_tickets))
html_out = f"""
-
📋 Laporan Ranking Bottleneck Multi-Tiket Berhasil Dibuat
-
Total Tiket yang Dievaluasi: {len(sorted_tickets)} Tiket
-
Rata-rata Waktu Penyelesaian: {avg_days:.1f} Hari Kalender
+
📋 Multi-Ticket Bottleneck Ranking Report Generated
+
Total Evaluated Tickets: {len(sorted_tickets)} Tickets
+
Average Resolution Time: {avg_days:.1f} Calendar Days
-
⚠️ Top 5 Tiket dengan Durasi Terlama:
+
⚠️ Top 5 Tickets with Longest Duration:
{''.join(f'- {item}
' for item in slowest_tickets)}
-
File Excel siap digunakan sebagai bahan evaluasi dan meeting manajemen.
+
Excel file is ready for team evaluation and management meetings.
"""
return html_out
+ return html_out