259 lines
8.3 KiB
TypeScript
259 lines
8.3 KiB
TypeScript
import type { Express } from "express";
|
|
import { createServer, type Server } from "http";
|
|
import { storage } from "./storage";
|
|
import { insertProductSchema, insertStockMovementSchema, insertDocumentSchema } from "@shared/schema";
|
|
import { z } from "zod";
|
|
|
|
export async function registerRoutes(app: Express): Promise<Server> {
|
|
// Products
|
|
app.get("/api/products", async (req, res) => {
|
|
try {
|
|
const products = await storage.getProducts();
|
|
res.json(products);
|
|
} catch (error) {
|
|
res.status(500).json({ message: "Failed to fetch products" });
|
|
}
|
|
});
|
|
|
|
app.get("/api/products/search", async (req, res) => {
|
|
try {
|
|
const query = req.query.q as string;
|
|
if (!query) {
|
|
return res.status(400).json({ message: "Search query is required" });
|
|
}
|
|
const products = await storage.searchProducts(query);
|
|
res.json(products);
|
|
} catch (error) {
|
|
res.status(500).json({ message: "Failed to search products" });
|
|
}
|
|
});
|
|
|
|
app.get("/api/products/low-stock", async (req, res) => {
|
|
try {
|
|
const products = await storage.getLowStockProducts();
|
|
res.json(products);
|
|
} catch (error) {
|
|
res.status(500).json({ message: "Failed to fetch low stock products" });
|
|
}
|
|
});
|
|
|
|
app.get("/api/products/out-of-stock", async (req, res) => {
|
|
try {
|
|
const products = await storage.getOutOfStockProducts();
|
|
res.json(products);
|
|
} catch (error) {
|
|
res.status(500).json({ message: "Failed to fetch out of stock products" });
|
|
}
|
|
});
|
|
|
|
app.get("/api/products/:id", async (req, res) => {
|
|
try {
|
|
const product = await storage.getProduct(req.params.id);
|
|
if (!product) {
|
|
return res.status(404).json({ message: "Product not found" });
|
|
}
|
|
res.json(product);
|
|
} catch (error) {
|
|
res.status(500).json({ message: "Failed to fetch product" });
|
|
}
|
|
});
|
|
|
|
app.post("/api/products", async (req, res) => {
|
|
try {
|
|
const productData = insertProductSchema.parse(req.body);
|
|
const product = await storage.createProduct(productData);
|
|
res.status(201).json(product);
|
|
} catch (error) {
|
|
if (error instanceof z.ZodError) {
|
|
return res.status(400).json({ message: "Invalid product data", errors: error.errors });
|
|
}
|
|
res.status(500).json({ message: "Failed to create product" });
|
|
}
|
|
});
|
|
|
|
app.patch("/api/products/:id", async (req, res) => {
|
|
try {
|
|
const updates = insertProductSchema.partial().parse(req.body);
|
|
const product = await storage.updateProduct(req.params.id, updates);
|
|
if (!product) {
|
|
return res.status(404).json({ message: "Product not found" });
|
|
}
|
|
res.json(product);
|
|
} catch (error) {
|
|
if (error instanceof z.ZodError) {
|
|
return res.status(400).json({ message: "Invalid product data", errors: error.errors });
|
|
}
|
|
res.status(500).json({ message: "Failed to update product" });
|
|
}
|
|
});
|
|
|
|
app.delete("/api/products/:id", async (req, res) => {
|
|
try {
|
|
const deleted = await storage.deleteProduct(req.params.id);
|
|
if (!deleted) {
|
|
return res.status(404).json({ message: "Product not found" });
|
|
}
|
|
res.status(204).send();
|
|
} catch (error) {
|
|
res.status(500).json({ message: "Failed to delete product" });
|
|
}
|
|
});
|
|
|
|
// Stock movements
|
|
app.get("/api/stock-movements", async (req, res) => {
|
|
try {
|
|
const productId = req.query.productId as string;
|
|
const movements = await storage.getStockMovements(productId);
|
|
res.json(movements);
|
|
} catch (error) {
|
|
res.status(500).json({ message: "Failed to fetch stock movements" });
|
|
}
|
|
});
|
|
|
|
app.post("/api/stock-movements", async (req, res) => {
|
|
try {
|
|
const movementData = insertStockMovementSchema.parse(req.body);
|
|
const movement = await storage.createStockMovement(movementData);
|
|
res.status(201).json(movement);
|
|
} catch (error) {
|
|
if (error instanceof z.ZodError) {
|
|
return res.status(400).json({ message: "Invalid stock movement data", errors: error.errors });
|
|
}
|
|
res.status(500).json({ message: "Failed to create stock movement" });
|
|
}
|
|
});
|
|
|
|
// Documents
|
|
app.get("/api/documents", async (req, res) => {
|
|
try {
|
|
const documents = await storage.getDocuments();
|
|
res.json(documents);
|
|
} catch (error) {
|
|
res.status(500).json({ message: "Failed to fetch documents" });
|
|
}
|
|
});
|
|
|
|
app.get("/api/documents/:id", async (req, res) => {
|
|
try {
|
|
const document = await storage.getDocument(req.params.id);
|
|
if (!document) {
|
|
return res.status(404).json({ message: "Document not found" });
|
|
}
|
|
res.json(document);
|
|
} catch (error) {
|
|
res.status(500).json({ message: "Failed to fetch document" });
|
|
}
|
|
});
|
|
|
|
app.post("/api/documents", async (req, res) => {
|
|
try {
|
|
const documentData = insertDocumentSchema.parse(req.body);
|
|
const document = await storage.createDocument(documentData);
|
|
res.status(201).json(document);
|
|
} catch (error) {
|
|
if (error instanceof z.ZodError) {
|
|
return res.status(400).json({ message: "Invalid document data", errors: error.errors });
|
|
}
|
|
res.status(500).json({ message: "Failed to create document" });
|
|
}
|
|
});
|
|
|
|
app.patch("/api/documents/:id", async (req, res) => {
|
|
try {
|
|
const updates = insertDocumentSchema.partial().parse(req.body);
|
|
const document = await storage.updateDocument(req.params.id, updates);
|
|
if (!document) {
|
|
return res.status(404).json({ message: "Document not found" });
|
|
}
|
|
res.json(document);
|
|
} catch (error) {
|
|
if (error instanceof z.ZodError) {
|
|
return res.status(400).json({ message: "Invalid document data", errors: error.errors });
|
|
}
|
|
res.status(500).json({ message: "Failed to update document" });
|
|
}
|
|
});
|
|
|
|
// Generate PDF document
|
|
app.get("/api/documents/:id/pdf", async (req, res) => {
|
|
try {
|
|
const document = await storage.getDocument(req.params.id);
|
|
if (!document) {
|
|
return res.status(404).json({ message: "Document not found" });
|
|
}
|
|
|
|
// Simple PDF generation for now - in production would use proper PDF library
|
|
const pdfContent = `
|
|
Document: ${document.title}
|
|
Type: ${document.type}
|
|
Number: ${document.documentNumber}
|
|
Status: ${document.status}
|
|
Created: ${document.createdAt}
|
|
|
|
Content:
|
|
${JSON.stringify(document.content, null, 2)}
|
|
`;
|
|
|
|
res.setHeader('Content-Type', 'application/pdf');
|
|
res.setHeader('Content-Disposition', `attachment; filename="${document.documentNumber}.pdf"`);
|
|
res.send(pdfContent);
|
|
} catch (error) {
|
|
res.status(500).json({ message: "Failed to generate PDF" });
|
|
}
|
|
});
|
|
|
|
// Notifications
|
|
app.get("/api/notifications", async (req, res) => {
|
|
try {
|
|
const notifications = await storage.getNotifications();
|
|
res.json(notifications);
|
|
} catch (error) {
|
|
res.status(500).json({ message: "Failed to fetch notifications" });
|
|
}
|
|
});
|
|
|
|
app.get("/api/notifications/unread-count", async (req, res) => {
|
|
try {
|
|
const count = await storage.getUnreadNotificationCount();
|
|
res.json({ count });
|
|
} catch (error) {
|
|
res.status(500).json({ message: "Failed to fetch notification count" });
|
|
}
|
|
});
|
|
|
|
app.patch("/api/notifications/:id/read", async (req, res) => {
|
|
try {
|
|
const success = await storage.markNotificationAsRead(req.params.id);
|
|
if (!success) {
|
|
return res.status(404).json({ message: "Notification not found" });
|
|
}
|
|
res.status(204).send();
|
|
} catch (error) {
|
|
res.status(500).json({ message: "Failed to mark notification as read" });
|
|
}
|
|
});
|
|
|
|
// Dashboard stats
|
|
app.get("/api/dashboard/stats", async (req, res) => {
|
|
try {
|
|
const products = await storage.getProducts();
|
|
const lowStockProducts = await storage.getLowStockProducts();
|
|
const outOfStockProducts = await storage.getOutOfStockProducts();
|
|
|
|
const stats = {
|
|
totalItems: products.length,
|
|
inStockItems: products.filter(p => p.currentStock > p.minThreshold).length,
|
|
lowStockItems: lowStockProducts.length,
|
|
outOfStockItems: outOfStockProducts.length,
|
|
};
|
|
|
|
res.json(stats);
|
|
} catch (error) {
|
|
res.status(500).json({ message: "Failed to fetch dashboard stats" });
|
|
}
|
|
});
|
|
|
|
const httpServer = createServer(app);
|
|
return httpServer;
|
|
}
|