-- ============================================================================ -- 新架构全配置化流程引擎 - 数据库表结构 -- 版本: v2.0 -- 说明: 新表与旧表共存,不修改现有表结构 -- 引擎通过 recipe_step 子表配置驱动,无硬编码 step_type -- ============================================================================ -- ========== 1. recipe / recipe_step 表 ========== -- 已由 ent schema 自动创建,无需重复建表 -- recipe 表: id, name, code, version, desc, isActive -- recipe_step.priority(调度优先级,数值越小越优先,默认 2)由 ent schema 生成。 -- 存量数据库升级时执行(幂等): -- ALTER TABLE recipe_step ADD COLUMN IF NOT EXISTS priority INT NOT NULL DEFAULT 2; -- ALTER TABLE recipe_step ADD COLUMN IF NOT EXISTS hold_until_handover BOOLEAN NOT NULL DEFAULT FALSE; -- ALTER TABLE recipe_step ADD COLUMN IF NOT EXISTS fallback_next_step VARCHAR(20) NOT NULL DEFAULT ''; -- fallback_next_step 双语义(两种场景互斥,不冲突): -- ① holding 步骤(如 OP20_WAIT 磨床等待加工)=挂起时无后继工件交接的收尾取料步骤; -- ② 取料步骤(如 OP50_UNLOAD 缓存取料)=被代做跳过(handover_proxy)时直接推进到的目标步骤。 -- ========== 2. recipe_step_resource(资源需求,引擎 ACQUIRE 阶段读取) ========== CREATE TABLE IF NOT EXISTS recipe_step_resource ( id SERIAL PRIMARY KEY, recipe_step_id INT NOT NULL REFERENCES recipe_step(id), resource_type VARCHAR(30) NOT NULL, -- 'robot' 机器人(互斥) -- 'temp_slot' 缓存槽(中间工件暂存) -- 'equipment_slot' 设备槽位(磨床/全检台/缓存台等) action VARCHAR(10) NOT NULL, -- 'acquire' 独占获取(SELECT FOR UPDATE),如获取机器人、空槽位 -- 'check' 非独占检查(SELECT FOR UPDATE 轻量锁),如检查至少 1 个磨床空槽位 equipment_type VARCHAR(50) DEFAULT '', -- 'GRINDER' | 'DOCK' | 'FULL_CHECK' | 'CACHE' | 'TURN_TABLE' | 'AIR_BLOW' resource_key VARCHAR(100) DEFAULT '', -- 指定槽位资源键:'{dock_slot}' 由引擎解析为 job 绑定的接驳台槽位(equipmentId:slotNo) -- 空 = 任意符合 expected_status 的槽位;仅 acquire 时生效,check 忽略 expected_status VARCHAR(20) NOT NULL, -- 'empty' | 'occupied' | 'done' | 'idle' | 'free' min_count INT DEFAULT 1, -- check 时最少数量 acquire_mode VARCHAR(20) NOT NULL DEFAULT 'self', -- 'self' 仅本 job 自持槽位(current_job_id IS NULL OR = 本 job)——常规取放料 -- 'any' 可跨 job 抢占任意槽位(换料:取前任工件磨好的 done 槽位,记录前任 job 交接) description TEXT DEFAULT '', created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), is_del BOOLEAN NOT NULL DEFAULT FALSE ); CREATE INDEX IF NOT EXISTS idx_recipe_step_resource_step ON recipe_step_resource(recipe_step_id) WHERE is_del = FALSE; -- ========== 4. recipe_step_signal(信号序列,引擎 EXECUTE/WAIT 阶段读取) ========== CREATE TABLE IF NOT EXISTS recipe_step_signal ( id SERIAL PRIMARY KEY, recipe_step_id INT NOT NULL REFERENCES recipe_step(id), signal_name VARCHAR(50) NOT NULL, -- 对应 signal 表的 name signal_role VARCHAR(10) NOT NULL, -- 'param' 参数信号:在 req 之前写入,如位置号、工件类型 -- 'req' 请求信号:发送给 PLC,引擎发送后立即返回 -- 'done' 完成信号:SignalRouter 异步监控,到达后触发 COMPLETE param_value_expr VARCHAR(100) DEFAULT '', -- 参数表达式,如 '{product_type}' sort_order INT DEFAULT 0, -- 发送顺序 wait_after_ms INT DEFAULT 0, -- 发送后等待毫秒数(0=不等待),用于 param 最后一条→req 之间 broadcast BOOLEAN NOT NULL DEFAULT FALSE, -- done 信号广播模式:到达时一次性唤醒全部等待步骤(批次信号,如 M2098 全检台批量检测完成) description TEXT DEFAULT '', created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), is_del BOOLEAN NOT NULL DEFAULT FALSE ); CREATE INDEX IF NOT EXISTS idx_recipe_step_signal_step ON recipe_step_signal(recipe_step_id) WHERE is_del = FALSE; -- ========== 5. recipe_step_action(后置操作,引擎 COMPLETE 阶段读取) ========== CREATE TABLE IF NOT EXISTS recipe_step_action ( id SERIAL PRIMARY KEY, recipe_step_id INT NOT NULL REFERENCES recipe_step(id), action_type VARCHAR(20) NOT NULL, -- 'release_resource' 释放资源(robot→idle, temp_slot→free) -- 'set_resource' 设置资源状态(equipment_slot→empty/occupied/done) -- 'send_signal' 发送额外触发信号(COMPLETE 阶段发送) -- 'jump_step' 跳转到指定步骤(用于 DECISION 步骤的分支跳转) resource_type VARCHAR(30) DEFAULT '', resource_key VARCHAR(100) DEFAULT '', -- '{allocated}' 引擎在 acquire 阶段解析为实际分配的资源标识 -- 'robot' 机器人 -- 'temp_slot' 缓存槽 target_status VARCHAR(20) DEFAULT '', -- 'idle' | 'free' | 'empty' | 'occupied' | 'done' signal_name VARCHAR(50) DEFAULT '', -- send_signal 时的信号名 jump_to_step_id VARCHAR(20) DEFAULT '', -- jump_step 时的目标 step_id sort_order INT DEFAULT 0, description TEXT DEFAULT '', created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), is_del BOOLEAN NOT NULL DEFAULT FALSE ); CREATE INDEX IF NOT EXISTS idx_recipe_step_action_step ON recipe_step_action(recipe_step_id) WHERE is_del = FALSE; -- ========== 6. recipe_step_decision(决策分支表) ========== CREATE TABLE IF NOT EXISTS recipe_step_decision ( id SERIAL PRIMARY KEY, recipe_step_id INT NOT NULL REFERENCES recipe_step(id), priority INT NOT NULL DEFAULT 0, -- 匹配优先级 condition_equipment_type VARCHAR(50) NOT NULL, -- 'FULL_CHECK' | 'CACHE' | 'GRINDER' | 'JOB'(虚拟:同工单剩余活跃件数) condition_status VARCHAR(20) NOT NULL, -- 'empty' | 'occupied' | 'done' | 'remaining'(JOB 专用) condition_operator VARCHAR(5) NOT NULL, -- '>=' | '==' | '<=' | '>' condition_value INT NOT NULL, target_next_step_id VARCHAR(20) NOT NULL, -- 匹配时的目标 step_id description TEXT DEFAULT '', created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), is_del BOOLEAN NOT NULL DEFAULT FALSE ); CREATE INDEX IF NOT EXISTS idx_recipe_step_decision_step ON recipe_step_decision(recipe_step_id) WHERE is_del = FALSE; -- ========== 7. resource_lock(资源锁表,引擎运行时状态) ========== CREATE TABLE IF NOT EXISTS resource_lock ( id SERIAL PRIMARY KEY, resource_type VARCHAR(30) NOT NULL, -- 'robot' | 'temp_slot' resource_key VARCHAR(100) NOT NULL, -- 'robot' | 'temp_slot' status VARCHAR(20) NOT NULL, -- 'idle' | 'busy' | 'free' | 'occupied' held_by_job_id INT DEFAULT 0, -- 持有锁的 job ID(0=空闲) held_at TIMESTAMP DEFAULT NULL, created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), is_del BOOLEAN NOT NULL DEFAULT FALSE ); CREATE UNIQUE INDEX IF NOT EXISTS idx_resource_lock_key ON resource_lock(resource_type, resource_key) WHERE is_del = FALSE; -- ========== 8. equipment_signal(设备信号映射表,逻辑名→物理地址) ========== CREATE TABLE IF NOT EXISTS equipment_signal ( id SERIAL PRIMARY KEY, equipment_id INT NOT NULL REFERENCES equipment(id), logical_name VARCHAR(50) NOT NULL, -- 'PlaceReq' | 'PlaceDone' | 'FetchReq' | 'FetchDone' | 'MachiningDone' signal_name VARCHAR(50) NOT NULL, -- 对应 signal 表的 name address VARCHAR(20) NOT NULL, -- PLC 地址 created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), is_del BOOLEAN NOT NULL DEFAULT FALSE ); CREATE INDEX IF NOT EXISTS idx_equipment_signal_eq ON equipment_signal(equipment_id) WHERE is_del = FALSE; -- ========== 9. job_step(job 运行时步骤快照) ========== CREATE TABLE IF NOT EXISTS job_step ( id SERIAL PRIMARY KEY, job_id INT NOT NULL, step_id VARCHAR(20) NOT NULL, -- 对应 recipe_step.step_id step_name VARCHAR(100) NOT NULL, step_index INT NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'pending', -- 'pending' 等待资源获取 -- 'executing' 正在发送信号 -- 'waiting_done' 等待 PLC Done 信号 -- 'completed' 步骤完成 -- 'holding' 完成但挂起等待交接(换料:磨完等下一件来交接) next_step_id VARCHAR(20) DEFAULT '', -- 快照时的下一步骤 ID allocated_equipment_id INT DEFAULT 0, allocated_slot_no INT DEFAULT 0, attempt_count INT DEFAULT 0, done_signal_received BOOLEAN NOT NULL DEFAULT FALSE, hold_until_handover BOOLEAN NOT NULL DEFAULT FALSE, -- 步骤完成后挂起等待交接(复制自 recipe_step) fallback_next_step VARCHAR(20) NOT NULL DEFAULT '', -- 双语义(复制自 recipe_step):①holding 步骤=挂起无后继交接的收尾步骤;②取料步骤=被代做跳过(handover_proxy)后的推进目标 prev_job_id INT NOT NULL DEFAULT 0, -- acquire_mode='any' 取前任工件槽位时记录的前任 job proxy_job_id INT NOT NULL DEFAULT 0, -- acquire_mode='proxy' 代取其他 job 工件时记录的被代做 job(handover_proxy 用) started_at TIMESTAMPTZ DEFAULT NULL, -- 进入 waiting_done 的时间(超时检测用,带时区避免 NOW()-started_at 时区偏差) timeout_seconds INT DEFAULT -1, -- 超时秒数(-1=一致监视+轮询兜底不超时,0=不超时,>0=超时秒数) created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), is_del BOOLEAN NOT NULL DEFAULT FALSE ); -- 存量数据库升级(幂等):表已存在时补齐缺失列(首次建表时 CREATE 已含该列,此语句自动跳过) ALTER TABLE job_step ADD COLUMN IF NOT EXISTS proxy_job_id INT NOT NULL DEFAULT 0; CREATE INDEX IF NOT EXISTS idx_job_step_job ON job_step(job_id) WHERE is_del = FALSE; -- ========== 9a. job_step_resource(job 运行时资源快照) ========== CREATE TABLE IF NOT EXISTS job_step_resource ( id SERIAL PRIMARY KEY, job_step_id INT NOT NULL REFERENCES job_step(id), resource_type VARCHAR(30) NOT NULL, action VARCHAR(10) NOT NULL, equipment_type VARCHAR(50) DEFAULT '', resource_key VARCHAR(100) DEFAULT '', expected_status VARCHAR(20) NOT NULL, min_count INT DEFAULT 1, acquire_mode VARCHAR(20) NOT NULL DEFAULT 'self', description TEXT DEFAULT '', created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), is_del BOOLEAN NOT NULL DEFAULT FALSE ); CREATE INDEX IF NOT EXISTS idx_job_step_resource_step ON job_step_resource(job_step_id) WHERE is_del = FALSE; -- ========== 9b. job_step_signal(job 运行时信号快照) ========== CREATE TABLE IF NOT EXISTS job_step_signal ( id SERIAL PRIMARY KEY, job_step_id INT NOT NULL REFERENCES job_step(id), signal_name VARCHAR(50) NOT NULL, signal_role VARCHAR(10) NOT NULL, param_value_expr VARCHAR(100) DEFAULT '', sort_order INT DEFAULT 0, wait_after_ms INT DEFAULT 0, -- 发送后等待毫秒数 broadcast BOOLEAN NOT NULL DEFAULT FALSE, -- 复制自 recipe_step_signal(done 广播模式) description TEXT DEFAULT '', created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), is_del BOOLEAN NOT NULL DEFAULT FALSE ); CREATE INDEX IF NOT EXISTS idx_job_step_signal_step ON job_step_signal(job_step_id) WHERE is_del = FALSE; -- ========== 9c. job_step_action(job 运行时后置操作快照) ========== CREATE TABLE IF NOT EXISTS job_step_action ( id SERIAL PRIMARY KEY, job_step_id INT NOT NULL REFERENCES job_step(id), action_type VARCHAR(20) NOT NULL, resource_type VARCHAR(30) DEFAULT '', resource_key VARCHAR(100) DEFAULT '', target_status VARCHAR(20) DEFAULT '', signal_name VARCHAR(50) DEFAULT '', jump_to_step_id VARCHAR(20) DEFAULT '', sort_order INT DEFAULT 0, description TEXT DEFAULT '', created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), is_del BOOLEAN NOT NULL DEFAULT FALSE ); CREATE INDEX IF NOT EXISTS idx_job_step_action_step ON job_step_action(job_step_id) WHERE is_del = FALSE; -- ========== 9d. job_step_decision(job 运行时决策快照) ========== CREATE TABLE IF NOT EXISTS job_step_decision ( id SERIAL PRIMARY KEY, job_step_id INT NOT NULL REFERENCES job_step(id), priority INT NOT NULL DEFAULT 0, condition_equipment_type VARCHAR(50) NOT NULL, condition_status VARCHAR(20) NOT NULL, condition_operator VARCHAR(5) NOT NULL, condition_value INT NOT NULL, target_next_step_id VARCHAR(20) NOT NULL, description TEXT DEFAULT '', created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), is_del BOOLEAN NOT NULL DEFAULT FALSE ); CREATE INDEX IF NOT EXISTS idx_job_step_decision_step ON job_step_decision(job_step_id) WHERE is_del = FALSE; -- ========== 10/11. 引擎运行时种子数据(resource_lock / equipment_signal) ========== -- 已迁移至 doc/new_recipe.sql(末尾"引擎运行时种子数据"节): -- resource_lock/equipment_signal 的 INSERT 依赖 equipment 表数据存在(equipment_signal 外键), -- 而 equipment 由 doc/data.sql 在 seed 阶段导入;reset-all 执行顺序为 -- ent 建表 → 本文件(纯结构) → data.sql → new_recipe.sql(含种子数据),INSERT 必须放在 seed 之后。 -- ========== 12. robot_task_priority(机器人任务优先级配置表) ========== -- 组合优先级:task_kind + from_type + to_type → priority(数值越小越优先), -- 产线调度策略的唯一配置源,与配方解耦。from_type/to_type 为搬运源/目标设备类型码, -- NULL 表示通配;查找顺序(最具体优先):精确 (kind,from,to) → (kind,from) → (kind,to) → kind-only → 内置。 -- 组合语义:一条搬运路径(如换向台→磨床链的 UNLOAD+TURN_TABLE=5)是否优先由三要素 -- 共同决定,任何设备组合可单独配置,无需发明新任务类型。 -- 缺省值与代码内置默认一致(EXCHANGE=0 / UNLOAD+TURN_TABLE=5 / LOAD=UNLOAD=WASH=10 / -- RELOAD=20 / TRANSFER=30),种子 INSERT 见 doc/new_recipe.sql 末尾;line 装配层启动时 -- 亦会幂等自举建表+种子(含存量库 ALTER 补列与旧索引替换)。 CREATE TABLE IF NOT EXISTS robot_task_priority ( id SERIAL PRIMARY KEY, task_kind VARCHAR(30) NOT NULL, -- EXCHANGE/LOAD/UNLOAD/WASH/RELOAD/TRANSFER from_type VARCHAR(30), -- 来源设备类型码,NULL=通配 to_type VARCHAR(30), -- 目标设备类型码,NULL=通配 priority INT NOT NULL, -- 数值越小越优先 created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), is_del BOOLEAN NOT NULL DEFAULT FALSE ); ALTER TABLE robot_task_priority ADD COLUMN IF NOT EXISTS from_type VARCHAR(30); ALTER TABLE robot_task_priority ADD COLUMN IF NOT EXISTS to_type VARCHAR(30); DROP INDEX IF EXISTS idx_robot_task_priority_kind; CREATE UNIQUE INDEX IF NOT EXISTS idx_robot_task_priority_combo ON robot_task_priority(task_kind, COALESCE(from_type, ''), COALESCE(to_type, '')) WHERE is_del = FALSE;