Preuve · code, extrait

backend/flexylead-claim-missed-call.mjs

Dépôt jeremy-flexylead/flexylead-crm · commit ccbbaa07870e0f786729003dd6f8862e4bcc8256 · dossier de production synchronisé le 14 septembre 2026 à 08 h 46

Extrait tel qu'à ce commit : lignes 47–107, sur 948 (numérotation d'origine, 30 lignes de contexte autour de chaque citation) · fichier intégral sur GitHub (dépôt privé, accès requis)

Lignes citées par l'audit : 77

61 lignes reproduites · ← retour à l'audit · catalogue des preuves

47  const error = new Error(message); error.statusCode = statusCode; return error;
48}
49
50const DEFAULT_POOL_DELAY_MS = 2 * 60 * 1000;
51const DEFAULT_CLAIM_TTL_MS = 10 * 60 * 1000;
52const MISSED_CLAIM_EXPIRY_ENABLED = true;
53const CLAIM_ARBITRATION_TTL_MS = 30 * 1000;
54const CENTRE_LOCK_TTL_MS = 90 * 24 * 60 * 60 * 1000;
55const CLAIM_TABLE = "commercial_missed_call_claims";
56let claimTableReady = false;
57
58export default async function handler(event, context) {
59  context = { ...context, [__authParityEvent]: event };
60
61  const body = event?.body && typeof event.body === "object" ? event.body : event;
62  assertNotSupervisionMutation(body);
63  const requestedCommercialId = clean(body?.commercial_id);
64  const callId = clean(body?.call_id);
65  const callRowId = Number(body?.call_row_id || 0) || null;
66  const nowMs = Date.now();
67  const now = new Date(nowMs).toISOString();
68
69  const actor = await resolveActorCommercial(context, body, requestedCommercialId);
70  const commercialId = actor.id;
71
72  let call = await getParentCall(context, callRowId, callId);
73  if (!call) throw new Error("Appel manqué introuvable.");
74
75  // Authorization is checked before any recovery mutation.
76  if (call.followup_appel_id) await assertCommercialCanClaim(context, actor.aliases, call);
77  call = await recoverFailedCallbackPointer(context, call, actor.aliases, nowMs);
78  await assertInteractionStillMissed(context, call);
79  assertMissedCallClaimable(call, actor.aliases, nowMs);
80  const claimRoute = await assertCommercialCanClaim(context, actor.aliases, call);
81  const claimGroupId = claimRoute.groupId;
82
83  const rowId = Number(call.id || callRowId || 0);
84  if (!rowId) throw new Error("Identifiant technique de l'appel introuvable.");
85
86  const linkedProspect = await getLinkedProspect(context, call);
87  const prospectCentreLock = linkedProspect
88    ? await resolveProspectCentreLock(context, linkedProspect, new Date(nowMs))
89    : null;
90  assertClaimantMatchesCentreLock(actor, prospectCentreLock);
91
92  const expiresAt = MISSED_CLAIM_EXPIRY_ENABLED
93    ? new Date(nowMs + DEFAULT_CLAIM_TTL_MS).toISOString()
94    : null;
95  // The claim row below is the durable 10-minute reservation. This lock only arbitrates
96  // concurrent requests and must never keep an agent blocked for the whole claim TTL.
97  const arbitrationExpiresAt = new Date(nowMs + CLAIM_ARBITRATION_TTL_MS).toISOString();
98  const currentOwner = clean(call.missed_claimed_by);
99  const currentExpiry = claimExpiryMs(call);
100  if (currentOwner && actor.aliases.has(currentOwner) && (!currentExpiry || currentExpiry > nowMs)) {
101    return claimResponse(call, commercialId, rowId);
102  }
103
104  await ensureClaimTable(context);
105  const commercialLock = await acquireClaimLock(context, {
106    callRef: `commercial:${commercialId}`,
107    rowId,