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.
This commit is contained in:
@@ -73,11 +73,12 @@ employeesRouter.get("/:id", (req: AuthedRequest, res) => {
|
||||
});
|
||||
|
||||
employeesRouter.post("/", ownerOnly, (req: AuthedRequest, res) => {
|
||||
const { name, role, email, phone, color, service_ids } = req.body ?? {};
|
||||
const { name, role, email, phone, color, service_ids, specialties, working_hours, efficiency_score } = req.body ?? {};
|
||||
if (!name) return err(res, 400, "El nombre es obligatorio");
|
||||
const r = db
|
||||
.prepare(
|
||||
`INSERT INTO employees (business_id, name, role, email, phone, color) VALUES (?, ?, ?, ?, ?, ?) RETURNING *`
|
||||
`INSERT INTO employees (business_id, name, role, email, phone, color, specialties, working_hours, efficiency_score)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`
|
||||
)
|
||||
.get(
|
||||
req.user!.business_id,
|
||||
@@ -85,7 +86,10 @@ employeesRouter.post("/", ownerOnly, (req: AuthedRequest, res) => {
|
||||
role || "Especialista",
|
||||
email || null,
|
||||
phone || null,
|
||||
color || "#3b66ff"
|
||||
color || "#3b66ff",
|
||||
JSON.stringify(Array.isArray(specialties) ? specialties : []),
|
||||
working_hours ? (typeof working_hours === "string" ? working_hours : JSON.stringify(working_hours)) : null,
|
||||
typeof efficiency_score === "number" ? efficiency_score : 50
|
||||
) as any;
|
||||
if (Array.isArray(service_ids)) {
|
||||
const ins = db.prepare(`INSERT OR IGNORE INTO employee_services (employee_id, service_id) VALUES (?, ?)`);
|
||||
@@ -98,11 +102,9 @@ employeesRouter.post("/", ownerOnly, (req: AuthedRequest, res) => {
|
||||
|
||||
employeesRouter.patch("/:id", ownerOnly, (req: AuthedRequest, res) => {
|
||||
const id = Number(req.params.id);
|
||||
const existing = db
|
||||
.prepare(`SELECT * FROM employees WHERE id = ? AND business_id = ?`)
|
||||
.get(id, req.user!.business_id as number) as any;
|
||||
const existing = db.prepare(`SELECT * FROM employees WHERE id = ? AND business_id = ?`).get(id, req.user!.business_id as number) as any;
|
||||
if (!existing) return err(res, 404, "Empleado no encontrado");
|
||||
const { name, role, email, phone, color, active, service_ids } = req.body ?? {};
|
||||
const { name, role, email, phone, color, active, service_ids, specialties, working_hours, efficiency_score } = req.body ?? {};
|
||||
const merged = {
|
||||
name: name ?? existing.name,
|
||||
role: role ?? existing.role,
|
||||
@@ -110,10 +112,17 @@ employeesRouter.patch("/:id", ownerOnly, (req: AuthedRequest, res) => {
|
||||
phone: phone ?? existing.phone,
|
||||
color: color ?? existing.color,
|
||||
active: active === undefined ? existing.active : active ? 1 : 0,
|
||||
specialties: specialties !== undefined
|
||||
? (typeof specialties === "string" ? specialties : JSON.stringify(Array.isArray(specialties) ? specialties : []))
|
||||
: existing.specialties,
|
||||
working_hours: working_hours !== undefined
|
||||
? (working_hours === null ? null : typeof working_hours === "string" ? working_hours : JSON.stringify(working_hours))
|
||||
: existing.working_hours,
|
||||
efficiency_score: efficiency_score !== undefined ? Number(efficiency_score) : (existing.efficiency_score ?? 50),
|
||||
};
|
||||
db.prepare(
|
||||
`UPDATE employees SET name = ?, role = ?, email = ?, phone = ?, color = ?, active = ? WHERE id = ?`
|
||||
).run(merged.name, merged.role, merged.email, merged.phone, merged.color, merged.active, id);
|
||||
`UPDATE employees SET name = ?, role = ?, email = ?, phone = ?, color = ?, active = ?, specialties = ?, working_hours = ?, efficiency_score = ? WHERE id = ?`
|
||||
).run(merged.name, merged.role, merged.email, merged.phone, merged.color, merged.active, merged.specialties, merged.working_hours, merged.efficiency_score, id);
|
||||
if (Array.isArray(service_ids)) {
|
||||
db.prepare(`DELETE FROM employee_services WHERE employee_id = ?`).run(id);
|
||||
const ins = db.prepare(`INSERT OR IGNORE INTO employee_services (employee_id, service_id) VALUES (?, ?)`);
|
||||
|
||||
Reference in New Issue
Block a user