// services/maintenanceRequestService.ts // Handle maintenance requests, tracking, and resolution workflow import { eq } from "drizzle-orm"; import { db } from "../db/db.ts"; import { maintenance_requests, properties } from "../db/schema.ts"; import { getUserById } from "./userService.ts"; import { getPropertyById } from "./propertyService.ts"; export interface MaintenanceRequest { id: string; property_id?: string | null; tenant_id?: string | null; request_type?: string | null; // 'plumbing', 'electrical', 'hvac', 'general' description?: string | null; // Encrypted request details status?: string | null; // 'open', 'in_progress', 'completed', 'cancelled' priority?: string | null; // 'low', 'medium', 'high', 'urgent' created_on?: number | null; resolved_on?: number | null; } // Extended interface for dashboard maintenance requests with UI properties export interface DashboardMaintenanceRequest extends MaintenanceRequest { tenant_name?: string; tenant_email?: string; property_address?: string; created_at?: number | null; // UI-friendly timestamp (maps to created_on) resolved_at?: number | null; // UI-friendly timestamp (maps to resolved_on) days_open?: number; // Calculated field for dashboard display } // Custom interface that combines maintenance request with user and property data export interface MaintenanceData { id: string; property_id: string; tenant_id: string; request_type: string | null; description: string | null; status: string | null; priority: string | null; created_on: number; resolved_on: number | null; // Enhanced data from related tables tenant_name: string; tenant_email: string; tenant_phone: string; property_address: string; property_city: string; property_state: string; property_zip: string; property_title: string; } // Create new maintenance request export async function createMaintenanceRequest( property_id: string, tenant_id: string, request_type: string, description: string, priority: string = "medium", ) { const now = Date.now(); return await db.insert(maintenance_requests) .values({ id: crypto.randomUUID(), property_id, tenant_id, request_type, description, status: "open", priority, created_on: now, resolved_on: null, }) .returning(); } // Get maintenance requests by property export async function getRequestsByProperty(property_id: string) { return await db.select() .from(maintenance_requests) .where(eq(maintenance_requests.property_id, property_id)) .orderBy(maintenance_requests.created_on); } // Get maintenance requests by tenant export async function getRequestsByTenant(tenant_id: string) { return await db.select() .from(maintenance_requests) .where(eq(maintenance_requests.tenant_id, tenant_id)) .orderBy(maintenance_requests.created_on); } // Get requests by status export async function getRequestsByStatus( status: string, property_ids?: string[], ) { if (property_ids && property_ids.length > 0) { // In a real implementation, you'd use an IN clause // For now, filtering by status only return await db.select() .from(maintenance_requests) .where(eq(maintenance_requests.status, status)) .orderBy(maintenance_requests.created_on); } return await db.select() .from(maintenance_requests) .where(eq(maintenance_requests.status, status)) .orderBy(maintenance_requests.created_on); } // Get open requests export async function getOpenRequests(property_ids?: string[]) { return await getRequestsByStatus("open", property_ids); } // Get requests by priority export async function getRequestsByPriority( priority: string, property_ids?: string[], ) { if (property_ids && property_ids.length > 0) { return await db.select() .from(maintenance_requests) .where(eq(maintenance_requests.priority, priority)) .orderBy(maintenance_requests.created_on); } return await db.select() .from(maintenance_requests) .where(eq(maintenance_requests.priority, priority)) .orderBy(maintenance_requests.created_on); } // Get urgent requests export async function getUrgentRequests(property_ids?: string[]) { return await getRequestsByPriority("urgent", property_ids); } // Update request status export async function updateRequestStatus( id: string, status: "open" | "in_progress" | "completed" | "cancelled", ) { const updates: { status: string; resolved_on?: number } = { status }; // Set resolved_on timestamp when completing or cancelling if (status === "completed" || status === "cancelled") { updates.resolved_on = Date.now(); } return await db.update(maintenance_requests) .set(updates) .where(eq(maintenance_requests.id, id)) .returning(); } // Update request priority export async function updateRequestPriority( id: string, priority: "low" | "medium" | "high" | "urgent", ) { return await db.update(maintenance_requests) .set({ priority }) .where(eq(maintenance_requests.id, id)) .returning(); } // Get request by ID export async function getRequestById(id: string) { const result = await db.select() .from(maintenance_requests) .where(eq(maintenance_requests.id, id)); return result[0] || null; } // Delete request export async function deleteMaintenanceRequest(id: string) { return await db.delete(maintenance_requests) .where(eq(maintenance_requests.id, id)) .returning(); } // Get request statistics for a property export async function getRequestStatsByProperty(property_id: string) { const requests = await getRequestsByProperty(property_id); return { total: requests.length, open: requests.filter((r) => r.status === "open").length, in_progress: requests.filter((r) => r.status === "in_progress").length, completed: requests.filter((r) => r.status === "completed").length, cancelled: requests.filter((r) => r.status === "cancelled").length, urgent: requests.filter((r) => r.priority === "urgent").length, high: requests.filter((r) => r.priority === "high").length, medium: requests.filter((r) => r.priority === "medium").length, low: requests.filter((r) => r.priority === "low").length, }; } // Get request statistics for a tenant export async function getRequestStatsByTenant(tenant_id: string) { const requests = await db.select() .from(maintenance_requests) .where(eq(maintenance_requests.tenant_id, tenant_id)); return { total: requests.length, open: requests.filter((r) => r.status === "open").length, in_progress: requests.filter((r) => r.status === "in_progress").length, completed: requests.filter((r) => r.status === "completed").length, }; } // Get maintenance requests by landlord (via properties) export async function getMaintenanceRequestsByLandlord(landlord_id: string) { return await db.select({ id: maintenance_requests.id, property_id: maintenance_requests.property_id, tenant_id: maintenance_requests.tenant_id, request_type: maintenance_requests.request_type, description: maintenance_requests.description, status: maintenance_requests.status, priority: maintenance_requests.priority, created_on: maintenance_requests.created_on, resolved_on: maintenance_requests.resolved_on, }) .from(maintenance_requests) .innerJoin(properties, eq(maintenance_requests.property_id, properties.id)) .where(eq(properties.owner_id, landlord_id)) .orderBy(maintenance_requests.created_on); } // Get maintenance requests by user (tenant) export async function getMaintenanceRequestsByUser( user_id: string, options: { limit?: number } = {}, ) { const query = db.select({ id: maintenance_requests.id, property_id: maintenance_requests.property_id, tenant_id: maintenance_requests.tenant_id, title: maintenance_requests.request_type, description: maintenance_requests.description, status: maintenance_requests.status, priority: maintenance_requests.priority, category: maintenance_requests.request_type, created_on: maintenance_requests.created_on, resolved_on: maintenance_requests.resolved_on, }) .from(maintenance_requests) .where(eq(maintenance_requests.tenant_id, user_id)) .orderBy(maintenance_requests.created_on); if (options.limit) { return await query.limit(options.limit); } return await query; } // Update maintenance request status (alias for compatibility) export async function updateMaintenanceRequestStatus( id: string, updates: { status?: string; priority?: string }, ) { const updateData: { status?: string; priority?: string; resolved_on?: number; } = {}; if (updates.status) { updateData.status = updates.status; if (updates.status === "completed") { updateData.resolved_on = Date.now(); } } if (updates.priority) { updateData.priority = updates.priority; } return await db.update(maintenance_requests) .set(updateData) .where(eq(maintenance_requests.id, id)) .returning(); } // Get maintenance requests with enhanced data (user names and property addresses) export async function getMaintenanceDataByLandlord( landlord_id: string, ): Promise { try { // First get all maintenance requests for the landlord const maintenanceRequests = await getMaintenanceRequestsByLandlord( landlord_id, ); // Build the enhanced data array const maintenanceData: MaintenanceData[] = []; for (const request of maintenanceRequests) { try { // Get tenant user data const tenant = await getUserById(request.tenant_id || ""); if (!tenant || !request.tenant_id) { console.warn(`Tenant not found for ID: ${request.tenant_id}`); continue; } // Get property data const property = await getPropertyById(request.property_id || ""); if (!property || !request.property_id) { console.warn(`Property not found for ID: ${request.property_id}`); continue; } // Build the enhanced maintenance data object const enhancedData: MaintenanceData = { id: request.id, property_id: request.property_id || "", tenant_id: request.tenant_id || "", request_type: request.request_type || null, description: request.description || null, status: request.status || null, priority: request.priority || null, created_on: request.created_on || Date.now(), resolved_on: request.resolved_on || null, // Enhanced data from user table tenant_name: tenant.name || "Unknown", tenant_email: tenant.email || "", tenant_phone: tenant.phone || "", // Enhanced data from property table property_address: `${property.address_1 || ""} ${property.address_2 || ""}`.trim() || "No address", property_city: property.city || "", property_state: property.state || "", property_zip: property.zip || "", property_title: property.title || "Untitled Property", }; maintenanceData.push(enhancedData); } catch (error) { console.error( `Error processing maintenance request ${request.id}:`, error, ); // Continue processing other requests even if one fails continue; } } return maintenanceData; } catch (error) { console.error("Error in getMaintenanceDataByLandlord:", error); throw error; } }