Checkpoint: Ajout de la table deletedInvoices (blacklist) : quand une facture est supprimée, son numéro est enregistré et l'import email/fichier ne la réimporte plus.
This commit is contained in:
@@ -244,11 +244,11 @@ export default function Invoices() {
|
|||||||
if (subscriptionFilter === "yes" && !isSub) return false;
|
if (subscriptionFilter === "yes" && !isSub) return false;
|
||||||
if (subscriptionFilter === "no" && isSub) return false;
|
if (subscriptionFilter === "no" && isSub) return false;
|
||||||
}
|
}
|
||||||
// Filter by entity (SANTINOVA = ventilationComptable === 'SANTINOVA', ITINOVA = autre)
|
// Filter by entity (SANTINOVA = serviceConcerne === 'DSI SANTINOVA', ITINOVA = autre)
|
||||||
if (entityFilter !== "all") {
|
if (entityFilter !== "all") {
|
||||||
const ventil = ((inv as any).ventilationComptable || "").toUpperCase();
|
const service = ((inv as any).serviceConcerne || "").trim().toUpperCase();
|
||||||
if (entityFilter === "santinova" && ventil !== "SANTINOVA") return false;
|
if (entityFilter === "santinova" && service !== "DSI SANTINOVA") return false;
|
||||||
if (entityFilter === "itinova" && ventil === "SANTINOVA") return false;
|
if (entityFilter === "itinova" && service === "DSI SANTINOVA") return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import bcrypt from "bcrypt";
|
|||||||
async function createAdmin() {
|
async function createAdmin() {
|
||||||
const db = drizzle(process.env.DATABASE_URL);
|
const db = drizzle(process.env.DATABASE_URL);
|
||||||
|
|
||||||
const email = "adminServFacturation";
|
const email = "adminItinova";
|
||||||
const password = "Itinova69!";
|
const password = "Itinova69!";
|
||||||
const name = "Administrateur";
|
const name = "Administrateur";
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ async function createAdmin() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
console.log("✅ Utilisateur administrateur créé avec succès !");
|
console.log("✅ Utilisateur administrateur créé avec succès !");
|
||||||
console.log("📧 Email/Login: adminServFacturation");
|
console.log("📧 Email/Login: adminItinova");
|
||||||
console.log("🔑 Mot de passe: Itinova69!");
|
console.log("🔑 Mot de passe: Itinova69!");
|
||||||
console.log("👤 Rôle: admin");
|
console.log("👤 Rôle: admin");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
9
drizzle/0035_eminent_dreadnoughts.sql
Normal file
9
drizzle/0035_eminent_dreadnoughts.sql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
CREATE TABLE `deletedInvoices` (
|
||||||
|
`id` int AUTO_INCREMENT NOT NULL,
|
||||||
|
`userId` int NOT NULL,
|
||||||
|
`invoiceNumber` varchar(100) NOT NULL,
|
||||||
|
`totalAmount` varchar(50),
|
||||||
|
`supplierName` varchar(255),
|
||||||
|
`deletedAt` timestamp NOT NULL DEFAULT (now()),
|
||||||
|
CONSTRAINT `deletedInvoices_id` PRIMARY KEY(`id`)
|
||||||
|
);
|
||||||
2229
drizzle/meta/0035_snapshot.json
Normal file
2229
drizzle/meta/0035_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -246,6 +246,13 @@
|
|||||||
"when": 1785167359371,
|
"when": 1785167359371,
|
||||||
"tag": "0034_black_shadowcat",
|
"tag": "0034_black_shadowcat",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 35,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1785404752594,
|
||||||
|
"tag": "0035_eminent_dreadnoughts",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -508,3 +508,19 @@ export const freeproSettings = mysqlTable("freeproSettings", {
|
|||||||
});
|
});
|
||||||
export type FreeproSettings = typeof freeproSettings.$inferSelect;
|
export type FreeproSettings = typeof freeproSettings.$inferSelect;
|
||||||
export type InsertFreeproSettings = typeof freeproSettings.$inferInsert;
|
export type InsertFreeproSettings = typeof freeproSettings.$inferInsert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deleted invoices blacklist — prevents re-import of manually deleted invoices.
|
||||||
|
* When a user deletes an invoice, its invoiceNumber + totalAmount are stored here.
|
||||||
|
* The email/file import service checks this table before inserting a new invoice.
|
||||||
|
*/
|
||||||
|
export const deletedInvoices = mysqlTable("deletedInvoices", {
|
||||||
|
id: int("id").autoincrement().primaryKey(),
|
||||||
|
userId: int("userId").notNull(),
|
||||||
|
invoiceNumber: varchar("invoiceNumber", { length: 100 }).notNull(),
|
||||||
|
totalAmount: varchar("totalAmount", { length: 50 }),
|
||||||
|
supplierName: varchar("supplierName", { length: 255 }),
|
||||||
|
deletedAt: timestamp("deletedAt").defaultNow().notNull(),
|
||||||
|
});
|
||||||
|
export type DeletedInvoice = typeof deletedInvoices.$inferSelect;
|
||||||
|
export type InsertDeletedInvoice = typeof deletedInvoices.$inferInsert;
|
||||||
|
|||||||
29
server/db.ts
29
server/db.ts
@@ -44,7 +44,10 @@ import {
|
|||||||
BapHistory,
|
BapHistory,
|
||||||
invoiceLearnings,
|
invoiceLearnings,
|
||||||
InsertInvoiceLearning,
|
InsertInvoiceLearning,
|
||||||
InvoiceLearning
|
InvoiceLearning,
|
||||||
|
deletedInvoices,
|
||||||
|
InsertDeletedInvoice,
|
||||||
|
DeletedInvoice
|
||||||
} from "../drizzle/schema";
|
} from "../drizzle/schema";
|
||||||
import { ENV } from './_core/env';
|
import { ENV } from './_core/env';
|
||||||
|
|
||||||
@@ -265,9 +268,33 @@ export async function updateInvoice(id: number, data: Partial<Invoice>) {
|
|||||||
export async function deleteInvoice(id: number) {
|
export async function deleteInvoice(id: number) {
|
||||||
const db = await getDb();
|
const db = await getDb();
|
||||||
if (!db) throw new Error("Database not available");
|
if (!db) throw new Error("Database not available");
|
||||||
|
// Enregistrer dans la blacklist avant suppression
|
||||||
|
const invoice = await db.select().from(invoices).where(eq(invoices.id, id)).limit(1);
|
||||||
|
if (invoice[0] && invoice[0].invoiceNumber) {
|
||||||
|
await db.insert(deletedInvoices).values({
|
||||||
|
userId: invoice[0].userId,
|
||||||
|
invoiceNumber: invoice[0].invoiceNumber,
|
||||||
|
totalAmount: invoice[0].totalAmount ?? undefined,
|
||||||
|
supplierName: invoice[0].supplierName ?? undefined,
|
||||||
|
}).onDuplicateKeyUpdate({ set: { deletedAt: new Date() } });
|
||||||
|
}
|
||||||
await db.delete(invoices).where(eq(invoices.id, id));
|
await db.delete(invoices).where(eq(invoices.id, id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Vérifie si une facture est dans la blacklist (supprimée manuellement) */
|
||||||
|
export async function isInvoiceBlacklisted(
|
||||||
|
invoiceNumber: string | null,
|
||||||
|
userId: number
|
||||||
|
): Promise<boolean> {
|
||||||
|
if (!invoiceNumber) return false;
|
||||||
|
const db = await getDb();
|
||||||
|
if (!db) return false;
|
||||||
|
const result = await db.select().from(deletedInvoices)
|
||||||
|
.where(and(eq(deletedInvoices.userId, userId), eq(deletedInvoices.invoiceNumber, invoiceNumber)))
|
||||||
|
.limit(1);
|
||||||
|
return result.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
export async function searchInvoices(userId: number | null, query: string): Promise<Invoice[]> {
|
export async function searchInvoices(userId: number | null, query: string): Promise<Invoice[]> {
|
||||||
const db = await getDb();
|
const db = await getDb();
|
||||||
if (!db) return [];
|
if (!db) return [];
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
updateSourceFile,
|
updateSourceFile,
|
||||||
getUserSettings,
|
getUserSettings,
|
||||||
findDuplicateInvoice,
|
findDuplicateInvoice,
|
||||||
|
isInvoiceBlacklisted,
|
||||||
createInvoice,
|
createInvoice,
|
||||||
createImportLog,
|
createImportLog,
|
||||||
} from "./db";
|
} from "./db";
|
||||||
@@ -130,6 +131,19 @@ async function processEmailAttachment(
|
|||||||
processingProgress: `Extraction ${i + 1}/${result.invoiceCount} factures...`,
|
processingProgress: `Extraction ${i + 1}/${result.invoiceCount} factures...`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Vérifier la blacklist (factures supprimées manuellement)
|
||||||
|
const blacklisted = await isInvoiceBlacklisted(invoiceData.invoiceNumber, userId);
|
||||||
|
if (blacklisted) {
|
||||||
|
duplicatesCount++;
|
||||||
|
duplicateDetails.push({
|
||||||
|
supplierName: invoiceData.supplierName,
|
||||||
|
invoiceNumber: invoiceData.invoiceNumber,
|
||||||
|
totalAmount: invoiceData.totalAmount,
|
||||||
|
reason: 'blacklisted',
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Check for duplicates (numéro de facture + montant)
|
// Check for duplicates (numéro de facture + montant)
|
||||||
const duplicate = await findDuplicateInvoice(
|
const duplicate = await findDuplicateInvoice(
|
||||||
invoiceData.invoiceNumber,
|
invoiceData.invoiceNumber,
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import {
|
|||||||
toggleUserActive,
|
toggleUserActive,
|
||||||
deleteUser,
|
deleteUser,
|
||||||
findDuplicateInvoice,
|
findDuplicateInvoice,
|
||||||
|
isInvoiceBlacklisted,
|
||||||
getAllInvoices,
|
getAllInvoices,
|
||||||
getAllImportLogs,
|
getAllImportLogs,
|
||||||
getAllBapHistory,
|
getAllBapHistory,
|
||||||
@@ -253,6 +254,19 @@ export const appRouter = router({
|
|||||||
processingProgress: `Extraction ${i + 1}/${result.invoiceCount} factures...`,
|
processingProgress: `Extraction ${i + 1}/${result.invoiceCount} factures...`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Vérifier la blacklist (factures supprimées manuellement)
|
||||||
|
const blacklisted = await isInvoiceBlacklisted(invoiceData.invoiceNumber, userId);
|
||||||
|
if (blacklisted) {
|
||||||
|
duplicatesCount++;
|
||||||
|
duplicateDetails.push({
|
||||||
|
supplierName: invoiceData.supplierName,
|
||||||
|
invoiceNumber: invoiceData.invoiceNumber,
|
||||||
|
totalAmount: invoiceData.totalAmount,
|
||||||
|
reason: 'blacklisted',
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Check for duplicates (numéro de facture + montant)
|
// Check for duplicates (numéro de facture + montant)
|
||||||
const duplicate = await findDuplicateInvoice(
|
const duplicate = await findDuplicateInvoice(
|
||||||
invoiceData.invoiceNumber,
|
invoiceData.invoiceNumber,
|
||||||
|
|||||||
Reference in New Issue
Block a user