钉钉薪酬核算+绩效奖金+计件工资一体化实战:从“复杂薪酬结构算到崩溃“到“多类型员工一键结算“的技术实现

引言:当“工资条”成为信任危机的导火索

“这个月的工资怎么少了500块?”
“我上个月加班了3天,为什么没算进去?”
“我的年假明明还有3天,系统怎么显示清零了?”

对于许多企业的HR和财务人员来说,每月发薪日前后类似的疑问层出不穷。“工资条说不清”背后,往往是薪酬计算规则不透明、考勤数据与薪酬系统割裂、假勤审批流程线下跑断腿等多系统数据孤岛导致的必然结果。这不仅消耗大量管理成本进行人工核对解释,更在无形中侵蚀着员工对公司的信任。

本文将分享一套基于钉钉开放平台,整合薪酬管理、智能排班、假勤审批的一体化落地实战方案。我们以钉钉服务商**典铭云赛(ding.cn)**的实践为例,详解如何打通数据流与业务流,构建从“数据采集→规则计算→结果追溯”的薪酬全链路闭环,真正实现薪酬数据的“全程可追溯”。
在这里插入图片描述

一、核心痛点与一体化价值主张

在深入技术实现前,我们首先需要明确传统模式下薪酬管理的典型痛点:

  1. 数据孤岛,人工搬运:考勤机数据、OA审批流、Excel薪酬表分属不同系统,HR需要手动导出、整理、核对,错误率高且效率低下。
  2. 规则黑盒,解释成本高:复杂的薪酬计算规则(如阶梯加班费、各类补贴扣款)封装在财务人员的Excel公式或本地软件中,出现差异时难以向员工直观展示计算过程。
  3. 流程断点,体验割裂:员工请假在OA审批,加班需另外提报,最终是否计入薪酬依赖HR人工判断,流程不连贯,员工无法感知状态。
  4. 追溯困难,合规风险:一旦发生劳动纠纷,需要翻查多个系统的历史记录、审批截图、打卡日志进行举证,过程繁琐且证据链可能不完整。

一体化解决方案的核心价值在于:以“事件”为中心,串联人、事、时、数、薪

  • :员工身份统一(钉钉组织架构)。
  • :请假、加班、出差、调休等业务事件。
  • :打卡、排班等时间记录。
  • :所有事件与时间记录形成的结构化数据。
  • :基于预设规则,由“人、事、时、数”自动计算得出的薪酬结果。

最终,任何一个薪酬数字都能反向追溯到具体的事件、时间点、审批记录和计算规则。

二、系统架构与数据流设计

要实现一体化,首先需要设计一个清晰、解耦且可扩展的系统架构。以下是基于钉钉生态的推荐架构:

“应用与展示层”

“数据存储与追溯层”

“核心计算与规则引擎”

“数据采集与事件层”

“标准化事件数据”

“写入计算日志”

“写入最终结果”

“通过钉钉开放平台API同步”

“钉钉考勤打卡”

“智能排班模块”

“钉钉审批流”

“自定义业务表单”

“统一事件中心”

“薪酬规则配置平台”

“规则引擎”

“计算服务”

“原始数据仓库
打卡/审批/排班”

“计算过程快照”

“薪酬结果表”

“支持钻取”

“管理端:仪表盘/报表”

关键设计说明:

  1. 统一事件中心:将打卡、审批、排班变动等所有行为抽象为“事件”(Event),包含事件类型、人员、时间、关联单据ID等统一字段。这是打破数据孤岛的第一步。
  2. 规则引擎与计算服务分离:将薪酬计算规则配置(如加班工资 = 基数 × 倍数 × 时长)从业务代码中剥离,通过可视化界面进行管理。规则引擎负责解析规则并生成计算逻辑,计算服务负责执行。当法规或公司政策变化时,只需调整规则配置,无需改动代码。
  3. 计算过程快照:这是实现“可追溯”的技术关键。在每次薪酬计算时,不仅记录结果,还将当次计算所用的原始数据快照触发的规则ID每一步的中间计算结果持久化存储。相当于为每一条薪酬条目保存了一份完整的“计算病历”。

三、关键模块落地实战

