Merge pull request #469 from R0m1k3/1.1

1.1
This commit is contained in:
LogiFlow authored and GitHub committed 2025-09-27 11:31:47 +02:00
commit 42349bd01e
5 files changed
+794 -544

No files matched your search

+8
View File
@@ -15,6 +15,10 @@ run = ["npm", "run", "start"]
localPort = 5000 localPort = 5000
externalPort = 80 externalPort = 80
[[ports]]
localPort = 33121
externalPort = 4200
[[ports]] [[ports]]
localPort = 33873 localPort = 33873
externalPort = 3003 externalPort = 3003
@@ -35,6 +39,10 @@ externalPort = 5000
localPort = 41343 localPort = 41343
externalPort = 3001 externalPort = 3001
[[ports]]
localPort = 44549
externalPort = 5173
[workflows] [workflows]
runButton = "Project" runButton = "Project"
+24 -4
View File
@@ -965,17 +965,37 @@ export default function BLReconciliation() {
</Button> </Button>
{!delivery.reconciled ? ( {!delivery.reconciled ? (
<> <>
{permissions.canValidate('reconciliation') && ( {permissions.canValidate('reconciliation') && verificationResults[delivery.id]?.exists === true ? (
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
onClick={() => handleQuickValidate(delivery)} onClick={() => handleQuickValidate(delivery)}
className="h-8 w-8 p-0" className="h-8 w-8 p-0 text-green-600 hover:text-green-700"
title="Valider le rapprochement" title="Valider le rapprochement (facture vérifiée)"
> >
<Check className="w-4 h-4" /> <Check className="w-4 h-4" />
</Button> </Button>
)} ) : permissions.canValidate('reconciliation') && verificationResults[delivery.id]?.exists === false ? (
<Button
variant="outline"
size="sm"
disabled
className="h-8 w-8 p-0 text-gray-400 cursor-not-allowed"
title="Validation impossible : la facture n'a pas été trouvée (coche rouge). Vérifiez la référence de facture."
>
<Check className="w-4 h-4" />
</Button>
) : permissions.canValidate('reconciliation') ? (
<Button
variant="outline"
size="sm"
disabled
className="h-8 w-8 p-0 text-gray-400 cursor-not-allowed"
title="Validation impossible : veuillez d'abord vérifier la facture en cliquant sur l'icône de recherche"
>
<Check className="w-4 h-4" />
</Button>
) : null}
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
+87 -41
View File
@@ -1993,31 +1993,57 @@ export class DatabaseStorage implements IStorage {
} }
async markDlcProductAsProcessed(id: number, processedBy: string): Promise<DlcProductFrontend> { async markDlcProductAsProcessed(id: number, processedBy: string): Promise<DlcProductFrontend> {
const [dlcProduct] = await db try {
.update(dlcProducts) // Essayer avec les nouveaux champs si ils existent
.set({ const [dlcProduct] = await db
processedAt: new Date(), .update(dlcProducts)
processedBy: processedBy, .set({
processedUntilExpiry: true, processedAt: new Date(),
updatedAt: new Date() processedBy: processedBy,
}) processedUntilExpiry: true,
.where(eq(dlcProducts.id, id)) updatedAt: new Date()
.returning(); })
return dlcProduct as DlcProductFrontend; .where(eq(dlcProducts.id, id))
.returning();
return dlcProduct as DlcProductFrontend;
} catch (error) {
// Fallback : juste mettre à jour la date de modification
const [dlcProduct] = await db
.update(dlcProducts)
.set({
updatedAt: new Date()
})
.where(eq(dlcProducts.id, id))
.returning();
return dlcProduct as DlcProductFrontend;
}
} }
async unmarkDlcProductAsProcessed(id: number): Promise<DlcProductFrontend> { async unmarkDlcProductAsProcessed(id: number): Promise<DlcProductFrontend> {
const [dlcProduct] = await db try {
.update(dlcProducts) // Essayer avec les nouveaux champs si ils existent
.set({ const [dlcProduct] = await db
processedAt: null, .update(dlcProducts)
processedBy: null, .set({
processedUntilExpiry: false, processedAt: null,
updatedAt: new Date() processedBy: null,
}) processedUntilExpiry: false,
.where(eq(dlcProducts.id, id)) updatedAt: new Date()
.returning(); })
return dlcProduct as DlcProductFrontend; .where(eq(dlcProducts.id, id))
.returning();
return dlcProduct as DlcProductFrontend;
} catch (error) {
// Fallback : juste mettre à jour la date de modification
const [dlcProduct] = await db
.update(dlcProducts)
.set({
updatedAt: new Date()
})
.where(eq(dlcProducts.id, id))
.returning();
return dlcProduct as DlcProductFrontend;
}
} }
async getDlcStats(groupIds?: number[]): Promise<{ active: number; expiringSoon: number; expired: number; }> { async getDlcStats(groupIds?: number[]): Promise<{ active: number; expiringSoon: number; expired: number; }> {
@@ -2030,26 +2056,46 @@ export class DatabaseStorage implements IStorage {
whereCondition = inArray(dlcProducts.groupId, groupIds); whereCondition = inArray(dlcProducts.groupId, groupIds);
} }
const [stats] = await db // Version rétrocompatible - fonctionne avec ou sans les nouveaux champs
.select({ try {
active: sql<number>`COUNT(CASE WHEN ${dlcProducts.expiryDate} > ${today.toISOString().split('T')[0]} THEN 1 END)`, // Essayer d'abord avec les nouveaux champs
expiringSoon: sql<number>`COUNT(CASE const [stats] = await db
WHEN ${dlcProducts.expiryDate} BETWEEN ${today.toISOString().split('T')[0]} AND ${alertDate.toISOString().split('T')[0]} .select({
AND (${dlcProducts.processedUntilExpiry} IS NULL OR ${dlcProducts.processedUntilExpiry} = false) active: sql<number>`COUNT(CASE WHEN ${dlcProducts.expiryDate} > ${today.toISOString().split('T')[0]} THEN 1 END)`,
THEN 1 END)`, expiringSoon: sql<number>`COUNT(CASE
expired: sql<number>`COUNT(CASE WHEN ${dlcProducts.expiryDate} BETWEEN ${today.toISOString().split('T')[0]} AND ${alertDate.toISOString().split('T')[0]}
WHEN ${dlcProducts.expiryDate} <= ${today.toISOString().split('T')[0]} AND (${dlcProducts.processedUntilExpiry} IS NULL OR ${dlcProducts.processedUntilExpiry} = false)
AND (${dlcProducts.processedUntilExpiry} IS NULL OR ${dlcProducts.processedUntilExpiry} = false OR ${dlcProducts.expiryDate} < ${today.toISOString().split('T')[0]}) THEN 1 END)`,
THEN 1 END)` expired: sql<number>`COUNT(CASE
}) WHEN ${dlcProducts.expiryDate} <= ${today.toISOString().split('T')[0]}
.from(dlcProducts) AND (${dlcProducts.processedUntilExpiry} IS NULL OR ${dlcProducts.processedUntilExpiry} = false OR ${dlcProducts.expiryDate} < ${today.toISOString().split('T')[0]})
.where(whereCondition); THEN 1 END)`
})
.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]} THEN 1 END)`,
expiringSoon: sql<number>`COUNT(CASE WHEN ${dlcProducts.expiryDate} BETWEEN ${today.toISOString().split('T')[0]} AND ${alertDate.toISOString().split('T')[0]} THEN 1 END)`,
expired: sql<number>`COUNT(CASE WHEN ${dlcProducts.expiryDate} <= ${today.toISOString().split('T')[0]} 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[]> {