213 calendar when staff creates appointment for a channel with reservation the email still should be for a successfull appointment booking (#218)

* If staff creates an appointment, treat it as being already confirmed

* Fixed tests
This commit is contained in:
Hendrik
2026-03-04 20:46:12 +01:00
committed by GitHub
parent 9d772a99d1
commit 9ac6ab81c6
3 changed files with 76 additions and 60 deletions
+29 -19
View File
@@ -489,21 +489,24 @@ export class AppointmentService {
/**
* Add appointment to existing client tunnel
*/
public async addAppointmentToTunnel(appointmentData: {
emailHash: string;
tunnelId: string;
channelId: string;
agentId: string;
appointmentDate: string;
duration: number;
clientEmail: string;
clientLanguage?: string;
encryptedAppointment: {
encryptedPayload: string;
iv: string;
authTag: string;
};
}): Promise<AppointmentResponse> {
public async addAppointmentToTunnel(
appointmentData: {
emailHash: string;
tunnelId: string;
channelId: string;
agentId: string;
appointmentDate: string;
duration: number;
clientEmail: string;
clientLanguage?: string;
encryptedAppointment: {
encryptedPayload: string;
iv: string;
authTag: string;
};
},
staffCreated = false,
): Promise<AppointmentResponse> {
const log = logger.setContext("AppointmentService");
log.info("Adding appointment to existing tunnel", {
@@ -547,8 +550,11 @@ export class AppointmentService {
throw new NotFoundError("Active channel not found");
}
const initialStatus = channelResult[0].requiresConfirmation ? "NEW" : "CONFIRMED";
const requiresConfirmation = channelResult[0].requiresConfirmation || false;
const initialStatus =
channelResult[0].requiresConfirmation && !staffCreated ? "NEW" : "CONFIRMED";
const requiresConfirmation = staffCreated
? false
: channelResult[0].requiresConfirmation || false;
// Create encrypted appointment
const appointmentResult = await db
@@ -597,6 +603,7 @@ export class AppointmentService {
*/
public async createNewClientWithAppointment(
clientData: ClientTunnelData,
staffCreated = false,
): Promise<AppointmentResponse> {
const log = logger.setContext("AppointmentService");
@@ -693,8 +700,11 @@ export class AppointmentService {
throw new NotFoundError("Channel not found");
}
const initialStatus = channelResult[0].requiresConfirmation ? "NEW" : "CONFIRMED";
const requiresConfirmation = channelResult[0].requiresConfirmation || false;
const initialStatus =
channelResult[0].requiresConfirmation && !staffCreated ? "NEW" : "CONFIRMED";
const requiresConfirmation = staffCreated
? false
: channelResult[0].requiresConfirmation || false;
// 4. Create encrypted appointment
const appointmentResult = await tx
@@ -321,7 +321,7 @@ export const POST: RequestHandler = async ({ params, request, locals }) => {
encryptedAppointment: validatedData.encryptedAppointment,
};
result = await appointmentService.addAppointmentToTunnel(appointmentData);
result = await appointmentService.addAppointmentToTunnel(appointmentData, true);
} else {
// New client - create tunnel and appointment
isNewClient = true;
@@ -360,7 +360,7 @@ export const POST: RequestHandler = async ({ params, request, locals }) => {
clientEncryptedTunnelKey: validatedData.clientEncryptedTunnelKey,
};
result = await appointmentService.createNewClientWithAppointment(clientData);
result = await appointmentService.createNewClientWithAppointment(clientData, true);
// For new clients with email: initiate PIN reset flow
if (validatedData.clientEmail && !validatedData.hasNoEmail) {
@@ -397,7 +397,7 @@ export const POST: RequestHandler = async ({ params, request, locals }) => {
validatedData.channelId,
validatedData.clientEmail,
validatedData.clientLanguage,
!!result.requiresConfirmation,
false,
);
} catch (error) {
logger.error("Failed to send appointment notification", {
@@ -102,30 +102,33 @@ describe("POST /api/tenants/[id]/appointments/staff-create", () => {
expect(data.id).toBe("appointment-123");
expect(data.isNewClient).toBe(true);
expect(data.pinResetToken).toBe("reset-token-123");
expect(mockAppointmentService.createNewClientWithAppointment).toHaveBeenCalledWith({
tunnelId: "tunnel-123",
channelId: "channel-123",
agentId: "agent-123",
appointmentDate: "2026-01-15T14:00:00.000Z",
duration: 30,
emailHash,
clientEmail: "test@example.com",
clientLanguage: "de",
clientPublicKey: "public-key",
privateKeyShare: "private-key-share",
encryptedAppointment: {
encryptedPayload: "encrypted-payload",
iv: "iv",
authTag: "auth-tag",
},
staffKeyShares: [
{
userId: "staff-123",
encryptedTunnelKey: "encrypted-for-staff",
expect(mockAppointmentService.createNewClientWithAppointment).toHaveBeenCalledWith(
{
tunnelId: "tunnel-123",
channelId: "channel-123",
agentId: "agent-123",
appointmentDate: "2026-01-15T14:00:00.000Z",
duration: 30,
emailHash,
clientEmail: "test@example.com",
clientLanguage: "de",
clientPublicKey: "public-key",
privateKeyShare: "private-key-share",
encryptedAppointment: {
encryptedPayload: "encrypted-payload",
iv: "iv",
authTag: "auth-tag",
},
],
clientEncryptedTunnelKey: "encrypted-tunnel-key",
});
staffKeyShares: [
{
userId: "staff-123",
encryptedTunnelKey: "encrypted-for-staff",
},
],
clientEncryptedTunnelKey: "encrypted-tunnel-key",
},
true,
);
expect(mockPinResetService.createResetToken).toHaveBeenCalledWith(emailHash, 60);
});
});
@@ -235,21 +238,24 @@ describe("POST /api/tenants/[id]/appointments/staff-create", () => {
expect(data.id).toBe("appointment-789");
expect(data.isNewClient).toBe(false);
expect(data.pinResetToken).toBeUndefined();
expect(mockAppointmentService.addAppointmentToTunnel).toHaveBeenCalledWith({
emailHash,
tunnelId: "tunnel-789",
channelId: "channel-123",
agentId: "agent-123",
appointmentDate: "2026-01-15T14:00:00.000Z",
duration: 30,
clientEmail: "existing@example.com",
clientLanguage: "de",
encryptedAppointment: {
encryptedPayload: "encrypted-payload",
iv: "iv",
authTag: "auth-tag",
expect(mockAppointmentService.addAppointmentToTunnel).toHaveBeenCalledWith(
{
emailHash,
tunnelId: "tunnel-789",
channelId: "channel-123",
agentId: "agent-123",
appointmentDate: "2026-01-15T14:00:00.000Z",
duration: 30,
clientEmail: "existing@example.com",
clientLanguage: "de",
encryptedAppointment: {
encryptedPayload: "encrypted-payload",
iv: "iv",
authTag: "auth-tag",
},
},
});
true,
);
});
});
@@ -468,7 +474,7 @@ describe("POST /api/tenants/[id]/appointments/staff-create", () => {
"channel-123",
"test@example.com",
"de",
true,
false,
);
});