integratedhub/services/maintenanceRequestService.ts
2026-02-14 02:20:19 +00:00

363 lines
11 KiB
TypeScript

// 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<MaintenanceData[]> {
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;
}
}