195 lines
5.0 KiB
TypeScript
195 lines
5.0 KiB
TypeScript
// services/rentChangeService.ts
|
|
// Handle rent change history tracking for properties
|
|
|
|
import { desc, eq } from "drizzle-orm";
|
|
import { db } from "../db/db.ts";
|
|
import { properties, rent_changes } from "../db/schema.ts";
|
|
|
|
// Use the type from the schema
|
|
export type RentChange = typeof rent_changes.$inferSelect;
|
|
|
|
/**
|
|
* Create a new rent change record
|
|
* Also updates the property's monthly_rent to the new value
|
|
*/
|
|
export async function createRentChange(
|
|
property_id: string,
|
|
effective_date: number,
|
|
monthly_rent: number,
|
|
options: {
|
|
reason?: string;
|
|
created_by?: string;
|
|
} = {},
|
|
) {
|
|
// First, get the current rent from the property
|
|
const propertyResult = await db.select({
|
|
monthly_rent: properties.monthly_rent,
|
|
})
|
|
.from(properties)
|
|
.where(eq(properties.id, property_id))
|
|
.limit(1);
|
|
|
|
const previous_rent = propertyResult[0]?.monthly_rent || null;
|
|
|
|
const now = Date.now();
|
|
|
|
// Create the rent change record
|
|
const result = await db.insert(rent_changes)
|
|
.values({
|
|
id: crypto.randomUUID(),
|
|
property_id,
|
|
effective_date,
|
|
monthly_rent,
|
|
previous_rent,
|
|
reason: options.reason || null,
|
|
created_by: options.created_by || null,
|
|
created_on: now,
|
|
})
|
|
.returning();
|
|
|
|
// Update the property with the new rent
|
|
await db.update(properties)
|
|
.set({ monthly_rent, updated_on: now })
|
|
.where(eq(properties.id, property_id));
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get all rent changes for a property, ordered by effective date (newest first)
|
|
*/
|
|
export async function getRentChangesByProperty(
|
|
property_id: string,
|
|
): Promise<RentChange[]> {
|
|
return await db.select()
|
|
.from(rent_changes)
|
|
.where(eq(rent_changes.property_id, property_id))
|
|
.orderBy(desc(rent_changes.effective_date));
|
|
}
|
|
|
|
/**
|
|
* Get the current rent for a property (most recent effective date that has passed)
|
|
*/
|
|
export async function getCurrentRentForProperty(
|
|
property_id: string,
|
|
): Promise<number | null> {
|
|
const now = Date.now();
|
|
const result = await db.select()
|
|
.from(rent_changes)
|
|
.where(eq(rent_changes.property_id, property_id))
|
|
.orderBy(desc(rent_changes.effective_date))
|
|
.limit(1);
|
|
|
|
// If there are rent changes, return the most recent one
|
|
if (result.length > 0) {
|
|
return result[0].monthly_rent;
|
|
}
|
|
|
|
// Otherwise, fall back to the property's base monthly_rent
|
|
const propertyResult = await db.select({
|
|
monthly_rent: properties.monthly_rent,
|
|
})
|
|
.from(properties)
|
|
.where(eq(properties.id, property_id))
|
|
.limit(1);
|
|
|
|
return propertyResult[0]?.monthly_rent || null;
|
|
}
|
|
|
|
/**
|
|
* Get rent change by ID
|
|
*/
|
|
export async function getRentChangeById(
|
|
changeId: string,
|
|
): Promise<RentChange | null> {
|
|
const result = await db.select()
|
|
.from(rent_changes)
|
|
.where(eq(rent_changes.id, changeId));
|
|
return result[0] || null;
|
|
}
|
|
|
|
/**
|
|
* Get upcoming rent changes for a property (effective_date is in the future)
|
|
*/
|
|
export async function getUpcomingRentChanges(
|
|
property_id: string,
|
|
): Promise<RentChange[]> {
|
|
const now = Date.now();
|
|
return await db.select()
|
|
.from(rent_changes)
|
|
.where(eq(rent_changes.property_id, property_id))
|
|
.orderBy(desc(rent_changes.effective_date));
|
|
}
|
|
|
|
/**
|
|
* Get rent history for a property including current rent from the property record
|
|
*/
|
|
export async function getRentHistory(property_id: string): Promise<
|
|
Array<{
|
|
effective_date: number;
|
|
monthly_rent: number;
|
|
previous_rent: number | null;
|
|
reason: string | null;
|
|
is_current: boolean;
|
|
}>
|
|
> {
|
|
// Get the property's current rent
|
|
const propertyResult = await db.select({
|
|
monthly_rent: properties.monthly_rent,
|
|
updated_on: properties.updated_on,
|
|
})
|
|
.from(properties)
|
|
.where(eq(properties.id, property_id))
|
|
.limit(1);
|
|
|
|
const property = propertyResult[0];
|
|
|
|
// Get all rent changes
|
|
const changes = await getRentChangesByProperty(property_id);
|
|
|
|
// Build history including current property rent as the baseline
|
|
const history: Array<{
|
|
effective_date: number;
|
|
monthly_rent: number;
|
|
previous_rent: number | null;
|
|
reason: string | null;
|
|
is_current: boolean;
|
|
}> = [];
|
|
|
|
// Add current property rent as the starting point
|
|
if (property && property.monthly_rent) {
|
|
history.push({
|
|
effective_date: property.updated_on || Date.now(),
|
|
monthly_rent: property.monthly_rent,
|
|
previous_rent: null,
|
|
reason: "Initial rent",
|
|
is_current: true,
|
|
});
|
|
}
|
|
|
|
// Add all historical changes
|
|
for (const change of changes) {
|
|
history.push({
|
|
effective_date: change.effective_date || 0,
|
|
monthly_rent: change.monthly_rent || 0,
|
|
previous_rent: change.previous_rent,
|
|
reason: change.reason,
|
|
is_current: false,
|
|
});
|
|
}
|
|
|
|
// Sort by effective date descending (newest first)
|
|
history.sort((a, b) => b.effective_date - a.effective_date);
|
|
|
|
return history;
|
|
}
|
|
|
|
/**
|
|
* Delete a rent change record
|
|
*/
|
|
export async function deleteRentChange(changeId: string) {
|
|
return await db.delete(rent_changes)
|
|
.where(eq(rent_changes.id, changeId))
|
|
.returning();
|
|
}
|