350 lines
10 KiB
TypeScript
350 lines
10 KiB
TypeScript
import {
|
|
type Product,
|
|
type InsertProduct,
|
|
type StockMovement,
|
|
type InsertStockMovement,
|
|
type Document,
|
|
type InsertDocument,
|
|
type Order,
|
|
type InsertOrder,
|
|
type Notification,
|
|
type InsertNotification
|
|
} from "@shared/schema";
|
|
import { randomUUID } from "crypto";
|
|
|
|
export interface IStorage {
|
|
// Products
|
|
getProducts(): Promise<Product[]>;
|
|
getProduct(id: string): Promise<Product | undefined>;
|
|
getProductBySku(sku: string): Promise<Product | undefined>;
|
|
createProduct(product: InsertProduct): Promise<Product>;
|
|
updateProduct(id: string, product: Partial<InsertProduct>): Promise<Product | undefined>;
|
|
deleteProduct(id: string): Promise<boolean>;
|
|
searchProducts(query: string): Promise<Product[]>;
|
|
getLowStockProducts(): Promise<Product[]>;
|
|
getOutOfStockProducts(): Promise<Product[]>;
|
|
|
|
// Stock movements
|
|
getStockMovements(productId?: string): Promise<StockMovement[]>;
|
|
createStockMovement(movement: InsertStockMovement): Promise<StockMovement>;
|
|
|
|
// Documents
|
|
getDocuments(): Promise<Document[]>;
|
|
getDocument(id: string): Promise<Document | undefined>;
|
|
createDocument(document: InsertDocument): Promise<Document>;
|
|
updateDocument(id: string, document: Partial<InsertDocument>): Promise<Document | undefined>;
|
|
deleteDocument(id: string): Promise<boolean>;
|
|
|
|
// Orders
|
|
getOrders(): Promise<Order[]>;
|
|
getOrder(id: string): Promise<Order | undefined>;
|
|
createOrder(order: InsertOrder): Promise<Order>;
|
|
updateOrder(id: string, order: Partial<InsertOrder>): Promise<Order | undefined>;
|
|
|
|
// Notifications
|
|
getNotifications(): Promise<Notification[]>;
|
|
createNotification(notification: InsertNotification): Promise<Notification>;
|
|
markNotificationAsRead(id: string): Promise<boolean>;
|
|
getUnreadNotificationCount(): Promise<number>;
|
|
}
|
|
|
|
export class MemStorage implements IStorage {
|
|
private products: Map<string, Product>;
|
|
private stockMovements: Map<string, StockMovement>;
|
|
private documents: Map<string, Document>;
|
|
private orders: Map<string, Order>;
|
|
private notifications: Map<string, Notification>;
|
|
|
|
constructor() {
|
|
this.products = new Map();
|
|
this.stockMovements = new Map();
|
|
this.documents = new Map();
|
|
this.orders = new Map();
|
|
this.notifications = new Map();
|
|
|
|
// Initialize with sample data
|
|
this.initializeSampleData();
|
|
}
|
|
|
|
private initializeSampleData() {
|
|
const sampleProducts: Product[] = [
|
|
{
|
|
id: "1",
|
|
sku: "SH-001",
|
|
name: "Safety Helmets - White",
|
|
description: "High-quality safety helmets for construction work",
|
|
currentStock: 125,
|
|
minThreshold: 20,
|
|
unit: "units",
|
|
price: "29.99",
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
},
|
|
{
|
|
id: "2",
|
|
sku: "WG-003",
|
|
name: "Work Gloves - Medium",
|
|
description: "Durable work gloves for general construction",
|
|
currentStock: 8,
|
|
minThreshold: 15,
|
|
unit: "pairs",
|
|
price: "12.50",
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
},
|
|
{
|
|
id: "3",
|
|
sku: "HV-005",
|
|
name: "Hi-Vis Vests - Large",
|
|
description: "High-visibility safety vests",
|
|
currentStock: 0,
|
|
minThreshold: 10,
|
|
unit: "units",
|
|
price: "15.75",
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
},
|
|
];
|
|
|
|
sampleProducts.forEach(product => {
|
|
this.products.set(product.id, product);
|
|
});
|
|
|
|
// Create notifications for low/out of stock items
|
|
const lowStockNotification: Notification = {
|
|
id: "notif-1",
|
|
type: "low_stock",
|
|
title: "Low Stock Alert",
|
|
message: "Work Gloves - Medium is below minimum threshold",
|
|
isRead: false,
|
|
relatedId: "2",
|
|
createdAt: new Date(),
|
|
};
|
|
|
|
const outOfStockNotification: Notification = {
|
|
id: "notif-2",
|
|
type: "out_of_stock",
|
|
title: "Out of Stock Alert",
|
|
message: "Hi-Vis Vests - Large is out of stock",
|
|
isRead: false,
|
|
relatedId: "3",
|
|
createdAt: new Date(),
|
|
};
|
|
|
|
this.notifications.set("notif-1", lowStockNotification);
|
|
this.notifications.set("notif-2", outOfStockNotification);
|
|
}
|
|
|
|
// Products
|
|
async getProducts(): Promise<Product[]> {
|
|
return Array.from(this.products.values());
|
|
}
|
|
|
|
async getProduct(id: string): Promise<Product | undefined> {
|
|
return this.products.get(id);
|
|
}
|
|
|
|
async getProductBySku(sku: string): Promise<Product | undefined> {
|
|
return Array.from(this.products.values()).find(product => product.sku === sku);
|
|
}
|
|
|
|
async createProduct(insertProduct: InsertProduct): Promise<Product> {
|
|
const id = randomUUID();
|
|
const product: Product = {
|
|
...insertProduct,
|
|
id,
|
|
description: insertProduct.description ?? null,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
};
|
|
this.products.set(id, product);
|
|
return product;
|
|
}
|
|
|
|
async updateProduct(id: string, updates: Partial<InsertProduct>): Promise<Product | undefined> {
|
|
const product = this.products.get(id);
|
|
if (!product) return undefined;
|
|
|
|
const updatedProduct: Product = {
|
|
...product,
|
|
...updates,
|
|
updatedAt: new Date(),
|
|
};
|
|
this.products.set(id, updatedProduct);
|
|
return updatedProduct;
|
|
}
|
|
|
|
async deleteProduct(id: string): Promise<boolean> {
|
|
return this.products.delete(id);
|
|
}
|
|
|
|
async searchProducts(query: string): Promise<Product[]> {
|
|
const products = Array.from(this.products.values());
|
|
const lowercaseQuery = query.toLowerCase();
|
|
return products.filter(product =>
|
|
product.name.toLowerCase().includes(lowercaseQuery) ||
|
|
product.sku.toLowerCase().includes(lowercaseQuery) ||
|
|
product.description?.toLowerCase().includes(lowercaseQuery)
|
|
);
|
|
}
|
|
|
|
async getLowStockProducts(): Promise<Product[]> {
|
|
return Array.from(this.products.values()).filter(
|
|
product => product.currentStock > 0 && product.currentStock <= product.minThreshold
|
|
);
|
|
}
|
|
|
|
async getOutOfStockProducts(): Promise<Product[]> {
|
|
return Array.from(this.products.values()).filter(
|
|
product => product.currentStock === 0
|
|
);
|
|
}
|
|
|
|
// Stock movements
|
|
async getStockMovements(productId?: string): Promise<StockMovement[]> {
|
|
const movements = Array.from(this.stockMovements.values());
|
|
if (productId) {
|
|
return movements.filter(movement => movement.productId === productId);
|
|
}
|
|
return movements;
|
|
}
|
|
|
|
async createStockMovement(insertMovement: InsertStockMovement): Promise<StockMovement> {
|
|
const id = randomUUID();
|
|
const movement: StockMovement = {
|
|
...insertMovement,
|
|
id,
|
|
reason: insertMovement.reason ?? null,
|
|
createdAt: new Date(),
|
|
};
|
|
this.stockMovements.set(id, movement);
|
|
|
|
// Update product stock
|
|
const product = this.products.get(insertMovement.productId);
|
|
if (product) {
|
|
let newStock = product.currentStock;
|
|
if (insertMovement.type === 'in') {
|
|
newStock += insertMovement.quantity;
|
|
} else if (insertMovement.type === 'out') {
|
|
newStock -= insertMovement.quantity;
|
|
} else if (insertMovement.type === 'adjustment') {
|
|
newStock = insertMovement.quantity;
|
|
}
|
|
|
|
await this.updateProduct(insertMovement.productId, { currentStock: Math.max(0, newStock) });
|
|
}
|
|
|
|
return movement;
|
|
}
|
|
|
|
// Documents
|
|
async getDocuments(): Promise<Document[]> {
|
|
return Array.from(this.documents.values());
|
|
}
|
|
|
|
async getDocument(id: string): Promise<Document | undefined> {
|
|
return this.documents.get(id);
|
|
}
|
|
|
|
async createDocument(insertDocument: InsertDocument): Promise<Document> {
|
|
const id = randomUUID();
|
|
const document: Document = {
|
|
...insertDocument,
|
|
id,
|
|
content: insertDocument.content ?? {},
|
|
status: insertDocument.status ?? "draft",
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
};
|
|
this.documents.set(id, document);
|
|
return document;
|
|
}
|
|
|
|
async updateDocument(id: string, updates: Partial<InsertDocument>): Promise<Document | undefined> {
|
|
const document = this.documents.get(id);
|
|
if (!document) return undefined;
|
|
|
|
const updatedDocument: Document = {
|
|
...document,
|
|
...updates,
|
|
updatedAt: new Date(),
|
|
};
|
|
this.documents.set(id, updatedDocument);
|
|
return updatedDocument;
|
|
}
|
|
|
|
async deleteDocument(id: string): Promise<boolean> {
|
|
return this.documents.delete(id);
|
|
}
|
|
|
|
// Orders
|
|
async getOrders(): Promise<Order[]> {
|
|
return Array.from(this.orders.values());
|
|
}
|
|
|
|
async getOrder(id: string): Promise<Order | undefined> {
|
|
return this.orders.get(id);
|
|
}
|
|
|
|
async createOrder(insertOrder: InsertOrder): Promise<Order> {
|
|
const id = randomUUID();
|
|
const order: Order = {
|
|
...insertOrder,
|
|
id,
|
|
status: insertOrder.status ?? "pending",
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
};
|
|
this.orders.set(id, order);
|
|
return order;
|
|
}
|
|
|
|
async updateOrder(id: string, updates: Partial<InsertOrder>): Promise<Order | undefined> {
|
|
const order = this.orders.get(id);
|
|
if (!order) return undefined;
|
|
|
|
const updatedOrder: Order = {
|
|
...order,
|
|
...updates,
|
|
updatedAt: new Date(),
|
|
};
|
|
this.orders.set(id, updatedOrder);
|
|
return updatedOrder;
|
|
}
|
|
|
|
// Notifications
|
|
async getNotifications(): Promise<Notification[]> {
|
|
return Array.from(this.notifications.values()).sort(
|
|
(a, b) => (b.createdAt?.getTime() || 0) - (a.createdAt?.getTime() || 0)
|
|
);
|
|
}
|
|
|
|
async createNotification(insertNotification: InsertNotification): Promise<Notification> {
|
|
const id = randomUUID();
|
|
const notification: Notification = {
|
|
...insertNotification,
|
|
id,
|
|
isRead: insertNotification.isRead ?? false,
|
|
relatedId: insertNotification.relatedId ?? null,
|
|
createdAt: new Date(),
|
|
};
|
|
this.notifications.set(id, notification);
|
|
return notification;
|
|
}
|
|
|
|
async markNotificationAsRead(id: string): Promise<boolean> {
|
|
const notification = this.notifications.get(id);
|
|
if (!notification) return false;
|
|
|
|
notification.isRead = true;
|
|
this.notifications.set(id, notification);
|
|
return true;
|
|
}
|
|
|
|
async getUnreadNotificationCount(): Promise<number> {
|
|
return Array.from(this.notifications.values()).filter(n => !n.isRead).length;
|
|
}
|
|
}
|
|
|
|
export const storage = new MemStorage();
|