Add price list management across the API, store, services, routes, navigation, and sales views. Support quotes for either a client or a client group, including PDF download and nullable client validation for group-based recipients. Extend client groups to manage assigned clients directly from the form and detail views, and refresh supplier, intervention, stock, and order screens with updated interactions and layouts.
155 lines
4.7 KiB
PHP
155 lines
4.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Repositories;
|
|
|
|
use App\Models\Product;
|
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
|
|
|
class ProductRepository extends BaseRepository implements ProductRepositoryInterface
|
|
{
|
|
public function __construct(Product $model)
|
|
{
|
|
parent::__construct($model);
|
|
}
|
|
|
|
/**
|
|
* Get paginated products with filters
|
|
*/
|
|
public function paginate(int $perPage = 15, array $filters = []): LengthAwarePaginator
|
|
{
|
|
$query = $this->model->newQuery()->with(['fournisseur', 'category']);
|
|
|
|
// Apply filters
|
|
if (!empty($filters['search'])) {
|
|
$query->where(function ($q) use ($filters) {
|
|
$q->where('nom', 'like', '%' . $filters['search'] . '%')
|
|
->orWhere('reference', 'like', '%' . $filters['search'] . '%')
|
|
->orWhere('fabricant', 'like', '%' . $filters['search'] . '%');
|
|
});
|
|
}
|
|
|
|
if (!empty($filters['categorie'])) {
|
|
$query->where('categorie_id', $filters['categorie']);
|
|
}
|
|
|
|
if (!empty($filters['fournisseur_id'])) {
|
|
$query->where('fournisseur_id', $filters['fournisseur_id']);
|
|
}
|
|
|
|
if (isset($filters['low_stock'])) {
|
|
$query->whereRaw('stock_actuel <= stock_minimum');
|
|
}
|
|
|
|
if (isset($filters['expiring_soon'])) {
|
|
$query->where('date_expiration', '<=', now()->addDays(30)->toDateString())
|
|
->where('date_expiration', '>=', now()->toDateString());
|
|
}
|
|
|
|
if (isset($filters['is_intervention'])) {
|
|
$query->whereHas('category', function ($q) use ($filters) {
|
|
$q->where('intervention', filter_var($filters['is_intervention'], FILTER_VALIDATE_BOOLEAN));
|
|
});
|
|
}
|
|
|
|
// Apply sorting
|
|
$sortField = $filters['sort_by'] ?? 'created_at';
|
|
$sortDirection = $filters['sort_direction'] ?? 'desc';
|
|
$query->orderBy($sortField, $sortDirection);
|
|
|
|
return $query->paginate($perPage);
|
|
}
|
|
|
|
/**
|
|
* Get products with low stock
|
|
*/
|
|
public function getLowStockProducts(int $perPage = 15): LengthAwarePaginator
|
|
{
|
|
return $this->model->newQuery()
|
|
->with(['fournisseur', 'category'])
|
|
->whereRaw('stock_actuel <= stock_minimum')
|
|
->orderBy('stock_actuel', 'asc')
|
|
->paginate($perPage);
|
|
}
|
|
|
|
/**
|
|
* Search products by name
|
|
*/
|
|
public function searchByName(string $name, int $perPage = 15, bool $exactMatch = false)
|
|
{
|
|
$query = $this->model->newQuery()->with(['fournisseur', 'category']);
|
|
|
|
if ($exactMatch) {
|
|
$query->where('nom', $name);
|
|
}
|
|
else {
|
|
$query->where('nom', 'like', '%' . $name . '%');
|
|
}
|
|
|
|
return $query->get();
|
|
}
|
|
|
|
/**
|
|
* Get products by category
|
|
*/
|
|
public function getByCategory(int $categoryId, int $perPage = 15): LengthAwarePaginator
|
|
{
|
|
return $this->model->newQuery()
|
|
->with(['fournisseur', 'category'])
|
|
->where('categorie_id', $categoryId)
|
|
->orderBy('nom')
|
|
->paginate($perPage);
|
|
}
|
|
|
|
/**
|
|
* Get products by fournisseur
|
|
*/
|
|
public function getProductsByFournisseur(int $fournisseurId): LengthAwarePaginator
|
|
{
|
|
return $this->model->newQuery()
|
|
->where('fournisseur_id', $fournisseurId)
|
|
->orderBy('nom')
|
|
->paginate(15);
|
|
}
|
|
|
|
/**
|
|
* Update stock quantity
|
|
*/
|
|
public function updateStock(int $productId, float $newQuantity): bool
|
|
{
|
|
return $this->model->where('id', $productId)
|
|
->update(['stock_actuel' => $newQuantity]) > 0;
|
|
}
|
|
|
|
/**
|
|
* Get product statistics
|
|
*/
|
|
public function getStatistics(): array
|
|
{
|
|
$totalProducts = $this->model->count();
|
|
$lowStockProducts = $this->model->whereRaw('stock_actuel <= stock_minimum')->count();
|
|
$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'));
|
|
|
|
return [
|
|
'total_products' => $totalProducts,
|
|
'low_stock_products' => $lowStockProducts,
|
|
'expiring_products' => $expiringProducts,
|
|
'total_value' => $totalValue,
|
|
];
|
|
}
|
|
/**
|
|
* Find a default intervention product (where category has intervention=true)
|
|
*/
|
|
public function findInterventionProduct(): ?Product
|
|
{
|
|
return $this->model->newQuery()
|
|
->whereHas('category', function ($query) {
|
|
$query->where('intervention', true);
|
|
})
|
|
->first();
|
|
}
|
|
} |