7009 contrat_groupe_partage: fields.contrat_groupe_partage ?? null, 7010 contrat_labels: fields.contrat_labels ?? [], 7011 }] }); 7012} 7013 7014export async function updateOffre(localId: string, fields: Partial<OffreFields>): Promise<void> { 7015 const id = await findOffreRowId(localId); 7016 if (!id) throw new Error('offre not found: ' + localId); 7017 await adminDataMutation('update', 'offres', { id, fields }); 7018} 7019 7020export async function replaceOfferRemunerationRules(offreId: string, rules: OfferRemunerationRuleFields[], removedRuleIds: string[] = []): Promise<void> { 7021 const cleanOfferId = offreId.trim(); 7022 if (!cleanOfferId) throw new Error('Offre requise pour la rémunération.'); 7023 await activeBusinessAction<OfferCatalogSaveResult>('offer-catalog.manage', { 7024 action: 'save_pricing', 7025 offer_id: cleanOfferId, 7026 rules, 7027 removed_rule_ids: removedRuleIds, 7028 }); 7029} 7030 7031// ── Manual rapport correction (human-in-the-loop) ────────────────────── 7032// The dedicated quality command validates revisions, computes scores and records the actor. 7033// Writes corrected evaluations + recomputed score back into the EXISTING 7034// rapports columns (evaluations JSON + score_total). The audit trail 7035// (ai_verdict / corrected_by / corrected_at) lives inside the evaluations 7036// JSON, so no table-schema change is needed. All dashboard metrics read 7037// these same fields, so a correction propagates everywhere automatically. 7038export async function correctRapport( 7039 rapportLocalId: string, 7040 fields: { evaluations: Evaluation[]; score_total: number; expected_modified_time?: string }, 7041): Promise<{ evaluations: Evaluation[]; score_total: number; modified_time: string }> { 7042 const detail=await apiPost<{row:{modified_time:string;evaluations:Evaluation[]} | null}>('/v1/quality/query',{action:'rapport_detail',rapport_id:rapportLocalId}); 7043 if(!detail.row)throw new Error('Rapport introuvable.'); 7044 const original=typeof detail.row.evaluations==='string'?JSON.parse(detail.row.evaluations):detail.row.evaluations; 7045 const corrections=fields.evaluations.flatMap((e,index)=>e.verdict===original[index]?.verdict&&e.commentaire===original[index]?.commentaire?[]:[{index,verdict:e.verdict,commentaire:e.commentaire}]); 7046 if(corrections.some(e=>!e.commentaire?.trim()))throw new Error('Expliquez chaque correction avant de l’enregistrer.'); 7047 const result=await apiPost<{row:{evaluations:Evaluation[];score_total:number;modified_time:string}}>('/v1/quality/query',{action:'rapport_correct',rapport_id:rapportLocalId,expected_modified_time:fields.expected_modified_time,corrections}); 7048 return result.row; 7049} 7050 7051// Update the list of offres a centre is configured to propose. Stored in the 7052// centres.offres_proposees json column (added 2026-05-21). Survives sync runs 7053// (centres upsert is deleteMissing:false + diffFields:[]). 7054export async function updateCentreOffres(centreLocalId: string, offreIds: string[]): Promise<void> { 7055 const rowId = await findCentreRowId(centreLocalId); 7056 await adminDataMutation('update', 'centres', { id: rowId, fields: { offres_proposees: offreIds } }); 7057} 7058 7059export function partnerUrl(token: string): string { 7060 return `${window.location.origin}/partner/${token}`; 7061} 7062 7063export function useTranscriptText(_centreId: string | null, appelId: string | null) { 7064 return useQuery({ 7065 queryKey: ['transcript-text', appelId], 7066 queryFn: async () => { 7067 if (!appelId) return { text: '' }; 7068 const r = await apteva.app('tables').tool<{ rows: Array<{ transcript_text?: string }> }>( 7069 'rows_search', 7070 { 7071 table: 'appels', 7072 where: [{ col: 'local_id', op: 'eq', value: appelId }], 7073 limit: 1, 7074 _project_id: PROJECT_ID, 7075 }, 7076 ); 7077 return { text: r.rows?.[0]?.transcript_text || '' }; 7078 }, 7079 enabled: !!appelId, 7080 staleTime: 60_000,