create new menu histori stock audit

This commit is contained in:
2025-06-16 17:27:59 +07:00
parent 567e4aa5fc
commit aa233eb793
13 changed files with 1264 additions and 1 deletions

View File

@@ -0,0 +1,83 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Menu;
use App\Models\Role;
use App\Models\Privilege;
class SetupStockAuditMenu extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'setup:stock-audit-menu';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Setup Stock Audit menu and privileges';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$this->info('Setting up Stock Audit menu...');
// Check if menu already exists
$existingMenu = Menu::where('link', 'stock-audit.index')->first();
if ($existingMenu) {
$this->warn('Stock Audit menu already exists!');
return 0;
}
// Create Stock Audit menu
$menu = Menu::create([
'name' => 'Audit Histori Stock',
'link' => 'stock-audit.index',
'created_at' => now(),
'updated_at' => now()
]);
$this->info('Stock Audit menu created with ID: ' . $menu->id);
// Give all roles access to this menu
$roles = Role::all();
$privilegeCount = 0;
foreach($roles as $role) {
// Check if privilege already exists
$existingPrivilege = Privilege::where('role_id', $role->id)
->where('menu_id', $menu->id)
->first();
if (!$existingPrivilege) {
Privilege::create([
'role_id' => $role->id,
'menu_id' => $menu->id,
'create' => 0, // Stock audit is view-only
'update' => 0, // Stock audit is view-only
'delete' => 0, // Stock audit is view-only
'view' => 1, // Allow viewing
'created_at' => now(),
'updated_at' => now()
]);
$privilegeCount++;
}
}
$this->info("Created {$privilegeCount} privileges for Stock Audit menu.");
$this->info('Stock Audit menu setup completed successfully!');
return 0;
}
}

View File

