// services/tenantApplicationService.ts // Handle tenant applications, reviews, and approval workflow import { and, eq } from "drizzle-orm"; import { db } from "../db/db.ts"; import { properties, tenant_applications } from "../db/schema.ts"; export interface TenantApplication { id: string; property_id?: string | null; applicant_id?: string | null; application_status?: string | null; // 'pending', 'approved', 'rejected' application_data_encrypted?: string | null; // Encrypted application details document_urls_json?: Record | null; submitted_on?: number | null; reviewed_on?: number | null; } export interface ApplicationData { employment_info?: { employer: string; position: string; annual_income: number; employment_length: string; }; references?: { previous_landlord?: string; previous_landlord_phone?: string; personal_reference?: string; personal_reference_phone?: string; }; background_info?: { credit_score?: number; criminal_background?: boolean; eviction_history?: boolean; pets?: { has_pets: boolean; pet_details?: string; }; }; additional_info?: string; } // Extended interface for dashboard tenant applications with UI properties export interface DashboardTenantApplication { id: string; property_id?: string | null; applicant_id?: string | null; application_status?: string | null; application_data_encrypted?: string | null; submitted_on?: number | null; reviewed_on?: number | null; applicant_name?: string; applicant_email?: string; property_address?: string; monthly_income?: number; application_notes?: string; status?: string; // UI-friendly status (maps to application_status) created_at?: number | null; // UI-friendly timestamp (maps to submitted_on) document_urls_json?: string | null; // UI-friendly string representation } // Create new tenant application export async function createTenantApplication( property_id: string, applicant_id: string, application_data_encrypted: string, documentUrls: { url: string; name: string; type: string }[] | null = null, ) { const documentUrlsJson = documentUrls && documentUrls.length > 0 ? JSON.stringify(documentUrls) : null; const now = Date.now(); return await db.insert(tenant_applications) .values({ id: crypto.randomUUID(), property_id, applicant_id, application_status: "pending", application_data_encrypted, document_urls_json: documentUrlsJson, submitted_on: now, }) .returning(); } // Get applications by property export async function getApplicationsByProperty(property_id: string) { return await db.select() .from(tenant_applications) .where(eq(tenant_applications.property_id, property_id)); } // Get applications by applicant export async function getApplicationsByApplicant(applicant_id: string) { return await db.select() .from(tenant_applications) .where(eq(tenant_applications.applicant_id, applicant_id)); } // Get specific application export async function getApplicationById(id: string) { const result = await db.select() .from(tenant_applications) .where(eq(tenant_applications.id, id)); return result[0] || null; } // Check if user has already applied for property export async function hasUserAppliedForProperty( property_id: string, applicant_id: string, ) { const result = await db.select() .from(tenant_applications) .where(and( eq(tenant_applications.property_id, property_id), eq(tenant_applications.applicant_id, applicant_id), )); return result.length > 0; } // Update application status (approve/reject) export async function updateApplicationStatus( id: string, status: "approved" | "rejected", reviewed_on?: number, ) { const now = reviewed_on || Date.now(); return await db.update(tenant_applications) .set({ application_status: status, reviewed_on: now, }) .where(eq(tenant_applications.id, id)) .returning(); } // Get pending applications for landlord review export async function getPendingApplications(property_ids: string[]) { if (property_ids.length === 0) return []; return await db.select() .from(tenant_applications) .where(and( eq(tenant_applications.application_status, "pending"), // Note: In a real implementation, you'd use an IN clause here // For now, we'll filter by the first property or implement proper IN logic )); } // Delete application (if needed) export async function deleteApplication(id: string) { return await db.delete(tenant_applications) .where(eq(tenant_applications.id, id)) .returning(); } // Get application statistics for a property export async function getApplicationStats(property_id: string) { const applications = await getApplicationsByProperty(property_id); return { total: applications.length, pending: applications.filter((app) => app.application_status === "pending").length, approved: applications.filter((app) => app.application_status === "approved") .length, rejected: applications.filter((app) => app.application_status === "rejected") .length, }; } // Get tenant applications by landlord (via properties they own) export async function getTenantApplicationsByLandlord(landlord_id: string) { return await db.select({ id: tenant_applications.id, property_id: tenant_applications.property_id, applicant_id: tenant_applications.applicant_id, application_status: tenant_applications.application_status, application_data_encrypted: tenant_applications.application_data_encrypted, submitted_on: tenant_applications.submitted_on, reviewed_on: tenant_applications.reviewed_on, }) .from(tenant_applications) .innerJoin(properties, eq(tenant_applications.property_id, properties.id)) .where(eq(properties.owner_id, landlord_id)) .orderBy(tenant_applications.submitted_on); } // Get tenant applications based on user role (tenant or landlord) // This is the main function for data isolation - filters applications by user context export async function getTenantApplicationsByUser( user_id: string, user_type: string, ) { if (user_type === "landlord") { // Landlords see applications for their properties return await getTenantApplicationsByLandlord(user_id); } else { // Tenants see only their own applications return await getApplicationsByApplicant(user_id); } } // Update tenant application status (alias for compatibility) export async function updateTenantApplicationStatus( id: string, status: "approved" | "rejected", reviewed_on?: number, ) { return await updateApplicationStatus(id, status, reviewed_on); }