diff --git a/coretabs.user.js b/coretabs.user.js index 4aecdbe..bd9b6b0 100644 --- a/coretabs.user.js +++ b/coretabs.user.js @@ -10,57 +10,25 @@ // @run-at document-idle // ==/UserScript== -(function () { - "use strict"; +(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; - } + // --- 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'; + // ---------------------------- - // 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, - }); + // --- State Management --- + let allMyCases = [], allCaseDocuments = [], allCaseUsers = [], refundReviewData = []; + let filteredRefundData = []; + let selectedCaseId = null; + let loadedDocsForCaseId = null; + let loadedUsersForCaseId = null; - // --- 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} + 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:9999;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-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; } @@ -88,16 +56,13 @@ .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 = ` + function createSidebar() { + const sidebarContainer = document.createElement('div'); + sidebarContainer.innerHTML = `
Loading my cases...
Please select a case to view its documents.
Please select a case to view its users.
Please select a case to view its history.
No cases match the selected filter.
'); - 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 += `No cases match the selected filter.
');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+=`No documents found or match the selected filter.
'); - 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 += `No documents found or match the selected filter.
');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+=`No users found or match the selected filter.
'); - 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 += ` + `}),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='No users found or match the selected filter.
');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+=`No history data available for this case.
'; - 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 = - 'No history items match the selected filter.
'; - 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 += `No refund review data matches the filter.
'); - 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 = ` +No refund review data matches the filter.
');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=` -Loading documents...
'; - 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 = - 'Loading users...
'; - 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 = - 'Loading history...
'; - 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; + // --- 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='Loading documents...
';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='Loading users...
';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 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) }; - // 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) + // 1. Primary API Attempt + const primaryApiUrl = "https://coretax.intranet.pajak.go.id/casecomponentspayment/api/c02form014/c02form009detail/current"; 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 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); } - 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)); + + // 2. Fallback API Attempt + const responseArea = document.querySelector('#tab-refund .results-container'); + responseArea.innerHTML = `Step 2/3: Fetching reference number (trying fallback API)...
`; + 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 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; - } + + 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)}} - // 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 = `Step 2/3: Fetching reference number (trying fallback API)...
`; - 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 = ''), - values.sort().forEach((e) => { - select.innerHTML += ``; - })); - } - function populateBooleanFilter(selectId) { - const e = document.getElementById(selectId); - e && - (e.innerHTML = - ''); - } - 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 = `Please refresh the page to log in again.
-An error occurred:
${error.message}
An error occurred:
${error.message}
Step 1/3: Fetching Sub Process ID...
`; - if (!caseId) throw new Error("A case must be selected."); - const subProcessId = await fetchSubProcessId(caseId); - responseArea.innerHTML = `Step 2/3: Fetching reference number (trying primary API)...
`; - const referenceNumber = await fetchC02FormDetail(caseId, subProcessId); - responseArea.innerHTML = `Step 3/3: Fetching refund details...
`; - 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 = - `Please use the 'Review Refund' action on a relevant case.
`; - 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"; + 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 { - row[map.excelHeader] = - item[map.apiField] === null ? "" : item[map.apiField]; + titleEl.textContent = 'No Case Selected'; + subtitleEl.textContent = 'Please select a case from the "My Cases" tab'; } - }); - 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"; + } + + // 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 = `Step 1/3: Fetching Sub Process ID...
`; + if (!caseId) throw new Error("A case must be selected."); + const subProcessId = await fetchSubProcessId(caseId); + responseArea.innerHTML = `Step 2/3: Fetching reference number (trying primary API)...
`; + const referenceNumber = await fetchC02FormDetail(caseId, subProcessId); + responseArea.innerHTML = `Step 3/3: Fetching refund details...
`; + await fetchRefundReview(caseId, referenceNumber); + } catch(error) { + handleError(error, responseArea); + } finally { + button.textContent = originalText; + button.disabled = false; } - }); } - // 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"; + 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 = `Please use the 'Review Refund' action on a relevant case.
`; + 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(`.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)} + 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); } - 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); - }); + 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'; } - const casesFilterEl = document.getElementById("cases-status-filter"); - if (casesFilterEl) - casesFilterEl.addEventListener("change", renderMyCasesTable); + // --- MAIN INITIALIZATION --- + function main() { + addStyles(); + createSidebar(); + document.getElementById('ct-sidebar-toggle').addEventListener('click', () => { + document.getElementById('ct-sidebar').classList.toggle('open'); + }); + document.getElementById('ct-tab-bar').addEventListener('click', (e) => { + if (e.target.matches('.ct-tab-button')) switchTab(e.target.dataset.tab); + }); + document.getElementById('cases-status-filter').addEventListener('change', renderMyCasesTable); + document.getElementById('docs-status-filter').addEventListener('change', renderCaseDocumentsTable); + document.getElementById('users-role-filter').addEventListener('change', renderCaseUsersTable); + document.getElementById('refund-reported-filter').addEventListener('change', renderRefundReviewTable); + document.getElementById('refund-download-btn').addEventListener('click', downloadRefundExcel); - const docsFilterEl = document.getElementById("docs-status-filter"); - if (docsFilterEl) - docsFilterEl.addEventListener("change", renderCaseDocumentsTable); + document.getElementById('toggle-cases-btn').addEventListener('click', (e) => toggleAllGroups('#tab-my-cases .results-container', e.target)); + document.getElementById('toggle-docs-btn').addEventListener('click', (e) => toggleAllGroups('#tab-docs .results-container', e.target)); + document.getElementById('toggle-refund-btn').addEventListener('click', (e) => toggleAllGroups('#tab-refund .results-container', e.target)); - const usersFilterEl = document.getElementById("users-role-filter"); - if (usersFilterEl) - usersFilterEl.addEventListener("change", renderCaseUsersTable); + fetchMyCases(); + } - 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(); - } -})(); +})(); \ No newline at end of file