resanomaliedoublejustifavecqrcode

This commit is contained in:
2026-06-18 11:01:54 +02:00
parent 3178e3bf40
commit 42ab01ed41
2 changed files with 102 additions and 79 deletions
+83 -61
View File
@@ -2091,50 +2091,63 @@ app.post('/api/notes', authenticateToken, upload.any(), async (req, res) => {
} }
} }
// ── QR par ligne : téléchargement + upload en parallèle ────────── // ── QR par ligne : ne re-télécharge/ré-upload que si le client n'a pas déjà le fichier ──
await Promise.all( await Promise.all(
lignesParsed.map(async (ligne, i) => { lignesParsed.map(async (ligne, i) => {
const ligneQrRef = ligne.qrNoteRef; const ligneQrRef = ligne.qrNoteRef;
if (!ligneQrRef) return; if (!ligneQrRef) return;
try {
const qrLigne = await pool.request()
.input('noteRef', sql.NVarChar, ligneQrRef)
.query(`SELECT TOP 1 fichiers FROM UploadTokens WHERE noteRef = @noteRef AND used = 1 ORDER BY expiresAt DESC`);
if (!qrLigne.recordset.length || !qrLigne.recordset[0].fichiers) {
console.warn(`⚠️ QR ligne ${i} (${ligneQrRef}) : token introuvable ou non utilisé`);
return;
}
const qrFichiers = JSON.parse(qrLigne.recordset[0].fichiers);
if (!ligne.qrFiles) ligne.qrFiles = [];
await Promise.all( // ✅ Si le client a déjà reçu ce fichier via le polling QR (origin 'qr'),
qrFichiers.map(async f => { // il est déjà dans ligne.qrFiles avec son uploadUrl d'origine.
try { // Pas besoin de re-télécharger/ré-uploader : on évite ainsi le doublon
const buf = await downloadFromSharePoint(f.uploadUrl); // causé par un nouveau nom de fichier (basé sur la référence finale de la note).
const fileObj = { const dejaRecuParClient = Array.isArray(ligne.qrFiles) &&
buffer: buf, ligne.qrFiles.some(f => !f.origin || f.origin === 'qr');
originalname: f.fileName,
mimetype: f.fileName?.endsWith('.pdf') ? 'application/pdf' : 'image/jpeg',
size: buf.length
};
allFiles.push(fileObj);
const uploaded = await uploadToSharePointHierarchique(fileObj, reference, nomDossier, moisDossier); if (dejaRecuParClient) {
const dejaSauve = ligne.qrFiles.some(x => x.fileName === uploaded.fileName); console.log(`✅ QR ligne ${i} (${ligneQrRef}) : fichier déjà reçu côté client, pas de re-upload`);
if (!dejaSauve) { return;
ligne.qrFiles.push({ fileName: uploaded.fileName, uploadUrl: uploaded.uploadUrl }); }
}
console.log(`✅ QR ligne ${i} stocké: ${uploaded.fileName}`); try {
} catch (e) { const qrLigne = await pool.request()
console.warn(`⚠️ QR ligne ${i} download/upload fail:`, e.message); .input('noteRef', sql.NVarChar, ligneQrRef)
.query(`SELECT TOP 1 fichiers FROM UploadTokens WHERE noteRef = @noteRef AND used = 1 ORDER BY expiresAt DESC`);
if (!qrLigne.recordset.length || !qrLigne.recordset[0].fichiers) {
console.warn(`⚠️ QR ligne ${i} (${ligneQrRef}) : token introuvable ou non utilisé`);
return;
} }
}) const qrFichiers = JSON.parse(qrLigne.recordset[0].fichiers);
); if (!ligne.qrFiles) ligne.qrFiles = [];
} catch (e) {
console.warn(`⚠️ QR ligne ${i} erreur DB:`, e.message); await Promise.all(
} qrFichiers.map(async f => {
}) try {
); const buf = await downloadFromSharePoint(f.uploadUrl);
const fileObj = {
buffer: buf,
originalname: f.fileName,
mimetype: f.fileName?.endsWith('.pdf') ? 'application/pdf' : 'image/jpeg',
size: buf.length
};
allFiles.push(fileObj);
const uploaded = await uploadToSharePointHierarchique(fileObj, reference, nomDossier, moisDossier);
const dejaSauve = ligne.qrFiles.some(x => x.fileName === uploaded.fileName);
if (!dejaSauve) {
ligne.qrFiles.push({ fileName: uploaded.fileName, uploadUrl: uploaded.uploadUrl });
}
console.log(`✅ QR ligne ${i} stocké: ${uploaded.fileName}`);
} catch (e) {
console.warn(`⚠️ QR ligne ${i} download/upload fail:`, e.message);
}
})
);
} catch (e) {
console.warn(`⚠️ QR ligne ${i} erreur DB:`, e.message);
}
})
);
// ── Upload fichiers soumis directement par ligne (files_${depId}) ── // ── Upload fichiers soumis directement par ligne (files_${depId}) ──
// Identique à la logique du PUT /api/notes/brouillons/:id // Identique à la logique du PUT /api/notes/brouillons/:id
@@ -2534,28 +2547,37 @@ app.put('/api/notes/:id', authenticateToken, upload.any(), async (req, res) => {
} }
// QR par ligne // QR par ligne
for (let i = 0; i < lignesParsed.length; i++) { // QR par ligne — skip si le client a déjà le fichier (évite doublon)
const ligneQrRef = lignesParsed[i].qrNoteRef; for (let i = 0; i < lignesParsed.length; i++) {
if (!ligneQrRef) continue; const ligneQrRef = lignesParsed[i].qrNoteRef;
try { if (!ligneQrRef) continue;
const qrLigne = await pool.request()
.input('noteRef', sql.NVarChar, ligneQrRef) const dejaRecuParClient = Array.isArray(lignesParsed[i].qrFiles) &&
.query(`SELECT TOP 1 fichiers FROM UploadTokens WHERE noteRef = @noteRef AND used = 1 ORDER BY expiresAt DESC`); lignesParsed[i].qrFiles.some(f => !f.origin || f.origin === 'qr');
if (qrLigne.recordset.length && qrLigne.recordset[0].fichiers) { if (dejaRecuParClient) {
const qrFichiers = JSON.parse(qrLigne.recordset[0].fichiers); console.log(`✅ [CAS1] QR ligne ${i} (${ligneQrRef}) : fichier déjà reçu côté client, pas de re-upload`);
for (const f of qrFichiers) { continue;
try {
const buf = await downloadFromSharePoint(f.uploadUrl);
const r = await uploadToSharePointHierarchique(
{ buffer: buf, originalname: f.fileName, mimetype: f.fileName?.endsWith('.pdf') ? 'application/pdf' : 'image/jpeg', size: buf.length },
nouvelleReference, nomDossier, moisDossier
);
fichiersUploades.push(r);
} catch (e) { console.warn(`⚠️ QR ligne ${i} download fail:`, e.message); }
} }
try {
const qrLigne = await pool.request()
.input('noteRef', sql.NVarChar, ligneQrRef)
.query(`SELECT TOP 1 fichiers FROM UploadTokens WHERE noteRef = @noteRef AND used = 1 ORDER BY expiresAt DESC`);
if (qrLigne.recordset.length && qrLigne.recordset[0].fichiers) {
const qrFichiers = JSON.parse(qrLigne.recordset[0].fichiers);
for (const f of qrFichiers) {
try {
const buf = await downloadFromSharePoint(f.uploadUrl);
const r = await uploadToSharePointHierarchique(
{ buffer: buf, originalname: f.fileName, mimetype: f.fileName?.endsWith('.pdf') ? 'application/pdf' : 'image/jpeg', size: buf.length },
nouvelleReference, nomDossier, moisDossier
);
fichiersUploades.push(r);
} catch (e) { console.warn(`⚠️ QR ligne ${i} download fail:`, e.message); }
}
}
} catch (e) { console.warn(`⚠️ QR ligne ${i} erreur DB:`, e.message); }
} }
} catch (e) { console.warn(`⚠️ QR ligne ${i} erreur DB:`, e.message); }
}
// lignesJsonCorrige APRÈS injection // lignesJsonCorrige APRÈS injection
const lignesJsonCorrige = JSON.stringify(lignesParsed); const lignesJsonCorrige = JSON.stringify(lignesParsed);
+19 -18
View File
@@ -1945,24 +1945,25 @@ export default function NouvelleNote({
if (!res.ok) { alert("Erreur génération QR"); return; } if (!res.ok) { alert("Erreur génération QR"); return; }
setDepenses(prev => prev.map(d => d.id === depenseId ? { ...d, qrLink: data.uploadLink, qrNoteRef: tempRef, qrUploaded: false } : d)); setDepenses(prev => prev.map(d => d.id === depenseId ? { ...d, qrLink: data.uploadLink, qrNoteRef: tempRef, qrUploaded: false } : d));
if (qrPollingRefs.current[depenseId]) clearInterval(qrPollingRefs.current[depenseId]); if (qrPollingRefs.current[depenseId]) clearInterval(qrPollingRefs.current[depenseId]);
qrPollingRefs.current[depenseId] = setInterval(async () => { qrPollingRefs.current[depenseId] = setInterval(async () => {
try { try {
const r = await fetch(`${apiBaseUrl}/api/upload/status/${tempRef}`, { headers: getHeaders() }); const r = await fetch(`${apiBaseUrl}/api/upload/status/${tempRef}`, { headers: getHeaders() });
const s = await r.json(); const s = await r.json();
if (s.uploaded) { if (s.uploaded) {
clearInterval(qrPollingRefs.current[depenseId]); clearInterval(qrPollingRefs.current[depenseId]);
setDepenses(prev => prev.map(d => d.id === depenseId delete qrPollingRefs.current[depenseId]; // ✅ empêche un 2e tick concurrent de re-déclencher la fusion
? { setDepenses(prev => prev.map(d => d.id === depenseId
...d, qrUploaded: true, qrLink: null, ? {
qrFiles: [ ...d, qrUploaded: true, qrLink: null,
...(d.qrFiles ?? []), qrFiles: [
...(Array.isArray(s.files) ? s.files.map((f: any) => ({ ...f, origin: 'qr' as const })) : []) ...(d.qrFiles ?? []),
] ...(Array.isArray(s.files) ? s.files.map((f: any) => ({ ...f, origin: 'qr' as const })) : [])
} ].filter((f, i, arr) => arr.findIndex(x => x.uploadUrl === f.uploadUrl) === i) // ✅ dédup par URL
: d)); }
} : d));
} catch { } }
}, 3000); } catch { }
}, 3000);
} catch { } } catch { }
}, [apiBaseUrl, getHeaders]); }, [apiBaseUrl, getHeaders]);