176 lines
5.5 KiB
JavaScript
176 lines
5.5 KiB
JavaScript
'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;
|