diff --git a/thanasoft-back/app/Repositories/ProductRepository.php b/thanasoft-back/app/Repositories/ProductRepository.php index 8c6bd5b..4d1525a 100644 --- a/thanasoft-back/app/Repositories/ProductRepository.php +++ b/thanasoft-back/app/Repositories/ProductRepository.php @@ -6,6 +6,7 @@ namespace App\Repositories; use App\Models\Product; use Illuminate\Contracts\Pagination\LengthAwarePaginator; +use Illuminate\Support\Facades\DB; class ProductRepository extends BaseRepository implements ProductRepositoryInterface { @@ -132,15 +133,148 @@ class ProductRepository extends BaseRepository implements ProductRepositoryInter $expiringProducts = $this->model->where('date_expiration', '<=', now()->addDays(30)->toDateString()) ->where('date_expiration', '>=', now()->toDateString()) ->count(); - $totalValue = $this->model->sum(\DB::raw('stock_actuel * prix_unitaire')); + + $stockItems = DB::table('stock_items') + ->join('products', 'stock_items.product_id', '=', 'products.id'); + + $totalValue = $stockItems + ->selectRaw('COALESCE(SUM(stock_items.qty_on_hand_base * products.prix_unitaire), 0) as total_value') + ->value('total_value'); + + $stockAlertCount = $stockItems + ->whereRaw('stock_items.qty_on_hand_base <= stock_items.safety_stock_base') + ->count(); + + $stockRotation = $this->getRotationByProduct(); + $warehouseMovements = $this->getWarehouseMovements(); + $stockByWarehouse = $this->getStockByWarehouse(); return [ 'total_products' => $totalProducts, 'low_stock_products' => $lowStockProducts, 'expiring_products' => $expiringProducts, 'total_value' => $totalValue, + 'stock_alerts_count' => $stockAlertCount, + 'stock_rotation_by_product' => $stockRotation, + 'warehouse_movements' => $warehouseMovements, + 'stock_by_warehouse' => $stockByWarehouse, ]; } + + private function getRotationByProduct(): array + { + $oneYearAgo = now()->subYear(); + + $movementSubquery = DB::table('stock_moves') + ->selectRaw('product_id, SUM(qty_base) as moved_qty') + ->where('moved_at', '>=', $oneYearAgo) + ->groupBy('product_id'); + + $stockOnHandSubquery = DB::table('stock_items') + ->selectRaw('product_id, SUM(qty_on_hand_base) as qty_on_hand') + ->groupBy('product_id'); + + $rotation = DB::table('products') + ->leftJoinSub($movementSubquery, 'movement', function ($join) { + $join->on('products.id', '=', 'movement.product_id'); + }) + ->leftJoinSub($stockOnHandSubquery, 'stock', function ($join) { + $join->on('products.id', '=', 'stock.product_id'); + }) + ->whereNotNull('movement.moved_qty') + ->selectRaw( + 'products.id as product_id, products.nom as product_name, ' . + 'movement.moved_qty, COALESCE(stock.qty_on_hand, 0) as qty_on_hand, ' . + 'CASE WHEN COALESCE(stock.qty_on_hand, 0) > 0 THEN ROUND(movement.moved_qty / stock.qty_on_hand, 2) ELSE null END as rotation_rate' + ) + ->orderByDesc('rotation_rate') + ->limit(10) + ->get() + ->map(function ($row) { + return [ + 'product_id' => (int) $row->product_id, + 'product_name' => $row->product_name, + 'moved_qty_last_12_months' => (float) $row->moved_qty, + 'qty_on_hand' => (float) $row->qty_on_hand, + 'rotation_rate' => $row->rotation_rate !== null ? (float) $row->rotation_rate : null, + ]; + }) + ->toArray(); + + return $rotation; + } + + private function getWarehouseMovements(): array + { + $incoming = DB::table('stock_moves') + ->selectRaw('to_warehouse_id as warehouse_id, COUNT(*) as incoming_moves, SUM(qty_base) as incoming_qty') + ->whereNotNull('to_warehouse_id') + ->groupBy('to_warehouse_id'); + + $outgoing = DB::table('stock_moves') + ->selectRaw('from_warehouse_id as warehouse_id, COUNT(*) as outgoing_moves, SUM(qty_base) as outgoing_qty') + ->whereNotNull('from_warehouse_id') + ->groupBy('from_warehouse_id'); + + $warehouses = DB::table('warehouses') + ->leftJoinSub($incoming, 'incoming', function ($join) { + $join->on('warehouses.id', '=', 'incoming.warehouse_id'); + }) + ->leftJoinSub($outgoing, 'outgoing', function ($join) { + $join->on('warehouses.id', '=', 'outgoing.warehouse_id'); + }) + ->select([ + 'warehouses.id as warehouse_id', + 'warehouses.name as warehouse_name', + DB::raw('COALESCE(incoming.incoming_moves, 0) as incoming_moves'), + DB::raw('COALESCE(incoming.incoming_qty, 0) as incoming_qty'), + DB::raw('COALESCE(outgoing.outgoing_moves, 0) as outgoing_moves'), + DB::raw('COALESCE(outgoing.outgoing_qty, 0) as outgoing_qty'), + DB::raw('COALESCE(COALESCE(incoming.incoming_moves, 0) + COALESCE(outgoing.outgoing_moves, 0), 0) as total_moves'), + ]) + ->get() + ->map(function ($row) { + return [ + 'warehouse_id' => (int) $row->warehouse_id, + 'warehouse_name' => $row->warehouse_name, + 'incoming_moves' => (int) $row->incoming_moves, + 'incoming_qty' => (float) $row->incoming_qty, + 'outgoing_moves' => (int) $row->outgoing_moves, + 'outgoing_qty' => (float) $row->outgoing_qty, + 'total_moves' => (int) $row->total_moves, + ]; + }) + ->toArray(); + + return $warehouses; + } + + private function getStockByWarehouse(): array + { + $warehouses = DB::table('warehouses') + ->leftJoin('stock_items', 'warehouses.id', '=', 'stock_items.warehouse_id') + ->leftJoin('products', 'stock_items.product_id', '=', 'products.id') + ->selectRaw( + 'warehouses.id as warehouse_id, warehouses.name as warehouse_name, ' . + 'COALESCE(SUM(stock_items.qty_on_hand_base), 0) as qty_on_hand, ' . + 'COALESCE(SUM(stock_items.qty_on_hand_base * products.prix_unitaire), 0) as stock_value, ' . + 'SUM(CASE WHEN stock_items.qty_on_hand_base <= stock_items.safety_stock_base THEN 1 ELSE 0 END) as alert_count' + ) + ->groupBy('warehouses.id', 'warehouses.name') + ->get() + ->map(function ($row) { + return [ + 'warehouse_id' => (int) $row->warehouse_id, + 'warehouse_name' => $row->warehouse_name, + 'qty_on_hand' => (float) $row->qty_on_hand, + 'stock_value' => (float) $row->stock_value, + 'alert_count' => (int) $row->alert_count, + ]; + }) + ->toArray(); + + return $warehouses; + } /** * Find a default intervention product (where category has intervention=true) */ diff --git a/thanasoft-back/app/Repositories/ProductRepositoryInterface.php b/thanasoft-back/app/Repositories/ProductRepositoryInterface.php index 961e047..6835f57 100644 --- a/thanasoft-back/app/Repositories/ProductRepositoryInterface.php +++ b/thanasoft-back/app/Repositories/ProductRepositoryInterface.php @@ -18,5 +18,7 @@ interface ProductRepositoryInterface extends BaseRepositoryInterface public function getProductsByFournisseur(int $fournisseurId): LengthAwarePaginator; + public function getStatistics(): array; + public function findInterventionProduct(): ?\App\Models\Product; }