# 通用预约系统表模板使用教程

很多时候，为了更好地分配资源，我们需要有这样一个系统应用：

+ 能够让大家提前提交预约，并且具体到实名
+ 能够让大家**方便地查看/查询当前预约状况**，以便自行安排预约
+ 能够对预约**做出一些限制**
+ 能够根据不同单位、不同会议室/设备**分类计费**

这样的需求，**单纯的问卷形式传统表单收集是做不到的**。而数据收集表虽然可以创建，**整体的填写体验却不佳**，也做不到判断填写是否有效。

如何将多种数据**查看**手段、数据**收集**手段，有机地结合起来，并赋以**自动化**？协同表格的通用应用、脚本、自动化规则综合使用，便是一种极好的方式。

> 本模板现在已经率先应用在唐仲英楼的会议室预约系统中。最古早的传统表单/数据收集表用法见2021年的推送：[《协同表格 | 校内免费问卷制作平台你还不知道？》](https://mp.weixin.qq.com/s/q6xBlXzW5qhftfI7b0cwWw)，这种用法作为早期智能表格的补充模块，比较简陋。现在面向复杂需求，更推荐使用的是通用应用：[《协同表格 | 让每个人只能看见自己的记录，通用外部应用一招秒杀！》](https://mp.weixin.qq.com/s/wEiBRBMQqvY3-ymAqAhR-g)。

太长不看？这次，我们做了个简单的演示视频，建议在电脑上播放噢！

<video src="https://box.nju.edu.cn/f/0e1f2df92d7a47be8911/?dl=1"  controls="controls" width=800 ></video>

## 使用模板

### 从模板创建

登录[协同表格](https://table.nju.edu.cn)，从“通用”-“通用预约系统表”创建一张新表。

[![从模板创建](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/scaled-1680-/image-1701350070063.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/image-1701350070063.png)

[![通用-通用预约系统表](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/scaled-1680-/image-1701350125987.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/image-1701350125987.png)

打开创建的“通用预约系统表”，下面准备开始修改内容。

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/scaled-1680-/image-1701350328378.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/image-1701350328378.png)

### 修改表格信息

#### 预约记录表-单选列标签

在创建的表格中，**“预约记录表”子表**内可以看到如下的这些列：

+ 申请序号（文本列，自动生成）
+ 申请人（创建者列，发放应用后填写人提交后自动生成）
+ **院系（单选列，管理员需根据情况设置）**
+ 其他院系部处（文本列，发放应用后如果填写人院系选择“其他”，则会提交）
+ 负责人姓名（文本列，发放应用后填写人提交）
+ 负责人工号（文本列，发放应用后填写人提交）
+ 手机（文本列，发放应用后填写人提交）
+ 邮箱（邮箱列，发放应用后填写人提交）
+ **会议室房间（单选列，管理员需根据情况设置）**
+ 会议室使用开始时间（日期列，发放应用后填写人提交）
+ 会议室使用时长（时长列，发放应用后填写人提交）
+ 会议室试用结束时间（公式列，类型为日期，由前两项自动计算）
+ 会议室使用用途（文本列，发放应用后填写人提交）
+ 备注（文本列，发放应用后填写人提交）
+ 费用（数字列，脚本自动计算）
+ 创建时间（创建时间列，发放应用后填写人提交后自动生成）
+ 状态（单选列，脚本自动计算）
+ 无效原因（文本列，脚本自动计算）
+ 申请人姓名学工号（文本列，发放应用后填写人提交后自动生成）
+ 申请人姓名（公式列，类型为文本，由前一项自动提取，提取技巧见：[协同表格 | 今日小技巧：从表单问卷的信息中提取填写者姓名学号](https://mp.weixin.qq.com/s/jex687myJJo-bg5e4UPxGw)）
+ 申请人学工号（文本列，发放应用后填写人提交后自动生成）

在上述诸多信息中，你 **只需要更改标出的两列**，点击“编辑单选”：

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/scaled-1680-/image-1701351015580.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/image-1701351015580.png)

其中，您可以根据您的实际需求，将院系更改为部门、年级、课题组等等其他人员分类；会议室房间**也可以是仪器设备等其他公共资源**。

> 如果您需要更改列名，请注意将下方脚本内的相关索引（代码中“院系”和“会议室房间”的关键词）一并更改！
> 
> [![](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/scaled-1680-/image-1701351421557.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/image-1701351421557.png)

#### 预约计价表-多选列标签

在前边我们修改过的“院系”和“会议室房间”（当然，根据你的需求，这里可能已经不叫这个名字了，但下面我们仍然沿用这两个名字），在 **“预约计价表”子表** 内也把相应的列名修改一致，并且标签需要一致：

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/scaled-1680-/image-1701352066149.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/image-1701352066149.png)

唯一不同的是，这里的“院系”和“会议室房间”**必须是多选列**。

> 这里有个小技巧：你可以通过“导入/导出标签”从前面的“预约记录表”子表精准地把标签导入过来。
>
> [![](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/scaled-1680-/image-1701352124484.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/image-1701352124484.png)

“预约计价表”子表提供了两种计费模式：**次单价和时单价**，并且逻辑上取**最大值**：

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/scaled-1680-/image-1701352392851.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/image-1701352392851.png)

该子表的每一行都是计价规则，其逻辑为：同一行内，对应的“院系”预约对应的“会议室房间”，按照后边的计算方式计算。

> 此逻辑更改需要动脚本代码。如果不需要某种计费方式，一个简单的方法是：直接将该值写为`0`。例如，不需要按次计费，那就把次单价改为`0`，这样就始终采用时单价的结果了。

需要注意，**这张子表应该覆盖所有的情况，并且规则互相不得有冲突**。

#### 预约规则配置表-变量值

**“预约规则配置表”子表**配置了用于脚本的变量。

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/scaled-1680-/image-1701352791702.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/image-1701352791702.png)

首列不要动，你只需要根据自己需求修改“值”即可。

需要特别注意的是，`OUTDUE`是宽限期，该参数设定在后期录入开始时间早于当前一定时间段的记录，也会被视作有效预约。

### 邮件通知（可选）

为了能较为即时反馈预约结果，本模板设计了邮件通知系统，但需要按照下文方式配置。首先打开**第三方集成**面板：

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/scaled-1680-/image-1701353395771.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/image-1701353395771.png)

在 **“邮件”-“增加邮件账号”** 内，添加一个用于发信的邮箱：

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/scaled-1680-/image-1701353472231.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/image-1701353472231.png)

