4.2 员工端:排班与接单
场景:美甲师不是桌椅板凳
档期表把一天切成时段格子,但格子是死的,美甲师是活的。开业第一个月,系统按纯时段容量放号:一个时段六把椅子,约满即止。结果有一天两位美甲师同时请假,剩下的人根本做不完六个同时段的单子,客人在店里等得冒火。林姐说:你们这系统算的是椅子,不是人。
问题的本质是产能和人员的解耦与再耦合:档期表管物理容量,排班表管谁在岗,接单表管派给谁。三层各自独立又能串起来,是这一节的设计主线。
林姐的原话更形象:椅子是死的,人是活的,系统要两头都看得见。这一节从三张表讲到企微里的三个页面,看起来都是小工具,撑起的却是门店每天最早开始运转的那件事:今天谁来、谁干什么。
顺带一个观察:整个员工端我们没写任何算法,但做出了全店最受欢迎的系统。有时候工具的价值不在智能,在于把每天重复的烦人事做掉。
方案:三张表一台戏
三张表的分工是这样的:员工表存人,排班表存人岗匹配,接单流水存结果。
| 表 | 管什么 | 关键字段 |
|---|---|---|
| t_staff | 员工档案 | shop_id、姓名、职级 |
| t_staff_daily | 每日排班 | staff_id、日期、班次 |
| t_assign | 接单流水 | staff_id、book_id、状态 |
排班表是核心。每天一条记录,班次直接用门店的时段模板对齐:早班 9 到 16 点(540 到 960 分钟),晚班 13 到 21 点。排班和时段容量之间是软约束关系:系统不阻止超卖,但会在排班少于容量时提醒店长。
CREATE TABLE t_staff_daily (
daily_id BIGINT PRIMARY KEY AUTO_INCREMENT,
staff_id INT NOT NULL,
duty_date DATE NOT NULL,
shop_duty INT NOT NULL COMMENT '当天在哪个店上班',
start_min INT NOT NULL,
end_min INT NOT NULL,
state TINYINT NOT NULL COMMENT '0 正常 1 请假 2 调班',
UNIQUE KEY uk_staff_date (staff_id, duty_date)
);
唯一键防止同一员工同一天被排两条班,state 字段让请假换班有记录可查。这张表是接单校验和业绩统计的共同数据源,全店每天的生活都压缩在这一行行里。
接单的派发逻辑我们做得克制:预约核销时由前台或店长手工指派给在岗的美甲师,不做自动派单。原因是美甲项目因技师手艺不同,客人也常有指定的技师,自动派单听起来酷但实际添乱。系统只做一件事:指派后在员工端推送一条提醒。
func assignStaff(bookID, staffID int) error {
if !onDuty(staffID, bookingDate, bookingStartMin) {
return errors.New("该员工当天不在班")
}
saveAssign(bookID, staffID)
pushWorkNotice(staffID, "新接单:" + bookingDetail)
return nil
}
onDuty 检查是接单的第一道闸门,所有派单请求都先过它。曾经有店长给休假员工派单成功,客人到店找不到人,从那以后这个校验从应用层挪到了数据库约束层。
派单成功的另一个动作是推送。推送内容带预约详情和核销码截图按钮,美甲师点开就能看到客人要做什么、有没有备注。我们故意把推送做得信息完整:门店每天最怕的就是美甲师到岗了才知道今天要做什么,提前看一眼能早做准备。
func pushWorkNotice(staffID int, text string) {
msg := map[string]interface{}{
"touser": staffWxID(staffID),
"msgtype": "text",
"agentid": qyAgentID,
"text": map[string]string{"content": text},
}
postJSON(qySendURL+"?access_token="+qyToken(), msg)
}
企业微信的发送接口和公众号同源,token 管理复用同一套缓存,没花多少额外功夫。
这里还有一个取舍值得记录:推送发企微还是发短信?企微免费但只在上班时间被看到,短信必达但要钱。最终规则是工作消息一律企微,只有客人侧的关键消息才动用短信。通道的克制是另一种省钱。
配置:员工端形态
员工端没有做独立 App。美甲师平均年龄三十五岁,教她们装新 App 的成本高于教她们用企业微信。最终形态是企业微信自建应用:排班、接单提醒、个人业绩三个页面,H5 嵌进企微工作台。打开率比我们预期高得多——美甲师每天进店第一件事就是打卡顺便看排班。
| 功能 | 形态 | 更新频率 |
|---|---|---|
| 我的排班 | H5 页面 | 实时 |
| 接单提醒 | 企微消息 | 即时 |
| 个人业绩 | H5 图表 | 每日刷新 |
| 请假申请 | 企微审批 | 按需 |
企微审批流串起了请假和排班:美甲师提交请假,店长审批通过后打卡记录同步写进排班表,当天该员工的接单提醒自动摘除,时段容量提示同步给店长。一条审批三个系统联动,员工只用点一下。
早会上林姐指着企微说,以前请假要在群里发红包求人换班,现在系统一条申请全搞定。员工满意度的提升有时候不来自技术多先进,而来自一个流程让人有尊严。
踩坑:排班现场的四个坑
第一个坑是排班班次和时段对不齐。早班写 9 点到 16 点,但时段模板从 10 点开始排,结果早班员工有一小时闲着却显示在岗。后来规定排班粒度必须与时段模板同格(30 分钟),班次边界必须是格子边界。
第二个坑是调班没走系统。两个美甲师私下换班,系统里排班还是旧的,接单校验把新班次的人全挡在外面。修复方案是允许员工在企微里发起换班申请,对方确认后写排班表,私下换班成了不可能完成的任务。
换班上线第一个月发生过一次温馨的意外:一位美甲师想换班去接孩子放学,系统里发起申请后秒批。她后来跟林姐说,这是她在这家店六年第一次觉得请假不是件难事。系统的温度有时候就藏在一个按钮里。
同样务实的决定还有排班的批量操作。每周日晚店长要排下周的班,逐条加记录能把人点到手酸。我们做了模板班次:选定员工和周期,一键复制上周排班,再针对调班单独改。这一个功能给店长每周省下四十分钟,她原话是终于有时间好好喝杯茶了。
第三个坑是业绩统计口径。财务按核销算提成,美甲师看自己页面按接单算,两个数字对不上,每月发工资必吵一次。统一口径为以核销为准后,接单表里增加核销回写,两个页面一个真相:
SELECT staff_id, COUNT(*) AS done_cnt, SUM(price) AS revenue
FROM t_assign a JOIN t_booking b ON a.book_id = b.book_id
WHERE a.state = 1 AND b.status = 2
AND b.verify_at BETWEEN ? AND ?
GROUP BY staff_id;
一条 SQL 就是工资单的底稿,财务和美甲师看到的是同一个数字。截止到上线当月,工资纠纷从平均每月三起到半年零起。
第四个坑是跨店支援的排班。A 店缺人时 B 店员工过去支援,排班表没有跨店记录,该员工在 A 店接单被拒。后来排班表加了 shop_duty 字段,一天可以在不同店各排一段,权限按排班店走而不是归属店。
跨店支援这块还有个尾巴:支援员工的业绩归支援店还是归属店,财务纠结了一周。最后的决定是业绩归支援店(客人服务和钱都在那边发生),归属店只看考勤。规则简单到不用解释,是它能活下来的原因。
小结
员工端的设计哲学是克制:不自动派单、不上复杂算法、不造新 App。把排班做准、把校验做硬、把入口做进员工已经在用的工具里。最高赞的功能不是任何炫技,而是我的排班页面让美甲师第一次能提前规划自己的生活——上两个月她们排班靠的是店长的心情。
一个数字结尾:系统上线半年,员工主动换班申请 240 次,零纠纷。技术最好的价值是让人不用吵架。
再补一组数字:排班与接单联动之后,门店早班的平均准备时间从 22 分钟压到 8 分钟——美甲师到岗前已经知道今天的第一单做什么、客人是谁。这 14 分钟全是客人少等的 14 分钟,也是林姐愿意续费服务器的 14 分钟。
延伸阅读
- 排班的地基是档期,翻 档期设计:时段表与容量
- 核销回写怎么做,见 到店核销:扫码与状态回写
- 员工业绩进入看板,翻 数据看板:预约转化漏斗
- 接单通知的通道,见 触达策略:多通道兜底
- 员工体系之上的客人资产,翻 会员体系:积分与复购
- 企微与后端的串联,见 技术栈对齐:后端框架与短信地图