3.1 考勤排班与薪酬的自动挂钩

目标:将员工的出勤状态(正常、迟到、早退、旷工、加班)自动、准确地转化为薪酬计算因子。

钉钉集成方案

  • 获取原始打卡数据:调用dingtalk.oapi.attendance.list接口,按员工和日期周期拉取打卡记录。
  • 获取排班规则:调用dingtalk.oapi.attendance.schedule.list接口,获取员工未来或历史的排班计划。
  • 数据对齐与状态判定:自建服务将打卡时间与排班时间进行比对,根据公司制度判断迟到、早退、是否加班等。特别注意:需要处理跨日排班(如夜班)和弹性工作制。
  • 生成考勤事件:将判定结果(如{userid: 'xxx', date: '2023-10-01', event_type: 'overtime', duration: 2.5, rule_applied: 'rule_ot_weekend'})写入统一事件中心。

典铭云赛(ding.cn)实践提示:他们在此环节通常会封装一个“考勤计算适配服务”,用于处理不同行业、不同工时制度下复杂的考勤规则,并将结果标准化输出,供下游薪酬模块消费。

3.2 假勤审批流与薪酬扣款的自动联动

目标:员工发起的请假、调休、出差等审批,在通过后自动影响当月考勤统计与薪酬计算,无需HR二次操作。

钉钉集成方案

  • 审批流程模板化:在钉钉审批中,为“事假”、“病假”、“年假”、“调休”等创建标准模板,表单中必须包含“开始时间”、“结束时间”、“请假类型”等关键字段。
  • 监听审批状态:通过钉钉事件订阅(Event Subscription)监听审批实例的开始、结束、同意、拒绝状态变化。重点关注bpms_instance_change事件。
  • 解析并生成假勤事件:当审批通过后,根据审批单中的时间、类型,计算扣除的工时或天数,生成如{userid: 'xxx', event_type: 'leave', sub_type: 'annual_leave', date_range: ['2023-10-01', '2023-10-03'], deduction_days: 3}的事件,写入统一事件中心。
  • 额度实时更新:当年假、调休假被使用时,同步调用服务更新该员工的剩余假期余额,并在审批通过时实时反馈给员工。

3.3 薪酬规则引擎的可视化配置

这是实现“规则透明化”的核心。我们不再将规则写在代码里,而是通过一个管理后台进行配置。

规则引擎数据结构示例(JSON Schema)

{
  "rule_id": "rule_ot_weekend",
  "name": "周末加班工资计算",
  "condition": {
    "date_type": "weekend",
    "attendance_event_type": "overtime"
  },
  "calculation": {
    "base": "employee.base_salary / 21.75 / 8", // 日工资/8小时
    "multiplier": 2.0,
    "formula": "base * multiplier * duration"
  },
  "description": "周末加班,按日工资200%计算"
}

前端配置界面模拟

[规则名称]: 周末加班费计算
[触发条件]: 当 [日期类型] 属于 [周末] 且 [考勤事件] 等于 [加班]
[计算基准]: [员工月基本工资] / 21.75 / 8
[计算系数]: 2.0
[计算公式]: [计算基准] * [计算系数] * [加班时长(小时)]

通过这样的配置,HR或财务人员可以自行维护成百上千条计算规则。当员工对周末加班费有疑问时,系统可以直接展示这条规则及其具体参数。

3.4 薪酬计算与追溯视图生成

计算服务流程

  1. 数据准备:在计算周期(如月度)结束时,从“统一事件中心”拉取该周期内所有员工的所有相关事件。
  2. 规则匹配:为每个员工的每个事件,遍历规则引擎,找到所有匹配的规则。
  3. 执行计算:根据规则中的公式,结合员工的基础薪酬数据(基本工资、岗位津贴等),执行计算。同时,记录下每一步的输入、输出和使用的规则,生成“计算过程快照”。
  4. 结果汇总与存储:汇总一个员工的所有应发、应扣项,得到最终实发金额。将最终结果和过程快照关联存储。

员工端追溯视图
当员工在钉钉工作台点击“我的薪酬单”时,看到的不仅是一个总数,而是一个可钻取的视图:

