197 lines
7.2 KiB
PHP
197 lines
7.2 KiB
PHP
<?php
|
|
/**
|
|
* TOPSIS method for product restocking prioritization
|
|
*
|
|
* This function implements the Technique for Order Preference by Similarity to Ideal Solution
|
|
* to determine which products should be prioritized for restocking based on multiple criteria.
|
|
* Modified to match the formulas provided in the documentation.
|
|
*/
|
|
function restockWithTOPSIS($conn, $criteria_weights = null) {
|
|
// Default weights if none provided
|
|
if ($criteria_weights === null) {
|
|
$criteria_weights = [
|
|
'stock_level' => 0.4, // Lower stock is more urgent (40% importance)
|
|
'price_value' => 0.3, // Higher price might indicate higher priority (30% importance)
|
|
'turnover_rate' => 0.3 // Higher turnover rate means faster selling (30% importance)
|
|
];
|
|
}
|
|
|
|
// 1. Get product data from database
|
|
$query = "SELECT b.id_barang, b.nama_barang, b.stok, b.harga,
|
|
COALESCE(COUNT(t.id_transaksi), 0) as sales_count
|
|
FROM barang b
|
|
LEFT JOIN detail_transaksi dt ON b.id_barang = dt.id_barangK
|
|
LEFT JOIN transaksi t ON dt.id_transaksi = t.id_transaksi
|
|
AND t.tanggal BETWEEN DATE_SUB(NOW(), INTERVAL 30 DAY) AND NOW()
|
|
GROUP BY b.id_barang";
|
|
|
|
$result = mysqli_query($conn, $query);
|
|
if (!$result) {
|
|
return ["error" => "Database query failed: " . mysqli_error($conn)];
|
|
}
|
|
|
|
$products = [];
|
|
while ($row = mysqli_fetch_assoc($result)) {
|
|
// Convert price from varchar to numeric
|
|
$row['harga'] = (float)str_replace(['Rp', '.', ','], ['', '', '.'], $row['harga']);
|
|
|
|
// Calculate turnover rate (sales per stock unit)
|
|
$row['turnover_rate'] = $row['stok'] > 0 ? $row['sales_count'] / $row['stok'] : 0;
|
|
|
|
$products[] = $row;
|
|
}
|
|
|
|
if (empty($products)) {
|
|
return ["message" => "No products found"];
|
|
}
|
|
|
|
// 2. Create decision matrix (X)
|
|
$decision_matrix = [];
|
|
foreach ($products as $product) {
|
|
$decision_matrix[] = [
|
|
'id_barang' => $product['id_barang'],
|
|
'nama_barang' => $product['nama_barang'],
|
|
'stock_level' => max(1, $product['stok']), // Ensure no zero values
|
|
'price_value' => $product['harga'],
|
|
'turnover_rate' => $product['turnover_rate'],
|
|
];
|
|
}
|
|
|
|
// 3. Normalize the decision matrix (R) according to formula 3.1
|
|
$normalized_matrix = normalizeMatrixEuclidean($decision_matrix);
|
|
|
|
// 4. Apply weights to normalized matrix (Y) according to formula 3.2
|
|
$weighted_matrix = applyWeightsToMatrix($normalized_matrix, $criteria_weights);
|
|
|
|
// 5. Determine ideal and negative-ideal solutions (A+, A-) according to formula 3.3
|
|
// Define benefit criteria (higher is better) and cost criteria (lower is better)
|
|
$benefit_criteria = ['price_value', 'turnover_rate'];
|
|
$cost_criteria = ['stock_level'];
|
|
|
|
$ideal_solution = [];
|
|
$negative_ideal_solution = [];
|
|
|
|
// For benefit criteria: higher values are better
|
|
// For cost criteria: lower values are better
|
|
foreach ($benefit_criteria as $criterion) {
|
|
$ideal_solution[$criterion] = max(array_column($weighted_matrix, $criterion));
|
|
$negative_ideal_solution[$criterion] = min(array_column($weighted_matrix, $criterion));
|
|
}
|
|
|
|
foreach ($cost_criteria as $criterion) {
|
|
$ideal_solution[$criterion] = min(array_column($weighted_matrix, $criterion));
|
|
$negative_ideal_solution[$criterion] = max(array_column($weighted_matrix, $criterion));
|
|
}
|
|
|
|
// 6. Calculate separation measures (D+, D-) according to formula 3.4
|
|
$separation_positive = [];
|
|
$separation_negative = [];
|
|
|
|
foreach ($weighted_matrix as $i => $product) {
|
|
// Distance to positive ideal solution (D+)
|
|
$sum_positive = 0;
|
|
foreach (array_merge($benefit_criteria, $cost_criteria) as $criterion) {
|
|
$sum_positive += pow($product[$criterion] - $ideal_solution[$criterion], 2);
|
|
}
|
|
$separation_positive[$i] = sqrt($sum_positive);
|
|
|
|
// Distance to negative ideal solution (D-)
|
|
$sum_negative = 0;
|
|
foreach (array_merge($benefit_criteria, $cost_criteria) as $criterion) {
|
|
$sum_negative += pow($product[$criterion] - $negative_ideal_solution[$criterion], 2);
|
|
}
|
|
$separation_negative[$i] = sqrt($sum_negative);
|
|
}
|
|
|
|
// 7. Calculate relative closeness to ideal solution (V) according to formula 3.5
|
|
$preference_scores = [];
|
|
foreach ($weighted_matrix as $i => $product) {
|
|
// Avoid division by zero
|
|
$denominator = $separation_negative[$i] + $separation_positive[$i];
|
|
$preference_scores[$i] = ($denominator > 0) ?
|
|
($separation_negative[$i] / $denominator) : 0;
|
|
}
|
|
|
|
// 8. Rank products based on preference scores
|
|
$topsis_results = [];
|
|
foreach ($preference_scores as $i => $score) {
|
|
$topsis_results[] = [
|
|
'id_barang' => $decision_matrix[$i]['id_barang'],
|
|
'nama_barang' => $decision_matrix[$i]['nama_barang'],
|
|
'stok' => $products[$i]['stok'],
|
|
'harga' => $products[$i]['harga'],
|
|
'topsis_score' => $score,
|
|
'rank' => 0 // Will be filled later
|
|
];
|
|
}
|
|
|
|
// Sort by preference score (descending)
|
|
usort($topsis_results, function($a, $b) {
|
|
return $b['topsis_score'] <=> $a['topsis_score'];
|
|
});
|
|
|
|
// Assign ranks
|
|
foreach ($topsis_results as $i => $product) {
|
|
$topsis_results[$i]['rank'] = $i + 1;
|
|
}
|
|
|
|
return $topsis_results;
|
|
}
|
|
|
|
/**
|
|
* Normalize the decision matrix using Euclidean length of vector (Formula 3.1)
|
|
* Rij = xij / sqrt(sum(xij^2))
|
|
*/
|
|
function normalizeMatrixEuclidean($matrix) {
|
|
$normalized = [];
|
|
$criteria = ['stock_level', 'price_value', 'turnover_rate'];
|
|
|
|
// Calculate the square root of the sum of squares for each criterion
|
|
$denominators = [];
|
|
foreach ($criteria as $criterion) {
|
|
$sum_squares = 0;
|
|
foreach ($matrix as $product) {
|
|
$sum_squares += pow($product[$criterion], 2);
|
|
}
|
|
$denominators[$criterion] = sqrt($sum_squares);
|
|
}
|
|
|
|
// Normalize each value according to formula 3.1
|
|
foreach ($matrix as $i => $product) {
|
|
$normalized[$i] = [
|
|
'id_barang' => $product['id_barang'],
|
|
'nama_barang' => $product['nama_barang']
|
|
];
|
|
|
|
foreach ($criteria as $criterion) {
|
|
// Avoid division by zero
|
|
$normalized[$i][$criterion] = ($denominators[$criterion] > 0) ?
|
|
($product[$criterion] / $denominators[$criterion]) : 0;
|
|
}
|
|
}
|
|
|
|
return $normalized;
|
|
}
|
|
|
|
/**
|
|
* Apply weights to the normalized matrix (Formula 3.2)
|
|
* yij = wi * rij
|
|
*/
|
|
function applyWeightsToMatrix($matrix, $weights) {
|
|
$weighted = [];
|
|
$criteria = ['stock_level', 'price_value', 'turnover_rate'];
|
|
|
|
foreach ($matrix as $i => $product) {
|
|
$weighted[$i] = [
|
|
'id_barang' => $product['id_barang'],
|
|
'nama_barang' => $product['nama_barang']
|
|
];
|
|
|
|
foreach ($criteria as $criterion) {
|
|
$weighted[$i][$criterion] = $product[$criterion] * $weights[$criterion];
|
|
}
|
|
}
|
|
|
|
return $weighted;
|
|
}
|
|
?>
|