# 出差审批

审批定义的表单包含 **出差控件组** 时，该定义下的审批实例通过时，触发该事件。

## 前提条件

- 应用已配置事件订阅，了解事件订阅可参见[事件订阅概述](https://open.feishu.cn/document/ukTMukTMukTM/uUTNz4SN1MjL1UzM)。

- 应用已调用[订阅审批事件](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/approval-v4/approval/subscribe)接口，订阅了审批实例对应的审批定义 Code。

## 事件

基本 | &nbsp;
---|---
事件类型 | approval.instance.trip_group_update_v4
支持的应用类型 | Custom App、Store App
权限要求<br>**订阅该事件所需的权限，开启其中任意一项权限即可订阅**<br>开启任一权限即可 | 访问审批应用(approval:approval:readonly)<br>查看、创建、更新、删除审批应用相关信息(approval:approval)
字段权限要求 | **注意事项**：该接口返回体中存在下列敏感字段，仅当开启对应的权限后才会返回；如果无需获取这些字段，则不建议申请<br>获取用户 user ID(contact:user.employee_id:readonly)
推送方式 | [Webhook](https://open.feishu.cn/document/ukTMukTMukTM/uUTNz4SN1MjL1UzM)

## 事件体

字段 | 数据类型 | 描述
---|---|---
schema | string | 事件模式
header | event_header | 事件头
event_id | string | 事件 ID
event_type | string | 事件类型
create_time | string | 事件创建时间戳（单位：毫秒）
token | string | 事件 Token
app_id | string | 应用 ID
tenant_key | string | 租户 Key
event | object | /-
object | object | 事件详细信息。
instance_code | string | 审批实例 Code。可调用[获取单个审批实例详情](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/approval-v4/instance/get)接口，根据 Code 查询实例详情。
start_time | int | 审批开始时间，秒级时间戳。
end_time | int | 审批结束时间，秒级时间戳。
schedules | object | 出差详细信息。
departure | string | 出发地。
departure_id | string | 出发地 ID。
destination | string | 目的地。
destination_ids | string[] | 目的地 ID。
remark | string | 备注信息。
transportation | string | 交通工具。
trip_start_time | string | 出差开始时间。示例格式：2022-08-25 12:00:00
trip_end_time | string | 出差结束时间。示例格式：2022-08-25 12:00:00
trip_interval | string | 出差时长。单位：秒
trip_type | string | 出差类型。可能值：<br>- 单程<br>- 往返
start_user | object | 审批提交人的信息。
id | object | 审批提交人的 ID。不同类型 ID 介绍参见[用户身份概述](https://open.feishu.cn/document/home/user-identity-introduction/introduction#9eb4f13c)。
open_id | string | 提交人的 open_id
union_id | string | 提交人的 union_id
user_id | string | 提交人的 user_id<br>为应用开通以下权限才能获取 user_id：<br>获取用户 user ID(contact:user.employee_id:readonly)
trip_interval | string | 出差时长。单位：秒
trip_reason | string | 出差原因。
type | string | 固定值 `APPROVED`
trip_peers | object | 出差同行人信息。
id | object | 同行人 ID。不同类型 ID 介绍参见[用户身份概述](https://open.feishu.cn/document/home/user-identity-introduction/introduction#9eb4f13c)。
open_id | string | 同行人的 open_id
union_id | string | 同行人的 union_id
user_id | string | 同行人的 user_id<br>为应用开通以下权限才能获取 user_id：<br>获取用户 user ID(contact:user.employee_id:readonly)

## 事件体示例

```json
{
  "schema": "2.0",
  "header": {
        "event_id": "b53e80a132ec090ba249a8bb74da0123",
        "token": "ijuh4XAlBSRUIwhBFtLDSfWnIC61234",
        "event_type":"approval.instance.trip_group_update_v4",
        "app_id": "cli_xxx",
        "tenant_key": "d6ae59e175fxxx"
        "event_create_time": "1502199207.7171419"，
  },
  "event": {
    "object": {
      "type": "APPROVED",
      "instance_code": "FC2CB9B2-966F-4B65-AC7E-B21E04F497CB",
      "start_user": {
        "id": {
          "user_id": "abc456",
          "open_id": "ou_0e188d8010729123",
          "union_id": "on_445705689d4d112"
        }
      },
      "start_time": "1661333019",
      "end_time": "1661333029",
      "schedules": [
        {
          "trip_start_time": "2022-08-25 12:00:00",
          "trip_end_time": "2022-09-01 00:00:00",
          "trip_interval": "561600",
          "departure": "中国/北京市/北京市/朝阳区",
          "departure_id": "6c758b5dc54930abc7a454c7477d3496cfca0a62fd941e0093b2d819e0e263a6",
          "destination": "美国",
          "destination_ids": [
            "dcb23aebd41ad8a8aabf3f6a72fa8fabed7fae73b662d332a4a7d411d6f2312e"
          ],
          "transportation": "飞机",
          "trip_type": "往返",
          "remark": "text demo"
        }
      ],
      "trip_interval": "561600",
      "trip_reason": "test",
      "trip_peers": [
        {
          "id": {
            "user_id": "abc123",
            "open_id": "ou_0e188d8010729321",
            "union_id": "on_445705689d4d132"
          }
        }
      ]
    }
  }
}
```

### 事件订阅示例代码

事件订阅流程可参考：[事件订阅概述](https://open.feishu.cn/document/ukTMukTMukTM/uUTNz4SN1MjL1UzM)，新手入门可参考：[教程](https://open.feishu.cn/document/uAjLw4CM/uMzNwEjLzcDMx4yM3ATM/develop-an-echo-bot/introduction)

**

package main

import (
        "context"
        "fmt"

larkcore "github.com/larksuite/oapi-sdk-go/v3/core"
        larkevent "github.com/larksuite/oapi-sdk-go/v3/event"
        "github.com/larksuite/oapi-sdk-go/v3/event/dispatcher"
        "github.com/larksuite/oapi-sdk-go/v3/service/approval/old"
        larkws "github.com/larksuite/oapi-sdk-go/v3/ws"
)

// SDK 使用说明 SDK user guide：https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/server-side-sdk/golang-sdk-guide/preparations
func main() {
        // 注册事件 Register event
        eventHandler := dispatcher.NewEventDispatcher("", "").
                OnCustomizedEvent("approval.instance.trip_group_update_v4", func(ctx context.Context, event *larkevent.EventReq) error {
                        fmt.Printf("[ OnCustomizedEvent access ], type: message, data: %s\n", string(event.Body))
                        return nil
                })

// 构建 client Build client
        cli := larkws.NewClient("YOUR_APP_ID", "YOUR_APP_SECRET",
                larkws.WithEventHandler(eventHandler),
                larkws.WithLogLevel(larkcore.LogLevelDebug),
        )

// 建立长连接 Establish persistent connection
        err := cli.Start(context.Background())

if err != nil {
                panic(err)
        }
}

# SDK 使用说明 SDK user guide：https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/server-side-sdk/python--sdk/preparations-before-development
import lark_oapi as lark

def do_customized_event(data: lark.CustomizedEvent) -> None:
    print(f'[ do_customized_event access ], type: message, data: {lark.JSON.marshal(data, indent=4)}')

# 注册事件 Register event
event_handler = lark.EventDispatcherHandler.builder("", "") \
    .register_p1_customized_event("approval.instance.trip_group_update_v4", do_customized_event) \
    .build()

def main():
    # 构建 client Build client
    cli = lark.ws.Client("APP_ID", "APP_SECRET",
                        event_handler=event_handler, log_level=lark.LogLevel.DEBUG)
    # 建立长连接 Establish persistent connection
    cli.start()

if __name__ == "__main__":
    main()

package com.example.sample;

import java.nio.charset.StandardCharsets;
import com.lark.oapi.core.request.EventReq;
import com.lark.oapi.event.CustomEventHandler;
import com.lark.oapi.event.EventDispatcher;
import com.lark.oapi.ws.Client;

// SDK 使用说明 SDK user guide：https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/server-side-sdk/java-sdk-guide/preparations
public class Sample {
    // 注册事件 Register event
    private static final EventDispatcher EVENT_HANDLER = EventDispatcher.newBuilder("", "")
            .onCustomizedEvent("approval.instance.trip_group_update_v4", new CustomEventHandler() {
                @Override
                public void handle(EventReq event) throws Exception {
                    System.out.printf("[ onCustomizedEvent access ], type: message, data: %s\n", new String(event.getBody(), StandardCharsets.UTF_8));
                }
            })
            .build();

public static void main(String[] args) {
        // 构建 client Build client
        Client client = new Client.Builder("APP_ID", "APP_SECRET")
                .eventHandler(EVENT_HANDLER)
                .build();
        // 建立长连接 Establish persistent connection
        client.start();
    }
}

// SDK 使用说明 SDK user guide：https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/server-side-sdk/nodejs-sdk/preparation-before-development
import * as Lark from '@larksuiteoapi/node-sdk';
const baseConfig = {
    appId: 'APP_ID',
    appSecret: 'APP_SECRET'
}
// 构建 client Build client
const wsClient = new Lark.WSClient(baseConfig);
// 建立长连接 Establish persistent connection
wsClient.start({
    // 注册事件 Register event
    eventDispatcher: new Lark.EventDispatcher({}).register({
        'approval.instance.trip_group_update_v4': async (data) => {
            console.log(data);
        }
    })
});

package main

import (
        "context"
        "fmt"
        "net/http"

larkcore "github.com/larksuite/oapi-sdk-go/v3/core"
        "github.com/larksuite/oapi-sdk-go/v3/core/httpserverext"
        larkevent "github.com/larksuite/oapi-sdk-go/v3/event"
        "github.com/larksuite/oapi-sdk-go/v3/event/dispatcher"
        "github.com/larksuite/oapi-sdk-go/v3/service/approval/old"
)

// SDK 使用说明 SDK user guide：https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/server-side-sdk/golang-sdk-guide/preparations
func main() {
        // 注册事件 Register event
        eventHandler := dispatcher.NewEventDispatcher("", "").
                OnCustomizedEvent("approval.instance.trip_group_update_v4", func(ctx context.Context, event *larkevent.EventReq) error {
                        fmt.Printf("[ OnCustomizedEvent access ], type: message, data: %s\n", string(event.Body))
                        return nil
                })

// 创建路由处理器 Create route handler
        http.HandleFunc("/webhook/event", httpserverext.NewEventHandlerFunc(handler, larkevent.WithLogLevel(larkcore.LogLevelDebug)))

err := http.ListenAndServe(":7777", nil)

if err != nil {
                panic(err)
        }
}

# SDK 使用说明 SDK user guide：https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/server-side-sdk/python--sdk/preparations-before-development
from flask import Flask
from lark_oapi.adapter.flask import *
import lark_oapi as lark

app = Flask(__name__)

def do_customized_event(data: lark.CustomizedEvent) -> None:
    print(f'[ do_customized_event access ], type: message, data: {lark.JSON.marshal(data, indent=4)}')

# 注册事件 Register event
event_handler = lark.EventDispatcherHandler.builder("", "") \
    .register_p1_customized_event("approval.instance.trip_group_update_v4", do_customized_event) \
    .build()

# 创建路由处理器 Create route handler
@app.route("/webhook/event", methods=["POST"])
def event():
    resp = handler.do(parse_req())
    return parse_resp(resp)

if __name__ == "__main__":
    app.run(port=7777)

package com.lark.oapi.sample.event;

import java.nio.charset.StandardCharsets;
import com.lark.oapi.core.request.EventReq;
import com.lark.oapi.event.CustomEventHandler;
import com.lark.oapi.sdk.servlet.ext.ServletAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

// SDK 使用说明 SDK user guide：https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/server-side-sdk/java-sdk-guide/preparations
@RestController
public class EventController {

// 注册事件 Register event
    private static final EventDispatcher EVENT_HANDLER = EventDispatcher.newBuilder("verificationToken", "encryptKey")
            .onCustomizedEvent("approval.instance.trip_group_update_v4", new CustomEventHandler() {
                @Override
                public void handle(EventReq event) throws Exception {
                    System.out.printf("[ onCustomizedEvent access ], type: message, data: %s\n", new String(event.getBody(), StandardCharsets.UTF_8));
                }
            })
            .build();

// 注入 ServletAdapter 实例 Inject ServletAdapter instance
    @Autowired
    private ServletAdapter servletAdapter;

// 创建路由处理器 Create route handler
    @RequestMapping("/webhook/event")
    public void event(HttpServletRequest request, HttpServletResponse response)
            throws Throwable {
        // 回调扩展包提供的事件回调处理器 Callback handler provided by the extension package
        servletAdapter.handleEvent(request, response, EVENT_DISPATCHER);
    }
}

// SDK 使用说明 SDK user guide：https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/server-side-sdk/nodejs-sdk/preparation-before-development
import http from 'http';
import * as lark from '@larksuiteoapi/node-sdk';

// 注册事件 Register event
const eventDispatcher = new lark.EventDispatcher({
    encryptKey: '',
    verificationToken: '',
}).register({
    'approval.instance.trip_group_update_v4': async (data) => {
        console.log(data);
        return 'success';
    },
});

const server = http.createServer();
// 创建路由处理器 Create route handler
server.on('request', lark.adaptDefault('/webhook/event', eventDispatcher));
server.listen(3000);