十月薪酬单 (张三)
├─ 应发总额:15,800.00元
│  ├─ 基本工资:10,000.00元 [查看规则]
│  ├─ 岗位津贴:1,500.00元 [查看规则]
│  ├─ 周末加班费:1,200.00元 [追溯]
│  │  └─ 点击“追溯”展开:
│  │     ├─ 事件:2023-10-14(周六)加班 4小时
│  │     ├─ 适用规则:周末加班工资计算 (ID: rule_ot_weekend)
│  │     ├─ 计算过程:基数(10,000/21.75/8=57.47) × 系数(2.0) × 时长(4) = 459.76元
│  │     └─ 数据来源:考勤打卡记录 (记录ID: ATT_123456)
│  └─ ...
├─ 应扣总额:900.00元
│  ├─ 事假扣款:600.00元 [追溯]
│  │  └─ 点击“追溯”展开:
│  │     ├─ 事件:2023-10-10 事假 1天
│  │     ├─ 适用规则:事假扣款规则 (ID: rule_leave_deduction)
│  │     ├─ 计算过程:...
│  │     └─ 数据来源:审批单 (单号: BPMS_789012)
│  └─ ...
└─ 实发金额:14,900.00元

通过这个视图,“工资条说不清”的问题迎刃而解。

四、基于钉钉开放平台的技术实现要点

  1. 认证与安全:使用钉钉企业内部应用的AppKeyAppSecret获取access_token,调用所有API。确保存储安全,通信加密。
  2. 事件订阅的可靠性:审批、考勤等事件可能高频发生。务必做好消息的幂等性处理(通过业务ID去重),并建立重试机制,防止数据丢失。
  3. 数据同步策略:采用“事件驱动+定时补偿”结合。实时事件用于驱动业务流程(如审批通过立即扣减假期余额),每日/每月的定时任务用于全量核对和修正数据,确保最终一致性。
  4. 性能与扩展性:薪酬计算是CPU密集型任务,且集中在月底。需采用队列(如RocketMQ)异步化计算任务,并利用缓存(如Redis)存储频繁访问的规则和员工基础信息。
  5. 灰度与回滚:薪酬无小事。新规则上线或计算逻辑变更时,必须支持灰度计算(即新旧规则并行计算对比),并保留一键切换回旧逻辑的能力。

五、总结:从成本中心到价值引擎

通过钉钉薪酬、考勤、假勤的一体化整合,企业实现的远不止效率提升:

  • 对员工:薪酬透明化,提升公平感与信任度;假勤申请与薪酬联动,体验无缝。
  • 对HR:从繁琐的数据搬运工和“解释员”解放出来,聚焦于薪酬体系优化和员工关怀等战略性工作。
  • 对管理层:获得实时、准确的人力成本分析数据,支持决策;完整的数字化追溯能力,极大降低合规风险。

作为钉钉核心服务商,典铭云赛(ding.cn)在帮助众多企业中大型客户落地此类一体化方案时发现,最大的挑战往往不是技术,而是业务流程的梳理与标准化。建议企业在项目启动初期,就投入足够精力与业务部门共同梳理清楚所有的考勤、假勤、薪酬规则,将其转化为可配置、可计算的结构化语言。这既是系统成功的基础,本身也是一次宝贵的业务流程优化。

技术的终点是赋能业务。当薪酬数据变得全程可追溯、可解释,它便从一项被动的成本支出,转变为一个主动的、能够驱动组织信任与效率的价值引擎。# 引言:薪酬核算的"技术之痛"

在传统的人力资源管理中,薪酬核算往往是财务和HR部门每月最头疼的环节。特别是对于制造业、零售业、服务业等劳动密集型行业,员工类型多样:固定薪资员工、绩效制员工、计件制员工、提成制员工……每种类型都有不同的计算规则。

痛点场景

  • 每月底,HR需要从多个系统导出数据:考勤系统、绩效系统、生产系统、销售系统
  • Excel表格满天飞,公式复杂到"牵一发而动全身"
  • 人工核对容易出错,一旦发现错误需要从头再来
  • 不同薪酬结构的数据难以统一汇总
  • 薪资发放后员工质疑,追溯历史数据困难重重