添加邮箱需要下列信息：

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/scaled-1680-/image-1701353599454.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/image-1701353599454.png)

这些信息你可以通过搜索**对应邮箱的第三方客户端配置**教程来填入。例如，以南京大学邮箱为例，你可以参考[《南大电子邮件使用办法》](https://itsc.nju.edu.cn/1b/ce/c21586a334798/page.htm)中关于“学生/教师邮箱客户端设置办法”的内容，寻找“SMTP”和“IMAP”端口号与客户端专用密码设置。

在上述账号添加完毕后，你可以**点击“发送”**来测试是否正确配置：

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/scaled-1680-/image-1701354033587.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/image-1701354033587.png)

接着，打开 **“规则”-“自动化规则”** 模块：

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/scaled-1680-/image-1701354108969.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/image-1701354108969.png)

其中已经有预置的三个核心自动化规则，你**只需要检查第一个“预约状态更新通知”自动化规则**：

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/scaled-1680-/image-1701354249657.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/image-1701354249657.png)

绝大多数设置都是无需修改的，只需要**将“第三方账户”从下拉框选中你刚刚添加的账户即可**：

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/scaled-1680-/image-1701354226716.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/image-1701354226716.png)

如果不设置这一部分，邮件将不会发送，但前面的功能不受影响。

### 脚本

本预约系统的核心脚本有两个，除非有前文中提到的需要修改列名的情况，一般情况下无需修改。

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/scaled-1680-/image-1701353301313.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/image-1701353301313.png)

> 可以注意到，这里的脚本有两个。其中，除了**新增记录规则判断**，我们还需要**定期冲突重复检查**。这是因为，记录提交后，后台会运行一个Python脚本，但由于这个过程是并发的，如果极小概率出现：A和B是两条有预约时间冲突/重叠的记录，A记录比B记录先触发脚本，但因为某些不可控的原因，A的脚本尚未执行完时B已经被执行了，B脚本是完全不知道A记录的存在的，因此可能会同时被判断为**预约成功**。为了解决这个问题，一般做法是通过队列或其他方式有一个顺序的控制，这在协同表格中可以单独以一张子表作为队列来解决——但是显然这非常麻烦。因此，每天定时做一次检查，将冲突情况搁置并让申请人通知管理员处理，是非常必要的。
> <div drawio-diagram="433"><img src="https://doc.nju.edu.cn/uploads/images/drawio/2023-12/drawing-4-1701484145.png"></div>

脚本会获取之前的记录，查询检测是否合法，然后计算费用，写入**预约记录表**的**状态**和**无效原因**列。

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/scaled-1680-/image-1701482850648.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/image-1701482850648.png)

