コンテンツにスキップ
HaneDOCSEN

アプリを作る

データと Workflow を使う

アプリから SDK で、レコードの読み書き、値の確定、CSV の取り込み、案件の開始と確認、Task の完了と承認を行う方法です。

このページでは、アプリの BFF から業務データ・Workflow・Task を使うコードを説明します。例の hane は、利用者のアクセストークンで作った SDK のクライアントです(SDK)。考え方は データ・Workflow・承認 にあります。

// 有効なバージョンの型と項目。画面の項目名は 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", ... } }
// 作る。値は { 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 が計算し直します。

人が書類で確かめた値は、確定に上げます。人だけができる操作なので、利用者のトークンで呼びます。確かめた人として利用者が記録されます。

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" } } }),
);

見出しは項目の名前にします。<項目>@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 で取り込めます。

アプリから案件を始めるときは、次のどちらかを使います。公開 API に、案件を直接作る口はありません。

始まり方 書き方
毎日のスケジュール Schedule の forEach で、条件に当たるレコードごとに案件を作る
画面の操作やチャットから workflow.start のツールを持つ Agent を、AgentRun で動かす

画面のボタンから案件を始めるときは、workflow.start だけを持つ台本の Agent を定義しておき、BFF から呼びます。

apiVersion: agents/v1alpha1
kind: Agent
metadata:
name: procurement-start
spec:
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_id
const 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);
// 案件の Task
const tasks = await unwrap(hane.GET("/v1/approvals/tasks", { params: { query: { case_id: caseId } } }));

case.attention には、確定していない値(推定・要照会など)が入ります。案件の画面で、判断の前に確かめる値として出せます。止めるときは POST /v1/workflows/cases/{id}/cancel に reason を付けます。

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 の値を使います。

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 },
}),
);