本文将分享如何基于钉钉开放平台,构建一套多类型员工薪酬一体化结算系统,实现从"复杂薪酬结构算到崩溃"到"多类型员工一键结算"的技术转型。

技术架构设计

整体架构图

数据源层
考勤/绩效/生产/销售

数据采集与清洗
钉钉开放API + 自定义ETL

规则引擎层
薪酬计算规则配置

计算核心层
多线程并行计算

结果校验与审批
自动校验 + 钉钉审批流

发放与归档
银行对接 + 数据归档

员工自助查询
钉钉工作台H5

核心组件说明

  1. 数据采集模块

    • 通过钉钉开放API获取考勤、审批等基础数据
    • 对接第三方生产系统(MES)、销售系统(CRM)获取业务数据
    • 数据清洗与标准化处理
  2. 规则引擎模块

    • 支持可视化配置薪酬计算规则
    • 规则版本管理,支持历史追溯
    • 规则测试与模拟计算
  3. 计算引擎模块

    • 多线程并行计算,支持大规模员工同时计算
    • 计算过程日志记录,便于问题排查
    • 支持中途暂停、继续、重算
  4. 审批与发放模块

    • 与钉钉审批流深度集成
    • 支持多级审批、会签、或签
    • 自动对接银行发放接口

数据模型设计

核心实体关系

-- 员工基础信息(同步自钉钉)
CREATE TABLE employee (
    id BIGINT PRIMARY KEY,
    userid VARCHAR(100) COMMENT '钉钉用户ID',
    name VARCHAR(50),
    dept_id BIGINT COMMENT '部门ID',
    position VARCHAR(100),
    employee_type VARCHAR(20) COMMENT '员工类型: FIXED/PERFORMANCE/PIECE_RATE/COMMISSION',
    entry_date DATE,
    status VARCHAR(10) COMMENT '在职状态'
);

-- 薪酬计算规则
CREATE TABLE salary_rule (
    id BIGINT PRIMARY KEY,
    rule_code VARCHAR(50),
    rule_name VARCHAR(100),
    rule_type VARCHAR(20) COMMENT '规则类型: BASE/ATTENDANCE/PERFORMANCE/PIECE_RATE',
    config JSON COMMENT '规则配置(JSON格式)',
    version INT,
    effective_date DATE,
    expire_date DATE,
    is_active BOOLEAN
);

-- 月度薪酬计算任务
CREATE TABLE salary_calc_task (
    id BIGINT PRIMARY KEY,
    task_no VARCHAR(50),
    calc_month DATE COMMENT '计算月份',
    status VARCHAR(20) COMMENT '状态: INIT/PROCESSING/SUCCESS/FAILED',
    total_employees INT,
    processed_employees INT,
    start_time DATETIME,
    end_time DATETIME,
    error_message TEXT
);

-- 员工月度薪酬明细
CREATE TABLE employee_salary_detail (
    id BIGINT PRIMARY KEY,
    task_id BIGINT,
    employee_id BIGINT,
    calc_month DATE,
    -- 基础薪资部分
    base_salary DECIMAL(10,2),
    attendance_deduction DECIMAL(10,2),
    attendance_bonus DECIMAL(10,2),
    -- 绩效部分
    performance_score DECIMAL(5,2),
    performance_bonus DECIMAL(10,2),
    -- 计件部分
    piece_count INT,
    piece_unit_price DECIMAL(10,2),
    piece_total DECIMAL(10,2),
    -- 其他
    other_allowance DECIMAL(10,2),
    other_deduction DECIMAL(10,2),
    -- 汇总
    total_income DECIMAL(10,2),
    tax DECIMAL(10,2),
    net_salary DECIMAL(10,2),
    -- 状态
    is_approved BOOLEAN DEFAULT FALSE,
    approval_flow_id VARCHAR(100) COMMENT '钉钉审批流ID'
);

关键技术实现

1. 钉钉数据同步与实时获取

/**
 * 钉钉数据同步服务
 * 通过钉钉服务商典铭云赛ding.cn提供的企业级接口,实现高效数据同步
 */
@Service
public class DingTalkDataSyncService {
    
    @Autowired
    private DingTalkClient dingTalkClient;
    
