アプリを作る
データと Workflow を使う
アプリから SDK で、レコードの読み書き、値の確定、CSV の取り込み、案件の開始と確認、Task の完了と承認を行う方法です。
このページでは、アプリの BFF から業務データ・Workflow・Task を使うコードを説明します。例の hane は、利用者のアクセストークンで作った SDK のクライアントです(SDK)。考え方は データ・Workflow・承認 にあります。
レコードを読む
Section titled “レコードを読む”// 有効なバージョンの型と項目。画面の項目名は title から引くconst { types } = await unwrap(hane.GET("/v1/data/types"));
// 契約の一覧に、今の関係を付けて読むconst contracts = await unwrap( hane.GET("/v1/data/records", { params: { query: { type: "LeaseContract", include: "relations", limit: 500 } } }),);
// 推定のままの満了日を持つ契約だけconst unconfirmed = await unwrap( hane.GET("/v1/data/records", { params: { query: { type: "LeaseContract", field: "end_date", state: "estimated" } } }),);レコードの値は、項目ごとに value・state(値の状態)・source(出典)を持ちます。画面では値と一緒に値の状態を出し、確定でない値をそれと分かるようにします。
const { entity } = await unwrap(hane.GET("/v1/data/records/{id}", { params: { path: { id } } }));const end = entity.values.end_date; // { value: "2027-03-31", state: "estimated", source: { kind: "computed", ... } }レコードを書く
Section titled “レコードを書く”// 作る。値は { value, state?, note? }await unwrap(hane.POST("/v1/data/records", { body: { type: "Vehicle", values: { registration_no: { value: "架空 300 あ 10-01" } } } }));
// 直す。読んだときのバージョンを If-Match に付け、理由を残すawait unwrap( hane.PATCH("/v1/data/records/{id}", { params: { path: { id }, header: { "If-Match": ifMatch(entity.version) } }, body: { values: { start_date: { value: "2024-04-01" } }, reason: "車検証で開始日を確かめた" }, }),);
// 関係を作るawait unwrap(hane.POST("/v1/data/relations", { body: { type: "covers", from: contractId, to: vehicleId } }));計算の規則が決める項目(満了日など)は直接書かず、入力の項目を直します。値と値の状態は Hane が計算し直します。
値を確定にする
Section titled “値を確定にする”人が書類で確かめた値は、確定に上げます。人だけができる操作なので、利用者のトークンで呼びます。確かめた人として利用者が記録されます。
await unwrap( hane.POST("/v1/data/records/{id}/values/{field}/confirm", { params: { path: { id, field: "start_date" } }, body: { note: "契約書の 1 ページで確かめた" }, }),);const history = await unwrap( hane.GET("/v1/data/records/{id}/values/{field}/history", { params: { path: { id, field: "start_date" } } }),);CSV を取り込む
Section titled “CSV を取り込む”見出しは項目の名前にします。<項目>@state の列で値の状態を、@rel:<関係> の列で相手のキーを渡せます。顧客の台帳の列から Hane の項目への変換は、アプリの側で済ませてから送ります。
const result = await unwrap( hane.POST("/v1/data/imports", { body: { type: "Vehicle", csv, label: "車両台帳 2026-10", default_state: "estimated" }, }),);console.log(result.rows, result.created, result.updated, result.failed);hanectl でも hanectl import Vehicle -f vehicles.csv --state estimated で取り込めます。
案件を始める
Section titled “案件を始める”アプリから案件を始めるときは、次のどちらかを使います。公開 API に、案件を直接作る口はありません。
| 始まり方 | 書き方 |
|---|---|
| 毎日のスケジュール | Schedule の forEach で、条件に当たるレコードごとに案件を作る |
| 画面の操作やチャットから | workflow.start のツールを持つ Agent を、AgentRun で動かす |
画面のボタンから案件を始めるときは、workflow.start だけを持つ台本の Agent を定義しておき、BFF から呼びます。
apiVersion: agents/v1alpha1kind: Agentmetadata: name: procurement-startspec: purpose: 入替・増車の依頼から、調達の案件を始める input: type: object required: [request_no] properties: request_no: { type: string, title: 調達の番号, pattern: "^P-[0-9]{4}$" } driver: scripted tools: [workflow.start] workflows: [procurement] limits: { maxToolCalls: 1, maxSeconds: 60 } output: type: object required: [status, case_id] properties: status: title: 結果 oneOf: - { const: started, title: 案件を始めた } case_id: { title: 案件, type: string } script: - call: workflow.start args: { workflow: '"procurement"', key: input.request_no } as: started - submit: status: '"started"' case_id: steps.started.case.case_idconst run = await unwrap(hane.POST("/v1/agents/runs", { body: { agent: "procurement-start", input: { request_no: "P-1001" } } }));スケジュールを今日の日付で今すぐ発火するには、POST /v1/workflows/schedules/{name}/trigger(hanectl schedules trigger <name>)を使います。
const { items } = await unwrap(hane.GET("/v1/workflows/cases", { params: { query: { workflow: "lease-expiry", status: "waiting" } } }));
// 経過・待機と、案件を始めたときの Workflow の定義const detail = await unwrap(hane.GET("/v1/workflows/cases/{id}", { params: { path: { id: caseId } } }));console.log(detail.case.current_step, detail.case.attention, detail.waits);
// 案件の Taskconst tasks = await unwrap(hane.GET("/v1/approvals/tasks", { params: { query: { case_id: caseId } } }));case.attention には、確定していない値(推定・要照会など)が入ります。案件の画面で、判断の前に確かめる値として出せます。止めるときは POST /v1/workflows/cases/{id}/cancel に reason を付けます。
Task を決める
Section titled “Task を決める”Workflow の task のステップは、割り当ての規則のグループか担当に Task を作ります。利用者が選んだ結果で Task を閉じると、案件が先へ進みます。
const mine = await unwrap(hane.GET("/v1/approvals/tasks", { params: { query: { mine: true, status: "open" } } }));const task = mine.items[0];
await unwrap( hane.POST("/v1/approvals/tasks/{id}/complete", { params: { path: { id: task.task_id }, header: { "If-Match": ifMatch(task.version) } }, body: { outcome: "renew", comment: "条件が変わらないので再リースにする" }, }),);outcome は Task の outcomes のキーのどれかです。表示名は outcomes の値を使います。
外への操作を承認する
Section titled “外への操作を承認する”Agent が提案したメール・SMS・電話などの外への操作(ActionRequest)は、承認の Task になります。承認の画面では、Task に結び付く ActionRequest を読んで中身を見せ、利用者が見た内容の digest を一緒に送ります。内容が変わっていれば 409 になり、承認も却下も記録されません。
const { items } = await unwrap( hane.GET("/v1/agents/action-requests", { params: { query: { approval_task_id: task.task_id, limit: 1 } } }),);const action = items[0]; // tool・parameters(宛先はマスクした値)・action_digest
await unwrap( hane.POST("/v1/approvals/tasks/{id}/complete", { params: { path: { id: task.task_id }, header: { "If-Match": ifMatch(task.version) } }, body: { outcome: "approve", action_digest: action.action_digest }, }),);