Compare commits
5 Commits
850f12e09d
...
7dd3873148
| Author | SHA1 | Date | |
|---|---|---|---|
| 7dd3873148 | |||
|
|
ddb0926dba | ||
|
|
1b0f5866a1 | ||
|
|
159022ef38 | ||
|
|
1a7d13d5b9 |
@@ -0,0 +1,8 @@
|
|||||||
|
-- AlterTable: add deleted_at column to clients
|
||||||
|
ALTER TABLE "clients" ADD COLUMN "deleted_at" TIMESTAMP(3);
|
||||||
|
|
||||||
|
-- AlterTable: add deleted_at column to projects
|
||||||
|
ALTER TABLE "projects" ADD COLUMN "deleted_at" TIMESTAMP(3);
|
||||||
|
|
||||||
|
-- AlterTable: add deleted_at column to time_entries
|
||||||
|
ALTER TABLE "time_entries" ADD COLUMN "deleted_at" TIMESTAMP(3);
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
-- AlterTable: add deleted_at column to client_targets
|
||||||
|
ALTER TABLE "client_targets" ADD COLUMN "deleted_at" TIMESTAMP(3);
|
||||||
|
|
||||||
|
-- AlterTable: add deleted_at column to balance_corrections
|
||||||
|
ALTER TABLE "balance_corrections" ADD COLUMN "deleted_at" TIMESTAMP(3);
|
||||||
@@ -30,6 +30,7 @@ model Client {
|
|||||||
description String? @db.Text
|
description String? @db.Text
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
updatedAt DateTime @updatedAt @map("updated_at")
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
|
deletedAt DateTime? @map("deleted_at")
|
||||||
|
|
||||||
userId String @map("user_id") @db.VarChar(255)
|
userId String @map("user_id") @db.VarChar(255)
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
@@ -47,6 +48,7 @@ model Project {
|
|||||||
color String? @db.VarChar(7) // Hex color code
|
color String? @db.VarChar(7) // Hex color code
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
updatedAt DateTime @updatedAt @map("updated_at")
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
|
deletedAt DateTime? @map("deleted_at")
|
||||||
|
|
||||||
userId String @map("user_id") @db.VarChar(255)
|
userId String @map("user_id") @db.VarChar(255)
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
@@ -69,6 +71,7 @@ model TimeEntry {
|
|||||||
description String? @db.Text
|
description String? @db.Text
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
updatedAt DateTime @updatedAt @map("updated_at")
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
|
deletedAt DateTime? @map("deleted_at")
|
||||||
|
|
||||||
userId String @map("user_id") @db.VarChar(255)
|
userId String @map("user_id") @db.VarChar(255)
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
@@ -102,6 +105,7 @@ model ClientTarget {
|
|||||||
startDate DateTime @map("start_date") @db.Date // Always a Monday
|
startDate DateTime @map("start_date") @db.Date // Always a Monday
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
updatedAt DateTime @updatedAt @map("updated_at")
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
|
deletedAt DateTime? @map("deleted_at")
|
||||||
|
|
||||||
userId String @map("user_id") @db.VarChar(255)
|
userId String @map("user_id") @db.VarChar(255)
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
@@ -123,6 +127,7 @@ model BalanceCorrection {
|
|||||||
description String? @db.VarChar(255)
|
description String? @db.VarChar(255)
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
updatedAt DateTime @updatedAt @map("updated_at")
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
|
deletedAt DateTime? @map("deleted_at")
|
||||||
|
|
||||||
clientTargetId String @map("client_target_id")
|
clientTargetId String @map("client_target_id")
|
||||||
clientTarget ClientTarget @relation(fields: [clientTargetId], references: [id], onDelete: Cascade)
|
clientTarget ClientTarget @relation(fields: [clientTargetId], references: [id], onDelete: Cascade)
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ import type { CreateClientInput, UpdateClientInput } from "../types";
|
|||||||
export class ClientService {
|
export class ClientService {
|
||||||
async findAll(userId: string) {
|
async findAll(userId: string) {
|
||||||
return prisma.client.findMany({
|
return prisma.client.findMany({
|
||||||
where: { userId },
|
where: { userId, deletedAt: null },
|
||||||
orderBy: { name: "asc" },
|
orderBy: { name: "asc" },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async findById(id: string, userId: string) {
|
async findById(id: string, userId: string) {
|
||||||
return prisma.client.findFirst({
|
return prisma.client.findFirst({
|
||||||
where: { id, userId },
|
where: { id, userId, deletedAt: null },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,8 +43,9 @@ export class ClientService {
|
|||||||
throw new NotFoundError("Client not found");
|
throw new NotFoundError("Client not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.client.delete({
|
await prisma.client.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
|
data: { deletedAt: new Date() },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,10 +68,10 @@ export interface ClientTargetWithBalance {
|
|||||||
export class ClientTargetService {
|
export class ClientTargetService {
|
||||||
async findAll(userId: string): Promise<ClientTargetWithBalance[]> {
|
async findAll(userId: string): Promise<ClientTargetWithBalance[]> {
|
||||||
const targets = await prisma.clientTarget.findMany({
|
const targets = await prisma.clientTarget.findMany({
|
||||||
where: { userId },
|
where: { userId, deletedAt: null, client: { deletedAt: null } },
|
||||||
include: {
|
include: {
|
||||||
client: { select: { id: true, name: true } },
|
client: { select: { id: true, name: true } },
|
||||||
corrections: { orderBy: { date: 'asc' } },
|
corrections: { where: { deletedAt: null }, orderBy: { date: 'asc' } },
|
||||||
},
|
},
|
||||||
orderBy: { client: { name: 'asc' } },
|
orderBy: { client: { name: 'asc' } },
|
||||||
});
|
});
|
||||||
@@ -81,10 +81,10 @@ export class ClientTargetService {
|
|||||||
|
|
||||||
async findById(id: string, userId: string) {
|
async findById(id: string, userId: string) {
|
||||||
return prisma.clientTarget.findFirst({
|
return prisma.clientTarget.findFirst({
|
||||||
where: { id, userId },
|
where: { id, userId, deletedAt: null, client: { deletedAt: null } },
|
||||||
include: {
|
include: {
|
||||||
client: { select: { id: true, name: true } },
|
client: { select: { id: true, name: true } },
|
||||||
corrections: { orderBy: { date: 'asc' } },
|
corrections: { where: { deletedAt: null }, orderBy: { date: 'asc' } },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -97,8 +97,8 @@ export class ClientTargetService {
|
|||||||
throw new BadRequestError('startDate must be a Monday');
|
throw new BadRequestError('startDate must be a Monday');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the client belongs to this user
|
// Ensure the client belongs to this user and is not soft-deleted
|
||||||
const client = await prisma.client.findFirst({ where: { id: data.clientId, userId } });
|
const client = await prisma.client.findFirst({ where: { id: data.clientId, userId, deletedAt: null } });
|
||||||
if (!client) {
|
if (!client) {
|
||||||
throw new NotFoundError('Client not found');
|
throw new NotFoundError('Client not found');
|
||||||
}
|
}
|
||||||
@@ -106,6 +106,18 @@ export class ClientTargetService {
|
|||||||
// Check for existing target (unique per user+client)
|
// Check for existing target (unique per user+client)
|
||||||
const existing = await prisma.clientTarget.findFirst({ where: { userId, clientId: data.clientId } });
|
const existing = await prisma.clientTarget.findFirst({ where: { userId, clientId: data.clientId } });
|
||||||
if (existing) {
|
if (existing) {
|
||||||
|
if (existing.deletedAt !== null) {
|
||||||
|
// Reactivate the soft-deleted target with the new settings
|
||||||
|
const reactivated = await prisma.clientTarget.update({
|
||||||
|
where: { id: existing.id },
|
||||||
|
data: { deletedAt: null, weeklyHours: data.weeklyHours, startDate },
|
||||||
|
include: {
|
||||||
|
client: { select: { id: true, name: true } },
|
||||||
|
corrections: { where: { deletedAt: null }, orderBy: { date: 'asc' } },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return this.computeBalance(reactivated);
|
||||||
|
}
|
||||||
throw new BadRequestError('A target already exists for this client. Delete the existing one first or update it.');
|
throw new BadRequestError('A target already exists for this client. Delete the existing one first or update it.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +130,7 @@ export class ClientTargetService {
|
|||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
client: { select: { id: true, name: true } },
|
client: { select: { id: true, name: true } },
|
||||||
corrections: { orderBy: { date: 'asc' } },
|
corrections: { where: { deletedAt: null }, orderBy: { date: 'asc' } },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -148,7 +160,7 @@ export class ClientTargetService {
|
|||||||
data: updateData,
|
data: updateData,
|
||||||
include: {
|
include: {
|
||||||
client: { select: { id: true, name: true } },
|
client: { select: { id: true, name: true } },
|
||||||
corrections: { orderBy: { date: 'asc' } },
|
corrections: { where: { deletedAt: null }, orderBy: { date: 'asc' } },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -158,7 +170,10 @@ export class ClientTargetService {
|
|||||||
async delete(id: string, userId: string): Promise<void> {
|
async delete(id: string, userId: string): Promise<void> {
|
||||||
const existing = await this.findById(id, userId);
|
const existing = await this.findById(id, userId);
|
||||||
if (!existing) throw new NotFoundError('Client target not found');
|
if (!existing) throw new NotFoundError('Client target not found');
|
||||||
await prisma.clientTarget.delete({ where: { id } });
|
await prisma.clientTarget.update({
|
||||||
|
where: { id },
|
||||||
|
data: { deletedAt: new Date() },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async addCorrection(targetId: string, userId: string, data: CreateCorrectionInput) {
|
async addCorrection(targetId: string, userId: string, data: CreateCorrectionInput) {
|
||||||
@@ -188,11 +203,14 @@ export class ClientTargetService {
|
|||||||
if (!target) throw new NotFoundError('Client target not found');
|
if (!target) throw new NotFoundError('Client target not found');
|
||||||
|
|
||||||
const correction = await prisma.balanceCorrection.findFirst({
|
const correction = await prisma.balanceCorrection.findFirst({
|
||||||
where: { id: correctionId, clientTargetId: targetId },
|
where: { id: correctionId, clientTargetId: targetId, deletedAt: null },
|
||||||
});
|
});
|
||||||
if (!correction) throw new NotFoundError('Correction not found');
|
if (!correction) throw new NotFoundError('Correction not found');
|
||||||
|
|
||||||
await prisma.balanceCorrection.delete({ where: { id: correctionId } });
|
await prisma.balanceCorrection.update({
|
||||||
|
where: { id: correctionId },
|
||||||
|
data: { deletedAt: new Date() },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async computeBalance(target: {
|
private async computeBalance(target: {
|
||||||
@@ -229,6 +247,8 @@ export class ClientTargetService {
|
|||||||
AND p.client_id = ${target.clientId}
|
AND p.client_id = ${target.clientId}
|
||||||
AND te.start_time >= ${periodStart}
|
AND te.start_time >= ${periodStart}
|
||||||
AND te.start_time <= ${periodEnd}
|
AND te.start_time <= ${periodEnd}
|
||||||
|
AND te.deleted_at IS NULL
|
||||||
|
AND p.deleted_at IS NULL
|
||||||
GROUP BY DATE_TRUNC('week', te.start_time AT TIME ZONE 'UTC')
|
GROUP BY DATE_TRUNC('week', te.start_time AT TIME ZONE 'UTC')
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ export class ProjectService {
|
|||||||
return prisma.project.findMany({
|
return prisma.project.findMany({
|
||||||
where: {
|
where: {
|
||||||
userId,
|
userId,
|
||||||
|
deletedAt: null,
|
||||||
|
client: { deletedAt: null },
|
||||||
...(clientId && { clientId }),
|
...(clientId && { clientId }),
|
||||||
},
|
},
|
||||||
orderBy: { name: "asc" },
|
orderBy: { name: "asc" },
|
||||||
@@ -23,7 +25,12 @@ export class ProjectService {
|
|||||||
|
|
||||||
async findById(id: string, userId: string) {
|
async findById(id: string, userId: string) {
|
||||||
return prisma.project.findFirst({
|
return prisma.project.findFirst({
|
||||||
where: { id, userId },
|
where: {
|
||||||
|
id,
|
||||||
|
userId,
|
||||||
|
deletedAt: null,
|
||||||
|
client: { deletedAt: null },
|
||||||
|
},
|
||||||
include: {
|
include: {
|
||||||
client: {
|
client: {
|
||||||
select: {
|
select: {
|
||||||
@@ -36,9 +43,9 @@ export class ProjectService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async create(userId: string, data: CreateProjectInput) {
|
async create(userId: string, data: CreateProjectInput) {
|
||||||
// Verify the client belongs to the user
|
// Verify the client belongs to the user and is not soft-deleted
|
||||||
const client = await prisma.client.findFirst({
|
const client = await prisma.client.findFirst({
|
||||||
where: { id: data.clientId, userId },
|
where: { id: data.clientId, userId, deletedAt: null },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!client) {
|
if (!client) {
|
||||||
@@ -70,10 +77,10 @@ export class ProjectService {
|
|||||||
throw new NotFoundError("Project not found");
|
throw new NotFoundError("Project not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
// If clientId is being updated, verify it belongs to the user
|
// If clientId is being updated, verify it belongs to the user and is not soft-deleted
|
||||||
if (data.clientId) {
|
if (data.clientId) {
|
||||||
const client = await prisma.client.findFirst({
|
const client = await prisma.client.findFirst({
|
||||||
where: { id: data.clientId, userId },
|
where: { id: data.clientId, userId, deletedAt: null },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!client) {
|
if (!client) {
|
||||||
@@ -108,8 +115,9 @@ export class ProjectService {
|
|||||||
throw new NotFoundError("Project not found");
|
throw new NotFoundError("Project not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.project.delete({
|
await prisma.project.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
|
data: { deletedAt: new Date() },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,11 @@ export class TimeEntryService {
|
|||||||
COUNT(te.id)::bigint AS entry_count
|
COUNT(te.id)::bigint AS entry_count
|
||||||
FROM time_entries te
|
FROM time_entries te
|
||||||
JOIN projects p ON p.id = te.project_id
|
JOIN projects p ON p.id = te.project_id
|
||||||
|
JOIN clients c ON c.id = p.client_id
|
||||||
WHERE te.user_id = ${userId}
|
WHERE te.user_id = ${userId}
|
||||||
|
AND te.deleted_at IS NULL
|
||||||
|
AND p.deleted_at IS NULL
|
||||||
|
AND c.deleted_at IS NULL
|
||||||
${filterClause}
|
${filterClause}
|
||||||
GROUP BY p.id, p.name, p.color
|
GROUP BY p.id, p.name, p.color
|
||||||
ORDER BY total_seconds DESC
|
ORDER BY total_seconds DESC
|
||||||
@@ -69,6 +73,9 @@ export class TimeEntryService {
|
|||||||
JOIN projects p ON p.id = te.project_id
|
JOIN projects p ON p.id = te.project_id
|
||||||
JOIN clients c ON c.id = p.client_id
|
JOIN clients c ON c.id = p.client_id
|
||||||
WHERE te.user_id = ${userId}
|
WHERE te.user_id = ${userId}
|
||||||
|
AND te.deleted_at IS NULL
|
||||||
|
AND p.deleted_at IS NULL
|
||||||
|
AND c.deleted_at IS NULL
|
||||||
${filterClause}
|
${filterClause}
|
||||||
GROUP BY c.id, c.name
|
GROUP BY c.id, c.name
|
||||||
ORDER BY total_seconds DESC
|
ORDER BY total_seconds DESC
|
||||||
@@ -81,7 +88,11 @@ export class TimeEntryService {
|
|||||||
COUNT(te.id)::bigint AS entry_count
|
COUNT(te.id)::bigint AS entry_count
|
||||||
FROM time_entries te
|
FROM time_entries te
|
||||||
JOIN projects p ON p.id = te.project_id
|
JOIN projects p ON p.id = te.project_id
|
||||||
|
JOIN clients c ON c.id = p.client_id
|
||||||
WHERE te.user_id = ${userId}
|
WHERE te.user_id = ${userId}
|
||||||
|
AND te.deleted_at IS NULL
|
||||||
|
AND p.deleted_at IS NULL
|
||||||
|
AND c.deleted_at IS NULL
|
||||||
${filterClause}
|
${filterClause}
|
||||||
`,
|
`,
|
||||||
),
|
),
|
||||||
@@ -125,10 +136,11 @@ export class TimeEntryService {
|
|||||||
|
|
||||||
const where: {
|
const where: {
|
||||||
userId: string;
|
userId: string;
|
||||||
|
deletedAt: null;
|
||||||
startTime?: { gte?: Date; lte?: Date };
|
startTime?: { gte?: Date; lte?: Date };
|
||||||
projectId?: string;
|
projectId?: string;
|
||||||
project?: { clientId?: string };
|
project?: { deletedAt: null; clientId?: string; client: { deletedAt: null } };
|
||||||
} = { userId };
|
} = { userId, deletedAt: null };
|
||||||
|
|
||||||
if (startDate || endDate) {
|
if (startDate || endDate) {
|
||||||
where.startTime = {};
|
where.startTime = {};
|
||||||
@@ -140,9 +152,13 @@ export class TimeEntryService {
|
|||||||
where.projectId = projectId;
|
where.projectId = projectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clientId) {
|
// Always filter out entries whose project or client is soft-deleted,
|
||||||
where.project = { clientId };
|
// merging the optional clientId filter into the project relation filter.
|
||||||
}
|
where.project = {
|
||||||
|
deletedAt: null,
|
||||||
|
client: { deletedAt: null },
|
||||||
|
...(clientId && { clientId }),
|
||||||
|
};
|
||||||
|
|
||||||
const [entries, total] = await Promise.all([
|
const [entries, total] = await Promise.all([
|
||||||
prisma.timeEntry.findMany({
|
prisma.timeEntry.findMany({
|
||||||
@@ -182,7 +198,12 @@ export class TimeEntryService {
|
|||||||
|
|
||||||
async findById(id: string, userId: string) {
|
async findById(id: string, userId: string) {
|
||||||
return prisma.timeEntry.findFirst({
|
return prisma.timeEntry.findFirst({
|
||||||
where: { id, userId },
|
where: {
|
||||||
|
id,
|
||||||
|
userId,
|
||||||
|
deletedAt: null,
|
||||||
|
project: { deletedAt: null, client: { deletedAt: null } },
|
||||||
|
},
|
||||||
include: {
|
include: {
|
||||||
project: {
|
project: {
|
||||||
select: {
|
select: {
|
||||||
@@ -217,9 +238,9 @@ export class TimeEntryService {
|
|||||||
throw new BadRequestError("Break time cannot exceed total duration");
|
throw new BadRequestError("Break time cannot exceed total duration");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the project belongs to the user
|
// Verify the project belongs to the user and is not soft-deleted (nor its client)
|
||||||
const project = await prisma.project.findFirst({
|
const project = await prisma.project.findFirst({
|
||||||
where: { id: data.projectId, userId },
|
where: { id: data.projectId, userId, deletedAt: null, client: { deletedAt: null } },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!project) {
|
if (!project) {
|
||||||
@@ -288,10 +309,10 @@ export class TimeEntryService {
|
|||||||
throw new BadRequestError("Break time cannot exceed total duration");
|
throw new BadRequestError("Break time cannot exceed total duration");
|
||||||
}
|
}
|
||||||
|
|
||||||
// If project changed, verify it belongs to the user
|
// If project changed, verify it belongs to the user and is not soft-deleted
|
||||||
if (data.projectId && data.projectId !== entry.projectId) {
|
if (data.projectId && data.projectId !== entry.projectId) {
|
||||||
const project = await prisma.project.findFirst({
|
const project = await prisma.project.findFirst({
|
||||||
where: { id: data.projectId, userId },
|
where: { id: data.projectId, userId, deletedAt: null, client: { deletedAt: null } },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!project) {
|
if (!project) {
|
||||||
@@ -345,8 +366,9 @@ export class TimeEntryService {
|
|||||||
throw new NotFoundError("Time entry not found");
|
throw new NotFoundError("Time entry not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.timeEntry.delete({
|
await prisma.timeEntry.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
|
data: { deletedAt: new Date() },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export interface Client {
|
|||||||
description: string | null;
|
description: string | null;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
deletedAt: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Project {
|
export interface Project {
|
||||||
@@ -22,6 +23,7 @@ export interface Project {
|
|||||||
client: Pick<Client, 'id' | 'name'>;
|
client: Pick<Client, 'id' | 'name'>;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
deletedAt: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TimeEntry {
|
export interface TimeEntry {
|
||||||
@@ -36,6 +38,7 @@ export interface TimeEntry {
|
|||||||
};
|
};
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
deletedAt: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OngoingTimer {
|
export interface OngoingTimer {
|
||||||
@@ -149,6 +152,7 @@ export interface BalanceCorrection {
|
|||||||
hours: number;
|
hours: number;
|
||||||
description: string | null;
|
description: string | null;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
deletedAt: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WeekBalance {
|
export interface WeekBalance {
|
||||||
|
|||||||
Reference in New Issue
Block a user