Skip to content

Eryxon MES Database Schema

This document provides a comprehensive overview of the database schema for AI agents working with the Eryxon Manufacturing Execution System.



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.

Core tables support soft delete via:

  • deleted_at - Timestamp when record was deleted (NULL = active)
  • deleted_by - UUID of user who performed deletion

Tables supporting ERP integration include:

  • external_id - Unique identifier from external system
  • external_source - Source system name (e.g., ‘SAP’, ‘NetSuite’)
  • synced_at - Last sync timestamp
  • sync_hash - MD5 hash for change detection

┌──────────────┐
│ tenants │
└──────┬───────┘
│ 1:N
┌──────────────┐ ┌──────────────┐
│ jobs │────►│ shipments │
└──────┬───────┘ └──────────────┘
│ 1:N
┌──────────────┐ ┌──────────────┐
│ parts │────►│ assignments │
└──────┬───────┘ └──────────────┘
│ 1:N
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ operations │────►│ issues │ │ time_entries│
└──────┬───────┘ └──────────────┘ └──────────────┘
│ N:N
┌──────────────┐ ┌──────────────┐
│ resources │◄────│operation_res │
└──────────────┘ └──────────────┘
┌──────────────┐
│ cells │ (Work Centers / Stages)
└──────────────┘

Root table for multi-tenant isolation. Contains subscription and billing info.

ColumnTypeDescription
idUUIDPrimary key
nameTEXTTenant name
company_nameTEXTCompany display name
plansubscription_planfree, pro, premium, enterprise
statussubscription_statusactive, cancelled, suspended, trial
max_jobsINTEGERJob limit for plan
max_parts_per_monthINTEGERMonthly parts limit
current_jobsINTEGERCurrent job count
current_parts_this_monthINTEGERParts created this month
demo_mode_enabledBOOLEANDemo data active
timezoneTEXTFactory timezone
factory_opening_timeTIMEDaily start time
factory_closing_timeTIMEDaily end time

Sales orders / production orders. Parent of parts.

ColumnTypeDescription
idUUIDPrimary key
tenant_idUUIDFK to tenants
job_numberTEXTUnique job identifier
customerTEXTCustomer name
statusjob_statusnot_started, in_progress, completed, on_hold
due_dateTIMESTAMPTZOriginal due date
due_date_overrideTIMESTAMPTZManual override
current_cell_idUUIDFK to cells (current work center)
notesTEXTJob notes
metadataJSONBCustom fields
external_idTEXTERP external ID
external_sourceTEXTERP source system
sync_hashTEXTChange detection hash
synced_atTIMESTAMPTZLast sync time
deleted_atTIMESTAMPTZSoft delete timestamp

Indexes:

  • idx_jobs_external_sync - Unique on (tenant_id, external_source, external_id) WHERE external_id IS NOT NULL
  • idx_jobs_active - Partial on (tenant_id, created_at DESC) WHERE deleted_at IS NULL

Work orders / line items within jobs. Parent of operations.

ColumnTypeDescription
idUUIDPrimary key
tenant_idUUIDFK to tenants
job_idUUIDFK to jobs
part_numberTEXTPart identifier
materialTEXTMaterial type
quantityINTEGEROrder quantity
statusjob_statusnot_started, in_progress, completed, on_hold
current_cell_idUUIDFK to cells
drawing_noTEXTDrawing reference
cnc_program_nameTEXTCNC program reference
parent_part_idUUIDSelf-reference for sub-assemblies
length_mmNUMERICDimensions
width_mmNUMERICDimensions
height_mmNUMERICDimensions
weight_kgNUMERICWeight
material_lotTEXTMaterial traceability
material_supplierTEXTSupplier info
material_cert_numberTEXTCertificate number
image_pathsTEXT[]Part images
file_pathsTEXT[]Attached files
is_bullet_cardBOOLEANBullet card flag
external_idTEXTERP external ID
external_sourceTEXTERP source system
sync_hashTEXTChange detection hash
synced_atTIMESTAMPTZLast sync time
deleted_atTIMESTAMPTZSoft delete timestamp

