Files
AgendaPro/server/routes/settings.ts
T
AgendaPro Dev ed608656e0 feat(backend): auto-assign + transactional double-booking guard + working-hours slots
POST /book and POST /appointments now resolve the specialist via autoAssign (or isAvailable-guard a chosen one) inside BEGIN IMMEDIATE...COMMIT, returning 409 on conflict. Slots endpoint derives the day window from real working_hours and ranks the best specialist per slot. settings/employees expose + persist the new fields. Adds booking-e2e.mjs and adapts e2e-test.mjs fixtures to in-hours slots.
2026-07-26 15:59:28 -06:00

67 lines
2.6 KiB
TypeScript

import { Router } from "express";
import { db } from "../db.ts";
import type { AuthedRequest } from "../lib/auth.ts";
import { err, ownerOnly } from "../lib/auth.ts";
export const settingsRouter = Router();
const FIELDS = [
"name",
"industry",
"phone",
"address",
"slug",
"booking_enabled",
"cancel_window_hours",
"cancel_penalty_pct",
"require_deposit",
"deposit_pct",
"timezone",
"auto_assign_specialist",
"working_hours",
] as const;
settingsRouter.get("/", (req: AuthedRequest, res) => {
const b = db
.prepare(
`SELECT id, name, industry, currency, currency_symbol, phone, address, slug, booking_enabled,
cancel_window_hours, cancel_penalty_pct, require_deposit, deposit_pct, timezone,
auto_assign_specialist, working_hours
FROM businesses WHERE id = ?`
)
.get(req.user!.business_id) as any;
if (!b) return err(res, 404, "Negocio no encontrado");
res.json({ settings: b });
});
settingsRouter.patch("/", ownerOnly, (req: AuthedRequest, res) => {
const existing = db.prepare(`SELECT * FROM businesses WHERE id = ?`).get(req.user!.business_id) as any;
if (!existing) return err(res, 404, "Negocio no encontrado");
const body = req.body ?? {};
const merged: any = {};
for (const f of FIELDS) {
if (body[f] !== undefined) merged[f] = body[f];
}
if (merged.working_hours && typeof merged.working_hours !== "string") {
merged.working_hours = JSON.stringify(merged.working_hours);
}
if (merged.auto_assign_specialist !== undefined) {
merged.auto_assign_specialist = merged.auto_assign_specialist ? 1 : 0;
}
// slug uniqueness check
if (merged.slug !== undefined) {
merged.slug = String(merged.slug).trim().toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
if (!merged.slug) return err(res, 400, "El slug no puede quedar vacío");
const clash = db.prepare(`SELECT id FROM businesses WHERE slug = ? AND id != ?`).get(merged.slug, req.user!.business_id);
if (clash) return err(res, 409, "Esa URL ya está en uso");
}
const sets = Object.keys(merged).map((k) => `${k} = @${k}`);
if (sets.length === 0) return res.json({ settings: existing });
merged.id = req.user!.business_id;
db.prepare(`UPDATE businesses SET ${sets.join(", ")} WHERE id = @id`).run(merged);
const updated = db
.prepare(`SELECT id, name, industry, currency, currency_symbol, phone, address, slug, booking_enabled, cancel_window_hours, cancel_penalty_pct, require_deposit, deposit_pct, timezone, auto_assign_specialist, working_hours FROM businesses WHERE id = ?`)
.get(req.user!.business_id);
res.json({ settings: updated });
});