[UPD] initial commit
This commit is contained in:
9
gmn_freshservice/models/__init__.py
Normal file
9
gmn_freshservice/models/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
# -*- 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_holiday_v01
|
||||
from . import gmn_freshservice_ticket_stage_log_v01
|
||||
from . import gmn_freshservice_ticket_conversation_v01
|
||||
from . import gmn_freshservice_ticket_v01
|
||||
BIN
gmn_freshservice/models/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
gmn_freshservice/models/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
gmn_freshservice/models/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
gmn_freshservice/models/__pycache__/__init__.cpython-314.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
276
gmn_freshservice/models/gmn_freshservice_holiday_v01.py
Normal file
276
gmn_freshservice/models/gmn_freshservice_holiday_v01.py
Normal file
@@ -0,0 +1,276 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Model Master Data Hari Libur Nasional & Cuti Bersama.
|
||||
Menyimpan daftar tanggal merah resmi Indonesia dan cuti bersama perusahaan
|
||||
yang digunakan oleh Working Hours Engine untuk memotong jam kerja secara presisi.
|
||||
|
||||
Mendukung:
|
||||
1. Input manual melalui antarmuka Tree/Form Odoo.
|
||||
2. Impor massal dari file spreadsheet Excel (.xlsx).
|
||||
3. Pengunduhan template spreadsheet Excel (.xlsx) dengan contoh data.
|
||||
4. Pemuatan instan data baku hari libur nasional Indonesia (2024-2026).
|
||||
"""
|
||||
import io
|
||||
import base64
|
||||
import logging
|
||||
from datetime import datetime, date
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import UserError
|
||||
import xlsxwriter
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
# Daftar Baku Hari Libur Nasional & Cuti Bersama Indonesia (2024-2026)
|
||||
DEFAULT_INDONESIA_HOLIDAYS = [
|
||||
# 2024
|
||||
('2024-01-01', 'Tahun Baru 2024 Masehi', 'public', 'SKB 3 Menteri 2024'),
|
||||
('2024-02-08', 'Isra Mikraj Nabi Muhammad SAW', 'public', 'SKB 3 Menteri 2024'),
|
||||
('2024-02-09', 'Cuti Bersama Tahun Baru Imlek 2575 Kongzili', 'joint_leave', 'SKB 3 Menteri 2024'),
|
||||
('2024-02-10', 'Tahun Baru Imlek 2575 Kongzili', 'public', 'SKB 3 Menteri 2024'),
|
||||
('2024-03-11', 'Hari Suci Nyepi Tahun Baru Saka 1946', 'public', 'SKB 3 Menteri 2024'),
|
||||
('2024-03-12', 'Cuti Bersama Hari Suci Nyepi', 'joint_leave', 'SKB 3 Menteri 2024'),
|
||||
('2024-03-29', 'Wafat Isa Al Masih', 'public', 'SKB 3 Menteri 2024'),
|
||||
('2024-03-31', 'Hari Paskah', 'public', 'SKB 3 Menteri 2024'),
|
||||
('2024-04-08', 'Cuti Bersama Hari Raya Idul Fitri 1445 H', 'joint_leave', 'SKB 3 Menteri 2024'),
|
||||
('2024-04-09', 'Cuti Bersama Hari Raya Idul Fitri 1445 H', 'joint_leave', 'SKB 3 Menteri 2024'),
|
||||
('2024-04-10', 'Hari Raya Idul Fitri 1445 Hijriah', 'public', 'SKB 3 Menteri 2024'),
|
||||
('2024-04-11', 'Hari Raya Idul Fitri 1445 Hijriah', 'public', 'SKB 3 Menteri 2024'),
|
||||
('2024-04-12', 'Cuti Bersama Hari Raya Idul Fitri 1445 H', 'joint_leave', 'SKB 3 Menteri 2024'),
|
||||
('2024-04-15', 'Cuti Bersama Hari Raya Idul Fitri 1445 H', 'joint_leave', 'SKB 3 Menteri 2024'),
|
||||
('2024-05-01', 'Hari Buruh Internasional', 'public', 'SKB 3 Menteri 2024'),
|
||||
('2024-05-09', 'Kenaikan Isa Al Masih', 'public', 'SKB 3 Menteri 2024'),
|
||||
('2024-05-10', 'Cuti Bersama Kenaikan Isa Al Masih', 'joint_leave', 'SKB 3 Menteri 2024'),
|
||||
('2024-05-23', 'Hari Raya Waisak 2568 BE', 'public', 'SKB 3 Menteri 2024'),
|
||||
('2024-05-24', 'Cuti Bersama Hari Raya Waisak', 'joint_leave', 'SKB 3 Menteri 2024'),
|
||||
('2024-06-01', 'Hari Lahir Pancasila', 'public', 'SKB 3 Menteri 2024'),
|
||||
('2024-06-17', 'Hari Raya Idul Adha 1445 Hijriah', 'public', 'SKB 3 Menteri 2024'),
|
||||
('2024-06-18', 'Cuti Bersama Hari Raya Idul Adha', 'joint_leave', 'SKB 3 Menteri 2024'),
|
||||
('2024-07-07', 'Tahun Baru Islam 1446 Hijriah', 'public', 'SKB 3 Menteri 2024'),
|
||||
('2024-08-17', 'Hari Proklamasi Kemerdekaan RI ke-79', 'public', 'SKB 3 Menteri 2024'),
|
||||
('2024-09-16', 'Maulid Nabi Muhammad SAW', 'public', 'SKB 3 Menteri 2024'),
|
||||
('2024-12-25', 'Hari Raya Natal', 'public', 'SKB 3 Menteri 2024'),
|
||||
('2024-12-26', 'Cuti Bersama Hari Raya Natal', 'joint_leave', 'SKB 3 Menteri 2024'),
|
||||
# 2025
|
||||
('2025-01-01', 'Tahun Baru 2025 Masehi', 'public', 'SKB 3 Menteri 2025'),
|
||||
('2025-01-27', 'Isra Mikraj Nabi Muhammad SAW', 'public', 'SKB 3 Menteri 2025'),
|
||||
('2025-01-28', 'Cuti Bersama Tahun Baru Imlek 2576 Kongzili', 'joint_leave', 'SKB 3 Menteri 2025'),
|
||||
('2025-01-29', 'Tahun Baru Imlek 2576 Kongzili', 'public', 'SKB 3 Menteri 2025'),
|
||||
('2025-03-28', 'Cuti Bersama Hari Suci Nyepi', 'joint_leave', 'SKB 3 Menteri 2025'),
|
||||
('2025-03-29', 'Hari Suci Nyepi Tahun Baru Saka 1947', 'public', 'SKB 3 Menteri 2025'),
|
||||
('2025-03-31', 'Hari Raya Idul Fitri 1446 Hijriah', 'public', 'SKB 3 Menteri 2025'),
|
||||
('2025-04-01', 'Hari Raya Idul Fitri 1446 Hijriah', 'public', 'SKB 3 Menteri 2025'),
|
||||
('2025-04-02', 'Cuti Bersama Hari Raya Idul Fitri 1446 H', 'joint_leave', 'SKB 3 Menteri 2025'),
|
||||
('2025-04-03', 'Cuti Bersama Hari Raya Idul Fitri 1446 H', 'joint_leave', 'SKB 3 Menteri 2025'),
|
||||
('2025-04-04', 'Cuti Bersama Hari Raya Idul Fitri 1446 H', 'joint_leave', 'SKB 3 Menteri 2025'),
|
||||
('2025-04-07', 'Cuti Bersama Hari Raya Idul Fitri 1446 H', 'joint_leave', 'SKB 3 Menteri 2025'),
|
||||
('2025-04-18', 'Wafat Isa Al Masih', 'public', 'SKB 3 Menteri 2025'),
|
||||
('2025-04-20', 'Hari Paskah', 'public', 'SKB 3 Menteri 2025'),
|
||||
('2025-05-01', 'Hari Buruh Internasional', 'public', 'SKB 3 Menteri 2025'),
|
||||
('2025-05-12', 'Hari Raya Waisak 2569 BE', 'public', 'SKB 3 Menteri 2025'),
|
||||
('2025-05-13', 'Cuti Bersama Hari Raya Waisak', 'joint_leave', 'SKB 3 Menteri 2025'),
|
||||
('2025-05-29', 'Kenaikan Isa Al Masih', 'public', 'SKB 3 Menteri 2025'),
|
||||
('2025-05-30', 'Cuti Bersama Kenaikan Isa Al Masih', 'joint_leave', 'SKB 3 Menteri 2025'),
|
||||
('2025-06-01', 'Hari Lahir Pancasila', 'public', 'SKB 3 Menteri 2025'),
|
||||
('2025-06-06', 'Hari Raya Idul Adha 1446 Hijriah', 'public', 'SKB 3 Menteri 2025'),
|
||||
('2025-06-09', 'Cuti Bersama Hari Raya Idul Adha', 'joint_leave', 'SKB 3 Menteri 2025'),
|
||||
('2025-06-27', '1 Muharam Tahun Baru Islam 1447 H', 'public', 'SKB 3 Menteri 2025'),
|
||||
('2025-08-17', 'Hari Proklamasi Kemerdekaan RI ke-80', 'public', 'SKB 3 Menteri 2025'),
|
||||
('2025-09-05', 'Maulid Nabi Muhammad SAW', 'public', 'SKB 3 Menteri 2025'),
|
||||
('2025-12-25', 'Hari Raya Natal', 'public', 'SKB 3 Menteri 2025'),
|
||||
('2025-12-26', 'Cuti Bersama Hari Raya Natal', 'joint_leave', 'SKB 3 Menteri 2025'),
|
||||
# 2026
|
||||
('2026-01-01', 'Tahun Baru 2026 Masehi', 'public', 'SKB 3 Menteri 2026'),
|
||||
('2026-01-16', 'Isra Mikraj Nabi Muhammad SAW', 'public', 'SKB 3 Menteri 2026'),
|
||||
('2026-02-17', 'Tahun Baru Imlek 2577 Kongzili', 'public', 'SKB 3 Menteri 2026'),
|
||||
('2026-03-19', 'Hari Raya Idul Fitri 1447 Hijriah', 'public', 'SKB 3 Menteri 2026'),
|
||||
('2026-03-20', 'Hari Raya Idul Fitri 1447 Hijriah', 'public', 'SKB 3 Menteri 2026'),
|
||||
('2026-03-21', 'Hari Suci Nyepi Tahun Baru Saka 1948', 'public', 'SKB 3 Menteri 2026'),
|
||||
('2026-04-03', 'Wafat Isa Al Masih', 'public', 'SKB 3 Menteri 2026'),
|
||||
('2026-05-01', 'Hari Buruh Internasional', 'public', 'SKB 3 Menteri 2026'),
|
||||
('2026-05-14', 'Kenaikan Isa Al Masih', 'public', 'SKB 3 Menteri 2026'),
|
||||
('2026-05-31', 'Hari Raya Waisak 2570 BE', 'public', 'SKB 3 Menteri 2026'),
|
||||
('2026-06-01', 'Hari Lahir Pancasila', 'public', 'SKB 3 Menteri 2026'),
|
||||
('2026-06-27', 'Hari Raya Idul Adha 1447 Hijriah', 'public', 'SKB 3 Menteri 2026'),
|
||||
('2026-07-16', 'Tahun Baru Islam 1448 Hijriah', 'public', 'SKB 3 Menteri 2026'),
|
||||
('2026-08-17', 'Hari Kemerdekaan Republik Indonesia ke-81', 'public', 'SKB 3 Menteri 2026'),
|
||||
('2026-08-25', 'Maulid Nabi Muhammad SAW', 'public', 'SKB 3 Menteri 2026'),
|
||||
('2026-12-25', 'Hari Raya Natal', 'public', 'SKB 3 Menteri 2026'),
|
||||
]
|
||||
|
||||
|
||||
class FreshserviceHoliday(models.Model):
|
||||
"""Model Master Data Hari Libur Nasional & Cuti Bersama.
|
||||
Menyimpan tanggal merah yang dijadikan acuan pemotongan jam kerja oleh modul.
|
||||
"""
|
||||
_name = 'freshservice.holiday'
|
||||
_description = 'Master Data Hari Libur & Cuti Bersama Indonesia'
|
||||
_order = 'date desc, id desc'
|
||||
|
||||
name = fields.Char(string='Nama Hari Libur / Keterangan', required=True, index=True)
|
||||
date = fields.Date(string='Tanggal Libur', required=True, index=True)
|
||||
holiday_type = fields.Selection([
|
||||
('public', 'Hari Libur Nasional'),
|
||||
('joint_leave', 'Cuti Bersama'),
|
||||
('company', 'Libur Khusus Perusahaan'),
|
||||
], string='Jenis Libur', default='public', required=True)
|
||||
year = fields.Integer(string='Tahun', compute='_compute_year', store=True, index=True)
|
||||
active = fields.Boolean(string='Aktif', default=True)
|
||||
notes = fields.Text(string='Catatan Tambahan')
|
||||
|
||||
_sql_constraints = [
|
||||
('date_unique', 'unique(date)', 'Tanggal hari libur ini sudah terdaftar dalam sistem!')
|
||||
]
|
||||
|
||||
@api.depends('date')
|
||||
def _compute_year(self):
|
||||
"""Menghitung tahun secara otomatis dari tanggal libur."""
|
||||
for rec in self:
|
||||
rec.year = rec.date.year if rec.date else 0
|
||||
|
||||
def action_populate_default_holidays(self, *args, **kwargs):
|
||||
"""Memuat daftar hari libur baku Indonesia (2024-2026) ke dalam database.
|
||||
Hanya menambahkan data yang belum terdaftar tanpa menimpa data yang sudah ada.
|
||||
"""
|
||||
created_count = 0
|
||||
for date_str, name, h_type, notes in DEFAULT_INDONESIA_HOLIDAYS:
|
||||
existing = self.search([('date', '=', date_str)], limit=1)
|
||||
if not existing:
|
||||
self.create({
|
||||
'date': date_str,
|
||||
'name': name,
|
||||
'holiday_type': h_type,
|
||||
'notes': notes,
|
||||
'active': True,
|
||||
})
|
||||
created_count += 1
|
||||
|
||||
message = _('Berhasil memuat %d hari libur nasional baku ke database.') % created_count if created_count > 0 else _('Seluruh hari libur nasional baku sudah terdaftar.')
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'title': _('Pemuatan Data Baku Selesai'),
|
||||
'message': message,
|
||||
'type': 'success',
|
||||
'sticky': False,
|
||||
'next': {'type': 'ir.actions.client', 'tag': 'reload'},
|
||||
}
|
||||
}
|
||||
|
||||
def action_download_template(self, *args, **kwargs):
|
||||
"""Membuat dan mengunduh file template Excel (.xlsx) untuk impor hari libur.
|
||||
Template dilengkapi dengan format kolom yang benar dan contoh data resmi 2026.
|
||||
"""
|
||||
output = io.BytesIO()
|
||||
workbook = xlsxwriter.Workbook(output, {'in_memory': True})
|
||||
worksheet = workbook.add_worksheet('Template Hari Libur')
|
||||
|
||||
# Formatter Styling Excel
|
||||
header_banner_fmt = workbook.add_format({
|
||||
'bold': True, 'font_size': 14, 'font_name': 'Calibri',
|
||||
'font_color': '#FFFFFF', 'bg_color': '#1B365D',
|
||||
'align': 'left', 'valign': 'vcenter'
|
||||
})
|
||||
info_fmt = workbook.add_format({
|
||||
'font_size': 9, 'font_name': 'Calibri', 'font_color': '#5D6D7E',
|
||||
'italic': True, 'valign': 'vcenter'
|
||||
})
|
||||
th_fmt = workbook.add_format({
|
||||
'bold': True, 'font_size': 10, 'font_name': 'Calibri',
|
||||
'font_color': '#FFFFFF', 'bg_color': '#2E4053',
|
||||
'align': 'center', 'valign': 'vcenter', 'border': 1, 'border_color': '#BDC3C7'
|
||||
})
|
||||
td_center_fmt = workbook.add_format({
|
||||
'font_size': 9, 'font_name': 'Calibri', 'align': 'center',
|
||||
'border': 1, 'border_color': '#D5D8DC', 'valign': 'vcenter'
|
||||
})
|
||||
td_date_fmt = workbook.add_format({
|
||||
'font_size': 9, 'font_name': 'Calibri', 'align': 'center',
|
||||
'border': 1, 'border_color': '#D5D8DC', 'valign': 'vcenter',
|
||||
'num_format': 'yyyy-mm-dd'
|
||||
})
|
||||
td_left_fmt = workbook.add_format({
|
||||
'font_size': 9, 'font_name': 'Calibri', 'align': 'left',
|
||||
'border': 1, 'border_color': '#D5D8DC', 'valign': 'vcenter'
|
||||
})
|
||||
|
||||
# Set Lebar Kolom
|
||||
worksheet.set_column('A:A', 6)
|
||||
worksheet.set_column('B:B', 16)
|
||||
worksheet.set_column('C:C', 40)
|
||||
worksheet.set_column('D:D', 26)
|
||||
worksheet.set_column('E:E', 35)
|
||||
|
||||
# Header Title Banner
|
||||
worksheet.merge_range('A1:E1', 'TEMPLATE IMPORT HARI LIBUR NASIONAL & CUTI BERSAMA', header_banner_fmt)
|
||||
worksheet.set_row(0, 30)
|
||||
|
||||
# Petunjuk Pengisian
|
||||
worksheet.write('A2', 'PETUNJUK PENGISIAN:', info_fmt)
|
||||
worksheet.write('A3', '1. Kolom Tanggal Libur (*) dan Nama Hari Libur (*) wajib diisi. Format tanggal: YYYY-MM-DD (contoh: 2026-08-17).', info_fmt)
|
||||
worksheet.write('A4', '2. Kolom Jenis Libur dapat diisi: "Hari Libur Nasional", "Cuti Bersama", atau "Libur Khusus Perusahaan".', info_fmt)
|
||||
worksheet.write('A5', '3. Anda dapat mengedit, menambah, atau menghapus baris contoh di bawah ini sebelum mengimpor.', info_fmt)
|
||||
|
||||
# Table Headers (Baris 7)
|
||||
headers = ['No', 'Tanggal Libur (*)', 'Nama Hari Libur (*)', 'Jenis Libur', 'Keterangan']
|
||||
for col_idx, h in enumerate(headers):
|
||||
worksheet.write(6, col_idx, h, th_fmt)
|
||||
worksheet.set_row(6, 24)
|
||||
|
||||
# Contoh Data (Hari Libur Resmi 2026 sebagai sampel lengkap)
|
||||
sample_data = [
|
||||
(1, '2026-01-01', 'Tahun Baru 2026 Masehi', 'Hari Libur Nasional', 'Libur Nasional Resmi'),
|
||||
(2, '2026-01-16', 'Isra Mikraj Nabi Muhammad SAW', 'Hari Libur Nasional', 'SKB 3 Menteri'),
|
||||
(3, '2026-02-17', 'Tahun Baru Imlek 2577 Kongzili', 'Hari Libur Nasional', 'SKB 3 Menteri'),
|
||||
(4, '2026-03-19', 'Hari Raya Idul Fitri 1447 Hijriah', 'Hari Libur Nasional', 'SKB 3 Menteri'),
|
||||
(5, '2026-03-20', 'Hari Raya Idul Fitri 1447 Hijriah', 'Hari Libur Nasional', 'SKB 3 Menteri'),
|
||||
(6, '2026-03-21', 'Hari Suci Nyepi Tahun Baru Saka 1948', 'Hari Libur Nasional', 'SKB 3 Menteri'),
|
||||
(7, '2026-04-03', 'Wafat Isa Al Masih', 'Hari Libur Nasional', 'SKB 3 Menteri'),
|
||||
(8, '2026-05-01', 'Hari Buruh Internasional', 'Hari Libur Nasional', 'Hari Buruh'),
|
||||
(9, '2026-05-14', 'Kenaikan Isa Al Masih', 'Hari Libur Nasional', 'SKB 3 Menteri'),
|
||||
(10, '2026-05-31', 'Hari Raya Waisak 2570 BE', 'Hari Libur Nasional', 'SKB 3 Menteri'),
|
||||
(11, '2026-06-01', 'Hari Lahir Pancasila', 'Hari Libur Nasional', 'Hari Lahir Pancasila'),
|
||||
(12, '2026-06-27', 'Hari Raya Idul Adha 1447 Hijriah', 'Hari Libur Nasional', 'SKB 3 Menteri'),
|
||||
(13, '2026-07-16', 'Tahun Baru Islam 1448 Hijriah', 'Hari Libur Nasional', 'SKB 3 Menteri'),
|
||||
(14, '2026-08-17', 'Hari Kemerdekaan Republik Indonesia ke-81', 'Hari Libur Nasional', 'Proklamasi Kemerdekaan'),
|
||||
(15, '2026-08-25', 'Maulid Nabi Muhammad SAW', 'Hari Libur Nasional', 'SKB 3 Menteri'),
|
||||
(16, '2026-12-25', 'Hari Raya Natal', 'Hari Libur Nasional', 'Hari Natal'),
|
||||
]
|
||||
|
||||
row = 7
|
||||
for no, dt_str, name, h_type, notes in sample_data:
|
||||
worksheet.write(row, 0, no, td_center_fmt)
|
||||
worksheet.write(row, 1, dt_str, td_date_fmt)
|
||||
worksheet.write(row, 2, name, td_left_fmt)
|
||||
worksheet.write(row, 3, h_type, td_left_fmt)
|
||||
worksheet.write(row, 4, notes, td_left_fmt)
|
||||
row += 1
|
||||
|
||||
workbook.close()
|
||||
output.seek(0)
|
||||
file_data = output.read()
|
||||
output.close()
|
||||
|
||||
filename = 'Template_Hari_Libur_Indonesia.xlsx'
|
||||
attachment = self.env['ir.attachment'].sudo().create({
|
||||
'name': filename,
|
||||
'datas': base64.b64encode(file_data),
|
||||
'mimetype': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'public': True,
|
||||
})
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': f'/web/content/{attachment.id}?download=true',
|
||||
'target': 'new',
|
||||
}
|
||||
|
||||
def action_open_import_wizard(self, *args, **kwargs):
|
||||
"""Membuka dialog popup wizard impor hari libur dari file Excel."""
|
||||
return {
|
||||
'name': _('Impor Hari Libur dari Excel'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'freshservice.holiday.import.wizard',
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
'context': dict(self.env.context),
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
# -*- 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):
|
||||
"""Menyimpan pesan/balasan pada tiket.
|
||||
Dipakai untuk menampilkan isi chat di tab percakapan dan di file Excel.
|
||||
"""
|
||||
_name = 'freshservice.ticket.conversation'
|
||||
_description = 'Percakapan & Balasan Tiket Freshservice'
|
||||
_order = 'created_at asc, id asc'
|
||||
|
||||
ticket_id = fields.Many2one(
|
||||
'freshservice.ticket',
|
||||
string='Tiket',
|
||||
required=True,
|
||||
ondelete='cascade',
|
||||
index=True
|
||||
)
|
||||
|
||||
external_id = fields.Char(string='ID Percakapan')
|
||||
user_name = fields.Char(string='Pengirim')
|
||||
user_id_external = fields.Char(string='User ID Freshservice')
|
||||
|
||||
incoming = fields.Boolean(
|
||||
string='Pesan Masuk (Dari User)',
|
||||
help='True jika dikirim oleh Customer/Requester, False jika balasan dari Agen IT.'
|
||||
)
|
||||
|
||||
sender_type = fields.Selection([
|
||||
('agent', 'IT Support (Agent)'),
|
||||
('customer', 'User (Requester)'),
|
||||
], string='Tipe Pengirim', compute='_compute_sender_type', store=True)
|
||||
|
||||
body_text = fields.Text(string='Isi Pesan')
|
||||
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'
|
||||
430
gmn_freshservice/models/gmn_freshservice_ticket_stage_log_v01.py
Normal file
430
gmn_freshservice/models/gmn_freshservice_ticket_stage_log_v01.py
Normal file
@@ -0,0 +1,430 @@
|
||||
# -*- 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
|
||||
|
||||
# ============================================================
|
||||
# KONSTANTA JAM KERJA OPERASIONAL (WIB / UTC+7)
|
||||
# Senin-Jumat, 08:30-17:30, Break 12:30-13:30 (8 jam efektif)
|
||||
# ============================================================
|
||||
_WORK_START = time(8, 30) # 08:30
|
||||
_WORK_END = time(17, 30) # 17:30
|
||||
_BREAK_START = time(12, 30) # 12:30
|
||||
_BREAK_END = time(13, 30) # 13:30
|
||||
_WORK_HOURS_PER_DAY = 8.0 # jam efektif per hari
|
||||
|
||||
|
||||
def _working_seconds_in_day(t_start, t_end):
|
||||
"""Hitung detik kerja efektif dalam satu hari antara t_start dan t_end (time objects).
|
||||
Mengecualikan break 12:30-13:30 dan di luar jam kerja 08:30-17:30.
|
||||
"""
|
||||
# Clamp ke jam kerja
|
||||
t_start = max(t_start, _WORK_START)
|
||||
t_end = min(t_end, _WORK_END)
|
||||
if t_end <= t_start:
|
||||
return 0.0
|
||||
|
||||
def to_sec(t):
|
||||
return t.hour * 3600 + t.minute * 60 + t.second
|
||||
|
||||
total = to_sec(t_end) - to_sec(t_start)
|
||||
|
||||
# Kurangi overlap dengan break 12:30-13:30
|
||||
break_s = max(t_start, _BREAK_START)
|
||||
break_e = min(t_end, _BREAK_END)
|
||||
if break_e > break_s:
|
||||
total -= (to_sec(break_e) - to_sec(break_s))
|
||||
|
||||
return max(0.0, total)
|
||||
|
||||
|
||||
# Hari Libur Nasional & Cuti Bersama Indonesia (2024-2026)
|
||||
_INDONESIA_HOLIDAYS = {
|
||||
# 2024
|
||||
datetime(2024, 1, 1).date(), datetime(2024, 2, 8).date(), datetime(2024, 2, 9).date(),
|
||||
datetime(2024, 2, 10).date(), datetime(2024, 3, 11).date(), datetime(2024, 3, 12).date(),
|
||||
datetime(2024, 3, 29).date(), datetime(2024, 3, 31).date(), datetime(2024, 4, 8).date(),
|
||||
datetime(2024, 4, 9).date(), datetime(2024, 4, 10).date(), datetime(2024, 4, 11).date(),
|
||||
datetime(2024, 4, 12).date(), datetime(2024, 4, 15).date(), datetime(2024, 5, 1).date(),
|
||||
datetime(2024, 5, 9).date(), datetime(2024, 5, 10).date(), datetime(2024, 5, 23).date(),
|
||||
datetime(2024, 5, 24).date(), datetime(2024, 6, 1).date(), datetime(2024, 6, 17).date(),
|
||||
datetime(2024, 6, 18).date(), datetime(2024, 7, 7).date(), datetime(2024, 8, 17).date(),
|
||||
datetime(2024, 9, 16).date(), datetime(2024, 12, 25).date(), datetime(2024, 12, 26).date(),
|
||||
# 2025
|
||||
datetime(2025, 1, 1).date(), datetime(2025, 1, 27).date(), datetime(2025, 1, 28).date(),
|
||||
datetime(2025, 1, 29).date(), datetime(2025, 3, 28).date(), datetime(2025, 3, 29).date(),
|
||||
datetime(2025, 3, 31).date(), datetime(2025, 4, 1).date(), datetime(2025, 4, 2).date(),
|
||||
datetime(2025, 4, 3).date(), datetime(2025, 4, 4).date(), datetime(2025, 4, 7).date(),
|
||||
datetime(2025, 4, 18).date(), datetime(2025, 4, 20).date(), datetime(2025, 5, 1).date(),
|
||||
datetime(2025, 5, 12).date(), datetime(2025, 5, 13).date(), datetime(2025, 5, 29).date(),
|
||||
datetime(2025, 5, 30).date(), datetime(2025, 6, 1).date(), datetime(2025, 6, 6).date(),
|
||||
datetime(2025, 6, 9).date(), datetime(2025, 6, 27).date(), datetime(2025, 8, 17).date(),
|
||||
datetime(2025, 9, 5).date(), datetime(2025, 12, 25).date(), datetime(2025, 12, 26).date(),
|
||||
# 2026
|
||||
datetime(2026, 1, 1).date(), datetime(2026, 1, 16).date(), datetime(2026, 2, 17).date(),
|
||||
datetime(2026, 3, 19).date(), datetime(2026, 3, 20).date(), datetime(2026, 3, 21).date(),
|
||||
datetime(2026, 4, 3).date(), datetime(2026, 5, 1).date(), datetime(2026, 5, 14).date(),
|
||||
datetime(2026, 5, 31).date(), datetime(2026, 6, 1).date(), datetime(2026, 6, 27).date(),
|
||||
datetime(2026, 7, 16).date(), datetime(2026, 8, 17).date(), datetime(2026, 8, 25).date(),
|
||||
datetime(2026, 12, 25).date(),
|
||||
}
|
||||
|
||||
|
||||
def _holiday_dates(env, start_date, end_date):
|
||||
"""Return public leave dates combining Indonesian public holidays with Odoo's calendar leaves.
|
||||
|
||||
Prioritas Pengambilan Data:
|
||||
1. Model Master Data `freshservice.holiday` di database (hasil input manual atau impor Excel).
|
||||
2. Fallback ke konstanta `_INDONESIA_HOLIDAYS` jika tabel master data di DB masih kosong
|
||||
atau jika env Odoo tidak tersedia (misal unit test standalone).
|
||||
3. Menggabungkan dengan cuti dinamis dari `resource.calendar.leaves` jika tersedia.
|
||||
"""
|
||||
res = set()
|
||||
used_db = False
|
||||
|
||||
if env and hasattr(env, '__getitem__'):
|
||||
try:
|
||||
HolidayModel = env['freshservice.holiday'].sudo()
|
||||
# Cek apakah ada record hari libur aktif di database
|
||||
db_records = HolidayModel.search([
|
||||
('date', '>=', start_date),
|
||||
('date', '<=', end_date),
|
||||
('active', '=', True),
|
||||
])
|
||||
if db_records:
|
||||
res.update(rec.date for rec in db_records)
|
||||
used_db = True
|
||||
elif HolidayModel.search_count([('active', '=', True)]) > 0:
|
||||
# Database sudah memiliki data libur, namun tidak ada libur di rentang tanggal ini
|
||||
used_db = True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not used_db:
|
||||
# Fallback ke konstanta _INDONESIA_HOLIDAYS jika DB belum terisi / tanpa koneksi DB
|
||||
res.update(d for d in _INDONESIA_HOLIDAYS if start_date <= d <= end_date)
|
||||
|
||||
if env and hasattr(env, '__getitem__'):
|
||||
try:
|
||||
leaves = env['resource.calendar.leaves'].sudo().search([
|
||||
('date_from', '<', datetime.combine(end_date + timedelta(days=1), time.min)),
|
||||
('date_to', '>=', datetime.combine(start_date, time.min)),
|
||||
])
|
||||
for leave in leaves:
|
||||
if leave.date_from:
|
||||
res.add(fields.Datetime.context_timestamp(env.user, leave.date_from).date())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def compute_working_hours(env, start_dt, end_dt=None):
|
||||
"""Menghitung jam kerja efektif antara start_dt dan end_dt.
|
||||
|
||||
Aturan:
|
||||
- Jam kerja: 08:30 - 17:30 WIB (UTC+7)
|
||||
- Break: 12:30 - 13:30 (tidak dihitung)
|
||||
- Hari kerja: Senin - Jumat (Sabtu & Minggu dilewati)
|
||||
- Jam di DB diasumsikan UTC → dikonversi ke WIB dengan +7 jam
|
||||
|
||||
Returns:
|
||||
float: total jam kerja efektif (dibulatkan 2 desimal)
|
||||
"""
|
||||
if not start_dt:
|
||||
return 0.0
|
||||
|
||||
# Normalisasi ke datetime naive (UTC)
|
||||
def to_naive_utc(dt):
|
||||
if isinstance(dt, str):
|
||||
for fmt in ('%Y-%m-%d %H:%M:%S', '%Y-%m-%dT%H:%M:%S', '%Y-%m-%d %H:%M:%S.%f'):
|
||||
try:
|
||||
return datetime.strptime(dt, fmt)
|
||||
except ValueError:
|
||||
continue
|
||||
return None
|
||||
if hasattr(dt, 'tzinfo') and dt.tzinfo is not None:
|
||||
return dt.replace(tzinfo=None) - dt.utcoffset()
|
||||
return dt
|
||||
|
||||
s_utc = to_naive_utc(start_dt)
|
||||
e_utc = to_naive_utc(end_dt) if end_dt else datetime.utcnow()
|
||||
|
||||
if not s_utc or not e_utc or e_utc <= s_utc:
|
||||
return 0.0
|
||||
|
||||
# Konversi ke WIB (UTC+7)
|
||||
s_wib = s_utc + timedelta(hours=7)
|
||||
e_wib = e_utc + timedelta(hours=7)
|
||||
|
||||
total_seconds = 0.0
|
||||
curr = s_wib.date()
|
||||
end_date = e_wib.date()
|
||||
holidays = _holiday_dates(env, curr, end_date)
|
||||
|
||||
while curr <= end_date:
|
||||
# Lewati Sabtu, Minggu, dan tanggal leave/libur nasional terdaftar
|
||||
if curr.weekday() >= 5 or curr in holidays:
|
||||
curr += timedelta(days=1)
|
||||
continue
|
||||
|
||||
# Tentukan window waktu untuk hari ini
|
||||
day_start = datetime.combine(curr, _WORK_START)
|
||||
day_end = datetime.combine(curr, _WORK_END)
|
||||
|
||||
# Clamp ke range s_wib - e_wib
|
||||
seg_start = max(datetime.combine(curr, time(0, 0)), s_wib)
|
||||
seg_end = min(datetime.combine(curr, time(23, 59, 59)), e_wib)
|
||||
|
||||
# Ambil time portion untuk _working_seconds_in_day
|
||||
t_s = max(seg_start, day_start)
|
||||
t_e = min(seg_end, day_end)
|
||||
|
||||
if t_e > t_s:
|
||||
total_seconds += _working_seconds_in_day(t_s.time(), t_e.time())
|
||||
|
||||
curr += timedelta(days=1)
|
||||
|
||||
return round(total_seconds / 3600.0, 2)
|
||||
|
||||
|
||||
def format_working_duration(hours_float, is_active=False):
|
||||
"""Format jam kerja efektif menjadi string yang mudah dibaca.
|
||||
Contoh: 12.0 → '12 Jam (1.5 Hari Kerja)'
|
||||
"""
|
||||
if not hours_float or hours_float <= 0:
|
||||
suffix = ' (Aktif)' if is_active else ''
|
||||
return f'0 Jam{suffix}'
|
||||
h = round(hours_float, 2)
|
||||
wd = round(h / _WORK_HOURS_PER_DAY, 2)
|
||||
# Tampilkan hari kerja: bulatkan ke .5 terdekat untuk keterbacaan
|
||||
wd_display = round(wd * 2) / 2
|
||||
suffix = ' (Aktif)' if is_active else ''
|
||||
return f'{h:.4g} Jam ({wd_display:.4g} Hari Kerja){suffix}'
|
||||
|
||||
|
||||
def compute_calendar_days(env, start_dt, end_dt=None, exclude_weekends=True):
|
||||
"""
|
||||
Menghitung selisih hari kerja (Working Days / Business Days) berdasarkan pergantian tanggal.
|
||||
Secara default mengecualikan hari Sabtu (weekday=5) dan Minggu (weekday=6),
|
||||
sehingga hari libur akhir pekan tidak terhitung sebagai waiting time / penalti SLA.
|
||||
|
||||
- exclude_weekends=True (default):
|
||||
* Dibuat Jumat, selesai Senin = 1 Hari Kerja (Sabtu & Minggu diskip).
|
||||
* Dibuat Jumat, selesai Jumat = 0 Hari Kerja.
|
||||
* Dibuat Sabtu, selesai Minggu = 0 Hari Kerja.
|
||||
- exclude_weekends=False:
|
||||
* Menghitung total hari kalender biasa murni.
|
||||
"""
|
||||
if not start_dt:
|
||||
return 0
|
||||
try:
|
||||
user_tz = env.context.get('tz') or env.user.tz or 'Asia/Jakarta'
|
||||
user_record = env.user.with_context(tz=user_tz)
|
||||
start_date = fields.Datetime.context_timestamp(user_record, start_dt).date()
|
||||
if end_dt:
|
||||
end_date = fields.Datetime.context_timestamp(user_record, end_dt).date()
|
||||
else:
|
||||
now_dt = fields.Datetime.now()
|
||||
end_date = fields.Datetime.context_timestamp(user_record, now_dt).date()
|
||||
|
||||
if end_date <= start_date:
|
||||
return 0
|
||||
|
||||
if not exclude_weekends:
|
||||
return (end_date - start_date).days
|
||||
|
||||
# Hitung hari kerja dengan melewati Sabtu (5) dan Minggu (6)
|
||||
work_days = 0
|
||||
curr = start_date + timedelta(days=1)
|
||||
while curr <= end_date:
|
||||
if curr.weekday() < 5: # 0=Senin ... 4=Jumat
|
||||
work_days += 1
|
||||
curr += timedelta(days=1)
|
||||
return work_days
|
||||
except Exception:
|
||||
s_date = start_dt.date() if hasattr(start_dt, 'date') else start_dt
|
||||
e_date = (end_dt or fields.Datetime.now()).date() if hasattr(end_dt or fields.Datetime.now(), 'date') else end_dt
|
||||
if e_date <= s_date:
|
||||
return 0
|
||||
if not exclude_weekends:
|
||||
return max(0, (e_date - s_date).days)
|
||||
work_days = 0
|
||||
curr = s_date + timedelta(days=1)
|
||||
while curr <= e_date:
|
||||
if curr.weekday() < 5:
|
||||
work_days += 1
|
||||
curr += timedelta(days=1)
|
||||
return work_days
|
||||
|
||||
def format_duration_days(days_int, is_active=False):
|
||||
"""Memformat durasi dalam bilangan bulat Hari."""
|
||||
days = max(0, int(days_int or 0))
|
||||
if is_active:
|
||||
return f"{days} Hari (Aktif)"
|
||||
return f"{days} Hari"
|
||||
|
||||
def format_duration_human(hours_float):
|
||||
"""Helper kompatibilitas yang sekarang menampilkan satuan hari."""
|
||||
if not hours_float or hours_float <= 0:
|
||||
return "0 Hari"
|
||||
days = int(round(hours_float / 24.0))
|
||||
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'
|
||||
|
||||
ticket_id = fields.Many2one(
|
||||
'freshservice.ticket',
|
||||
string='Tiket',
|
||||
required=True,
|
||||
ondelete='cascade',
|
||||
index=True
|
||||
)
|
||||
|
||||
stage_name = fields.Char(string='Status / Stage', required=True)
|
||||
|
||||
responsibility = fields.Selection([
|
||||
('internal', 'Agent Active Work'),
|
||||
('it_hold', 'Agent Hold & Discussion'),
|
||||
('customer', 'Waiting on Requester'),
|
||||
('user_after_auto_resolve', 'Waiting on User After Auto-Resolve'),
|
||||
('needs_verification', 'Waiting on User Verification'),
|
||||
('scheduled_meeting', 'Scheduled Meeting (Shared)'),
|
||||
('third_party', 'Waiting on Third Party'),
|
||||
('resolved_idle', 'Resolved / Idle'),
|
||||
# Legacy values are retained for historical audit snapshots.
|
||||
('on_hold', 'On Hold (Legacy)'),
|
||||
('internal_discussion', 'Internal Discussion (Legacy)'),
|
||||
('external_activity', 'External Activity (Legacy)'),
|
||||
], string='Task Owner', required=True, default='internal', index=True)
|
||||
|
||||
effective_status = fields.Char(string='Effective Status', index=True)
|
||||
classification_basis = fields.Char(string='Classification Basis')
|
||||
needs_verification = fields.Boolean(string='User Verification Required')
|
||||
delay_cause = fields.Selection([
|
||||
('it', 'Agent'), ('user', 'User'), ('none', 'None'),
|
||||
], string='Delay Cause', default='none')
|
||||
automatic_responsibility = fields.Selection(related='responsibility', string='Tanggung Jawab Otomatis', readonly=True)
|
||||
override_responsibility = fields.Selection([
|
||||
('internal', 'Agent Active Work'), ('it_hold', 'Agent Hold & Discussion'), ('customer', 'Waiting on Requester'),
|
||||
('user_after_auto_resolve', 'Waiting on User After Auto-Resolve'),
|
||||
('needs_verification', 'Waiting on User Verification'), ('scheduled_meeting', 'Scheduled Meeting (Shared)'),
|
||||
('third_party', 'Waiting on Third Party'), ('resolved_idle', 'Resolved / Idle'),
|
||||
], string='Task Owner Override')
|
||||
override_source = fields.Char(string='Override Source')
|
||||
override_reviewer = fields.Char(string='Override Reviewer')
|
||||
override_at = fields.Datetime(string='Override Date')
|
||||
|
||||
actor_name = fields.Char(string='Pelaku / Actor')
|
||||
actor_type = fields.Selection([
|
||||
('Agent', 'IT Support / Agent'),
|
||||
('Requester', 'User / Customer'),
|
||||
('System', 'System'),
|
||||
], string='Tipe Pelaku', default='Agent')
|
||||
|
||||
activity_content = fields.Text(string='Deskripsi Aktivitas')
|
||||
|
||||
start_date = fields.Datetime(string='Waktu Mulai', required=True)
|
||||
end_date = fields.Datetime(string='Waktu Selesai')
|
||||
|
||||
is_active = fields.Boolean(
|
||||
string='Sedang Berjalan',
|
||||
compute='_compute_is_active',
|
||||
store=True
|
||||
)
|
||||
|
||||
duration_days = fields.Integer(
|
||||
string='Durasi (Hari)',
|
||||
compute='_compute_duration',
|
||||
store=True
|
||||
)
|
||||
|
||||
duration_hours = fields.Float(
|
||||
string='Durasi (Jam)',
|
||||
compute='_compute_duration',
|
||||
store=True,
|
||||
digits=(12, 2)
|
||||
)
|
||||
|
||||
duration_display = fields.Char(
|
||||
string='Durasi',
|
||||
compute='_compute_duration',
|
||||
store=True
|
||||
)
|
||||
|
||||
working_hours = fields.Float(
|
||||
string='Jam Kerja',
|
||||
compute='_compute_working_metrics',
|
||||
store=True,
|
||||
digits=(12, 2)
|
||||
)
|
||||
|
||||
working_days_effective = fields.Float(
|
||||
string='Hari Kerja Efektif',
|
||||
compute='_compute_working_metrics',
|
||||
store=True,
|
||||
digits=(12, 2)
|
||||
)
|
||||
|
||||
working_duration_display = fields.Char(
|
||||
string='Durasi Jam Kerja',
|
||||
compute='_compute_working_metrics',
|
||||
store=True
|
||||
)
|
||||
|
||||
@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)
|
||||
rec.working_hours = wh
|
||||
rec.working_days_effective = round(wh / 8.0, 2)
|
||||
rec.working_duration_display = format_working_duration(wh, is_active=rec.is_active)
|
||||
else:
|
||||
rec.working_hours = 0.0
|
||||
rec.working_days_effective = 0.0
|
||||
rec.working_duration_display = "-"
|
||||
|
||||
@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)
|
||||
rec.duration_days = days
|
||||
if rec.end_date:
|
||||
delta = rec.end_date - rec.start_date
|
||||
hours = max(0.0, delta.total_seconds() / 3600.0)
|
||||
rec.duration_hours = round(hours, 2)
|
||||
rec.duration_display = format_duration_days(days)
|
||||
else:
|
||||
# Sedang aktif berjalan sampai sekarang
|
||||
delta = fields.Datetime.now() - rec.start_date
|
||||
hours = max(0.0, delta.total_seconds() / 3600.0)
|
||||
rec.duration_hours = round(hours, 2)
|
||||
rec.duration_display = format_duration_days(days, is_active=True)
|
||||
else:
|
||||
rec.duration_days = 0
|
||||
rec.duration_hours = 0.0
|
||||
rec.duration_display = "-"
|
||||
1131
gmn_freshservice/models/gmn_freshservice_ticket_v01.py
Normal file
1131
gmn_freshservice/models/gmn_freshservice_ticket_v01.py
Normal file
File diff suppressed because it is too large
Load Diff
81
gmn_freshservice/models/gmn_res_config_settings_v01.py
Normal file
81
gmn_freshservice/models/gmn_res_config_settings_v01.py
Normal file
@@ -0,0 +1,81 @@
|
||||
# -*- 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(
|
||||
string='Freshservice URL',
|
||||
config_parameter='gmn_freshservice.freshservice_url',
|
||||
default='https://ptglobalmultimedianetwork.freshservice.com'
|
||||
)
|
||||
freshservice_api_key = fields.Char(
|
||||
string='Freshservice API Key',
|
||||
config_parameter='gmn_freshservice.freshservice_api_key'
|
||||
)
|
||||
freshservice_sync_limit = fields.Integer(
|
||||
string='Limit Sinkronisasi Tiket',
|
||||
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='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):
|
||||
"""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:
|
||||
raise UserError("URL Freshservice tidak boleh kosong!")
|
||||
|
||||
if self.freshservice_use_mock:
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'title': 'Mode Mock Aktif',
|
||||
'message': 'Engine simulasi aktif. Anda dapat langsung menarik tiket contoh tanpa API Key!',
|
||||
'type': 'success',
|
||||
'sticky': False,
|
||||
}
|
||||
}
|
||||
|
||||
if not self.freshservice_api_key:
|
||||
raise UserError("API Key wajib diisi untuk pengujian Live API!")
|
||||
|
||||
try:
|
||||
auth = (self.freshservice_api_key, 'X')
|
||||
headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
|
||||
res = requests.get(f"{url}/api/v2/tickets?per_page=1", auth=auth, headers=headers, timeout=10)
|
||||
if res.status_code == 200:
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'title': 'Koneksi Berhasil!',
|
||||
'message': f'Berhasil terhubung ke Live Freshservice API ({url}).',
|
||||
'type': 'success',
|
||||
'sticky': False,
|
||||
}
|
||||
}
|
||||
else:
|
||||
raise UserError(f"Gagal terhubung (HTTP {res.status_code}): {res.text}")
|
||||
except Exception as e:
|
||||
raise UserError(f"Terjadi kesalahan koneksi: {str(e)}")
|
||||
Reference in New Issue
Block a user