其中，**状态**有4个：

+ 预约成功：预约完全符合条件
+ 预约失败：出现冲突或者其他违反预约规则的时间段
+ 等待审核：提交后的默认状态
+ 正在调解：出现极小概率冲突时，定期检查会将**预约成功**切换至此，该检查默认每次预约提交后第二天0点进行，因此如果第二天没有变化，那么一定是**预约成功**了

预约失败的原因一般包括：

+ [合法性检查错误] 超出最大预约时长 MAX_DURATION，请仔细阅读说明。
+ [合法性检查错误] 未达到最短预约时长 MIN_DURATION，请仔细阅读说明。
+ [合法性检查错误] 不能预约已经超过回溯期 OUTDUE 的时间。
+ [合法性检查错误] 超出允许的最大提前预约时间 MAX_PERIOD，请仔细阅读说明。
+ [合法性检查错误] 该时段已有预约，请预约前通过查询功能检查该时段是否可约。

如果自动化运行未生效（没有自动判断预约成功），可以通过脚本日志查看是不是哪里设置有问题（尤其是前文提到的预约计价表情况是否定义完全）：

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/scaled-1680-/image-1701482738956.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/image-1701482738956.png)

脚本日志的常见报错包括：

+ [规则错误] 冲突的规则：预约计价表里面有多次定义的费用规则
+ [规则错误] 缺少规则：预约计较表里面缺少费用定义

## 编辑应用

通用应用是一个基于智能表格可让用户自行搭建简易APP的功能。通过简单的页面类型选择、组织，用户就可以为原先“简陋”的表格“套”上一层友好的交互页面。应用内的人员管理也是与表格原先的“协作人”独立，每一个用户登录应用时，都会被注册为一个应用的角色。

因此，通用应用功能相比之前依靠共享表格、视图以及传统表单、数据收集表实现的各种数据收集协作场景，都更加安全且功能丰富，而且**登入/注册应用的用户不会受到人数限制**。在面向大批量、不确定身份的人员很多行政目的的表格协作中，直接共享表格不仅权限不安全也有可能受到人数限制，使用通用应用是个推荐选择。

在“应用”模块，已经有一个样例应用。我们将通过下面这个应用来展示通用应用的优势。

点击右侧的小笔<img src=https://doc.nju.edu.cn/uploads/images/gallery/2023-11/scaled-1680-/image-1701354452848.png class="pic_inline">编辑此应用：

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/scaled-1680-/image-1701354395940.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/image-1701354395940.png)

从左侧导航栏可以看见，这个应用设计了这些页面：

+ 说明
+ 所有预约日历
+ 所有预约查询
+ 预约申请
+ 申请人
  + 预约取消（申请人）
  + 有效预约（申请人）
  + 无效预约（申请人）
+ 负责人
  + 预约取消（负责人）
  + 有效预约（负责人）
  + 无效预约（负责人）
+ 预约管理

可以看到，我们预期本应用的设计需要满足预约填写，但又**远不止是填写**。我们希望：

+ 有一个**对填写者友好的导航**，能够补充一些必要信息：需要注意到，手机上打开应用时，左侧导航栏一般会默认隐藏，设计一个**说明**页是必要的。
+ **填写人有充分的手段查看当前预约情况，而且方式灵活**：按照日历看（时间维度）时间更方便，增设**预约日历**；或者有目的地检索某个房间（地点维度）预约情况，增设**所有预约查询**。
+ 有一个**统一的填写入口**：制作**预约申请**表单页。
+ **申请人与负责人均有权限取消预约和查看预约情况**：在前面的表格中，我们收集了两个人员信息，一个是填写者，也就是申请人的学号/工号，另一个是负责人的工号；通过[预过滤技巧](https://mp.weixin.qq.com/s/wEiBRBMQqvY3-ymAqAhR-g)，我们很容易实现页面**分别按照申请人和负责人**过滤。
+ **为应用设置管理员，以多人合作**：设计**预约管理**页，仅限特定人员可见。


### 说明页

进入编辑界面，说明页可以根据你的具体情况修改。

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/scaled-1680-/image-1701354496163.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-11/image-1701354496163.png)

### 所有预约日历/所有预约查询

这两个页面直观展示当前预约情况：

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/scaled-1680-/image-1701415067307.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/image-1701415067307.png)

其中，检索功能更具体一些：

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/scaled-1680-/image-1701415078915.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/image-1701415078915.png)

这两个页面无需修改。

### 预约申请

这一页是用于预约填写的表单：

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/scaled-1680-/image-1701415280811.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/image-1701415280811.png)

