"feat-sharepoint-test-button-and-error-feedback"
This commit is contained in:
@@ -337,6 +337,7 @@ router.post('/:id/send', wrap(async (req: AuthRequest, res: Response): Promise<v
|
||||
|
||||
// ── SharePoint (non bloquant) ─────────────────────────────
|
||||
let trackingAdded = false;
|
||||
let trackingError: string | null = null;
|
||||
if (invoice.add_to_tracking) {
|
||||
try {
|
||||
const [year, month, day] = dateStr.split('-');
|
||||
@@ -351,8 +352,10 @@ router.post('/:id/send', wrap(async (req: AuthRequest, res: Response): Promise<v
|
||||
});
|
||||
trackingAdded = true;
|
||||
} catch (err: any) {
|
||||
// Non bloquant : l'email est déjà envoyé, on log l'erreur
|
||||
// Non bloquant : l'email est déjà envoyé, mais on remonte l'erreur
|
||||
// pour que le frontend puisse afficher un avertissement.
|
||||
console.warn('[SharePoint] Erreur non bloquante :', err.message);
|
||||
trackingError = err.message;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,7 +368,8 @@ router.post('/:id/send', wrap(async (req: AuthRequest, res: Response): Promise<v
|
||||
[path.join('pdfs', `${invoice.id}.pdf`), pdfFilename, trackingAdded, invoice.id]
|
||||
);
|
||||
|
||||
res.json(await getInvoiceById(invoice.id));
|
||||
const updated = await getInvoiceById(invoice.id);
|
||||
res.json({ ...updated, tracking_error: trackingError });
|
||||
}));
|
||||
|
||||
/**
|
||||
|
||||
@@ -140,4 +140,15 @@ router.put('/app', validate(appSettingsSchema), wrap(async (req: AuthRequest, re
|
||||
res.json({ success: true });
|
||||
}));
|
||||
|
||||
/** POST /api/settings/sharepoint/test — Vérifie la connexion Graph + accès au fichier Excel */
|
||||
router.post('/sharepoint/test', wrap(async (_req: AuthRequest, res: Response): Promise<void> => {
|
||||
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;
|
||||
|
||||
@@ -76,6 +76,27 @@ async function getAccessToken(cfg: GraphConfig): Promise<string> {
|
||||
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<void> {
|
||||
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 ──────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 }))} />
|
||||
</div>
|
||||
<button type="submit" disabled={save.isPending} className="btn-primary py-2 text-sm">
|
||||
{save.isPending ? 'Sauvegarde…' : 'Sauvegarder'}
|
||||
</button>
|
||||
<div className="flex gap-2">
|
||||
<button type="submit" disabled={save.isPending} className="btn-primary py-2 text-sm">
|
||||
{save.isPending ? 'Sauvegarde…' : 'Sauvegarder'}
|
||||
</button>
|
||||
<button type="button" onClick={() => testSp.mutate()}
|
||||
disabled={testSp.isPending || !isConfigured}
|
||||
className="btn-secondary py-2 text-sm">
|
||||
{testSp.isPending ? 'Test…' : 'Tester'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user