Skip to content

Data Models — Foot Factory

All data lives in MongoDB Atlas. Every collection is tenant-scoped via a tenantId field.

Entity Relationship Overview

erDiagram
    TENANT ||--o{ TENANT_CONFIG : "has one"
    TENANT ||--o{ USER : "has many"
    TENANT ||--o{ WORKER : "has many"
    TENANT ||--o{ ARTICLE : "has many"
    TENANT ||--o{ JOB : "has many"
    TENANT ||--o{ CUSTOMER : "has many"
    TENANT ||--o{ MASTER_DATA_VALUES : "has many"

    JOB ||--o{ JOB_ITEM : "contains"
    JOB ||--o{ JOB_STEP : "has steps"
    JOB_ITEM }o--|| ARTICLE : "references"
    JOB_STEP }o--|| WORKER : "assigned to"

    WORKER ||--o{ LEDGER_ENTRY : "has ledger"
    WORKER ||--o{ TRANSACTION : "has transactions"

    TENANT_CONFIG ||--o{ JOB_STEP_DEF : "defines steps"
    TENANT_CONFIG ||--o{ ARTICLE_ATTR_DEF : "defines attributes"

Tenant

The MongoDB Tenant document is a lightweight reference — status, plan, name, and slug are authoritative in the Central Registry (Supabase).

{
  _id: ObjectId,           // matches Supabase tenant UUID
  slug: String,            // URL-safe identifier
  settings: {
    maxUsers: Number,      // copied from plan at provisioning time
    maxWorkers: Number,
  },
  contact: {
    email: String,
    phone: String,
  },
  domain: String,          // e.g. "acme-corp.foot-factory.dotevolve.net"
  domainStatus: String,    // "active" | "pending" | "failed"
  owner: ObjectId,         // ref: User
  createdAt: Date,
}

User (Profile Document)

Passwords are managed by Supabase Auth. The MongoDB User document stores only profile data.

{
  _id: ObjectId,
  supabaseId: String,      // unique — links to auth.users.id
  name: String,
  username: String,
  email: String,
  role: String,            // "admin" | "user" | "super-admin" (legacy field)
  tenantId: ObjectId,      // ref: Tenant
  avatarUrl: String,
  preferences: Mixed,
  createdAt: Date,
}

TenantConfig

Defines the dynamic schema for a tenant — drives all UI rendering in the client app.

{
  _id: ObjectId,
  tenantId: ObjectId,
  jobSteps: [{
    name: String,          // e.g. "Bottom", "Upper", "Lower", "Polish"
    order: Number,
    isPaid: Boolean,
  }],
  articleAttributes: [{
    name: String,          // e.g. "color", "sole", "material"
    label: String,
    type: String,          // "string" | "number" | "boolean"
    isRequired: Boolean,
  }],
  sizeRange: { min: Number, max: Number },
  customLedgerTypes: [String],
  pagination: { defaultPageSize: Number, maxPageSize: Number },
}

Article

{
  _id: ObjectId,
  tenantId: ObjectId,
  articleId: String,       // e.g. "ART-001"
  articleName: String,
  description: String,
  stepRates: Map,          // { "Bottom": 20, "Upper": 25, ... }
  isDeleted: Boolean,
}

Worker

{
  _id: ObjectId,
  tenantId: ObjectId,
  workerId: String,        // e.g. "WRK-001"
  firstName: String,
  lastName: String,
  phone: String,
  email: String,
  skills: [String],        // e.g. ["Upper", "Bottom"]
  address: { houseNo, area, city, state, pin, country },
  amount: Number,          // total earned (running total)
  balance: Number,         // unpaid balance
  jobsPendingPayment: [{
    jobId: ObjectId,
    jobStepName: String,
    completedDate: Date,
    earnedAmount: Number,
  }],
  jobsPaid: [{ jobId, jobStepName, paidDate, paidAmount }],
}

Job

{
  _id: ObjectId,
  tenantId: ObjectId,
  taskName: String,
  jobStatus: String,       // "In Progress" | "Completed"
  items: [{
    articleId: ObjectId,
    articleName: String,
    attributes: Map,       // dynamic — driven by TenantConfig.articleAttributes
    sizes: Map,            // { "6": 10, "7": 15, ... }
    totalQuantity: Number,
  }],
  steps: [{
    stepName: String,      // matches TenantConfig.jobSteps[].name
    order: Number,
    isPaid: Boolean,
    status: String,        // "Pending" | "In-Progress" | "Completed"
    workerId: ObjectId,
  }],
  createdAt: Date,
}

LedgerEntry

{
  _id: ObjectId,
  tenantId: ObjectId,
  workerId: ObjectId,
  type: String,            // "job_earning" | "payment" | "adjustment"
  credit: Number,
  debit: Number,
  runningBalance: Number,
  description: String,
  referenceModel: String,
  referenceId: ObjectId,
  createdAt: Date,
}

Transaction

{
  _id: ObjectId,
  tenantId: ObjectId,
  workerId: ObjectId,
  amount: Number,
  transactionType: String, // "Cash" | "Transfer"
  createdAt: Date,
}