Files
neo/SYSTEM_FIELDS_FIX.md

9.3 KiB

System Fields Validation Fix - Checklist

Problem

When creating or updating records, frontend validation was showing:

  • "Created At is required"
  • "Updated At is required"

This happened because system-managed fields were marked with isRequired: true in the database and frontend was trying to validate them.

Root Causes Identified

  1. Backend Issue: Standard field definitions were created with isRequired: true

    • ownerId - marked required but auto-set by system
    • created_at - marked required but auto-set by system
    • updated_at - marked required but auto-set by system
    • name - marked required but should be optional
  2. Backend Issue: System fields not marked with isSystem: true

    • Missing flag that identifies auto-managed fields
    • Frontend couldn't distinguish system fields from user fields
  3. Frontend Issue: Field hiding logic didn't fully account for system fields

    • Only checked against hardcoded list of field names
    • Didn't check isSystem flag from backend
  4. Frontend Issue: Form data wasn't filtered before saving

    • System fields might be included in submission
    • Could cause validation errors on backend

Fixes Applied

Backend Changes

File: backend/src/object/object.service.ts

Changed standard field definitions:

// BEFORE (lines 100-132)
ownerId:      isRequired: true
name:         isRequired: true
created_at:   isRequired: true
updated_at:   isRequired: true

// AFTER 
ownerId:      isRequired: false, isSystem: true
name:         isRequired: false, isSystem: false
created_at:   isRequired: false, isSystem: true
updated_at:   isRequired: false, isSystem: true

