Files
Regisflow/server/storage.ts
T
michaelschal e91b8f1a8b Enable support for multiple products within a single sale
Implements multi-product sales functionality by adding a `sale_products` table, updating the sales form, and modifying server-side logic.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: e80fed01-7137-4997-b9af-dad57132d430
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/0715008c-7244-40f4-befc-26c014852236/e80fed01-7137-4997-b9af-dad57132d430/58t7aEc
2025-07-19 10:10:41 +00:00

274 lines
8.2 KiB
TypeScript

import {
users,
stores,
sales,
saleProducts,
type User,
type InsertUser,
type Store,
type InsertStore,
type Sale,
type InsertSale,
type SaleProduct,
type InsertSaleProduct,
} from "@shared/schema";
import { db } from "./db";
import { eq, and, gte, lte, desc } from "drizzle-orm";
import bcrypt from "bcryptjs";
export interface IStorage {
// User operations
getUser(id: number): Promise<User | undefined>;
getUserByUsername(username: string): Promise<User | undefined>;
createUser(user: InsertUser): Promise<User>;
updateUser(id: number, user: Partial<InsertUser>): Promise<User | undefined>;
deleteUser(id: number): Promise<void>;
getAllUsers(): Promise<User[]>;
// Store operations
getStore(id: number): Promise<Store | undefined>;
createStore(store: InsertStore): Promise<Store>;
getAllStores(): Promise<Store[]>;
updateStore(id: number, store: Partial<InsertStore>): Promise<Store | undefined>;
deleteStore(id: number): Promise<void>;
// Sale operations
getSale(id: number): Promise<Sale | undefined>;
createSaleWithProducts(sale: InsertSale, products: InsertSaleProduct[]): Promise<Sale>;
getSalesByStore(storeId: number, startDate?: string, endDate?: string): Promise<Sale[]>;
getSaleWithProducts(id: number): Promise<(Sale & { products: SaleProduct[] }) | undefined>;
deleteSale(id: number): Promise<void>;
// Auth operations
authenticateUser(username: string, password: string): Promise<User | null>;
hashPassword(password: string): Promise<string>;
initializeDefaults(): Promise<{ defaultAdminCredentials?: { username: string; password: string } }>;
isUsingDefaultPassword(): Promise<boolean>;
}
export class DatabaseStorage implements IStorage {
// User operations
async getUser(id: number): Promise<User | undefined> {
const [user] = await db.select().from(users).where(eq(users.id, id));
return user;
}
async getUserByUsername(username: string): Promise<User | undefined> {
const [user] = await db.select().from(users).where(eq(users.username, username));
return user;
}
async createUser(userData: InsertUser): Promise<User> {
// Hash password if provided
if (userData.password) {
userData.password = await this.hashPassword(userData.password);
}
// Convert empty email to null to avoid unique constraint issues
if (userData.email === "") {
userData.email = null;
}
const [user] = await db
.insert(users)
.values(userData)
.returning();
return user;
}
async updateUser(id: number, userData: Partial<InsertUser>): Promise<User | undefined> {
// Hash password if provided
if (userData.password) {
userData.password = await this.hashPassword(userData.password);
}
// Convert empty email to null to avoid unique constraint issues
if (userData.email === "") {
userData.email = null;
}
const [user] = await db
.update(users)
.set(userData)
.where(eq(users.id, id))
.returning();
return user;
}
async getAllUsers(): Promise<User[]> {
return await db.select().from(users).orderBy(desc(users.createdAt));
}
async deleteUser(id: number): Promise<void> {
await db.delete(users).where(eq(users.id, id));
}
// Store operations
async getStore(id: number): Promise<Store | undefined> {
const [store] = await db.select().from(stores).where(eq(stores.id, id));
return store;
}
async createStore(storeData: InsertStore): Promise<Store> {
const [store] = await db
.insert(stores)
.values(storeData)
.returning();
return store;
}
async getAllStores(): Promise<Store[]> {
return await db.select().from(stores).orderBy(desc(stores.createdAt));
}
async updateStore(id: number, storeData: Partial<InsertStore>): Promise<Store | undefined> {
const [store] = await db
.update(stores)
.set(storeData)
.where(eq(stores.id, id))
.returning();
return store;
}
async deleteStore(id: number): Promise<void> {
await db.delete(stores).where(eq(stores.id, id));
}
// Sale operations
async getSale(id: number): Promise<Sale | undefined> {
const [sale] = await db.select().from(sales).where(eq(sales.id, id));
return sale;
}
async createSaleWithProducts(saleData: InsertSale, products: InsertSaleProduct[]): Promise<Sale> {
// Use transaction to ensure data consistency
return await db.transaction(async (tx) => {
// Create the sale first
const [sale] = await tx
.insert(sales)
.values(saleData)
.returning();
// Create all products for this sale
if (products.length > 0) {
await tx
.insert(saleProducts)
.values(products.map(product => ({
...product,
saleId: sale.id
})));
}
return sale;
});
}
async getSaleWithProducts(id: number): Promise<(Sale & { products: SaleProduct[] }) | undefined> {
const [sale] = await db.select().from(sales).where(eq(sales.id, id));
if (!sale) return undefined;
const products = await db.select().from(saleProducts).where(eq(saleProducts.saleId, id));
return { ...sale, products };
}
async getSalesByStore(storeId: number, startDate?: string, endDate?: string): Promise<Sale[]> {
let query = db.select().from(sales).where(eq(sales.storeId, storeId));
if (startDate && endDate) {
// Convert dates to timestamp format for comparison
const startTimestamp = new Date(startDate + 'T00:00:00').toISOString();
const endTimestamp = new Date(endDate + 'T23:59:59').toISOString();
query = db.select().from(sales).where(
and(
eq(sales.storeId, storeId),
gte(sales.timestamp, startTimestamp),
lte(sales.timestamp, endTimestamp)
)
);
}
const salesData = await query.orderBy(desc(sales.timestamp));
// Get products for each sale
const salesWithProducts = await Promise.all(
salesData.map(async (sale) => {
const products = await db.select().from(saleProducts).where(eq(saleProducts.saleId, sale.id));
return { ...sale, products };
})
);
return salesWithProducts;
}
async deleteSale(id: number): Promise<void> {
// Delete sale products first (cascade should handle this, but being explicit)
await db.delete(saleProducts).where(eq(saleProducts.saleId, id));
await db.delete(sales).where(eq(sales.id, id));
}
// Auth operations
async authenticateUser(username: string, password: string): Promise<User | null> {
const user = await this.getUserByUsername(username);
if (!user || !user.isActive) {
return null;
}
const isValid = await bcrypt.compare(password, user.password);
return isValid ? user : null;
}
async hashPassword(password: string): Promise<string> {
return await bcrypt.hash(password, 12);
}
// Check if admin still uses default password
async isUsingDefaultPassword(): Promise<boolean> {
const adminUser = await this.getUserByUsername('admin');
if (!adminUser) {
return false;
}
// Check if the stored password hash matches the default password "admin123"
return await bcrypt.compare('admin123', adminUser.password);
}
// Initialize default admin user and store if none exist
async initializeDefaults(): Promise<{ defaultAdminCredentials?: { username: string; password: string } }> {
const existingUsers = await this.getAllUsers();
if (existingUsers.length === 0) {
// Create default store
const defaultStore = await this.createStore({
name: "Magasin Principal",
address: "À définir",
isActive: true,
});
// Create default admin user
const defaultPassword = "admin123";
const adminUser = await this.createUser({
username: "admin",
password: defaultPassword,
email: "admin@example.com",
firstName: "Administrateur",
lastName: "Principal",
role: "admin",
storeId: defaultStore.id,
isActive: true,
});
return {
defaultAdminCredentials: {
username: "admin",
password: defaultPassword
}
};
}
return {};
}
}
export const storage = new DatabaseStorage();