From 9adacb17deaf7b2eda2c1ba98ecc625012368feb Mon Sep 17 00:00:00 2001 From: deploy Date: Wed, 29 Apr 2026 15:40:21 +0200 Subject: [PATCH] "feat-sharepoint-test-button-and-error-feedback" --- backend/src/routes/invoices.ts | 8 ++++++-- backend/src/routes/settings.ts | 11 +++++++++++ backend/src/services/sharepoint.ts | 21 +++++++++++++++++++++ frontend/src/pages/NewInvoice.tsx | 5 ++++- frontend/src/pages/Settings.tsx | 19 ++++++++++++++++--- 5 files changed, 58 insertions(+), 6 deletions(-) diff --git a/backend/src/routes/invoices.ts b/backend/src/routes/invoices.ts index a3c3b8d..ee20b81 100644 --- a/backend/src/routes/invoices.ts +++ b/backend/src/routes/invoices.ts @@ -337,6 +337,7 @@ router.post('/:id/send', wrap(async (req: AuthRequest, res: Response): Promise => { + try { + const { testSharepointConnection } = await import('../services/sharepoint'); + await testSharepointConnection(); + res.json({ success: true, message: 'Connexion SharePoint OK — fichier Excel accessible.' }); + } catch (err: any) { + res.status(400).json({ error: err.message }); + } +})); + export default router; diff --git a/backend/src/services/sharepoint.ts b/backend/src/services/sharepoint.ts index 81904d8..31e3b5d 100644 --- a/backend/src/services/sharepoint.ts +++ b/backend/src/services/sharepoint.ts @@ -76,6 +76,27 @@ async function getAccessToken(cfg: GraphConfig): Promise { return data.access_token; } +// ─── Test de connexion (sans écriture) ─────────────────────── + +/** + * Vérifie que le token Graph s'obtient et que la feuille Excel est accessible. + * Utilisé par POST /api/settings/sharepoint/test. + */ +export async function testSharepointConnection(): Promise { + const cfg = await getGraphConfig(); + const token = await getAccessToken(cfg); + + const baseUrl = `https://graph.microsoft.com/v1.0/sites/${cfg.sharepointSiteId}/drive/items/${cfg.sharepointItemId}/workbook/worksheets/${encodeURIComponent(cfg.sharepointSheet)}`; + const resp = await fetch(`${baseUrl}/usedRange?$select=rowCount`, { + headers: { Authorization: `Bearer ${token}` }, + }); + + if (!resp.ok) { + const body = await resp.text(); + throw new Error(`Impossible d'accéder à la feuille "${cfg.sharepointSheet}" (${resp.status}) : ${body.slice(0, 200)}`); + } +} + // ─── Fonction principale ────────────────────────────────────── /** diff --git a/frontend/src/pages/NewInvoice.tsx b/frontend/src/pages/NewInvoice.tsx index 2c2bb71..7d51a32 100644 --- a/frontend/src/pages/NewInvoice.tsx +++ b/frontend/src/pages/NewInvoice.tsx @@ -138,8 +138,11 @@ export default function NewInvoice() { const sendRes = await api.post(`/invoices/${createRes.data.id}/send`); return sendRes.data; }, - onSuccess: () => { + onSuccess: (data: any) => { toast.success('Facture envoyée avec succès !'); + if (data?.tracking_error) { + toast.error(`⚠️ Suivi SharePoint : ${data.tracking_error}`, { duration: 10000 }); + } qc.invalidateQueries({ queryKey: ['invoices'] }); createdInvoiceIdRef.current = null; resetForm(); diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index a13eea8..96da5c8 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -316,6 +316,12 @@ function GraphSection() { onError: (e: any) => toast.error(e.response?.data?.error ?? 'Erreur'), }); + const testSp = useMutation({ + mutationFn: () => api.post('/settings/sharepoint/test'), + onSuccess: (r: any) => toast.success(r.data?.message ?? 'SharePoint OK'), + onError: (e: any) => toast.error(e.response?.data?.error ?? 'Erreur SharePoint', { duration: 8000 }), + }); + function handleSubmit(e: React.FormEvent) { e.preventDefault(); save.mutate({ @@ -399,9 +405,16 @@ function GraphSection() { placeholder={data?.sharepoint_sheet_name ?? 'Feuil1'} value={form.sheetName ?? ''} onChange={e => setForm(f => ({ ...f, sheetName: e.target.value }))} /> - +
+ + +
)}