企业用印管理系统全流程设计:印章台账+BPM审批+外借冲突检测——从"随便盖章"到"一印一审"

0 阅读22分钟

企业用印管理系统全流程设计:印章台账+BPM审批+外借冲突检测——从"随便盖章"到"一印一审"

🌐 文档地址ruoyioffice.com |📦 源码1ruoyi-office-vben |📦 源码2ruoyi-office |📦 源码3ruoyi-office |💬 微信:17156169080(备注「RuoYi Office」)

印章是企业经营活动中最敏感的法律凭证——一个公章盖错地方,轻则合同纠纷,重则法律责任。但大量企业的印章管理还停留在"找行政拿章、签字登记、盖完还回来"的阶段:印章借走三天没人追,同一枚章被两个部门同时借走,离职员工还能以保管人身份用印。RuoYi Office 用 2 张表、5 种状态机、1 套 BPM 审批流程 + 外借时间冲突检测算法,构建了从印章建档→用印申请→审批→用印→外借→归还的全生命周期管理闭环。

引言:用印管理到底难在哪?

"不就是登记一下谁盖了什么章吗?"——初次接到需求的开发者大多这么想。但真正动手时会发现,用印管理的复杂度远超"登记盖章"四个字:

印章种类多,权限不同:公章能代表公司签合同,财务章只能盖在财务文件上,合同专用章只能用于合同。不同类型的印章审批层级和使用范围完全不同。

用章方式分两种:现场用印(拿文件到行政处盖章,盖完就走)和外借用章(把印章带走,用完再还)。外借涉及借出时间、归还追踪、逾期提醒,复杂度翻倍。

外借时间冲突:一枚印章在同一时间段只能借给一个人。如果 A 部门借了公章 3.20-3.25,B 部门想借 3.22-3.28,系统必须检测到时间重叠并拒绝。

审批流程不能跳过:任何用印操作都必须走审批,审批通过后才能用章。审批中的申请单也要纳入冲突检测范围——防止"审批还没通过就把章借走了"。

状态流转复杂:现场用印审批通过即完成;外借用印还要经历"外借中→已归还"的后续流程,可能还会逾期。

本文以 RuoYi Office 的用印管理模块为例,完整拆解其业务建模、数据结构、冲突检测算法、前后端实现方案。


一、业务设计:从一次用印申请说起

1.1 业务全景

一次完整的用印业务涉及三个角色、四个阶段:

阶段角色操作系统行为
建档行政管理员录入印章信息(编号/名称/类型/分类/保管人)创建台账记录,状态为「在库」
申请普通员工选择印章、填写用途和方式 → 提交审批创建申请单,发起BPM流程
审批审批人审核用印事由、文件信息审批通过后,用印状态变为「待处理」
用印/归还行政管理员现场盖章或登记外借,外借到期后确认归还状态流转至「已完成」或「已归还」

1.2 现场用印 vs 外借用章:两条分叉路

这是本方案最核心的业务抽象——用章方式决定了申请单的生命周期长度seal-apply-flow.png

系统在申请单层面定义了用章方式(useMode),在审批通过后自动判断:

  • 现场用印useMode = 1):管理员确认用印后,状态直接变为「已完成」,流程结束
  • 外借用章useMode = 2):管理员登记借出后,状态变为「外借中」,等待后续归还确认;如果超过预计归还时间仍未归还,状态变为「已逾期」

这种设计用一个字段值就实现了两条截然不同的流转路径,避免了为两种用章方式各写一套业务逻辑。

1.3 五种用印状态的生命周期

每张用印申请单都有独立的用印状态,由 SealUseStatusEnum 定义:

状态码状态名触发条件可执行操作
0待处理BPM审批通过管理员可确认用印或登记外借
1已完成现场用印确认完成无(终态)
2外借中外借用章登记借出可归还
3已归还外借用章归还确认无(终态)
4已逾期超过预计归还时间未归还可归还(补归还)

