167 168export function validatedInput(body) { 169 const label = clean(body?.label); 170 if (label.length < 2 || label.length > 120) throw new Error("Libellé de période invalide."); 171 const scopeType = clean(body?.scope_type).toLowerCase() || "global"; 172 if (!["global", "centre"].includes(scopeType)) throw new Error("Périmètre de période invalide."); 173 const scopeId = scopeType === "centre" ? clean(body?.scope_id) : null; 174 if (scopeType === "centre" && !scopeId) throw new Error("Centre requis pour cette période."); 175 const startsAt = validIso(body?.starts_at); 176 const endsAt = validIso(body?.ends_at); 177 if (!startsAt || !endsAt || Date.parse(startsAt) >= Date.parse(endsAt)) { 178 throw new Error("Les dates de période sont invalides."); 179 } 180 const timezone = clean(body?.timezone) || "Europe/Paris"; 181 assertTimeZone(timezone); 182 return { 183 label, 184 scope_type: scopeType, 185 scope_id: scopeId, 186 starts_at: startsAt, 187 ends_at: endsAt, 188 timezone, 189 }; 190} 191 192async function assertNoOverlap(context, input, excludedId = 0) { 193 const found = await context.call("tables", "rows_search", { 194 table: TABLE, 195 where: [ 196 { col: "scope_type", op: "eq", value: input.scope_type }, 197 { col: "active", op: "eq", value: true }, 198 ], 199 order_by: "id asc", 200 limit: 1000, 201 }); 202 const conflicting = (found?.rows || []) 203 .filter((row) => Number(row.id) !== excludedId) 204 .filter((row) => clean(row.scope_id) === clean(input.scope_id)) 205 .find((row) => rangesOverlap(row, input)); 206 if (conflicting) { 207 throw new Error(`Cette période chevauche « ${clean(conflicting.label) || clean(conflicting.local_id)} ».`); 208 } 209} 210 211export function rangesOverlap(a, b) { 212 return Date.parse(a.starts_at) <= Date.parse(b.ends_at) 213 && Date.parse(b.starts_at) <= Date.parse(a.ends_at); 214} 215 216function containsTime(period, value) { 217 return Date.parse(period.starts_at) <= value && value <= Date.parse(period.ends_at); 218} 219 220async function findPeriod(context, localId) { 221 const result = await context.call("tables", "rows_search", { 222 table: TABLE, 223 where: [{ col: "local_id", op: "eq", value: localId }], 224 order_by: "id desc", 225 limit: 1, 226 }); 227 return result?.rows?.[0] || null; 228} 229 230async function getRow(context, id) {