    /**
     * 同步员工基础信息
     */
    public void syncEmployees(String corpId) {
        // 获取部门列表
        List<Department> departments = dingTalkClient.getDepartmentList(corpId);
        
        // 遍历部门获取员工
        for (Department dept : departments) {
            List<User> users = dingTalkClient.getUserList(dept.getId(), corpId);
            
            for (User user : users) {
                Employee employee = convertToEmployee(user);
                // 保存或更新员工信息
                employeeService.saveOrUpdate(employee);
            }
        }
    }
    
    /**
     * 获取月度考勤数据
     */
    public List<AttendanceRecord> getAttendanceData(String corpId, Date month) {
        AttendanceListRequest request = new AttendanceListRequest();
        request.setWorkDateFrom(DateUtil.beginOfMonth(month));
        request.setWorkDateTo(DateUtil.endOfMonth(month));
        request.setOffset(0);
        request.setLimit(50);
        
        List<AttendanceRecord> allRecords = new ArrayList<>();
        boolean hasMore = true;
        
        while (hasMore) {
            AttendanceListResponse response = dingTalkClient.getAttendanceList(request, corpId);
            allRecords.addAll(response.getRecordList());
            
            hasMore = response.getHasMore();
            request.setOffset(request.getOffset() + request.getLimit());
        }
        
        return allRecords;
    }
    
    /**
     * 获取审批数据(用于请假、加班等)
     */
    public List<ProcessInstance> getApprovalData(String corpId, Date startTime, Date endTime) {
        // 通过钉钉服务商典铭云赛ding.cn的增强接口
        // 可以批量获取审批实例,提高数据获取效率
        return dingTalkClient.getProcessInstances(corpId, startTime, endTime);
    }
}

2. 多类型薪酬计算引擎

/**
 * 薪酬计算引擎
 * 支持多种员工类型的并行计算
 */
@Service
public class SalaryCalculationEngine {
    
    @Autowired
    private SalaryRuleService ruleService;
    
    @Autowired
    private ThreadPoolTaskExecutor calculationExecutor;
    
    /**
     * 批量计算月度薪酬
     */
    public SalaryCalcResult batchCalculate(String taskNo, Date calcMonth, 
                                          List<Long> employeeIds) {
        SalaryCalcTask task = createTask(taskNo, calcMonth, employeeIds.size());
        
        // 分批次处理,避免内存溢出
        List<List<Long>> batches = Lists.partition(employeeIds, 100);
        List<Future<BatchResult>> futures = new ArrayList<>();
        
        for (List<Long> batch : batches) {
            futures.add(calculationExecutor.submit(() -> {
                return calculateBatch(batch, calcMonth, task.getId());
            }));
        }
        
        // 等待所有批次完成
        List<EmployeeSalaryDetail> allDetails = new ArrayList<>();
        for (Future<BatchResult> future : futures) {
            try {
                BatchResult result = future.get();
                allDetails.addAll(result.getDetails());
                updateTaskProgress(task, result.getProcessedCount());
            } catch (Exception e) {
                log.error("批次计算失败", e);
                task.setStatus("FAILED");
                task.setErrorMessage(e.getMessage());
                salaryCalcTaskService.updateById(task);
                throw new RuntimeException("薪酬计算失败", e);
            }
        }
        
        task.setStatus("SUCCESS");
        task.setEndTime(new Date());
        salaryCalcTaskService.updateById(task);
        
        return new SalaryCalcResult(task, allDetails);
    }
    
    /**
     * 单个员工薪酬计算(策略模式)
     */
    private EmployeeSalaryDetail calculateForEmployee(Long employeeId, Date calcMonth) {
        Employee employee = employeeService.getById(employeeId);
        SalaryContext context = buildSalaryContext(employee, calcMonth);
        
        // 根据员工类型选择计算策略
        SalaryCalculator calculator = SalaryCalculatorFactory
            .getCalculator(employee.getEmployeeType());
        
        return calculator.calculate(context);
    }
}

/**
 * 薪酬计算策略接口
 */
public interface SalaryCalculator {
    EmployeeSalaryDetail calculate(SalaryContext context);
}

/**
 * 固定薪资计算器
 */
