| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import dayjs from "dayjs"; |
| | | import { |
| | | deleteFinReimbursement, |
| | | getFinReimbursementDetail, |
| | | listFinReimbursementPage, |
| | | persistFinReimbursement, |
| | | } from "@/api/officeProcessAutomation/finReimbursement.js"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | import { computed, getCurrentInstance, nextTick, onMounted, reactive, ref } from "vue"; |
| | | import { |
| | | buildCostReimbursementSaveDto, |
| | | buildFinReimbursementListParams, |
| | | filterReimbursementRowsBySearch, |
| | | hasActiveReimbursementSearch, |
| | | canDeleteReimbursementRow, |
| | | canEditReimbursementRow, |
| | | enrichReimbursementListRowsWithApprovalFlow, |
| | | filterRowsByReimbursementType, |
| | | FIN_REIMBURSEMENT_TYPE, |
| | | mapCostReimbursementRow, |
| | | mapFinReimbursementDetailRow, |
| | | resolveReimbursementDeleteId, |
| | | unwrapFinReimbursementDetail, |
| | | unwrapFinReimbursementPage, |
| | | validateReimbursementApprovalNodes, |
| | | validateReimbursementPersistDto, |
| | | } from "../shared/finReimbursementMappers.js"; |
| | | import { consumeReimburseEditFromApprove } from "../shared/reimburseApproveBridge.js"; |
| | | import { |
| | | EXPENSE_CATEGORY_OPTIONS, |
| | | CATEGORY_TEMPLATES, |
| | | EXPENSE_SUBJECT_OPTIONS, |
| | | expenseCategoryLabel, |
| | | expenseSubjectLabel, |
| | | statusLabel, |
| | | statusTagType, |
| | | formatApprovalFlowSummary, |
| | | buildAutoApprovalFlow, |
| | | getApprovalRuleHint, |
| | | createEmptyExpenseDetail, |
| | | createEmptyForm, |
| | | applyCategoryTemplate, |
| | | initApprovalFlowNodes, |
| | | advanceApprovalFlow, |
| | | rejectApprovalFlow, |
| | | normalizeImportedRow, |
| | | } from "./costReimburseUtils.js"; |
| | | |
| | | function unwrapArray(payload) { |
| | | if (Array.isArray(payload)) return payload; |
| | | if (payload?.data && Array.isArray(payload.data)) return payload.data; |
| | | if (payload?.rows && Array.isArray(payload.rows)) return payload.rows; |
| | | return []; |
| | | } |
| | | |
| | | function isActiveUser(u) { |
| | | if (u.delFlag === "2" || u.delFlag === 2) return false; |
| | | if (u.status == null) return true; |
| | | return String(u.status) === "0"; |
| | | } |
| | | |
| | | function demoFlowNodes(amount = 1200, category = "transport") { |
| | | return buildAutoApprovalFlow(amount, category); |
| | | } |
| | | |
| | | export function useCostReimburse() { |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const allRows = ref([]); |
| | | |
| | | const searchForm = reactive({ |
| | | applicantKeyword: "", |
| | | }); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ current: 1, size: 10, total: 0 }); |
| | | const importInputRef = ref(null); |
| | | const allUsersCache = ref([]); |
| | | const applicantFormSearchLoading = ref(false); |
| | | const applicantFormOptions = ref([]); |
| | | const formRef = ref(); |
| | | const form = reactive(createEmptyForm()); |
| | | const formDialog = reactive({ visible: false, title: "", mode: "add", readonly: false }); |
| | | const detailDialog = reactive({ visible: false }); |
| | | const detailLoading = ref(false); |
| | | const detailRow = ref({}); |
| | | const approveDialog = reactive({ visible: false, row: null }); |
| | | const approveOpinion = ref(""); |
| | | const submitSaving = ref(false); |
| | | |
| | | const tableData = computed(() => |
| | | allRows.value.map((r) => ({ |
| | | ...r, |
| | | approvalFlowSummary: formatApprovalFlowSummary(r), |
| | | })) |
| | | ); |
| | | |
| | | async function fetchList() { |
| | | tableLoading.value = true; |
| | | try { |
| | | const res = await listFinReimbursementPage( |
| | | buildFinReimbursementListParams({ |
| | | page, |
| | | searchForm, |
| | | reimbursementType: FIN_REIMBURSEMENT_TYPE.COST, |
| | | }) |
| | | ); |
| | | const { records, total } = unwrapFinReimbursementPage(res); |
| | | const filtered = filterRowsByReimbursementType( |
| | | records, |
| | | FIN_REIMBURSEMENT_TYPE.COST |
| | | ); |
| | | let mapped = filtered.map(mapCostReimbursementRow); |
| | | mapped = await enrichReimbursementListRowsWithApprovalFlow( |
| | | mapped, |
| | | FIN_REIMBURSEMENT_TYPE.COST |
| | | ); |
| | | if (hasActiveReimbursementSearch(searchForm)) { |
| | | mapped = filterReimbursementRowsBySearch(mapped, searchForm); |
| | | } |
| | | allRows.value = mapped; |
| | | const dropped = records.length - filtered.length; |
| | | let nextTotal = |
| | | dropped > 0 ? Math.max(0, Number(total) - dropped) : Number(total); |
| | | if (hasActiveReimbursementSearch(searchForm)) { |
| | | nextTotal = mapped.length; |
| | | } |
| | | page.total = nextTotal; |
| | | } catch { |
| | | allRows.value = []; |
| | | page.total = 0; |
| | | proxy?.$modal?.msgError?.("è´¹ç¨æ¥éå表å 载失败"); |
| | | } finally { |
| | | tableLoading.value = false; |
| | | } |
| | | } |
| | | |
| | | const flowUserOptions = computed(() => allUsersCache.value.filter(isActiveUser)); |
| | | |
| | | const detailTotalAmount = computed(() => { |
| | | const sum = (form.expenseDetails || []).reduce((s, d) => s + (Number(d.amount) || 0), 0); |
| | | return Math.round(sum * 100) / 100; |
| | | }); |
| | | |
| | | const approvalRuleHint = computed(() => |
| | | getApprovalRuleHint(form.applyAmount ?? detailTotalAmount.value, form.expenseCategory) |
| | | ); |
| | | |
| | | const tableColumn = ref([ |
| | | { label: "æ¥éåå·", prop: "reimburseNo", width: 150 }, |
| | | { label: "ç³è¯·äººç¼å·", prop: "applicantNo", width: 110 }, |
| | | { label: "ç³è¯·äºº", prop: "applicantName", minWidth: 90 }, |
| | | { label: "æ¥ééé¢(å
)", prop: "applyAmount", width: 110 }, |
| | | { label: "æ¥éåå ", prop: "reimburseReason", minWidth: 160, showOverflowTooltip: true }, |
| | | { label: "ç³è¯·æ¶é´", prop: "applyTime", width: 165 }, |
| | | { label: "å建æ¶é´", prop: "createTime", width: 165 }, |
| | | { |
| | | label: "æ¥éç¶æ", |
| | | prop: "approvalResult", |
| | | width: 100, |
| | | dataType: "tag", |
| | | formatData: (v) => statusLabel(v), |
| | | formatType: (v) => statusTagType(v), |
| | | }, |
| | | { |
| | | label: "å®¡æ¹æµç¨", |
| | | prop: "approvalFlowSummary", |
| | | minWidth: 200, |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 220, |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | disabled: (row) => !canEditReimbursementRow(row), |
| | | clickFun: (row) => openFormDialog("edit", row), |
| | | }, |
| | | { name: "详æ
", type: "text", clickFun: (row) => openDetail(row) }, |
| | | { |
| | | name: "å é¤", |
| | | type: "danger", |
| | | disabled: (row) => !canDeleteReimbursementRow(row), |
| | | clickFun: (row) => confirmRemoveRow(row), |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | |
| | | const formRules = { |
| | | applicantId: [{ required: true, message: "è¯·éæ©åå·¥", trigger: "change" }], |
| | | expenseCategory: [{ required: true, message: "è¯·éæ©è´¹ç¨ç±»å", trigger: "change" }], |
| | | reimburseReason: [{ required: true, message: "è¯·å¡«åæ¥éåå ", trigger: "blur" }], |
| | | applyAmount: [{ required: true, message: "è¯·å¡«åæ¥ééé¢", trigger: "blur" }], |
| | | payee: [{ required: true, message: "è¯·å¡«åæ¶æ¬¾äºº", trigger: "blur" }], |
| | | payeeAccount: [{ required: true, message: "è¯·å¡«åæ¶æ¬¾è´¦å·", trigger: "blur" }], |
| | | bankBranch: [{ required: true, message: "请填å弿·æ¯è¡", trigger: "blur" }], |
| | | approvalFlowNodes: [ |
| | | { |
| | | validator: (_r, _v, cb) => { |
| | | const nodes = form.approvalFlowNodes || []; |
| | | if (!nodes.length) { |
| | | cb(new Error("请è³å°é
ç½®ä¸ä¸ªå®¡æ¹èç¹")); |
| | | return; |
| | | } |
| | | if (nodes.some((n) => n.approverId == null || n.approverId === "")) { |
| | | cb(new Error("æ¯ä¸ªèç¹é¡»éæ©å®¡æ¹äºº")); |
| | | return; |
| | | } |
| | | cb(); |
| | | }, |
| | | trigger: "change", |
| | | }, |
| | | ], |
| | | }; |
| | | |
| | | async function loadUserPool() { |
| | | try { |
| | | allUsersCache.value = unwrapArray(await userListNoPageByTenantId()); |
| | | } catch { |
| | | allUsersCache.value = []; |
| | | } |
| | | } |
| | | |
| | | function userSelectLabel(u) { |
| | | const nick = u.nickName || ""; |
| | | const name = u.userName || ""; |
| | | if (nick && name && nick !== name) return `${nick}ï¼${name}ï¼`; |
| | | return nick || name || `ç¨æ·${u.userId ?? u.id ?? ""}`; |
| | | } |
| | | |
| | | function userById(id) { |
| | | return allUsersCache.value.find((u) => String(u.userId ?? u.id) === String(id)); |
| | | } |
| | | |
| | | function employeeNoFromUser(u) { |
| | | if (!u) return ""; |
| | | return u.userName ?? u.userCode ?? u.jobNumber ?? u.workNo ?? (u.userId != null ? String(u.userId) : ""); |
| | | } |
| | | |
| | | function filterUsersByQuery(query) { |
| | | const list = allUsersCache.value.filter(isActiveUser); |
| | | const q = (query || "").trim().toLowerCase(); |
| | | if (!q) return [...list]; |
| | | return list.filter((u) => { |
| | | const nick = (u.nickName || "").toLowerCase(); |
| | | const uname = (u.userName || "").toLowerCase(); |
| | | return nick.includes(q) || uname.includes(q); |
| | | }); |
| | | } |
| | | |
| | | async function remoteSearchApplicantForm(query) { |
| | | applicantFormSearchLoading.value = true; |
| | | try { |
| | | if (!allUsersCache.value.length) await loadUserPool(); |
| | | applicantFormOptions.value = filterUsersByQuery(query); |
| | | } finally { |
| | | applicantFormSearchLoading.value = false; |
| | | } |
| | | } |
| | | |
| | | function onApplicantChange(uid) { |
| | | const u = userById(uid); |
| | | if (u) { |
| | | form.employeeName = u.nickName || u.userName || ""; |
| | | form.employeeNo = employeeNoFromUser(u); |
| | | form.payee = form.payee || form.employeeName; |
| | | form.deptId = String(u.deptId ?? u.sysDeptId ?? ""); |
| | | form.deptName = u.dept?.deptName ?? u.deptName ?? ""; |
| | | } else { |
| | | form.employeeName = ""; |
| | | form.employeeNo = ""; |
| | | } |
| | | } |
| | | |
| | | function autoAssignApprovalFlow() { |
| | | const amount = Number(form.applyAmount) || detailTotalAmount.value || 0; |
| | | form.approvalFlowNodes = buildAutoApprovalFlow( |
| | | amount, |
| | | form.expenseCategory || "other", |
| | | form.approvalFlowNodes |
| | | ); |
| | | nextTick(() => formRef.value?.validateField?.("approvalFlowNodes")); |
| | | } |
| | | |
| | | function onExpenseCategoryChange(val) { |
| | | if (val && !(form.expenseDetails || []).length) { |
| | | applyCategoryTemplate(form, val); |
| | | syncApplyAmountFromDetails(); |
| | | } |
| | | autoAssignApprovalFlow(); |
| | | } |
| | | |
| | | function applyTemplate(category) { |
| | | applyCategoryTemplate(form, category); |
| | | syncApplyAmountFromDetails(); |
| | | autoAssignApprovalFlow(); |
| | | proxy?.$modal?.msgSuccess?.(`å·²åºç¨ã${CATEGORY_TEMPLATES[category]?.label || category}ãå¡«æ¥æ¨¡æ¿`); |
| | | } |
| | | |
| | | function onDetailAmountChange() { |
| | | syncApplyAmountFromDetails(); |
| | | autoAssignApprovalFlow(); |
| | | } |
| | | |
| | | function onApprovalFlowChange() { |
| | | nextTick(() => formRef.value?.validateField?.("approvalFlowNodes")); |
| | | } |
| | | |
| | | function addExpenseDetail() { |
| | | form.expenseDetails.push(createEmptyExpenseDetail()); |
| | | } |
| | | |
| | | function removeExpenseDetail(index) { |
| | | form.expenseDetails.splice(index, 1); |
| | | syncApplyAmountFromDetails(); |
| | | autoAssignApprovalFlow(); |
| | | } |
| | | |
| | | function syncApplyAmountFromDetails() { |
| | | form.applyAmount = detailTotalAmount.value; |
| | | } |
| | | |
| | | function handleQuery() { |
| | | page.current = 1; |
| | | return fetchList(); |
| | | } |
| | | |
| | | function resetSearch() { |
| | | searchForm.applicantKeyword = ""; |
| | | handleQuery(); |
| | | } |
| | | |
| | | function pagination(obj) { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | return fetchList(); |
| | | } |
| | | |
| | | async function loadCostDetailRow(row) { |
| | | const id = resolveReimbursementDeleteId(row); |
| | | if (id == null) { |
| | | throw new Error("missing id"); |
| | | } |
| | | const res = await getFinReimbursementDetail(id); |
| | | const raw = unwrapFinReimbursementDetail(res); |
| | | return mapFinReimbursementDetailRow(raw, FIN_REIMBURSEMENT_TYPE.COST); |
| | | } |
| | | |
| | | async function openDetail(row) { |
| | | const id = resolveReimbursementDeleteId(row); |
| | | if (id == null) { |
| | | proxy?.$modal?.msgWarning?.("æ æ³æ¥ç详æ
ï¼ç¼ºå°æ¥éå ID"); |
| | | return; |
| | | } |
| | | detailDialog.visible = true; |
| | | detailLoading.value = true; |
| | | detailRow.value = { ...row }; |
| | | try { |
| | | detailRow.value = await loadCostDetailRow(row); |
| | | } catch { |
| | | proxy?.$modal?.msgError?.("å 载详æ
失败"); |
| | | detailDialog.visible = false; |
| | | } finally { |
| | | detailLoading.value = false; |
| | | } |
| | | } |
| | | |
| | | async function confirmRemoveRow(row) { |
| | | const id = resolveReimbursementDeleteId(row); |
| | | if (id == null) { |
| | | proxy?.$modal?.msgWarning?.("æ æ³å é¤ï¼ç¼ºå°æ¥éå ID"); |
| | | return; |
| | | } |
| | | const title = row.reimburseNo || row.billNo || row.reimburseReason || "该æ¥éå"; |
| | | try { |
| | | await ElMessageBox.confirm( |
| | | `ç¡®å®è¦å é¤ã${title}ãåï¼å é¤åä¸å¯æ¢å¤ã`, |
| | | "å é¤ç¡®è®¤", |
| | | { |
| | | type: "warning", |
| | | confirmButtonText: "ç¡®å®å é¤", |
| | | cancelButtonText: "åæ¶", |
| | | distinguishCancelAndClose: true, |
| | | autofocus: false, |
| | | } |
| | | ); |
| | | } catch { |
| | | return; |
| | | } |
| | | try { |
| | | await deleteFinReimbursement([id]); |
| | | proxy?.$modal?.msgSuccess?.("å 餿å"); |
| | | if (detailDialog.visible && resolveReimbursementDeleteId(detailRow.value) === id) { |
| | | detailDialog.visible = false; |
| | | } |
| | | await handleQuery(); |
| | | } catch { |
| | | proxy?.$modal?.msgError?.("å é¤å¤±è´¥"); |
| | | } |
| | | } |
| | | |
| | | function openApprove(row) { |
| | | approveDialog.row = { ...row }; |
| | | approveDialog.visible = true; |
| | | } |
| | | |
| | | function approvalActionLabel(v) { |
| | | if (v === "approved") return "éè¿"; |
| | | if (v === "rejected") return "驳å"; |
| | | return "æäº¤"; |
| | | } |
| | | |
| | | async function openFormDialog(mode, row) { |
| | | formDialog.mode = mode; |
| | | formDialog.readonly = false; |
| | | formDialog.title = mode === "add" ? "æ°å¢è´¹ç¨æ¥é" : "ç¼è¾è´¹ç¨æ¥é"; |
| | | if (!allUsersCache.value.length) await loadUserPool(); |
| | | Object.assign(form, createEmptyForm()); |
| | | if (mode === "edit" && row) { |
| | | let editRow = row; |
| | | try { |
| | | editRow = await loadCostDetailRow(row); |
| | | } catch { |
| | | proxy?.$modal?.msgError?.("å è½½æ¥é详æ
失败"); |
| | | return; |
| | | } |
| | | Object.assign(form, { |
| | | ...JSON.parse(JSON.stringify(editRow)), |
| | | reimbursementId: editRow.reimbursementId ?? editRow.id, |
| | | attachmentList: JSON.parse(JSON.stringify(editRow.attachmentList || editRow.invoiceAttachments || [])), |
| | | approvalFlowNodes: JSON.parse(JSON.stringify(editRow.approvalFlowNodes || [])), |
| | | expenseDetails: JSON.parse(JSON.stringify(editRow.expenseDetails || [])), |
| | | }); |
| | | const u = userById(editRow.applicantId); |
| | | applicantFormOptions.value = u |
| | | ? [u] |
| | | : [{ userId: editRow.applicantId, nickName: editRow.employeeName, userName: editRow.employeeNo }]; |
| | | } else { |
| | | form.approvalFlowNodes = buildAutoApprovalFlow(0, "other"); |
| | | remoteSearchApplicantForm(""); |
| | | } |
| | | formDialog.visible = true; |
| | | nextTick(() => { |
| | | formRef.value?.clearValidate?.(); |
| | | }); |
| | | } |
| | | |
| | | function onFormClosed() { |
| | | formRef.value?.resetFields?.(); |
| | | } |
| | | |
| | | async function submitForm() { |
| | | try { |
| | | await formRef.value?.validate?.(); |
| | | } catch { |
| | | return; |
| | | } |
| | | if (!(form.expenseDetails || []).length) { |
| | | proxy?.$modal?.msgWarning?.("请è³å°æ·»å 䏿¡æ¥éæç»"); |
| | | return; |
| | | } |
| | | syncApplyAmountFromDetails(); |
| | | |
| | | if (submitSaving.value) return; |
| | | const isEdit = formDialog.mode === "edit"; |
| | | const dto = buildCostReimbursementSaveDto(form); |
| | | const check = validateReimbursementPersistDto(dto, isEdit); |
| | | if (!check.ok) { |
| | | proxy?.$modal?.msgWarning?.(check.message); |
| | | return; |
| | | } |
| | | const nodeCheck = validateReimbursementApprovalNodes(dto); |
| | | if (!nodeCheck.ok) { |
| | | proxy?.$modal?.msgWarning?.(nodeCheck.message); |
| | | return; |
| | | } |
| | | submitSaving.value = true; |
| | | try { |
| | | await persistFinReimbursement(dto, isEdit); |
| | | proxy?.$modal?.msgSuccess?.(isEdit ? "ä¿åæå" : "æäº¤æå"); |
| | | formDialog.visible = false; |
| | | await handleQuery(); |
| | | } catch { |
| | | proxy?.$modal?.msgError?.(isEdit ? "ä¿å失败" : "æäº¤å¤±è´¥"); |
| | | } finally { |
| | | submitSaving.value = false; |
| | | } |
| | | } |
| | | |
| | | async function submitApprove(result) { |
| | | const row = approveDialog.row; |
| | | if (!row) return; |
| | | if (result === "rejected" && !(approveOpinion.value || "").trim()) { |
| | | proxy?.$modal?.msgWarning?.("驳å须填åå®¡æ¹æè§ï¼å¦ï¼å票模ç³ééä¼ ï¼"); |
| | | return; |
| | | } |
| | | const idx = allRows.value.findIndex((r) => r.id === row.id); |
| | | if (idx === -1) return; |
| | | const cur = allRows.value[idx]; |
| | | const operatorName = "å½å审æ¹äºº"; |
| | | const record = { |
| | | operatorName, |
| | | result, |
| | | opinion: approveOpinion.value || (result === "approved" ? "åæ" : "驳å"), |
| | | time: dayjs().format("YYYY-MM-DD HH:mm:ss"), |
| | | }; |
| | | const records = [...(cur.approvalRecords || []), record]; |
| | | let flowUpdate; |
| | | if (result === "approved") { |
| | | flowUpdate = advanceApprovalFlow(cur, approveOpinion.value); |
| | | } else { |
| | | flowUpdate = rejectApprovalFlow(cur, approveOpinion.value); |
| | | } |
| | | allRows.value[idx] = { |
| | | ...cur, |
| | | approvalFlowNodes: flowUpdate.nodes, |
| | | currentNodeIndex: flowUpdate.currentNodeIndex, |
| | | approvalResult: flowUpdate.approvalResult, |
| | | rejectReason: flowUpdate.rejectReason ?? cur.rejectReason, |
| | | approvalRecords: records, |
| | | }; |
| | | proxy?.$modal?.msgSuccess?.(result === "approved" ? "å·²éè¿" : "已驳å"); |
| | | approveDialog.visible = false; |
| | | handleQuery(); |
| | | } |
| | | |
| | | function handleExport() { |
| | | const data = allRows.value; |
| | | const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json;charset=utf-8" }); |
| | | const url = URL.createObjectURL(blob); |
| | | const a = document.createElement("a"); |
| | | a.href = url; |
| | | a.download = `è´¹ç¨æ¥é导åº_${dayjs().format("YYYYMMDDHHmmss")}.json`; |
| | | a.click(); |
| | | URL.revokeObjectURL(url); |
| | | proxy?.$modal?.msgSuccess?.(`å·²å¯¼åº ${data.length} æ¡`); |
| | | } |
| | | |
| | | function handleImportClick() { |
| | | importInputRef.value?.click?.(); |
| | | } |
| | | |
| | | function onImportFile(e) { |
| | | const file = e.target.files?.[0]; |
| | | e.target.value = ""; |
| | | if (!file) return; |
| | | const reader = new FileReader(); |
| | | reader.onload = () => { |
| | | try { |
| | | const parsed = JSON.parse(String(reader.result || "")); |
| | | const arr = Array.isArray(parsed) ? parsed : parsed?.rows || parsed?.data; |
| | | if (!Array.isArray(arr) || !arr.length) { |
| | | proxy?.$modal?.msgWarning?.("导å
¥æ ¼å¼é¡»ä¸ºè´¹ç¨æ¥é JSON æ°ç»"); |
| | | return; |
| | | } |
| | | arr.forEach((raw, i) => allRows.value.unshift(normalizeImportedRow(raw, i))); |
| | | proxy?.$modal?.msgSuccess?.(`æå导å
¥ ${arr.length} æ¡`); |
| | | handleQuery(); |
| | | } catch { |
| | | proxy?.$modal?.msgError?.("è§£æå¤±è´¥"); |
| | | } |
| | | }; |
| | | reader.readAsText(file, "utf-8"); |
| | | } |
| | | |
| | | onMounted(async () => { |
| | | loadUserPool(); |
| | | await fetchList(); |
| | | const editPayload = consumeReimburseEditFromApprove(); |
| | | if (editPayload?.reimbursementId != null) { |
| | | await openFormDialog("edit", { reimbursementId: editPayload.reimbursementId }); |
| | | } |
| | | }); |
| | | |
| | | return { |
| | | Search, |
| | | EXPENSE_CATEGORY_OPTIONS, |
| | | CATEGORY_TEMPLATES, |
| | | EXPENSE_SUBJECT_OPTIONS, |
| | | expenseCategoryLabel, |
| | | expenseSubjectLabel, |
| | | searchForm, |
| | | tableLoading, |
| | | page, |
| | | tableData, |
| | | tableColumn, |
| | | importInputRef, |
| | | formRef, |
| | | form, |
| | | formDialog, |
| | | formRules, |
| | | detailDialog, |
| | | detailLoading, |
| | | detailRow, |
| | | approveDialog, |
| | | approveOpinion, |
| | | applicantFormSearchLoading, |
| | | applicantFormOptions, |
| | | flowUserOptions, |
| | | detailTotalAmount, |
| | | approvalRuleHint, |
| | | handleQuery, |
| | | resetSearch, |
| | | pagination, |
| | | remoteSearchApplicantForm, |
| | | userSelectLabel, |
| | | onApplicantChange, |
| | | onExpenseCategoryChange, |
| | | applyTemplate, |
| | | onDetailAmountChange, |
| | | onApprovalFlowChange, |
| | | addExpenseDetail, |
| | | removeExpenseDetail, |
| | | syncApplyAmountFromDetails, |
| | | autoAssignApprovalFlow, |
| | | openFormDialog, |
| | | onFormClosed, |
| | | submitForm, |
| | | submitSaving, |
| | | openDetail, |
| | | approvalActionLabel, |
| | | submitApprove, |
| | | handleExport, |
| | | handleImportClick, |
| | | onImportFile, |
| | | }; |
| | | } |