resanomaliedoublejustifavecqrcode
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user