Indexes:

  • idx_parts_external_sync - Unique on (tenant_id, external_source, external_id) WHERE external_id IS NOT NULL
  • idx_parts_active - Partial on (tenant_id, job_id) WHERE deleted_at IS NULL

Routing steps / manufacturing operations within parts.

ColumnTypeDescription
idUUIDPrimary key
tenant_idUUIDFK to tenants
part_idUUIDFK to parts
cell_idUUIDFK to cells (work center)
operation_nameTEXTOperation description
sequenceINTEGEROrder in routing
statustask_statusnot_started, in_progress, completed, on_hold
estimated_timeNUMERICEstimated hours
actual_timeNUMERICRecorded hours
setup_timeNUMERICSetup time in hours
run_time_per_unitNUMERICPer-piece runtime
changeover_timeNUMERICChangeover time
wait_timeNUMERICQueue time
planned_startTIMESTAMPTZScheduled start
planned_endTIMESTAMPTZScheduled end
completed_atTIMESTAMPTZActual completion
completion_percentageINTEGERProgress 0-100
assigned_operator_idUUIDFK to profiles
icon_nameTEXTDisplay icon
external_idTEXTERP external ID
external_sourceTEXTERP source system
synced_atTIMESTAMPTZLast sync time
deleted_atTIMESTAMPTZSoft delete timestamp

Indexes:

  • idx_operations_external_sync - Unique on (tenant_id, external_source, external_id) WHERE external_id IS NOT NULL
  • idx_operations_active - Partial on (tenant_id, part_id) WHERE deleted_at IS NULL

Work centers / manufacturing stages. Operations are performed at cells.

ColumnTypeDescription
idUUIDPrimary key
tenant_idUUIDFK to tenants
nameTEXTCell/stage name
descriptionTEXTDescription
sequenceINTEGERDisplay order
colorTEXTDisplay color
icon_nameTEXTDisplay icon
image_urlTEXTCell image
activeBOOLEANIs active
wip_limitINTEGERWork-in-progress limit
wip_warning_thresholdINTEGERWarning threshold
enforce_wip_limitBOOLEANBlock at limit
show_capacity_warningBOOLEANShow warnings
capacity_hours_per_dayNUMERICDaily capacity
external_idTEXTERP external ID
external_sourceTEXTERP source system
synced_atTIMESTAMPTZLast sync time
deleted_atTIMESTAMPTZSoft delete timestamp

Equipment, tooling, and other resources.

ColumnTypeDescription
idUUIDPrimary key
tenant_idUUIDFK to tenants
nameTEXTResource name
typeTEXTResource type (equipment, tool, fixture, etc.)
identifierTEXTAsset tag / serial number
descriptionTEXTDescription
locationTEXTPhysical location
statusTEXTavailable, in_use, maintenance, etc.
activeBOOLEANIs active
metadataJSONBCustom fields
external_idTEXTERP external ID
external_sourceTEXTERP source system
synced_atTIMESTAMPTZLast sync time
deleted_atTIMESTAMPTZSoft delete timestamp

Many-to-many link between operations and resources.

ColumnTypeDescription
idUUIDPrimary key
operation_idUUIDFK to operations
resource_idUUIDFK to resources
quantityINTEGERQuantity required
notesTEXTNotes

User accounts. Linked to Supabase auth.users.

ColumnTypeDescription
idUUIDPrimary key (matches auth.users.id)
tenant_idUUIDFK to tenants
emailTEXTEmail address
usernameTEXTDisplay username
full_nameTEXTFull name
roleapp_roleoperator, admin
activeBOOLEANIs active
employee_idTEXTEmployee ID for operators
pin_hashTEXTHashed PIN for terminal login
has_email_loginBOOLEANCan login via email
is_machineBOOLEANMachine account flag
is_root_adminBOOLEANRoot admin flag
onboarding_completedBOOLEANCompleted onboarding

