302 lines
15 KiB
PHP
302 lines
15 KiB
PHP
<?php
|
|
|
|
namespace App\Filament\Pages;
|
|
|
|
use App\Models\Customer;
|
|
use App\Models\DelayedGood;
|
|
use App\Models\EggCollection;
|
|
use App\Models\EggCollectionItem;
|
|
use App\Models\Expense;
|
|
use App\Models\Feed;
|
|
use App\Models\FeedPurchase;
|
|
use App\Models\Order;
|
|
use App\Models\Payroll;
|
|
use BackedEnum;
|
|
use Carbon\Carbon;
|
|
use Filament\Forms\Components\DatePicker;
|
|
use Filament\Forms\Components\Select;
|
|
use Filament\Forms\Concerns\InteractsWithForms;
|
|
use Filament\Forms\Contracts\HasForms;
|
|
use Filament\Pages\Page;
|
|
use Filament\Schemas\Components\Grid;
|
|
use Filament\Schemas\Components\Section;
|
|
use Filament\Schemas\Schema;
|
|
use Filament\Support\Icons\Heroicon;
|
|
|
|
class Analisys extends Page implements HasForms
|
|
{
|
|
use InteractsWithForms;
|
|
|
|
public ?array $data = [];
|
|
|
|
public function mount(): void
|
|
{
|
|
$this->form->fill([
|
|
'period' => 'this_month',
|
|
'from_date' => now()->startOfMonth()->format('d F Y'),
|
|
'to_date' => now()->format('d F Y'),
|
|
]);
|
|
}
|
|
|
|
public function form(Schema $form): Schema
|
|
{
|
|
return $form
|
|
->schema([
|
|
Section::make()
|
|
->schema([
|
|
Grid::make(3)
|
|
->schema([
|
|
Select::make('period')
|
|
->label('Periode Cepat')
|
|
->options([
|
|
'today' => 'Hari Ini',
|
|
'this_week' => 'Minggu Ini',
|
|
'this_month' => 'Bulan Ini',
|
|
'this_year' => 'Tahun Ini',
|
|
'custom' => 'Kustom Tanggal',
|
|
])
|
|
->live()
|
|
->afterStateUpdated(function ($state, $set, $get) {
|
|
if ($state === 'today') {
|
|
$set('from_date', now()->format('d F Y'));
|
|
$set('to_date', now()->format('d F Y'));
|
|
} elseif ($state === 'this_week') {
|
|
$set('from_date', now()->startOfWeek()->format('d F Y'));
|
|
$set('to_date', now()->endOfWeek()->format('d F Y'));
|
|
} elseif ($state === 'this_month') {
|
|
$set('from_date', now()->startOfMonth()->format('d F Y'));
|
|
$set('to_date', now()->endOfMonth()->format('d F Y'));
|
|
} elseif ($state === 'this_year') {
|
|
$set('from_date', now()->startOfYear()->format('d F Y'));
|
|
$set('to_date', now()->endOfYear()->format('d F Y'));
|
|
}
|
|
$this->dispatch('stats-updated');
|
|
}),
|
|
|
|
DatePicker::make('from_date')
|
|
->label('Dari Tanggal')
|
|
->native(false)
|
|
->displayFormat('d F Y')
|
|
->live()
|
|
->afterStateUpdated(function ($set) {
|
|
$set('period', 'custom');
|
|
$this->dispatch('stats-updated');
|
|
}),
|
|
|
|
DatePicker::make('to_date')
|
|
->label('Sampai Tanggal')
|
|
->native(false)
|
|
->displayFormat('d F Y')
|
|
->live()
|
|
->afterStateUpdated(function ($set) {
|
|
$set('period', 'custom');
|
|
$this->dispatch('stats-updated');
|
|
}),
|
|
]),
|
|
])
|
|
->compact(),
|
|
])
|
|
->statePath('data');
|
|
}
|
|
|
|
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedChartPie;
|
|
|
|
protected static ?string $navigationLabel = 'Analisis';
|
|
|
|
protected static ?string $slug = 'analisys';
|
|
|
|
protected static ?string $title = 'Analisis';
|
|
|
|
protected string $view = 'filament.pages.analisys';
|
|
|
|
public function getViewData(): array
|
|
{
|
|
$formatNumber = fn ($val) => number_format((float) $val, $val == floor($val) ? 0 : 2, ',', '.');
|
|
$now = now();
|
|
|
|
$fromDateInput = $this->data['from_date'] ?? now()->startOfMonth()->format('d F Y');
|
|
$toDateInput = $this->data['to_date'] ?? now()->format('d F Y');
|
|
|
|
// Normalize for DB queries
|
|
$fromDate = Carbon::parse($fromDateInput)->format('Y-m-d');
|
|
$toDate = Carbon::parse($toDateInput)->format('Y-m-d');
|
|
|
|
// 1. Count customers
|
|
$countPelanggan = Customer::count();
|
|
|
|
// 2. Count total eggs (by unit)
|
|
$totalEggsByUnit = EggCollectionItem::query()
|
|
->join('egg_collections', 'egg_collection_items.egg_collection_id', '=', 'egg_collections.id')
|
|
->join('units', 'egg_collection_items.unit_id', '=', 'units.id')
|
|
->whereNull('egg_collections.deleted_at')
|
|
->whereBetween('egg_collections.production_date', [$fromDate, $toDate])
|
|
->selectRaw('units.name as unit_name,
|
|
sum(case when is_broken = 0 then quantity else 0 end) as total_good,
|
|
sum(case when is_broken = 1 then quantity else 0 end) as total_broken')
|
|
->groupBy('units.name')
|
|
->get()
|
|
->map(fn ($item) => [
|
|
'unit_name' => $item->unit_name,
|
|
'total_good' => $formatNumber($item->total_good),
|
|
'total_broken' => $formatNumber($item->total_broken),
|
|
]);
|
|
|
|
// 3. Count total orders
|
|
$countPesanan = Order::whereBetween('order_date', [$fromDate, $toDate])->count();
|
|
|
|
// 4. Count total delayed goods (by unit)
|
|
$totalDelayedByUnit = DelayedGood::query()
|
|
->join('units', 'delayed_goods.unit_id', '=', 'units.id')
|
|
->whereBetween('delayed_goods.stored_date', [$fromDate, $toDate])
|
|
->selectRaw('units.name as unit_name, sum(quantity) as total')
|
|
->groupBy('units.name')
|
|
->get()
|
|
->map(fn ($item) => [
|
|
'unit_name' => $item->unit_name,
|
|
'total' => $formatNumber($item->total),
|
|
]);
|
|
|
|
// 5. Revenue
|
|
$pendapatanPesanan = Order::whereBetween('order_date', [$fromDate, $toDate])->sum('total_amount');
|
|
$pendapatanTertunda = DelayedGood::whereBetween('stored_date', [$fromDate, $toDate])->sum('total_amount');
|
|
$totalPendapatan = $pendapatanPesanan + $pendapatanTertunda;
|
|
|
|
// 6. Financial totals
|
|
$totalPengeluaran = Expense::whereBetween('expense_date', [$fromDate, $toDate])->sum('amount');
|
|
$totalPenggajian = Payroll::whereBetween('period_month', [Carbon::parse($fromDate)->format('Y-m'), Carbon::parse($toDate)->format('Y-m')])->sum('total_salary');
|
|
$totalBelanjaPakan = FeedPurchase::whereBetween('purchase_date', [$fromDate, $toDate])->sum('total_price');
|
|
|
|
$labaKotor = $totalPendapatan - $totalBelanjaPakan;
|
|
$labaBersih = $labaKotor - $totalPengeluaran - $totalPenggajian;
|
|
|
|
$paidInRange = DelayedGood::whereBetween('stored_date', [$fromDate, $toDate])->sum('paid_amount');
|
|
$totalInRange = DelayedGood::whereBetween('stored_date', [$fromDate, $toDate])->sum('total_amount');
|
|
$totalPiutang = $totalInRange - $paidInRange;
|
|
|
|
$mainStats = [
|
|
['label' => 'Total Pelanggan', 'desc' => 'Jumlah pelanggan terdaftar', 'value' => $countPelanggan, 'prefix' => ''],
|
|
['label' => 'Total Pesanan', 'desc' => 'Jumlah transaksi pesanan', 'value' => $countPesanan, 'prefix' => ''],
|
|
['label' => 'Laba Bersih', 'desc' => 'Setelah dikurangi beban operasional', 'value' => $formatNumber($labaBersih), 'prefix' => 'Rp'],
|
|
['label' => 'Total Piutang', 'desc' => 'Piutang barang tertunda (periode)', 'value' => $formatNumber($totalPiutang), 'prefix' => 'Rp'],
|
|
['label' => 'Total Pendapatan', 'desc' => 'Akumulasi semua pemasukan', 'value' => $formatNumber($totalPendapatan), 'prefix' => 'Rp'],
|
|
['label' => 'Laba Kotor', 'desc' => 'Pendapatan dikurangi biaya pakan', 'value' => $formatNumber($labaKotor), 'prefix' => 'Rp'],
|
|
['label' => 'Total Pengeluaran', 'desc' => 'Biaya operasional & umum', 'value' => $formatNumber($totalPengeluaran), 'prefix' => 'Rp'],
|
|
['label' => 'Total Penggajian', 'desc' => 'Total gaji karyawan', 'value' => $formatNumber($totalPenggajian), 'prefix' => 'Rp'],
|
|
['label' => 'Belanja Pakan', 'desc' => 'Total pembelian pakan ayam', 'value' => $formatNumber($totalBelanjaPakan), 'prefix' => 'Rp'],
|
|
];
|
|
|
|
// Top 5 Customers by Order Amount in Range
|
|
$topCustomers = Customer::query()
|
|
->withSum(['orders' => fn ($q) => $q->whereBetween('order_date', [$fromDate, $toDate])], 'total_amount')
|
|
->withSum(['delayedGoods' => fn ($q) => $q->whereBetween('stored_date', [$fromDate, $toDate])], 'total_amount')
|
|
->get()
|
|
->map(function ($customer) {
|
|
$customer->total_spent = ($customer->orders_sum_total_amount ?? 0) + ($customer->delayed_goods_sum_total_amount ?? 0);
|
|
|
|
return $customer;
|
|
})
|
|
->sortByDesc('total_spent')
|
|
->take(5)
|
|
->values();
|
|
|
|
// 7. Chart: Daily Production (In range)
|
|
$diffDays = Carbon::parse($fromDate)->diffInDays(Carbon::parse($toDate));
|
|
$rangeQueryDays = collect(range(0, min($diffDays, 30)))->map(fn ($i) => Carbon::parse($fromDate)->addDays($i)->format('Y-m-d'))->values();
|
|
|
|
$productionChart = [
|
|
'labels' => $rangeQueryDays->map(fn ($d) => Carbon::parse($d)->format('d M'))->values(),
|
|
'good' => $rangeQueryDays->map(fn ($d) => (float) EggCollection::whereDate('production_date', $d)->sum('total_eggs'))->values(),
|
|
'broken' => $rangeQueryDays->map(fn ($d) => (float) EggCollection::whereDate('production_date', $d)->sum('total_broken_eggs'))->values(),
|
|
];
|
|
|
|
// 8. Financial Trends (Monthly - 6 months prior to to_date)
|
|
$months = collect(range(0, 5))->map(fn ($i) => Carbon::parse($toDate)->subMonths($i)->format('Y-m'))->reverse();
|
|
$financialChart = [
|
|
'labels' => $months->map(fn ($m) => Carbon::parse($m)->translatedFormat('M Y'))->values(),
|
|
'revenue' => $months->map(function ($m) {
|
|
$orderRev = Order::whereDate('order_date', 'like', "$m%")->sum('total_amount');
|
|
$delayedRev = DelayedGood::whereDate('stored_date', 'like', "$m%")->sum('total_amount');
|
|
|
|
return (float) ($orderRev + $delayedRev);
|
|
})->values(),
|
|
'expenses' => $months->map(function ($m) {
|
|
$exp = Expense::whereDate('expense_date', 'like', "$m%")->sum('amount');
|
|
$payroll = Payroll::where('period_month', 'like', "$m%")->sum('total_salary');
|
|
$feed = FeedPurchase::whereDate('purchase_date', 'like', "$m%")->sum('total_price');
|
|
|
|
return (float) ($exp + $payroll + $feed);
|
|
})->values(),
|
|
];
|
|
|
|
// 8b. Chart: Monthly Profit Trend
|
|
$financialChart['profit'] = collect($financialChart['revenue'])->map(fn ($rev, $i) => (float) ($rev - $financialChart['expenses'][$i]))->values();
|
|
|
|
// 9. Chart: Expense Distribution
|
|
$expenseDistribution = [
|
|
'labels' => ['Gaji Karyawan', 'Belanja Pakan', 'Pengeluaran Umum'],
|
|
'data' => [(float) $totalPenggajian, (float) $totalBelanjaPakan, (float) $totalPengeluaran],
|
|
];
|
|
|
|
// 10. Chart: Sales Distribution by Unit
|
|
$salesByUnit = Order::whereBetween('order_date', [$fromDate, $toDate])
|
|
->join('units', 'orders.unit_id', '=', 'units.id')
|
|
->selectRaw('units.name, sum(total_amount) as total')
|
|
->groupBy('units.name')
|
|
->get();
|
|
|
|
// 11. Chart: Debt Status (In range)
|
|
$debtStatus = [
|
|
'paid' => (float) $paidInRange,
|
|
'unpaid' => (float) ($totalInRange - $paidInRange),
|
|
];
|
|
|
|
// 12. Chart: Feed Stocks (Current)
|
|
$feedStocks = Feed::query()
|
|
->join('units', 'feeds.unit_id', '=', 'units.id')
|
|
->select('feeds.name', 'feeds.stock', 'units.alias as unit_alias')
|
|
->get();
|
|
|
|
// 13. Chart: Production by Warehouse (In range)
|
|
$productionByWarehouse = EggCollection::whereBetween('production_date', [$fromDate, $toDate])
|
|
->join('warehouses', 'egg_collections.warehouse_id', '=', 'warehouses.id')
|
|
->selectRaw('warehouses.name, sum(total_eggs) as total')
|
|
->groupBy('warehouses.name')
|
|
->get();
|
|
|
|
// 14. Chart: Revenue Split (In range)
|
|
$revenueSplit = [
|
|
'orders' => (float) $pendapatanPesanan,
|
|
'delayed' => (float) $pendapatanTertunda,
|
|
];
|
|
|
|
return [
|
|
'mainStats' => $mainStats,
|
|
'totalEggsByUnit' => $totalEggsByUnit,
|
|
'totalDelayedByUnit' => $totalDelayedByUnit,
|
|
'topCustomers' => $topCustomers,
|
|
'productionChart' => $productionChart,
|
|
'financialChart' => $financialChart,
|
|
'expenseDistribution' => $expenseDistribution,
|
|
'salesByUnit' => [
|
|
'labels' => $salesByUnit->pluck('name')->values(),
|
|
'data' => $salesByUnit->pluck('total')->map(fn ($v) => (float) $v)->values(),
|
|
],
|
|
'debtStatus' => $debtStatus,
|
|
'feedStocks' => [
|
|
'labels' => $feedStocks->pluck('name')->values(),
|
|
'data' => $feedStocks->pluck('stock')->map(fn ($v) => (float) $v)->values(),
|
|
'units' => $feedStocks->pluck('unit_alias')->values(),
|
|
],
|
|
'productionByWarehouse' => [
|
|
'labels' => $productionByWarehouse->pluck('name')->values(),
|
|
'data' => $productionByWarehouse->pluck('total')->map(fn ($v) => (float) $v)->values(),
|
|
],
|
|
'revenueSplit' => [
|
|
'labels' => ['Penjualan Langsung', 'Barang Tertunda'],
|
|
'data' => [$revenueSplit['orders'], $revenueSplit['delayed']],
|
|
],
|
|
'formatNumber' => $formatNumber,
|
|
];
|
|
}
|
|
}
|