envanter-dokuman/routes/products.js

176 lines
5.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
/**
* routes/products.js
* Ürün CRUD rotaları — hem HTML sayfa görünümleri hem REST API.
*/
const express = require('express');
const router = express.Router();
const productModel = require('../models/productModel');
const categoryModel = require('../models/categoryModel');
const stockModel = require('../models/stockModel');
const lowStockService = require('../services/lowStockService');
/** Ürün listesini kategori adıyla zenginleştirir. */
function enrich(products) {
const categories = categoryModel.readAll();
return products.map((p) => {
const cat = categories.find((c) => c.id === Number(p.categoryId));
return {
...p,
categoryName: cat ? cat.name : 'Kategori Yok',
lowStock: Number(p.stock) <= (Number(p.minimumStock) || 10)
};
});
}
// --- Sayfa görünümleri ---
/** GET /new — yeni ürün formu sayfası */
router.get('/new', (req, res) => {
const categories = categoryModel.readAll();
res.render('products/form', {
title: 'Yeni Ürün',
product: null,
categories,
action: '/api/products',
bodyClass: 'form-page'
});
});
/** GET /:id/edit — ürün düzenleme formu */
router.get('/:id/edit', (req, res) => {
const product = productModel.findById(req.params.id);
if (!product) return res.status(404).send('Ürün bulunamadı');
const categories = categoryModel.readAll();
res.render('products/form', {
title: 'Ürünü Düzenle',
product,
categories,
action: `/api/products/${product.id}`,
bodyClass: 'form-page'
});
});
/** GET /:id — ürün detay sayfası */
router.get('/:id', (req, res) => {
const product = productModel.findById(req.params.id);
if (!product) return res.status(404).send('Ürün bulunamadı');
const categories = categoryModel.readAll();
const cat = categories.find((c) => c.id === Number(product.categoryId));
const movements = stockModel.getByProduct(product.id).slice().reverse();
const lowStock = Number(product.stock) <= (Number(product.minimumStock) || 10);
res.render('products/detail', {
title: product.name,
product: { ...product, categoryName: cat ? cat.name : '—' },
movements,
lowStock,
bodyClass: 'detail-page'
});
});
/** GET / — ürün listesi (filtre destekli) */
router.get('/', (req, res) => {
const search = (req.query.search || '').toString().trim().toLowerCase();
const category = req.query.category;
let products = productModel.readAll();
if (search) {
products = products.filter(
(p) =>
p.name.toLowerCase().includes(search) ||
String(p.id).includes(search)
);
}
if (category) {
products = products.filter((p) => String(p.categoryId) === String(category));
}
const categories = categoryModel.readAll();
const enriched = enrich(products);
const lowStockCount = lowStockService.countLowStock();
res.render('products/list', {
title: 'Ürünler',
products: enriched,
categories,
search,
selectedCategory: category || '',
lowStockCount,
bodyClass: 'list-page'
});
});
// --- REST API ---
/** POST / — yeni ürün oluşturur */
router.post('/', (req, res) => {
const { name, categoryId, unitPrice, stock, minimumStock } = req.body || {};
if (!name || !String(name).trim()) {
return res.status(400).json({ error: 'Ürün adı zorunludur.' });
}
if (Number(unitPrice) < 0 || (Number(unitPrice) !== 0 && isNaN(Number(unitPrice)))) {
return res.status(400).json({ error: 'Birim fiyat geçerli bir sayı olmalıdır.' });
}
if (Number(stock) < 0) {
return res.status(400).json({ error: 'Stok miktarı negatif olamaz.' });
}
// Kategori varlığını kontrol et (0 değilse)
if (categoryId && Number(categoryId) !== 0 && !categoryModel.findById(categoryId)) {
return res.status(400).json({ error: 'Geçersiz kategori.' });
}
const product = productModel.create({
name,
categoryId,
unitPrice,
stock,
minimumStock
});
if (req.get('Accept') === 'application/json' || req.xhr || req.body._api) {
return res.status(201).json(product);
}
res.redirect('/api/products');
});
/** PUT /:id — ürün günceller */
router.put('/:id', (req, res) => {
const product = productModel.findById(req.params.id);
if (!product) return res.status(404).json({ error: 'Ürün bulunamadı' });
const { name, categoryId, unitPrice, stock, minimumStock } = req.body || {};
const updates = {};
if (name !== undefined && !String(name).trim()) {
return res.status(400).json({ error: 'Ürün adı boş olamaz.' });
}
if (name !== undefined) updates.name = name;
if (categoryId !== undefined) updates.categoryId = categoryId;
if (unitPrice !== undefined && Number(unitPrice) < 0) {
return res.status(400).json({ error: 'Birim fiyat negatif olamaz.' });
}
if (unitPrice !== undefined) updates.unitPrice = unitPrice;
if (stock !== undefined && Number(stock) < 0) {
return res.status(400).json({ error: 'Stok miktarı negatif olamaz.' });
}
if (stock !== undefined) updates.stock = stock;
if (minimumStock !== undefined) updates.minimumStock = minimumStock;
const updated = productModel.update(product.id, updates);
return res.json(updated);
});
/** DELETE /:id — ürün siler */
router.delete('/:id', (req, res) => {
const ok = productModel.remove(req.params.id);
if (!ok) return res.status(404).json({ error: 'Ürün bulunamadı' });
return res.status(204).end();
});
// API varyantları (HTML yerine JSON döndürme için bazı ekstra uçlar)
router.get('/api/all', (req, res) => {
res.json(enrich(productModel.readAll()));
});
module.exports = router;