Rename regular absences to recurring absences

This commit is contained in:
Karl Ludwig Weise
2026-07-21 16:51:11 +02:00
parent 6adc09029f
commit 46a33263c8
19 changed files with 75 additions and 75 deletions
+2 -2
View File
@@ -661,8 +661,8 @@
"title": "Einmalig",
"description": "für Urlaub etc."
},
"REGULAR": {
"title": "Dauerhaft",
"RECURRING": {
"title": "Wiederholend",
"description": "jede Woche"
}
}
+2 -2
View File
@@ -669,8 +669,8 @@
"title": "One-Time",
"description": "for vacation etc."
},
"REGULAR": {
"title": "Regular",
"RECURRING": {
"title": "Recurring",
"description": "every week"
}
}
+5 -5
View File
@@ -26,7 +26,7 @@ export const appointmentStatusEnum = pgEnum("appointment_status", [
"NO_SHOW",
]);
export const absenceTypeEnum = pgEnum("agent_absence_type", ["ONE_TIME", "REGULAR"]);
export const absenceTypeEnum = pgEnum("agent_absence_type", ["ONE_TIME", "RECURRING"]);
export const notificationTypes = [
"APPOINTMENT_CONFIRMED",
@@ -202,7 +202,7 @@ export const appointment = pgTable("appointment", {
export const agentAbsence = pgTable("agent_absence", {
/** Primary key - unique identifier */
id: uuid("id").primaryKey().defaultRandom(),
/** Enum ONE_TIME or REGULAR, defaults to ONE_TIME */
/** Enum ONE_TIME or RECURRING, defaults to ONE_TIME */
type: absenceTypeEnum("type").notNull().default("ONE_TIME"),
/** Foreign key to agent who is absent */
agentId: uuid("agent_id")
@@ -216,11 +216,11 @@ export const agentAbsence = pgTable("agent_absence", {
absenceType: text("absence_type").notNull().default(""),
/** Optional description/reason for absence */
description: text("description"),
/** Set, when of type REGULAR: Bitmask for weekdays (1=Monday, 2=Tuesday, 4=Wednesday, etc.), is NULL by default */
/** Set, when of type RECURRING: Bitmask for weekdays (1=Monday, 2=Tuesday, 4=Wednesday, etc.), is NULL by default */
weekdays: integer("weekdays"),
/** Set, when of type REGULAR: Start time for the regular absence */
/** Set, when of type RECURRING: Start time for the recurring absence */
from: time("from"),
/** Set, when of type REGULAR: End time for the regular absence */
/** Set, when of type RECURRING: End time for the recurring absence */
to: time("to"),
});
@@ -747,9 +747,9 @@ describe("AgentService", () => {
});
});
it("should create regular absence successfully", async () => {
it("should create recurring absence successfully", async () => {
const request: AbsenceCreationRequest = {
type: "REGULAR",
type: "RECURRING",
agentId: "123e4567-e89b-12d3-a456-426614174001",
startDate: "2024-01-01T08:00:00.000Z",
endDate: "2024-01-01T17:00:00.000Z",
@@ -833,9 +833,9 @@ describe("AgentService", () => {
).rejects.toThrow(ValidationError);
});
it("should validate regular absence from-time to be before to-time", async () => {
it("should validate recurring absence from-time to be before to-time", async () => {
const invalidRequest: AbsenceCreationRequest = {
type: "REGULAR",
type: "RECURRING",
agentId: "123e4567-e89b-12d3-a456-426614174001",
startDate: "2024-01-01T08:00:00.000Z",
endDate: "2024-01-01T17:00:00.000Z",
@@ -1063,7 +1063,7 @@ describe("AgentService", () => {
it("should update absence successfully", async () => {
const updateData: AbsenceUpdateRequest = {
type: "REGULAR",
type: "RECURRING",
absenceType: "Krankheit",
description: "Away every Wed and Fri afternoon",
weekdays: 8,
@@ -1120,7 +1120,7 @@ describe("AgentService", () => {
it("should validate update request", async () => {
const invalidUpdate: AbsenceUpdateRequest = {
type: "REGULAR",
type: "RECURRING",
startDate: "2024-01-01T08:00:00.000Z",
endDate: "2024-01-01T17:00:00.000Z",
absenceType: "Urlaub",
@@ -837,7 +837,7 @@ describe("ScheduleService", () => {
expect(channel2Schedule.availableSlots[0].from).toBe("2024-01-01T09:00:00.000Z");
});
it("should exclude slots where an agent has a regular absence", async () => {
it("should exclude slots where an agent has a recurring absence", async () => {
const validRequest: ScheduleRequest = {
startDate: "2024-01-01T00:00:00.000Z",
endDate: "2024-01-01T23:59:59.999Z",
@@ -874,7 +874,7 @@ describe("ScheduleService", () => {
const mockAbsences = [
{
id: "absence1",
type: "REGULAR",
type: "RECURRING",
agentId: "agent1",
startDate: "2024-01-01T00:00:00.000Z",
endDate: "2024-01-01T23:59:59.999Z",
+9 -9
View File
@@ -34,7 +34,7 @@ const absenceCreationSchema = z.discriminatedUnion("type", [
description: z.string().optional(),
}),
z.object({
type: z.literal("REGULAR"),
type: z.literal("RECURRING"),
agentId: z.uuid({ message: "Invalid UUID format" }),
startDate: z.string().datetime({ message: "Invalid datetime format" }),
endDate: z.string().datetime({ message: "Invalid datetime format" }),
@@ -55,7 +55,7 @@ const absenceUpdateSchema = z.discriminatedUnion("type", [
description: z.string().optional(),
}),
z.object({
type: z.literal("REGULAR"),
type: z.literal("RECURRING"),
startDate: z.string().datetime({ message: "Invalid datetime format" }).optional(),
endDate: z.string().datetime({ message: "Invalid datetime format" }).optional(),
absenceType: z.string().min(1).max(100).optional(),
@@ -477,7 +477,7 @@ export class AgentService {
}
// Validate time settings
if (request.type === "REGULAR" && !isToAfterFrom(request.from, request.to)) {
if (request.type === "RECURRING" && !isToAfterFrom(request.from, request.to)) {
throw new ValidationError("Impossible time setting: `to` must be after `from` time");
}
@@ -507,7 +507,7 @@ export class AgentService {
// Check for overlapping absences
const overlappingAbsences =
request.type === "REGULAR"
request.type === "RECURRING"
? []
: await db
.select()
@@ -554,9 +554,9 @@ export class AgentService {
endDate: new Date(request.endDate),
absenceType: request.absenceType,
description: request.description,
weekdays: request.type === "REGULAR" ? request.weekdays : null,
from: request.type === "REGULAR" ? request.from : null,
to: request.type === "REGULAR" ? request.to : null,
weekdays: request.type === "RECURRING" ? request.weekdays : null,
from: request.type === "RECURRING" ? request.from : null,
to: request.type === "RECURRING" ? request.to : null,
})
.returning();
@@ -809,7 +809,7 @@ export class AgentService {
}
// Validate time settings
if (updateData.type === "REGULAR" && !isToAfterFrom(updateData.from, updateData.to)) {
if (updateData.type === "RECURRING" && !isToAfterFrom(updateData.from, updateData.to)) {
throw new ValidationError("Impossible time setting: `to` must be after `from` time");
}
@@ -844,7 +844,7 @@ export class AgentService {
// Check for overlapping absences (excluding current absence)
const overlappingAbsences =
updateData.type === "REGULAR"
updateData.type === "RECURRING"
? []
: await db
.select()
+1 -1
View File
@@ -495,7 +495,7 @@ export class ScheduleService {
case "ONE_TIME": {
return slotStartsDuringAbsence || slotEndsDuringAbsence || slotCoversEntireAbsence;
}
case "REGULAR": {
case "RECURRING": {
return (
(slotStartsDuringAbsence || slotEndsDuringAbsence || slotCoversEntireAbsence) &&
isWithin(slotStart, slotEnd, absence.weekdays, absence.from, absence.to, timeZone)
@@ -102,7 +102,7 @@
bind:value={$formData.type}
options={Object.values(types)}
onValueChange={(v) => {
if (v === "REGULAR") {
if (v === "RECURRING") {
isAllDay = true;
$formData.absenceType = "OTHER";
$formData.weekdays = 0;
@@ -145,7 +145,7 @@
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<Form.Field {form} name="absenceType" class={$formData.type === "REGULAR" ? "hidden" : ""}>
<Form.Field {form} name="absenceType" class={$formData.type === "RECURRING" ? "hidden" : ""}>
<Form.Control>
{#snippet children({ props })}
<Form.Label>{m["absences.add.fields.absenceType.title"]()}</Form.Label>
@@ -179,9 +179,9 @@
setTimeout(() => (endDateTouched = false), 100);
}
}}
class={cn("mt-2 mb-1", $formData.type === "REGULAR" ? "hidden" : "")}
class={cn("mt-2 mb-1", $formData.type === "RECURRING" ? "hidden" : "")}
/>
<div class={cn($formData.type === "REGULAR" ? "grid grid-cols-2 gap-2" : "")}>
<div class={cn($formData.type === "RECURRING" ? "grid grid-cols-2 gap-2" : "")}>
<Form.Field {form} name="startDate">
<Form.Control>
{#snippet children({ props })}
@@ -231,7 +231,7 @@
<Form.FieldErrors />
</Form.Field>
</div>
{#if $formData.type === "REGULAR"}
{#if $formData.type === "RECURRING"}
<Form.Field {form} name="weekdays">
<Form.Control>
{#snippet children({ props })}
@@ -4,7 +4,7 @@ import { z } from "zod";
export const formSchema = z
.object({
type: z.enum(["ONE_TIME", "REGULAR"]),
type: z.enum(["ONE_TIME", "RECURRING"]),
agent: z.string().uuid({ message: m["form.errors.noAgentsSelected"]() }),
absenceType: z.string().min(1).max(100),
description: z.string().optional(),
@@ -21,7 +21,7 @@ export const formSchema = z
.optional(),
})
.superRefine((data, ctx) => {
if (data.type === "REGULAR") {
if (data.type === "RECURRING") {
if (data.weekdays == null || data.weekdays === 0) {
ctx.addIssue({
code: "custom",
@@ -109,7 +109,7 @@
bind:value={$formData.type}
options={Object.values(types)}
onValueChange={(v) => {
if (v === "REGULAR") {
if (v === "RECURRING") {
isAllDay = true;
$formData.absenceType = "OTHER";
$formData.weekdays = 0;
@@ -153,7 +153,7 @@
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<Form.Field {form} name="absenceType" class={$formData.type === "REGULAR" ? "hidden" : ""}>
<Form.Field {form} name="absenceType" class={$formData.type === "RECURRING" ? "hidden" : ""}>
<Form.Control>
{#snippet children({ props })}
<Form.Label>{m["absences.add.fields.absenceType.title"]()}</Form.Label>
@@ -187,9 +187,9 @@
setTimeout(() => (endDateTouched = false), 100);
}
}}
class={cn("mt-2 mb-1", $formData.type === "REGULAR" ? "hidden" : "")}
class={cn("mt-2 mb-1", $formData.type === "RECURRING" ? "hidden" : "")}
/>
<div class={cn($formData.type === "REGULAR" ? "grid grid-cols-2 gap-2" : "")}>
<div class={cn($formData.type === "RECURRING" ? "grid grid-cols-2 gap-2" : "")}>
<Form.Field {form} name="startDate">
<Form.Control>
{#snippet children({ props })}
@@ -239,7 +239,7 @@
<Form.FieldErrors />
</Form.Field>
</div>
{#if $formData.type === "REGULAR"}
{#if $formData.type === "RECURRING"}
<Form.Field {form} name="weekdays">
<Form.Control>
{#snippet children({ props })}
@@ -5,7 +5,7 @@ import { z } from "zod";
export const formSchema = z
.object({
id: z.string(),
type: z.enum(["ONE_TIME", "REGULAR"]),
type: z.enum(["ONE_TIME", "RECURRING"]),
agent: z.string().uuid({ message: m["form.errors.noAgentsSelected"]() }),
absenceType: z.string().min(1).max(100),
description: z.string().optional(),
@@ -22,7 +22,7 @@ export const formSchema = z
.nullable(),
})
.superRefine((data, ctx) => {
if (data.type === "REGULAR") {
if (data.type === "RECURRING") {
if (data.weekdays == null || data.weekdays === 0) {
ctx.addIssue({
code: "custom",
@@ -17,10 +17,10 @@ export const types = {
title: m["absences.types.ONE_TIME.title"](),
description: m["absences.types.ONE_TIME.description"](),
},
REGULAR: {
RECURRING: {
icon: RefreshCw,
value: "REGULAR",
title: m["absences.types.REGULAR.title"](),
description: m["absences.types.REGULAR.description"](),
value: "RECURRING",
title: m["absences.types.RECURRING.title"](),
description: m["absences.types.RECURRING.description"](),
},
};
@@ -84,7 +84,7 @@
title={agent?.name || item.agentId}
image={agent?.image || UnknownItemIcon}
description={renderDescription(item)}
icons={item.type === "REGULAR" ? [RefreshCw] : []}
icons={item.type === "RECURRING" ? [RefreshCw] : []}
actions={[
{
type: "action",
@@ -10,8 +10,8 @@ import { getLocalTimeZone } from "@internationalized/date";
export const renderAbsenceTimespan = (item: TAbsence) => {
if (item.type === "ONE_TIME") {
return renderOneTimeAbsenceTimespan(item);
} else if (item.type === "REGULAR") {
return renderRegularAbsenceTimespan(item);
} else if (item.type === "RECURRING") {
return renderRecurringAbsenceTimespan(item);
} else {
return "";
}
@@ -48,7 +48,7 @@ export const renderOneTimeAbsenceTimespan = (item: TAbsence) => {
}
};
export const renderRegularAbsenceTimespan = (item: TAbsence) => {
export const renderRecurringAbsenceTimespan = (item: TAbsence) => {
if (!item.from || !item.to) {
return "";
}
@@ -37,7 +37,7 @@ registerOpenAPIRoute("/tenants/{id}/agents/{agentId}/absences", "POST", {
properties: {
type: {
type: "string",
enum: ["ONE_TIME", "REGULAR"],
enum: ["ONE_TIME", "RECURRING"],
description: "Type of absence",
example: "ONE_TIME",
},
@@ -67,19 +67,19 @@ registerOpenAPIRoute("/tenants/{id}/agents/{agentId}/absences", "POST", {
weekdays: {
type: "integer",
description:
"Bitmask representing weekdays for regular absences (0 = Sunday, 1 = Monday, ..., 6 = Saturday). Only applicable for REGULAR absences.",
"Bitmask representing weekdays for recurring absences (0 = Sunday, 1 = Monday, ..., 6 = Saturday). Only applicable for RECURRING absences.",
example: 62,
},
from: {
type: "string",
format: "time",
description: "Start time for REGULAR absences",
description: "Start time for RECURRING absences",
example: "09:00:00",
},
to: {
type: "string",
format: "time",
description: "End time for REGULAR absences",
description: "End time for RECURRING absences",
example: "17:00:00",
},
},
@@ -103,7 +103,7 @@ registerOpenAPIRoute("/tenants/{id}/agents/{agentId}/absences", "POST", {
id: { type: "string", format: "uuid", description: "Absence ID" },
type: {
type: "string",
enum: ["ONE_TIME", "REGULAR"],
enum: ["ONE_TIME", "RECURRING"],
description: "Type of absence",
},
agentId: { type: "string", format: "uuid", description: "Agent ID" },
@@ -114,17 +114,17 @@ registerOpenAPIRoute("/tenants/{id}/agents/{agentId}/absences", "POST", {
weekdays: {
type: "integer",
description:
"Bitmask representing weekdays for regular absences (0 = Sunday, 1 = Monday, ..., 6 = Saturday). Only applicable for REGULAR absences.",
"Bitmask representing weekdays for recurring absences (0 = Sunday, 1 = Monday, ..., 6 = Saturday). Only applicable for RECURRING absences.",
},
from: {
type: "string",
format: "time",
description: "Start time for REGULAR absences",
description: "Start time for RECURRING absences",
},
to: {
type: "string",
format: "time",
description: "End time for REGULAR absences",
description: "End time for RECURRING absences",
},
},
required: ["id", "type", "agentId", "startDate", "endDate", "absenceType"],
@@ -238,7 +238,7 @@ registerOpenAPIRoute("/tenants/{id}/agents/{agentId}/absences", "GET", {
id: { type: "string", format: "uuid", description: "Absence ID" },
type: {
type: "string",
enum: ["ONE_TIME", "REGULAR"],
enum: ["ONE_TIME", "RECURRING"],
description: "Type of absence",
},
agentId: { type: "string", format: "uuid", description: "Agent ID" },
@@ -249,17 +249,17 @@ registerOpenAPIRoute("/tenants/{id}/agents/{agentId}/absences", "GET", {
weekdays: {
type: "integer",
description:
"Bitmask representing weekdays for regular absences (0 = Sunday, 1 = Monday, ..., 6 = Saturday). Only applicable for REGULAR absences.",
"Bitmask representing weekdays for recurring absences (0 = Sunday, 1 = Monday, ..., 6 = Saturday). Only applicable for RECURRING absences.",
},
from: {
type: "string",
format: "time",
description: "Start time for REGULAR absences",
description: "Start time for RECURRING absences",
},
to: {
type: "string",
format: "time",
description: "End time for REGULAR absences",
description: "End time for RECURRING absences",
},
},
required: ["id", "type", "agentId", "startDate", "endDate", "absenceType"],
@@ -55,7 +55,7 @@ registerOpenAPIRoute("/tenants/{id}/agents/{agentId}/absences/{absenceId}", "GET
id: { type: "string", format: "uuid", description: "Absence ID" },
type: {
type: "string",
enum: ["ONE_TIME", "REGULAR"],
enum: ["ONE_TIME", "RECURRING"],
description: "Type of absence",
},
agentId: { type: "string", format: "uuid", description: "Agent ID" },
@@ -66,17 +66,17 @@ registerOpenAPIRoute("/tenants/{id}/agents/{agentId}/absences/{absenceId}", "GET
weekdays: {
type: "integer",
description:
"Bitmask representing weekdays for regular absences (0 = Sunday, 1 = Monday, ..., 6 = Saturday). Only applicable for REGULAR absences.",
"Bitmask representing weekdays for recurring absences (0 = Sunday, 1 = Monday, ..., 6 = Saturday). Only applicable for RECURRING absences.",
},
from: {
type: "string",
format: "time",
description: "Start time for REGULAR absences",
description: "Start time for RECURRING absences",
},
to: {
type: "string",
format: "time",
description: "End time for REGULAR absences",
description: "End time for RECURRING absences",
},
},
required: ["id", "type", "agentId", "startDate", "endDate", "absenceType"],
@@ -201,7 +201,7 @@ registerOpenAPIRoute("/tenants/{id}/agents/{agentId}/absences/{absenceId}", "PUT
id: { type: "string", format: "uuid", description: "Absence ID" },
type: {
type: "string",
enum: ["ONE_TIME", "REGULAR"],
enum: ["ONE_TIME", "RECURRING"],
description: "Type of absence",
},
agentId: { type: "string", format: "uuid", description: "Agent ID" },
@@ -212,17 +212,17 @@ registerOpenAPIRoute("/tenants/{id}/agents/{agentId}/absences/{absenceId}", "PUT
weekdays: {
type: "integer",
description:
"Bitmask representing weekdays for regular absences (0 = Sunday, 1 = Monday, ..., 6 = Saturday). Only applicable for REGULAR absences.",
"Bitmask representing weekdays for recurring absences (0 = Sunday, 1 = Monday, ..., 6 = Saturday). Only applicable for RECURRING absences.",
},
from: {
type: "string",
format: "time",
description: "Start time for REGULAR absences",
description: "Start time for RECURRING absences",
},
to: {
type: "string",
format: "time",
description: "End time for REGULAR absences",
description: "End time for RECURRING absences",
},
},
required: ["id", "type", "agentId", "startDate", "endDate", "absenceType"],
@@ -51,7 +51,7 @@ registerOpenAPIRoute("/tenants/{id}/agents/absences", "GET", {
id: { type: "string", format: "uuid", description: "Absence ID" },
type: {
type: "string",
enum: ["REGULAR", "ONE_TIME"],
enum: ["RECURRING", "ONE_TIME"],
description: "Type of absence",
},
agentId: { type: "string", format: "uuid", description: "Agent ID" },
@@ -61,17 +61,17 @@ registerOpenAPIRoute("/tenants/{id}/agents/absences", "GET", {
description: { type: "string", description: "Description" },
weekdays: {
type: "integer",
description: "Weekdays bitmask for regular absences",
description: "Weekdays bitmask for recurring absences",
},
from: {
type: "string",
format: "time",
description: "Start time for regular absences",
description: "Start time for recurring absences",
},
to: {
type: "string",
format: "time",
description: "End time for regular absences",
description: "End time for recurring absences",
},
},
required: [
@@ -1,4 +1,4 @@
CREATE TYPE "public"."absence_type" AS ENUM('ONE_TIME', 'REGULAR');--> statement-breakpoint
CREATE TYPE "public"."absence_type" AS ENUM('ONE_TIME', 'RECURRING');--> statement-breakpoint
ALTER TABLE "agent_absence" ADD COLUMN "type" "absence_type" DEFAULT 'ONE_TIME' NOT NULL;--> statement-breakpoint
ALTER TABLE "agent_absence" ADD COLUMN "weekdays" integer;--> statement-breakpoint
ALTER TABLE "agent_absence" ADD COLUMN "from" time;--> statement-breakpoint
+1 -1
View File
@@ -994,7 +994,7 @@
"public.agent_absence_type": {
"name": "agent_absence_type",
"schema": "public",
"values": ["ONE_TIME", "REGULAR"]
"values": ["ONE_TIME", "RECURRING"]
},
"public.appointment_status": {
"name": "appointment_status",