feat: warningMessage quota IA dans rapports d'import + redémarrage auto services
This commit is contained in:
@@ -25,6 +25,7 @@ type ImportLog = {
|
|||||||
errors: number;
|
errors: number;
|
||||||
duplicateDetails: string | null;
|
duplicateDetails: string | null;
|
||||||
errorDetails: string | null;
|
errorDetails: string | null;
|
||||||
|
warningMessage?: string | null;
|
||||||
importedAt: Date | string;
|
importedAt: Date | string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -390,7 +391,7 @@ export default function ImportReport() {
|
|||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-center">
|
<TableCell className="text-center">
|
||||||
{(log.duplicatesIgnored > 0 || log.errors > 0) && (
|
{(log.duplicatesIgnored > 0 || log.errors > 0 || (log as any).warningMessage) && (
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -486,6 +487,17 @@ export default function ImportReport() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Avertissement quota IA */}
|
||||||
|
{selectedLog.warningMessage && (
|
||||||
|
<div className="bg-orange-50 border border-orange-300 rounded-lg p-4 flex gap-3">
|
||||||
|
<AlertTriangle className="w-5 h-5 text-orange-500 flex-shrink-0 mt-0.5" />
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-semibold text-orange-700 mb-1">Avertissement</p>
|
||||||
|
<p className="text-sm text-orange-600">{selectedLog.warningMessage}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Erreurs */}
|
{/* Erreurs */}
|
||||||
{selectedLog.errors > 0 && (
|
{selectedLog.errors > 0 && (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
1
drizzle/0031_mean_squadron_supreme.sql
Normal file
1
drizzle/0031_mean_squadron_supreme.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `importLogs` ADD `warningMessage` text;
|
||||||
2147
drizzle/meta/0031_snapshot.json
Normal file
2147
drizzle/meta/0031_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -218,6 +218,13 @@
|
|||||||
"when": 1780930222253,
|
"when": 1780930222253,
|
||||||
"tag": "0030_vengeful_warbird",
|
"tag": "0030_vengeful_warbird",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 31,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1781021650065,
|
||||||
|
"tag": "0031_mean_squadron_supreme",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -222,6 +222,7 @@ export const importLogs = mysqlTable("importLogs", {
|
|||||||
errors: int("errors").default(0).notNull(),
|
errors: int("errors").default(0).notNull(),
|
||||||
duplicateDetails: text("duplicateDetails"), // JSON array of duplicate invoice info
|
duplicateDetails: text("duplicateDetails"), // JSON array of duplicate invoice info
|
||||||
errorDetails: text("errorDetails"), // JSON array of error messages
|
errorDetails: text("errorDetails"), // JSON array of error messages
|
||||||
|
warningMessage: text("warningMessage"), // Warning message (e.g. quota exhausted)
|
||||||
importedAt: timestamp("importedAt").defaultNow().notNull(),
|
importedAt: timestamp("importedAt").defaultNow().notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ import { registerOAuthRoutes } from "./oauth";
|
|||||||
import { appRouter } from "../routers";
|
import { appRouter } from "../routers";
|
||||||
import { createContext } from "./context";
|
import { createContext } from "./context";
|
||||||
import { serveStatic, setupVite } from "./vite";
|
import { serveStatic, setupVite } from "./vite";
|
||||||
|
import { getAllUsers } from "../db";
|
||||||
|
import { startEmailImportService } from "../emailImportService";
|
||||||
|
import { startFolderImportService } from "../folderImportService";
|
||||||
|
import { getImportSettingsByUser } from "../db";
|
||||||
|
|
||||||
function isPortAvailable(port: number): Promise<boolean> {
|
function isPortAvailable(port: number): Promise<boolean> {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
@@ -164,6 +168,31 @@ async function startServer() {
|
|||||||
server.listen(port, () => {
|
server.listen(port, () => {
|
||||||
console.log(`Server running on http://localhost:${port}/`);
|
console.log(`Server running on http://localhost:${port}/`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Redémarrer automatiquement les services actifs (IMAP, dossier) après redémarrage du serveur
|
||||||
|
setTimeout(async () => {
|
||||||
|
try {
|
||||||
|
const users = await getAllUsers();
|
||||||
|
for (const user of users) {
|
||||||
|
const settings = await getImportSettingsByUser(user.id);
|
||||||
|
if (!settings) continue;
|
||||||
|
if (settings.emailImportEnabled === 1) {
|
||||||
|
console.log(`[AutoRestart] Restarting email import service for user ${user.id}...`);
|
||||||
|
await startEmailImportService(user.id).catch(e =>
|
||||||
|
console.error(`[AutoRestart] Failed to restart email service for user ${user.id}:`, e.message)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ((settings as any).autoImportEnabled === 1) {
|
||||||
|
console.log(`[AutoRestart] Restarting folder import service for user ${user.id}...`);
|
||||||
|
await startFolderImportService(user.id).catch(e =>
|
||||||
|
console.error(`[AutoRestart] Failed to restart folder service for user ${user.id}:`, e.message)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error('[AutoRestart] Error during service auto-restart:', e.message);
|
||||||
|
}
|
||||||
|
}, 5000); // Attendre 5s que le serveur soit prêt
|
||||||
}
|
}
|
||||||
|
|
||||||
startServer().catch(console.error);
|
startServer().catch(console.error);
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ async function processEmailAttachment(
|
|||||||
userId: number,
|
userId: number,
|
||||||
attachment: Attachment,
|
attachment: Attachment,
|
||||||
emailSubject: string
|
emailSubject: string
|
||||||
): Promise<{ success: boolean; totalInvoices: number; imported: number; duplicates: number; errors: number }> {
|
): Promise<{ success: boolean; totalInvoices: number; imported: number; duplicates: number; errors: number; quotaError?: boolean }> {
|
||||||
const fileName = attachment.filename || `email-attachment-${Date.now()}.pdf`;
|
const fileName = attachment.filename || `email-attachment-${Date.now()}.pdf`;
|
||||||
console.log(`[EmailImport] Processing attachment: ${fileName} from email: ${emailSubject}`);
|
console.log(`[EmailImport] Processing attachment: ${fileName} from email: ${emailSubject}`);
|
||||||
|
|
||||||
@@ -206,6 +206,20 @@ async function processEmailAttachment(
|
|||||||
processingProgress: `Terminé: ${importedCount} importée(s), ${duplicatesCount} doublon(s)`,
|
processingProgress: `Terminé: ${importedCount} importée(s), ${duplicatesCount} doublon(s)`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Détecter si des erreurs de quota ont eu lieu
|
||||||
|
const quotaErrors = errorDetails.filter(e =>
|
||||||
|
e.error && (
|
||||||
|
e.error.includes('usage exhausted') ||
|
||||||
|
e.error.includes('quota') ||
|
||||||
|
e.error.includes('rate limit') ||
|
||||||
|
e.error.includes('Precondition Failed') ||
|
||||||
|
e.error.includes('429')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const warningMessage = quotaErrors.length > 0
|
||||||
|
? `Quota IA épuisé : ${quotaErrors.length} facture(s) non extraite(s). Vérifiez votre quota Manus ou configurez une clé API externe dans Paramétrage → IA.`
|
||||||
|
: null;
|
||||||
|
|
||||||
// Create import log
|
// Create import log
|
||||||
await createImportLog({
|
await createImportLog({
|
||||||
userId,
|
userId,
|
||||||
@@ -217,6 +231,7 @@ async function processEmailAttachment(
|
|||||||
errors: errorsCount,
|
errors: errorsCount,
|
||||||
duplicateDetails: duplicateDetails.length > 0 ? JSON.stringify(duplicateDetails) : null,
|
duplicateDetails: duplicateDetails.length > 0 ? JSON.stringify(duplicateDetails) : null,
|
||||||
errorDetails: errorDetails.length > 0 ? JSON.stringify(errorDetails) : null,
|
errorDetails: errorDetails.length > 0 ? JSON.stringify(errorDetails) : null,
|
||||||
|
warningMessage,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`[EmailImport] Successfully processed attachment: ${fileName}`);
|
console.log(`[EmailImport] Successfully processed attachment: ${fileName}`);
|
||||||
@@ -229,14 +244,23 @@ async function processEmailAttachment(
|
|||||||
duplicates: duplicatesCount,
|
duplicates: duplicatesCount,
|
||||||
errors: errorsCount,
|
errors: errorsCount,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
console.error(`[EmailImport] Error processing attachment ${attachment.filename}:`, error);
|
console.error(`[EmailImport] Error processing attachment ${attachment.filename}:`, error);
|
||||||
|
// Détecter l'erreur de quota IA
|
||||||
|
const isQuotaError = error?.message && (
|
||||||
|
error.message.includes('usage exhausted') ||
|
||||||
|
error.message.includes('quota') ||
|
||||||
|
error.message.includes('429') ||
|
||||||
|
error.message.includes('rate limit') ||
|
||||||
|
error.message.includes('Precondition Failed')
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
totalInvoices: 0,
|
totalInvoices: 0,
|
||||||
imported: 0,
|
imported: 0,
|
||||||
duplicates: 0,
|
duplicates: 0,
|
||||||
errors: 1,
|
errors: 1,
|
||||||
|
quotaError: isQuotaError as boolean,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user