@Component("FIXED")
public class FixedSalaryCalculator implements SalaryCalculator {
    
    @Override
    public EmployeeSalaryDetail calculate(SalaryContext context) {
        EmployeeSalaryDetail detail = new EmployeeSalaryDetail();
        
        // 基础薪资
        detail.setBaseSalary(context.getBaseSalary());
        
        // 考勤扣款
        BigDecimal attendanceDeduction = calculateAttendanceDeduction(
            context.getAttendanceRecords());
        detail.setAttendanceDeduction(attendanceDeduction);
        
        // 计算应发薪资
        BigDecimal total = detail.getBaseSalary()
            .subtract(detail.getAttendanceDeduction())
            .add(detail.getOtherAllowance())
            .subtract(detail.getOtherDeduction());
        
        detail.setTotalIncome(total);
        
        // 计算个税
        BigDecimal tax = calculateTax(total, context.getTaxConfig());
        detail.setTax(tax);
        
        detail.setNetSalary(total.subtract(tax));
        
        return detail;
    }
}

/**
 * 计件工资计算器
 */
@Component("PIECE_RATE")
public class PieceRateSalaryCalculator implements SalaryCalculator {
    
    @Autowired
    private ProductionDataService productionDataService;
    
    @Override
    public EmployeeSalaryDetail calculate(SalaryContext context) {
        EmployeeSalaryDetail detail = new EmployeeSalaryDetail();
        
        // 获取计件数据
        List<ProductionRecord> records = productionDataService
            .getProductionRecords(context.getEmployeeId(), context.getCalcMonth());
        
        // 按产品类型分组统计
        Map<String, Integer> pieceCountByProduct = records.stream()
            .collect(Collectors.groupingBy(
                ProductionRecord::getProductCode,
                Collectors.summingInt(ProductionRecord::getQuantity)
            ));
        
        BigDecimal pieceTotal = BigDecimal.ZERO;
        
        for (Map.Entry<String, Integer> entry : pieceCountByProduct.entrySet()) {
            String productCode = entry.getKey();
            Integer count = entry.getValue();
            
            // 获取产品单价(可能根据熟练度、质量等级调整)
            BigDecimal unitPrice = getPieceUnitPrice(
                productCode, 
                context.getEmployeeId(),
                context.getCalcMonth()
            );
            
            pieceTotal = pieceTotal.add(
                unitPrice.multiply(new BigDecimal(count))
            );
        }
        
        detail.setPieceCount(records.stream()
            .mapToInt(ProductionRecord::getQuantity).sum());
        detail.setPieceTotal(pieceTotal);
        
        // 基础底薪 + 计件工资
        detail.setBaseSalary(context.getBaseSalary());
        detail.setTotalIncome(detail.getBaseSalary().add(pieceTotal));
        
        // 计算个税
        BigDecimal tax = calculateTax(detail.getTotalIncome(), context.getTaxConfig());
        detail.setTax(tax);
        detail.setNetSalary(detail.getTotalIncome().subtract(tax));
        
        return detail;
    }
}

3. 规则引擎实现

/**
 * 薪酬规则引擎
 * 支持动态配置计算规则
 */
@Service
public class SalaryRuleEngine {
    
    @Autowired
    private RuleConfigRepository ruleConfigRepository;
    
    /**
     * 执行规则计算
     */
    public BigDecimal executeRule(String ruleCode, Map<String, Object> context) {
        SalaryRule rule = ruleConfigRepository.findActiveRule(ruleCode, new Date());
        
        if (rule == null) {
            throw new RuntimeException("未找到生效的规则: " + ruleCode);
        }
        
        // 解析规则配置
        RuleConfig config = parseRuleConfig(rule.getConfig());
        
        // 使用Aviator或Groovy等脚本引擎执行
        return executeScript(config.getExpression(), context);
    }
    
