Introduces a new 'Case History' tab to display case routing history, grouped by case type and including current role information. Implements API calls to fetch history and subprocess data, adds filtering and group toggling, and updates tab switching logic to support the new feature.
1252 lines
52 KiB
JavaScript
1252 lines
52 KiB
JavaScript
// ==UserScript==
|
|
// @name CoreTabs
|
|
// @namespace https://git.diasbaskara.id/diasbaskara/userscripts/
|
|
// @version 0.4
|
|
// @description Manage your cases easily.
|
|
// @author Dias Baskara
|
|
// @match https://coretax.intranet.pajak.go.id/*
|
|
// @grant GM_addStyle
|
|
// @require https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js
|
|
// @run-at document-idle
|
|
// ==/UserScript==
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// Prevent running inside iframes and prevent duplicate injection into the top window.
|
|
// This ensures only one instance of the sidebar is created even if the userscript is executed
|
|
// for multiple frames on the same page.
|
|
try {
|
|
if (window.top !== window.self) {
|
|
// We're in an iframe - do nothing.
|
|
return;
|
|
}
|
|
} catch (e) {
|
|
// In rare cross-origin cases accessing window.top may throw; if so, abort to be safe.
|
|
return;
|
|
}
|
|
|
|
// Guard against double-injection in the top window (e.g., userscript re-run)
|
|
if (window.__coretabs_injected) {
|
|
return;
|
|
}
|
|
Object.defineProperty(window, "__coretabs_injected", {
|
|
value: true,
|
|
configurable: false,
|
|
writable: false,
|
|
enumerable: false,
|
|
});
|
|
|
|
// --- SCRIPT CONFIGURATION ---
|
|
const AUTH_STORAGE_KEY =
|
|
"cats-angular-clientuser:https://coretax.intranet.pajak.go.id/identityprovider:cats-angular-client";
|
|
const DEFAULT_CASES_FILTER = "In Progress";
|
|
const REFUND_CASE_PREFIX = "Pengembalian";
|
|
// ----------------------------
|
|
|
|
// --- State Management ---
|
|
let allMyCases = [],
|
|
allCaseDocuments = [],
|
|
allCaseUsers = [],
|
|
caseHistoryData = [],
|
|
caseSubProcessData = {},
|
|
refundReviewData = [],
|
|
filteredRefundData = [],
|
|
selectedCaseId = null,
|
|
loadedDocsForCaseId = null,
|
|
loadedUsersForCaseId = null,
|
|
loadedHistoryForCaseId = null;
|
|
|
|
function addStyles() {
|
|
GM_addStyle(`
|
|
#ct-sidebar{position:fixed;top:100px;right:-950px;width:950px;height:80vh;max-height:800px;background-color:#f9f9f9;border:1px solid #ccc;border-radius:8px 0 0 8px;box-shadow:-3px 0 8px rgba(0,0,0,.15);z-index:2147483647;transition:right .4s ease-in-out;display:flex;flex-direction:column;font-family:sans-serif}#ct-sidebar.open{right:0}#ct-sidebar-toggle{position:absolute;top:50%;right:950px;transform:translateY(-50%);width:30px;height:80px;background-color:#0056b3;color:#fff;border:none;border-radius:8px 0 0 8px;cursor:pointer;writing-mode:vertical-rl;text-orientation:mixed;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:700;letter-spacing:1px}
|
|
.ct-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background-color:transparent;z-index:2147483646;display:none}
|
|
#ct-header-area { padding: 12px 15px; background-color: #343a40; color: white; flex-shrink: 0; display: flex; align-items: center; gap: 15px; border-bottom: 1px solid #495057; }
|
|
#ct-header-icon { flex-shrink: 0; }
|
|
#ct-header-icon svg { width: 32px; height: 32px; fill: #e9ecef; }
|
|
#ct-header-text { flex-grow: 1; min-width: 0; }
|
|
#ct-header-title { font-size: 16px; font-weight: bold; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
#ct-header-subtitle { font-size: 12px; color: #adb5bd; }
|
|
#ct-header-actions { margin-left: auto; flex-shrink: 0; display: flex; gap: 5px; }
|
|
#ct-tab-bar{display:flex;background-color:#e9ecef;border-bottom:1px solid #ccc;flex-shrink:0}.ct-tab-button{padding:10px 15px;border:none;background-color:transparent;cursor:pointer;font-size:14px;border-bottom:3px solid transparent;transition:background-color .2s,border-color .2s}.ct-tab-button:hover{background-color:#dcdcdc}.ct-tab-button.active{border-bottom:3px solid #0056b3;font-weight:700;background-color:#fff}#ct-tab-content-area{padding:15px;flex-grow:1;overflow:hidden;display:flex;flex-direction:column}.ct-tab-panel{display:none;flex-grow:1;overflow:hidden;flex-direction:column}.ct-tab-panel.active{display:flex}
|
|
.filter-container{margin-bottom:10px;flex-shrink:0;display:flex;gap:15px;align-items:center;}
|
|
.filter-container label{font-weight:700;margin-right:5px}.filter-container select{padding:5px;border-radius:4px;border:1px solid #ccc}
|
|
.results-container{flex-grow:1;overflow-y:auto;overflow-x:auto;border:1px solid #ddd}
|
|
.ct-results-table{width:100%;border-collapse:collapse;font-size:12px}.ct-results-table td,.ct-results-table th{border:1px solid #ddd;padding:8px;text-align:left;vertical-align:middle}.ct-results-table th{background-color:#f2f2f2;font-weight:700;position:sticky;top:-1px}
|
|
.ct-results-table tbody tr:not(.group-header) { cursor: pointer; }
|
|
.ct-results-table tbody tr:not(.group-header):hover{background-color:#e9ecef}.ct-results-table tr.selected{background-color:#dbeafe!important;font-weight:700}.group-header td{background-color:#343a40;color:#fff;font-weight:700;padding:6px 8px}
|
|
.actions-cell{text-align:left!important;white-space:nowrap}.action-btn{display:inline-block;padding:4px 8px;margin:0 2px;border-radius:4px;text-decoration:none;cursor:pointer;border:1px solid #ccc;font-size:11px}.action-btn:disabled{background-color:#e9ecef;color:#6c757d;cursor:not-allowed;border-color:#ddd}.action-btn.open-case{background-color:#6c757d;color:#fff}.action-btn.view-docs{background-color:#007bff;color:#fff;border-color:#007bff}.action-btn.view-users{background-color:#17a2b8;color:#fff;border-color:#17a2b8}
|
|
.action-btn.download-doc { background-color: #28a745; color: white; border-color: #28a745; }
|
|
.action-btn.review-refund-case { background-color: #6f42c1; color: white; border-color: #6f42c1; }
|
|
.refresh-btn{margin-top:10px;padding:8px 12px;font-weight:700;cursor:pointer;border:1px solid #007bff;background-color:#007bff;color:#fff;border-radius:4px;transition:background-color .2s}.refresh-btn:hover{background-color:#0056b3}
|
|
.group-header .group-title { font-weight: bold; font-size: 14px; color: white; }
|
|
.group-header .group-subtitle { font-size: 11px; color: #ccc; margin-top: 2px; font-weight: normal; }
|
|
.reported-cell { text-align: center !important; font-size: 16px; }
|
|
.toggle-icon { display: inline-block; width: 1em; }
|
|
.group-header.expanded .toggle-icon::before { content: '▼'; }
|
|
.group-header.collapsed .toggle-icon::before { content: '►'; }
|
|
.currency-wrapper { display: flex; justify-content: space-between; }
|
|
.currency-num { font-variant-numeric: tabular-nums; }
|
|
`);
|
|
}
|
|
|
|
function createSidebar() {
|
|
// ensure we don't create duplicate DOM elements even if something else tries to run
|
|
if (document.getElementById("ct-sidebar")) return;
|
|
|
|
const sidebarContainer = document.createElement("div");
|
|
sidebarContainer.innerHTML = `
|
|
<div id="ct-sidebar">
|
|
<button id="ct-sidebar-toggle">Coretabs</button>
|
|
<div id="ct-header-area">
|
|
<div id="ct-header-icon">
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 6h-8l-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 12H4V6h5.17l2 2H20v10z"/></svg>
|
|
</div>
|
|
<div id="ct-header-text">
|
|
<div id="ct-header-title">No Case Selected</div>
|
|
<div id="ct-header-subtitle">Please select a case from the "My Cases" tab</div>
|
|
</div>
|
|
<div id="ct-header-actions"></div>
|
|
</div>
|
|
<div id="ct-tab-bar">
|
|
<button class="ct-tab-button active" data-tab="tab-my-cases">My Cases</button>
|
|
<button class="ct-tab-button" data-tab="tab-docs">Case Documents</button>
|
|
<button class="ct-tab-button" data-tab="tab-users">Case Users</button>
|
|
<button class="ct-tab-button" data-tab="tab-history">Case History</button>
|
|
<button class="ct-tab-button" data-tab="tab-refund">Refund Review</button>
|
|
</div>
|
|
<div id="ct-tab-content-area">
|
|
<div id="tab-my-cases" class="ct-tab-panel active"><div class="filter-container"><div><label for="cases-status-filter">Filter by Status:</label><select id="cases-status-filter"></select></div><button id="toggle-cases-btn" class="action-btn" style="margin-left:auto">Collapse All</button></div><div class="results-container"><p style="padding:15px;color:#666;">Loading my cases...</p></div></div>
|
|
<div id="tab-docs" class="ct-tab-panel"><div class="filter-container"><div><label for="docs-status-filter">Filter by Status:</label><select id="docs-status-filter"></select></div><button id="toggle-docs-btn" class="action-btn" style="margin-left:auto">Collapse All</button></div><div class="results-container"><p style="padding:15px;color:#666;">Please select a case to view its documents.</p></div></div>
|
|
<div id="tab-users" class="ct-tab-panel"><div class="filter-container"><div><label for="users-role-filter">Filter by Role:</label><select id="users-role-filter"></select></div></div><div class="results-container"><p style="padding:15px;color:#666;">Please select a case to view its users.</p></div></div>
|
|
<div id="tab-history" class="ct-tab-panel">
|
|
<div class="filter-container">
|
|
<div><label for="history-type-filter">Filter by Case Type:</label><select id="history-type-filter"></select></div>
|
|
<button id="toggle-history-btn" class="action-btn" style="margin-left:auto">Collapse All</button>
|
|
</div>
|
|
<div class="results-container"><p style="padding:15px;color:#666;">Please select a case to view its history.</p></div>
|
|
</div>
|
|
<div id="tab-refund" class="ct-tab-panel">
|
|
<div class="filter-container">
|
|
<div><label for="refund-reported-filter">Filter by Reported:</label><select id="refund-reported-filter"></select></div>
|
|
<button id="toggle-refund-btn" class="action-btn" style="margin-left:auto">Collapse All</button>
|
|
<button id="refund-download-btn" class="action-btn download-doc">Download Excel</button>
|
|
</div>
|
|
<div class="results-container"><p style="padding:15px;color:#666;">Select a refund case and click "Refund Review" in the header or row.</p></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
// Append to document.body (we are running only in top window so this is safe)
|
|
document.body.appendChild(sidebarContainer);
|
|
|
|
// Create overlay for click-outside functionality
|
|
const overlay = document.createElement("div");
|
|
overlay.className = "ct-overlay";
|
|
document.body.appendChild(overlay);
|
|
}
|
|
|
|
// --- RENDER FUNCTIONS ---
|
|
function renderMyCasesTable() {
|
|
const responseArea = document.querySelector(
|
|
"#tab-my-cases .results-container",
|
|
),
|
|
filterValue = document.getElementById("cases-status-filter").value,
|
|
filteredCases =
|
|
"all" === filterValue
|
|
? allMyCases
|
|
: allMyCases.filter((e) => e.CaseStatus === filterValue);
|
|
if (
|
|
((document.getElementById("toggle-cases-btn").textContent =
|
|
"Collapse All"),
|
|
0 === filteredCases.length)
|
|
)
|
|
return void (responseArea.innerHTML =
|
|
'<p style="padding:15px;color:#666;">No cases match the selected filter.</p>');
|
|
filteredCases.sort((e, t) => {
|
|
const o = (e.CaseTypeName || "").localeCompare(t.CaseTypeName || "");
|
|
return 0 !== o
|
|
? o
|
|
: (t.CaseNumber || "").localeCompare(e.CaseNumber || "", void 0, {
|
|
numeric: !0,
|
|
});
|
|
});
|
|
const table = createTable([
|
|
"Case Number",
|
|
"Taxpayer Name",
|
|
"Case Type",
|
|
"Status",
|
|
"Created Date",
|
|
"Actions",
|
|
]),
|
|
tbody = document.createElement("tbody");
|
|
let currentGroup = "";
|
|
let groupIndex = 0;
|
|
(filteredCases.forEach((e) => {
|
|
if (e.CaseTypeName !== currentGroup) {
|
|
currentGroup = e.CaseTypeName;
|
|
groupIndex++;
|
|
tbody.innerHTML += `<tr class="group-header expanded" data-group-id="my-cases-group-${groupIndex}"><td colspan="6"><span class="toggle-icon"></span>${currentGroup || "Uncategorized"}</td></tr>`;
|
|
}
|
|
const t = document.createElement("tr");
|
|
t.className = `group-member my-cases-group-${groupIndex}`;
|
|
const o = e.AggregateIdentifier;
|
|
((t.dataset.id = o), o === selectedCaseId && t.classList.add("selected"));
|
|
const a = new Date(e.CreatedDate).toLocaleDateString("id-ID"),
|
|
s = o && "string" == typeof o && "" !== o.trim(),
|
|
r = s ? "" : 'disabled title="Action unavailable: Case ID is missing"';
|
|
let d = "";
|
|
(e.CaseTypeName &&
|
|
e.CaseTypeName.startsWith(REFUND_CASE_PREFIX) &&
|
|
(d = `<button class="action-btn review-refund-case" data-id="${o}">Refund Review</button>`),
|
|
(t.innerHTML = `
|
|
<td>${e.CaseNumber || "N/A"}</td>
|
|
<td>${e.MainTaxpayerName || "N/A"}</td>
|
|
<td>${e.CaseTypeName || "N/A"}</td>
|
|
<td>${e.CaseStatus || "N/A"}</td>
|
|
<td>${a}</td>
|
|
<td class="actions-cell">
|
|
<a href="https://coretax.intranet.pajak.go.id/case-management/id-ID/case-overview/${o}" class="action-btn open-case" ${r}>Open</a>
|
|
<button class="action-btn view-docs" data-id="${o}" ${r}>View Docs</button>
|
|
<button class="action-btn view-users" data-id="${o}" ${r}>View Users</button>
|
|
${d}
|
|
</td>`),
|
|
tbody.appendChild(t));
|
|
}),
|
|
table.appendChild(tbody),
|
|
(responseArea.innerHTML = ""),
|
|
responseArea.appendChild(table),
|
|
tbody.addEventListener("click", handleGroupToggle),
|
|
tbody.addEventListener("click", handleCaseSelection));
|
|
}
|
|
function renderCaseDocumentsTable() {
|
|
const responseArea = document.querySelector("#tab-docs .results-container"),
|
|
filterValue = document.getElementById("docs-status-filter").value,
|
|
filteredDocs =
|
|
"all" === filterValue
|
|
? allCaseDocuments
|
|
: allCaseDocuments.filter((e) => e.DocumentStatus === filterValue);
|
|
if (
|
|
((document.getElementById("toggle-docs-btn").textContent =
|
|
"Collapse All"),
|
|
0 === filteredDocs.length)
|
|
)
|
|
return void (responseArea.innerHTML =
|
|
'<p style="padding:15px;color:#666;">No documents found or match the selected filter.</p>');
|
|
filteredDocs.sort((e, t) =>
|
|
(e.DocumentTypeCode || "").localeCompare(t.DocumentTypeCode || ""),
|
|
);
|
|
const table = createTable([
|
|
"Letter Number",
|
|
"File Name",
|
|
"Status",
|
|
"Date",
|
|
"Actions",
|
|
]),
|
|
tbody = document.createElement("tbody");
|
|
let currentGroup = "";
|
|
let groupIndex = 0;
|
|
(filteredDocs.forEach((e) => {
|
|
if (e.DocumentTypeCode !== currentGroup) {
|
|
currentGroup = e.DocumentTypeCode;
|
|
groupIndex++;
|
|
tbody.innerHTML += `<tr class="group-header expanded" data-group-id="docs-group-${groupIndex}"><td colspan="5"><span class="toggle-icon"></span>${currentGroup || "Uncategorized"}</td></tr>`;
|
|
}
|
|
const t = e.DocumentDate
|
|
? new Date(e.DocumentDate).toLocaleDateString("id-ID")
|
|
: "N/A";
|
|
tbody.innerHTML += `
|
|
<tr class="group-member docs-group-${groupIndex}">
|
|
<td>${e.LetterNumber || "N/A"}</td>
|
|
<td>${e.FileName || "N/A"}</td>
|
|
<td>${e.DocumentStatus || "N/A"}</td>
|
|
<td>${t}</td>
|
|
<td class="actions-cell">
|
|
<button class="action-btn download-doc" data-doc-id="${e.DocumentAggregateIdentifier}" data-filename="${e.OriginalName}">Download</button>
|
|
</td>
|
|
</tr>`;
|
|
}),
|
|
table.appendChild(tbody),
|
|
(responseArea.innerHTML = ""),
|
|
responseArea.appendChild(table),
|
|
tbody.addEventListener("click", handleGroupToggle),
|
|
tbody.addEventListener("click", handleDocumentAction));
|
|
}
|
|
function renderCaseUsersTable() {
|
|
const responseArea = document.querySelector(
|
|
"#tab-users .results-container",
|
|
),
|
|
filterValue = document.getElementById("users-role-filter").value,
|
|
filteredUsers =
|
|
"all" === filterValue
|
|
? allCaseUsers
|
|
: allCaseUsers.filter((e) => e.CaseRoleType === filterValue);
|
|
if (0 === filteredUsers.length)
|
|
return void (responseArea.innerHTML =
|
|
'<p style="padding:15px;color:#666;">No users found or match the selected filter.</p>');
|
|
filteredUsers.sort((e, t) =>
|
|
(e.FullName || "").localeCompare(t.FullName || ""),
|
|
);
|
|
const table = createTable([
|
|
"Full Name",
|
|
"NIP",
|
|
"Position",
|
|
"Office",
|
|
"Case Role",
|
|
]),
|
|
tbody = document.createElement("tbody");
|
|
(filteredUsers.forEach((e) => {
|
|
tbody.innerHTML += `
|
|
<tr>
|
|
<td>${e.FullName || "N/A"}</td>
|
|
<td>${e.Nip || "N/A"}</td>
|
|
<td>${e.Jabatan || "N/A"}</td>
|
|
<td>${e.OfficeName || "N/A"}</td>
|
|
<td>${e.CaseRoleType || "N/A"}</td>
|
|
</tr>`;
|
|
}),
|
|
table.appendChild(tbody),
|
|
(responseArea.innerHTML = ""),
|
|
responseArea.appendChild(table));
|
|
}
|
|
function renderCaseHistoryTable() {
|
|
const responseArea = document.querySelector(
|
|
"#tab-history .results-container",
|
|
);
|
|
if (!caseHistoryData || caseHistoryData.length === 0) {
|
|
responseArea.innerHTML =
|
|
'<p style="padding:15px;color:#666;">No history data available for this case.</p>';
|
|
return;
|
|
}
|
|
|
|
const filterValue = document.getElementById("history-type-filter").value;
|
|
const filteredHistory = caseHistoryData.filter(
|
|
(item) => filterValue === "all" || item.CaseType === filterValue,
|
|
);
|
|
|
|
if (filteredHistory.length === 0) {
|
|
responseArea.innerHTML =
|
|
'<p style="padding:15px;color:#666;">No history items match the selected filter.</p>';
|
|
return;
|
|
}
|
|
|
|
const table = createTable([
|
|
"Routing Date",
|
|
"Performed By",
|
|
"Workflow Step",
|
|
]),
|
|
tbody = document.createElement("tbody");
|
|
|
|
// Group items by CaseType
|
|
const groupedHistory = {};
|
|
filteredHistory.forEach((item) => {
|
|
const caseType = item.CaseType || "Unknown";
|
|
if (!groupedHistory[caseType]) {
|
|
groupedHistory[caseType] = [];
|
|
}
|
|
groupedHistory[caseType].push(item);
|
|
});
|
|
|
|
// Create table rows with grouping
|
|
Object.keys(groupedHistory).forEach((caseType) => {
|
|
const groupItems = groupedHistory[caseType];
|
|
|
|
// Create group header row
|
|
const sanitizedCaseType = caseType.replace(/[^a-zA-Z0-9]/g, "_");
|
|
tbody.innerHTML += `<tr class="group-header expanded" data-group-id="history-group-${sanitizedCaseType}"><td colspan="3"><span class="toggle-icon"></span>${caseType} (${groupItems.length} items)</td></tr>`;
|
|
|
|
// Add current role type at the top of each group
|
|
// Find the most recent item (with latest RoutingDate)
|
|
const mostRecentItem = groupItems.reduce((latest, current) => {
|
|
const latestDate = new Date(latest.RoutingDate);
|
|
const currentDate = new Date(current.RoutingDate);
|
|
return currentDate > latestDate ? current : latest;
|
|
});
|
|
const currentCaseRoleTypeCode = mostRecentItem.ToWorkflowStepIdentifier
|
|
? caseSubProcessData[mostRecentItem.ToWorkflowStepIdentifier]
|
|
: null;
|
|
|
|
// Always add the current role row, even if CaseRoleTypeCode is not available
|
|
const currentRoleRow = document.createElement("tr");
|
|
currentRoleRow.className = `group-member history-group-${sanitizedCaseType}`;
|
|
|
|
// Empty Routing Date
|
|
const emptyDateCell = document.createElement("td");
|
|
emptyDateCell.textContent = "";
|
|
currentRoleRow.appendChild(emptyDateCell);
|
|
|
|
// CaseRoleTypeCode in Performed By column
|
|
const roleCell = document.createElement("td");
|
|
if (currentCaseRoleTypeCode) {
|
|
roleCell.innerHTML = `<strong>Current Role:</strong><br><small style="color: #666;">${currentCaseRoleTypeCode}</small>`;
|
|
} else {
|
|
roleCell.innerHTML = `<strong>Current Role:</strong><br><small style="color: #666; font-style: italic;">Not available</small>`;
|
|
}
|
|
currentRoleRow.appendChild(roleCell);
|
|
|
|
// Last To Step in Workflow Step column
|
|
const workflowStepCell = document.createElement("td");
|
|
workflowStepCell.textContent = mostRecentItem.ToWorkflowStep || "-";
|
|
currentRoleRow.appendChild(workflowStepCell);
|
|
|
|
tbody.appendChild(currentRoleRow);
|
|
|
|
// Create member rows for this group
|
|
groupItems.forEach((item) => {
|
|
const row = document.createElement("tr");
|
|
row.className = `group-member history-group-${sanitizedCaseType}`;
|
|
|
|
// Format RoutingDate
|
|
const routingDate = new Date(item.RoutingDate);
|
|
const dateCell = document.createElement("td");
|
|
dateCell.textContent = routingDate.toLocaleString("id-ID");
|
|
row.appendChild(dateCell);
|
|
|
|
// PerformedByUser
|
|
const performedByCell = document.createElement("td");
|
|
performedByCell.textContent = item.PerformedByUser || "-";
|
|
row.appendChild(performedByCell);
|
|
|
|
// FromWorkflowStep (renamed to Workflow Step)
|
|
const workflowStepCell = document.createElement("td");
|
|
workflowStepCell.textContent = item.FromWorkflowStep || "-";
|
|
row.appendChild(workflowStepCell);
|
|
|
|
tbody.appendChild(row);
|
|
});
|
|
});
|
|
|
|
responseArea.innerHTML = "";
|
|
table.appendChild(tbody);
|
|
responseArea.appendChild(table);
|
|
tbody.addEventListener("click", handleGroupToggle);
|
|
}
|
|
function renderRefundReviewTable() {
|
|
const responseArea = document.querySelector(
|
|
"#tab-refund .results-container",
|
|
),
|
|
filterValue = document.getElementById("refund-reported-filter").value;
|
|
((document.getElementById("refund-download-btn").disabled =
|
|
!refundReviewData || 0 === refundReviewData.length),
|
|
(document.getElementById("toggle-refund-btn").textContent =
|
|
"Collapse All"));
|
|
let dataToRender = refundReviewData;
|
|
("all" !== filterValue &&
|
|
(dataToRender = refundReviewData.filter(
|
|
(e) => e.ReportedBySeller === ("true" === filterValue),
|
|
)),
|
|
(filteredRefundData = dataToRender));
|
|
if (!dataToRender || 0 === dataToRender.length)
|
|
return void (responseArea.innerHTML =
|
|
'<p style="padding:15px;color:#666;">No refund review data matches the filter.</p>');
|
|
dataToRender.sort((e, t) => {
|
|
const o = (e.Tin || "") + (e.Name || ""),
|
|
a = (t.Tin || "") + (t.Name || "");
|
|
return o.localeCompare(a);
|
|
});
|
|
const table = createTable([
|
|
"Doc Number",
|
|
"Date",
|
|
"Selling Price",
|
|
"VAT Paid",
|
|
"STLG Paid",
|
|
"Trans Code",
|
|
"Reported",
|
|
]),
|
|
tbody = document.createElement("tbody");
|
|
let currentGroupKey = "";
|
|
let groupIndex = 0;
|
|
((responseArea.innerHTML = ""),
|
|
table.appendChild(tbody),
|
|
responseArea.appendChild(table),
|
|
dataToRender.forEach((e) => {
|
|
const t = (e.Tin || "") + (e.Name || "");
|
|
if (t !== currentGroupKey) {
|
|
((currentGroupKey = t), groupIndex++);
|
|
const o = tbody.insertRow();
|
|
((o.className = "group-header expanded"),
|
|
(o.dataset.groupId = `refund-group-${groupIndex}`));
|
|
const a = o.insertCell();
|
|
((a.colSpan = 7),
|
|
(a.innerHTML = `
|
|
<span class="toggle-icon"></span>
|
|
<span>
|
|
<div class="group-title">${e.Name || "Unknown Name"}</div>
|
|
<div class="group-subtitle">${e.Tin || "Unknown TIN"}</div>
|
|
</span>`));
|
|
}
|
|
const o = tbody.insertRow();
|
|
o.className = `group-member refund-group-${groupIndex}`;
|
|
const a = e.DocumentDate
|
|
? new Date(e.DocumentDate).toLocaleDateString("id-ID")
|
|
: "N/A",
|
|
s = e.ReportedBySeller
|
|
? '<span style="color: #28a745;">✔</span>'
|
|
: '<span style="color: #dc3545;">❌</span>';
|
|
((o.insertCell().textContent = e.DocumentNumber || "N/A"),
|
|
(o.insertCell().textContent = a));
|
|
const r = o.insertCell();
|
|
r.innerHTML = `<div class="currency-wrapper"><span>Rp</span><span class="currency-num">${(e.SellingPrice || 0).toLocaleString("id-ID")}</span></div>`;
|
|
const d = o.insertCell();
|
|
d.innerHTML = `<div class="currency-wrapper"><span>Rp</span><span class="currency-num">${(e.VatPaid || 0).toLocaleString("id-ID")}</span></div>`;
|
|
const n = o.insertCell();
|
|
((n.innerHTML = `<div class="currency-wrapper"><span>Rp</span><span class="currency-num">${(e.StlgPaid || 0).toLocaleString("id-ID")}</span></div>`),
|
|
(o.insertCell().textContent = e.TransactionCode || "N/A"));
|
|
const c = o.insertCell();
|
|
((c.className = "reported-cell"), (c.innerHTML = s));
|
|
}),
|
|
tbody.addEventListener("click", handleGroupToggle));
|
|
}
|
|
|
|
// --- DATA FETCHING FUNCTIONS ---
|
|
async function fetchMyCases() {
|
|
const responseArea = document.querySelector(
|
|
"#tab-my-cases .results-container",
|
|
);
|
|
try {
|
|
const authToken = getAuthToken(),
|
|
apiUrl =
|
|
"https://coretax.intranet.pajak.go.id/casemanagement/api/caselist/mycases",
|
|
fetchOptions = {
|
|
method: "POST",
|
|
headers: getHeaders(),
|
|
body: JSON.stringify({}),
|
|
},
|
|
response = await fetch(apiUrl, fetchOptions);
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(
|
|
`API Error: ${errorData.Message || response.statusText}`,
|
|
);
|
|
}
|
|
const data = await response.json();
|
|
((allMyCases = data?.Payload?.Data || []),
|
|
populateFilter("cases-status-filter", allMyCases, "CaseStatus"));
|
|
const casesFilter = document.getElementById("cases-status-filter");
|
|
(Array.from(casesFilter.options).some(
|
|
(e) => e.value === DEFAULT_CASES_FILTER,
|
|
) && (casesFilter.value = DEFAULT_CASES_FILTER),
|
|
renderMyCasesTable());
|
|
} catch (error) {
|
|
handleError(error, responseArea);
|
|
}
|
|
}
|
|
async function fetchCaseDocuments(caseId) {
|
|
const responseArea = document.querySelector("#tab-docs .results-container");
|
|
responseArea.innerHTML =
|
|
'<p style="padding:15px;color:#666;">Loading documents...</p>';
|
|
try {
|
|
if (!caseId) throw new Error("No Case ID provided.");
|
|
const apiUrl =
|
|
"https://coretax.intranet.pajak.go.id/casemanagement/api/casedocument/list",
|
|
fetchOptions = {
|
|
method: "POST",
|
|
headers: getHeaders(caseId),
|
|
body: JSON.stringify({ AggregateIdentifier: caseId }),
|
|
},
|
|
response = await fetch(apiUrl, fetchOptions);
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(
|
|
`API Error: ${errorData.Message || response.statusText}`,
|
|
);
|
|
}
|
|
const data = await response.json();
|
|
((allCaseDocuments = data?.Payload?.Data || []),
|
|
(loadedDocsForCaseId = caseId),
|
|
populateFilter(
|
|
"docs-status-filter",
|
|
allCaseDocuments,
|
|
"DocumentStatus",
|
|
),
|
|
renderCaseDocumentsTable());
|
|
} catch (error) {
|
|
handleError(error, responseArea);
|
|
}
|
|
}
|
|
async function fetchCaseUsers(caseId) {
|
|
const responseArea = document.querySelector(
|
|
"#tab-users .results-container",
|
|
);
|
|
responseArea.innerHTML =
|
|
'<p style="padding:15px;color:#666;">Loading users...</p>';
|
|
try {
|
|
if (!caseId) throw new Error("No Case ID provided.");
|
|
const apiUrl =
|
|
"https://coretax.intranet.pajak.go.id/casemanagement/api/caseuser/list",
|
|
payload = {
|
|
AggregateIdentifier: caseId,
|
|
First: 0,
|
|
Rows: 200,
|
|
SortField: "",
|
|
SortOrder: 1,
|
|
Filters: [],
|
|
LanguageId: "id-ID",
|
|
},
|
|
fetchOptions = {
|
|
method: "POST",
|
|
headers: getHeaders(caseId),
|
|
body: JSON.stringify(payload),
|
|
},
|
|
response = await fetch(apiUrl, fetchOptions);
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(
|
|
`API Error: ${errorData.Message || response.statusText}`,
|
|
);
|
|
}
|
|
const data = await response.json();
|
|
((allCaseUsers = data?.Payload?.Data || []),
|
|
(loadedUsersForCaseId = caseId),
|
|
populateFilter("users-role-filter", allCaseUsers, "CaseRoleType"),
|
|
renderCaseUsersTable());
|
|
} catch (error) {
|
|
handleError(error, responseArea);
|
|
}
|
|
}
|
|
async function fetchCaseHistory(caseId) {
|
|
const responseArea = document.querySelector(
|
|
"#tab-history .results-container",
|
|
);
|
|
responseArea.innerHTML =
|
|
'<p style="padding:15px;color:#666;">Loading history...</p>';
|
|
try {
|
|
if (!caseId) throw new Error("No Case ID provided.");
|
|
const apiUrl =
|
|
"https://coretax.intranet.pajak.go.id/casemanagement/api/caseroutinghistory/list",
|
|
payload = {
|
|
AggregateIdentifier: caseId,
|
|
First: 0,
|
|
Rows: 1000,
|
|
SortField: "RoutingDate",
|
|
SortOrder: -1,
|
|
Filters: [],
|
|
LanguageId: "id-ID",
|
|
},
|
|
fetchOptions = {
|
|
method: "POST",
|
|
headers: getHeaders(caseId),
|
|
body: JSON.stringify(payload),
|
|
},
|
|
response = await fetch(apiUrl, fetchOptions);
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(
|
|
`API Error: ${errorData.Message || response.statusText}`,
|
|
);
|
|
}
|
|
const data = await response.json();
|
|
caseHistoryData = data?.Payload?.Data || [];
|
|
loadedHistoryForCaseId = caseId;
|
|
|
|
// Fetch CaseRoleTypeCode for each ToWorkflowStepIdentifier
|
|
await fetchCaseSubProcessData(caseId);
|
|
|
|
populateFilter("history-type-filter", caseHistoryData, "CaseType");
|
|
renderCaseHistoryTable();
|
|
} catch (error) {
|
|
handleError(error, responseArea);
|
|
}
|
|
}
|
|
async function fetchCaseSubProcessData(caseId) {
|
|
try {
|
|
const apiUrl =
|
|
"https://coretax.intranet.pajak.go.id/casemanagement/api/casesubprocess/list",
|
|
payload = {
|
|
First: 0,
|
|
Rows: 10000,
|
|
Filters: [],
|
|
AggregateIdentifier: caseId,
|
|
},
|
|
fetchOptions = {
|
|
method: "POST",
|
|
headers: getHeaders(caseId),
|
|
body: JSON.stringify(payload),
|
|
},
|
|
response = await fetch(apiUrl, fetchOptions);
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(
|
|
`Case SubProcess API Error: ${errorData.Message || response.statusText}`,
|
|
);
|
|
}
|
|
|
|
const data = await response.json();
|
|
const subProcessData = data?.Payload?.Data || [];
|
|
|
|
// Create a lookup map for WorkflowStepIdentifier to CaseRoleTypeCode
|
|
caseSubProcessData = {};
|
|
subProcessData.forEach((item) => {
|
|
if (item.WorkflowStepIdentifier && item.CaseRoleTypeCode) {
|
|
caseSubProcessData[item.WorkflowStepIdentifier] =
|
|
item.CaseRoleTypeCode;
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error("Error fetching case subprocess data:", error);
|
|
caseSubProcessData = {};
|
|
}
|
|
}
|
|
|
|
async function downloadDocument(docId, filename, button) {
|
|
const originalText = button.textContent;
|
|
((button.textContent = "Downloading..."), (button.disabled = !0));
|
|
try {
|
|
const apiUrl =
|
|
"https://coretax.intranet.pajak.go.id/documentmanagement/api/download",
|
|
payload = {
|
|
DocumentAggregateIdentifier: docId,
|
|
IsDocumentCases: !1,
|
|
IsNeedWatermark: null,
|
|
},
|
|
fetchOptions = {
|
|
method: "POST",
|
|
headers: getHeaders(),
|
|
body: JSON.stringify(payload),
|
|
},
|
|
response = await fetch(apiUrl, fetchOptions);
|
|
if (!response.ok)
|
|
try {
|
|
const errorData = await response.json();
|
|
throw new Error(
|
|
`API Error: ${errorData.Message || response.statusText}`,
|
|
);
|
|
} catch (e) {
|
|
throw new Error(`API request failed! Status: ${response.statusText}`);
|
|
}
|
|
const blob = await response.blob(),
|
|
url = window.URL.createObjectURL(blob),
|
|
a = document.createElement("a");
|
|
((a.style.display = "none"),
|
|
(a.href = url),
|
|
(a.download = filename || "download.pdf"),
|
|
document.body.appendChild(a),
|
|
a.click(),
|
|
window.URL.revokeObjectURL(url),
|
|
a.remove());
|
|
} catch (error) {
|
|
alert(`Download failed: ${error.message}`);
|
|
} finally {
|
|
((button.textContent = originalText), (button.disabled = !1));
|
|
}
|
|
}
|
|
async function fetchSubProcessId(caseId) {
|
|
const apiUrl =
|
|
"https://coretax.intranet.pajak.go.id/casemanagement/api/caserouting/view",
|
|
payload = { AggregateIdentifier: caseId, LanguageId: "id-ID" },
|
|
fetchOptions = {
|
|
method: "POST",
|
|
headers: getHeaders(caseId),
|
|
body: JSON.stringify(payload),
|
|
},
|
|
response = await fetch(apiUrl, fetchOptions);
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(
|
|
`Sub Process ID API Error: ${errorData.Message || response.statusText}`,
|
|
);
|
|
}
|
|
const data = await response.json(),
|
|
firstResult = data?.Payload?.[0];
|
|
if (!firstResult || !firstResult.SubProcessIdentifier)
|
|
throw new Error(
|
|
"Could not find a 'SubProcessIdentifier' in the caserouting/view response.",
|
|
);
|
|
return firstResult.SubProcessIdentifier;
|
|
}
|
|
|
|
// NEW: This function now includes the fallback logic.
|
|
async function fetchC02FormDetail(caseId, subProcessId) {
|
|
const payload = {
|
|
caseAggregateIdentifier: caseId,
|
|
CaseSubProcessIdentifier: subProcessId,
|
|
};
|
|
const fetchOptions = {
|
|
method: "POST",
|
|
headers: getHeaders(caseId),
|
|
body: JSON.stringify(payload),
|
|
};
|
|
|
|
// 1. Primary API Attempt
|
|
const primaryApiUrl =
|
|
"https://coretax.intranet.pajak.go.id/casecomponentspayment/api/c02form014/c02form009detail/current";
|
|
try {
|
|
const response = await fetch(primaryApiUrl, fetchOptions);
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
const reference = data?.Payload?.[0]?.Reference;
|
|
if (reference) return reference; // Success on primary API
|
|
}
|
|
} catch (error) {
|
|
console.warn("Primary API for C02Form failed, trying fallback.", error);
|
|
}
|
|
|
|
// 2. Fallback API Attempt
|
|
const responseArea = document.querySelector(
|
|
"#tab-refund .results-container",
|
|
);
|
|
responseArea.innerHTML = `<p style="padding:15px;color:#666;">Step 2/3: Fetching reference number (trying fallback API)...</p>`;
|
|
const fallbackApiUrl =
|
|
"https://coretax.intranet.pajak.go.id/casecomponentspayment/api/c02form014/view";
|
|
const fallbackResponse = await fetch(fallbackApiUrl, fetchOptions);
|
|
if (!fallbackResponse.ok) {
|
|
const errorData = await fallbackResponse.json();
|
|
throw new Error(
|
|
`C02Form Detail API Error (Fallback): ${errorData.Message || fallbackResponse.statusText}`,
|
|
);
|
|
}
|
|
const fallbackData = await fallbackResponse.json();
|
|
const fallbackReference = fallbackData?.Payload?.Details?.[0]?.Reference;
|
|
if (fallbackReference) return fallbackReference; // Success on fallback API
|
|
|
|
// 3. If both fail
|
|
throw new Error(
|
|
"Could not find a 'Reference' number in either C02Form Detail response.",
|
|
);
|
|
}
|
|
|
|
async function fetchRefundReview(caseId, refNumber) {
|
|
const responseArea = document.querySelector(
|
|
"#tab-refund .results-container",
|
|
);
|
|
try {
|
|
const apiUrl =
|
|
"https://coretax.intranet.pajak.go.id/casecomponentspayment/api/refundprocessreview/get-detailed-review",
|
|
payload = {
|
|
CaseAggregateIdentifier: caseId,
|
|
RevenueCode: "411211",
|
|
TaxPaymentCode: "100",
|
|
TaxReturnType: "VAT_VATR",
|
|
ReferenceNumber: refNumber,
|
|
},
|
|
fetchOptions = {
|
|
method: "POST",
|
|
headers: getHeaders(caseId),
|
|
body: JSON.stringify(payload),
|
|
},
|
|
response = await fetch(apiUrl, fetchOptions);
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(
|
|
`Refund Review API Error: ${errorData.Message || response.statusText}`,
|
|
);
|
|
}
|
|
const data = await response.json();
|
|
((refundReviewData = data?.Payload || []),
|
|
populateBooleanFilter("refund-reported-filter"),
|
|
renderRefundReviewTable());
|
|
} catch (error) {
|
|
handleError(error, responseArea);
|
|
}
|
|
}
|
|
|
|
// --- HELPER FUNCTIONS ---
|
|
function getHeaders(caseId = null) {
|
|
const authToken = getAuthToken(),
|
|
requestFromUrl = caseId
|
|
? `https://coretax.intranet.pajak.go.id/case-management/id-ID/case-overview/${caseId}`
|
|
: window.location.href;
|
|
return {
|
|
accept: "application/json, text/plain, */*",
|
|
authorization: `Bearer ${authToken}`,
|
|
"content-type": "application/json",
|
|
languageid: "id-ID",
|
|
request_from: requestFromUrl,
|
|
};
|
|
}
|
|
function populateFilter(selectId, data, key) {
|
|
const select = document.getElementById(selectId);
|
|
if (!select) return;
|
|
const values = [...new Set(data.map((e) => e[key]).filter(Boolean))];
|
|
((select.innerHTML = '<option value="all">Show All</option>'),
|
|
values.sort().forEach((e) => {
|
|
select.innerHTML += `<option value="${e}">${e}</option>`;
|
|
}));
|
|
}
|
|
function populateBooleanFilter(selectId) {
|
|
const e = document.getElementById(selectId);
|
|
e &&
|
|
(e.innerHTML =
|
|
'<option value="all">Show All</option><option value="true">Yes</option><option value="false">No</option>');
|
|
}
|
|
function getAuthToken() {
|
|
const userDataString = localStorage.getItem(AUTH_STORAGE_KEY),
|
|
userData = userDataString ? JSON.parse(userDataString) : null,
|
|
authToken = userData?.access_token;
|
|
if (!authToken) throw new Error("Authorization Token not found.");
|
|
return authToken;
|
|
}
|
|
function createTable(headers) {
|
|
const table = document.createElement("table");
|
|
table.className = "ct-results-table";
|
|
const thead = document.createElement("thead");
|
|
thead.innerHTML = `<tr>${headers.map((e) => `<th>${e}</th>`).join("")}</tr>`;
|
|
table.appendChild(thead);
|
|
return table;
|
|
}
|
|
function handleError(error, area) {
|
|
console.error("Userscript Error:", error);
|
|
let errorHtml;
|
|
(error.message.includes("Authorization Token not found")
|
|
? (errorHtml = `
|
|
<div style="padding:15px; color: #d9534f; text-align: center;">
|
|
<b>Session Expired or Token Not Found</b>
|
|
<p style="margin: 10px 0;">Please refresh the page to log in again.</p>
|
|
<button id="auth-refresh-btn" class="refresh-btn">Refresh Page</button>
|
|
</div>`)
|
|
: (errorHtml = `<p style="color: #d9534f;padding:15px;"><b>An error occurred:</b><br>${error.message}</p>`),
|
|
(area.innerHTML = errorHtml));
|
|
const refreshBtn = area.querySelector("#auth-refresh-btn");
|
|
refreshBtn &&
|
|
refreshBtn.addEventListener("click", () => window.location.reload());
|
|
}
|
|
function updateHeader(caseObject) {
|
|
const titleEl = document.getElementById("ct-header-title");
|
|
const subtitleEl = document.getElementById("ct-header-subtitle");
|
|
const actionsContainer = document.getElementById("ct-header-actions");
|
|
actionsContainer.innerHTML = ""; // Clear previous buttons
|
|
|
|
if (caseObject) {
|
|
titleEl.textContent = caseObject.MainTaxpayerName || "N/A";
|
|
subtitleEl.textContent = caseObject.CaseNumber || "N/A";
|
|
const caseId = caseObject.AggregateIdentifier;
|
|
const hasValidId =
|
|
caseId && typeof caseId === "string" && caseId.trim() !== "";
|
|
|
|
const openBtn = document.createElement("a");
|
|
openBtn.href = `https://coretax.intranet.pajak.go.id/case-management/id-ID/case-overview/${caseId}`;
|
|
openBtn.className = "action-btn open-case";
|
|
openBtn.textContent = "Open";
|
|
if (!hasValidId) openBtn.disabled = true;
|
|
|
|
const docsBtn = document.createElement("button");
|
|
docsBtn.className = "action-btn view-docs";
|
|
docsBtn.textContent = "View Docs";
|
|
if (!hasValidId) docsBtn.disabled = true;
|
|
docsBtn.addEventListener("click", () => switchTab("tab-docs"));
|
|
|
|
const usersBtn = document.createElement("button");
|
|
usersBtn.className = "action-btn view-users";
|
|
usersBtn.textContent = "View Users";
|
|
if (!hasValidId) usersBtn.disabled = true;
|
|
usersBtn.addEventListener("click", () => switchTab("tab-users"));
|
|
|
|
actionsContainer.append(openBtn, docsBtn, usersBtn);
|
|
|
|
if (
|
|
caseObject.CaseTypeName &&
|
|
caseObject.CaseTypeName.startsWith(REFUND_CASE_PREFIX)
|
|
) {
|
|
const refundBtn = document.createElement("button");
|
|
refundBtn.className = "action-btn review-refund-case";
|
|
refundBtn.textContent = "Refund Review";
|
|
actionsContainer.appendChild(refundBtn);
|
|
refundBtn.addEventListener("click", () =>
|
|
startRefundReviewProcess(caseId, refundBtn),
|
|
);
|
|
}
|
|
} else {
|
|
titleEl.textContent = "No Case Selected";
|
|
subtitleEl.textContent = 'Please select a case from the "My Cases" tab';
|
|
}
|
|
}
|
|
|
|
// MODIFIED: This function has updated user-facing messages.
|
|
async function startRefundReviewProcess(caseId, button) {
|
|
const originalText = button.textContent;
|
|
button.textContent = "...";
|
|
button.disabled = true;
|
|
const responseArea = document.querySelector(
|
|
"#tab-refund .results-container",
|
|
);
|
|
try {
|
|
switchTab("tab-refund");
|
|
responseArea.innerHTML = `<p style="padding:15px;color:#666;">Step 1/3: Fetching Sub Process ID...</p>`;
|
|
if (!caseId) throw new Error("A case must be selected.");
|
|
const subProcessId = await fetchSubProcessId(caseId);
|
|
responseArea.innerHTML = `<p style="padding:15px;color:#666;">Step 2/3: Fetching reference number (trying primary API)...</p>`;
|
|
const referenceNumber = await fetchC02FormDetail(caseId, subProcessId);
|
|
responseArea.innerHTML = `<p style="padding:15px;color:#666;">Step 3/3: Fetching refund details...</p>`;
|
|
await fetchRefundReview(caseId, referenceNumber);
|
|
} catch (error) {
|
|
handleError(error, responseArea);
|
|
} finally {
|
|
button.textContent = originalText;
|
|
button.disabled = false;
|
|
}
|
|
}
|
|
|
|
function handleCaseSelection(event) {
|
|
const selectedRow = event.target.closest("tr");
|
|
if (!selectedRow || selectedRow.classList.contains("group-header")) return;
|
|
const caseId = selectedRow.dataset.id;
|
|
if (!caseId || caseId === selectedCaseId) return;
|
|
|
|
const selectedCase = allMyCases.find(
|
|
(e) => e.AggregateIdentifier === caseId,
|
|
);
|
|
updateHeader(selectedCase);
|
|
selectedCaseId = caseId;
|
|
loadedDocsForCaseId = null;
|
|
loadedUsersForCaseId = null;
|
|
refundReviewData = [];
|
|
filteredRefundData = [];
|
|
document.querySelector("#tab-refund .results-container").innerHTML =
|
|
`<p style="padding:15px;color:#666;">Please use the 'Review Refund' action on a relevant case.</p>`;
|
|
document.getElementById("refund-download-btn").disabled = true;
|
|
|
|
const allRows = selectedRow.closest("tbody").querySelectorAll("tr");
|
|
allRows.forEach((e) => e.classList.remove("selected"));
|
|
selectedRow.classList.add("selected");
|
|
|
|
const actionButton = event.target.closest(".action-btn");
|
|
if (actionButton) {
|
|
if (actionButton.matches(".view-docs")) {
|
|
switchTab("tab-docs");
|
|
} else if (actionButton.matches(".view-users")) {
|
|
switchTab("tab-users");
|
|
} else if (actionButton.matches(".review-refund-case")) {
|
|
startRefundReviewProcess(caseId, actionButton);
|
|
}
|
|
}
|
|
}
|
|
function handleDocumentAction(event) {
|
|
const target = event.target.closest(".action-btn");
|
|
if (!target) return;
|
|
if (target.matches(".download-doc")) {
|
|
const docId = target.dataset.docId;
|
|
const filename = target.dataset.filename;
|
|
downloadDocument(docId, filename, target);
|
|
}
|
|
}
|
|
function handleGroupToggle(event) {
|
|
const headerRow = event.target.closest(".group-header");
|
|
if (!headerRow) return;
|
|
const groupId = headerRow.dataset.groupId;
|
|
if (!groupId) return;
|
|
headerRow.classList.toggle("collapsed");
|
|
headerRow.classList.toggle("expanded");
|
|
const isCollapsed = headerRow.classList.contains("collapsed");
|
|
const tableBody = headerRow.parentElement;
|
|
const memberRows = tableBody.querySelectorAll(`tr.group-member.${groupId}`);
|
|
memberRows.forEach((row) => {
|
|
row.style.display = isCollapsed ? "none" : "table-row";
|
|
});
|
|
}
|
|
function switchTab(tabId) {
|
|
(document
|
|
.querySelectorAll(".ct-tab-button")
|
|
.forEach((e) => e.classList.remove("active")),
|
|
document
|
|
.querySelectorAll(".ct-tab-panel")
|
|
.forEach((e) => e.classList.remove("active")),
|
|
document.querySelector(`[data-tab="${tabId}"]`).classList.add("active"),
|
|
document.getElementById(tabId).classList.add("active"),
|
|
"tab-docs" === tabId &&
|
|
selectedCaseId &&
|
|
selectedCaseId !== loadedDocsForCaseId &&
|
|
fetchCaseDocuments(selectedCaseId),
|
|
"tab-users" === tabId &&
|
|
selectedCaseId &&
|
|
selectedCaseId !== loadedUsersForCaseId &&
|
|
fetchCaseUsers(selectedCaseId),
|
|
"tab-history" === tabId &&
|
|
selectedCaseId &&
|
|
selectedCaseId !== loadedHistoryForCaseId &&
|
|
fetchCaseHistory(selectedCaseId));
|
|
}
|
|
function downloadRefundExcel() {
|
|
if (!filteredRefundData || filteredRefundData.length === 0) {
|
|
alert("No data to download. Please filter the data first.");
|
|
return;
|
|
}
|
|
const now = new Date();
|
|
const year = now.getFullYear();
|
|
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
const day = String(now.getDate()).padStart(2, "0");
|
|
const hours = String(now.getHours()).padStart(2, "0");
|
|
const minutes = String(now.getMinutes()).padStart(2, "0");
|
|
const datetimeSuffix = `${year}${month}${day}_${hours}${minutes}`;
|
|
let filenamePrefix = "Refund_Review_Data";
|
|
if (selectedCaseId) {
|
|
const selectedCase = allMyCases.find(
|
|
(c) => c.AggregateIdentifier === selectedCaseId,
|
|
);
|
|
if (selectedCase && selectedCase.CaseNumber) {
|
|
const sanitizedCaseNumber = selectedCase.CaseNumber.replace(
|
|
/[\\/:"*?<>|]/g,
|
|
"_",
|
|
);
|
|
filenamePrefix = `Refund_Review_Data_${sanitizedCaseNumber}`;
|
|
}
|
|
}
|
|
const filename = `${filenamePrefix}_${datetimeSuffix}.xlsx`;
|
|
const headerMapping = [
|
|
{
|
|
apiField: "ReportedBySeller",
|
|
excelHeader: "Telah dilaporkan oleh Penjual?",
|
|
},
|
|
{ apiField: "Tin", excelHeader: "NPWP Penjual" },
|
|
{ apiField: "Name", excelHeader: "Nama Penjual" },
|
|
{
|
|
apiField: "DocumentNumber",
|
|
excelHeader:
|
|
"Nomor Faktur Pajak/Dokumen yang Dipersamakan/Nota Retur/Nota Pembatalan",
|
|
},
|
|
{
|
|
apiField: "DocumentDate",
|
|
excelHeader:
|
|
"Tanggal Faktur Pajak/Dokumen yang Dipersamakan/Nota Retur/Nota Pembatalan",
|
|
},
|
|
{ apiField: "TransactionCode", excelHeader: "Kode Transaksi" },
|
|
{
|
|
apiField: "SellingPrice",
|
|
excelHeader:
|
|
"Harga Jual/Dasar Pengenaan Pajak/Dasar Pengenaan Pajak Lainnya (Rp)",
|
|
},
|
|
{
|
|
apiField: "VatPaid",
|
|
excelHeader: "PPN yang dikreditkan pada SPT yang Dilaporkan",
|
|
},
|
|
{
|
|
apiField: "StlgPaid",
|
|
excelHeader: "PPnBM yang dikreditkan pada SPT yang Dilaporkan",
|
|
},
|
|
];
|
|
const excelData = filteredRefundData.map((item) => {
|
|
const row = {};
|
|
headerMapping.forEach((map) => {
|
|
if (map.apiField === "ReportedBySeller") {
|
|
row[map.excelHeader] = item[map.apiField] ? "Yes" : "No";
|
|
} else {
|
|
row[map.excelHeader] =
|
|
item[map.apiField] === null ? "" : item[map.apiField];
|
|
}
|
|
});
|
|
return row;
|
|
});
|
|
const worksheet = XLSX.utils.json_to_sheet(excelData);
|
|
const workbook = XLSX.utils.book_new();
|
|
XLSX.utils.book_append_sheet(workbook, worksheet, "Refund Review");
|
|
XLSX.writeFile(workbook, filename);
|
|
}
|
|
|
|
function toggleAllGroups(containerSelector, button) {
|
|
const isCollapsing = button.textContent === "Collapse All";
|
|
const headers = document.querySelectorAll(
|
|
`${containerSelector} .group-header`,
|
|
);
|
|
const members = document.querySelectorAll(
|
|
`${containerSelector} .group-member`,
|
|
);
|
|
|
|
headers.forEach((h) => {
|
|
h.classList.toggle("expanded", !isCollapsing);
|
|
h.classList.toggle("collapsed", isCollapsing);
|
|
});
|
|
members.forEach((m) => {
|
|
m.style.display = isCollapsing ? "none" : "table-row";
|
|
});
|
|
|
|
button.textContent = isCollapsing ? "Expand All" : "Collapse All";
|
|
}
|
|
|
|
// --- MAIN INITIALIZATION ---
|
|
function main() {
|
|
addStyles();
|
|
createSidebar();
|
|
const toggleBtn = document.getElementById("ct-sidebar-toggle");
|
|
if (toggleBtn) {
|
|
toggleBtn.addEventListener("click", () => {
|
|
const sidebar = document.getElementById("ct-sidebar");
|
|
const overlay = document.querySelector(".ct-overlay");
|
|
if (sidebar) {
|
|
sidebar.classList.toggle("open");
|
|
if (overlay)
|
|
overlay.style.display = sidebar.classList.contains("open")
|
|
? "block"
|
|
: "none";
|
|
}
|
|
});
|
|
}
|
|
|
|
// Add click outside handler
|
|
const overlay = document.querySelector(".ct-overlay");
|
|
if (overlay) {
|
|
overlay.addEventListener("click", () => {
|
|
const sidebar = document.getElementById("ct-sidebar");
|
|
if (sidebar && sidebar.classList.contains("open")) {
|
|
sidebar.classList.remove("open");
|
|
overlay.style.display = "none";
|
|
}
|
|
});
|
|
}
|
|
|
|
const tabBar = document.getElementById("ct-tab-bar");
|
|
if (tabBar) {
|
|
tabBar.addEventListener("click", (e) => {
|
|
if (e.target.matches(".ct-tab-button")) switchTab(e.target.dataset.tab);
|
|
});
|
|
}
|
|
|
|
const casesFilterEl = document.getElementById("cases-status-filter");
|
|
if (casesFilterEl)
|
|
casesFilterEl.addEventListener("change", renderMyCasesTable);
|
|
|
|
const docsFilterEl = document.getElementById("docs-status-filter");
|
|
if (docsFilterEl)
|
|
docsFilterEl.addEventListener("change", renderCaseDocumentsTable);
|
|
|
|
const usersFilterEl = document.getElementById("users-role-filter");
|
|
if (usersFilterEl)
|
|
usersFilterEl.addEventListener("change", renderCaseUsersTable);
|
|
|
|
const historyFilterEl = document.getElementById("history-type-filter");
|
|
if (historyFilterEl)
|
|
historyFilterEl.addEventListener("change", renderCaseHistoryTable);
|
|
|
|
const refundFilterEl = document.getElementById("refund-reported-filter");
|
|
if (refundFilterEl)
|
|
refundFilterEl.addEventListener("change", renderRefundReviewTable);
|
|
|
|
const refundDownloadBtn = document.getElementById("refund-download-btn");
|
|
if (refundDownloadBtn)
|
|
refundDownloadBtn.addEventListener("click", downloadRefundExcel);
|
|
|
|
const toggleCasesBtn = document.getElementById("toggle-cases-btn");
|
|
if (toggleCasesBtn)
|
|
toggleCasesBtn.addEventListener("click", (e) =>
|
|
toggleAllGroups("#tab-my-cases .results-container", e.target),
|
|
);
|
|
|
|
const toggleDocsBtn = document.getElementById("toggle-docs-btn");
|
|
if (toggleDocsBtn)
|
|
toggleDocsBtn.addEventListener("click", (e) =>
|
|
toggleAllGroups("#tab-docs .results-container", e.target),
|
|
);
|
|
|
|
const toggleRefundBtn = document.getElementById("toggle-refund-btn");
|
|
if (toggleRefundBtn)
|
|
toggleRefundBtn.addEventListener("click", (e) =>
|
|
toggleAllGroups("#tab-refund .results-container", e.target),
|
|
);
|
|
|
|
const toggleHistoryBtn = document.getElementById("toggle-history-btn");
|
|
if (toggleHistoryBtn)
|
|
toggleHistoryBtn.addEventListener("click", (e) =>
|
|
toggleAllGroups("#tab-history .results-container", e.target),
|
|
);
|
|
|
|
// initial load
|
|
fetchMyCases();
|
|
}
|
|
|
|
// Run main when DOM is ready (script runs at document-idle but ensure body exists)
|
|
if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", main);
|
|
} else {
|
|
main();
|
|
}
|
|
})();
|