TableColumns
jobsexternal_id, external_source, synced_at, sync_hash
partsexternal_id, external_source, synced_at, sync_hash
operationsexternal_id, external_source, synced_at
resourcesexternal_id, external_source, synced_at
cellsexternal_id, external_source, synced_at

Tracks batch import operations for audit.

ColumnTypeDescription
idUUIDPrimary key
tenant_idUUIDFK to tenants
sourceTEXT’csv’, ‘api’, ‘erp_sap’, etc.
entity_typeTEXT’jobs’, ‘parts’, ‘operations’, etc.
statusTEXTpending, processing, completed, failed
total_recordsINTEGERTotal records in batch
created_countINTEGERNew records created
updated_countINTEGERExisting records updated
skipped_countINTEGERUnchanged records skipped
error_countINTEGERRecords with errors
errorsJSONBError details array
file_nameTEXTOriginal filename for CSV
started_atTIMESTAMPTZProcessing start
completed_atTIMESTAMPTZProcessing end
created_byUUIDUser who initiated
-- Generate MD5 hash for change detection
SELECT generate_sync_hash('{"job_number": "J001", "customer": "ACME"}'::jsonb);

'not_started' | 'in_progress' | 'completed' | 'on_hold'
'pending' | 'approved' | 'rejected' | 'closed'
'low' | 'medium' | 'high' | 'critical'
'operator' | 'admin'
'free' | 'pro' | 'premium' | 'enterprise'
'active' | 'cancelled' | 'suspended' | 'trial'
'draft' | 'planned' | 'loading' | 'in_transit' | 'delivered' | 'cancelled'

-- Get current user's tenant ID (from JWT)
SELECT get_user_tenant_id();
-- Get current user's role
SELECT get_user_role();
-- Check if user is root admin
SELECT is_root_admin();
-- Check if can create a job
SELECT can_create_job(p_tenant_id);
-- Check if can create parts
SELECT can_create_parts(p_tenant_id, p_quantity);
-- Get tenant quota info
SELECT * FROM get_tenant_quota(p_tenant_id);
-- Log activity and dispatch webhooks
SELECT 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
);
-- Generate hash for change detection
SELECT generate_sync_hash(
'{"job_number": "J001", "customer": "ACME"}'::jsonb
);

REST API authentication keys.

ColumnTypeDescription
idUUIDPrimary key
tenant_idUUIDFK to tenants
nameTEXTKey name
key_hashTEXTbcrypt hash of full key
key_prefixTEXTFirst 8 chars for lookup
activeBOOLEANIs active
created_byUUIDUser who created
last_used_atTIMESTAMPTZLast usage time

Outbound webhook configurations.

ColumnTypeDescription
idUUIDPrimary key
tenant_idUUIDFK to tenants
urlTEXTWebhook endpoint URL
secret_keyTEXTHMAC signing key
eventsTEXT[]Subscribed events
activeBOOLEANIs active

Webhook Events:

  • job.created, job.updated, job.deleted
  • part.created, part.updated, part.deleted
  • operation.started, operation.completed
  • issue.created, issue.resolved
  • sync.jobs.completed, sync.parts.completed, sync.batch.completed

MQTT publisher configurations for industrial integration.

ColumnTypeDescription
idUUIDPrimary key
tenant_idUUIDFK to tenants
nameTEXTPublisher name
broker_urlTEXTMQTT broker URL
portINTEGERBroker port (default 1883)
usernameTEXTAuth username
passwordTEXTAuth password
use_tlsBOOLEANUse TLS/SSL
topic_patternTEXTTopic pattern with placeholders
eventsTEXT[]Subscribed events
activeBOOLEANIs active
default_enterpriseTEXTISA-95 enterprise
default_siteTEXTISA-95 site
default_areaTEXTISA-95 area