上方的业务流转全景图已完整展示了五种状态的流转路径(参见 1.2 节配图),下面用表格说明具体的状态转换规则:

1.4 印章分类与类型体系

印章台账通过两个维度对印章进行分类管理:

维度字典编码典型值用途
印章分类OA_SEAL_CLS行政类 / 业务类 / 财务类 / 其他类台账左侧导航筛选
印章类型OA_SEAL_TYPE公章 / 合同专用章 / 财务专用章 / 发票专用章 / 法人章标识印章用途和法律效力

两个维度正交组合,一枚"行政类·公章"和一枚"财务类·财务专用章"在台账中清晰区分,搜索和审批时一目了然。


二、系统设计:两个子模块的协作

2.1 模块组成

RuoYi Office 的用印管理位于 OA 办公管理 → 用印管理 目录下,由两个紧密协作的子模块组成:

子模块菜单名称功能定位面向角色
印章台账印章信息管理印章主数据管理:编号、名称、类型、分类、保管人、状态行政管理员
用印申请用印申请管理员工发起用印申请、走BPM审批、外借归还全体员工

2.2 核心设计决策

决策点方案理由
数据模型台账+申请单两张表台账管"有哪些章",申请单管"谁用了什么章"
冲突检测范围审批中 + 外借中审批中的单据也占用时间段,防止审批后才发现冲突
冲突检测时机提交时校验保存不校验(允许草稿),提交审批时才严格检查
印章选择范围仅在库印章SealSelectModal 固定 status: 0,停用/使用中的章不可选
冗余存储申请单冗余印章编号/名称/保管人列表展示不需要 JOIN 台账表,历史数据不受台账修改影响
审批集成FlowBillService标准接口复用BPM框架回调,状态同步零耦合
我的单据后端强制注入creator数据安全,员工只能看到自己的申请
流程变量额外传入 sealUseMode审批流程可根据用章方式走不同分支

三、PC 端功能实现

3.1 印章台账

台账页面是用印管理的"数据基石",采用左侧分类导航 + 右侧表格列表的经典布局。 image.png

image.png

▲ 印章台账:左侧按字典 OA_SEAL_CLS 动态渲染分类菜单(行政类、业务类、财务类、其他类),点击分类自动筛选;右侧表格展示印章编号、名称、类型、状态、保管人等关键信息

