Eryxon MES Database Schema
This document provides a comprehensive overview of the database schema for AI agents working with the Eryxon Manufacturing Execution System.
Table of Contents
Section titled “Table of Contents”- Core Concepts
- Entity Relationship Diagram
- Core Tables
- ERP Sync Fields
- Status Enums
- Key Functions
- Integration Tables
- Useful Queries
- MCP Tool Interactions
Core Concepts
Section titled “Core Concepts”Multi-Tenancy
Section titled “Multi-Tenancy”All tables include a tenant_id column referencing the tenants table. Row-Level Security (RLS) policies enforce tenant isolation. Always include tenant_id in queries.
Soft Delete
Section titled “Soft Delete”Core tables support soft delete via:
deleted_at- Timestamp when record was deleted (NULL = active)deleted_by- UUID of user who performed deletion
External ID Tracking (ERP Sync)
Section titled “External ID Tracking (ERP Sync)”Tables supporting ERP integration include:
external_id- Unique identifier from external systemexternal_source- Source system name (e.g., ‘SAP’, ‘NetSuite’)synced_at- Last sync timestampsync_hash- MD5 hash for change detection
Entity Relationship Diagram
Section titled “Entity Relationship Diagram”┌──────────────┐│ tenants │└──────┬───────┘ │ │ 1:N ▼┌──────────────┐│ jobs │└──────┬───────┘ │ │ 1:N ▼┌──────────────┐ ┌──────────────┐│ parts │────►│ assignments │└──────┬───────┘ └──────────────┘ │ │ 1:N ▼┌──────────────┐ ┌──────────────┐ ┌──────────────┐│ operations │────►│ issues │ │ time_entries│└──────┬───────┘ └──────────────┘ └──────────────┘ │ │ N:N ▼┌──────────────┐ ┌──────────────┐│ resources │◄────│operation_res │└──────────────┘ └──────────────┘
┌──────────────┐│ cells │ (Work Centers / Stages)└──────────────┘Core Tables
Section titled “Core Tables”tenants
Section titled “tenants”Root table for multi-tenant isolation. Contains subscription and billing info.
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
name | TEXT | Tenant name |
company_name | TEXT | Company display name |
plan | subscription_plan | free, pro, premium, enterprise |
status | subscription_status | active, cancelled, suspended, trial |
max_jobs | INTEGER | Job limit for plan |
max_parts_per_month | INTEGER | Monthly parts limit |
current_jobs | INTEGER | Current job count |
current_parts_this_month | INTEGER | Parts created this month |
demo_mode_enabled | BOOLEAN | Demo data active |
timezone | TEXT | Factory timezone |
factory_opening_time | TIME | Daily start time |
factory_closing_time | TIME | Daily end time |
Sales orders / production orders. Parent of parts.
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
tenant_id | UUID | FK to tenants |
job_number | TEXT | Unique job identifier |
customer | TEXT | Customer name |
status | job_status | not_started, in_progress, completed, on_hold |
due_date | TIMESTAMPTZ | Original due date |
due_date_override | TIMESTAMPTZ | Manual override |
current_cell_id | UUID | FK to cells (current work center) |
notes | TEXT | Job notes |
metadata | JSONB | Custom fields |
external_id | TEXT | ERP external ID |
external_source | TEXT | ERP source system |
sync_hash | TEXT | Change detection hash |
synced_at | TIMESTAMPTZ | Last sync time |
deleted_at | TIMESTAMPTZ | Soft delete timestamp |
Indexes:
idx_jobs_external_sync- Unique on (tenant_id, external_source, external_id) WHERE external_id IS NOT NULLidx_jobs_active- Partial on (tenant_id, created_at DESC) WHERE deleted_at IS NULL
Work orders / line items within jobs. Parent of operations.
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
tenant_id | UUID | FK to tenants |
job_id | UUID | FK to jobs |
part_number | TEXT | Part identifier |
material | TEXT | Material type |
quantity | INTEGER | Order quantity |
status | job_status | not_started, in_progress, completed, on_hold |
current_cell_id | UUID | FK to cells |
drawing_no | TEXT | Drawing reference |
cnc_program_name | TEXT | CNC program reference |
parent_part_id | UUID | Self-reference for sub-assemblies |
length_mm | NUMERIC | Dimensions |
width_mm | NUMERIC | Dimensions |
height_mm | NUMERIC | Dimensions |
weight_kg | NUMERIC | Weight |
material_lot | TEXT | Material traceability |
material_supplier | TEXT | Supplier info |
material_cert_number | TEXT | Certificate number |
image_paths | TEXT[] | Part images |
file_paths | TEXT[] | Attached files |
is_bullet_card | BOOLEAN | Bullet card flag |
external_id | TEXT | ERP external ID |
external_source | TEXT | ERP source system |
sync_hash | TEXT | Change detection hash |
synced_at | TIMESTAMPTZ | Last sync time |
deleted_at | TIMESTAMPTZ | Soft delete timestamp |
Indexes:
idx_parts_external_sync- Unique on (tenant_id, external_source, external_id) WHERE external_id IS NOT NULLidx_parts_active- Partial on (tenant_id, job_id) WHERE deleted_at IS NULL
operations
Section titled “operations”Routing steps / manufacturing operations within parts.
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
tenant_id | UUID | FK to tenants |
part_id | UUID | FK to parts |
cell_id | UUID | FK to cells (work center) |
operation_name | TEXT | Operation description |
sequence | INTEGER | Order in routing |
status | task_status | not_started, in_progress, completed, on_hold |
estimated_time | NUMERIC | Estimated hours |
actual_time | NUMERIC | Recorded hours |
setup_time | NUMERIC | Setup time in hours |
run_time_per_unit | NUMERIC | Per-piece runtime |
changeover_time | NUMERIC | Changeover time |
wait_time | NUMERIC | Queue time |
planned_start | TIMESTAMPTZ | Scheduled start |
planned_end | TIMESTAMPTZ | Scheduled end |
completed_at | TIMESTAMPTZ | Actual completion |
completion_percentage | INTEGER | Progress 0-100 |
assigned_operator_id | UUID | FK to profiles |
icon_name | TEXT | Display icon |
external_id | TEXT | ERP external ID |
external_source | TEXT | ERP source system |
synced_at | TIMESTAMPTZ | Last sync time |
deleted_at | TIMESTAMPTZ | Soft delete timestamp |
Indexes:
idx_operations_external_sync- Unique on (tenant_id, external_source, external_id) WHERE external_id IS NOT NULLidx_operations_active- Partial on (tenant_id, part_id) WHERE deleted_at IS NULL
Work centers / manufacturing stages. Operations are performed at cells.
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
tenant_id | UUID | FK to tenants |
name | TEXT | Cell/stage name |
description | TEXT | Description |
sequence | INTEGER | Display order |
color | TEXT | Display color |
icon_name | TEXT | Display icon |
image_url | TEXT | Cell image |
active | BOOLEAN | Is active |
wip_limit | INTEGER | Work-in-progress limit |
wip_warning_threshold | INTEGER | Warning threshold |
enforce_wip_limit | BOOLEAN | Block at limit |
show_capacity_warning | BOOLEAN | Show warnings |
capacity_hours_per_day | NUMERIC | Daily capacity |
external_id | TEXT | ERP external ID |
external_source | TEXT | ERP source system |
synced_at | TIMESTAMPTZ | Last sync time |
deleted_at | TIMESTAMPTZ | Soft delete timestamp |
resources
Section titled “resources”Equipment, tooling, and other resources.
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
tenant_id | UUID | FK to tenants |
name | TEXT | Resource name |
type | TEXT | Resource type (equipment, tool, fixture, etc.) |
identifier | TEXT | Asset tag / serial number |
description | TEXT | Description |
location | TEXT | Physical location |
status | TEXT | available, in_use, maintenance, etc. |
active | BOOLEAN | Is active |
metadata | JSONB | Custom fields |
external_id | TEXT | ERP external ID |
external_source | TEXT | ERP source system |
synced_at | TIMESTAMPTZ | Last sync time |
deleted_at | TIMESTAMPTZ | Soft delete timestamp |
operation_resources
Section titled “operation_resources”Many-to-many link between operations and resources.
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
operation_id | UUID | FK to operations |
resource_id | UUID | FK to resources |
quantity | INTEGER | Quantity required |
notes | TEXT | Notes |
profiles
Section titled “profiles”User accounts. Linked to Supabase auth.users.
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key (matches auth.users.id) |
tenant_id | UUID | FK to tenants |
email | TEXT | Email address |
username | TEXT | Display username |
full_name | TEXT | Full name |
role | app_role | operator, admin |
active | BOOLEAN | Is active |
employee_id | TEXT | Employee ID for operators |
pin_hash | TEXT | Hashed PIN for terminal login |
has_email_login | BOOLEAN | Can login via email |
is_machine | BOOLEAN | Machine account flag |
is_root_admin | BOOLEAN | Root admin flag |
onboarding_completed | BOOLEAN | Completed onboarding |
ERP Sync Fields
Section titled “ERP Sync Fields”Sync Columns on Core Tables
Section titled “Sync Columns on Core Tables”| Table | Columns |
|---|---|
| jobs | external_id, external_source, synced_at, sync_hash |
| parts | external_id, external_source, synced_at, sync_hash |
| operations | external_id, external_source, synced_at |
| resources | external_id, external_source, synced_at |
| cells | external_id, external_source, synced_at |
sync_imports Table
Section titled “sync_imports Table”Tracks batch import operations for audit.
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
tenant_id | UUID | FK to tenants |
source | TEXT | ’csv’, ‘api’, ‘erp_sap’, etc. |
entity_type | TEXT | ’jobs’, ‘parts’, ‘operations’, etc. |
status | TEXT | pending, processing, completed, failed |
total_records | INTEGER | Total records in batch |
created_count | INTEGER | New records created |
updated_count | INTEGER | Existing records updated |
skipped_count | INTEGER | Unchanged records skipped |
error_count | INTEGER | Records with errors |
errors | JSONB | Error details array |
file_name | TEXT | Original filename for CSV |
started_at | TIMESTAMPTZ | Processing start |
completed_at | TIMESTAMPTZ | Processing end |
created_by | UUID | User who initiated |
Sync Hash Function
Section titled “Sync Hash Function”-- Generate MD5 hash for change detectionSELECT generate_sync_hash('{"job_number": "J001", "customer": "ACME"}'::jsonb);Status Enums
Section titled “Status Enums”job_status / task_status
Section titled “job_status / task_status”'not_started' | 'in_progress' | 'completed' | 'on_hold'issue_status
Section titled “issue_status”'pending' | 'approved' | 'rejected' | 'closed'issue_severity
Section titled “issue_severity”'low' | 'medium' | 'high' | 'critical'app_role
Section titled “app_role”'operator' | 'admin'subscription_plan
Section titled “subscription_plan”'free' | 'pro' | 'premium' | 'enterprise'subscription_status
Section titled “subscription_status”'active' | 'cancelled' | 'suspended' | 'trial'Key Functions
Section titled “Key Functions”Tenant Context
Section titled “Tenant Context”-- Get current user's tenant ID (from JWT)SELECT get_user_tenant_id();
-- Get current user's roleSELECT get_user_role();
-- Check if user is root adminSELECT is_root_admin();Quota Functions
Section titled “Quota Functions”-- Check if can create a jobSELECT can_create_job(p_tenant_id);
-- Check if can create partsSELECT can_create_parts(p_tenant_id, p_quantity);
-- Get tenant quota infoSELECT * FROM get_tenant_quota(p_tenant_id);Activity Logging
Section titled “Activity Logging”-- Log activity and dispatch webhooksSELECT log_activity_and_webhook( p_tenant_id, p_user_id, p_action, -- 'create', 'update', 'delete' p_entity_type, -- 'job', 'part', 'operation' p_entity_id, p_entity_name, p_description, p_changes, -- JSONB of changes p_metadata -- Additional metadata);Sync Hash Generation
Section titled “Sync Hash Generation”-- Generate hash for change detectionSELECT generate_sync_hash( '{"job_number": "J001", "customer": "ACME"}'::jsonb);Integration Tables
Section titled “Integration Tables”api_keys
Section titled “api_keys”REST API authentication keys.
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
tenant_id | UUID | FK to tenants |
name | TEXT | Key name |
key_hash | TEXT | bcrypt hash of full key |
key_prefix | TEXT | First 8 chars for lookup |
active | BOOLEAN | Is active |
created_by | UUID | User who created |
last_used_at | TIMESTAMPTZ | Last usage time |
webhooks
Section titled “webhooks”Outbound webhook configurations.
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
tenant_id | UUID | FK to tenants |
url | TEXT | Webhook endpoint URL |
secret_key | TEXT | HMAC signing key |
events | TEXT[] | Subscribed events |
active | BOOLEAN | Is active |
Webhook Events:
job.created,job.updated,job.deletedpart.created,part.updated,part.deletedoperation.started,operation.completedissue.created,issue.resolvedsync.jobs.completed,sync.parts.completed,sync.batch.completed
mqtt_publishers
Section titled “mqtt_publishers”MQTT publisher configurations for industrial integration.
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
tenant_id | UUID | FK to tenants |
name | TEXT | Publisher name |
broker_url | TEXT | MQTT broker URL |
port | INTEGER | Broker port (default 1883) |
username | TEXT | Auth username |
password | TEXT | Auth password |
use_tls | BOOLEAN | Use TLS/SSL |
topic_pattern | TEXT | Topic pattern with placeholders |
events | TEXT[] | Subscribed events |
active | BOOLEAN | Is active |
default_enterprise | TEXT | ISA-95 enterprise |
default_site | TEXT | ISA-95 site |
default_area | TEXT | ISA-95 area |
mcp_authentication_keys
Section titled “mcp_authentication_keys”MCP server authentication keys.
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
tenant_id | UUID | FK to tenants |
name | TEXT | Key name |
key_hash | TEXT | bcrypt hash |
key_prefix | TEXT | Lookup prefix |
environment | TEXT | development, staging, production |
enabled | BOOLEAN | Is enabled |
allowed_tools | JSONB | Tool whitelist (null = all) |
rate_limit | INTEGER | Requests per minute |
usage_count | INTEGER | Total usage count |
Useful Queries
Section titled “Useful Queries”Get Active Jobs with Parts Count
Section titled “Get Active Jobs with Parts Count”SELECT j.id, j.job_number, j.customer, j.status, j.due_date, COUNT(p.id) as parts_count, SUM(CASE WHEN p.status = 'completed' THEN 1 ELSE 0 END) as completed_partsFROM jobs jLEFT JOIN parts p ON j.id = p.job_id AND p.deleted_at IS NULLWHERE j.tenant_id = :tenant_id AND j.deleted_at IS NULLGROUP BY j.idORDER BY j.due_date;Get Part Routing (Operations)
Section titled “Get Part Routing (Operations)”SELECT o.id, o.sequence, o.operation_name, o.status, c.name as cell_name, o.estimated_time, o.actual_time, p.full_name as assigned_operatorFROM operations oJOIN cells c ON o.cell_id = c.idLEFT JOIN profiles p ON o.assigned_operator_id = p.idWHERE o.part_id = :part_id AND o.deleted_at IS NULLORDER BY o.sequence;Look Up by External ID
Section titled “Look Up by External ID”-- Find job by external IDSELECT * FROM jobsWHERE tenant_id = :tenant_id AND external_source = :source AND external_id = :external_id AND deleted_at IS NULL;
-- Find part by external IDSELECT * FROM partsWHERE tenant_id = :tenant_id AND external_source = :source AND external_id = :external_id AND deleted_at IS NULL;Get Cell WIP Count
Section titled “Get Cell WIP Count”SELECT get_cell_wip_count(:cell_id, :tenant_id);Get Operations in Cell
Section titled “Get Operations in Cell”SELECT o.*, p.part_number, j.job_number, j.customerFROM operations oJOIN parts p ON o.part_id = p.idJOIN jobs j ON p.job_id = j.idWHERE o.cell_id = :cell_id AND o.tenant_id = :tenant_id AND o.status IN ('not_started', 'in_progress') AND o.deleted_at IS NULLORDER BY j.due_date, o.sequence;Check Sync Status
Section titled “Check Sync Status”SELECT entity_type, status, total_records, created_count, updated_count, skipped_count, error_count, completed_atFROM sync_importsWHERE tenant_id = :tenant_idORDER BY created_at DESCLIMIT 10;MCP Tool Interactions
Section titled “MCP Tool Interactions”jobs Module
Section titled “jobs Module”| Tool | Tables Used |
|---|---|
list_jobs | jobs, parts |
get_job_details | jobs, parts, operations |
create_job | jobs |
update_job_status | jobs |
parts Module
Section titled “parts Module”| Tool | Tables Used |
|---|---|
list_parts | parts, jobs, operations |
get_part_details | parts, operations, cells |
create_part | parts, jobs |
update_part_status | parts |
operations Module
Section titled “operations Module”| Tool | Tables Used |
|---|---|
list_operations | operations, parts, cells |
start_operation | operations, time_entries |
complete_operation | operations, time_entries |
get_operation_details | operations, substeps, resources |
erp_sync Module
Section titled “erp_sync Module”| Tool | Tables Used |
|---|---|
erp_sync_diff | jobs, parts, resources |
erp_sync_execute | jobs, parts, resources, sync_imports |
erp_lookup_external_id | jobs, parts, operations, resources |
erp_sync_status | sync_imports |
erp_batch_lookup | jobs, parts, resources |
erp_resolve_ids | jobs, parts, cells |
Best Practices
Section titled “Best Practices”Always Include tenant_id
Section titled “Always Include tenant_id”-- GoodSELECT * FROM jobs WHERE tenant_id = :tenant_id AND id = :job_id;
-- Bad - RLS will filter but explicit is betterSELECT * FROM jobs WHERE id = :job_id;Check Soft Deletes
Section titled “Check Soft Deletes”-- Good - exclude deleted recordsSELECT * FROM jobs WHERE deleted_at IS NULL;
-- Explicit include for admin viewsSELECT * FROM jobs WHERE deleted_at IS NOT NULL;Use Sync Hash for Updates
Section titled “Use Sync Hash for Updates”-- Calculate hash before updateWITH new_hash AS ( SELECT generate_sync_hash(:payload::jsonb) as hash)UPDATE jobsSET customer = :customer, sync_hash = (SELECT hash FROM new_hash), synced_at = NOW()WHERE id = :id AND (sync_hash IS NULL OR sync_hash != (SELECT hash FROM new_hash));Batch Operations with Prefetch
Section titled “Batch Operations with Prefetch”-- Prefetch existing records by external_idSELECT id, external_id, sync_hashFROM jobsWHERE tenant_id = :tenant_id AND external_source = :source AND external_id = ANY(:external_ids) AND deleted_at IS NULL;Schema Version
Section titled “Schema Version”Last updated: 2024-12-06
Migration: 20251204000000_add_erp_sync_columns.sql