827 if (!note || note === clean(prospect?.notes)) return {}; 828 829 const commercialCentreId = clean(commercial?.row?.centre_appel_id); 830 if (!commercialCentreId) { 831 throw new Error("Le centre du commercial est introuvable; le commentaire n’a pas été enregistré."); 832 } 833 834 const existingCentreId = clean(prospect?.centre_lock_id); 835 const existingSource = clean(prospect?.centre_lock_source).toLowerCase(); 836 const existingUntilMs = Date.parse(clean(prospect?.centre_lock_until)); 837 const nowMs = Date.parse(clean(now)); 838 const activeSaleLock = existingSource === "sale" 839 && existingCentreId 840 && Number.isFinite(existingUntilMs) 841 && Number.isFinite(nowMs) 842 && existingUntilMs > nowMs; 843 const activeAffinityLock = ["answered_call", "commercial_comment"].includes(existingSource) 844 && existingCentreId 845 && (!Number.isFinite(existingUntilMs) || !Number.isFinite(nowMs) || existingUntilMs > nowMs); 846 847 if (activeSaleLock || activeAffinityLock) { 848 if (normalizeCentre(existingCentreId) !== normalizeCentre(commercialCentreId)) { 849 throw new Error(`Ce prospect est protégé pour le centre ${existingCentreId}.`); 850 } 851 return {}; 852 } 853 854 return { 855 centre_lock_id: commercialCentreId, 856 centre_lock_until: null, 857 centre_lock_source: "commercial_comment", 858 }; 859} 860 861function callTelephone(appel) { 862 return clean(appel?.direction).toLowerCase() === 'sortant' 863 ? clean(appel?.to_number) || clean(appel?.from_number) 864 : clean(appel?.from_number) || clean(appel?.to_number); 865} 866 867function qualificationStateForCall(prospect, appel) { 868 const event = clean(appel?.qualification_event).toLowerCase(); 869 const status = event.startsWith("nrp") 870 ? "nrp" 871 : ["qualifie", "ko", "rdv", "en_cours"].includes(event) 872 ? event === "rdv" ? "rappel" : event 873 : "nouveau"; 874 return { 875 ...prospect, 876 statut: status, 877 qualification_cycle: Math.max(1, integer(appel?.qualification_cycle) || 1), 878 nrp_count: Math.max(0, integer(appel?.nrp_number) || integer(appel?.nrp_count) || 0), 879 last_qualification_event: event || null, 880 last_qualification_at: event ? clean(appel?.status_at) || null : null, 881 next_callback_at: clean(appel?.next_callback_at) || null, 882 }; 883} 884 885function buildProspectPatch(prospect, body, eventName, now) { 886 const cycle = Math.max(1, integer(prospect.qualification_cycle) || 1); 887 const currentNrp = Math.max(0, integer(prospect.nrp_count) || 0); ··· 950 lead_hidden_until: null, 951 next_callback_at: null, 952 last_qualification_event: "qualifie", 953 qualification_status_reason: clean(body?.reason) || "qualified", 954 last_appel_id: appelRef || undefined, 955 notes: appendNote(prospect.notes, notes), 956 }), 957 }; 958 } 959 960 if (eventName === "ko") { 961 const releasesAnsweredCallAffinity = ["answered_call", "commercial_comment"] 962 .includes(clean(prospect?.centre_lock_source).toLowerCase()); 963 return { 964 event: "ko", 965 nrp: currentNrp, 966 fields: compactFields({ 967 ...base, 968 statut: "ko", 969 nrp_count: currentNrp, 970 qualification_cycle: cycle, 971 lead_hidden_until: null, 972 next_callback_at: null, 973 last_qualification_event: "ko", 974 qualification_status_reason: clean(body?.reason) || "ko", 975 last_appel_id: appelRef || undefined, 976 notes: appendNote(prospect.notes, notes), 977 ...(releasesAnsweredCallAffinity ? { 978 centre_lock_id: null, 979 centre_lock_until: now, 980 centre_lock_source: "released_ko", 981 assigned_commercial_id: null, 982 assigned_at: null, 983 assignment_source: "released_ko", 984 } : {}), 985 }), 986 }; 987 } 988 989 if (eventName === "en_cours") { 990 return { 991 event: "en_cours", 992 nrp: currentNrp, 993 fields: compactFields({ 994 ...base, 995 statut: "en_cours", 996 nrp_count: currentNrp, 997 qualification_cycle: cycle, 998 lead_hidden_until: null, 999 next_callback_at: null, 1000 last_qualification_event: "en_cours", 1001 qualification_status_reason: clean(body?.reason) || "work_started", 1002 notes: appendNote(prospect.notes, notes), 1003 }), 1004 }; 1005 } 1006 1007 throw new Error(`Unsupported qualification event: ${eventName}`); 1008} 1009 1010async function resolveActorCommercial(context, body) {