Commission Rate、Payout Model 的三级级联机制,以及为什么 Payroll Defaults 不像 Tip Settings 那样按门店 Override
你可能注意到 Settings 页面中,Store Tip Settings 和 POS Tip Presets 都有 Default + 每个门店 Override 的表格形式,而 Payroll Defaults 只有两个全局字段。这是有意的设计决策,因为两者的继承模型根本不同:
Store Override → Tenant DefaultEmployee → Job Title → Tenant Default核心区别:Tip Settings 的覆盖维度是门店(Store),Payroll 的覆盖维度是人员(Employee / Job Title)。Payroll 里加"按门店 Override"在业务上没有意义——一个美甲师不应该因为在 QQ Nails Queens 干活就拿 40% 佣金,换到 Test Center 又变成 30%。
graph TD
subgraph "Tip Settings 继承模型"
TD[Tenant Default] --> SQ[QQ Nails Queens]
TD --> ST[Test Center]
SQ -->|"各店可独立覆盖"| SQV["Tip before payment"]
ST -->|"各店可独立覆盖"| STV["Tip after payment"]
end
subgraph "Payroll 继承模型"
PD[Tenant Default: 0%] --> JT1["Job Title: Nail Tech"]
PD --> JT2["Job Title: Senior Tech"]
JT1 -->|"40%"| E1["员工 Alice"]
JT1 -->|"40%"| E2["员工 Bob"]
JT2 -->|"50%"| E3["员工 Carol"]
E3 -->|"员工级覆盖 55%"| E3O["Carol 实际: 55%"]
end
Payroll 系统使用三级级联来确定每个员工的最终佣金率:
// commissionRecorder.js - 级联解析逻辑
const effectiveRate =
employee.commission_rate != null // 1️⃣ 员工级 Override
? Number(employee.commission_rate)
: employee.jt_commission_rate != null // 2️⃣ 职位默认
? Number(employee.jt_commission_rate)
: tenantDefaultRate; // 3️⃣ 全局默认
const effectivePayoutModel =
employee.payout_model // 1️⃣ 员工级
|| employee.jt_payout_model // 2️⃣ 职位
|| tenantDefaultPayoutModel // 3️⃣ 全局
|| null;
注意:如果三级全部为 null 或 0,则不会记录佣金(commission_rate = 0 意味着该员工无佣金模式)。系统会 fallback 到旧的 commission_rules 表查找。
| Payout Model | 公式 | 适用场景 | 示例 |
|---|---|---|---|
| Hourly Only | basePay + overtimePay + tips |
纯时薪员工,不参与佣金分成 | 前台接待、清洁人员 |
| Commission + Base | basePay + commission + overtimePay + tips |
底薪 + 佣金的标准模式 | 大多数美甲师($15/hr + 40% commission) |
| Greater Of | MAX(basePay, commission) + overtimePay + tips |
保底薪资,佣金高时拿佣金 | 高级美甲师(保底 $2000/周 vs 50% commission) |
假设一个美甲师的薪期数据:
| Model | 计算过程 | Gross Pay |
|---|---|---|
| Hourly Only | $600 + $112.50 + $200 | $912.50 |
| Commission + Base | $600 + $800 + $112.50 + $200 | $1,712.50 |
| Greater Of | MAX($600, $800) + $112.50 + $200 = $800 + $112.50 + $200 | $1,112.50 |
进入 Settings → General 页面,滚动到底部找到 Payroll Defaults 卡片:
| 字段 | 说明 | 存储位置 |
|---|---|---|
| Default Commission Rate | 全局默认佣金百分比(0-100%)。新员工如果没有在 Job Title 或员工级别设置佣金率,就使用这个值 | setting_values 表,key = payroll.default_commission_rate |
| Default Payout Model | 全局默认的薪资计算模式(三选一) | setting_values 表,key = payroll.default_payout_model |
推荐配置:大多数美甲沙龙应该设置 Default Commission Rate = 40(40% 是行业常见比例),Default Payout Model = Commission + Base(底薪 + 佣金最常见)。然后在 Job Title 或员工级别按需调整个别人的比例。
进入 Permissions → Job Titles,每个职位可以设置:
commission_rate — 该职位的默认佣金率payout_model — 该职位的默认薪资模式所有该职位的员工,如果没有员工级覆盖,都会继承这个设置。
进入 Employees → [员工详情],可以单独覆盖:
commission_rate — 这个员工的专属佣金率payout_model — 这个员工的专属薪资模式Tenant Default: commission_rate = 40%, payout_model = commission_plus_base Job Title "Nail Tech": commission_rate = 40% (与默认相同,可不设) payout_model = null (继承 Tenant Default) Job Title "Senior Tech": commission_rate = 50% (职位级覆盖) payout_model = greater_of (职位级覆盖) Employee "Alice" (Nail Tech): commission_rate = null → 继承 Job Title → 40% payout_model = null → 继承 Job Title → null → Tenant → commission_plus_base Employee "Carol" (Senior Tech): commission_rate = 55% → 员工级覆盖,忽略 Job Title 的 50% payout_model = null → 继承 Job Title → greater_of
每次客户完成支付(刷卡/现金),系统自动记录佣金:
sequenceDiagram
participant C as Customer Payment
participant CR as commissionRecorder
participant DB as Database
C->>CR: Payment completed
CR->>DB: Fetch tenant defaults (setting_values)
loop Each sub-appointment
CR->>DB: Fetch employee + job_title commission fields
CR->>CR: Resolve effective rate (3-level cascade)
alt effective rate > 0
CR->>DB: INSERT commission_record
Note over DB: base_amount × rate = amount
else rate = 0 or null
CR->>DB: Check legacy commission_rules
end
end
| 字段 | 说明 | 示例 |
|---|---|---|
| employee_id | 执行服务的员工 | emp_001 |
| base_amount | 服务价格(计佣基数) | $60.00 |
| rate | 佣金率(小数) | 0.4 (= 40%) |
| amount | 佣金金额 = base_amount × rate | $24.00 |
| commission_type | 'service' 或 'tip' | service |
| service_date | 服务日期 | 2026-03-02 |
| sub_appointment_id | 关联的子预约 | sub_apt_123 |
管理员在 Admin → Payroll 页面生成薪期报告时的完整计算流程:
// payrollService.js - Gross Pay 计算(简化版)
switch (payoutModel) {
case 'hourly_only':
grossPay = basePay + overtimePay + tips;
break;
case 'commission_plus_base':
grossPay = basePay + commission + overtimePay + tips;
break;
case 'greater_of':
grossPay = Math.max(basePay, commission) + overtimePay + tips;
break;
}
// 1099 独立承包商特殊处理
if (workerClassification === '1099') {
grossPay = commission + tips; // 无 basePay, 无 overtimePay
}
// 扣减
netPay = grossPay - totalDeductions + totalAdjustments;
graph LR
A[Create Pay Period] --> B[Generate Report]
B --> C[Review Details]
C --> D{Need Adjustments?}
D -->|Yes| E[Add Adjustments]
E --> C
D -->|No| F[Finalize]
F --> G[Mark as Paid]
G --> H[Export CSV]
H --> I[Upload to Gusto/ADP]
1099 Workers 是美国税法中的独立承包商(Independent Contractor),与 W-2 雇员有本质区别。美甲行业中,部分高级技师以 1099 形式合作。
| 方面 | W-2 员工 | 1099 承包商 |
|---|---|---|
| Base Pay(底薪) | 有 | 无 |
| Overtime Pay(加班费) | 有 | 无 |
| Commission(佣金) | 有 | 有 |
| Tips(小费) | 有 | 有 |
| Payout Model 选择 | 三种都可 | 锁定为 Commission + Base (实际公式 = commission + tips) |
1099 承包商的薪资公式实际上被简化为:grossPay = commission + tips,因为 basePay 和 overtimePay 在代码中被强制设为 0。
| 文件 | 职责 |
|---|---|
backend/services/payroll/commissionRecorder.js |
Checkout 时记录佣金(三级级联解析) |
backend/services/payroll/commissionEngine.js |
单笔服务的佣金计算(含旧规则表兼容) |
backend/services/payrollService.js |
Payroll 报告生成(hours + tips + commission → gross → net) |
backend/api/payroll.js |
Payroll API 路由 |
frontend/.../admin/payroll/page.tsx |
Admin Payroll 管理页面 |
frontend/.../admin/settings/page.tsx |
Settings 页面(含 Payroll Defaults 卡片) |
frontend/web_app/src/hooks/usePayrollApi.ts |
Payroll React Query hooks |
backend/database/migrations/1808000000004_* |
Payroll 默认设置的种子数据迁移 |
关键数据库表:
setting_values (public schema) — 存储 Tenant Default 佣金率和 Payout Modelemployees (tenant schema) — 员工级佣金覆盖 (commission_rate, payout_model)job_titles (tenant schema) — 职位级佣金默认 (commission_rate, payout_model)commission_records (tenant schema) — 每笔服务的佣金记录payroll_reports (tenant schema) — 薪期报告