Payroll 佣金系统配置指南

Commission Rate、Payout Model 的三级级联机制,以及为什么 Payroll Defaults 不像 Tip Settings 那样按门店 Override

目录

1. 为什么 Payroll Defaults 和 Tip Settings 的模式不同? 2. 三级级联机制(Commission Rate) 3. 三种 Payout Model 详解 4. Settings 页面如何配置 5. 员工/职位级别 Override 6. 佣金记录流程(Checkout 时) 7. Payroll Report 计算流程 8. 1099 独立承包商的特殊处理 9. 关键文件一览

1. 为什么 Payroll Defaults 和 Tip Settings 模式不同?

你可能注意到 Settings 页面中,Store Tip SettingsPOS Tip Presets 都有 Default + 每个门店 Override 的表格形式,而 Payroll Defaults 只有两个全局字段。这是有意的设计决策,因为两者的继承模型根本不同:

Tip Settings 按门店区分

  • 小费收集方式跟门店走
  • 不同门店可能用不同的 Tip Collection 方式(before/after payment)
  • POS 小费百分比预设也可能因门店而异
  • 继承链:Store Override → Tenant Default

Payroll Defaults 按人员区分

  • 佣金率跟人走,不跟门店
  • 一个技师在多个门店工作,佣金率应一致
  • 佣金由员工等级/职位决定
  • 继承链:Employee → 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
    

2. 三级级联机制(Commission Rate)

Payroll 系统使用三级级联来确定每个员工的最终佣金率:

1
Employee Override
员工详情页单独设置的佣金率(最高优先级)
2
Job Title Default
该职位的默认佣金率
3
Tenant Default
Settings 页面的全局默认值(最低优先级)
// 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 表查找。

3. 三种 Payout Model 详解

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

4. Settings 页面如何配置

进入 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 或员工级别按需调整个别人的比例。

5. 员工/职位级别 Override

在 Job Title(职位)级别设置

进入 Permissions → Job Titles,每个职位可以设置:

所有该职位的员工,如果没有员工级覆盖,都会继承这个设置。

在 Employee(员工)级别覆盖

进入 Employees → [员工详情],可以单独覆盖:

级联示例

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

6. 佣金记录流程(Checkout 时)

每次客户完成支付(刷卡/现金),系统自动记录佣金:

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
    

commission_records 表结构

字段说明示例
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

7. Payroll Report 计算流程

管理员在 Admin → Payroll 页面生成薪期报告时的完整计算流程:

1
创建薪期
设定 Pay Period 的起止日期
2
汇总数据
聚合 Time Cards + Commission Records + Tips
3
计算 Gross Pay
按 Payout Model 公式计算每人毛薪
4
扣减与调整
Deductions + Adjustments = Net Pay
// 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;

Payroll 管理操作流程

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]
    

8. 1099 独立承包商的特殊处理

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。

9. 关键文件一览

文件职责
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 默认设置的种子数据迁移

关键数据库表