340 lines
13 KiB
PHP
340 lines
13 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Admin;
|
|
|
|
use App\Enums\CashTransactionType;
|
|
use App\Enums\EmployeeAdvanceStatus;
|
|
use App\Enums\LeaveRequestStatus;
|
|
use App\Enums\OrderChannel;
|
|
use App\Enums\OrderStatus;
|
|
use App\Enums\PaymentType;
|
|
use App\Enums\PayrollStatus;
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\CashAccount;
|
|
use App\Models\CashTransaction;
|
|
use App\Models\Cutting;
|
|
use App\Models\CuttingResult;
|
|
use App\Models\Customer;
|
|
use App\Models\EmployeeAdvance;
|
|
use App\Models\LeaveRequest;
|
|
use App\Models\Order;
|
|
use App\Models\Payroll;
|
|
use App\Models\PayrollPeriod;
|
|
use App\Models\ProductVariant;
|
|
use App\Models\Purchase;
|
|
use App\Models\RawMaterialPrice;
|
|
use App\Models\Supplier;
|
|
use App\Models\User;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Inertia\Inertia;
|
|
use Inertia\Response;
|
|
|
|
class DashboardController extends Controller
|
|
{
|
|
public function index(): Response
|
|
{
|
|
$now = Carbon::now();
|
|
$startOfMonth = $now->copy()->startOfMonth();
|
|
$endOfMonth = $now->copy()->endOfMonth();
|
|
|
|
return Inertia::render('admin/Dashboard', [
|
|
'rawMaterialStock' => $this->getRawMaterialStock(),
|
|
'productStock' => $this->getProductStock(),
|
|
'topSuppliers' => $this->getTopSuppliers(),
|
|
'topCustomers' => $this->getTopCustomers(),
|
|
'purchaseSummary' => $this->getPurchaseSummary(),
|
|
'cuttingSummary' => $this->getCuttingSummary(),
|
|
'orderStats' => $this->getOrderStats(),
|
|
'revenueSummary' => $this->getRevenueSummary(),
|
|
'cashAccounts' => $this->getCashAccounts(),
|
|
'kasbonSummary' => $this->getKasbonSummary(),
|
|
'payrollSummary' => $this->getPayrollSummary($startOfMonth, $endOfMonth),
|
|
'leaveRequestSummary' => $this->getLeaveRequestSummary($startOfMonth, $endOfMonth),
|
|
]);
|
|
}
|
|
|
|
private function getRawMaterialStock(): array
|
|
{
|
|
$data = RawMaterialPrice::query()
|
|
->selectRaw('SUM(stock) as total_stock, SUM(stock * price) as total_value')
|
|
->first();
|
|
|
|
return [
|
|
'total_stock' => (float) ($data->total_stock ?? 0),
|
|
'total_value' => (int) ($data->total_value ?? 0),
|
|
];
|
|
}
|
|
|
|
private function getProductStock(): array
|
|
{
|
|
$data = ProductVariant::query()
|
|
->selectRaw('SUM(stock) as total_stock, SUM(reject_stock) as total_reject')
|
|
->first();
|
|
|
|
$totalStock = (int) ($data->total_stock ?? 0);
|
|
|
|
$totalValue = Cutting::query()
|
|
->whereNotNull('cost_per_unit')
|
|
->join('cutting_results', 'cuttings.id', '=', 'cutting_results.cutting_id')
|
|
->selectRaw('SUM(cutting_results.warehouse_stock * cuttings.cost_per_unit) as total_value')
|
|
->value('total_value');
|
|
|
|
return [
|
|
'total_stock' => $totalStock,
|
|
'total_reject' => (int) ($data->total_reject ?? 0),
|
|
'total_value' => (int) ($totalValue ?? 0),
|
|
];
|
|
}
|
|
|
|
private function getTopSuppliers(): array
|
|
{
|
|
return Purchase::query()
|
|
->join('suppliers', 'purchases.supplier_id', '=', 'suppliers.id')
|
|
->selectRaw('suppliers.name, SUM(purchases.total) as total_amount, COUNT(purchases.id) as purchase_count')
|
|
->groupBy('suppliers.id', 'suppliers.name')
|
|
->orderByDesc('total_amount')
|
|
->limit(5)
|
|
->get()
|
|
->map(fn ($item) => [
|
|
'name' => $item->name,
|
|
'total_amount' => (int) $item->total_amount,
|
|
'purchase_count' => (int) $item->purchase_count,
|
|
])
|
|
->toArray();
|
|
}
|
|
|
|
private function getTopCustomers(): array
|
|
{
|
|
return Order::query()
|
|
->where('status', OrderStatus::COMPLETED)
|
|
->join('customers', 'orders.customer_id', '=', 'customers.id')
|
|
->selectRaw('customers.name, SUM(orders.total_amount) as total_amount, COUNT(orders.id) as order_count')
|
|
->groupBy('customers.id', 'customers.name')
|
|
->orderByDesc('total_amount')
|
|
->limit(5)
|
|
->get()
|
|
->map(fn ($item) => [
|
|
'name' => $item->name,
|
|
'total_amount' => (int) $item->total_amount,
|
|
'order_count' => (int) $item->order_count,
|
|
])
|
|
->toArray();
|
|
}
|
|
|
|
private function getPurchaseSummary(): array
|
|
{
|
|
$data = Purchase::query()
|
|
->selectRaw('COUNT(*) as total_purchases, SUM(total) as total_spent, SUM(discount) as total_discount')
|
|
->first();
|
|
|
|
return [
|
|
'total_purchases' => (int) ($data->total_purchases ?? 0),
|
|
'total_spent' => (int) ($data->total_spent ?? 0),
|
|
'total_discount' => (int) ($data->total_discount ?? 0),
|
|
];
|
|
}
|
|
|
|
private function getCuttingSummary(): array
|
|
{
|
|
$totalCost = Cutting::query()
|
|
->selectRaw('COALESCE(SUM(total_material_cost), 0) + COALESCE(SUM(sewing_cost), 0) + COALESCE(SUM(other_cost), 0) as total_cost')
|
|
->value('total_cost');
|
|
|
|
$totalResults = CuttingResult::query()
|
|
->selectRaw('SUM(cutting_result) as total_cutting, SUM(warehouse_stock) as total_warehouse, SUM(cutting_reject) as total_reject')
|
|
->first();
|
|
|
|
return [
|
|
'total_cost' => (int) ($totalCost ?? 0),
|
|
'total_cutting' => (int) ($totalResults->total_cutting ?? 0),
|
|
'total_warehouse' => (int) ($totalResults->total_warehouse ?? 0),
|
|
'total_reject' => (int) ($totalResults->total_reject ?? 0),
|
|
];
|
|
}
|
|
|
|
private function getOrderStats(): array
|
|
{
|
|
$byChannel = Order::query()
|
|
->selectRaw('channel, COUNT(*) as count, SUM(total_amount) as total')
|
|
->groupBy('channel')
|
|
->get()
|
|
->map(fn ($item) => [
|
|
'channel' => $item->channel instanceof OrderChannel ? $item->channel->value : $item->channel,
|
|
'label' => $item->channel instanceof OrderChannel ? $item->channel->label() : OrderChannel::from($item->channel)->label(),
|
|
'count' => (int) $item->count,
|
|
'total' => (int) $item->total,
|
|
]);
|
|
|
|
$byPaymentType = Order::query()
|
|
->selectRaw('payment_type, COUNT(*) as count, SUM(total_amount) as total')
|
|
->groupBy('payment_type')
|
|
->get()
|
|
->map(fn ($item) => [
|
|
'payment_type' => $item->payment_type instanceof PaymentType ? $item->payment_type->value : $item->payment_type,
|
|
'label' => $item->payment_type instanceof PaymentType ? $item->payment_type->label() : PaymentType::from($item->payment_type)->label(),
|
|
'count' => (int) $item->count,
|
|
'total' => (int) $item->total,
|
|
]);
|
|
|
|
$byMarketing = Order::query()
|
|
->whereNotNull('marketing_id')
|
|
->join('users', 'orders.marketing_id', '=', 'users.id')
|
|
->leftJoin('user_profiles', 'users.id', '=', 'user_profiles.user_id')
|
|
->selectRaw("COALESCE(user_profiles.full_name, users.username) as name, COUNT(*) as count, SUM(orders.total_amount) as total")
|
|
->groupBy('orders.marketing_id', 'users.username', 'user_profiles.full_name')
|
|
->orderByDesc('total')
|
|
->limit(5)
|
|
->get()
|
|
->map(fn ($item) => [
|
|
'name' => $item->name,
|
|
'count' => (int) $item->count,
|
|
'total' => (int) $item->total,
|
|
]);
|
|
|
|
$byStatus = Order::query()
|
|
->selectRaw('status, COUNT(*) as count')
|
|
->groupBy('status')
|
|
->get()
|
|
->map(fn ($item) => [
|
|
'status' => $item->status instanceof OrderStatus ? $item->status->value : $item->status,
|
|
'label' => $item->status instanceof OrderStatus ? $item->status->label() : OrderStatus::from($item->status)->label(),
|
|
'count' => (int) $item->count,
|
|
]);
|
|
|
|
return [
|
|
'by_channel' => $byChannel->toArray(),
|
|
'by_payment_type' => $byPaymentType->toArray(),
|
|
'by_marketing' => $byMarketing->toArray(),
|
|
'by_status' => $byStatus->toArray(),
|
|
];
|
|
}
|
|
|
|
private function getRevenueSummary(): array
|
|
{
|
|
$data = Order::query()
|
|
->where('status', OrderStatus::COMPLETED)
|
|
->selectRaw('SUM(total_amount) as total_revenue, SUM(discount) as total_discount, SUM(shipping_cost) as total_shipping, COUNT(*) as total_orders')
|
|
->first();
|
|
|
|
return [
|
|
'total_revenue' => (int) ($data->total_revenue ?? 0),
|
|
'total_discount' => (int) ($data->total_discount ?? 0),
|
|
'total_shipping' => (int) ($data->total_shipping ?? 0),
|
|
'total_orders' => (int) ($data->total_orders ?? 0),
|
|
];
|
|
}
|
|
|
|
private function getCashAccounts(): array
|
|
{
|
|
return CashAccount::query()
|
|
->selectRaw('name, balance')
|
|
->get()
|
|
->map(fn ($item) => [
|
|
'name' => $item->name,
|
|
'balance' => (int) $item->balance,
|
|
])
|
|
->toArray();
|
|
}
|
|
|
|
private function getKasbonSummary(): array
|
|
{
|
|
$pending = EmployeeAdvance::query()
|
|
->where('status', EmployeeAdvanceStatus::PENDING)
|
|
->selectRaw('COUNT(*) as count, COALESCE(SUM(amount), 0) as total')
|
|
->first();
|
|
|
|
$approved = EmployeeAdvance::query()
|
|
->where('status', EmployeeAdvanceStatus::APPROVED)
|
|
->selectRaw('COUNT(*) as count, COALESCE(SUM(amount), 0) as total')
|
|
->first();
|
|
|
|
$paid = EmployeeAdvance::query()
|
|
->where('status', EmployeeAdvanceStatus::PAID)
|
|
->selectRaw('COUNT(*) as count, COALESCE(SUM(amount), 0) as total')
|
|
->first();
|
|
|
|
return [
|
|
'pending' => [
|
|
'count' => (int) ($pending->count ?? 0),
|
|
'total' => (int) ($pending->total ?? 0),
|
|
],
|
|
'approved' => [
|
|
'count' => (int) ($approved->count ?? 0),
|
|
'total' => (int) ($approved->total ?? 0),
|
|
],
|
|
'paid' => [
|
|
'count' => (int) ($paid->count ?? 0),
|
|
'total' => (int) ($paid->total ?? 0),
|
|
],
|
|
'total' => (int) ($pending->total ?? 0) + (int) ($approved->total ?? 0) + (int) ($paid->total ?? 0),
|
|
];
|
|
}
|
|
|
|
private function getPayrollSummary(Carbon $startOfMonth, Carbon $endOfMonth): array
|
|
{
|
|
$period = PayrollPeriod::query()
|
|
->where('year', $startOfMonth->year)
|
|
->where('month', $startOfMonth->month)
|
|
->first();
|
|
|
|
if (! $period) {
|
|
return [
|
|
'has_period' => false,
|
|
'total_employees' => 0,
|
|
'total_amount' => 0,
|
|
'paid_amount' => 0,
|
|
'unpaid_amount' => 0,
|
|
'paid_count' => 0,
|
|
'unpaid_count' => 0,
|
|
];
|
|
}
|
|
|
|
$payrolls = Payroll::query()
|
|
->where('payroll_period_id', $period->id)
|
|
->selectRaw("
|
|
COUNT(*) as total_employees,
|
|
COALESCE(SUM(total_amount), 0) as total_amount,
|
|
COALESCE(SUM(CASE WHEN status = 'paid' THEN total_amount ELSE 0 END), 0) as paid_amount,
|
|
COALESCE(SUM(CASE WHEN status = 'unpaid' THEN total_amount ELSE 0 END), 0) as unpaid_amount,
|
|
SUM(CASE WHEN status = 'paid' THEN 1 ELSE 0 END) as paid_count,
|
|
SUM(CASE WHEN status = 'unpaid' THEN 1 ELSE 0 END) as unpaid_count
|
|
")
|
|
->first();
|
|
|
|
return [
|
|
'has_period' => true,
|
|
'period_status' => $period->status->value,
|
|
'total_employees' => (int) ($payrolls->total_employees ?? 0),
|
|
'total_amount' => (int) ($payrolls->total_amount ?? 0),
|
|
'paid_amount' => (int) ($payrolls->paid_amount ?? 0),
|
|
'unpaid_amount' => (int) ($payrolls->unpaid_amount ?? 0),
|
|
'paid_count' => (int) ($payrolls->paid_count ?? 0),
|
|
'unpaid_count' => (int) ($payrolls->unpaid_count ?? 0),
|
|
];
|
|
}
|
|
|
|
private function getLeaveRequestSummary(Carbon $startOfMonth, Carbon $endOfMonth): array
|
|
{
|
|
$data = LeaveRequest::query()
|
|
->where(function ($q) use ($startOfMonth, $endOfMonth) {
|
|
$q->whereBetween('start_date', [$startOfMonth, $endOfMonth])
|
|
->orWhereBetween('end_date', [$startOfMonth, $endOfMonth]);
|
|
})
|
|
->selectRaw("
|
|
COUNT(*) as total,
|
|
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending_count,
|
|
SUM(CASE WHEN status = 'approved' THEN 1 ELSE 0 END) as approved_count,
|
|
SUM(CASE WHEN status = 'rejected' THEN 1 ELSE 0 END) as rejected_count
|
|
")
|
|
->first();
|
|
|
|
return [
|
|
'total' => (int) ($data->total ?? 0),
|
|
'pending' => (int) ($data->pending_count ?? 0),
|
|
'approved' => (int) ($data->approved_count ?? 0),
|
|
'rejected' => (int) ($data->rejected_count ?? 0),
|
|
];
|
|
}
|
|
}
|