diff --git a/backend/app/api/crm_contract_routes.py b/backend/app/api/crm_contract_routes.py index 854492bdd..51f1848c4 100644 --- a/backend/app/api/crm_contract_routes.py +++ b/backend/app/api/crm_contract_routes.py @@ -8,7 +8,7 @@ from fastapi import Depends, Query from sqlalchemy.orm import Session from app.core.database import get_db -from app.core.menu_guard import require_admin_or_super_admin +from app.core.menu_guard import require_admin_or_super_admin, require_menu_code from app.core.oper_log_decorator import oper_log from app.core.response import error_response, success_response from app.core.tenant_context import get_tenant_no @@ -48,6 +48,9 @@ from app.crud.crm.contract_approval import ( approve_contract_by_contract_id, list_contract_approvals, ) +from app.crud.crm.contract_approver_setting import get_setting as get_contract_approver_setting +from app.crud.crm.contract_approver_setting import save_setting as save_contract_approver_setting +from app.schemas.crm.contract_approver_setting import ContractApproverSettingSaveBody from app.schemas.crm.contract import ( ContractApprovalActionBody, ContractBatchAssignBody, @@ -762,6 +765,42 @@ def register_contract_routes(router): except Exception as e: return error_response(500, str(e)) + @router.post("/contract-approver-setting/get") + async def contract_approver_setting_get( + _user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), + ): + try: + tenant_no = get_tenant_no(db, _user) + data = get_contract_approver_setting(db, tenant_no=tenant_no) + return success_response(data or {"approverIds": [], "status": "0"}) + except Exception as e: + return error_response(500, str(e)) + + @router.post("/contract-approver-setting/save") + async def contract_approver_setting_save( + body: ContractApproverSettingSaveBody, + _user: dict = Depends(require_menu_code("crm:contract:approverSetting")), + db: Session = Depends(get_db), + ): + try: + tenant_no = get_tenant_no(db, _user) + user_id = _user.get("user_id", "") or _user.get("id", "") + dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or "" + data = save_contract_approver_setting( + db, + tenant_no=tenant_no, + approver_ids=body.approverIds or [], + status=body.status or "0", + user_id=user_id, + dept_id=dept_id, + ) + return success_response(data, message="保存成功") + except ValueError as e: + return error_response(400, str(e)) + except Exception as e: + return error_response(500, str(e)) + @router.get("/contract/{contract_id}") async def contract_detail( contract_id: str, diff --git a/backend/app/crud/crm/contract_approver_setting.py b/backend/app/crud/crm/contract_approver_setting.py new file mode 100644 index 000000000..9ac68417f --- /dev/null +++ b/backend/app/crud/crm/contract_approver_setting.py @@ -0,0 +1,95 @@ +from __future__ import annotations + +from datetime import datetime +from typing import Any, Dict, List, Optional + +from sqlalchemy.orm import Session + +from app.core.utils import generate_uuid +from app.crud.crm.quote_approver_setting import join_approver_ids, parse_approver_ids +from app.models.crm_models import CrmContractApproverSetting +from app.models.models import SysUser + + +def _row_to_dict(row: CrmContractApproverSetting) -> Dict[str, Any]: + return { + "id": row.id, + "approverIds": parse_approver_ids(row.approver_ids), + "status": row.status or "0", + "tenantNo": row.tenant_no, + "updateTime": row.update_time.isoformat(sep=" ") if row.update_time else None, + } + + +def get_setting(db: Session, *, tenant_no: str) -> Optional[Dict[str, Any]]: + row = ( + db.query(CrmContractApproverSetting) + .filter( + CrmContractApproverSetting.tenant_no == tenant_no, + CrmContractApproverSetting.del_flag == 0, + ) + .order_by(CrmContractApproverSetting.update_time.desc()) + .first() + ) + return _row_to_dict(row) if row else None + + +def _assert_users_exist(db: Session, *, tenant_no: str, approver_ids: List[str]) -> None: + ids = [str(i).strip() for i in approver_ids if str(i).strip()] + if not ids: + return + rows = ( + db.query(SysUser.id) + .filter(SysUser.id.in_(ids), SysUser.del_flag == 0) + .all() + ) + found = {str(r[0]) for r in rows} + missing = [i for i in ids if i not in found] + if missing: + raise ValueError(f"审批人无效或不存在:{','.join(missing)}") + + +def save_setting( + db: Session, + *, + tenant_no: str, + approver_ids: List[str], + status: str, + user_id: str = None, + dept_id: str = "", +) -> Dict[str, Any]: + status_norm = "1" if str(status) == "1" else "0" + ids = [str(i).strip() for i in (approver_ids or []) if str(i).strip()] + _assert_users_exist(db, tenant_no=tenant_no, approver_ids=ids) + + row = ( + db.query(CrmContractApproverSetting) + .filter( + CrmContractApproverSetting.tenant_no == tenant_no, + CrmContractApproverSetting.del_flag == 0, + ) + .first() + ) + now = datetime.now() + if row: + row.approver_ids = join_approver_ids(ids) + row.status = status_norm + row.update_by = user_id + row.update_time = now + else: + row = CrmContractApproverSetting( + id=generate_uuid(), + approver_ids=join_approver_ids(ids), + status=status_norm, + tenant_no=tenant_no, + create_by=user_id, + create_dept_id=dept_id or "", + create_time=now, + update_by=user_id, + update_time=now, + del_flag=0, + ) + db.add(row) + db.commit() + db.refresh(row) + return _row_to_dict(row) diff --git a/backend/app/models/crm_models.py b/backend/app/models/crm_models.py index 6410f5683..af92fbd28 100644 --- a/backend/app/models/crm_models.py +++ b/backend/app/models/crm_models.py @@ -590,6 +590,25 @@ class CrmContract(Base): del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") +class CrmContractApproverSetting(Base): + """CRM-合同审批人设置(租户级)""" + + __tablename__ = "crm_contract_approver_setting" + + id = Column(String(64), primary_key=True, comment="主键ID(UUID)") + approver_ids = Column(String(1000), nullable=False, default="", comment="有序审批人用户ID,逗号分隔") + status = Column(String(1), nullable=False, default="0", comment="0启用 1停用") + tenant_no = Column(String(64), nullable=False, default="shanxia2.0", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(500), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建者") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建部门") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新者") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") + + class CrmContractApproval(Base): """CRM-合同审批记录表""" diff --git a/backend/app/schemas/crm/contract_approver_setting.py b/backend/app/schemas/crm/contract_approver_setting.py new file mode 100644 index 000000000..c4355ca6f --- /dev/null +++ b/backend/app/schemas/crm/contract_approver_setting.py @@ -0,0 +1,10 @@ +from typing import List + +from pydantic import BaseModel, ConfigDict, Field + + +class ContractApproverSettingSaveBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + approverIds: List[str] = Field(default_factory=list) + status: str = Field(default="0") # 0启用 1停用 diff --git a/backend/scripts/migrate_20260801_crm_contract_approver_setting.sql b/backend/scripts/migrate_20260801_crm_contract_approver_setting.sql new file mode 100644 index 000000000..f7f30077d --- /dev/null +++ b/backend/scripts/migrate_20260801_crm_contract_approver_setting.sql @@ -0,0 +1,40 @@ +-- backend/scripts/migrate_20260801_crm_contract_approver_setting.sql + +CREATE TABLE IF NOT EXISTS `crm_contract_approver_setting` ( + `id` varchar(64) NOT NULL COMMENT '主键ID(UUID)', + `approver_ids` varchar(1000) NOT NULL DEFAULT '' COMMENT '有序审批人用户ID,逗号分隔', + `status` char(1) NOT NULL DEFAULT '0' COMMENT '0启用 1停用', + `tenant_no` varchar(64) NOT NULL DEFAULT 'shanxia2.0' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(500) DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) DEFAULT NULL COMMENT '创建者', + `create_dept_id` varchar(64) NOT NULL DEFAULT '' COMMENT '创建部门', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) DEFAULT NULL COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` smallint(6) NOT NULL DEFAULT '0' COMMENT '删除标志', + PRIMARY KEY (`id`), + KEY `idx_tenant_no` (`tenant_no`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CRM-合同审批人设置(租户级)'; + +INSERT INTO `sys_menu` ( + `id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, + `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, + `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag` +) VALUES ( + 'CRM3000000000000000000000000026', + '合同审批人设置', + 'CRM00000000000000000000000000001', + 'C', + 'contract-approver-setting', + '/crm/contract/approver-setting/index', + 'crm:contract:approverSetting', + 1, '1', '1', 'shanxia2.0', 56, + '租户级默认合同审批人', + 'system', '', NOW(), NULL, NOW(), 0 +) +ON DUPLICATE KEY UPDATE + `menu_name`=VALUES(`menu_name`), + `component`=VALUES(`component`), + `perms`=VALUES(`perms`), + `path`=VALUES(`path`); diff --git a/docs/sql/shanxia2.0.sql b/docs/sql/shanxia2.0.sql index 51ccadaed..b4021e3ec 100644 --- a/docs/sql/shanxia2.0.sql +++ b/docs/sql/shanxia2.0.sql @@ -2296,6 +2296,33 @@ CREATE TABLE `crm_quote_approver_setting` ( BEGIN; COMMIT; +-- ---------------------------- +-- Table structure for crm_contract_approver_setting +-- ---------------------------- +DROP TABLE IF EXISTS `crm_contract_approver_setting`; +CREATE TABLE `crm_contract_approver_setting` ( + `id` varchar(64) NOT NULL COMMENT '主键ID(UUID)', + `approver_ids` varchar(1000) NOT NULL DEFAULT '' COMMENT '有序审批人用户ID,逗号分隔', + `status` char(1) NOT NULL DEFAULT '0' COMMENT '0启用 1停用', + `tenant_no` varchar(64) NOT NULL DEFAULT 'shanxia2.0' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(500) DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) DEFAULT NULL COMMENT '创建者', + `create_dept_id` varchar(64) NOT NULL DEFAULT '' COMMENT '创建部门', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) DEFAULT NULL COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` smallint(6) NOT NULL DEFAULT '0' COMMENT '删除标志', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_tenant_no` (`tenant_no`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CRM-合同审批人设置(租户级)'; + +-- ---------------------------- +-- Records of crm_contract_approver_setting +-- ---------------------------- +BEGIN; +COMMIT; + -- ---------------------------- -- Table structure for crm_quote_product -- ---------------------------- @@ -9838,6 +9865,7 @@ INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `co INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('CRM3000000000000000000000000010', '报价单管理', 'CRM00000000000000000000000000001', 'C', 'quote', '/crm/quote/index', 'crm:quote:list', 1, '1', '1', 'shanxia2.0', 5, NULL, 'system', '', '2026-07-05 21:00:00', NULL, '2026-07-05 21:00:00', 0); INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('CRM3000000000000000000000000015', '报价单审批人设置', 'CRM00000000000000000000000000001', 'C', 'quote-approver-setting', '/crm/quote/approver-setting/index', 'crm:quote:approverSetting', 1, '1', '1', 'shanxia2.0', 55, '租户级默认报价审批人', 'system', '', '2026-08-01 00:00:00', NULL, '2026-08-01 00:00:00', 0); INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('CRM3000000000000000000000000020', '合同管理', 'CRM00000000000000000000000000001', 'C', 'contract', '/crm/contract/index', 'crm:contract:list', 1, '1', '1', 'shanxia2.0', 6, NULL, 'system', '', '2026-07-05 21:00:00', NULL, '2026-07-05 21:00:00', 0); +INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('CRM3000000000000000000000000026', '合同审批人设置', 'CRM00000000000000000000000000001', 'C', 'contract-approver-setting', '/crm/contract/approver-setting/index', 'crm:contract:approverSetting', 1, '1', '1', 'shanxia2.0', 56, '租户级默认合同审批人', 'system', '', '2026-08-01 00:00:00', NULL, '2026-08-01 00:00:00', 0); INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('CRM3000000000000000000000000030', '调价函管理', 'CRM00000000000000000000000000001', 'C', 'price-letter', '/crm/price-letter/index', 'crm:priceLetter:list', 1, '1', '1', 'shanxia2.0', 7, '独立调价函档案:接受(附件) / 发出(富文本打印);不关联合同与报价单', 'system', '', '2026-07-15 20:23:03', NULL, '2026-07-26 12:01:03', 0); INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('CRM3000000000000000000000000031', '调价函新增', 'CRM3000000000000000000000000030', 'F', '', NULL, 'crm:priceLetter:add', 1, '1', '1', 'shanxia2.0', 1, NULL, 'system', '', '2026-07-15 20:23:03', NULL, '2026-07-15 20:23:03', 0); INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('CRM3000000000000000000000000032', '调价函编辑', 'CRM3000000000000000000000000030', 'F', '', NULL, 'crm:priceLetter:edit', 1, '1', '1', 'shanxia2.0', 2, NULL, 'system', '', '2026-07-15 20:23:03', NULL, '2026-07-15 20:23:03', 0); diff --git a/docs/superpowers/specs/2026-08-01-contract-approver-setting-design.md b/docs/superpowers/specs/2026-08-01-contract-approver-setting-design.md new file mode 100644 index 000000000..cab6fbe66 --- /dev/null +++ b/docs/superpowers/specs/2026-08-01-contract-approver-setting-design.md @@ -0,0 +1,41 @@ +# 合同审批人设置(租户级默认审批人) + +**日期:** 2026-08-01 +**状态:** 已实现 +**选定方案:** A — 1:1 复用「报价单审批人设置」 +**对照:** `docs/superpowers/specs/2026-08-01-quote-approver-setting-design.md` + +## 1. 目标 + +1. 管理员在「合同审批人设置」配置租户级有序默认审批人。 +2. 合同「提交审批」弹窗(`ApprovalDialog`,含批量)自动预填,可改。 +3. 未配置 / 停用时与现网一致(审批人必填、手选)。 + +## 2. 非目标 + +与报价单首版相同:无默认备注、无部门/金额分岔、不共用报价设置表、不接工作流引擎。 + +## 3. 决策(继承报价单) + +| 项 | 决策 | +|----|------| +| 粒度 | 租户级一份 | +| 提交可改 | 可以 | +| 维护权限 | 菜单 `crm:contract:approverSetting` | +| 未配置 | 手选 | +| 空配置 get | `{ approverIds: [], status: '0' }`(避免首次落到停用) | + +## 4. 数据 / API / UI + +- 表:`crm_contract_approver_setting`(字段同 `crm_quote_approver_setting`) +- API:`POST /web/crm/contract-approver-setting/get|save` +- 页:`#/crm/contract-approver-setting` → `views/crm/contract/approver-setting/index.vue` +- 预填:仅改 `contract/components/ApprovalDialog.vue`(列表/表单/详情均复用该弹窗) +- 菜单 ID:`CRM3000000000000000000000000026` + +## 5. 验收 + +1. 设置启用审批人后,提交审批弹窗预填且可改。 +2. 停用/清空后恢复手选。 +3. 无设置菜单权限不可 save;有合同权限可读 get。 +4. 租户隔离。 diff --git a/docs/web-admin-ui-pattern-index.md b/docs/web-admin-ui-pattern-index.md index 29ed4bae4..c5d3c48c4 100644 --- a/docs/web-admin-ui-pattern-index.md +++ b/docs/web-admin-ui-pattern-index.md @@ -454,6 +454,7 @@ const viewSections = computed(() => [{ | protocol(录入) | P01 + P12 | | demo/product | P01 + 专用 product-view-dialog | | **crm/quote/approver-setting**(报价单审批人设置) | 单对象配置表单(对照 `docs/fresh-mint-quote-approver-setting-demo.html`;无独立 P 编号) | +| **crm/contract/approver-setting**(合同审批人设置) | 单对象配置表单(与报价审批人设置同模式) | --- diff --git a/web-admin/src/api/crm/contract/index.ts b/web-admin/src/api/crm/contract/index.ts index 0e9f79727..7bcb99b00 100644 --- a/web-admin/src/api/crm/contract/index.ts +++ b/web-admin/src/api/crm/contract/index.ts @@ -2,9 +2,16 @@ * 合同管理 API(门户 http + Java 对齐 GET/PUT/DELETE 语义) */ import request from '@/utils/http' -import type { ContractForm, ContractQuery, ContractVO } from './types' +import type { + ContractApproverSettingForm, + ContractApproverSettingVO, + ContractForm, + ContractQuery, + ContractVO +} from './types' const BASE = '/web/crm/contract' +const SETTING_BASE = '/web/crm/contract-approver-setting' function unwrapData(res: any) { return res?.data ?? res ?? {} @@ -323,4 +330,27 @@ export async function listContractOperLog(_contractId: number | string) { return { rows: data.list || data.rows || [], total: data.total ?? 0 } } +export async function getContractApproverSetting() { + const res = await request.post({ url: `${SETTING_BASE}/get`, data: {} }) + const data = unwrapData(res) || {} + return { + data: { + id: data.id, + approverIds: (data.approverIds || []).map(String), + status: data.status === '1' ? '1' : '0', + updateTime: data.updateTime + } as ContractApproverSettingVO + } +} + +export function saveContractApproverSetting(data: ContractApproverSettingForm) { + return request.post({ + url: `${SETTING_BASE}/save`, + data: { + approverIds: (data.approverIds || []).map(String), + status: data.status + } + }) +} + export * from './types' diff --git a/web-admin/src/api/crm/contract/types.ts b/web-admin/src/api/crm/contract/types.ts index 7c5c64cf1..dde9fa114 100644 --- a/web-admin/src/api/crm/contract/types.ts +++ b/web-admin/src/api/crm/contract/types.ts @@ -379,3 +379,16 @@ export type CreateFromQuoteForm = Pick< ContractForm, 'contractNo' | 'contractName' | 'customerId' | 'contractType' | 'amount' | 'signDate' | 'startDate' | 'endDate' | 'remark' | 'products' > + +/** 合同审批人设置 */ +export interface ContractApproverSettingVO { + id?: string + approverIds: (string | number)[] + status: '0' | '1' | string + updateTime?: string +} + +export interface ContractApproverSettingForm { + approverIds: (string | number)[] + status: '0' | '1' +} diff --git a/web-admin/src/router/modules/sqxp.ts b/web-admin/src/router/modules/sqxp.ts index c6d2a9a7e..b519da225 100644 --- a/web-admin/src/router/modules/sqxp.ts +++ b/web-admin/src/router/modules/sqxp.ts @@ -414,6 +414,17 @@ export const sqxpAdminRoutes: AppRouteRecord[] = [ component: '/crm/contract/index', meta: { title: '合同管理', keepAlive: true, menuCode: 'crm:contract:list' } }, + { + id: 'CRM3000000000000000000000000026', + name: 'CrmContractApproverSetting', + path: 'contract-approver-setting', + component: '/crm/contract/approver-setting/index', + meta: { + title: '合同审批人设置', + keepAlive: true, + menuCode: 'crm:contract:approverSetting' + } + }, { id: 'CRM3000000000000000000000000030', name: 'CrmPriceLetter', diff --git a/web-admin/src/views/crm/contract/approver-setting/index.vue b/web-admin/src/views/crm/contract/approver-setting/index.vue new file mode 100644 index 000000000..b5702e769 --- /dev/null +++ b/web-admin/src/views/crm/contract/approver-setting/index.vue @@ -0,0 +1,146 @@ + + + + + 合同审批人设置 + + + + + + + 默认审批人支持多人,按选择顺序依次审批 + + + + + + + + 启用 + 停用 + + + + + + + 重置 + + + 保存设置 + + + + + + + + + + diff --git a/web-admin/src/views/crm/contract/components/ApprovalDialog.vue b/web-admin/src/views/crm/contract/components/ApprovalDialog.vue index 45720db00..d9e0388e4 100644 --- a/web-admin/src/views/crm/contract/components/ApprovalDialog.vue +++ b/web-admin/src/views/crm/contract/components/ApprovalDialog.vue @@ -35,6 +35,14 @@ 选择审批人支持搜索用户姓名 + import { ref, computed } from 'vue' import { Check, Close } from '@element-plus/icons-vue' -import { submitContract, batchSubmitContract, approveContract, rejectContract, withdrawContract } from '@/api/crm/contract' +import { + submitContract, + batchSubmitContract, + approveContract, + rejectContract, + withdrawContract, + getContractApproverSetting +} from '@/api/crm/contract' import { fetchUserOptions } from '@/api/admin' import { $modal } from '@/utils/modal' interface ApproverUser { - userId: string | number + userId: string nickName: string userName: string } @@ -146,7 +161,7 @@ const contractIds = ref<(string | number)[]>() const form = ref({ approvalType: 'sequential', - approverIds: [] as (string | number)[], + approverIds: [] as string[], comment: '', remark: '', reason: '' @@ -155,6 +170,7 @@ const form = ref({ const approverList = ref([]) const submitLoading = ref(false) const userLoading = ref(false) +const submitPrefillTip = ref(false) const formRef = ref() const rules = computed(() => { @@ -195,18 +211,19 @@ const confirmButtonText = computed(() => { return textMap[actionType.value] }) -const open = (id: string | number, type: 'submit' | 'approve' | 'reject' | 'withdraw' = 'submit') => { +const open = async (id: string | number, type: 'submit' | 'approve' | 'reject' | 'withdraw' = 'submit') => { contractId.value = id contractIds.value = undefined actionType.value = type dialogVisible.value = true resetForm() if (type === 'submit') { - loadApproverList() + await loadApproverList() + await applyPrefillApprovers() } } -const openBatch = ( +const openBatch = async ( idList: (string | number)[], type: 'submit' | 'approve' | 'reject' | 'withdraw' = 'submit' ) => { @@ -216,7 +233,8 @@ const openBatch = ( dialogVisible.value = true resetForm() if (type === 'submit') { - loadApproverList() + await loadApproverList() + await applyPrefillApprovers() } } @@ -225,11 +243,13 @@ const loadApproverList = async (nickName?: string) => { try { const res = await fetchUserOptions() const options = (res as any)?.data ?? res ?? [] - let list = (Array.isArray(options) ? options : []).map((item: any) => ({ - userId: item.value ?? item.userId ?? item.id, - nickName: item.label ?? item.nickName ?? item.userName ?? '', - userName: item.userNo ?? item.userName ?? '' - })) + let list = (Array.isArray(options) ? options : []) + .map((item: any) => ({ + userId: String(item.value ?? item.userId ?? item.id ?? ''), + nickName: item.label ?? item.nickName ?? item.userName ?? '', + userName: item.userNo ?? item.userName ?? '' + })) + .filter((u) => u.userId) if (nickName) { const q = nickName.toLowerCase() list = list.filter( @@ -246,6 +266,23 @@ const loadApproverList = async (nickName?: string) => { } } +async function resolvePrefillApproverIds(userList: ApproverUser[]) { + try { + const { data } = await getContractApproverSetting() + if (!data || String(data.status) !== '0') return [] as string[] + const valid = new Set(userList.map((u) => String(u.userId))) + return (data.approverIds || []).map(String).filter((id) => valid.has(id)) + } catch { + return [] + } +} + +async function applyPrefillApprovers() { + const prefillIds = await resolvePrefillApproverIds(approverList.value) + form.value.approverIds = prefillIds + submitPrefillTip.value = prefillIds.length > 0 +} + const handleSearchUser = (query: string) => { if (query) { loadApproverList(query) @@ -299,14 +336,21 @@ const handleClose = () => { resetForm() } +const defaultComment = (type: typeof actionType.value) => { + if (type === 'approve') return '审批通过' + if (type === 'reject') return '审批驳回' + return '' +} + const resetForm = () => { form.value = { approvalType: 'sequential', approverIds: [], - comment: '', + comment: defaultComment(actionType.value), remark: '', reason: '' } + submitPrefillTip.value = false formRef.value?.clearValidate() }