mirror of
https://github.com/R0m1k3/Regisflow.git
synced 2026-10-22 22:58:16 +02:00
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
This commit is contained in:
1 parent
54595e75df
commit
e91b8f1a8b
10 files changed
+1303
-50
No files matched your search
+18
-2
@@ -243,9 +243,17 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
targetStoreId = user.storeId;
|
||||
}
|
||||
|
||||
// Extract products from request body
|
||||
const { products = [], ...saleFields } = req.body;
|
||||
|
||||
// Validate products
|
||||
if (!Array.isArray(products) || products.length === 0) {
|
||||
return res.status(400).json({ error: "Au moins un produit est requis" });
|
||||
}
|
||||
|
||||
// Map frontend camelCase to database snake_case for photos
|
||||
const mappedBody = {
|
||||
...req.body,
|
||||
...saleFields,
|
||||
};
|
||||
|
||||
// Add photo fields with proper mapping, only if they exist and are not empty
|
||||
@@ -270,7 +278,15 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
userId: user.id
|
||||
});
|
||||
|
||||
const sale = await storage.createSale(saleData);
|
||||
// Parse and validate products
|
||||
const validatedProducts = products.map((product: any) => ({
|
||||
typeArticle: product.typeArticle,
|
||||
categorie: product.categorie,
|
||||
quantite: parseInt(product.quantite),
|
||||
gencode: product.gencode
|
||||
}));
|
||||
|
||||
const sale = await storage.createSaleWithProducts(saleData, validatedProducts);
|
||||
res.status(201).json(sale);
|
||||
} catch (error) {
|
||||
console.error('Create sale error:', error);
|
||||
|
||||
+47
-8
@@ -2,12 +2,15 @@ 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";
|
||||
@@ -31,8 +34,9 @@ export interface IStorage {
|
||||
|
||||
// Sale operations
|
||||
getSale(id: number): Promise<Sale | undefined>;
|
||||
createSale(sale: InsertSale): Promise<Sale>;
|
||||
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
|
||||
@@ -136,12 +140,35 @@ export class DatabaseStorage implements IStorage {
|
||||
return sale;
|
||||
}
|
||||
|
||||
async createSale(saleData: InsertSale): Promise<Sale> {
|
||||
const [sale] = await db
|
||||
.insert(sales)
|
||||
.values(saleData)
|
||||
.returning();
|
||||
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[]> {
|
||||
@@ -161,10 +188,22 @@ export class DatabaseStorage implements IStorage {
|
||||
);
|
||||
}
|
||||
|
||||
return await query.orderBy(desc(sales.timestamp));
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user