这里也应用了一个技巧，设置**申请人姓名学号**和**申请人学工号**两个的默认值分别为`{creator.name}`和`{creator.id}`，并**设置默认值之后不能更改**，就可以直接获取当前登录者的信息，**填写人不需要手动填自己的姓名学号**。该技巧见：[协同表格 | 今日小技巧：从表单问卷的信息中提取填写者姓名学号](https://mp.weixin.qq.com/s/jex687myJJo-bg5e4UPxGw)

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/scaled-1680-/image-1701415468476.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/image-1701415468476.png)

预约申请页面一般也无需修改。

### 申请人/负责人

申请人/负责人文件夹内的页面结构是一样的

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/scaled-1680-/image-1701415181545.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/image-1701415181545.png)

其中，对应的人员在**预约取消**一栏可以删除已经存在的记录，但是不能修改或新增。如要重新预约，必须要从上面的**预约申请**提交新的预约。

### 预约管理

预约管理是可以设置为我们指定的人来访问，其他人无权限访问这一页面。这样，我们就可以在应用内就完成数据协作，无需共享表格。

鼠标移动到“设置”小齿轮，

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/scaled-1680-/image-1701416247243.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/image-1701416247243.png)

就可以打开这一页的页面设置：

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/scaled-1680-/image-1701416306891.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/image-1701416306891.png)

可以发现，各种权限能够设置的选项有四个：管理员（这张智能表格的管理员）、没有人、任何人、**特定角色**。

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/scaled-1680-/image-1701416344886.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/image-1701416344886.png)

这里的**特定角色**与智能表格中的**指定用户**是完全不同的两个概念。**特定角色**仅在本应用内存在。在编辑本应用时，左侧可以看见**用户和角色管理**一项：

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/scaled-1680-/image-1701416476672.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/image-1701416476672.png)

在**用户**一栏中的用户，便是本应用目前为止注册的用户：

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/scaled-1680-/image-1701416533459.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/image-1701416533459.png)

> 这与原始的表格是完全独立的——除非你点击右上角**将用户同步到表格**。

在**角色**一栏我们点右上角**增加角色**，例如图中我们创建了一个**辅助管理**角色：

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/scaled-1680-/image-1701416625536.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/image-1701416625536.png)

现在回到之前的页面设置，刷新后你将在这里看见**辅助管理**这个角色，选中它，这一页就只能被**辅助管理**查看了

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/scaled-1680-/image-1701416833991.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/image-1701416833991.png)

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/scaled-1680-/image-1701416904351.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/image-1701416904351.png)

## 发放应用

至此，我们已经编辑完毕主要的内容。下面的问题是，我们要如何发放这个应用？

首先，在编辑应用的左侧我们可以看见齿轮，这是整个应用的设置。这里我们可以设置一个好记的自定义URL。

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/scaled-1680-/image-1701416991015.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/image-1701416991015.png)

但是直接复制这个应用发放，你会发现收到的人点开没有权限！你需要打开**用户和角色管理**-**开放式访问**-**任何登录用户访问应用程序都将自动注册为应用程序用户**，并设置自动注册的角色：

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/scaled-1680-/image-1701417316837.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/image-1701417316837.png)

这样，用户拿到上方的 App URL 或者前文的自定义 URL，点击登录后都会自动创建为设定的角色并注册为此应用的用户。

实际上，本来我们需要在**用户和角色管理**内的**邀请链接**，为**默认角色**（或者你自创的某个角色）创建一个邀请链接，访问这个链接的人登录后才会自动按照这个角色注册为应用用户，这也是不进行上文设置会提示没有权限的原因。

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/scaled-1680-/image-1701417123286.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/image-1701417123286.png)

但是，**开放式访问**的功能简化了这一流程。前面的“辅助管理”自定义角色可以单独创建邀请链接发放给协作者：

[![](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/scaled-1680-/image-1701417610334.png)](https://doc.nju.edu.cn/uploads/images/gallery/2023-12/image-1701417610334.png)

------

在完成上述步骤后，你应该得到了：
+ 一个公开的APP链接地址，预约申请人和负责人访问后进行查询、预约或取消预约的操作
+ 一个发给其他同事的辅助管理链接

分发公开的APP链接地址给需要预约的用户，然后将辅助管理的链接分发给一起管理（主要是解决小概率冲突才需要人工介入，其余情况基本为自动化计算）的同事。

需要注意的是，上文所有的查询功能**必须基于用户登录**才能实现，因此**千万不能开启开放式访问-允许匿名用户访问**。