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' }), 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;