Components are now split into two concepts: - Component (type definition): title, componentType, brand, partNumber, specs, notes, default condition/firmware/location - Component Instance (individual physical unit): serialNumber, condition, firmwareVersion, notes, currentDeviceId, locationId Key changes: - New component_instances table with per-unit tracking - Component list shows cards with total/installed/available counts - Component detail page shows all instances with inline edit - Add instances: bulk (quantity) or one at a time - Each instance has install/remove/edit/delete actions - Installation log now references instances, not components - Device detail shows installed instances with instance numbers - Dashboard and sidebar counts use instance totals - Location pages show instances, not component types - Labels show component type info (serial is per-instance) IMPORTANT: Run db:push on the server after deploy to create the new tables and columns. Existing component data will need manual migration. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -123,7 +123,7 @@ export const deviceDocuments = pgTable(
|
||||
(table) => [index('device_documents_device_idx').on(table.deviceId)]
|
||||
);
|
||||
|
||||
// ─── Components ─────────────────────────────────────────────────────
|
||||
// ─── Components (type definitions) ──────────────────────────────────
|
||||
|
||||
export const components = pgTable(
|
||||
'components',
|
||||
@@ -133,10 +133,32 @@ export const components = pgTable(
|
||||
componentType: text('component_type').notNull(),
|
||||
brand: text('brand'),
|
||||
partNumber: text('part_number'),
|
||||
specs: text('specs'),
|
||||
notes: text('notes'),
|
||||
defaultCondition: text('default_condition').notNull().default('Working'),
|
||||
defaultFirmwareVersion: text('default_firmware_version'),
|
||||
defaultLocationId: uuid('default_location_id').references(() => locations.id),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
|
||||
},
|
||||
(table) => [
|
||||
index('components_type_idx').on(table.componentType)
|
||||
]
|
||||
);
|
||||
|
||||
// ─── Component Instances (individual physical units) ────────────────
|
||||
|
||||
export const componentInstances = pgTable(
|
||||
'component_instances',
|
||||
{
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
componentId: uuid('component_id')
|
||||
.notNull()
|
||||
.references(() => components.id, { onDelete: 'cascade' }),
|
||||
instanceNumber: integer('instance_number').notNull().default(1),
|
||||
serialNumber: text('serial_number'),
|
||||
condition: text('condition').notNull().default('Working'),
|
||||
firmwareVersion: text('firmware_version'),
|
||||
specs: text('specs'),
|
||||
notes: text('notes'),
|
||||
currentDeviceId: uuid('current_device_id').references(() => devices.id, {
|
||||
onDelete: 'set null'
|
||||
@@ -146,10 +168,10 @@ export const components = pgTable(
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
|
||||
},
|
||||
(table) => [
|
||||
index('components_type_idx').on(table.componentType),
|
||||
index('components_device_idx').on(table.currentDeviceId),
|
||||
index('components_location_idx').on(table.locationId),
|
||||
check('components_condition_check', sql`${table.condition} IN ('Working', 'Faulty', 'Unknown', 'Refurbished')`)
|
||||
index('instances_component_idx').on(table.componentId),
|
||||
index('instances_device_idx').on(table.currentDeviceId),
|
||||
index('instances_location_idx').on(table.locationId),
|
||||
check('instances_condition_check', sql`${table.condition} IN ('Working', 'Faulty', 'Unknown', 'Refurbished')`)
|
||||
]
|
||||
);
|
||||
|
||||
@@ -194,9 +216,9 @@ export const installationLog = pgTable(
|
||||
'installation_log',
|
||||
{
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
componentId: uuid('component_id')
|
||||
instanceId: uuid('instance_id')
|
||||
.notNull()
|
||||
.references(() => components.id, { onDelete: 'cascade' }),
|
||||
.references(() => componentInstances.id, { onDelete: 'cascade' }),
|
||||
deviceId: uuid('device_id')
|
||||
.notNull()
|
||||
.references(() => devices.id, { onDelete: 'cascade' }),
|
||||
@@ -206,7 +228,7 @@ export const installationLog = pgTable(
|
||||
performedAt: timestamp('performed_at', { withTimezone: true }).defaultNow().notNull()
|
||||
},
|
||||
(table) => [
|
||||
index('install_log_component_idx').on(table.componentId),
|
||||
index('install_log_instance_idx').on(table.instanceId),
|
||||
index('install_log_device_idx').on(table.deviceId),
|
||||
index('install_log_date_idx').on(table.performedAt),
|
||||
check('install_log_action_check', sql`${table.action} IN ('installed', 'removed', 'swapped')`)
|
||||
|
||||
Reference in New Issue
Block a user