Improve how expired DLC products are handled and displayed

Refactor the database query in `server/storage.ts` to correctly calculate and display statistics for DLC products, ensuring expired items are not incorrectly re-activated and that statistics accurately reflect their expired status.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 1e4433b5-47ab-464c-b663-fea2e53367dd
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/1e4433b5-47ab-464c-b663-fea2e53367dd/bfW6MZh
This commit is contained in:
michaelschal committed 2025-10-29 17:49:21 +00:00
1 parent 0265d164e2
commit 706066089f
2 files changed
+307 -310

No files matched your search

+36 -48
View File
@@ -2076,49 +2076,34 @@ export class DatabaseStorage implements IStorage {
whereCondition = inArray(dlcProducts.groupId, groupIds); whereCondition = inArray(dlcProducts.groupId, groupIds);
} }
// Version rétrocompatible - fonctionne avec ou sans les nouveaux champs // IMPORTANT: Filtrer sur processedUntilExpiry pour ne pas réafficher les produits déjà traités
// IMPORTANT: Ne PAS filtrer sur processedUntilExpiry car cela crée une incohérence // Les produits "traités" (processedUntilExpiry=true) ne doivent pas apparaître dans les alertes quotidiennes
// entre getDlcProducts (qui ne filtre pas) et getDlcStats (qui filtrait) const [stats] = await db
// Les produits expirés sont expirés, qu'ils aient été traités ou non .select({
try { active: sql<number>`COUNT(CASE
// Essayer d'abord avec les nouveaux champs WHEN ${dlcProducts.expiryDate} > ${today.toISOString().split('T')[0]}
const [stats] = await db AND ${dlcProducts.status} != 'valides'
.select({ AND (${dlcProducts.processedUntilExpiry} IS NULL OR ${dlcProducts.processedUntilExpiry} = false)
active: sql<number>`COUNT(CASE WHEN ${dlcProducts.expiryDate} > ${today.toISOString().split('T')[0]} AND ${dlcProducts.status} != 'valides' THEN 1 END)`, THEN 1 END)`,
expiringSoon: sql<number>`COUNT(CASE expiringSoon: sql<number>`COUNT(CASE
WHEN ${dlcProducts.expiryDate} BETWEEN ${today.toISOString().split('T')[0]} AND ${alertDate.toISOString().split('T')[0]} WHEN ${dlcProducts.expiryDate} BETWEEN ${today.toISOString().split('T')[0]} AND ${alertDate.toISOString().split('T')[0]}
AND ${dlcProducts.status} != 'valides' AND ${dlcProducts.status} != 'valides'
THEN 1 END)`, AND (${dlcProducts.processedUntilExpiry} IS NULL OR ${dlcProducts.processedUntilExpiry} = false)
expired: sql<number>`COUNT(CASE THEN 1 END)`,
WHEN ${dlcProducts.expiryDate} <= ${today.toISOString().split('T')[0]} expired: sql<number>`COUNT(CASE
AND ${dlcProducts.status} != 'valides' WHEN ${dlcProducts.expiryDate} <= ${today.toISOString().split('T')[0]}
THEN 1 END)` AND ${dlcProducts.status} != 'valides'
}) AND (${dlcProducts.processedUntilExpiry} IS NULL OR ${dlcProducts.processedUntilExpiry} = false)
.from(dlcProducts) THEN 1 END)`
.where(whereCondition); })
.from(dlcProducts)
.where(whereCondition);
return { return {
active: stats.active || 0, active: stats.active || 0,
expiringSoon: stats.expiringSoon || 0, expiringSoon: stats.expiringSoon || 0,
expired: stats.expired || 0 expired: stats.expired || 0
}; };
} catch (error) {
// Fallback pour les bases de données sans les nouveaux champs
const [stats] = await db
.select({
active: sql<number>`COUNT(CASE WHEN ${dlcProducts.expiryDate} > ${today.toISOString().split('T')[0]} AND ${dlcProducts.status} != 'valides' THEN 1 END)`,
expiringSoon: sql<number>`COUNT(CASE WHEN ${dlcProducts.expiryDate} BETWEEN ${today.toISOString().split('T')[0]} AND ${alertDate.toISOString().split('T')[0]} AND ${dlcProducts.status} != 'valides' THEN 1 END)`,
expired: sql<number>`COUNT(CASE WHEN ${dlcProducts.expiryDate} <= ${today.toISOString().split('T')[0]} AND ${dlcProducts.status} != 'valides' THEN 1 END)`
})
.from(dlcProducts)
.where(whereCondition);
return {
active: stats.active || 0,
expiringSoon: stats.expiringSoon || 0,
expired: stats.expired || 0
};
}
} }
async getTasks(groupIds?: number[], userRole?: string): Promise<TaskWithRelations[]> { async getTasks(groupIds?: number[], userRole?: string): Promise<TaskWithRelations[]> {
@@ -4242,20 +4227,23 @@ export class MemStorage implements IStorage {
products = products.filter(product => groupIds.includes(product.groupId)); products = products.filter(product => groupIds.includes(product.groupId));
} }
// IMPORTANT: Ne PAS filtrer sur processedUntilExpiry car cela crée une incohérence // IMPORTANT: Filtrer sur processedUntilExpiry pour ne pas réafficher les produits déjà traités
// entre getDlcProducts (qui ne filtre pas) et getDlcStats (qui filtrait) // Les produits "traités" (processedUntilExpiry=true) ne doivent pas apparaître dans les alertes quotidiennes
// Les produits expirés sont expirés, qu'ils aient été traités ou non
return { return {
active: products.filter(p => new Date(p.expiryDate) > today && p.status !== 'valides').length, active: products.filter(p =>
new Date(p.expiryDate) > today &&
p.status !== 'valides' &&
!p.processedUntilExpiry
).length,
expiringSoon: products.filter(p => { expiringSoon: products.filter(p => {
const expiry = new Date(p.expiryDate); const expiry = new Date(p.expiryDate);
const isExpiringSoon = expiry >= today && expiry <= alertDate; const isExpiringSoon = expiry >= today && expiry <= alertDate;
return isExpiringSoon && p.status !== 'valides'; return isExpiringSoon && p.status !== 'valides' && !p.processedUntilExpiry;
}).length, }).length,
expired: products.filter(p => { expired: products.filter(p => {
const expiry = new Date(p.expiryDate); const expiry = new Date(p.expiryDate);
const isExpired = expiry <= today; const isExpired = expiry <= today;
return isExpired && p.status !== 'valides'; return isExpired && p.status !== 'valides' && !p.processedUntilExpiry;
}).length }).length
}; };
} }