From a55d399eff9bd28deae0b3ed5211f0b2980101dd Mon Sep 17 00:00:00 2001 From: numex-admin Date: Thu, 10 Sep 2026 08:58:56 +0000 Subject: [PATCH] =?UTF-8?q?Numex=20yay=C4=B1n:=20routes/products.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routes/products.js | 175 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 routes/products.js diff --git a/routes/products.js b/routes/products.js new file mode 100644 index 0000000..5388262 --- /dev/null +++ b/routes/products.js @@ -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;