收银员交班时,钱箱里的现金和系统销售额对不上、两班之间的销售串到同一个班次、交接完才发现少钱要翻监控——这几分钟是门店最容易出错的环节。读完你能拿到一套"班次状态机 + 钱箱清点 + 差异确认"的设计,附可直接抄走的表结构与并发控制写法。
先拆开看,交接班不是"记一条交班记录"那么简单,实际要同时保证四件事:
归属清楚:每一笔销售、每一笔退款都能归到某个班次,两班之间不重不串; 钱箱清楚:现金实收多少、找零支出多少,交班清点与系统记录一致; 过程可查:谁在什么时候开的班、交的班,操作留痕; 差异可追:清点有差额时,能定位到单、确认处理,而不是静默改数。
如果只做一张"班次表"存开始时间和结束时间,上面四个问题一个都保证不了:销售没有班次归属、并发下两个收银终端可能同时关掉同一个班次、差额没有确认流程。下面按这四个问题逐个给设计。
班次建议用状态机管理:OPEN(开班接单)→ CLOSING(交班盘点中)→ CONFIRMED(已确认)。状态存表,不在代码里写死:
CREATE TABLE shift (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
shift_no VARCHAR(32) NOT NULL, -- 班次号,如 S20260928090001
store_id BIGINT NOT NULL,
cashier_id BIGINT NOT NULL, -- 收银员
device_id BIGINT NOT NULL, -- 收银终端
status VARCHAR(16) NOT NULL DEFAULT 'OPEN', -- OPEN/CLOSING/CONFIRMED
opened_at DATETIME NOT NULL,
closed_at DATETIME,
opened_cash_cents BIGINT NOT NULL DEFAULT 0, -- 开班备用金,单位分
UNIQUE KEY uk_shift_no (shift_no),
UNIQUE KEY uk_device_active (device_id, status) -- 同一终端同一时刻只能有一个 OPEN 班次
);销售订单上挂 shift_id,交班时按班次汇总即可:
SELECT shift_id, COUNT(*) AS order_cnt, SUM(pay_cents) AS total_cents
FROM orders
WHERE shift_id = #{shiftId}
GROUP BY shift_id;串班通常发生在两种场景:同一终端误开第二个班次;两个收银员共用一台终端,交接没锁住。第一种用唯一索引兜底——uk_device_active (device_id, status) 保证同一终端同时只有一个 OPEN 班次;第二种开班前要校验收银员与终端绑定关系,交班动作本身用乐观锁防止重复提交:
UPDATE shift
SET status = 'CLOSING', closed_at = NOW()
WHERE id = #{shiftId}
AND status = 'OPEN'
AND cashier_id = #{cashierId};影响行数为 0 说明班次不是 OPEN 态或操作人不对,前端提示"该班次已被交接或不属于当前收银员",不要静默覆盖。CLOSING 之后新的销售订单不允许挂到这个班次,收银端强制重新开班,从根上避免"交接了还能继续开单"。
还有一个容易漏的边界:交接瞬间顾客正在扫码收款。如果支付回调在交接完成后才到达,金额容易被算进新班次。处理方式是按支付创建时间归属,而不是按回调时间——支付创建时挂在哪个班次,回调成功就记到哪个班次,避免"旧班次少一单、新班次多一单"的对不上。
交班盘点的是"钱箱里的钱",它和系统销售额天然可能不一致:找零时多给少给、现金收款漏入账、备用金变动。因此不要直接改销售数据去凑平,而是走一张清点确认单:
CREATE TABLE shift_cash_check (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
shift_id BIGINT NOT NULL,
check_type VARCHAR(16) NOT NULL, -- CASH/COUPON/OTHER
expected_cents BIGINT NOT NULL, -- 系统期望数
actual_cents BIGINT NOT NULL, -- 实点数
diff_cents BIGINT NOT NULL, -- 差额 = actual - expected
status VARCHAR(16) NOT NULL DEFAULT 'PENDING', -- PENDING/CONFIRMED/EXCEPTION
confirmed_by BIGINT,
created_at DATETIME NOT NULL,
UNIQUE KEY uk_shift_type (shift_id, check_type)
);差额处理流程用状态机,不让店员自己改账:
交班发起盘点 → 录实点金额 → 计算差额 → 差额为 0:直接确认;差额非 0:标记 EXCEPTION → 店长/老板复核(翻单、看流水)→ 填写差异原因(找零、漏收、备用金)→ 确认入账
确认后的差额只在"差异记录表"留痕,不动原订单金额——原单金额是不可变事实,调整一律走差异单,这是账能对上的前提。差异记录与订单流水分开存,是为了让对账口径单一:订单金额永远等于各明细之和,任何调整都能通过差异单逐单追溯到来源,而不是靠"手工平账"把问题掩盖掉。
交接瞬间往往有"顾客点单还没结账"的挂单。如果直接把挂单算进旧班次,下一班结账时金额归属就串了。建议规则:交接时强制提醒处理挂单——要么当场结清,要么挂单随班次转移并记录转移日志:
交接提醒:本班次存在 N 笔挂单未结账,请结清或移交下一班;移交的挂单在订单表记录 from_shift_id 与 to_shift_id,结账时金额计入新班次,交接汇总里注明"挂单移交 N 笔"。
这样旧班次汇总数不会被新班次的结账动作污染,两班对账口径都是干净的。
班次系统建议与订单、收银、对账共用一套幂等与状态机规范:班次号全局唯一、状态只允许单向流转、差异走独立记录表而非原地改数。先按"归属清楚、钱箱清楚、过程可查、差异可追"四件事过一遍自己系统的数据模型,再决定是补 shift_id 还是重构班次状态机。本文方案仅作技术分享,具体实现按自身业务取舍。
Q:收银员交接班时,钱箱和销售怎么保证不错乱?
A:销售订单挂班次号、同一终端只允许一个进行中班次、钱箱清点走独立确认单,差额留痕并走店长复核,不原地改订单金额。
Q:两个收银员共用一台终端,交接怎么防止串班?
A:开班绑定收银员与终端,交班用乐观锁只允许 OPEN 态由本人操作,交班后新销售强制重新开班,从流程上杜绝串班。
Q:交接班发现有差额,怎么定位是哪笔错?
A:先看清点单差额方向,再按班次内流水逐笔核对现金单、找零与退款记录,标记差异原因后确认入账;原订单金额不做修改。
Q:交接时还有没结账的挂单怎么办?
A:强制提醒当场结清或移交下一班,移交时记录来源班次与目标班次,结账金额计入新班次并在汇总中注明。
交接班错账多数不是算错,而是归属没设计清楚。把班次状态机、钱箱确认单和挂单移交规则立住,账自然对得上。仅作技术分享。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。