gaoluyang
7 小时以前 18357486a9f327fba9d02dfe451c6ea3fc035a1b
src/views/officeProcessAutomation/ApproveManage/approve-list/useApproveList.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,702 @@
import {
  getApprovalTemplateDetail,
  listApprovalTemplate,
  TEMPLATE_TYPE_CUSTOM,
} from "@/api/officeProcessAutomation/approvalTemplate.js";
import {
  approveApprovalInstance,
  deleteApprovalInstance,
  listApprovalInstancePage,
  saveApprovalInstance,
  updateApprovalInstance,
} from "@/api/officeProcessAutomation/approvalInstance.js";
import { getQuotationList } from "@/api/salesManagement/salesQuotation.js";
import { getPurchaseByCode } from "@/api/procurementManagement/procurementLedger.js";
import { getDeliveryDetailByShippingNo } from "@/api/salesManagement/deliveryLedger.js";
import useUserStore from "@/store/modules/user";
import { Search } from "@element-plus/icons-vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { computed, getCurrentInstance, reactive, ref } from "vue";
import {
  inferReimburseModuleKeyFromInstance,
  loadReimburseDetailForInstance,
  navigateToReimburseManageForEdit,
  resolveFinReimbursementIdFromInstance,
} from "../../ReimburseManage/shared/reimburseApproveBridge.js";
import {
  fetchBusinessTypeOptions,
  formatDisplayTime,
  mapEnabledFromApi,
  unwrapTemplateList,
} from "../approve-template/approveTemplateConstants.js";
import {
  buildFormPayloadRules,
  buildTemplateBindingFromDetail,
  validateTemplateBinding,
} from "../approve-shared/approvalTemplateBindingUtils.js";
import {
  APPROVAL_STATUS_SEARCH_OPTIONS,
  APPROVAL_TYPE_OPTIONS,
  approvalStatusLabel,
  approvalStatusTagType,
  approvalTypeLabel,
  buildApprovalInstanceListParams,
  buildApproveInstanceDto,
  buildEditFormFromInstanceRow,
  buildInstanceDto,
  createEmptySubmitForm,
  mapInstanceFromApi,
  mapSubmitTemplateCard,
  matchBusinessTypeValue,
  unwrapInstancePage,
} from "./approveListConstants.js";
export function useApproveList() {
  const { proxy } = getCurrentInstance() || {};
  const userStore = useUserStore();
  const tableData = ref([]);
  const searchBusinessTypeOptions = ref([]);
  const submitBusinessTypeOptions = ref([]);
  const allSubmitTemplates = ref([]);
  const selectedBusinessType = ref("");
  const submitTemplatesLoading = ref(false);
  const submitTemplateCards = computed(() => {
    if (selectedBusinessType.value == null || selectedBusinessType.value === "") return [];
    return allSubmitTemplates.value.filter((card) =>
      matchBusinessTypeValue(card.businessType, selectedBusinessType.value)
    );
  });
  const searchForm = reactive({
    businessType: "",
    status: "",
    createTimeRange: [],
  });
  const tableLoading = ref(false);
  const page = reactive({ current: 1, size: 10, total: 0 });
  const detailDialog = reactive({ visible: false });
  const detailRow = ref({});
  const detailData = ref({});
  const approveDialog = reactive({ visible: false, row: null });
  const approveOpinion = ref("");
  const approveSubmitting = ref(false);
  /** å·®æ—…/费用报销专用详情、审批弹窗 */
  const reimburseDialog = reactive({
    visible: false,
    mode: "detail",
    moduleKey: "",
    loading: false,
    reimburseRow: {},
    instanceRow: null,
  });
  const submitDialog = reactive({ visible: false, step: 1, mode: "add" });
  const submitEditRow = ref(null);
  const submitForm = reactive(createEmptySubmitForm(""));
  const submitFormRef = ref();
  const submitSaving = ref(false);
  const isSubmitEdit = computed(() => submitDialog.mode === "edit");
  const submitDialogTitle = computed(() => {
    if (submitDialog.mode === "edit") {
      return `修改${activeTemplate.value?.label || submitForm.templateName || "审批"}`;
    }
    if (submitDialog.step === 1) return "选择模板类型";
    if (submitDialog.step === 2) return `选择审批模板${businessTypeLabel(selectedBusinessType.value) ? `(${businessTypeLabel(selectedBusinessType.value)})` : ""}`;
    return `提交${activeTemplate.value?.label || "审批"}`;
  });
  const selectedBusinessTypeLabel = computed(() => businessTypeLabel(selectedBusinessType.value));
  function businessTypeLabel(type) {
    if (type == null || type === "") return "";
    const hit = submitBusinessTypeOptions.value.find((x) => matchBusinessTypeValue(x.value, type));
    return hit?.label || "";
  }
  function countTemplatesByBusinessType(type) {
    return allSubmitTemplates.value.filter((card) => matchBusinessTypeValue(card.businessType, type)).length;
  }
  const activeTemplate = computed(() => submitForm.templateSnapshot || null);
  /** å¡«æŠ¥é¡¹å®šä¹‰ï¼ˆæ–°å¢ž/修改与 formConfig ä¸€è‡´ï¼‰ */
  const submitFormFields = computed(() => {
    const tplFields = activeTemplate.value?.fields;
    if (tplFields?.length) return tplFields;
    return submitForm.formFieldDefs || [];
  });
  const submitFormRules = computed(() => ({
    templateKey: [{ required: true, message: "请选择审批类型", trigger: "change" }],
    ...buildFormPayloadRules(submitFormFields.value),
  }));
  const tableColumn = ref([
    // { label: "申请人编号", prop: "applicantNo", width: 110 },
    { label: "发起人", prop: "applicantName", minWidth: 100 },
    { label: "模板类型", prop: "businessName", minWidth: 120 },
    {
      label: "审批类型",
      prop: "approvalType",
      minWidth: 140,
      dataType: "slot",
      slot: "approveType",
    },
    {
      label: "待我审批",
      prop: "unread",
      width: 90,
      align: "center",
      dataType: "tag",
      formatData: (v) => (v ? "是" : "否"),
      formatType: (v) => (v ? "success" : "danger"),
    },
    {
      label: "审批状态",
      prop: "approvalStatus",
      width: 100,
      dataType: "tag",
      formatData: (v) => approvalStatusLabel(v),
      formatType: (v) => approvalStatusTagType(v),
    },
    {
      label: "创建时间",
      prop: "createTime",
      width: 170,
      formatData: (v) => formatDisplayTime(v),
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
      fixed: "right",
      width: 240,
      operation: [
        { name: "详情", type: "text", clickFun: (row) => openDetail(row) },
        {
          name: "修改",
          type: "text",
          disabled: (row) => row.approvalStatus !== "pending",
          clickFun: (row) => openEditDialog(row),
        },
        {
          name: "审批",
          type: "text",
          disabled: (row) => row.approvalStatus !== "pending" || !row.isApprove,
          clickFun: (row) => openApprove(row),
        },
        {
          name: "删除",
          type: "danger",
          clickFun: (row) => removeInstance(row),
        },
      ],
    },
  ]);
  async function fetchApprovalList() {
    tableLoading.value = true;
    try {
      const res = await listApprovalInstancePage(
        buildApprovalInstanceListParams({ page, searchForm })
      );
      const { records, total } = unwrapInstancePage(res);
      tableData.value = records.map(mapInstanceFromApi);
      page.total = total;
    } catch {
      tableData.value = [];
      page.total = 0;
      ElMessage.error("审批列表加载失败");
    } finally {
      tableLoading.value = false;
    }
  }
  async function loadSubmitTemplates() {
    submitTemplatesLoading.value = true;
    try {
      const [typeOptions, customRes] = await Promise.all([
        fetchBusinessTypeOptions(),
        listApprovalTemplate(TEMPLATE_TYPE_CUSTOM),
      ]);
      submitBusinessTypeOptions.value = typeOptions.filter((x) => ![5, 6, 7].includes(x.value));
      allSubmitTemplates.value = unwrapTemplateList(customRes)
        .filter((row) => mapEnabledFromApi(row.enabled))
        .map(mapSubmitTemplateCard);
    } catch {
      submitBusinessTypeOptions.value = [];
      allSubmitTemplates.value = [];
      ElMessage.error("加载审批模板失败");
    } finally {
      submitTemplatesLoading.value = false;
    }
  }
  function handleQuery() {
    page.current = 1;
    fetchApprovalList();
  }
  function resetSearch() {
    searchForm.businessType = "";
    searchForm.status = "";
    searchForm.createTimeRange = [];
    handleQuery();
  }
  async function loadSearchBusinessTypeOptions() {
    try {
      searchBusinessTypeOptions.value = await fetchBusinessTypeOptions();
    } catch {
      searchBusinessTypeOptions.value = [];
    }
  }
  function pagination({ page: p, limit }) {
    page.current = p;
    page.size = limit;
    fetchApprovalList();
  }
  async function openReimburseDetail(row, mode) {
    const moduleKey = inferReimburseModuleKeyFromInstance(row);
    if (!moduleKey) return false;
    reimburseDialog.mode = mode;
    reimburseDialog.moduleKey = moduleKey;
    reimburseDialog.instanceRow = row;
    reimburseDialog.visible = true;
    reimburseDialog.loading = true;
    reimburseDialog.reimburseRow = {};
    try {
      const { reimburseRow, moduleKey: resolvedMk } =
        await loadReimburseDetailForInstance(row, moduleKey);
      reimburseDialog.moduleKey = resolvedMk || moduleKey;
      reimburseDialog.reimburseRow = reimburseRow;
      return true;
    } catch {
      ElMessage.error("加载报销详情失败");
      reimburseDialog.visible = false;
      return false;
    } finally {
      reimburseDialog.loading = false;
    }
  }
  async function openDetail(row) {
    if (isReimburseApprovalInstance(row)) {
      await openReimburseDetail(row, "detail");
      return;
    }
    detailRow.value = { ...row };
    // æŠ¥ä»·å®¡æ‰¹ï¼šç”¨å®¡æ‰¹äº‹ç”±å­—段承载的"报价单号"去查报价列表
    if (row.businessType === 6) {
      const quotationNo = row?.quotationNo;
      if (quotationNo) {
        const res = await getQuotationList({ quotationNo });
        const records = res?.data?.records || [];
        detailData.value = records[0] || {};
      }
    }
    // é‡‡è´­å®¡æ‰¹ï¼šç”¨å®¡æ‰¹äº‹ç”±å­—段承载的"采购合同号"去查采购详情
    else if (row.businessType === 5) {
      const purchaseContractNumber = row?.purchaseContractNumber;
      if (purchaseContractNumber) {
        const res = await getPurchaseByCode({ purchaseContractNumber });
        detailData.value = res || {};
      }
    }
    // å‘货审批:用审批事由字段承载的"发货单号"去查发货详情
    else if (row.businessType === 7) {
      const shippingNo = row?.shippingNo;
      if (shippingNo) {
        const res = await getDeliveryDetailByShippingNo({ shippingNo });
        detailData.value = res?.data || res || {};
      }
    }
    // å…¶ä»–审批类型
    else {
      detailData.value = {};
    }
    detailDialog.visible = true;
  }
  async function openApprove(row) {
    if (inferReimburseModuleKeyFromInstance(row)) {
      approveOpinion.value = "";
      await openReimburseDetail(row, "approve");
      return;
    }
    approveDialog.row = { ...row };
    approveOpinion.value = "";
    // æŠ¥ä»·å®¡æ‰¹ï¼šç”¨å®¡æ‰¹äº‹ç”±å­—段承载的"报价单号"去查报价列表
    if (row.businessType === 6) {
      const quotationNo = row?.quotationNo;
      if (quotationNo) {
        const res = await getQuotationList({ quotationNo });
        const records = res?.data?.records || [];
        detailData.value = records[0] || {};
      }
    }
    // é‡‡è´­å®¡æ‰¹ï¼šç”¨å®¡æ‰¹äº‹ç”±å­—段承载的"采购合同号"去查采购详情
    else if (row.businessType === 5) {
      const purchaseContractNumber = row?.purchaseContractNumber;
      if (purchaseContractNumber) {
        const res = await getPurchaseByCode({ purchaseContractNumber });
        detailData.value = res || {};
      }
    }
    // å‘货审批:用审批事由字段承载的"发货单号"去查发货详情
    else if (row.businessType === 7) {
      const shippingNo = row?.shippingNo;
      if (shippingNo) {
        const res = await getDeliveryDetailByShippingNo({ shippingNo });
        detailData.value = res?.data || res || {};
      }
    }
    // å…¶ä»–审批类型
    else {
      detailData.value = {};
    }
    approveDialog.visible = true;
  }
  function isReimburseApprovalInstance(row) {
    return Boolean(inferReimburseModuleKeyFromInstance(row));
  }
  function resetSubmitDialogState() {
    submitDialog.mode = "add";
    submitDialog.step = 1;
    selectedBusinessType.value = "";
    submitEditRow.value = null;
    Object.assign(submitForm, createEmptySubmitForm(""));
  }
  function openSubmitDialog() {
    resetSubmitDialogState();
    submitDialog.visible = true;
    loadSubmitTemplates();
  }
  async function openEditDialog(row) {
    if (row?.approvalStatus !== "pending") {
      ElMessage.warning("仅审核中的审批可修改");
      return;
    }
    const moduleKey = inferReimburseModuleKeyFromInstance(row);
    if (moduleKey) {
      const rid = resolveFinReimbursementIdFromInstance(row);
      if (rid == null) {
        ElMessage.warning("无法修改:缺少报销单 ID");
        return;
      }
      try {
        await navigateToReimburseManageForEdit(proxy?.$router, moduleKey, rid);
      } catch {
        ElMessage.warning("未找到差旅/费用报销菜单路由,请从左侧菜单进入后再编辑");
      }
      return;
    }
    if (!row?.id) {
      ElMessage.warning("无法修改:缺少审批实例 ID");
      return;
    }
    submitDialog.mode = "edit";
    submitDialog.step = 3;
    submitEditRow.value = { ...row };
    Object.assign(submitForm, buildEditFormFromInstanceRow(row));
    submitDialog.visible = true;
  }
  async function onTemplatePick(card) {
    if (!card?.id) return;
    submitTemplatesLoading.value = true;
    try {
      const res = await getApprovalTemplateDetail(card.id);
      const applied = buildTemplateBindingFromDetail(res);
      Object.assign(submitForm, {
        templateKey: String(card.id),
        ...applied,
        businessType:
          applied.businessType ?? card.businessType ?? selectedBusinessType.value,
      });
      submitDialog.step = 3;
    } catch {
      ElMessage.error("加载模板详情失败");
    } finally {
      submitTemplatesLoading.value = false;
    }
  }
  function onBusinessTypePick(type) {
    if (!countTemplatesByBusinessType(type)) {
      ElMessage.warning("该类型下暂无可用审批模板");
      return;
    }
    selectedBusinessType.value = type;
    submitDialog.step = 2;
  }
  function backToBusinessTypePick() {
    selectedBusinessType.value = "";
    submitDialog.step = 1;
  }
  function backToTemplatePick() {
    submitDialog.step = 2;
  }
  async function submitInstanceForm() {
    if (submitDialog.mode === "edit") return submitEditApproval();
    return submitNewApproval();
  }
  async function submitNewApproval() {
    if (!submitFormRef.value) return false;
    try {
      await submitFormRef.value.validate();
    } catch {
      return false;
    }
    if (!activeTemplate.value) return false;
    const bindingCheck = validateTemplateBinding({ flowNodes: submitForm.flowNodes });
    if (!bindingCheck.ok) {
      ElMessage.warning(bindingCheck.message);
      return false;
    }
    if (!submitForm.templateId) {
      ElMessage.warning("缺少模板 ID,无法提交");
      return false;
    }
    if (submitSaving.value) return false;
    submitSaving.value = true;
    try {
      await saveApprovalInstance(
        buildInstanceDto({
          submitForm,
          activeTemplate: activeTemplate.value,
          userStore,
          flowNodes: bindingCheck.nodes,
        })
      );
      submitDialog.visible = false;
      page.current = 1;
      await fetchApprovalList();
      return true;
    } catch {
      return false;
    } finally {
      submitSaving.value = false;
    }
  }
  async function submitEditApproval() {
    if (!submitFormRef.value) return false;
    try {
      await submitFormRef.value.validate();
    } catch {
      return false;
    }
    if (!activeTemplate.value) return false;
    const bindingCheck = validateTemplateBinding({ flowNodes: submitForm.flowNodes });
    if (!bindingCheck.ok) {
      ElMessage.warning(bindingCheck.message);
      return false;
    }
    if (!submitForm.instanceId) {
      ElMessage.warning("缺少审批实例 ID,无法保存");
      return false;
    }
    if (submitSaving.value) return false;
    submitSaving.value = true;
    try {
      await updateApprovalInstance(
        buildInstanceDto({
          submitForm,
          activeTemplate: activeTemplate.value,
          flowNodes: bindingCheck.nodes,
          existingRow: submitEditRow.value,
        })
      );
      submitDialog.visible = false;
      await fetchApprovalList();
      if (detailDialog.visible && detailRow.value?.id === submitForm.instanceId) {
        const hit = tableData.value.find((r) => r.id === submitForm.instanceId);
        if (hit) detailRow.value = { ...hit };
        else detailDialog.visible = false;
      }
      return true;
    } catch {
      return false;
    } finally {
      submitSaving.value = false;
    }
  }
  async function removeInstance(row) {
    if (row?.id == null || row.id === "") {
      ElMessage.warning("无法删除:缺少审批实例 ID");
      return;
    }
    const title = row.title || row.templateName || row.instanceNo || "该审批";
    try {
      await ElMessageBox.confirm(
        `确定要删除审批「${title}」吗?删除后不可恢复。`,
        "删除确认",
        {
          type: "warning",
          confirmButtonText: "确定删除",
          cancelButtonText: "取消",
          distinguishCancelAndClose: true,
          autofocus: false,
        }
      );
    } catch {
      return;
    }
    try {
      await deleteApprovalInstance([row.id]);
      ElMessage.success("删除成功");
      if (detailDialog.visible && detailRow.value?.id === row.id) {
        detailDialog.visible = false;
      }
      if (approveDialog.visible && approveDialog.row?.id === row.id) {
        approveDialog.visible = false;
      }
      await fetchApprovalList();
    } catch {
      /* é”™è¯¯ç”±æ‹¦æˆªå™¨æç¤º */
    }
  }
  async function submitReimburseApprove(result) {
    const row = reimburseDialog.instanceRow;
    if (!row?.id) return { ok: false };
    if (result === "rejected" && !(approveOpinion.value || "").trim()) {
      return { needOpinion: true };
    }
    if (approveSubmitting.value) return { ok: false };
    approveSubmitting.value = true;
    try {
      await approveApprovalInstance(
        buildApproveInstanceDto(row, result, approveOpinion.value)
      );
      reimburseDialog.visible = false;
      await fetchApprovalList();
      return { ok: true, result };
    } catch {
      ElMessage.error("审批操作失败");
      return { ok: false };
    } finally {
      approveSubmitting.value = false;
    }
  }
  async function submitApprove(result) {
    const row = approveDialog.row;
    if (!row?.id) return { ok: false };
    if (result === "rejected" && !(approveOpinion.value || "").trim()) {
      return { needOpinion: true };
    }
    if (approveSubmitting.value) return { ok: false };
    approveSubmitting.value = true;
    try {
      await approveApprovalInstance(
        buildApproveInstanceDto(row, result, approveOpinion.value)
      );
      approveDialog.visible = false;
      await fetchApprovalList();
      if (detailDialog.visible && detailRow.value?.id === row.id) {
        const hit = tableData.value.find((r) => r.id === row.id);
        if (hit) detailRow.value = { ...hit };
        else detailDialog.visible = false;
      }
      return { ok: true, result };
    } catch {
      ElMessage.error("审批操作失败");
      return { ok: false };
    } finally {
      approveSubmitting.value = false;
    }
  }
  function approvalActionLabel(result) {
    if (result === "approved") return "通过";
    if (result === "rejected") return "驳回";
    return "待处理";
  }
  return {
    Search,
    APPROVAL_TYPE_OPTIONS,
    APPROVAL_STATUS_SEARCH_OPTIONS,
    searchBusinessTypeOptions,
    loadSearchBusinessTypeOptions,
    approvalTypeLabel,
    approvalStatusLabel,
    approvalStatusTagType,
    approvalActionLabel,
    searchForm,
    tableLoading,
    page,
    tableData,
    tableColumn,
    detailDialog,
    detailRow,
    detailData,
    reimburseDialog,
    approveDialog,
    approveOpinion,
    approveSubmitting,
    submitReimburseApprove,
    isReimburseApprovalInstance,
    submitDialog,
    isSubmitEdit,
    submitDialogTitle,
    submitForm,
    submitFormRef,
    submitSaving,
    activeTemplate,
    submitFormFields,
    submitFormRules,
    submitBusinessTypeOptions,
    submitTemplateCards,
    selectedBusinessType,
    selectedBusinessTypeLabel,
    businessTypeLabel,
    countTemplatesByBusinessType,
    submitTemplatesLoading,
    handleQuery,
    resetSearch,
    pagination,
    resetSubmitDialogState,
    openSubmitDialog,
    openEditDialog,
    onBusinessTypePick,
    onTemplatePick,
    backToBusinessTypePick,
    backToTemplatePick,
    submitInstanceForm,
    submitNewApproval,
    submitApprove,
    openDetail,
    openApprove,
    fetchApprovalList,
  };
}