台账设计要点

  • 分类导航联动:左侧 Menu 组件绑定 selectedSealCls 状态,切换分类时自动触发 gridApi.query(),查询参数中注入 sealCls 过滤条件
  • 状态三色标签:通过字典 oa_seal_status 渲染为绿色标签(在库)、灰色标签(停用)、橙色标签(使用中)
  • 类型/分类双维度sealTypesealCls 分别通过 CellDict 组件渲染,支持两个维度的独立筛选
  • 保管人联动:新增/编辑时选择保管人自动填充部门信息(keeperName + keeperDeptName
  • 印章照片CellImage 组件展示印章照片缩略图,支持点击放大预览
  • 导出Excel:支持按当前筛选条件导出印章数据

分类导航的前端实现简洁高效:

const selectedSealCls = ref<null | number>(null);
const sealClsOptions = getDictOptions(DICT_TYPE.OA_SEAL_CLS, 'number');

watch(selectedSealCls, () => {
  onRefresh();
});

function handleSelectSealCls(sealCls: null | number) {
  selectedSealCls.value = sealCls;
}

查询时将分类条件合并到请求参数中:

query: async ({ page }, formValues) => {
  const queryParams = {
    pageNo: page.currentPage,
    pageSize: page.pageSize,
    ...formValues,
    ...(selectedSealCls.value === null
      ? {}
      : { sealCls: selectedSealCls.value }),
  };
  return await getSealPage(queryParams);
},

3.2 用印申请列表

申请列表展示当前用户创建的所有用印申请单,支持新建、查看详情、删除操作。 image.png

▲ 用印申请列表:支持按单据编号、流程状态、印章名称、用章类型、用章方式、用印状态等多维度搜索筛选。列表默认只显示当前登录用户的申请单

列表设计要点

  • 我的单据过滤:后端 getSealApplyBillPage 自动注入 creator = 当前用户ID,前端也传入 creatorcompanyId 双重保障
  • 单据编号链接:点击编号列自动跳转到详情页,编号格式为 OA110-{YYYYMMDD}{5位流水}
  • 双状态展示:同时展示「流程状态」(审批中/已通过/已拒绝)和「用印状态」(待处理/已完成/外借中/已归还/已逾期),通过 CellDict 渲染为不同颜色标签
  • 删除约束:删除前校验流程状态,审批中的单据不可删除
  • 批量删除:勾选多条记录一键批量删除,同样遵守状态约束
  • 印章选择搜索:搜索区的印章字段使用 HelpInput 组件,点击弹出 SealSelectModal 选择印章

3.3 用印申请详情

详情页是申请单的核心交互页面,集 基本信息表单、印章信息、时间信息、附件管理 于一体。image.png

「基本信息」区域

字段组件说明
印章名称HelpInput点击弹出SealSelectModal选择印章,选中后自动回填编号/名称/类型/保管人
用章类型Select合同用章/证明用章/公函用章/其他用章(字典 oa_seal_use_type
用章方式Select现场用印/外借用章(字典 oa_seal_use_mode
文件标题Input需要用印的文件标题
文件类型Input文件类型描述
文件份数InputNumber需要盖章的文件份数
合同金额InputNumber用章类型=合同时显示
合同对方Input用章类型=合同时显示
预计用章时间DatePicker必填
预计归还时间DatePicker用章方式=外借时显示且必填
是否紧急Select否/是
用章事由TextArea必填,审批人以此判断合理性
备注TextArea选填

印章选择弹窗 SealSelectModal

弹窗内部是一个独立的分页表格,支持按编号、名称、类型搜索过滤。选择一枚印章后点击确认,选中的印章信息自动回填到表单中。弹窗强制只展示状态为"在库"的印章status: 0),停用和使用中的印章不可被选择:

query: async ({ page }, formValues) => {
  const queryParams = {
    pageNo: page.currentPage,
    pageSize: page.pageSize,
    status: 0, // 只显示在库状态的印章
    ...formValues,
  };
  return await getSealPage(queryParams);
},

条件字段联动

  • useType = 1(合同用章)时,动态显示「合同金额」和「合同对方」字段
  • useMode = 2(外借用章)时,动态显示「预计归还时间」字段
  • 审批流程到达「申请人归还印章」节点时,显示「实际归还时间」字段供填写
  • 审批通过后所有字段自动切换为只读模式

四、流程设计:BPM 审批驱动状态流转

4.1 流程编排

用印申请使用 Flowable 引擎编排,流程定义 Key 为 oa_seal_apply_bill。流程设计兼顾了现场用印和外借用章两种场景:

  1. 员工填写用印信息(选择印章 + 填写事由/方式) → 提交流程
  2. 部门负责人审批
  3. 行政/法务审批
  4. 审批通过,用印状态变为「待处理」
  5. 行政管理员确认用印(现场用印 → 已完成)或登记外借(外借 → 外借中)
  6. 外借用章到期后,申请人归还印章 → 管理员确认 → 已归还

流程变量的妙用:提交时额外传入 sealUseMode(用章方式),Flowable 流程可以根据这个变量走不同的审批分支——比如外借用章增加法务审批节点,现场用印只需部门负责人审批即可。

4.2 FlowBillService 回调驱动状态变化

申请单服务实现了 FlowBillService<OaBillTypeEnum> 接口,BPM 引擎在流程状态变化时自动回调 updateProcessStatus

@Override
public void updateProcessStatus(String businessKey, Integer status) {
    Long id = Long.parseLong(businessKey);
    log.info("[updateProcessStatus] 更新用印申请单流程状态,id: {}, status: {}", id, status);

    // 校验用印申请单存在
    validateSealApplyBillExists(id);

    // 更新流程状态
    SealApplyBillDO updateObj = new SealApplyBillDO();
    updateObj.setId(id);
    updateObj.setProcessStatus(status);
    
    // 如果审批通过,设置用印状态为待处理
    if (APPROVE.getStatus().equals(status)) {
        updateObj.setUseStatus(SealUseStatusEnum.PENDING.getStatus());
    }
    
    sealApplyBillMapper.updateById(updateObj);
}

关键设计

  • 审批通过时,useStatus 自动设为 PENDING(待处理),管理员可以在后续操作中确认用印或登记外借
  • 审批拒绝/撤回时,只更新 processStatususeStatus 保持为空——申请单在业务层面"从未生效"
  • 整个状态流转由 BPM 框架通过 processDefinitionKey 匹配 FlowBillService 实现类,零硬编码

4.3 用印状态的后续流转

审批通过后,用印状态通过一组 markAs* 方法驱动:

方法目标状态触发场景
markAsCompletedCOMPLETED(1)现场用印确认完成
markAsBorrowedBORROWED(2)外借用章登记借出
markAsReturnedRETURNED(3)外借用章归还确认
markAsOverdueOVERDUE(4)超过预计归还时间未归还

每个方法底层都调用 updateUseStatus,实现简洁统一:

@Override
public void updateUseStatus(Long id, Integer useStatus) {
    validateSealApplyBillExists(id);
    
    SealApplyBillDO updateObj = new SealApplyBillDO();
    updateObj.setId(id);
    updateObj.setUseStatus(useStatus);
    sealApplyBillMapper.updateById(updateObj);
}

@Override
public void markAsCompleted(Long id) {
    updateUseStatus(id, SealUseStatusEnum.COMPLETED.getStatus());
}

@Override
public void markAsBorrowed(Long id) {
    updateUseStatus(id, SealUseStatusEnum.BORROWED.getStatus());
}

五、后端核心实现

5.1 两层数据模型

用印管理使用 2 张表实现完整的业务数据存储:

oa_seal(印章台账)
    └── 1:N ──▶ oa_seal_apply_bill(用印申请单)
                   通过 seal_id 关联

与办公用品管理不同,用印管理没有明细行表——因为每次用印申请只针对一枚印章,不存在"一次申请盖多枚章"的场景。这种一对一的关系让数据模型更加简洁。

5.2 单据编号生成

每张申请单都有唯一的单据编号,格式为 OA110-{YYYYMMDD}{5位流水}(如 OA110-2026032700001)。编号在首次保存或提交时自动生成:

if (StringUtils.isBlank(saveReqVO.getBillCode())) {
    saveReqVO.setBillCode(BillCodeUtils.generateBillCode(
        SystemEnum.OA, OaBillTypeEnum.OA_SEAL_APPLY_BILL));
}

110 是 OA 模块中用印申请单的类型编码,由 OaBillTypeEnum.OA_SEAL_APPLY_BILL 定义。流水号基于 Redis 自增,key 为 bill_code:OA:110:{yyyyMMdd},过期时间 2 天,保证分布式环境下编号唯一且递增。

5.3 提交流程:外借校验是关键

提交用印申请是整个业务中逻辑最密集的环节——需要生成编号、校验外借冲突、保存数据、发起BPM流程、保存附件,全部在一个方法中完成:

@Override
public Long submitSealApplyBill(SealApplyBillSaveReqVO saveReqVO) {
    // 1. 生成单据编号
    if (StringUtils.isBlank(saveReqVO.getBillCode())) {
        saveReqVO.setBillCode(BillCodeUtils.generateBillCode(
            SystemEnum.OA, OaBillTypeEnum.OA_SEAL_APPLY_BILL));
    }

    // 2. 外借时间冲突校验(仅外借用章需要)
    if (saveReqVO.getUseMode() != null && saveReqVO.getUseMode() == 2) {
        validateTimeConflict(saveReqVO);
    }

    // 3. 保存申请单(状态设为审批中)
    SealApplyBillDO sealApplyBill = BeanUtils.toBean(saveReqVO, SealApplyBillDO.class)
            .setProcessStatus(BpmTaskStatusEnum.RUNNING.getStatus());
    sealApplyBillMapper.insertOrUpdate(sealApplyBill);

    // 4. 发起 BPM 流程实例
    Map<String, Object> variables = BpmProcessVariableUtils.buildBillVariables(saveReqVO);
    variables.put(PV_SEAL_USE_MODE, saveReqVO.getUseMode());
    String processInstanceId = processInstanceApi.submitProcessInstance(
        Long.valueOf(saveReqVO.getCreator()),
        new BpmProcessInstanceCreateReqDTO()
            .setProcessDefinitionKey("oa_seal_apply_bill")
            .setVariables(variables)
            .setBusinessKey(String.valueOf(sealApplyBill.getId()))
    ).getCheckedData();

    // 5. 回写流程实例ID
    sealApplyBillMapper.updateById(new SealApplyBillDO()
        .setId(sealApplyBill.getId())
        .setProcessInstanceId(processInstanceId));
    
    // 6. 保存附件信息
    if (saveReqVO.getAttachments() != null) {
        attachmentService.saveAttachmentList(
            OaBillTypeEnum.OA_SEAL_APPLY_BILL.getTypeCode(),
            sealApplyBill.getId(), saveReqVO.getAttachments());
    }
    
    return sealApplyBill.getId();
}

注意:提交流程时只校验冲突,不改变印章状态。印章状态(在库/使用中)的变更由管理员在确认用印或登记外借时手动触发,这样即使审批被撤回,印章状态也不会出现不一致。

5.4 外借时间冲突检测:防止一章多借

validateTimeConflict 是用印管理中最复杂的校验逻辑——需要判断同一枚印章在申请的外借时间段内,是否已有其他冲突的申请单:

private void validateTimeConflict(SealApplyBillSaveReqVO saveReqVO) {
    if (saveReqVO.getSealId() == null 
        || saveReqVO.getExpectedUseTime() == null 
        || saveReqVO.getExpectedReturnTime() == null) {
        return;
    }

    // 预计用章时间不能晚于预计归还时间
    if (saveReqVO.getExpectedUseTime().isAfter(saveReqVO.getExpectedReturnTime())) {
        throw exception(SEAL_TIME_CONFLICT);
    }

    // 查询同一印章在相同时间段内的冲突申请单
    List<SealApplyBillDO> conflictBills = sealApplyBillMapper.selectList(
        new LambdaQueryWrapperX<SealApplyBillDO>()
            .eq(SealApplyBillDO::getSealId, saveReqVO.getSealId())
            .eq(SealApplyBillDO::getUseMode, 2) // 仅检查外借用章
            .ne(saveReqVO.getId() != null, SealApplyBillDO::getId, saveReqVO.getId())
            .and(wrapper -> wrapper
                // 场景1:审批中的申请单时间重合
                .and(sub -> sub
                    .eq(SealApplyBillDO::getProcessStatus, RUNNING.getStatus())
                    .and(time -> time
                        .and(t -> t.le(SealApplyBillDO::getExpectedUseTime, 
                                       saveReqVO.getExpectedUseTime())
                                  .ge(SealApplyBillDO::getExpectedReturnTime, 
                                       saveReqVO.getExpectedUseTime()))
                        .or(t -> t.le(SealApplyBillDO::getExpectedUseTime, 
                                      saveReqVO.getExpectedReturnTime())
                                 .ge(SealApplyBillDO::getExpectedReturnTime, 
                                      saveReqVO.getExpectedReturnTime()))
                        .or(t -> t.ge(SealApplyBillDO::getExpectedUseTime, 
                                      saveReqVO.getExpectedUseTime())
                                 .le(SealApplyBillDO::getExpectedReturnTime, 
                                      saveReqVO.getExpectedReturnTime()))
                    ))
                // 场景2:已通过且外借中的申请单时间重合
                .or(sub -> sub
                    .eq(SealApplyBillDO::getProcessStatus, APPROVE.getStatus())
                    .eq(SealApplyBillDO::getUseStatus, 
                        SealUseStatusEnum.BORROWED.getStatus())
                    // ... 同样的三种时间重叠判断
                )
            )
    );

    if (!conflictBills.isEmpty()) {
        throw exception(SEAL_TIME_CONFLICT);
    }
}

冲突检测的三种时间重叠模式

已有区间:      |=====已有外借=====|
新申请区间:
模式1(左交叉):  |====新====|           → 新开始在已有区间内
模式2(右交叉):           |====新====|  → 新结束在已有区间内
模式3(包含):    |==========新==========| → 新完全包含已有

检测的两类冲突源

冲突源条件含义
审批中的申请单processStatus = RUNNING还在审批但已占用时间段
已通过且外借中processStatus = APPROVE + useStatus = BORROWED印章实际正在外借

已完成、已归还、已拒绝的申请单不参与冲突检测——它们不再占用印章的时间资源。编辑自己的申请单时通过 .ne(SealApplyBillDO::getId, saveReqVO.getId()) 排除自身,避免"自己跟自己冲突"。

5.5 删除时清理流程与附件

删除申请单时,需要同时清理 BPM 流程实例和附件:

@Override
public void deleteSealApplyBill(Long id) {
    SealApplyBillDO bill = sealApplyBillMapper.selectById(id);
    if (bill == null) {
        throw exception(SEAL_APPLY_BILL_NOT_EXISTS);
    }

    // 清理 BPM 流程实例(尽力清理,不阻塞删除)
    if (StringUtils.isNotBlank(bill.getProcessInstanceId())) {
        try {
            processInstanceApi.deleteProcessInstance(
                Long.valueOf(bill.getCreator()), bill.getProcessInstanceId());
        } catch (Exception e) {
            log.warn("[deleteSealApplyBill] 清理流程实例失败: {}", e.getMessage());
        }
    }

    // 清理附件和主表
    attachmentService.deleteAttachmentByBusiness(
        OaBillTypeEnum.OA_SEAL_APPLY_BILL.getTypeCode(), id);
    sealApplyBillMapper.deleteById(id);
}

流程清理使用 try-catch 包裹,即使流程引擎出错也不影响业务数据删除——这是一种"尽力清理"的防御性设计。


六、RuoYi Office 的创新设计

6.1 外借冲突检测:审批中也算占用

很多用印系统只检测"当前正在外借中"的印章,忽略了审批中的申请。RuoYi Office 将审批中的外借申请也纳入冲突检测范围

  • 传统方案:只检测 useStatus = 外借中 → A 审批中、B 也能申请同一时段 → 两个都通过后印章冲突
  • RuoYi Office:检测 processStatus = 审批中 + useStatus = 外借中 → 从审批阶段就防止冲突

这种设计虽然可能导致"A 审批被拒绝后,B 才能提交同一时段"的小延迟,但彻底杜绝了"两张审批通过的申请单争抢同一枚章"的严重问题。

6.2 现场用印不检测冲突

只有外借用章(useMode = 2)才触发时间冲突检测。现场用印是"到行政处盖一下就走",不存在印章被带走的问题,多人可以在同一天现场用同一枚章——这符合真实业务场景。

6.3 流程变量驱动审批分支

提交时额外传入 sealUseMode 流程变量,Flowable 流程可以据此设计不同的审批路径:

processInstanceVariables.put(PV_SEAL_USE_MODE, saveReqVO.getUseMode());

典型的分支策略:

  • 现场用印sealUseMode = 1):部门负责人 → 行政确认 → 完成
  • 外借用章sealUseMode = 2):部门负责人 → 法务审核 → 行政登记 → 申请人归还 → 完成

