import { apiRequest } from './api.js' export const REIMBURSEMENT_LIST_PREVIEW_PARAMS = Object.freeze({ page: 1, pageSize: 100 }) export const REIMBURSEMENT_LIST_FULL_PAGE_SIZE = 100 const REIMBURSEMENT_LIST_MAX_PAGES = 200 function buildListQuery(params = {}) { const search = new URLSearchParams() const page = params.page const pageSize = params.pageSize || params.page_size if (page) { search.set('page', String(page)) } if (pageSize) { search.set('page_size', String(pageSize)) } const query = search.toString() return query ? `?${query}` : '' } export function extractExpenseClaimItems(payload) { if (Array.isArray(payload)) { return payload } return Array.isArray(payload?.items) ? payload.items : [] } function isPaginatedExpenseClaimPayload(payload) { return Boolean( payload && typeof payload === 'object' && Array.isArray(payload.items) && Number.isFinite(Number(payload.page)) && Number.isFinite(Number(payload.page_size)) ) } async function fetchAllExpenseClaimPages(fetchPage, params = {}) { const pageSize = Number(params.pageSize || params.page_size || REIMBURSEMENT_LIST_FULL_PAGE_SIZE) const normalizedPageSize = Number.isFinite(pageSize) && pageSize > 0 ? Math.min(REIMBURSEMENT_LIST_FULL_PAGE_SIZE, Math.floor(pageSize)) : REIMBURSEMENT_LIST_FULL_PAGE_SIZE let page = Math.max(1, Math.floor(Number(params.page || 1) || 1)) const items = [] for (let index = 0; index < REIMBURSEMENT_LIST_MAX_PAGES; index += 1) { const payload = await fetchPage({ ...params, page, pageSize: normalizedPageSize }) if (!isPaginatedExpenseClaimPayload(payload)) { return extractExpenseClaimItems(payload) } items.push(...extractExpenseClaimItems(payload)) const total = Number(payload.total || 0) const totalPages = Number(payload.total_pages || 0) const hasNext = Boolean(payload.has_next) if (!hasNext || (totalPages && page >= totalPages) || (total && items.length >= total)) { break } page += 1 } return items } export function fetchExpenseClaims(params = {}, options = {}) { return apiRequest(`/reimbursements/claims${buildListQuery(params)}`, options) } export function fetchAllExpenseClaims(params = {}) { return fetchAllExpenseClaimPages(fetchExpenseClaims, params) } export function fetchApprovalExpenseClaims(params = {}, options = {}) { return apiRequest(`/reimbursements/claims/approvals${buildListQuery(params)}`, options) } export function fetchAllApprovalExpenseClaims(params = {}) { return fetchAllExpenseClaimPages(fetchApprovalExpenseClaims, params) } export function fetchArchivedExpenseClaims(params = {}, options = {}) { return apiRequest(`/reimbursements/claims/archives${buildListQuery(params)}`, options) } export function fetchAllArchivedExpenseClaims(params = {}) { return fetchAllExpenseClaimPages(fetchArchivedExpenseClaims, params) } export function fetchExpenseClaimDetail(claimId) { return apiRequest(`/reimbursements/claims/${encodeURIComponent(String(claimId || '').trim())}`) } export function fetchExpenseClaimBudgetAnalysis(claimId) { return apiRequest(`/reimbursements/claims/${encodeURIComponent(String(claimId || '').trim())}/budget-analysis`) } export function fetchEmployeeLatestProfile(employeeId, params = {}) { const query = new URLSearchParams() Object.entries(params || {}).forEach(([key, value]) => { const normalized = String(value ?? '').trim() if (normalized) { query.set(key, normalized) } }) const suffix = query.toString() ? `?${query.toString()}` : '' return apiRequest(`/employee-profiles/${encodeURIComponent(String(employeeId || '').trim())}/latest${suffix}`) } export function fetchCurrentEmployeeLatestProfile(params = {}) { const query = new URLSearchParams() Object.entries(params || {}).forEach(([key, value]) => { const normalized = String(value ?? '').trim() if (normalized) { query.set(key, normalized) } }) const suffix = query.toString() ? `?${query.toString()}` : '' return apiRequest(`/employee-profiles/me/latest${suffix}`) } export function updateExpenseClaim(claimId, payload = {}) { return apiRequest(`/reimbursements/claims/${encodeURIComponent(String(claimId || '').trim())}`, { method: 'PATCH', body: JSON.stringify(payload) }) } export function acceptExpenseClaimStandardAdjustment(claimId, payload = {}) { return apiRequest(`/reimbursements/claims/${encodeURIComponent(String(claimId || '').trim())}/standard-adjustment`, { method: 'POST', body: JSON.stringify(payload) }) } export function calculateTravelReimbursement(payload = {}) { return apiRequest('/reimbursements/travel-calculator', { method: 'POST', body: JSON.stringify(payload) }) } export function createExpenseClaimItem(claimId, payload = {}) { return apiRequest(`/reimbursements/claims/${encodeURIComponent(String(claimId || '').trim())}/items`, { method: 'POST', body: JSON.stringify(payload) }) } export function deleteExpenseClaimItem(claimId, itemId) { return apiRequest(`/reimbursements/claims/${encodeURIComponent(String(claimId || '').trim())}/items/${encodeURIComponent(String(itemId || '').trim())}`, { method: 'DELETE' }) } export function uploadExpenseClaimItemAttachment(claimId, itemId, file) { const formData = new FormData() formData.append('file', file) if (file?.receiptId) { formData.append('receipt_id', String(file.receiptId)) } return apiRequest(`/reimbursements/claims/${encodeURIComponent(String(claimId || '').trim())}/items/${encodeURIComponent(String(itemId || '').trim())}/attachment`, { method: 'POST', body: formData, contentType: null }) } export function fetchExpenseClaimItemAttachmentMeta(claimId, itemId) { return apiRequest(`/reimbursements/claims/${encodeURIComponent(String(claimId || '').trim())}/items/${encodeURIComponent(String(itemId || '').trim())}/attachment/meta`) } export function fetchExpenseClaimItemAttachmentPreview(claimId, itemId) { return apiRequest(`/reimbursements/claims/${encodeURIComponent(String(claimId || '').trim())}/items/${encodeURIComponent(String(itemId || '').trim())}/attachment/preview`, { responseType: 'blob' }) } export function fetchExpenseClaimItemAttachment(claimId, itemId) { return apiRequest(`/reimbursements/claims/${encodeURIComponent(String(claimId || '').trim())}/items/${encodeURIComponent(String(itemId || '').trim())}/attachment`, { responseType: 'blob' }) } export async function fetchExpenseClaimAttachmentAsset(pathOrUrl) { const target = String(pathOrUrl || '').trim() if (!target) { throw new Error('预览地址为空。') } if (/^https?:\/\//i.test(target)) { const response = await fetch(target) if (!response.ok) { throw new Error(`预览加载失败(${response.status})。`) } return response.blob() } return apiRequest(target, { responseType: 'blob' }) } export function deleteExpenseClaimItemAttachment(claimId, itemId) { return apiRequest(`/reimbursements/claims/${encodeURIComponent(String(claimId || '').trim())}/items/${encodeURIComponent(String(itemId || '').trim())}/attachment`, { method: 'DELETE' }) } export function updateExpenseClaimItem(claimId, itemId, payload) { return apiRequest(`/reimbursements/claims/${encodeURIComponent(String(claimId || '').trim())}/items/${encodeURIComponent(String(itemId || '').trim())}`, { method: 'PATCH', body: JSON.stringify(payload) }) } function createPreReviewRequestId() { if (typeof globalThis.crypto?.randomUUID === 'function') { return `pre-review:${globalThis.crypto.randomUUID()}` } return `pre-review:${Date.now()}:${Math.random().toString(16).slice(2)}` } export function preReviewExpenseClaim(claimId, requestId = '') { const normalizedRequestId = String(requestId || '').trim() || createPreReviewRequestId() return apiRequest(`/reimbursements/claims/${encodeURIComponent(String(claimId || '').trim())}/pre-review`, { method: 'POST', headers: { 'X-Request-ID': normalizedRequestId }, body: JSON.stringify({}) }) } export function submitExpenseClaim(claimId, payload = {}) { return apiRequest(`/reimbursements/claims/${encodeURIComponent(String(claimId || '').trim())}/submit`, { method: 'POST', body: JSON.stringify(payload || {}) }) } export function returnExpenseClaim(claimId, payload = {}) { return apiRequest(`/reimbursements/claims/${encodeURIComponent(String(claimId || '').trim())}/return`, { method: 'POST', body: JSON.stringify(payload) }) } export function approveExpenseClaim(claimId, payload = {}) { return apiRequest(`/reimbursements/claims/${encodeURIComponent(String(claimId || '').trim())}/approve`, { method: 'POST', body: JSON.stringify(payload) }) } export function payExpenseClaim(claimId) { return apiRequest(`/reimbursements/claims/${encodeURIComponent(String(claimId || '').trim())}/pay`, { method: 'POST', body: JSON.stringify({}) }) } export function deleteExpenseClaim(claimId) { return apiRequest(`/reimbursements/claims/${encodeURIComponent(String(claimId || '').trim())}`, { method: 'DELETE' }) }