@@ -0,0 +1,227 @@
<?php
namespace App\Http\Controllers\WarehouseManagement;
use App\Http\Controllers\Controller;
use App\Models\StockLog;
use App\Models\Menu;
use App\Models\Dealer;
use App\Models\Product;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Yajra\DataTables\DataTables;
class StockAuditController extends Controller
{
public function index(Request $request)
{
$menu = Menu::where('link', 'stock-audit.index')->first();
$dealers = Dealer::all();
$products = Product::all();
if ($request->ajax()) {
Log::info('Stock audit ajax request received', [
'filters' => $request->only(['dealer', 'product', 'change_type', 'date']),
'user_id' => auth()->id(),
'user_dealer_id' => auth()->user()->dealer_id
]);
$data = StockLog::query()
->with([
'stock.product',
'stock.dealer',
'user.role',
'source'
])
->leftJoin('stocks', 'stock_logs.stock_id', '=', 'stocks.id')
->leftJoin('products', 'stocks.product_id', '=', 'products.id')
->leftJoin('dealers', 'stocks.dealer_id', '=', 'dealers.id')
->leftJoin('users', 'stock_logs.user_id', '=', 'users.id')
->select('stock_logs.*');
// Filter berdasarkan dealer jika user bukan admin
if (auth()->user()->dealer_id) {
$data->whereHas('stock', function($query) {
$query->where('dealer_id', auth()->user()->dealer_id);
});
}
// Apply filters from request
if ($request->filled('dealer')) {
$data->where('dealers.name', 'like', '%' . $request->dealer . '%');
}
if ($request->filled('product')) {
$data->where('products.name', 'like', '%' . $request->product . '%');
}
if ($request->filled('change_type')) {
$data->where('stock_logs.change_type', $request->change_type);
}
if ($request->filled('date')) {
$data->whereDate('stock_logs.created_at', $request->date);
}
return DataTables::of($data)
->addIndexColumn()
->addColumn('product_name', function($row) {
return $row->stock->product->name ?? '-';
})
->addColumn('dealer_name', function($row) {
return $row->stock->dealer->name ?? '-';
})
->addColumn('change_type', function($row) {
$changeType = $row->change_type;
$class = match($changeType->value) {
'increase' => 'text-success',
'decrease' => 'text-danger',
'adjustment' => 'text-warning',
'no_change' => 'text-muted',
default => 'text-dark'
};
return "<span class=\"font-weight-bold {$class}\">{$changeType->label()}</span>";
})
->addColumn('quantity_change', function($row) {
$change = $row->quantity_change;
if ($change > 0) {
return "<span class=\"text-success\">+{$change}</span>";
} elseif ($change < 0) {
return "<span class=\"text-danger\">{$change}</span>";
} else {
return "<span class=\"text-muted\">0</span>";
}
})
->addColumn('stock_before_after', function($row) {
return "{$row->previous_quantity}{$row->new_quantity}";
})
->addColumn('source_info', function($row) {
if ($row->source_type === 'App\\Models\\Mutation') {
$mutationNumber = $row->source ? $row->source->mutation_number : '-';
return "Mutasi: {$mutationNumber}";
} elseif ($row->source_type === 'App\\Models\\Opname') {
return "Opname";
} else {
return $row->source_type ?? '-';
}
})
->addColumn('user_name', function($row) {
return $row->user->name ?? '-';
})
->addColumn('created_at', function($row) {
return $row->created_at->format('d M Y, H:i');
})
->addColumn('action', function($row) {
$buttons = '<button type="button" class="btn btn-info btn-sm" onclick="showAuditDetail('.$row->id.')">
Detail
</button>';
return $buttons;
})
// Filtering
->filterColumn('product_name', function($query, $keyword) {
$query->where('products.name', 'like', "%{$keyword}%");
})
->filterColumn('dealer_name', function($query, $keyword) {
$query->where('dealers.name', 'like', "%{$keyword}%");
})
->filterColumn('change_type', function($query, $keyword) {
$query->where('stock_logs.change_type', 'like', "%{$keyword}%");
})
->filterColumn('source_info', function($query, $keyword) {
$query->where(function($q) use ($keyword) {
$q->where('stock_logs.source_type', 'like', "%{$keyword}%")
->orWhere('stock_logs.description', 'like', "%{$keyword}%");
});
})
->filterColumn('user_name', function($query, $keyword) {
$query->where('users.name', 'like', "%{$keyword}%");
})
->filterColumn('created_at', function($query, $keyword) {
$query->whereDate('stock_logs.created_at', 'like', "%{$keyword}%");
})
// Order column mapping
->orderColumn('product_name', function($query, $order) {
return $query->orderBy('products.name', $order);
})
->orderColumn('dealer_name', function($query, $order) {
return $query->orderBy('dealers.name', $order);
})
->orderColumn('user_name', function($query, $order) {
return $query->orderBy('users.name', $order);
})
->orderColumn('created_at', function($query, $order) {
return $query->orderBy('stock_logs.created_at', $order);
})
->orderColumn('quantity_change', function($query, $order) {
return $query->orderBy('stock_logs.quantity_change', $order);
})
->orderColumn('stock_before_after', function($query, $order) {
return $query->orderBy('stock_logs.previous_quantity', $order);
})
->orderColumn('change_type', function($query, $order) {
return $query->orderBy('stock_logs.change_type', $order);
})
->orderColumn('source_info', function($query, $order) {
return $query->orderBy('stock_logs.source_type', $order);
})
->rawColumns(['change_type', 'quantity_change', 'action'])
->make(true);
}
return view('warehouse_management.stock_audit.index', compact('menu', 'dealers', 'products'));
}
public function getDetail(StockLog $stockLog)
{
try {
$stockLog->load([
'stock.product',
'stock.dealer',
'user.role',
'source'
]);
// Format data untuk response
$stockLog->created_at_formatted = $stockLog->created_at->format('d M Y, H:i');
$stockLog->change_type_label = $stockLog->change_type->label();
// Detail source berdasarkan tipe
$sourceDetail = null;
if ($stockLog->source) {
if ($stockLog->source_type === 'App\\Models\\Mutation') {
$mutation = $stockLog->source;
$mutation->load(['fromDealer', 'toDealer', 'requestedBy', 'approvedBy']);
// Format approved_at date if exists
if ($mutation->approved_at) {
$mutation->approved_at_formatted = $mutation->approved_at->format('d M Y, H:i');
}
$sourceDetail = [
'type' => 'mutation',
'data' => $mutation
];
} elseif ($stockLog->source_type === 'App\\Models\\StockOpname') {
$opname = $stockLog->source;
$opname->load(['dealer', 'user']);
$sourceDetail = [
'type' => 'opname',
'data' => $opname
];
}
}
return response()->json([
'success' => true,
'data' => $stockLog,
'source_detail' => $sourceDetail
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => 'Gagal memuat detail audit: ' . $e->getMessage()
], 500);
}
}
}