Prevent duplicate time entries and unify collaborator information

Adds a unique constraint to the database schema for time entries, preventing duplicates for the same month and collaborator. Also updates the API to check for existing entries before creation and unifies the collaborator name used in the dialog.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 55837015-10e9-4be9-b857-7f5e6be73772
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 30c6f661-5ee7-47bf-80d3-9d74ec4d729b
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1cc377db-7ea0-49f2-97ce-c3e87e0228cc/55837015-10e9-4be9-b857-7f5e6be73772/NRBGdxQ
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
SylvainP1 2026-04-14 08:21:56 +00:00
parent 1df1e34c21
commit 248c9a775d
4 changed files with 19 additions and 3 deletions

View File

@ -54,6 +54,20 @@ router.post("/timesheets", async (req, res): Promise<void> => {
return;
}
const [existing] = await db
.select()
.from(timesheetsTable)
.where(and(
eq(timesheetsTable.year, parsed.data.year),
eq(timesheetsTable.month, parsed.data.month),
eq(timesheetsTable.collaborator, parsed.data.collaborator)
));
if (existing) {
res.status(409).json({ error: "Un CRA existe déjà pour ce mois et ce collaborateur" });
return;
}
const [timesheet] = await db.insert(timesheetsTable).values(parsed.data).returning();
res.status(201).json(GetTimesheetResponse.parse({ ...timesheet, lines: [] }));
});

View File

@ -173,7 +173,7 @@ function CreateTimesheetDialog({ open, onOpenChange }: { open: boolean; onOpenCh
data: {
year: parseInt(year),
month: parseInt(month),
collaborator: "Jean Dupont" // In a real app, from auth context
collaborator: "PHAM Sylvain"
}
}, {
onSuccess: (data) => {

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@ -1,4 +1,4 @@
import { pgTable, text, serial, timestamp, integer, real } from "drizzle-orm/pg-core";
import { pgTable, text, serial, timestamp, integer, real, unique } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { z } from "zod/v4";
@ -11,7 +11,9 @@ export const timesheetsTable = pgTable("timesheets", {
totalHours: real("total_hours").notNull().default(0),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow().$onUpdate(() => new Date()),
});
}, (table) => [
unique("timesheets_year_month_collaborator_unique").on(table.year, table.month, table.collaborator),
]);
export const insertTimesheetSchema = createInsertSchema(timesheetsTable).omit({ id: true, totalHours: true, createdAt: true, updatedAt: true });
export type InsertTimesheet = z.infer<typeof insertTimesheetSchema>;