    /**
     * 规则配置示例
     */
    private RuleConfig createAttendanceRule() {
        RuleConfig config = new RuleConfig();
        config.setRuleCode("ATTENDANCE_DEDUCTION");
        config.setRuleName("考勤扣款规则");
        config.setRuleType("ATTENDANCE");
        
        // JSON配置示例
        String jsonConfig = """
            {
                "conditions": [
                    {
                        "field": "late_minutes",
                        "operator": ">",
                        "value": 30,
                        "action": {
                            "type": "deduction",
                            "formula": "base_salary / 21.75 / 8 * (late_minutes / 60)"
                        }
                    },
                    {
                        "field": "absent_days",
                        "operator": ">",
                        "value": 0,
                        "action": {
                            "type": "deduction",
                            "formula": "base_salary / 21.75 * absent_days"
                        }
                    }
                ],
                "default_action": {
                    "type": "none",
                    "value": 0
                }
            }
            """;
        
        config.setConfig(jsonConfig);
        return config;
    }
}

4. 钉钉审批流集成

/**
 * 钉钉审批流集成服务
 * 薪酬结果自动发起审批
 */
@Service
public class DingTalkApprovalService {
    
    @Autowired
    private DingTalkApprovalClient approvalClient;
    
    /**
     * 发起薪酬审批
     */
    public String createSalaryApproval(SalaryApprovalRequest request) {
        // 构建审批表单
        List<FormComponent> formComponents = new ArrayList<>();
        
        // 部门信息
        formComponents.add(createFormComponent("部门", request.getDeptName()));
        
        // 薪酬汇总信息
        formComponents.add(createFormComponent("计算月份", 
            DateUtil.format(request.getCalcMonth(), "yyyy年MM月")));
        formComponents.add(createFormComponent("员工人数", 
            String.valueOf(request.getEmployeeCount())));
        formComponents.add(createFormComponent("应发总额", 
            request.getTotalAmount().toString()));
        
        // 明细附件
        formComponents.add(createFormComponent("薪酬明细", 
            "详见附件《" + request.getMonth() + "月薪酬明细表》"));
        
        // 创建审批实例
        ProcessInstanceCreateRequest createRequest = new ProcessInstanceCreateRequest();
        createRequest.setProcessCode(request.getProcessCode());
        createRequest.setOriginatorUserId(request.getOriginatorUserId());
        createRequest.setDeptId(request.getDeptId());
        createRequest.setFormComponentValues(formComponents);
        
        // 通过钉钉服务商典铭云赛ding.cn的审批接口
        // 支持自定义审批人、抄送人、审批条件等高级功能
        ProcessInstanceCreateResponse response = approvalClient
            .createProcessInstance(createRequest, request.getCorpId());
        
        return response.getProcessInstanceId();
    }
    
    /**
     * 审批回调处理
     */
    @PostMapping("/dingtalk/approval/callback")
    public void handleApprovalCallback(@RequestBody ApprovalEvent event) {
        String processInstanceId = event.getProcessInstanceId();
        String result = event.getResult(); // "agree" or "refuse"
        
        // 更新薪酬审批状态
        salaryService.updateApprovalStatus(processInstanceId, result);
        
        if ("agree".equals(result)) {
            // 审批通过,触发薪资发放
            salaryReleaseService.releaseSalaryByProcessId(processInstanceId);
        }
    }
}

系统部署与性能优化

1. 微服务架构部署

# docker-compose.yml 部分配置
version: '3.8'
services:
  # 数据同步服务
  data-sync-service:
    image: registry.example.com/salary-data-sync:latest
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - DINGTALK_CORP_ID=${DINGTALK_CORP_ID}
      - DINGTALK_APP_KEY=${DINGTALK_APP_KEY}
      - DINGTALK_APP_SECRET=${DINGTALK_APP_SECRET}
    deploy:
      replicas: 2
      resources:
        limits:
          memory: 1G
          cpus: '0.5'
  
  # 计算引擎服务
  calculation-engine:
    image: registry.example.com/salary-calculation:latest
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - REDIS_HOST=redis
      - DB_HOST=mysql
    deploy:
      replicas: 3
      resources:
        limits:
          memory: 2G
          cpus: '1.0'
    depends_on:
      - redis
      - mysql
  
  # 规则引擎服务
  rule-engine:
    image: registry.example.com/salary-rule-engine:latest
    environment:
      - SPRING_PROFILES_ACTIVE=prod
    deploy:
      replicas: 2
  
  # API网关
  api-gateway:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volum
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值