外借涉及印章离开公司,风险更高,增加法务审核节点是合理的。

6.4 条件字段的前端动态控制

申请表单不是所有字段一次性全部展示,而是根据用户选择动态显示:

  • 选择「合同用章」→ 显示合同金额、合同对方
  • 选择「外借用章」→ 显示预计归还时间
  • 流程到达「归还」节点 → 显示实际归还时间(可编辑)
  • 审批通过后 → 所有字段切换为只读

这种渐进式表单设计减少了用户的认知负担,只在需要时展示相关字段。

6.5 印章信息冗余保障历史追溯

申请单中冗余存储了 sealNo(编号)、sealName(名称)、sealType(类型)、keeperName(保管人)等印章信息。即使后续印章台账中的保管人更换了、名称修改了,历史申请单中的数据不受影响——"2026 年 3 月这次盖章时,保管人是张三"这一事实永远可查。


七、数据结构

7.1 表结构:oa_seal(印章台账)

字段类型说明
idbigint主键
company_id / company_namebigint / varchar所属公司
seal_novarchar(100)印章编号
seal_namevarchar(200)印章名称
seal_typebigint印章类型(字典 oa_seal_type
seal_clsbigint印章分类(字典 oa_seal_cls
keeper_idbigint保管人ID
keeper_namevarchar(100)保管人名称
keeper_dept_id / keeper_dept_namebigint / varchar保管部门
purchase_datedate购买日期
enable_datedate启用日期
disable_datedate停用日期
pic_urlvarchar(500)印章照片
sortint排序
statusint状态(0在库 / 1停用 / 2使用中)
remarkvarchar(500)备注

7.2 表结构:oa_seal_apply_bill(用印申请单)

字段类型说明
idbigint主键
bill_codevarchar(50)单据编号(OA110-YYYYMMDD00001
process_instance_idvarchar(64)Flowable 流程实例 ID
process_statusint流程状态(BpmTaskStatusEnum)
seal_idbigint关联印章ID(外键)
seal_no / seal_namevarchar印章编号/名称(冗余)
seal_typeint印章类型(冗余)
keeper_id / keeper_namebigint / varchar保管人(冗余)
keeper_dept_id / keeper_dept_namebigint / varchar保管部门(冗余)
causevarchar(500)用章事由
use_typeint用章类型(1合同/2证明/3公函/4其他)
use_modeint用章方式(1现场/2外借)
document_titlevarchar(200)文件标题
document_typevarchar(100)文件类型
document_countint文件份数
contract_amountdecimal(10,2)合同金额(合同用章时填写)
contract_partyvarchar(200)合同对方(合同用章时填写)
expected_use_timedatetime预计用章时间
actual_use_timedatetime实际用章时间
expected_return_timedatetime预计归还时间(外借时填写)
actual_return_timedatetime实际归还时间(外借时填写)
use_statusint用印状态(0待处理/1已完成/2外借中/3已归还/4已逾期)
is_urgentint是否紧急(0否/1是)
creator_namevarchar(100)申请人姓名
company_id / company_namebigint / varchar所属公司
dept_id / dept_namebigint / varchar所属部门
remarkvarchar(500)备注

7.3 设计要点

  • 冗余存储:申请单冗余了台账的 sealNosealNamesealTypekeeperName 等字段,避免列表查询时 JOIN 台账表。即使台账信息后续修改,已有申请单的历史数据不受影响
  • 合同字段条件存储contractAmountcontractParty 仅在 useType = 1(合同用章)时有值,其他用章类型下为 null,不浪费存储空间
  • 四个时间字段expectedUseTime + expectedReturnTime 用于冲突检测和计划管理,actualUseTime + actualReturnTime 用于实际操作记录,两组时间形成"计划 vs 实际"的对照
  • 双状态字段processStatus 跟踪审批流转,useStatus 跟踪用印业务流转,两者独立但有因果关系(审批通过才触发用印状态初始化)

八、技术亮点总结

设计要点实现方式价值
2 表简洁模型台账+申请单,一次申请对应一枚章数据模型简洁,无需明细行
5 种状态机SealUseStatusEnum 驱动用印流转精确跟踪每张申请的全生命周期
现场/外借分流useMode 一个字段控制两条路径一套代码覆盖两种用章场景
外借冲突检测validateTimeConflict 三模式时间段比对防止同一章同一时段被多人借用
审批中也检测冲突范围含 RUNNING 状态从审批阶段就杜绝冲突
流程变量分支sealUseMode 驱动审批路径外借增加法务审核,灵活可配
条件字段联动合同字段/归还字段按需显示减少认知负担,渐进式表单
分类导航联动字典驱动菜单 + 查询/导出联动可扩展、交互流畅
FlowBillService 标准化统一接口接收 BPM 回调新增单据只需实现接口
我的单据过滤后端强制注入 creator数据安全,用户只看自己的
删除级联清理流程+附件+主表数据一致性,无孤儿记录
冗余保障追溯申请单冗余印章/保管人信息历史数据不受台账修改影响

九、快速体验

操作路径:OA → 用印管理

推荐体验流程

  1. 建台账:进入「印章信息管理」,新增几枚印章(如公章/在库、合同专用章/在库),设置类型和分类
  2. 分类导航:点击左侧「行政类」「财务类」等分类,观察右侧表格自动筛选
  3. 发起申请(现场用印):进入「用印申请管理」,点击「新建」,选择一枚在库印章,用章方式选「现场用印」,填写事由,提交审批
  4. 发起申请(外借用章):再新建一张,用章方式选「外借用章」,填写预计用章时间和预计归还时间
  5. 测试冲突检测:用同一枚印章再发起一张外借申请,时间段与步骤 4 重叠,观察系统报错
  6. 审批:到流程中心的待办任务中找到申请,审批通过
  7. 观察状态:审批通过后,用印状态自动变为「待处理」
  8. 确认用印:现场用印确认后状态变为「已完成」;外借登记后变为「外借中」
  9. 归还:对外借的申请执行归还操作,观察状态变为「已归还」

源码仓库

平台地址
GitCode(后端)gitcode.com/zhouzhongya…
GitCode(前端)gitcode.com/zhouzhongya…
GitHub(后端)github.com/yuqing2026/…

结语

用印管理看似是 OA 系统中一个"小而精"的模块,但它承载的是企业最高等级的法律风险管控。2 张表的简洁模型让数据职责清晰,5 种状态机让每次用印都有据可查,外借冲突检测算法让"一章多借"成为不可能,流程变量驱动的审批分支让现场用印和外借用章走不同的审核路径。

这套设计模式不仅适用于用印管理,还可以推广到其他"资源独占+时间段预约"类的场景(如会议室预约、公车调度、设备外借等)。核心思想是:用时间段重叠检测保护独占资源,用 BPM 审批确保合规性,用状态机驱动业务全生命周期。

如果你正在设计类似的资源管理模块,或者对外借冲突检测算法感兴趣,欢迎参考源码实现。


📚 想要体验 RuoYi Office 的强大功能?

🌐 在线演示ruoyioffice.com/web/(账号 admin / admin123)

📦 GitCode 开源gitcode.com/zhouzhongya…

💬 技术咨询:添加微信 17156169080,备注「RuoYi Office」

如果觉得不错,请给个 Star 支持一下!