292 lines
11 KiB
PHP
292 lines
11 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Product;
|
|
use App\Models\Dealer;
|
|
use App\Models\Stock;
|
|
use App\Models\StockLog;
|
|
use App\Models\Role;
|
|
use App\Models\User;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Facades\Auth;
|
|
|
|
class StockReportService
|
|
{
|
|
/**
|
|
* Get stock report data for all products and dealers on a specific date
|
|
*/
|
|
public function getStockReportData($targetDate = null)
|
|
{
|
|
$targetDate = $targetDate ? Carbon::parse($targetDate) : now();
|
|
|
|
// Get dealers based on user role
|
|
$dealers = $this->getDealersBasedOnUserRole();
|
|
|
|
// Get all active products
|
|
$products = Product::where('active', true)
|
|
->with(['category'])
|
|
->orderBy('name')
|
|
->get();
|
|
|
|
$data = [];
|
|
|
|
foreach ($products as $product) {
|
|
$row = [
|
|
'product_id' => $product->id,
|
|
'product_code' => $product->code,
|
|
'product_name' => $product->name,
|
|
'category_name' => $product->category ? $product->category->name : '-',
|
|
'unit' => $product->unit ?? '-',
|
|
'total_stock' => 0
|
|
];
|
|
|
|
// Calculate stock for each dealer on the target date
|
|
foreach ($dealers as $dealer) {
|
|
$stockOnDate = $this->getStockOnDate($product->id, $dealer->id, $targetDate);
|
|
$row["dealer_{$dealer->id}"] = $stockOnDate;
|
|
$row['total_stock'] += $stockOnDate;
|
|
}
|
|
|
|
$data[] = $row;
|
|
}
|
|
|
|
return [
|
|
'data' => $data,
|
|
'dealers' => $dealers
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get stock quantity for a specific product and dealer on a given date
|
|
*/
|
|
public function getStockOnDate($productId, $dealerId, $targetDate)
|
|
{
|
|
// Get the latest stock log entry before or on the target date
|
|
$latestStockLog = StockLog::whereHas('stock', function($query) use ($productId, $dealerId) {
|
|
$query->where('product_id', $productId)
|
|
->where('dealer_id', $dealerId);
|
|
})
|
|
->where('created_at', '<=', $targetDate->endOfDay())
|
|
->orderBy('created_at', 'desc')
|
|
->first();
|
|
|
|
if ($latestStockLog) {
|
|
// Return the new_quantity from the latest log entry
|
|
return $latestStockLog->new_quantity;
|
|
}
|
|
|
|
// If no stock log found, check if there's a current stock record
|
|
$currentStock = Stock::where('product_id', $productId)
|
|
->where('dealer_id', $dealerId)
|
|
->first();
|
|
|
|
if ($currentStock) {
|
|
// Check if the stock was created before or on the target date
|
|
if ($currentStock->created_at <= $targetDate) {
|
|
return $currentStock->quantity;
|
|
}
|
|
}
|
|
|
|
// No stock data available for this date
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Get optimized stock data using a single query approach
|
|
*/
|
|
public function getOptimizedStockReportData($targetDate = null)
|
|
{
|
|
$targetDate = $targetDate ? Carbon::parse($targetDate) : now();
|
|
|
|
// Get dealers based on user role
|
|
$dealers = $this->getDealersBasedOnUserRole();
|
|
|
|
// Get all active products with their stock data
|
|
$products = Product::where('active', true)
|
|
->with(['category', 'stocks.dealer'])
|
|
->orderBy('name')
|
|
->get();
|
|
|
|
$data = [];
|
|
|
|
foreach ($products as $product) {
|
|
$row = [
|
|
'product_id' => $product->id,
|
|
'product_code' => $product->code,
|
|
'product_name' => $product->name,
|
|
'category_name' => $product->category ? $product->category->name : '-',
|
|
'unit' => $product->unit ?? '-',
|
|
'total_stock' => 0
|
|
];
|
|
|
|
// Calculate stock for each dealer on the target date
|
|
foreach ($dealers as $dealer) {
|
|
$stockOnDate = $this->getOptimizedStockOnDate($product->id, $dealer->id, $targetDate);
|
|
$row["dealer_{$dealer->id}"] = $stockOnDate;
|
|
$row['total_stock'] += $stockOnDate;
|
|
}
|
|
|
|
$data[] = $row;
|
|
}
|
|
|
|
return [
|
|
'data' => $data,
|
|
'dealers' => $dealers
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get dealers based on logged-in user's role
|
|
*/
|
|
public function getDealersBasedOnUserRole()
|
|
{
|
|
// Get current authenticated user
|
|
$user = Auth::user();
|
|
|
|
if (!$user) {
|
|
Log::warning('No authenticated user found, returning all dealers');
|
|
return Dealer::whereNull('deleted_at')->orderBy('name')->get();
|
|
}
|
|
|
|
Log::info('Getting dealers for user:', [
|
|
'user_id' => $user->id,
|
|
'user_role_id' => $user->role_id,
|
|
'user_dealer_id' => $user->dealer_id
|
|
]);
|
|
|
|
// If user has role, check role type and dealer access
|
|
if ($user->role_id) {
|
|
$role = Role::with(['dealers' => function($query) {
|
|
$query->whereNull('dealers.deleted_at'); // Only active dealers
|
|
}])->find($user->role_id);
|
|
|
|
if ($role) {
|
|
// Check if role is admin type
|
|
if ($this->isAdminRole($role)) {
|
|
// Admin role - check if has pivot dealers
|
|
if ($role->dealers->count() > 0) {
|
|
// Admin with pivot dealers - return pivot dealers only
|
|
Log::info('Admin role with pivot dealers, returning pivot dealers only');
|
|
$dealers = $role->dealers()->whereNull('dealers.deleted_at')->orderBy('name')->get();
|
|
Log::info('Returning pivot dealers for admin:', $dealers->pluck('name')->toArray());
|
|
return $dealers;
|
|
} else {
|
|
// Admin without pivot dealers - return all dealers
|
|
Log::info('Admin role without pivot dealers, returning all dealers');
|
|
$allDealers = Dealer::whereNull('deleted_at')->orderBy('name')->get();
|
|
Log::info('Returning all dealers for admin:', $allDealers->pluck('name')->toArray());
|
|
return $allDealers;
|
|
}
|
|
}
|
|
|
|
// Non-admin role - return dealers from role pivot
|
|
if ($role->dealers->count() > 0) {
|
|
Log::info('Non-admin role with dealers, returning role dealers');
|
|
$dealers = $role->dealers()->whereNull('dealers.deleted_at')->orderBy('name')->get();
|
|
Log::info('Returning dealers from role:', $dealers->pluck('name')->toArray());
|
|
return $dealers;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If user has specific dealer_id but no role dealers, check if they can access their dealer_id
|
|
if ($user->dealer_id) {
|
|
Log::info('User has specific dealer_id:', ['dealer_id' => $user->dealer_id]);
|
|
if ($user->role_id) {
|
|
$role = Role::with(['dealers' => function($query) {
|
|
$query->whereNull('dealers.deleted_at'); // Only active dealers
|
|
}])->find($user->role_id);
|
|
|
|
if ($role && $role->hasDealer($user->dealer_id)) {
|
|
Log::info('User can access their dealer_id, returning single dealer');
|
|
$dealer = Dealer::where('id', $user->dealer_id)->whereNull('deleted_at')->orderBy('name')->get();
|
|
Log::info('Returning dealer:', $dealer->pluck('name')->toArray());
|
|
return $dealer;
|
|
} else {
|
|
Log::info('User cannot access their dealer_id');
|
|
}
|
|
}
|
|
Log::info('User has dealer_id but no role or no access, returning empty');
|
|
return collect();
|
|
}
|
|
|
|
// Fallback: return all dealers if no restrictions
|
|
Log::info('No restrictions found, returning all dealers');
|
|
$allDealers = Dealer::whereNull('deleted_at')->orderBy('name')->get();
|
|
Log::info('Returning all dealers:', $allDealers->pluck('name')->toArray());
|
|
return $allDealers;
|
|
}
|
|
|
|
/**
|
|
* Check if role is admin type (should show all dealers if no pivot)
|
|
*/
|
|
private function isAdminRole($role)
|
|
{
|
|
// Define admin role names that should have access to all dealers
|
|
$adminRoleNames = [
|
|
'admin'
|
|
];
|
|
|
|
// Check if role name contains admin keywords (but not "area")
|
|
$roleName = strtolower(trim($role->name));
|
|
foreach ($adminRoleNames as $adminName) {
|
|
if (strpos($roleName, $adminName) !== false && strpos($roleName, 'area') === false) {
|
|
Log::info('Role identified as admin type:', ['role_name' => $role->name]);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Role with "area" in name should use pivot dealers, not all dealers
|
|
if (strpos($roleName, 'area') !== false) {
|
|
Log::info('Role contains "area", treating as area role (use pivot dealers):', ['role_name' => $role->name]);
|
|
return false;
|
|
}
|
|
|
|
Log::info('Role is not admin type:', ['role_name' => $role->name]);
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Optimized method to get stock on date using subquery
|
|
*/
|
|
private function getOptimizedStockOnDate($productId, $dealerId, $targetDate)
|
|
{
|
|
try {
|
|
// Use a subquery to get the latest stock log entry efficiently
|
|
$latestStockLog = DB::table('stock_logs')
|
|
->join('stocks', 'stock_logs.stock_id', '=', 'stocks.id')
|
|
->where('stocks.product_id', $productId)
|
|
->where('stocks.dealer_id', $dealerId)
|
|
->where('stock_logs.created_at', '<=', $targetDate->endOfDay())
|
|
->orderBy('stock_logs.created_at', 'desc')
|
|
->select('stock_logs.new_quantity')
|
|
->first();
|
|
|
|
if ($latestStockLog) {
|
|
return $latestStockLog->new_quantity;
|
|
}
|
|
|
|
// If no stock log found, check current stock
|
|
$currentStock = Stock::where('product_id', $productId)
|
|
->where('dealer_id', $dealerId)
|
|
->first();
|
|
|
|
if ($currentStock && $currentStock->created_at <= $targetDate) {
|
|
return $currentStock->quantity;
|
|
}
|
|
|
|
return 0;
|
|
} catch (\Exception $e) {
|
|
// Log error and return 0
|
|
Log::error('Error getting stock on date: ' . $e->getMessage(), [
|
|
'product_id' => $productId,
|
|
'dealer_id' => $dealerId,
|
|
'target_date' => $targetDate
|
|
]);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|