Numex yayın: services/reportService.js
This commit is contained in:
parent
c03b1a771d
commit
546bfdb792
146
services/reportService.js
Normal file
146
services/reportService.js
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* reportService.js
|
||||
* Rapor hesaplama motoru — kategori bazlı raporlar, stok değerleri ve hareket özetleri.
|
||||
* Saf fonksiyonlar; test edilebilir, route'tan bağımsız.
|
||||
*/
|
||||
|
||||
const productModel = require('../models/productModel');
|
||||
const stockModel = require('../models/stockModel');
|
||||
const categoryModel = require('../models/categoryModel');
|
||||
|
||||
/** ISO tarihini Date nesnesine güvenle çevirir (geçersizse boş aralık dönmez). */
|
||||
function toDate(value) {
|
||||
const d = value ? new Date(value) : null;
|
||||
return d && !isNaN(d.getTime()) ? d : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Belirli bir kategoriye ait ürünlerin toplam stok değerini hesaplar.
|
||||
* @param {number} categoryId kategori ID
|
||||
* @returns {number} toplam değer (stock * unitPrice)
|
||||
*/
|
||||
function valueByCategory(categoryId) {
|
||||
const categories = categoryModel.readAll();
|
||||
const catId = Number(categoryId);
|
||||
|
||||
// id:0 / 'all' => tüm kategoriler
|
||||
if (catId === 0 || String(categoryId).toLowerCase() === 'all') {
|
||||
const products = productModel.readAll();
|
||||
return products.reduce((sum, p) => sum + Number(p.stock) * Number(p.unitPrice), 0);
|
||||
}
|
||||
|
||||
const cat = categories.find((c) => c.id === catId);
|
||||
if (!cat) return 0;
|
||||
|
||||
const products = productModel.findByCategory(catId);
|
||||
return products.reduce((sum, p) => sum + Number(p.stock) * Number(p.unitPrice), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tarih aralığında kategori ürünlerine ait giriş/çıkış özeti.
|
||||
* @param {number|null} categoryId kategori ID (0/null = hepsi)
|
||||
* @param {string} fromDate başlangıç tarihi (ISO)
|
||||
* @param {string} toDate bitiş tarihi (ISO)
|
||||
* @returns {Object} {in, out, net}
|
||||
*/
|
||||
function movementSummary(categoryId = null, fromDate = '', toDate = '') {
|
||||
const from = toDate(fromDate);
|
||||
const to = toDate(toDate);
|
||||
|
||||
// Kategoriye ait ürün ID'lerini bul
|
||||
let productIds = new Set();
|
||||
const catId = Number(categoryId);
|
||||
if (catId && !isNaN(catId) && catId !== 0) {
|
||||
productModel.findByCategory(catId).forEach((p) => productIds.add(Number(p.id)));
|
||||
} else {
|
||||
productModel.readAll().forEach((p) => productIds.add(Number(p.id)));
|
||||
}
|
||||
|
||||
const movements = stockModel.readAll().filter((m) => {
|
||||
if (!productIds.has(Number(m.productId))) return false;
|
||||
const d = new Date(m.date);
|
||||
if (from && d < from) return false;
|
||||
if (to) {
|
||||
// to bitiş gününü kapsar
|
||||
const toEnd = new Date(to);
|
||||
toEnd.setHours(23, 59, 59, 999);
|
||||
if (d > toEnd) return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
let inQty = 0;
|
||||
let outQty = 0;
|
||||
for (const m of movements) {
|
||||
if (m.type === 'in') inQty += Number(m.quantity) || 0;
|
||||
else if (m.type === 'out') outQty += Number(m.quantity) || 0;
|
||||
}
|
||||
|
||||
return { in: inQty, out: outQty, net: inQty - outQty, movements };
|
||||
}
|
||||
|
||||
/**
|
||||
* Kategori bazlı tam rapor.
|
||||
* @param {number|null} categoryId kategori ID
|
||||
* @param {Object} opts {fromDate, toDate}
|
||||
* @returns {Object} {category, totalProducts, totalStockValue, incomingQty, outgoingQty, movements, lowStockCount}
|
||||
*/
|
||||
function categoryReport(categoryId = null, opts = {}) {
|
||||
const { fromDate = '', toDate = '' } = opts || {};
|
||||
const catId = Number(categoryId);
|
||||
const categories = categoryModel.readAll();
|
||||
const category =
|
||||
(catId && categories.find((c) => c.id === catId)) ||
|
||||
(String(categoryId).toLowerCase() === 'all' ? { id: 0, name: 'Tümü' } : null);
|
||||
|
||||
const products = category && category.id !== 0
|
||||
? productModel.findByCategory(catId)
|
||||
: productModel.readAll();
|
||||
|
||||
const totalStockValue = products.reduce((s, p) => s + Number(p.stock) * Number(p.unitPrice), 0);
|
||||
const summary = movementSummary(catId || 0, fromDate, toDate);
|
||||
|
||||
return {
|
||||
category,
|
||||
totalProducts: products.length,
|
||||
totalStockValue,
|
||||
incomingQty: summary.in,
|
||||
outgoingQty: summary.out,
|
||||
netQty: summary.net,
|
||||
movements: summary.movements,
|
||||
lowStockCount: products.filter(
|
||||
(p) => Number(p.stock) <= (Number(p.minimumStock) || 10)
|
||||
).length
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Tüm kategorilerin özet raporları (dashboard / rapor sayfası tablosu).
|
||||
* @returns {Array} [{id, name, totalProducts, totalStockValue, lowStockCount}]
|
||||
*/
|
||||
function allCategorySummary() {
|
||||
const categories = categoryModel.readAll();
|
||||
return categories.map((cat) => {
|
||||
const products = productModel.findByCategory(cat.id);
|
||||
const totalStockValue = products.reduce((s, p) => s + Number(p.stock) * Number(p.unitPrice), 0);
|
||||
return {
|
||||
id: cat.id,
|
||||
name: cat.name,
|
||||
slug: cat.slug,
|
||||
totalProducts: products.length,
|
||||
totalStockValue,
|
||||
lowStockCount: products.filter(
|
||||
(p) => Number(p.stock) <= (Number(p.minimumStock) || 10)
|
||||
).length
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
categoryReport,
|
||||
valueByCategory,
|
||||
movementSummary,
|
||||
allCategorySummary
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user