refactor(reporting): enhance timezone handling and clean up migration

- Updated SQL queries in reporting controllers to join with the timezones table for accurate timezone retrieval.
- Refactored ReportingMembersController to extend ReportingControllerBaseWithTimezone for centralized timezone logic.
- Removed obsolete migration file that added a timezone column to the users table, as it is no longer needed.
This commit is contained in:
chamikaJ
2025-07-24 09:25:50 +05:30
parent 69b2fe1a90
commit de26417247
5 changed files with 29 additions and 25 deletions

View File

@@ -4,7 +4,9 @@
"Bash(find:*)",
"Bash(npm run build:*)",
"Bash(npm run type-check:*)",
"Bash(npm run:*)"
"Bash(npm run:*)",
"Bash(grep:*)",
"Bash(rm:*)"
],
"deny": []
}

View File

@@ -14,10 +14,10 @@ export async function getMemberTimeSheets(req: IWorkLenzRequest, res: IWorkLenzR
const teamIds = teams.map(id => `'${id}'`).join(",");
const projects = (req.body.projects || []) as string[];
const projectIds = projects.map(p => `'${p}'`).join(",");
const billable = req.body.billable;
const {billable} = req.body;
// Get user timezone from request or database
const userTimezone = req.body.timezone || await getUserTimezone(req.user?.id || '');
const userTimezone = req.body.timezone || await getUserTimezone(req.user?.id || "");
if (!teamIds || !projectIds.length)
return res.status(200).send(new ServerResponse(true, { users: [], projects: [] }));
@@ -30,13 +30,13 @@ export async function getMemberTimeSheets(req: IWorkLenzRequest, res: IWorkLenzR
if (date_range && date_range.length === 2) {
// Convert user's local dates to their timezone's start/end of day
startDate = moment.tz(date_range[0], userTimezone).startOf('day');
endDate = moment.tz(date_range[1], userTimezone).endOf('day');
startDate = moment.tz(date_range[0], userTimezone).startOf("day");
endDate = moment.tz(date_range[1], userTimezone).endOf("day");
} else if (duration === DATE_RANGES.ALL_TIME) {
const minDateQuery = `SELECT MIN(COALESCE(start_date, created_at)) as min_date FROM projects WHERE id IN (${projectIds})`;
const minDateResult = await db.query(minDateQuery, []);
const minDate = minDateResult.rows[0]?.min_date;
startDate = minDate ? moment.tz(minDate, userTimezone) : moment.tz('2000-01-01', userTimezone);
startDate = minDate ? moment.tz(minDate, userTimezone) : moment.tz("2000-01-01", userTimezone);
endDate = moment.tz(userTimezone);
} else {
// Calculate ranges based on user's timezone
@@ -44,8 +44,8 @@ export async function getMemberTimeSheets(req: IWorkLenzRequest, res: IWorkLenzR
switch (duration) {
case DATE_RANGES.YESTERDAY:
startDate = now.clone().subtract(1, "day").startOf('day');
endDate = now.clone().subtract(1, "day").endOf('day');
startDate = now.clone().subtract(1, "day").startOf("day");
endDate = now.clone().subtract(1, "day").endOf("day");
break;
case DATE_RANGES.LAST_WEEK:
startDate = now.clone().subtract(1, "week").startOf("isoWeek");
@@ -74,11 +74,11 @@ export async function getMemberTimeSheets(req: IWorkLenzRequest, res: IWorkLenzR
let workingDays = 0;
const current = startDate.clone();
while (current.isSameOrBefore(endDate, 'day')) {
while (current.isSameOrBefore(endDate, "day")) {
if (current.isoWeekday() >= 1 && current.isoWeekday() <= 5) {
workingDays++;
}
current.add(1, 'day');
current.add(1, "day");
}
// Updated SQL query with proper timezone handling
@@ -154,9 +154,12 @@ export async function getMemberTimeSheets(req: IWorkLenzRequest, res: IWorkLenzR
}
async function getUserTimezone(userId: string): Promise<string> {
const q = `SELECT timezone FROM users WHERE id = $1`;
const q = `SELECT tz.name as timezone
FROM users u
JOIN timezones tz ON u.timezone_id = tz.id
WHERE u.id = $1`;
const result = await db.query(q, [userId]);
return result.rows[0]?.timezone || 'UTC';
return result.rows[0]?.timezone || "UTC";
}
function buildBillableQuery(billable: { billable: boolean; nonBillable: boolean }): string {

View File

@@ -12,7 +12,10 @@ export default abstract class ReportingControllerBaseWithTimezone extends Workle
* @returns The user's timezone or 'UTC' as default
*/
protected static async getUserTimezone(userId: string): Promise<string> {
const q = `SELECT timezone FROM users WHERE id = $1`;
const q = `SELECT tz.name as timezone
FROM users u
JOIN timezones tz ON u.timezone_id = tz.id
WHERE u.id = $1`;
const result = await db.query(q, [userId]);
return result.rows[0]?.timezone || 'UTC';
}

View File

@@ -6,10 +6,10 @@ import { IWorkLenzResponse } from "../../interfaces/worklenz-response";
import { ServerResponse } from "../../models/server-response";
import { DATE_RANGES, TASK_PRIORITY_COLOR_ALPHA } from "../../shared/constants";
import { formatDuration, getColor, int } from "../../shared/utils";
import ReportingControllerBase from "./reporting-controller-base";
import ReportingControllerBaseWithTimezone from "./reporting-controller-base-with-timezone";
import Excel from "exceljs";
export default class ReportingMembersController extends ReportingControllerBase {
export default class ReportingMembersController extends ReportingControllerBaseWithTimezone {
private static async getMembers(
teamId: string, searchQuery = "",
@@ -487,7 +487,9 @@ export default class ReportingMembersController extends ReportingControllerBase
dateRange = date_range.split(",");
}
const durationClause = ReportingMembersController.getDateRangeClauseMembers(duration as string || DATE_RANGES.LAST_WEEK, dateRange, "twl");
// Get user timezone for proper date filtering
const userTimezone = await this.getUserTimezone(req.user?.id as string);
const durationClause = this.getDateRangeClauseWithTimezone(duration as string || DATE_RANGES.LAST_WEEK, dateRange, userTimezone);
const minMaxDateClause = this.getMinMaxDates(duration as string || DATE_RANGES.LAST_WEEK, dateRange, "task_work_log");
const memberName = (req.query.member_name as string)?.trim() || null;
@@ -1038,7 +1040,9 @@ export default class ReportingMembersController extends ReportingControllerBase
public static async getMemberTimelogs(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const { team_member_id, team_id, duration, date_range, archived, billable } = req.body;
const durationClause = ReportingMembersController.getDateRangeClauseMembers(duration || DATE_RANGES.LAST_WEEK, date_range, "twl");
// Get user timezone for proper date filtering
const userTimezone = await this.getUserTimezone(req.user?.id as string);
const durationClause = this.getDateRangeClauseWithTimezone(duration || DATE_RANGES.LAST_WEEK, date_range, userTimezone);
const minMaxDateClause = this.getMinMaxDates(duration || DATE_RANGES.LAST_WEEK, date_range, "task_work_log");
const billableQuery = this.buildBillableQuery(billable);

View File

@@ -1,8 +0,0 @@
-- Add timezone column to users table
ALTER TABLE users ADD COLUMN IF NOT EXISTS timezone VARCHAR(50) DEFAULT 'UTC';
-- Add index for better query performance
CREATE INDEX IF NOT EXISTS idx_users_timezone ON users(timezone);
-- Update existing users to use their browser timezone (this would be done via application logic)
COMMENT ON COLUMN users.timezone IS 'IANA timezone identifier (e.g., America/New_York, Asia/Tokyo)';