Changes made:

  • Set isRequired: false for all system fields (they're auto-managed)
  • Added isSystem: true flag for ownerId, created_at, updated_at
  • Set isCustom: false for all standard fields
  • Set name as optional field (isRequired: false)

Frontend Changes

File: frontend/composables/useFieldViews.ts

Enhanced field mapping logic:

// BEFORE
const isAutoGeneratedField = ['id', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy']

// AFTER
const isSystemField = Boolean(fieldDef.isSystem)  // Check backend flag
const isAutoGeneratedField = ['id', 'createdAt', 'updatedAt', 'created_at', 'updated_at', 'createdBy', 'updatedBy']
const shouldHideOnEdit = isSystemField || isAutoGeneratedField  // Check both

showOnEdit: fieldDef.uiMetadata?.showOnEdit ?? !shouldHideOnEdit  // Hide system fields

Changes made:

  • Added check for backend isSystem flag
  • Added snake_case field names (created_at, updated_at)
  • Combined both checks to hide system fields on edit
  • System fields still visible on list and detail views (read-only)

File: frontend/components/views/EditViewEnhanced.vue

Added data filtering before save:

// BEFORE
const handleSave = () => {
  if (validateForm()) {
    emit('save', formData.value)
  }
}

// AFTER
const handleSave = () => {
  if (validateForm()) {
    // Filter out system fields from save data
    const saveData = { ...formData.value }
    const systemFields = ['id', 'tenantId', 'ownerId', 'created_at', 'updated_at', 'createdAt', 'updatedAt']
    for (const field of systemFields) {
      delete saveData[field]
    }
    emit('save', saveData)
  }
}

Changes made:

  • Strip system fields before sending to API
  • Prevents accidental submission of read-only fields
  • Ensures API receives only user-provided data

How It Works Now

Create Record Flow

User fills form with business data:
  { name: "Acme", revenue: 1000000 }
         ↓
Frontend validation skips system fields:
  - created_at (showOnEdit: false, filtered)
  - updated_at (showOnEdit: false, filtered)
  - ownerId (showOnEdit: false, filtered)
         ↓
Frontend filters system fields before save:
  deleteProperty(saveData, 'created_at')
  deleteProperty(saveData, 'updated_at')
  deleteProperty(saveData, 'ownerId')
         ↓
API receives clean data:
  { name: "Acme", revenue: 1000000 }
         ↓
Backend's Objection model auto-manages:
  $beforeInsert() hook:
    - Sets id (UUID)
    - Sets ownerId (from userId)
    - Sets created_at (now)
    - Sets updated_at (now)
         ↓
Database receives complete record with all fields

Update Record Flow

User edits record, changes revenue:
  { revenue: 1500000 }
         ↓
Frontend validation skips system fields
Frontend filters before save:
  - Removes ownerId (read-only)
  - Removes created_at (immutable)
  - Removes updated_at (will be set by system)
         ↓
API receives:
  { revenue: 1500000 }
         ↓
Backend filters out protected fields (double-check):
  delete allowedData.ownerId
  delete allowedData.created_at
  delete allowedData.tenantId
         ↓
Backend's Objection model:
  $beforeUpdate() hook:
    - Sets updated_at (now)
         ↓
Database receives update with timestamp updated

Field Visibility Rules

System fields now properly hidden:

Field Create Detail List Edit Notes
id No Yes No No Auto-generated UUID
ownerId No Yes No No Auto-set from auth
created_at No Yes Yes No Auto-set on insert
updated_at No Yes No No Auto-set on insert/update
name No Yes Yes Yes Optional user field
custom fields No Yes Yes Yes User-defined fields

Legend:

  • No = Field not visible to users
  • Yes = Field visible (read-only or editable)

Backend System Field Management

Standard fields auto-created for every new object:

ownerId (type: LOOKUP)
  ├─ isRequired: false
  ├─ isSystem: true
  ├─ isCustom: false
  └─ Auto-set by ObjectService.createRecord()

name (type: TEXT)
  ├─ isRequired: false
  ├─ isSystem: false
  ├─ isCustom: false
  └─ Optional user field

created_at (type: DATE_TIME)
  ├─ isRequired: false
  ├─ isSystem: true
  ├─ isCustom: false
  └─ Auto-set by DynamicModel.$beforeInsert()

updated_at (type: DATE_TIME)
  ├─ isRequired: false
  ├─ isSystem: true
  ├─ isCustom: false
  └─ Auto-set by DynamicModel.$beforeInsert/Update()

Validation Logic

Frontend Validation (EditViewEnhanced.vue)

  1. Skip fields with showOnEdit === false

    • System fields automatically excluded
    • Created At, Updated At, ownerId won't be validated
  2. Validate only remaining fields:

    • Check required fields have values
    • Apply custom validation rules
    • Show errors inline
  3. Filter data before save:

    • Remove system fields
    • Send clean data to API

Backend Validation (ObjectService)

  1. Check object definition exists
  2. Get bound Objection model
  3. Model validates field types (JSON schema)
  4. Model auto-manages system fields via hooks
  5. Insert/Update data in database

Testing the Fix

Test 1: Create Record

# In Nuxt app, create new record
POST /api/records/Account
Body: { 
  name: "Test Account",
  revenue: 1000000
}

# Should NOT show validation error for Created At or Updated At
# Should create record with auto-populated system fields

Test 2: Check System Fields Are Hidden

Look at create form:
- ✅ ownerId field - NOT visible
- ✅ created_at field - NOT visible
- ✅ updated_at field - NOT visible
- ✅ name field - VISIBLE (optional)
- ✅ custom fields - VISIBLE

Test 3: Update Record

# Edit existing record
PATCH /api/records/Account/record-id
Body: {
  revenue: 1500000
}

# Should NOT show validation error
# Should NOT allow changing ownerId
# Should auto-update timestamp

Test 4: Verify Frontend Filtering

Open browser console:
- Check form data before save
- Should NOT include id, ownerId, created_at, updated_at
- Should include user-provided fields only

Files Modified

File Changes Status
backend/src/object/object.service.ts Standard fields: isRequired→false, added isSystem, isCustom
frontend/composables/useFieldViews.ts Field hiding logic: check isSystem flag + snake_case names
frontend/components/views/EditViewEnhanced.vue handleSave: filter system fields before emit

Verification

Backend compiles: npm run build successful System fields marked with isSystem: true System fields marked with isRequired: false Frontend filtering implemented Frontend hiding logic enhanced

Summary

The fix ensures that system-managed fields (id, ownerId, created_at, updated_at) are:

  1. Never required from users - Marked isRequired: false
  2. Clearly marked as system - Have isSystem: true flag
  3. Hidden from edit forms - Via showOnEdit: false
  4. Filtered before submission - Not sent to API
  5. Auto-managed by backend - Set by model hooks
  6. Protected from modification - Backend filters out in updates