mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-24 07:28:19 +02:00
3 files changed
+77
-31
No files matched your search
@@ -82,6 +82,7 @@ export function DlcAlertModal({ isOpen, onClose, dlcStats, selectedStoreId }: Dl
|
||||
});
|
||||
// Refresh DLC stats and products
|
||||
queryClient.invalidateQueries({ queryKey: ["/api/dlc-products"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["/api/dlc-products/stats"] });
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
|
||||
+45
-4
@@ -5,7 +5,7 @@ import path from 'path';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { eq, desc } from "drizzle-orm";
|
||||
import { db } from "./db";
|
||||
import { databaseBackups, utilities } from "@shared/schema";
|
||||
import { databaseBackups, utilities, users } from "@shared/schema";
|
||||
import type { DatabaseBackup, InsertDatabaseBackup } from "@shared/schema";
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
@@ -70,6 +70,31 @@ export class BackupService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a valid user ID for system-initiated backups.
|
||||
* Returns the first admin user ID, or the first user if no admin exists.
|
||||
* This avoids FK violations on database_backups.created_by -> users.id.
|
||||
*/
|
||||
private async resolveSystemUserId(): Promise<string | null> {
|
||||
try {
|
||||
// Try to find an admin user first
|
||||
const [adminUser] = await db.select({ id: users.id })
|
||||
.from(users)
|
||||
.where(eq(users.role, 'admin'))
|
||||
.limit(1);
|
||||
if (adminUser) return adminUser.id;
|
||||
|
||||
// Fallback: any existing user
|
||||
const [anyUser] = await db.select({ id: users.id })
|
||||
.from(users)
|
||||
.limit(1);
|
||||
return anyUser?.id ?? null;
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to resolve system user ID for backup:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async createBackup(type: 'manual' | 'automatic' = 'manual', createdBy: string = 'system'): Promise<DatabaseBackup> {
|
||||
try {
|
||||
const id = nanoid();
|
||||
@@ -240,10 +265,20 @@ export class BackupService {
|
||||
const lastBackup = existingBackupToday[0];
|
||||
const lastBackupDate = lastBackup ? lastBackup.createdAt.toISOString().split('T')[0] : null;
|
||||
|
||||
// Si aucune sauvegarde automatique aujourd'hui, en créer une
|
||||
// Only perform backup if no automatic backup exists for today
|
||||
if (lastBackupDate !== today) {
|
||||
// Resolve a real user ID if called with default 'system'
|
||||
let resolvedUserId = userId;
|
||||
if (userId === 'system') {
|
||||
const systemId = await this.resolveSystemUserId();
|
||||
if (!systemId) {
|
||||
console.warn('⚠️ No user found in database — skipping daily backup');
|
||||
return { backupPerformed: false, message: 'Aucun utilisateur trouvé pour la sauvegarde' };
|
||||
}
|
||||
resolvedUserId = systemId;
|
||||
}
|
||||
console.log('🔄 Première connexion du jour - Création de la sauvegarde automatique...');
|
||||
await this.createBackup('automatic', userId);
|
||||
await this.createBackup('automatic', resolvedUserId);
|
||||
console.log('✅ Sauvegarde quotidienne effectuée avec succès');
|
||||
return {
|
||||
backupPerformed: true,
|
||||
@@ -298,8 +333,14 @@ export class BackupService {
|
||||
|
||||
// Only proceed if no automatic backup exists for today
|
||||
if (lastBackupDate !== today) {
|
||||
// Resolve a real user ID to satisfy FK constraint
|
||||
const systemUserId = await this.resolveSystemUserId();
|
||||
if (!systemUserId) {
|
||||
console.warn('⚠️ No user found in database — skipping automatic backup');
|
||||
return;
|
||||
}
|
||||
console.log('🔄 Starting automatic backup...');
|
||||
await this.createBackup('automatic', 'system');
|
||||
await this.createBackup('automatic', systemUserId);
|
||||
this.lastAutomaticBackupDate = today;
|
||||
console.log('✅ Automatic backup completed');
|
||||
}
|
||||
|
||||
+31
-27
@@ -1520,20 +1520,20 @@ export class DatabaseStorage implements IStorage {
|
||||
// LOG: Debug des publicités récupérées
|
||||
console.log(`📋 PUBLICITES FETCHED: ${results.length} résultats pour année ${year || 'toutes'}`);
|
||||
if (results.length > 0) {
|
||||
console.log('🔍 PREMIERS RESULTATS:', results.slice(0, 3).map((p, i) => `${i+1}. N°${p.pubNumber} - ${p.designation}`));
|
||||
console.log('🔍 PREMIERS RESULTATS:', results.slice(0, 3).map((p, i) => `${i + 1}. N°${p.pubNumber} - ${p.designation}`));
|
||||
}
|
||||
|
||||
const publicityIds = results.map((p: any) => p.id);
|
||||
const participations = publicityIds.length > 0
|
||||
? await db
|
||||
.select({
|
||||
publicityId: publicityParticipations.publicityId,
|
||||
groupId: publicityParticipations.groupId,
|
||||
group: groups,
|
||||
})
|
||||
.from(publicityParticipations)
|
||||
.leftJoin(groups, eq(publicityParticipations.groupId, groups.id))
|
||||
.where(inArray(publicityParticipations.publicityId, publicityIds))
|
||||
.select({
|
||||
publicityId: publicityParticipations.publicityId,
|
||||
groupId: publicityParticipations.groupId,
|
||||
group: groups,
|
||||
})
|
||||
.from(publicityParticipations)
|
||||
.leftJoin(groups, eq(publicityParticipations.groupId, groups.id))
|
||||
.where(inArray(publicityParticipations.publicityId, publicityIds))
|
||||
: [];
|
||||
|
||||
// Sort by pubNumber as integer on the server side for consistency
|
||||
@@ -2127,16 +2127,19 @@ export class DatabaseStorage implements IStorage {
|
||||
WHEN ${dlcProducts.expiryDate} > ${today.toISOString().split('T')[0]}
|
||||
AND ${dlcProducts.status} != 'valides'
|
||||
AND (${dlcProducts.processedUntilExpiry} IS NULL OR ${dlcProducts.processedUntilExpiry} = false)
|
||||
AND (${dlcProducts.stockEpuise} IS NULL OR ${dlcProducts.stockEpuise} = false)
|
||||
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'
|
||||
AND (${dlcProducts.processedUntilExpiry} IS NULL OR ${dlcProducts.processedUntilExpiry} = false)
|
||||
AND (${dlcProducts.stockEpuise} IS NULL OR ${dlcProducts.stockEpuise} = false)
|
||||
THEN 1 END)`,
|
||||
expired: sql<number>`COUNT(CASE
|
||||
WHEN ${dlcProducts.expiryDate} <= ${today.toISOString().split('T')[0]}
|
||||
AND ${dlcProducts.status} != 'valides'
|
||||
AND (${dlcProducts.processedUntilExpiry} IS NULL OR ${dlcProducts.processedUntilExpiry} = false)
|
||||
AND (${dlcProducts.stockEpuise} IS NULL OR ${dlcProducts.stockEpuise} = false)
|
||||
THEN 1 END)`
|
||||
})
|
||||
.from(dlcProducts)
|
||||
@@ -3075,8 +3078,8 @@ export class DatabaseStorage implements IStorage {
|
||||
count: sql<number>`COUNT(${deliveries.id})`,
|
||||
amount: sql<number>`COALESCE(SUM(CAST(${deliveries.blAmount} AS NUMERIC)), 0)`
|
||||
})
|
||||
.from(deliveries)
|
||||
.innerJoin(suppliers, eq(deliveries.supplierId, suppliers.id));
|
||||
.from(deliveries)
|
||||
.innerJoin(suppliers, eq(deliveries.supplierId, suppliers.id));
|
||||
|
||||
if (deliveryConditions.length) supplierQuery.where(and(...deliveryConditions));
|
||||
const topSuppliers = await supplierQuery
|
||||
@@ -3091,9 +3094,9 @@ export class DatabaseStorage implements IStorage {
|
||||
orders: sql<number>`COUNT(DISTINCT ${orders.id})`,
|
||||
deliveries: sql<number>`COUNT(DISTINCT ${deliveries.id})`
|
||||
})
|
||||
.from(groups)
|
||||
.leftJoin(orders, eq(groups.id, orders.groupId))
|
||||
.leftJoin(deliveries, eq(groups.id, deliveries.groupId));
|
||||
.from(groups)
|
||||
.leftJoin(orders, eq(groups.id, orders.groupId))
|
||||
.leftJoin(deliveries, eq(groups.id, deliveries.groupId));
|
||||
|
||||
if (filters.groupIds?.length) {
|
||||
storeQuery.where(inArray(groups.id, filters.groupIds));
|
||||
@@ -3137,7 +3140,7 @@ export class DatabaseStorage implements IStorage {
|
||||
}): Promise<Array<{ date: string; orders: number; deliveries: number }>> {
|
||||
const granularity = filters.granularity || 'day';
|
||||
const dateFormat = granularity === 'day' ? 'YYYY-MM-DD' :
|
||||
granularity === 'week' ? 'YYYY-IW' : 'YYYY-MM';
|
||||
granularity === 'week' ? 'YYYY-IW' : 'YYYY-MM';
|
||||
|
||||
// Build WHERE clauses with direct value substitution
|
||||
const orderConditions: string[] = [];
|
||||
@@ -3226,8 +3229,8 @@ export class DatabaseStorage implements IStorage {
|
||||
deliveries: sql<number>`COUNT(${deliveries.id})`,
|
||||
amount: sql<number>`COALESCE(SUM(CAST(${deliveries.blAmount} AS NUMERIC)), 0)`
|
||||
})
|
||||
.from(deliveries)
|
||||
.innerJoin(suppliers, eq(deliveries.supplierId, suppliers.id));
|
||||
.from(deliveries)
|
||||
.innerJoin(suppliers, eq(deliveries.supplierId, suppliers.id));
|
||||
|
||||
if (conditions.length) query.where(and(...conditions));
|
||||
|
||||
@@ -3980,7 +3983,7 @@ export class MemStorage implements IStorage {
|
||||
|
||||
async getUserGroups(): Promise<UserGroup[]> { return []; }
|
||||
async assignUserToGroup(): Promise<UserGroup> { return {} as UserGroup; }
|
||||
async removeUserFromGroup(): Promise<void> {}
|
||||
async removeUserFromGroup(): Promise<void> { }
|
||||
|
||||
async getMonthlyStats(): Promise<any> {
|
||||
return {
|
||||
@@ -4030,9 +4033,9 @@ export class MemStorage implements IStorage {
|
||||
async getPublicity(): Promise<PublicityWithRelations | undefined> { return undefined; }
|
||||
async createPublicity(): Promise<Publicity> { return {} as Publicity; }
|
||||
async updatePublicity(): Promise<Publicity> { return {} as Publicity; }
|
||||
async deletePublicity(): Promise<void> {}
|
||||
async deletePublicity(): Promise<void> { }
|
||||
async getPublicityParticipations(): Promise<PublicityParticipation[]> { return []; }
|
||||
async setPublicityParticipations(): Promise<void> {}
|
||||
async setPublicityParticipations(): Promise<void> { }
|
||||
|
||||
async getCustomerOrders(groupIds?: number[]): Promise<CustomerOrderWithRelations[]> {
|
||||
let orders = Array.from(this.customerOrders.values());
|
||||
@@ -4103,9 +4106,9 @@ export class MemStorage implements IStorage {
|
||||
// Find orders where client not notified and status is 'Disponible'
|
||||
const pendingOrders = orders.filter(order => {
|
||||
return !order.customerNotified &&
|
||||
(order.status === 'Disponible' || order.status === 'disponible' ||
|
||||
order.status === 'Arrivé' || order.status === 'arrivé' ||
|
||||
order.status === 'Prêt' || order.status === 'prêt');
|
||||
(order.status === 'Disponible' || order.status === 'disponible' ||
|
||||
order.status === 'Arrivé' || order.status === 'arrivé' ||
|
||||
order.status === 'Prêt' || order.status === 'prêt');
|
||||
});
|
||||
|
||||
return pendingOrders.map(order => ({
|
||||
@@ -4151,7 +4154,7 @@ export class MemStorage implements IStorage {
|
||||
products = products.filter(product => {
|
||||
const supplier = this.suppliers.get(product.supplierId);
|
||||
return product.productName.toLowerCase().includes(searchTerm) ||
|
||||
(supplier && supplier.name.toLowerCase().includes(searchTerm));
|
||||
(supplier && supplier.name.toLowerCase().includes(searchTerm));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4299,17 +4302,18 @@ export class MemStorage implements IStorage {
|
||||
active: products.filter(p =>
|
||||
new Date(p.expiryDate) > today &&
|
||||
p.status !== 'valides' &&
|
||||
!p.processedUntilExpiry
|
||||
!p.processedUntilExpiry &&
|
||||
!p.stockEpuise
|
||||
).length,
|
||||
expiringSoon: products.filter(p => {
|
||||
const expiry = new Date(p.expiryDate);
|
||||
const isExpiringSoon = expiry >= today && expiry <= alertDate;
|
||||
return isExpiringSoon && p.status !== 'valides' && !p.processedUntilExpiry;
|
||||
return isExpiringSoon && p.status !== 'valides' && !p.processedUntilExpiry && !p.stockEpuise;
|
||||
}).length,
|
||||
expired: products.filter(p => {
|
||||
const expiry = new Date(p.expiryDate);
|
||||
const isExpired = expiry <= today;
|
||||
return isExpired && p.status !== 'valides' && !p.processedUntilExpiry;
|
||||
return isExpired && p.status !== 'valides' && !p.processedUntilExpiry && !p.stockEpuise;
|
||||
}).length
|
||||
};
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user