Numex yayın: routes/products.js

This commit is contained in:
numex-admin 2026-09-10 08:58:56 +00:00
parent 6cb533c4e0
commit a55d399eff

175
routes/products.js Normal file
View File

@ -0,0 +1,175 @@
'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;