feat: warningMessage quota IA dans rapports d'import + redémarrage auto services

This commit is contained in:
Manus
2026-06-09 12:15:26 -04:00
parent 595f5dded1
commit f49eadda2b
7 changed files with 2224 additions and 3 deletions

View File

@@ -25,6 +25,7 @@ type ImportLog = {
errors: number;
duplicateDetails: string | null;
errorDetails: string | null;
warningMessage?: string | null;
importedAt: Date | string;
};
@@ -390,7 +391,7 @@ export default function ImportReport() {
/>
</TableCell>
<TableCell className="text-center">
{(log.duplicatesIgnored > 0 || log.errors > 0) && (
{(log.duplicatesIgnored > 0 || log.errors > 0 || (log as any).warningMessage) && (
<Button
size="sm"
variant="ghost"
@@ -486,6 +487,17 @@ export default function ImportReport() {
</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 */}
{selectedLog.errors > 0 && (
<div>

View File

@@ -0,0 +1 @@
ALTER TABLE `importLogs` ADD `warningMessage` text;

File diff suppressed because it is too large Load Diff

View File

@@ -218,6 +218,13 @@
"when": 1780930222253,
"tag": "0030_vengeful_warbird",
"breakpoints": true
},
{
"idx": 31,
"version": "5",
"when": 1781021650065,
"tag": "0031_mean_squadron_supreme",
"breakpoints": true
}
]
}

View File

@@ -222,6 +222,7 @@ export const importLogs = mysqlTable("importLogs", {
errors: int("errors").default(0).notNull(),
duplicateDetails: text("duplicateDetails"), // JSON array of duplicate invoice info
errorDetails: text("errorDetails"), // JSON array of error messages
warningMessage: text("warningMessage"), // Warning message (e.g. quota exhausted)
importedAt: timestamp("importedAt").defaultNow().notNull(),
});

View File

@@ -10,6 +10,10 @@ import { registerOAuthRoutes } from "./oauth";
import { appRouter } from "../routers";
import { createContext } from "./context";
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> {
return new Promise(resolve => {
@@ -164,6 +168,31 @@ async function startServer() {
server.listen(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);

View File

@@ -40,7 +40,7 @@ async function processEmailAttachment(
userId: number,
attachment: Attachment,
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`;
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)`,
});
// 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
await createImportLog({
userId,
@@ -217,6 +231,7 @@ async function processEmailAttachment(
errors: errorsCount,
duplicateDetails: duplicateDetails.length > 0 ? JSON.stringify(duplicateDetails) : null,
errorDetails: errorDetails.length > 0 ? JSON.stringify(errorDetails) : null,
warningMessage,
});
console.log(`[EmailImport] Successfully processed attachment: ${fileName}`);
@@ -229,14 +244,23 @@ async function processEmailAttachment(
duplicates: duplicatesCount,
errors: errorsCount,
};
} catch (error) {
} catch (error: any) {
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 {
success: false,
totalInvoices: 0,
imported: 0,
duplicates: 0,
errors: 1,
quotaError: isQuotaError as boolean,
};
}
}