187 lines
6.9 KiB
TypeScript
187 lines
6.9 KiB
TypeScript
import {
|
|
pgTable,
|
|
text,
|
|
boolean,
|
|
integer,
|
|
numeric,
|
|
timestamp,
|
|
date,
|
|
jsonb,
|
|
index,
|
|
uniqueIndex,
|
|
primaryKey,
|
|
} from 'drizzle-orm/pg-core';
|
|
import { ports } from './ports';
|
|
import { clients } from './clients';
|
|
|
|
export const berths = pgTable(
|
|
'berths',
|
|
{
|
|
id: text('id')
|
|
.primaryKey()
|
|
.$defaultFn(() => crypto.randomUUID()),
|
|
portId: text('port_id')
|
|
.notNull()
|
|
.references(() => ports.id),
|
|
mooringNumber: text('mooring_number').notNull(),
|
|
area: text('area'),
|
|
status: text('status').notNull().default('available'), // available, under_offer, sold
|
|
lengthFt: numeric('length_ft'),
|
|
widthFt: numeric('width_ft'),
|
|
draftFt: numeric('draft_ft'),
|
|
lengthM: numeric('length_m'),
|
|
widthM: numeric('width_m'),
|
|
draftM: numeric('draft_m'),
|
|
widthIsMinimum: boolean('width_is_minimum').default(false),
|
|
nominalBoatSize: text('nominal_boat_size'),
|
|
nominalBoatSizeM: text('nominal_boat_size_m'),
|
|
waterDepth: numeric('water_depth'),
|
|
waterDepthM: numeric('water_depth_m'),
|
|
waterDepthIsMinimum: boolean('water_depth_is_minimum').default(false),
|
|
sidePontoon: text('side_pontoon'),
|
|
powerCapacity: text('power_capacity'),
|
|
voltage: text('voltage'),
|
|
mooringType: text('mooring_type'),
|
|
cleatType: text('cleat_type'),
|
|
cleatCapacity: text('cleat_capacity'),
|
|
bollardType: text('bollard_type'),
|
|
bollardCapacity: text('bollard_capacity'),
|
|
access: text('access'),
|
|
price: numeric('price'),
|
|
priceCurrency: text('price_currency').notNull().default('USD'),
|
|
bowFacing: text('bow_facing'),
|
|
berthApproved: boolean('berth_approved').default(false),
|
|
tenureType: text('tenure_type').notNull().default('permanent'), // permanent, fixed_term
|
|
tenureYears: integer('tenure_years'),
|
|
tenureStartDate: date('tenure_start_date'),
|
|
tenureEndDate: date('tenure_end_date'),
|
|
statusLastChangedBy: text('status_last_changed_by'), // user ID
|
|
statusLastChangedReason: text('status_last_changed_reason'),
|
|
statusLastModified: timestamp('status_last_modified', { withTimezone: true }),
|
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
index('idx_berths_port').on(table.portId),
|
|
index('idx_berths_status').on(table.portId, table.status),
|
|
index('idx_berths_area').on(table.portId, table.area),
|
|
uniqueIndex('idx_berths_mooring').on(table.portId, table.mooringNumber),
|
|
],
|
|
);
|
|
|
|
export const berthMapData = pgTable(
|
|
'berth_map_data',
|
|
{
|
|
id: text('id')
|
|
.primaryKey()
|
|
.$defaultFn(() => crypto.randomUUID()),
|
|
berthId: text('berth_id')
|
|
.notNull()
|
|
.unique()
|
|
.references(() => berths.id, { onDelete: 'cascade' }),
|
|
svgPath: text('svg_path'),
|
|
x: numeric('x'),
|
|
y: numeric('y'),
|
|
transform: text('transform'),
|
|
fontSize: numeric('font_size'),
|
|
extraData: jsonb('extra_data').default({}),
|
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [uniqueIndex('berth_map_data_berth_id_idx').on(table.berthId)],
|
|
);
|
|
|
|
export const berthRecommendations = pgTable(
|
|
'berth_recommendations',
|
|
{
|
|
id: text('id')
|
|
.primaryKey()
|
|
.$defaultFn(() => crypto.randomUUID()),
|
|
interestId: text('interest_id').notNull(), // references interests.id
|
|
berthId: text('berth_id')
|
|
.notNull()
|
|
.references(() => berths.id, { onDelete: 'cascade' }),
|
|
matchScore: numeric('match_score'), // 0-100
|
|
matchReasons: jsonb('match_reasons'), // { "dimensional_fit": 95, "power_match": 80, ... }
|
|
source: text('source').notNull().default('ai'), // ai, manual
|
|
createdBy: text('created_by'), // user ID for manual recommendations
|
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
uniqueIndex('berth_rec_interest_berth_idx').on(table.interestId, table.berthId),
|
|
index('idx_br_interest').on(table.interestId),
|
|
],
|
|
);
|
|
|
|
export const berthWaitingList = pgTable(
|
|
'berth_waiting_list',
|
|
{
|
|
id: text('id')
|
|
.primaryKey()
|
|
.$defaultFn(() => crypto.randomUUID()),
|
|
berthId: text('berth_id')
|
|
.notNull()
|
|
.references(() => berths.id, { onDelete: 'cascade' }),
|
|
clientId: text('client_id')
|
|
.notNull()
|
|
.references(() => clients.id, { onDelete: 'cascade' }),
|
|
yachtId: text('yacht_id'), // FK added via relation; nullable (waiting for this yacht)
|
|
position: integer('position').notNull(),
|
|
priority: text('priority').notNull().default('normal'), // normal, high
|
|
notifyPref: text('notify_pref').default('email'), // email, in_app, both
|
|
notes: text('notes'),
|
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
uniqueIndex('berth_waiting_list_berth_client_idx').on(table.berthId, table.clientId),
|
|
index('idx_bwl_berth').on(table.berthId, table.position),
|
|
],
|
|
);
|
|
|
|
export const berthMaintenanceLog = pgTable(
|
|
'berth_maintenance_log',
|
|
{
|
|
id: text('id')
|
|
.primaryKey()
|
|
.$defaultFn(() => crypto.randomUUID()),
|
|
berthId: text('berth_id')
|
|
.notNull()
|
|
.references(() => berths.id, { onDelete: 'cascade' }),
|
|
portId: text('port_id')
|
|
.notNull()
|
|
.references(() => ports.id),
|
|
category: text('category').notNull(), // routine, repair, inspection, upgrade
|
|
description: text('description').notNull(),
|
|
cost: numeric('cost'),
|
|
costCurrency: text('cost_currency').default('USD'),
|
|
responsibleParty: text('responsible_party'),
|
|
performedDate: date('performed_date').notNull(),
|
|
photoFileIds: text('photo_file_ids').array(), // references to files table
|
|
createdBy: text('created_by').notNull(),
|
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [index('idx_bml_berth').on(table.berthId), index('idx_bml_port').on(table.portId)],
|
|
);
|
|
|
|
export const berthTags = pgTable(
|
|
'berth_tags',
|
|
{
|
|
berthId: text('berth_id')
|
|
.notNull()
|
|
.references(() => berths.id, { onDelete: 'cascade' }),
|
|
tagId: text('tag_id').notNull(), // references tags.id
|
|
},
|
|
(table) => [primaryKey({ columns: [table.berthId, table.tagId] })],
|
|
);
|
|
|
|
export type Berth = typeof berths.$inferSelect;
|
|
export type NewBerth = typeof berths.$inferInsert;
|
|
export type BerthMapData = typeof berthMapData.$inferSelect;
|
|
export type NewBerthMapData = typeof berthMapData.$inferInsert;
|
|
export type BerthRecommendation = typeof berthRecommendations.$inferSelect;
|
|
export type NewBerthRecommendation = typeof berthRecommendations.$inferInsert;
|
|
export type BerthWaitingList = typeof berthWaitingList.$inferSelect;
|
|
export type NewBerthWaitingList = typeof berthWaitingList.$inferInsert;
|
|
export type BerthMaintenanceLog = typeof berthMaintenanceLog.$inferSelect;
|
|
export type NewBerthMaintenanceLog = typeof berthMaintenanceLog.$inferInsert;
|