MCP server authentication keys.

ColumnTypeDescription
idUUIDPrimary key
tenant_idUUIDFK to tenants
nameTEXTKey name
key_hashTEXTbcrypt hash
key_prefixTEXTLookup prefix
environmentTEXTdevelopment, staging, production
enabledBOOLEANIs enabled
allowed_toolsJSONBTool whitelist (null = all)
rate_limitINTEGERRequests per minute
usage_countINTEGERTotal usage 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_parts
FROM jobs j
LEFT JOIN parts p ON j.id = p.job_id AND p.deleted_at IS NULL
WHERE j.tenant_id = :tenant_id
AND j.deleted_at IS NULL
GROUP BY j.id
ORDER BY j.due_date;
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_operator
FROM operations o
JOIN cells c ON o.cell_id = c.id
LEFT JOIN profiles p ON o.assigned_operator_id = p.id
WHERE o.part_id = :part_id
AND o.deleted_at IS NULL
ORDER BY o.sequence;
-- Find job by external ID
SELECT * FROM jobs
WHERE tenant_id = :tenant_id
AND external_source = :source
AND external_id = :external_id
AND deleted_at IS NULL;
-- Find part by external ID
SELECT * FROM parts
WHERE tenant_id = :tenant_id
AND external_source = :source
AND external_id = :external_id
AND deleted_at IS NULL;
SELECT get_cell_wip_count(:cell_id, :tenant_id);
SELECT
o.*,
p.part_number,
j.job_number,
j.customer
FROM operations o
JOIN parts p ON o.part_id = p.id
JOIN jobs j ON p.job_id = j.id
WHERE o.cell_id = :cell_id
AND o.tenant_id = :tenant_id
AND o.status IN ('not_started', 'in_progress')
AND o.deleted_at IS NULL
ORDER BY j.due_date, o.sequence;
SELECT
entity_type,
status,
total_records,
created_count,
updated_count,
skipped_count,
error_count,
completed_at
FROM sync_imports
WHERE tenant_id = :tenant_id
ORDER BY created_at DESC
LIMIT 10;

ToolTables Used
list_jobsjobs, parts
get_job_detailsjobs, parts, operations
create_jobjobs
update_job_statusjobs
ToolTables Used
list_partsparts, jobs, operations
get_part_detailsparts, operations, cells
create_partparts, jobs
update_part_statusparts
ToolTables Used
list_operationsoperations, parts, cells
start_operationoperations, time_entries
complete_operationoperations, time_entries
get_operation_detailsoperations, substeps, resources
ToolTables Used
erp_sync_diffjobs, parts, resources
erp_sync_executejobs, parts, resources, sync_imports
erp_lookup_external_idjobs, parts, operations, resources
erp_sync_statussync_imports
erp_batch_lookupjobs, parts, resources
erp_resolve_idsjobs, parts, cells

-- Good
SELECT * FROM jobs WHERE tenant_id = :tenant_id AND id = :job_id;
-- Bad - RLS will filter but explicit is better
SELECT * FROM jobs WHERE id = :job_id;
-- Good - exclude deleted records
SELECT * FROM jobs WHERE deleted_at IS NULL;
-- Explicit include for admin views
SELECT * FROM jobs WHERE deleted_at IS NOT NULL;
-- Calculate hash before update
WITH new_hash AS (
SELECT generate_sync_hash(:payload::jsonb) as hash
)
UPDATE jobs
SET
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));
-- Prefetch existing records by external_id
SELECT id, external_id, sync_hash
FROM jobs
WHERE tenant_id = :tenant_id
AND external_source = :source
AND external_id = ANY(:external_ids)
AND deleted_at IS NULL;

Last updated: 2024-12-06

Migration: 20251204000000_add_erp_sync_columns.sql