@seasonkoh/webaz 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +189 -0
- package/dist/cron-enforcement.js +83 -0
- package/dist/demo-agent.js +167 -0
- package/dist/index.js +182 -0
- package/dist/layer0-foundation/L0-1-database/schema.js +179 -0
- package/dist/layer0-foundation/L0-2-state-machine/engine.js +183 -0
- package/dist/layer0-foundation/L0-2-state-machine/transitions.js +197 -0
- package/dist/layer0-foundation/L0-5-manifest/manifest.js +306 -0
- package/dist/layer1-agent/L1-1-mcp-server/auth.js +21 -0
- package/dist/layer1-agent/L1-1-mcp-server/server.js +1062 -0
- package/dist/layer2-business/L2-6-notifications/notification-engine.js +217 -0
- package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +678 -0
- package/dist/layer4-economics/L4-3-reputation/reputation-engine.js +205 -0
- package/dist/layer4-economics/L4-4-skill-market/skill-engine.js +258 -0
- package/dist/mcp.js +11 -0
- package/dist/pwa/server.js +760 -0
- package/dist/test-dispute.js +153 -0
- package/dist/test-manifest.js +61 -0
- package/dist/test-mcp-tools.js +135 -0
- package/dist/test-reputation.js +116 -0
- package/dist/test-skill-market.js +101 -0
- package/package.json +60 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L0-1 · 数据库 Schema
|
|
3
|
+
* 所有表结构定义。协议里每个角色、每笔交易、每个状态都存在这里。
|
|
4
|
+
*/
|
|
5
|
+
import Database from 'better-sqlite3';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const DB_PATH = path.join(__dirname, '../../../webaz.db');
|
|
10
|
+
export function initDatabase() {
|
|
11
|
+
const db = new Database(DB_PATH);
|
|
12
|
+
db.pragma('journal_mode = WAL'); // 更好的并发性能
|
|
13
|
+
db.pragma('foreign_keys = ON'); // 强制外键约束
|
|
14
|
+
db.exec(`
|
|
15
|
+
|
|
16
|
+
/* ──────────────────────────────────────────
|
|
17
|
+
用户表 · 每个参与协议的人
|
|
18
|
+
────────────────────────────────────────── */
|
|
19
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
20
|
+
id TEXT PRIMARY KEY, -- 唯一ID,格式:usr_xxxx
|
|
21
|
+
name TEXT NOT NULL,
|
|
22
|
+
role TEXT NOT NULL, -- seller / buyer / logistics / reviewer / arbitrator / promoter
|
|
23
|
+
api_key TEXT UNIQUE NOT NULL, -- Agent 调用时用这个验证身份
|
|
24
|
+
stake REAL DEFAULT 0, -- 当前质押金额(模拟货币)
|
|
25
|
+
reputation REAL DEFAULT 100, -- 声誉分(满分100)
|
|
26
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
27
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
/* ──────────────────────────────────────────
|
|
31
|
+
商品表 · 卖家上架的商品
|
|
32
|
+
────────────────────────────────────────── */
|
|
33
|
+
CREATE TABLE IF NOT EXISTS products (
|
|
34
|
+
id TEXT PRIMARY KEY, -- 格式:prd_xxxx
|
|
35
|
+
seller_id TEXT NOT NULL REFERENCES users(id),
|
|
36
|
+
title TEXT NOT NULL,
|
|
37
|
+
description TEXT NOT NULL,
|
|
38
|
+
price REAL NOT NULL,
|
|
39
|
+
currency TEXT DEFAULT 'DCP', -- 协议内部模拟货币
|
|
40
|
+
stock INTEGER DEFAULT 1,
|
|
41
|
+
category TEXT,
|
|
42
|
+
images TEXT DEFAULT '[]', -- JSON 数组,存图片路径
|
|
43
|
+
stake_amount REAL DEFAULT 0, -- 卖家为这个商品质押的金额
|
|
44
|
+
status TEXT DEFAULT 'active', -- active / paused / delisted
|
|
45
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
46
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
/* ──────────────────────────────────────────
|
|
50
|
+
订单表 · 一笔完整的交易
|
|
51
|
+
────────────────────────────────────────── */
|
|
52
|
+
CREATE TABLE IF NOT EXISTS orders (
|
|
53
|
+
id TEXT PRIMARY KEY, -- 格式:ord_xxxx
|
|
54
|
+
product_id TEXT NOT NULL REFERENCES products(id),
|
|
55
|
+
buyer_id TEXT NOT NULL REFERENCES users(id),
|
|
56
|
+
seller_id TEXT NOT NULL REFERENCES users(id),
|
|
57
|
+
logistics_id TEXT REFERENCES users(id), -- 接单的物流方
|
|
58
|
+
promoter_id TEXT REFERENCES users(id), -- 带来流量的推荐人(可为空)
|
|
59
|
+
|
|
60
|
+
quantity INTEGER DEFAULT 1,
|
|
61
|
+
unit_price REAL NOT NULL,
|
|
62
|
+
total_amount REAL NOT NULL, -- 买家支付总额
|
|
63
|
+
escrow_amount REAL NOT NULL, -- 托管中的金额
|
|
64
|
+
|
|
65
|
+
-- 当前状态(L0-2 状态机管理这个字段)
|
|
66
|
+
status TEXT NOT NULL DEFAULT 'created',
|
|
67
|
+
-- 状态枚举:
|
|
68
|
+
-- created 买家下单,资金待托管
|
|
69
|
+
-- paid 资金已托管
|
|
70
|
+
-- accepted 卖家确认接单
|
|
71
|
+
-- shipped 卖家已交物流
|
|
72
|
+
-- picked_up 物流已揽收
|
|
73
|
+
-- in_transit 运输中
|
|
74
|
+
-- delivered 物流已投递
|
|
75
|
+
-- confirmed 买家确认收货 → 触发结算
|
|
76
|
+
-- disputed 争议中
|
|
77
|
+
-- completed 交易完成,资金已分配
|
|
78
|
+
-- cancelled 取消
|
|
79
|
+
|
|
80
|
+
-- 各节点截止时间(超时自动判责)
|
|
81
|
+
pay_deadline TEXT, -- 下单后 24h 内必须完成支付
|
|
82
|
+
accept_deadline TEXT, -- 支付后 24h 内卖家必须接单
|
|
83
|
+
ship_deadline TEXT, -- 接单后按承诺时间发货
|
|
84
|
+
pickup_deadline TEXT, -- 发货后 48h 内物流必须揽收
|
|
85
|
+
delivery_deadline TEXT, -- 揽收后 X 天内必须投递
|
|
86
|
+
confirm_deadline TEXT, -- 投递后 72h 内买家确认(否则自动确认)
|
|
87
|
+
|
|
88
|
+
shipping_address TEXT, -- 收货地址(JSON)
|
|
89
|
+
notes TEXT, -- 买家备注
|
|
90
|
+
|
|
91
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
92
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
/* ──────────────────────────────────────────
|
|
96
|
+
状态历史表 · 订单每次状态变更的完整记录
|
|
97
|
+
这是「自举证」系统的核心,任何状态变更都留档
|
|
98
|
+
────────────────────────────────────────── */
|
|
99
|
+
CREATE TABLE IF NOT EXISTS order_state_history (
|
|
100
|
+
id TEXT PRIMARY KEY,
|
|
101
|
+
order_id TEXT NOT NULL REFERENCES orders(id),
|
|
102
|
+
from_status TEXT NOT NULL,
|
|
103
|
+
to_status TEXT NOT NULL,
|
|
104
|
+
actor_id TEXT NOT NULL REFERENCES users(id), -- 谁触发了这次状态变更
|
|
105
|
+
actor_role TEXT NOT NULL, -- 触发者的角色
|
|
106
|
+
evidence_ids TEXT DEFAULT '[]', -- 本次附上的证据(JSON数组)
|
|
107
|
+
notes TEXT, -- 说明
|
|
108
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
/* ──────────────────────────────────────────
|
|
112
|
+
证据表 · 每一份上传的证明材料
|
|
113
|
+
────────────────────────────────────────── */
|
|
114
|
+
CREATE TABLE IF NOT EXISTS evidence (
|
|
115
|
+
id TEXT PRIMARY KEY, -- 格式:evt_xxxx
|
|
116
|
+
order_id TEXT NOT NULL REFERENCES orders(id),
|
|
117
|
+
uploader_id TEXT NOT NULL REFERENCES users(id),
|
|
118
|
+
type TEXT NOT NULL, -- photo / video / document / gps / signature
|
|
119
|
+
description TEXT NOT NULL, -- 这份证据证明什么
|
|
120
|
+
file_path TEXT, -- 本地存储路径(Phase 0)
|
|
121
|
+
file_hash TEXT, -- 文件内容的哈希(防篡改)
|
|
122
|
+
metadata TEXT DEFAULT '{}', -- 额外信息,如GPS坐标(JSON)
|
|
123
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
/* ──────────────────────────────────────────
|
|
127
|
+
争议表 · 出现纠纷时的记录
|
|
128
|
+
────────────────────────────────────────── */
|
|
129
|
+
CREATE TABLE IF NOT EXISTS disputes (
|
|
130
|
+
id TEXT PRIMARY KEY, -- 格式:dsp_xxxx
|
|
131
|
+
order_id TEXT NOT NULL REFERENCES orders(id),
|
|
132
|
+
initiator_id TEXT NOT NULL REFERENCES users(id), -- 谁发起的争议
|
|
133
|
+
reason TEXT NOT NULL, -- 争议原因
|
|
134
|
+
stake_deposit REAL DEFAULT 0, -- 发起方质押的保证金(防恶意争议)
|
|
135
|
+
|
|
136
|
+
status TEXT DEFAULT 'open', -- open / in_review / resolved / dismissed
|
|
137
|
+
assigned_arbitrators TEXT DEFAULT '[]', -- 分配到的仲裁员(JSON数组)
|
|
138
|
+
verdict TEXT, -- 裁定结果(JSON)
|
|
139
|
+
verdict_reason TEXT, -- 裁定理由
|
|
140
|
+
|
|
141
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
142
|
+
resolved_at TEXT
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
/* ──────────────────────────────────────────
|
|
146
|
+
钱包表 · 协议内的模拟资金
|
|
147
|
+
────────────────────────────────────────── */
|
|
148
|
+
CREATE TABLE IF NOT EXISTS wallets (
|
|
149
|
+
user_id TEXT PRIMARY KEY REFERENCES users(id),
|
|
150
|
+
balance REAL DEFAULT 0, -- 可用余额
|
|
151
|
+
staked REAL DEFAULT 0, -- 质押中(锁定不可用)
|
|
152
|
+
escrowed REAL DEFAULT 0, -- 托管中(交易进行时锁定)
|
|
153
|
+
earned REAL DEFAULT 0, -- 累计收益(统计用)
|
|
154
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
/* ──────────────────────────────────────────
|
|
158
|
+
收益分配记录
|
|
159
|
+
────────────────────────────────────────── */
|
|
160
|
+
CREATE TABLE IF NOT EXISTS payouts (
|
|
161
|
+
id TEXT PRIMARY KEY,
|
|
162
|
+
order_id TEXT NOT NULL REFERENCES orders(id),
|
|
163
|
+
recipient_id TEXT NOT NULL REFERENCES users(id),
|
|
164
|
+
role TEXT NOT NULL, -- 以什么角色获得这笔收益
|
|
165
|
+
amount REAL NOT NULL,
|
|
166
|
+
reason TEXT NOT NULL, -- seller_share / promoter_fee / logistics_fee / etc.
|
|
167
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
`);
|
|
171
|
+
console.log('✅ L0-1 数据库初始化完成:', DB_PATH);
|
|
172
|
+
return db;
|
|
173
|
+
}
|
|
174
|
+
// 生成唯一 ID 的工具函数
|
|
175
|
+
export function generateId(prefix) {
|
|
176
|
+
const timestamp = Date.now().toString(36);
|
|
177
|
+
const random = Math.random().toString(36).slice(2, 6);
|
|
178
|
+
return `${prefix}_${timestamp}${random}`;
|
|
179
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L0-2 · 状态机引擎
|
|
3
|
+
*
|
|
4
|
+
* 三个核心职责:
|
|
5
|
+
* 1. transition() — 执行状态转移(验证权限 + 记录历史)
|
|
6
|
+
* 2. checkTimeouts() — 扫描超时订单,自动判责
|
|
7
|
+
* 3. getStatus() — 查询订单当前状态和责任方
|
|
8
|
+
*/
|
|
9
|
+
import { generateId } from '../L0-1-database/schema.js';
|
|
10
|
+
import { VALID_TRANSITIONS, CURRENT_RESPONSIBLE } from './transitions.js';
|
|
11
|
+
// ─── 核心函数 ────────────────────────────────────────────────
|
|
12
|
+
/**
|
|
13
|
+
* 执行状态转移
|
|
14
|
+
* @param db 数据库连接
|
|
15
|
+
* @param orderId 订单ID
|
|
16
|
+
* @param toStatus 目标状态
|
|
17
|
+
* @param actorId 操作者用户ID
|
|
18
|
+
* @param evidenceIds 附上的证据ID列表
|
|
19
|
+
* @param notes 备注说明
|
|
20
|
+
*/
|
|
21
|
+
export function transition(db, orderId, toStatus, actorId, evidenceIds = [], notes = '') {
|
|
22
|
+
// 1. 读取订单和操作者
|
|
23
|
+
const order = db.prepare('SELECT * FROM orders WHERE id = ?').get(orderId);
|
|
24
|
+
if (!order)
|
|
25
|
+
return { success: false, error: `订单不存在:${orderId}` };
|
|
26
|
+
const actor = db.prepare('SELECT * FROM users WHERE id = ?').get(actorId);
|
|
27
|
+
if (!actor)
|
|
28
|
+
return { success: false, error: `用户不存在:${actorId}` };
|
|
29
|
+
const fromStatus = order.status;
|
|
30
|
+
// 2. 查找合法转移规则
|
|
31
|
+
const transitionKey = `${fromStatus}→${toStatus}`;
|
|
32
|
+
const rule = VALID_TRANSITIONS[transitionKey];
|
|
33
|
+
if (!rule) {
|
|
34
|
+
return {
|
|
35
|
+
success: false,
|
|
36
|
+
error: `非法状态转移:${fromStatus} → ${toStatus}(协议不允许此操作)`
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
// 3. 验证角色权限
|
|
40
|
+
if (!rule.allowedRoles.includes(actor.role)) {
|
|
41
|
+
return {
|
|
42
|
+
success: false,
|
|
43
|
+
error: `权限不足:${actor.role} 无法执行 ${fromStatus} → ${toStatus}。` +
|
|
44
|
+
`允许的角色:${rule.allowedRoles.join(', ')}`
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
// 4. 验证证据要求
|
|
48
|
+
if (rule.requiresEvidence && evidenceIds.length === 0) {
|
|
49
|
+
return {
|
|
50
|
+
success: false,
|
|
51
|
+
error: `此操作需要上传证据。提示:${rule.evidenceHint ?? '请上传相关证明文件'}`
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// 5. 执行转移(数据库事务,保证原子性)
|
|
55
|
+
const historyId = generateId('hist');
|
|
56
|
+
const execute = db.transaction(() => {
|
|
57
|
+
// 更新订单状态
|
|
58
|
+
db.prepare(`
|
|
59
|
+
UPDATE orders
|
|
60
|
+
SET status = ?, updated_at = datetime('now')
|
|
61
|
+
WHERE id = ?
|
|
62
|
+
`).run(toStatus, orderId);
|
|
63
|
+
// 记录状态历史(自举证的核心)
|
|
64
|
+
db.prepare(`
|
|
65
|
+
INSERT INTO order_state_history
|
|
66
|
+
(id, order_id, from_status, to_status, actor_id, actor_role, evidence_ids, notes)
|
|
67
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
68
|
+
`).run(historyId, orderId, fromStatus, toStatus, actorId, actor.role, JSON.stringify(evidenceIds), notes);
|
|
69
|
+
});
|
|
70
|
+
execute();
|
|
71
|
+
return { success: true, newStatus: toStatus, historyId };
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 扫描所有超时订单,自动判责
|
|
75
|
+
* 这个函数应该定期运行(如每分钟),是「协议自动执法」的实现
|
|
76
|
+
*/
|
|
77
|
+
export function checkTimeouts(db) {
|
|
78
|
+
const now = new Date().toISOString();
|
|
79
|
+
const details = [];
|
|
80
|
+
// 找出所有进行中的订单
|
|
81
|
+
const activeOrders = db.prepare(`
|
|
82
|
+
SELECT * FROM orders
|
|
83
|
+
WHERE status NOT IN ('completed', 'cancelled', 'fault_buyer', 'fault_seller', 'fault_logistics')
|
|
84
|
+
`).all();
|
|
85
|
+
for (const order of activeOrders) {
|
|
86
|
+
const transitionKey = findActiveDeadlineTransition(order, now);
|
|
87
|
+
if (!transitionKey)
|
|
88
|
+
continue;
|
|
89
|
+
const [, autoFaultState] = transitionKey;
|
|
90
|
+
const systemUser = getSystemUser(db);
|
|
91
|
+
// 系统自动触发判责状态
|
|
92
|
+
const result = transition(db, order.id, autoFaultState, systemUser.id, [], `系统自动判责:超过截止时间 ${new Date(now).toLocaleString()}`);
|
|
93
|
+
if (result.success) {
|
|
94
|
+
details.push({
|
|
95
|
+
orderId: order.id,
|
|
96
|
+
action: `${order.status} → ${autoFaultState}(超时自动判责)`
|
|
97
|
+
});
|
|
98
|
+
// 如果判责状态可以自动完成,继续执行
|
|
99
|
+
const completionKey = `${autoFaultState}→completed`;
|
|
100
|
+
if (VALID_TRANSITIONS[completionKey]) {
|
|
101
|
+
transition(db, order.id, 'completed', systemUser.id, [], '系统自动执行处置');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return { processed: details.length, details };
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* 查询订单的完整状态(含当前责任方、距截止时间)
|
|
109
|
+
*/
|
|
110
|
+
export function getOrderStatus(db, orderId) {
|
|
111
|
+
const order = db.prepare('SELECT * FROM orders WHERE id = ?').get(orderId);
|
|
112
|
+
if (!order)
|
|
113
|
+
return null;
|
|
114
|
+
const history = db.prepare(`
|
|
115
|
+
SELECT h.*, u.name as actor_name, u.role as actor_role_name
|
|
116
|
+
FROM order_state_history h
|
|
117
|
+
JOIN users u ON h.actor_id = u.id
|
|
118
|
+
WHERE h.order_id = ?
|
|
119
|
+
ORDER BY h.created_at ASC
|
|
120
|
+
`).all(orderId);
|
|
121
|
+
const currentResponsible = CURRENT_RESPONSIBLE[order.status] ?? null;
|
|
122
|
+
const activeDeadline = getActiveDeadline(order);
|
|
123
|
+
return {
|
|
124
|
+
order,
|
|
125
|
+
history,
|
|
126
|
+
currentResponsible,
|
|
127
|
+
activeDeadline,
|
|
128
|
+
isOverdue: activeDeadline ? new Date() > new Date(activeDeadline.deadline) : false
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// ─── 内部工具函数 ─────────────────────────────────────────────
|
|
132
|
+
/** 找出当前订单超时的转移(如果有) */
|
|
133
|
+
function findActiveDeadlineTransition(order, now) {
|
|
134
|
+
// 按当前状态找对应的截止时间规则
|
|
135
|
+
const relevantRules = Object.entries(VALID_TRANSITIONS).filter(([key, rule]) => key.startsWith(`${order.status}→`) &&
|
|
136
|
+
rule.deadlineField &&
|
|
137
|
+
rule.autoFaultState);
|
|
138
|
+
for (const [, rule] of relevantRules) {
|
|
139
|
+
const deadlineField = rule.deadlineField;
|
|
140
|
+
const deadline = order[deadlineField];
|
|
141
|
+
if (deadline && now > deadline && rule.autoFaultState) {
|
|
142
|
+
return [deadlineField, rule.autoFaultState];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
/** 获取当前有效的截止时间 */
|
|
148
|
+
function getActiveDeadline(order) {
|
|
149
|
+
const deadlineMap = {
|
|
150
|
+
created: 'pay_deadline',
|
|
151
|
+
paid: 'accept_deadline',
|
|
152
|
+
accepted: 'ship_deadline',
|
|
153
|
+
shipped: 'pickup_deadline',
|
|
154
|
+
in_transit: 'delivery_deadline',
|
|
155
|
+
delivered: 'confirm_deadline',
|
|
156
|
+
};
|
|
157
|
+
const field = deadlineMap[order.status];
|
|
158
|
+
if (!field)
|
|
159
|
+
return null;
|
|
160
|
+
const deadline = order[field];
|
|
161
|
+
if (!deadline)
|
|
162
|
+
return null;
|
|
163
|
+
return { field, deadline };
|
|
164
|
+
}
|
|
165
|
+
/** 获取或创建系统用户(用于自动触发),启动时调用一次 */
|
|
166
|
+
export function initSystemUser(db) {
|
|
167
|
+
return getSystemUser(db);
|
|
168
|
+
}
|
|
169
|
+
function getSystemUser(db) {
|
|
170
|
+
let sys = db.prepare("SELECT * FROM users WHERE id = 'sys_protocol'").get();
|
|
171
|
+
if (!sys) {
|
|
172
|
+
db.prepare(`
|
|
173
|
+
INSERT OR IGNORE INTO users (id, name, role, api_key)
|
|
174
|
+
VALUES ('sys_protocol', '协议系统', 'system', 'sys_internal_key')
|
|
175
|
+
`).run();
|
|
176
|
+
db.prepare(`
|
|
177
|
+
INSERT OR IGNORE INTO wallets (user_id, balance)
|
|
178
|
+
VALUES ('sys_protocol', 0)
|
|
179
|
+
`).run();
|
|
180
|
+
sys = db.prepare("SELECT * FROM users WHERE id = 'sys_protocol'").get();
|
|
181
|
+
}
|
|
182
|
+
return sys;
|
|
183
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L0-2 · 状态机:合法转移表
|
|
3
|
+
*
|
|
4
|
+
* 每一行定义:从哪个状态 → 到哪个状态,谁有权触发,需要什么证据,用哪个截止时间。
|
|
5
|
+
* 不在这张表里的转移,一律拒绝——这是「无歧义」设计的核心。
|
|
6
|
+
*/
|
|
7
|
+
// key 格式:'from_status→to_status'
|
|
8
|
+
export const VALID_TRANSITIONS = {
|
|
9
|
+
// ── 买家付款 ──────────────────────────────────────────────
|
|
10
|
+
'created→paid': {
|
|
11
|
+
allowedRoles: ['buyer'],
|
|
12
|
+
deadlineField: 'pay_deadline',
|
|
13
|
+
requiresEvidence: false,
|
|
14
|
+
autoFaultState: 'cancelled',
|
|
15
|
+
faultParty: 'buyer',
|
|
16
|
+
description: '买家完成付款,资金进入托管'
|
|
17
|
+
},
|
|
18
|
+
'created→cancelled': {
|
|
19
|
+
allowedRoles: ['buyer', 'seller', 'system'],
|
|
20
|
+
requiresEvidence: false,
|
|
21
|
+
description: '下单后付款前取消订单'
|
|
22
|
+
},
|
|
23
|
+
// ── 卖家接单 ──────────────────────────────────────────────
|
|
24
|
+
'paid→accepted': {
|
|
25
|
+
allowedRoles: ['seller'],
|
|
26
|
+
deadlineField: 'accept_deadline',
|
|
27
|
+
requiresEvidence: false,
|
|
28
|
+
autoFaultState: 'fault_seller',
|
|
29
|
+
faultParty: 'seller',
|
|
30
|
+
description: '卖家确认接单,承诺按时发货'
|
|
31
|
+
},
|
|
32
|
+
'paid→cancelled': {
|
|
33
|
+
allowedRoles: ['buyer'],
|
|
34
|
+
requiresEvidence: false,
|
|
35
|
+
description: '卖家接单前买家可取消(全额退款)'
|
|
36
|
+
},
|
|
37
|
+
// ── 卖家发货 ──────────────────────────────────────────────
|
|
38
|
+
'accepted→shipped': {
|
|
39
|
+
allowedRoles: ['seller'],
|
|
40
|
+
deadlineField: 'ship_deadline',
|
|
41
|
+
requiresEvidence: true,
|
|
42
|
+
evidenceHint: '上传:物流单号截图 + 包裹称重/外观照片',
|
|
43
|
+
autoFaultState: 'fault_seller',
|
|
44
|
+
faultParty: 'seller',
|
|
45
|
+
description: '卖家将包裹交给物流,提交发货证明'
|
|
46
|
+
},
|
|
47
|
+
// ── 物流揽收 ──────────────────────────────────────────────
|
|
48
|
+
'shipped→picked_up': {
|
|
49
|
+
allowedRoles: ['logistics'],
|
|
50
|
+
deadlineField: 'pickup_deadline',
|
|
51
|
+
requiresEvidence: true,
|
|
52
|
+
evidenceHint: '上传:揽收扫描记录 + 当前GPS位置',
|
|
53
|
+
autoFaultState: 'fault_logistics',
|
|
54
|
+
faultParty: 'logistics',
|
|
55
|
+
description: '物流方确认已揽收,包裹完整'
|
|
56
|
+
},
|
|
57
|
+
// ── 运输中更新 ─────────────────────────────────────────────
|
|
58
|
+
'picked_up→in_transit': {
|
|
59
|
+
allowedRoles: ['logistics', 'system'],
|
|
60
|
+
requiresEvidence: false,
|
|
61
|
+
description: '包裹开始运输(可自动触发)'
|
|
62
|
+
},
|
|
63
|
+
// ── 物流投递 ──────────────────────────────────────────────
|
|
64
|
+
'in_transit→delivered': {
|
|
65
|
+
allowedRoles: ['logistics'],
|
|
66
|
+
deadlineField: 'delivery_deadline',
|
|
67
|
+
requiresEvidence: true,
|
|
68
|
+
evidenceHint: '上传:投递照片(含门牌号)+ 收件人签收/GPS坐标',
|
|
69
|
+
autoFaultState: 'fault_logistics',
|
|
70
|
+
faultParty: 'logistics',
|
|
71
|
+
description: '物流确认投递完成,提交投递证明'
|
|
72
|
+
},
|
|
73
|
+
// ── 买家确认 ──────────────────────────────────────────────
|
|
74
|
+
'delivered→confirmed': {
|
|
75
|
+
allowedRoles: ['buyer', 'system'], // system = 超时自动确认
|
|
76
|
+
deadlineField: 'confirm_deadline',
|
|
77
|
+
requiresEvidence: false,
|
|
78
|
+
autoFaultState: 'confirmed', // 超时不是判责,而是自动确认
|
|
79
|
+
faultParty: 'system',
|
|
80
|
+
description: '买家确认收货,触发资金结算'
|
|
81
|
+
},
|
|
82
|
+
// ── 发起争议(任何阶段都可触发)──────────────────────────────
|
|
83
|
+
'paid→disputed': {
|
|
84
|
+
allowedRoles: ['buyer', 'seller'],
|
|
85
|
+
requiresEvidence: true,
|
|
86
|
+
evidenceHint: '描述问题并上传相关证据',
|
|
87
|
+
description: '资金托管后发现问题,发起争议'
|
|
88
|
+
},
|
|
89
|
+
'accepted→disputed': {
|
|
90
|
+
allowedRoles: ['buyer', 'seller'],
|
|
91
|
+
requiresEvidence: true,
|
|
92
|
+
evidenceHint: '描述问题并上传相关证据',
|
|
93
|
+
description: '卖家接单后发现问题'
|
|
94
|
+
},
|
|
95
|
+
'shipped→disputed': {
|
|
96
|
+
allowedRoles: ['buyer', 'seller', 'logistics'],
|
|
97
|
+
requiresEvidence: true,
|
|
98
|
+
evidenceHint: '描述问题并上传相关证据',
|
|
99
|
+
description: '发货后出现问题'
|
|
100
|
+
},
|
|
101
|
+
'picked_up→disputed': {
|
|
102
|
+
allowedRoles: ['buyer', 'seller', 'logistics'],
|
|
103
|
+
requiresEvidence: true,
|
|
104
|
+
evidenceHint: '描述问题并上传相关证据',
|
|
105
|
+
description: '揽收后出现问题(如包裹损毁)'
|
|
106
|
+
},
|
|
107
|
+
'in_transit→disputed': {
|
|
108
|
+
allowedRoles: ['buyer', 'seller', 'logistics'],
|
|
109
|
+
requiresEvidence: true,
|
|
110
|
+
evidenceHint: '描述问题并上传相关证据',
|
|
111
|
+
description: '运输中出现问题'
|
|
112
|
+
},
|
|
113
|
+
'delivered→disputed': {
|
|
114
|
+
allowedRoles: ['buyer'],
|
|
115
|
+
requiresEvidence: true,
|
|
116
|
+
evidenceHint: '上传:收到货物的照片 + 问题描述',
|
|
117
|
+
description: '买家收货后发现货不对版或货损'
|
|
118
|
+
},
|
|
119
|
+
// ── 仲裁结束 ──────────────────────────────────────────────
|
|
120
|
+
'disputed→completed': {
|
|
121
|
+
allowedRoles: ['arbitrator', 'system'], // system = 超时自动裁定
|
|
122
|
+
requiresEvidence: false,
|
|
123
|
+
evidenceHint: '上传仲裁裁定书',
|
|
124
|
+
description: '仲裁员完成裁定,释放资金给卖家'
|
|
125
|
+
},
|
|
126
|
+
'disputed→cancelled': {
|
|
127
|
+
allowedRoles: ['arbitrator', 'system'], // system = 超时自动裁定
|
|
128
|
+
requiresEvidence: false,
|
|
129
|
+
evidenceHint: '上传仲裁裁定书',
|
|
130
|
+
description: '仲裁裁定取消交易,全额退款给买家'
|
|
131
|
+
},
|
|
132
|
+
// ── 正常完成 ──────────────────────────────────────────────
|
|
133
|
+
'confirmed→completed': {
|
|
134
|
+
allowedRoles: ['system'],
|
|
135
|
+
requiresEvidence: false,
|
|
136
|
+
description: '买家确认后系统自动结算,交易完成'
|
|
137
|
+
},
|
|
138
|
+
// ── 超时自动判责转移(system 触发)────────────────────────────
|
|
139
|
+
// 这些转移不在正常操作流程里,只由 checkTimeouts 自动触发
|
|
140
|
+
'created→fault_buyer': {
|
|
141
|
+
allowedRoles: ['system'],
|
|
142
|
+
requiresEvidence: false,
|
|
143
|
+
description: '买家超时未付款,自动取消并标记违约'
|
|
144
|
+
},
|
|
145
|
+
'paid→fault_seller': {
|
|
146
|
+
allowedRoles: ['system'],
|
|
147
|
+
requiresEvidence: false,
|
|
148
|
+
description: '卖家超时未接单,自动退款并标记违约'
|
|
149
|
+
},
|
|
150
|
+
'accepted→fault_seller': {
|
|
151
|
+
allowedRoles: ['system'],
|
|
152
|
+
requiresEvidence: false,
|
|
153
|
+
description: '卖家超时未发货,自动退款并标记违约'
|
|
154
|
+
},
|
|
155
|
+
'shipped→fault_logistics': {
|
|
156
|
+
allowedRoles: ['system'],
|
|
157
|
+
requiresEvidence: false,
|
|
158
|
+
description: '物流超时未揽收,标记物流违约'
|
|
159
|
+
},
|
|
160
|
+
'picked_up→fault_logistics': {
|
|
161
|
+
allowedRoles: ['system'],
|
|
162
|
+
requiresEvidence: false,
|
|
163
|
+
description: '物流超时未投递,标记物流违约'
|
|
164
|
+
},
|
|
165
|
+
'in_transit→fault_logistics': {
|
|
166
|
+
allowedRoles: ['system'],
|
|
167
|
+
requiresEvidence: false,
|
|
168
|
+
description: '物流超时未投递,标记物流违约'
|
|
169
|
+
},
|
|
170
|
+
// ── 判责后的处置结算 ─────────────────────────────────────────
|
|
171
|
+
'fault_seller→completed': {
|
|
172
|
+
allowedRoles: ['system'],
|
|
173
|
+
requiresEvidence: false,
|
|
174
|
+
description: '卖家违约:退款买家,扣除卖家质押'
|
|
175
|
+
},
|
|
176
|
+
'fault_logistics→completed': {
|
|
177
|
+
allowedRoles: ['system'],
|
|
178
|
+
requiresEvidence: false,
|
|
179
|
+
description: '物流违约:从物流质押池赔付'
|
|
180
|
+
},
|
|
181
|
+
'fault_buyer→completed': {
|
|
182
|
+
allowedRoles: ['system'],
|
|
183
|
+
requiresEvidence: false,
|
|
184
|
+
description: '买家违约:资金转给卖家,扣除买家质押'
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
/** 给定当前状态,返回「当前应该由谁来操作」 */
|
|
188
|
+
export const CURRENT_RESPONSIBLE = {
|
|
189
|
+
created: 'buyer', // 等买家付款
|
|
190
|
+
paid: 'seller', // 等卖家接单
|
|
191
|
+
accepted: 'seller', // 等卖家发货
|
|
192
|
+
shipped: 'logistics', // 等物流揽收
|
|
193
|
+
picked_up: 'logistics', // 等物流投递
|
|
194
|
+
in_transit: 'logistics', // 等物流投递
|
|
195
|
+
delivered: 'buyer', // 等买家确认
|
|
196
|
+
disputed: 'arbitrator', // 等仲裁处理
|
|
197
|
+
};
|