feat: Introduce a generic stock activity log method and translate log descriptions to Indonesian.
This commit is contained in:
parent
3b289588f2
commit
44576b6bf7
@ -82,7 +82,7 @@ public function save(Warehouse $warehouse): void
|
||||
StockActivityLogService::logWarehouse(
|
||||
logName: 'adjustment',
|
||||
event: 'update',
|
||||
description: "Manual adjustment for {$this->name}: changed from {$oldStockValue} to {$newStockValue}. Note: ".($this->note ?? '-'),
|
||||
description: "Penyesuaian manual untuk {$this->name}: berubah dari {$oldStockValue} menjadi {$newStockValue}. Catatan: ".($this->note ?? '-'),
|
||||
performedOn: $item,
|
||||
warehouse: $warehouse,
|
||||
quantityChange: $newStockValue - $oldStockValue,
|
||||
|
||||
@ -128,7 +128,7 @@ public function render(): View
|
||||
[
|
||||
'title' => 'Pegawai',
|
||||
'value' => User::whereHas('employee')
|
||||
->when(! empty($outletIds), fn($q) => $q->whereHas('outlets', fn($q2) => $q2->whereIn('outlets.id', $outletIds)))
|
||||
->when(! empty($outletIds), fn ($q) => $q->whereHas('outlets', fn ($q2) => $q2->whereIn('outlets.id', $outletIds)))
|
||||
->when($dateQuery || ($startDate && $endDate), $filterDate)
|
||||
->withoutDeveloper()
|
||||
->count(),
|
||||
@ -141,7 +141,7 @@ public function render(): View
|
||||
],
|
||||
[
|
||||
'title' => 'Voucher Aktif',
|
||||
'value' => Voucher::when(! empty($outletIds), fn($q) => $q->whereHas('outlets', fn($q2) => $q2->whereIn('outlets.id', $outletIds)))
|
||||
'value' => Voucher::when(! empty($outletIds), fn ($q) => $q->whereHas('outlets', fn ($q2) => $q2->whereIn('outlets.id', $outletIds)))
|
||||
->when($dateQuery || ($startDate && $endDate), $filterDate)
|
||||
->active()
|
||||
->count(),
|
||||
@ -150,7 +150,7 @@ public function render(): View
|
||||
[
|
||||
'title' => 'Member',
|
||||
'value' => User::whereHas('customer')
|
||||
->when(! empty($outletIds), fn($q) => $q->whereHas('outlets', fn($q2) => $q2->whereIn('outlets.id', $outletIds)))
|
||||
->when(! empty($outletIds), fn ($q) => $q->whereHas('outlets', fn ($q2) => $q2->whereIn('outlets.id', $outletIds)))
|
||||
->when($dateQuery || ($startDate && $endDate), $filterDate)
|
||||
->count(),
|
||||
'description' => 'Total pelanggan yang sudah mendaftar sebagai member resmi.',
|
||||
@ -174,39 +174,39 @@ public function render(): View
|
||||
],
|
||||
[
|
||||
'title' => 'Parfum',
|
||||
'value' => Perfume::when(! empty($outletIds), fn($q) => $q->whereHas('outlets', fn($q2) => $q2->whereIn('outlets.id', $outletIds)))
|
||||
'value' => Perfume::when(! empty($outletIds), fn ($q) => $q->whereHas('outlets', fn ($q2) => $q2->whereIn('outlets.id', $outletIds)))
|
||||
->when($dateQuery || ($startDate && $endDate), $filterDate)
|
||||
->count(),
|
||||
'description' => 'Jumlah varian bibit parfum yang tersedia dalam katalog.',
|
||||
],
|
||||
[
|
||||
'title' => 'Produk',
|
||||
'value' => Product::when(! empty($outletIds), fn($q) => $q->whereHas('outlets', fn($q2) => $q2->whereIn('outlets.id', $outletIds)))
|
||||
'value' => Product::when(! empty($outletIds), fn ($q) => $q->whereHas('outlets', fn ($q2) => $q2->whereIn('outlets.id', $outletIds)))
|
||||
->when($dateQuery || ($startDate && $endDate), $filterDate)
|
||||
->count(),
|
||||
'description' => 'Jumlah item produk jadi (seperti aksesoris) yang terdaftar.',
|
||||
],
|
||||
[
|
||||
'title' => 'Botol',
|
||||
'value' => Bottle::when(! empty($outletIds), fn($q) => $q->whereHas('outlets', fn($q2) => $q2->whereIn('outlets.id', $outletIds)))
|
||||
'value' => Bottle::when(! empty($outletIds), fn ($q) => $q->whereHas('outlets', fn ($q2) => $q2->whereIn('outlets.id', $outletIds)))
|
||||
->when($dateQuery || ($startDate && $endDate), $filterDate)
|
||||
->count(),
|
||||
'description' => 'Jumlah jenis botol parfum yang tersedia.',
|
||||
],
|
||||
];
|
||||
|
||||
$orderQuery = Order::when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
$orderQuery = Order::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->when($dateQuery || ($startDate && $endDate), $filterDate);
|
||||
|
||||
$totalIncome = (clone $orderQuery)->sum('total');
|
||||
$totalCogs = (clone $orderQuery)->sum('cogs');
|
||||
$totalDiscount = (clone $orderQuery)->sum(DB::raw('discount + COALESCE(voucher_discount, 0)'));
|
||||
|
||||
$expenseQuery = Expense::when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
$expenseQuery = Expense::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->when($dateQuery || ($startDate && $endDate), $filterDate);
|
||||
$totalExpense = $expenseQuery->sum('amount');
|
||||
|
||||
$payrollQuery = Payroll::when(! empty($outletIds), fn($q) => $q->whereHas('user.outlets', fn($q2) => $q2->whereIn('outlets.id', $outletIds)))
|
||||
$payrollQuery = Payroll::when(! empty($outletIds), fn ($q) => $q->whereHas('user.outlets', fn ($q2) => $q2->whereIn('outlets.id', $outletIds)))
|
||||
->when($dateQuery || ($startDate && $endDate), $filterDate)
|
||||
->paid();
|
||||
$totalPayroll = $payrollQuery->sum('total_salary');
|
||||
@ -215,7 +215,7 @@ public function render(): View
|
||||
$netProfit = $grossProfit - $totalExpense - $totalPayroll;
|
||||
|
||||
$totalOrdersCount = (clone $orderQuery)->count();
|
||||
$totalItemsCount = OrderItem::whereHas('order', fn($q) => $q->when(! empty($outletIds), fn($q2) => $q2->whereIn('outlet_id', $outletIds))->when($dateQuery || ($startDate && $endDate), $filterDate))->sum('quantity');
|
||||
$totalItemsCount = OrderItem::whereHas('order', fn ($q) => $q->when(! empty($outletIds), fn ($q2) => $q2->whereIn('outlet_id', $outletIds))->when($dateQuery || ($startDate && $endDate), $filterDate))->sum('quantity');
|
||||
|
||||
$aov = $totalOrdersCount > 0 ? $totalIncome / $totalOrdersCount : 0;
|
||||
$profitMargin = $totalIncome > 0 ? ($netProfit / $totalIncome) * 100 : 0;
|
||||
@ -225,7 +225,7 @@ public function render(): View
|
||||
->whereNotNull('customer_id')
|
||||
->groupBy('customer_id')
|
||||
->get();
|
||||
$repeatCustomers = $customerOrders->filter(fn($c) => $c->count > 1)->count();
|
||||
$repeatCustomers = $customerOrders->filter(fn ($c) => $c->count > 1)->count();
|
||||
$totalCustomers = $customerOrders->count();
|
||||
$loyaltyRate = $totalCustomers > 0 ? ($repeatCustomers / $totalCustomers) * 100 : 0;
|
||||
|
||||
@ -237,12 +237,12 @@ public function render(): View
|
||||
],
|
||||
$user->hasRole(['Developer', 'Owner']) ? [
|
||||
'title' => 'Profit Margin',
|
||||
'value' => round($profitMargin, 1) . '%',
|
||||
'value' => round($profitMargin, 1).'%',
|
||||
'description' => 'Persentase keuntungan bersih dibandingkan dengan total pendapatan.',
|
||||
] : null,
|
||||
[
|
||||
'title' => 'Loyalty Rate',
|
||||
'value' => round($loyaltyRate, 1) . '%',
|
||||
'value' => round($loyaltyRate, 1).'%',
|
||||
'description' => 'Persentase pelanggan yang melakukan pembelian lebih dari satu kali.',
|
||||
],
|
||||
[
|
||||
@ -295,27 +295,27 @@ public function render(): View
|
||||
'title' => 'Parfum Terjual',
|
||||
'value' => formatCurrencyNumber(
|
||||
OrderItem::where('orderable_type', Perfume::class)
|
||||
->whereHas('order', fn($q) => $q->when(! empty($outletIds), fn($q2) => $q2->whereIn('outlet_id', $outletIds))->when($dateQuery || ($startDate && $endDate), $filterDate))
|
||||
->whereHas('order', fn ($q) => $q->when(! empty($outletIds), fn ($q2) => $q2->whereIn('outlet_id', $outletIds))->when($dateQuery || ($startDate && $endDate), $filterDate))
|
||||
->sum('quantity')
|
||||
) . ' ml',
|
||||
).' ml',
|
||||
'description' => 'Total volume cairan parfum yang terjual dalam satuan mililiter (ml).',
|
||||
],
|
||||
[
|
||||
'title' => 'Produk Terjual',
|
||||
'value' => formatCurrencyNumber(
|
||||
OrderItem::where('orderable_type', Product::class)
|
||||
->whereHas('order', fn($q) => $q->when(! empty($outletIds), fn($q2) => $q2->whereIn('outlet_id', $outletIds))->when($dateQuery || ($startDate && $endDate), $filterDate))
|
||||
->whereHas('order', fn ($q) => $q->when(! empty($outletIds), fn ($q2) => $q2->whereIn('outlet_id', $outletIds))->when($dateQuery || ($startDate && $endDate), $filterDate))
|
||||
->sum('quantity')
|
||||
) . ' pcs',
|
||||
).' pcs',
|
||||
'description' => 'Total jumlah unit produk yang terjual dalam satuan pcs.',
|
||||
],
|
||||
[
|
||||
'title' => 'Botol Terjual',
|
||||
'value' => formatCurrencyNumber(
|
||||
OrderItem::where('orderable_type', Bottle::class)
|
||||
->whereHas('order', fn($q) => $q->when(! empty($outletIds), fn($q2) => $q2->whereIn('outlet_id', $outletIds))->when($dateQuery || ($startDate && $endDate), $filterDate))
|
||||
->whereHas('order', fn ($q) => $q->when(! empty($outletIds), fn ($q2) => $q2->whereIn('outlet_id', $outletIds))->when($dateQuery || ($startDate && $endDate), $filterDate))
|
||||
->sum('quantity')
|
||||
) . ' pcs',
|
||||
).' pcs',
|
||||
'description' => 'Total jumlah unit botol yang terjual dalam satuan pcs.',
|
||||
],
|
||||
]);
|
||||
@ -324,7 +324,7 @@ public function render(): View
|
||||
[
|
||||
'title' => 'Beban Toko',
|
||||
'value' => formatCurrencyNumber(
|
||||
Expense::when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
Expense::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->when($dateQuery || ($startDate && $endDate), $filterDate)
|
||||
->sum('amount'),
|
||||
'Rp'
|
||||
@ -345,7 +345,7 @@ public function render(): View
|
||||
[
|
||||
'title' => 'Penggajian',
|
||||
'value' => formatCurrencyNumber(
|
||||
Payroll::when(! empty($outletIds), fn($q) => $q->whereHas('user.outlets', fn($q2) => $q2->whereIn('outlets.id', $outletIds)))
|
||||
Payroll::when(! empty($outletIds), fn ($q) => $q->whereHas('user.outlets', fn ($q2) => $q2->whereIn('outlets.id', $outletIds)))
|
||||
->when($dateQuery || ($startDate && $endDate), $filterDate)
|
||||
->paid()
|
||||
->sum('total_salary'),
|
||||
@ -357,7 +357,7 @@ public function render(): View
|
||||
|
||||
$topPerfumes = OrderItem::where('orderable_type', Perfume::class)
|
||||
->whereHas('order', function ($q) use ($outletIds, $dateQuery, $startDate, $endDate, $filterDate) {
|
||||
$q->when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
$q->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->when($dateQuery || ($startDate && $endDate), $filterDate);
|
||||
})
|
||||
->with('orderable')
|
||||
@ -366,7 +366,7 @@ public function render(): View
|
||||
->orderByDesc('total_sales_value')
|
||||
->take(5)
|
||||
->get()
|
||||
->map(fn($item) => [
|
||||
->map(fn ($item) => [
|
||||
'name' => $item->orderable?->name ?? 'Unknown',
|
||||
'sale_price' => formatCurrencyNumber($item->orderable?->sale_price ?? 0, 'Rp'),
|
||||
'total_sold' => formatCurrencyNumber($item->total_sold),
|
||||
@ -376,7 +376,7 @@ public function render(): View
|
||||
|
||||
$topProducts = OrderItem::where('orderable_type', Product::class)
|
||||
->whereHas('order', function ($q) use ($outletIds, $dateQuery, $startDate, $endDate, $filterDate) {
|
||||
$q->when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
$q->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->when($dateQuery || ($startDate && $endDate), $filterDate);
|
||||
})
|
||||
->with('orderable')
|
||||
@ -385,7 +385,7 @@ public function render(): View
|
||||
->orderByDesc('total_sales_value')
|
||||
->take(5)
|
||||
->get()
|
||||
->map(fn($item) => [
|
||||
->map(fn ($item) => [
|
||||
'name' => $item->orderable?->name ?? 'Unknown',
|
||||
'sale_price' => formatCurrencyNumber($item->orderable?->sale_price ?? 0, 'Rp'),
|
||||
'total_sold' => formatCurrencyNumber($item->total_sold),
|
||||
@ -395,7 +395,7 @@ public function render(): View
|
||||
|
||||
$topBottles = OrderItem::where('orderable_type', Bottle::class)
|
||||
->whereHas('order', function ($q) use ($outletIds, $dateQuery, $startDate, $endDate, $filterDate) {
|
||||
$q->when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
$q->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->when($dateQuery || ($startDate && $endDate), $filterDate);
|
||||
})
|
||||
->with('orderable')
|
||||
@ -404,7 +404,7 @@ public function render(): View
|
||||
->orderByDesc('total_sales_value')
|
||||
->take(5)
|
||||
->get()
|
||||
->map(fn($item) => [
|
||||
->map(fn ($item) => [
|
||||
'name' => $item->orderable?->name ?? 'Unknown',
|
||||
'sale_price' => formatCurrencyNumber($item->orderable?->sale_price ?? 0, 'Rp'),
|
||||
'total_sold' => formatCurrencyNumber($item->total_sold),
|
||||
@ -415,9 +415,9 @@ public function render(): View
|
||||
// --- Chart Data Logic ---
|
||||
|
||||
// 1. Revenue & Profit Trend
|
||||
$trendDates = collect(range(29, 0))->map(fn($i) => now()->subDays($i)->format('Y-m-d'));
|
||||
$trendDates = collect(range(29, 0))->map(fn ($i) => now()->subDays($i)->format('Y-m-d'));
|
||||
$ordersTrend = Order::query()
|
||||
->when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->where('created_at', '>=', now()->subDays(30)->startOfDay())
|
||||
->select(DB::raw('DATE(created_at) as date'), DB::raw('SUM(total) as revenue'), DB::raw('SUM(total) - SUM(cogs) - SUM(discount) - SUM(COALESCE(voucher_discount,0)) as gross_profit'), DB::raw('count(*) as count'))
|
||||
->groupBy('date')
|
||||
@ -425,7 +425,7 @@ public function render(): View
|
||||
->keyBy('date');
|
||||
|
||||
$expensesTrend = Expense::query()
|
||||
->when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->where('created_at', '>=', now()->subDays(30)->startOfDay())
|
||||
->select(DB::raw('DATE(created_at) as date'), DB::raw('SUM(amount) as total_expense'))
|
||||
->groupBy('date')
|
||||
@ -433,9 +433,9 @@ public function render(): View
|
||||
->keyBy('date');
|
||||
|
||||
$this->revenueTrendChart = [
|
||||
'labels' => $trendDates->map(fn($d) => Carbon::parse($d)->format('d M'))->toArray(),
|
||||
'revenue' => $trendDates->map(fn($d) => (int) ($ordersTrend->get($d)?->revenue ?? 0))->toArray(),
|
||||
'profit' => $trendDates->map(fn($d) => (int) (($ordersTrend->get($d)?->gross_profit ?? 0) - ($expensesTrend->get($d)?->total_expense ?? 0)))->toArray(),
|
||||
'labels' => $trendDates->map(fn ($d) => Carbon::parse($d)->format('d M'))->toArray(),
|
||||
'revenue' => $trendDates->map(fn ($d) => (int) ($ordersTrend->get($d)?->revenue ?? 0))->toArray(),
|
||||
'profit' => $trendDates->map(fn ($d) => (int) (($ordersTrend->get($d)?->gross_profit ?? 0) - ($expensesTrend->get($d)?->total_expense ?? 0)))->toArray(),
|
||||
];
|
||||
|
||||
// 2. busiest hour
|
||||
@ -448,26 +448,26 @@ public function render(): View
|
||||
->keyBy('hour');
|
||||
|
||||
$this->hourlySalesChart = [
|
||||
'labels' => collect(range(0, 23))->map(fn($h) => str_pad($h, 2, '0', STR_PAD_LEFT) . ':00')->toArray(),
|
||||
'data' => collect(range(0, 23))->map(fn($h) => $hourlySales->get($h)?->count ?? 0)->toArray(),
|
||||
'labels' => collect(range(0, 23))->map(fn ($h) => str_pad($h, 2, '0', STR_PAD_LEFT).':00')->toArray(),
|
||||
'data' => collect(range(0, 23))->map(fn ($h) => $hourlySales->get($h)?->count ?? 0)->toArray(),
|
||||
];
|
||||
|
||||
// 3. Product Category Distribution
|
||||
$perfumeSales = OrderItem::where('orderable_type', Perfume::class)
|
||||
->whereHas('order', function ($q) use ($outletIds, $dateQuery, $startDate, $endDate, $filterDate) {
|
||||
$q->when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
$q->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->when($dateQuery || ($startDate && $endDate), $filterDate);
|
||||
})->sum(DB::raw('unit_price * quantity'));
|
||||
|
||||
$productSales = OrderItem::where('orderable_type', Product::class)
|
||||
->whereHas('order', function ($q) use ($outletIds, $dateQuery, $startDate, $endDate, $filterDate) {
|
||||
$q->when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
$q->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->when($dateQuery || ($startDate && $endDate), $filterDate);
|
||||
})->sum(DB::raw('unit_price * quantity'));
|
||||
|
||||
$bottleSales = OrderItem::where('orderable_type', Bottle::class)
|
||||
->whereHas('order', function ($q) use ($outletIds, $dateQuery, $startDate, $endDate, $filterDate) {
|
||||
$q->when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
$q->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->when($dateQuery || ($startDate && $endDate), $filterDate);
|
||||
})->sum(DB::raw('unit_price * quantity'));
|
||||
|
||||
@ -478,7 +478,7 @@ public function render(): View
|
||||
|
||||
// 4. Payment Method
|
||||
$paymentMethods = Payment::whereHas('order', function ($q) use ($outletIds, $dateQuery, $startDate, $endDate, $filterDate) {
|
||||
$q->when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
$q->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->when($dateQuery || ($startDate && $endDate), $filterDate);
|
||||
})
|
||||
->select('method', DB::raw('SUM(amount) as total'))
|
||||
@ -486,13 +486,13 @@ public function render(): View
|
||||
->get();
|
||||
|
||||
$this->paymentMethodChart = [
|
||||
'labels' => $paymentMethods->map(fn($pm) => $pm->method?->label() ?? 'Unknown')->toArray(),
|
||||
'labels' => $paymentMethods->map(fn ($pm) => $pm->method?->label() ?? 'Unknown')->toArray(),
|
||||
'data' => $paymentMethods->pluck('total')->toArray(),
|
||||
];
|
||||
|
||||
// 5. Outlet Performance
|
||||
$outletPerformance = Order::query()
|
||||
->when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->when($dateQuery || ($startDate && $endDate), $filterDate)
|
||||
->select('outlet_id', DB::raw('SUM(total) as revenue'))
|
||||
->groupBy('outlet_id')
|
||||
@ -500,7 +500,7 @@ public function render(): View
|
||||
->get();
|
||||
|
||||
$this->outletPerformanceChart = [
|
||||
'labels' => $outletPerformance->map(fn($op) => $op->outlet?->name ?? 'Outlet #' . $op->outlet_id)->toArray(),
|
||||
'labels' => $outletPerformance->map(fn ($op) => $op->outlet?->name ?? 'Outlet #'.$op->outlet_id)->toArray(),
|
||||
'data' => $outletPerformance->pluck('revenue')->toArray(),
|
||||
];
|
||||
|
||||
@ -546,7 +546,7 @@ public function render(): View
|
||||
|
||||
$this->dayOfWeekSalesChart = [
|
||||
'labels' => array_values($days),
|
||||
'data' => collect(range(1, 7))->map(fn($d) => (int) ($dayOfWeekSales->get($d)?->revenue ?? 0))->toArray(),
|
||||
'data' => collect(range(1, 7))->map(fn ($d) => (int) ($dayOfWeekSales->get($d)?->revenue ?? 0))->toArray(),
|
||||
];
|
||||
|
||||
// 8. Customer Type (Member vs Guest)
|
||||
@ -560,13 +560,13 @@ public function render(): View
|
||||
|
||||
// 9. Transaction Trend (Daily Count)
|
||||
$this->transactionTrendChart = [
|
||||
'labels' => $trendDates->map(fn($d) => Carbon::parse($d)->format('d M'))->toArray(),
|
||||
'data' => $trendDates->map(fn($d) => (int) ($ordersTrend->get($d)?->count ?? 0))->toArray(),
|
||||
'labels' => $trendDates->map(fn ($d) => Carbon::parse($d)->format('d M'))->toArray(),
|
||||
'data' => $trendDates->map(fn ($d) => (int) ($ordersTrend->get($d)?->count ?? 0))->toArray(),
|
||||
];
|
||||
|
||||
// 10. AOV Trend (Daily Revenue / Daily Count)
|
||||
$this->aovTrendChart = [
|
||||
'labels' => $trendDates->map(fn($d) => Carbon::parse($d)->format('d M'))->toArray(),
|
||||
'labels' => $trendDates->map(fn ($d) => Carbon::parse($d)->format('d M'))->toArray(),
|
||||
'data' => $trendDates->map(function ($d) use ($ordersTrend) {
|
||||
$rev = $ordersTrend->get($d)?->revenue ?? 0;
|
||||
$count = $ordersTrend->get($d)?->count ?? 0;
|
||||
@ -595,7 +595,7 @@ public function render(): View
|
||||
|
||||
// 14. Voucher Usage Trend
|
||||
$vouchersTrend = Order::query()
|
||||
->when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->where('created_at', '>=', now()->subDays(30)->startOfDay())
|
||||
->whereNotNull('voucher_id')
|
||||
->select(DB::raw('DATE(created_at) as date'), DB::raw('count(*) as count'))
|
||||
@ -604,8 +604,8 @@ public function render(): View
|
||||
->keyBy('date');
|
||||
|
||||
$this->voucherUsageTrendChart = [
|
||||
'labels' => $trendDates->map(fn($d) => Carbon::parse($d)->format('d M'))->toArray(),
|
||||
'data' => $trendDates->map(fn($d) => (int) ($vouchersTrend->get($d)?->count ?? 0))->toArray(),
|
||||
'labels' => $trendDates->map(fn ($d) => Carbon::parse($d)->format('d M'))->toArray(),
|
||||
'data' => $trendDates->map(fn ($d) => (int) ($vouchersTrend->get($d)?->count ?? 0))->toArray(),
|
||||
];
|
||||
|
||||
$this->countingData = $countingData;
|
||||
|
||||
@ -59,53 +59,53 @@ public function render(): View
|
||||
$outletIds = $user->outlets()->pluck('outlets.id')->toArray();
|
||||
}
|
||||
|
||||
$todayOrder = Order::when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
$todayOrder = Order::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->today()
|
||||
->count();
|
||||
|
||||
$yesterdayOrder = Order::when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
$yesterdayOrder = Order::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->yesterday()
|
||||
->count();
|
||||
|
||||
$todayIncome = Order::when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
$todayIncome = Order::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->today()
|
||||
->sum('total');
|
||||
|
||||
$yesterdayIncome = Order::when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
$yesterdayIncome = Order::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->yesterday()
|
||||
->sum('total');
|
||||
|
||||
$todayCogs = Order::when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
$todayCogs = Order::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->today()
|
||||
->sum('cogs');
|
||||
|
||||
$yesterdayCogs = Order::when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
$yesterdayCogs = Order::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->yesterday()
|
||||
->sum('cogs');
|
||||
|
||||
$todayDiscount = Order::when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
$todayDiscount = Order::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->today()
|
||||
->sum(DB::raw('discount + COALESCE(voucher_discount, 0)'));
|
||||
|
||||
$yesterdayDiscount = Order::when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
$yesterdayDiscount = Order::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->yesterday()
|
||||
->sum(DB::raw('discount + COALESCE(voucher_discount, 0)'));
|
||||
|
||||
$todayVoucher = Order::when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
$todayVoucher = Order::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->today()
|
||||
->whereNotNull('voucher_id')
|
||||
->count();
|
||||
|
||||
$yesterdayVoucher = Order::when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
$yesterdayVoucher = Order::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->yesterday()
|
||||
->whereNotNull('voucher_id')
|
||||
->count();
|
||||
|
||||
$todayExpense = Expense::when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
$todayExpense = Expense::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->today()
|
||||
->sum('amount');
|
||||
|
||||
$yesterdayExpense = Expense::when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
$yesterdayExpense = Expense::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->yesterday()
|
||||
->sum('amount');
|
||||
|
||||
@ -122,21 +122,21 @@ public function render(): View
|
||||
$todaySoldPerfume = OrderItem::where('orderable_type', Perfume::class)
|
||||
->whereHas(
|
||||
'order',
|
||||
fn($q) => $q->today()->when(! empty($outletIds), fn($q2) => $q2->whereIn('outlet_id', $outletIds))
|
||||
fn ($q) => $q->today()->when(! empty($outletIds), fn ($q2) => $q2->whereIn('outlet_id', $outletIds))
|
||||
)
|
||||
->sum('quantity');
|
||||
|
||||
$yesterdaySoldPerfume = OrderItem::where('orderable_type', Perfume::class)
|
||||
->whereHas(
|
||||
'order',
|
||||
fn($q) => $q->yesterday()->when(! empty($outletIds), fn($q2) => $q2->whereIn('outlet_id', $outletIds))
|
||||
fn ($q) => $q->yesterday()->when(! empty($outletIds), fn ($q2) => $q2->whereIn('outlet_id', $outletIds))
|
||||
)
|
||||
->sum('quantity');
|
||||
|
||||
$todayTopPerfume = OrderItem::where('orderable_type', Perfume::class)
|
||||
->whereHas(
|
||||
'order',
|
||||
fn($q) => $q->today()->when(! empty($outletIds), fn($q2) => $q2->whereIn('outlet_id', $outletIds))
|
||||
fn ($q) => $q->today()->when(! empty($outletIds), fn ($q2) => $q2->whereIn('outlet_id', $outletIds))
|
||||
)
|
||||
->select('orderable_id', 'orderable_type', DB::raw('SUM(quantity) as total_sold'))
|
||||
->groupBy('orderable_id', 'orderable_type')
|
||||
@ -147,7 +147,7 @@ public function render(): View
|
||||
$yesterdayTopPerfume = OrderItem::where('orderable_type', Perfume::class)
|
||||
->whereHas(
|
||||
'order',
|
||||
fn($q) => $q->yesterday()->when(! empty($outletIds), fn($q2) => $q2->whereIn('outlet_id', $outletIds))
|
||||
fn ($q) => $q->yesterday()->when(! empty($outletIds), fn ($q2) => $q2->whereIn('outlet_id', $outletIds))
|
||||
)
|
||||
->select('orderable_id', 'orderable_type', DB::raw('SUM(quantity) as total_sold'))
|
||||
->groupBy('orderable_id', 'orderable_type')
|
||||
@ -172,26 +172,26 @@ public function render(): View
|
||||
$hourFunc = DB::getDriverName() === 'sqlite' ? "CAST(strftime('%H', created_at) AS INTEGER)" : 'HOUR(created_at)';
|
||||
|
||||
$todayHourly = Order::whereDate('created_at', now())
|
||||
->when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->select(DB::raw("$hourFunc as hour"), DB::raw('count(*) as count'))
|
||||
->groupBy('hour')
|
||||
->get()->keyBy('hour');
|
||||
|
||||
$yesterdayHourly = Order::whereDate('created_at', now()->yesterday())
|
||||
->when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->select(DB::raw("$hourFunc as hour"), DB::raw('count(*) as count'))
|
||||
->groupBy('hour')
|
||||
->get()->keyBy('hour');
|
||||
|
||||
$this->hourlyComparisonChart = [
|
||||
'labels' => collect(range(0, 23))->map(fn($h) => str_pad($h, 2, '0', STR_PAD_LEFT) . ':00')->toArray(),
|
||||
'today' => collect(range(0, 23))->map(fn($h) => $todayHourly->get($h)?->count ?? 0)->toArray(),
|
||||
'yesterday' => collect(range(0, 23))->map(fn($h) => $yesterdayHourly->get($h)?->count ?? 0)->toArray(),
|
||||
'labels' => collect(range(0, 23))->map(fn ($h) => str_pad($h, 2, '0', STR_PAD_LEFT).':00')->toArray(),
|
||||
'today' => collect(range(0, 23))->map(fn ($h) => $todayHourly->get($h)?->count ?? 0)->toArray(),
|
||||
'yesterday' => collect(range(0, 23))->map(fn ($h) => $yesterdayHourly->get($h)?->count ?? 0)->toArray(),
|
||||
];
|
||||
|
||||
// 2. Today's Distribution (Category)
|
||||
$categorySales = OrderItem::whereHas('order', function ($q) use ($outletIds) {
|
||||
$q->today()->when(! empty($outletIds), fn($q2) => $q2->whereIn('outlet_id', $outletIds));
|
||||
$q->today()->when(! empty($outletIds), fn ($q2) => $q2->whereIn('outlet_id', $outletIds));
|
||||
})
|
||||
->select('orderable_type', DB::raw('SUM(unit_price * quantity) as total'))
|
||||
->groupBy('orderable_type')
|
||||
@ -208,25 +208,25 @@ public function render(): View
|
||||
|
||||
// 3. Today's Payment Methods
|
||||
$paymentMethods = Payment::whereHas('order', function ($q) use ($outletIds) {
|
||||
$q->today()->when(! empty($outletIds), fn($q2) => $q2->whereIn('outlet_id', $outletIds));
|
||||
$q->today()->when(! empty($outletIds), fn ($q2) => $q2->whereIn('outlet_id', $outletIds));
|
||||
})
|
||||
->select('method', DB::raw('SUM(amount) as total'))
|
||||
->groupBy('method')
|
||||
->get();
|
||||
|
||||
$this->todayPaymentChart = [
|
||||
'labels' => $paymentMethods->map(fn($pm) => $pm->method?->label() ?? 'Unknown')->toArray(),
|
||||
'labels' => $paymentMethods->map(fn ($pm) => $pm->method?->label() ?? 'Unknown')->toArray(),
|
||||
'data' => $paymentMethods->pluck('total')->toArray(),
|
||||
];
|
||||
|
||||
// 4. Today's Customer Type
|
||||
$todayMemberOrders = Order::query()
|
||||
->today()
|
||||
->when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->whereNotNull('customer_id')->count();
|
||||
$todayGuestOrders = Order::query()
|
||||
->today()
|
||||
->when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->whereNull('customer_id')->count();
|
||||
|
||||
$this->todayCustomerTypeChart = [
|
||||
@ -236,7 +236,7 @@ public function render(): View
|
||||
|
||||
// 5. Today's Top 5 Selling Items
|
||||
$todayTopItems = OrderItem::whereHas('order', function ($q) use ($outletIds) {
|
||||
$q->today()->when(! empty($outletIds), fn($q2) => $q2->whereIn('outlet_id', $outletIds));
|
||||
$q->today()->when(! empty($outletIds), fn ($q2) => $q2->whereIn('outlet_id', $outletIds));
|
||||
})
|
||||
->with('orderable')
|
||||
->select('orderable_id', 'orderable_type', DB::raw('SUM(quantity) as total_sold'))
|
||||
@ -246,14 +246,14 @@ public function render(): View
|
||||
->get();
|
||||
|
||||
$this->todayTopSellingChart = [
|
||||
'labels' => $todayTopItems->map(fn($item) => $item->orderable?->name ?? 'Unknown')->toArray(),
|
||||
'data' => $todayTopItems->pluck('total_sold')->map(fn($val) => (int) $val)->toArray(),
|
||||
'labels' => $todayTopItems->map(fn ($item) => $item->orderable?->name ?? 'Unknown')->toArray(),
|
||||
'data' => $todayTopItems->pluck('total_sold')->map(fn ($val) => (int) $val)->toArray(),
|
||||
];
|
||||
|
||||
// 6. Today's Top 5 Customers
|
||||
$todayTopCustomers = Order::query()
|
||||
->today()
|
||||
->when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->whereNotNull('customer_id')
|
||||
->select('customer_id', DB::raw('SUM(total) as total_spent'))
|
||||
->groupBy('customer_id')
|
||||
@ -263,18 +263,18 @@ public function render(): View
|
||||
->get();
|
||||
|
||||
$this->todayTopCustomersChart = [
|
||||
'labels' => $todayTopCustomers->map(fn($o) => $o->customer?->user?->employee?->full_name ?? $o->customer?->user?->name ?? 'Customer #' . $o->customer_id)->toArray(),
|
||||
'data' => $todayTopCustomers->pluck('total_spent')->map(fn($val) => (int) $val)->toArray(),
|
||||
'labels' => $todayTopCustomers->map(fn ($o) => $o->customer?->user?->employee?->full_name ?? $o->customer?->user?->name ?? 'Customer #'.$o->customer_id)->toArray(),
|
||||
'data' => $todayTopCustomers->pluck('total_spent')->map(fn ($val) => (int) $val)->toArray(),
|
||||
];
|
||||
|
||||
// 7. Today's Discount Distribution (With Discount vs Regular)
|
||||
$withDiscount = Order::query()
|
||||
->today()
|
||||
->when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->where('discount', '>', 0)->count();
|
||||
$noDiscount = Order::query()
|
||||
->today()
|
||||
->when(! empty($outletIds), fn($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
|
||||
->where('discount', '<=', 0)->count();
|
||||
|
||||
$this->todayDiscountChart = [
|
||||
@ -288,7 +288,7 @@ public function render(): View
|
||||
'value' => formatCurrencyNumber($todayAov, 'Rp'),
|
||||
'previous' => formatCurrencyNumber($yesterdayAov, 'Rp'),
|
||||
'trend' => $yesterdayAov > 0
|
||||
? round((($todayAov - $yesterdayAov) / $yesterdayAov) * 100, 1) . '%'
|
||||
? round((($todayAov - $yesterdayAov) / $yesterdayAov) * 100, 1).'%'
|
||||
: '∞%',
|
||||
'trendUp' => $todayAov > $yesterdayAov,
|
||||
'description' => 'Rata-rata nilai belanja pelanggan dalam satu kali checkout.',
|
||||
@ -296,10 +296,10 @@ public function render(): View
|
||||
|
||||
$user->hasRole(['Developer', 'Owner']) ? [
|
||||
'title' => 'Profit Margin',
|
||||
'value' => round($todayProfitMargin, 1) . '%',
|
||||
'previous' => round($yesterdayProfitMargin, 1) . '%',
|
||||
'value' => round($todayProfitMargin, 1).'%',
|
||||
'previous' => round($yesterdayProfitMargin, 1).'%',
|
||||
'trend' => $yesterdayProfitMargin > 0
|
||||
? round($todayProfitMargin - $yesterdayProfitMargin, 1) . '%'
|
||||
? round($todayProfitMargin - $yesterdayProfitMargin, 1).'%'
|
||||
: '∞%',
|
||||
'trendUp' => $todayProfitMargin > $yesterdayProfitMargin,
|
||||
'description' => 'Persentase keuntungan bersih dibandingkan dengan total pendapatan.',
|
||||
@ -310,7 +310,7 @@ public function render(): View
|
||||
'value' => $todayOrder,
|
||||
'previous' => $yesterdayOrder,
|
||||
'trend' => $yesterdayOrder > 0
|
||||
? round((($todayOrder - $yesterdayOrder) / $yesterdayOrder) * 100, 1) . '%'
|
||||
? round((($todayOrder - $yesterdayOrder) / $yesterdayOrder) * 100, 1).'%'
|
||||
: '∞%',
|
||||
'trendUp' => $todayOrder > $yesterdayOrder,
|
||||
'description' => 'Total frekuensi transaksi atau pesanan yang masuk selama periode ini.',
|
||||
@ -321,7 +321,7 @@ public function render(): View
|
||||
'value' => formatCurrencyNumber($todayIncome, 'Rp'),
|
||||
'previous' => formatCurrencyNumber($yesterdayIncome, 'Rp'),
|
||||
'trend' => $yesterdayIncome > 0
|
||||
? round((($todayIncome - $yesterdayIncome) / $yesterdayIncome) * 100, 1) . '%'
|
||||
? round((($todayIncome - $yesterdayIncome) / $yesterdayIncome) * 100, 1).'%'
|
||||
: '∞%',
|
||||
'trendUp' => $todayIncome > $yesterdayIncome,
|
||||
'formatted' => formatCurrencyNumber($todayIncome, 'Rp'),
|
||||
@ -333,7 +333,7 @@ public function render(): View
|
||||
'value' => formatCurrencyNumber($todayCogs, 'Rp'),
|
||||
'previous' => formatCurrencyNumber($yesterdayCogs, 'Rp'),
|
||||
'trend' => $yesterdayCogs > 0
|
||||
? round((($todayCogs - $yesterdayCogs) / $yesterdayCogs) * 100, 1) . '%'
|
||||
? round((($todayCogs - $yesterdayCogs) / $yesterdayCogs) * 100, 1).'%'
|
||||
: '∞%',
|
||||
'trendUp' => $todayCogs > $yesterdayCogs,
|
||||
'formatted' => formatCurrencyNumber($todayCogs, 'Rp'),
|
||||
@ -342,10 +342,10 @@ public function render(): View
|
||||
|
||||
[
|
||||
'title' => 'Member Baru',
|
||||
'value' => $todayNewMembers . ' Orang',
|
||||
'previous' => $yesterdayNewMembers . ' Orang',
|
||||
'value' => $todayNewMembers.' Orang',
|
||||
'previous' => $yesterdayNewMembers.' Orang',
|
||||
'trend' => $yesterdayNewMembers > 0
|
||||
? round((($todayNewMembers - $yesterdayNewMembers) / $yesterdayNewMembers) * 100, 1) . '%'
|
||||
? round((($todayNewMembers - $yesterdayNewMembers) / $yesterdayNewMembers) * 100, 1).'%'
|
||||
: '∞%',
|
||||
'trendUp' => $todayNewMembers > $yesterdayNewMembers,
|
||||
'description' => 'Jumlah pelanggan baru yang mendaftar menjadi member selama periode ini.',
|
||||
@ -356,7 +356,7 @@ public function render(): View
|
||||
'value' => formatCurrencyNumber($todayDiscount, 'Rp'),
|
||||
'previous' => formatCurrencyNumber($yesterdayDiscount, 'Rp'),
|
||||
'trend' => $yesterdayDiscount > 0
|
||||
? round((($todayDiscount - $yesterdayDiscount) / $yesterdayDiscount) * 100, 1) . '%'
|
||||
? round((($todayDiscount - $yesterdayDiscount) / $yesterdayDiscount) * 100, 1).'%'
|
||||
: '∞%',
|
||||
'trendUp' => $todayDiscount > $yesterdayDiscount,
|
||||
'formatted' => formatCurrencyNumber($todayDiscount, 'Rp'),
|
||||
@ -368,7 +368,7 @@ public function render(): View
|
||||
'value' => $todayVoucher,
|
||||
'previous' => $yesterdayVoucher,
|
||||
'trend' => $yesterdayVoucher > 0
|
||||
? round((($todayVoucher - $yesterdayVoucher) / $yesterdayVoucher) * 100, 1) . '%'
|
||||
? round((($todayVoucher - $yesterdayVoucher) / $yesterdayVoucher) * 100, 1).'%'
|
||||
: '∞%',
|
||||
'trendUp' => $todayVoucher > $yesterdayVoucher,
|
||||
'description' => 'Jumlah transaksi yang menggunakan voucher diskon selama periode ini.',
|
||||
@ -379,7 +379,7 @@ public function render(): View
|
||||
'value' => formatCurrencyNumber($todayExpense, 'Rp'),
|
||||
'previous' => formatCurrencyNumber($yesterdayExpense, 'Rp'),
|
||||
'trend' => $yesterdayExpense > 0
|
||||
? round((($todayExpense - $yesterdayExpense) / $yesterdayExpense) * 100, 1) . '%'
|
||||
? round((($todayExpense - $yesterdayExpense) / $yesterdayExpense) * 100, 1).'%'
|
||||
: '∞%',
|
||||
'trendUp' => $todayExpense > $yesterdayExpense,
|
||||
'description' => 'Total biaya operasional atau pengeluaran toko yang tercatat selama periode ini.',
|
||||
@ -390,7 +390,7 @@ public function render(): View
|
||||
'value' => formatCurrencyNumber($todayPurchase, 'Rp'),
|
||||
'previous' => formatCurrencyNumber($yesterdayPurchase, 'Rp'),
|
||||
'trend' => $yesterdayPurchase > 0
|
||||
? round((($todayPurchase - $yesterdayPurchase) / $yesterdayPurchase) * 100, 1) . '%'
|
||||
? round((($todayPurchase - $yesterdayPurchase) / $yesterdayPurchase) * 100, 1).'%'
|
||||
: '∞%',
|
||||
'trendUp' => $todayPurchase > $yesterdayPurchase,
|
||||
'description' => 'Total pengeluaran untuk pembelian stok barang atau inventaris selama periode ini.',
|
||||
@ -401,7 +401,7 @@ public function render(): View
|
||||
'value' => formatCurrencyNumber($todayGrossProfit, 'Rp'),
|
||||
'previous' => formatCurrencyNumber($yesterdayGrossProfit, 'Rp'),
|
||||
'trend' => $yesterdayGrossProfit > 0
|
||||
? round((($todayGrossProfit - $yesterdayGrossProfit) / $yesterdayGrossProfit) * 100, 1) . '%'
|
||||
? round((($todayGrossProfit - $yesterdayGrossProfit) / $yesterdayGrossProfit) * 100, 1).'%'
|
||||
: '∞%',
|
||||
'trendUp' => $todayGrossProfit > $yesterdayGrossProfit,
|
||||
'description' => 'Pendapatan dikurangi dengan HPP dan Diskon.',
|
||||
@ -412,7 +412,7 @@ public function render(): View
|
||||
'value' => formatCurrencyNumber($todayNetProfit, 'Rp'),
|
||||
'previous' => formatCurrencyNumber($yesterdayNetProfit, 'Rp'),
|
||||
'trend' => $yesterdayNetProfit > 0
|
||||
? round((($todayNetProfit - $yesterdayNetProfit) / $yesterdayNetProfit) * 100, 1) . '%'
|
||||
? round((($todayNetProfit - $yesterdayNetProfit) / $yesterdayNetProfit) * 100, 1).'%'
|
||||
: '∞%',
|
||||
'trendUp' => $todayNetProfit > $yesterdayNetProfit,
|
||||
'description' => 'Laba kotor dikurangi dengan beban operasional dan gaji.',
|
||||
@ -420,10 +420,10 @@ public function render(): View
|
||||
|
||||
[
|
||||
'title' => 'Parfum Terjual',
|
||||
'value' => $todaySoldPerfume . ' ml',
|
||||
'previous' => $yesterdaySoldPerfume . ' ml',
|
||||
'value' => $todaySoldPerfume.' ml',
|
||||
'previous' => $yesterdaySoldPerfume.' ml',
|
||||
'trend' => $yesterdaySoldPerfume > 0
|
||||
? round((($todaySoldPerfume - $yesterdaySoldPerfume) / $yesterdaySoldPerfume) * 100, 1) . '%'
|
||||
? round((($todaySoldPerfume - $yesterdaySoldPerfume) / $yesterdaySoldPerfume) * 100, 1).'%'
|
||||
: '∞%',
|
||||
'trendUp' => $todaySoldPerfume > $yesterdaySoldPerfume,
|
||||
'description' => 'Total volume cairan parfum yang terjual dalam satuan mililiter (ml).',
|
||||
@ -431,10 +431,10 @@ public function render(): View
|
||||
|
||||
[
|
||||
'title' => 'Top Parfum',
|
||||
'value' => optional($todayTopPerfume?->orderable)->name . ' (' . $todayTopPerfume?->total_sold . 'ml)' ?? '-',
|
||||
'previous' => optional($yesterdayTopPerfume?->orderable)->name . '(' . $yesterdayTopPerfume?->total_sold . 'ml)' ?? '-',
|
||||
'value' => optional($todayTopPerfume?->orderable)->name.' ('.$todayTopPerfume?->total_sold.'ml)' ?? '-',
|
||||
'previous' => optional($yesterdayTopPerfume?->orderable)->name.'('.$yesterdayTopPerfume?->total_sold.'ml)' ?? '-',
|
||||
'trend' => ($yesterdayTopPerfume && $yesterdayTopPerfume->total_sold > 0)
|
||||
? round((($todayTopPerfume?->total_sold - $yesterdayTopPerfume?->total_sold) / $yesterdayTopPerfume?->total_sold) * 100, 1) . '%'
|
||||
? round((($todayTopPerfume?->total_sold - $yesterdayTopPerfume?->total_sold) / $yesterdayTopPerfume?->total_sold) * 100, 1).'%'
|
||||
: '∞%',
|
||||
'trendUp' => ($todayTopPerfume?->total_sold ?? 0) > ($yesterdayTopPerfume?->total_sold ?? 0),
|
||||
'description' => 'Nama varian parfum yang paling banyak terjual hari ini.',
|
||||
|
||||
@ -37,7 +37,7 @@ public static function purchase(
|
||||
self::logWarehouse(
|
||||
logName: self::LOG_PURCHASE,
|
||||
event: 'increase',
|
||||
description: "Purchase #{$purchase->hash_id}: Added {$quantity} stock to Warehouse.",
|
||||
description: "Pembelian #{$purchase->hash_id}: Menambah {$quantity} stok ke Gudang.",
|
||||
performedOn: $product,
|
||||
warehouse: $purchase->warehouse,
|
||||
quantityChange: $quantity,
|
||||
@ -69,7 +69,7 @@ public static function distribution(
|
||||
self::logWarehouse(
|
||||
logName: self::LOG_DISTRIBUTION,
|
||||
event: 'decrease',
|
||||
description: "Restock #{$restock->hash_id}: Moved {$quantity} stock from Warehouse to Outlet.",
|
||||
description: "Restock #{$restock->hash_id}: Memindahkan {$quantity} stok dari Gudang ke Outlet.",
|
||||
performedOn: $product,
|
||||
warehouse: $restock->warehouse,
|
||||
quantityChange: -1 * abs($quantity),
|
||||
@ -87,7 +87,7 @@ public static function distribution(
|
||||
self::logOutlet(
|
||||
logName: self::LOG_DISTRIBUTION,
|
||||
event: 'increase',
|
||||
description: "Restock #{$restock->hash_id}: Received {$quantity} stock from Warehouse.",
|
||||
description: "Restock #{$restock->hash_id}: Menerima {$quantity} stok dari Gudang.",
|
||||
performedOn: $product,
|
||||
outlet: $restock->outlet,
|
||||
quantityChange: abs($quantity),
|
||||
@ -116,7 +116,7 @@ public static function sale(
|
||||
self::logOutlet(
|
||||
logName: self::LOG_SALE,
|
||||
event: 'decrease',
|
||||
description: "Order #{$order->hash_id}: Sold {$quantity} items.",
|
||||
description: "Pesanan #{$order->hash_id}: Menjual {$quantity} item.",
|
||||
performedOn: $product,
|
||||
outlet: $order->outlet,
|
||||
quantityChange: -1 * abs($quantity),
|
||||
@ -147,7 +147,7 @@ public static function stockOpname(
|
||||
self::logOutlet(
|
||||
logName: self::LOG_STOCK_OPNAME,
|
||||
event: $event,
|
||||
description: "Stock Opname #{$stockOpname->hash_id}: Adjusted stock by {$quantityChange}.",
|
||||
description: "Stock Opname #{$stockOpname->hash_id}: Penyesuaian stok sebesar {$quantityChange}.",
|
||||
performedOn: $product,
|
||||
outlet: $stockOpname->outlet,
|
||||
quantityChange: $quantityChange,
|
||||
@ -180,7 +180,7 @@ public static function transfer(
|
||||
self::logOutlet(
|
||||
logName: self::LOG_TRANSFER,
|
||||
event: 'decrease',
|
||||
description: "Stock Transfer #{$stockTransfer->hash_id}: Moved {$quantity} stock from Source to Destination Outlet.",
|
||||
description: "Transfer Stok #{$stockTransfer->hash_id}: Memindahkan {$quantity} stok dari Outlet Asal ke Outlet Tujuan.",
|
||||
performedOn: $product,
|
||||
outlet: $stockTransfer->sourceOutlet,
|
||||
quantityChange: -1 * abs($quantity),
|
||||
@ -198,7 +198,7 @@ public static function transfer(
|
||||
self::logOutlet(
|
||||
logName: self::LOG_TRANSFER,
|
||||
event: 'increase',
|
||||
description: "Stock Transfer #{$stockTransfer->hash_id}: Received {$quantity} stock from Source Outlet.",
|
||||
description: "Transfer Stok #{$stockTransfer->hash_id}: Menerima {$quantity} stok dari Outlet Asal.",
|
||||
performedOn: $product,
|
||||
outlet: $stockTransfer->destinationOutlet,
|
||||
quantityChange: abs($quantity),
|
||||
@ -213,6 +213,55 @@ public static function transfer(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic log method that delegates to logOutlet or logWarehouse.
|
||||
*/
|
||||
public static function log(
|
||||
string $logName,
|
||||
string $event,
|
||||
string $description,
|
||||
Model $performedOn,
|
||||
?Model $outlet = null,
|
||||
?Warehouse $warehouse = null,
|
||||
int $quantityChange = 0,
|
||||
int $previousStock = 0,
|
||||
int $newStock = 0,
|
||||
array $extraProperties = [],
|
||||
?Model $causedBy = null
|
||||
): void {
|
||||
if ($warehouse) {
|
||||
self::logWarehouse(
|
||||
logName: $logName,
|
||||
event: $event,
|
||||
description: $description,
|
||||
performedOn: $performedOn,
|
||||
warehouse: $warehouse,
|
||||
quantityChange: $quantityChange,
|
||||
previousStock: $previousStock,
|
||||
newStock: $newStock,
|
||||
extraProperties: $extraProperties,
|
||||
causedBy: $causedBy
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($outlet) {
|
||||
self::logOutlet(
|
||||
logName: $logName,
|
||||
event: $event,
|
||||
description: $description,
|
||||
performedOn: $performedOn,
|
||||
outlet: $outlet,
|
||||
quantityChange: $quantityChange,
|
||||
previousStock: $previousStock,
|
||||
newStock: $newStock,
|
||||
extraProperties: $extraProperties,
|
||||
causedBy: $causedBy
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log stock related activity for Outlet.
|
||||
*/
|
||||
@ -236,7 +285,7 @@ public static function logOutlet(
|
||||
'previous_stock' => $previousStock,
|
||||
'new_stock' => $newStock,
|
||||
'location_type' => 'outlet',
|
||||
'location_name' => $outlet->name ?? 'Unknown Outlet',
|
||||
'location_name' => $outlet->name ?? 'Outlet Tidak Diketahui',
|
||||
], $extraProperties);
|
||||
|
||||
activity($logName)
|
||||
@ -270,7 +319,7 @@ public static function logWarehouse(
|
||||
'previous_stock' => $previousStock,
|
||||
'new_stock' => $newStock,
|
||||
'location_type' => 'warehouse',
|
||||
'location_name' => $warehouse->name ?? 'Unknown Warehouse',
|
||||
'location_name' => $warehouse->name ?? 'Gudang Tidak Diketahui',
|
||||
], $extraProperties);
|
||||
|
||||
activity($logName)
|
||||
|
||||
@ -128,7 +128,7 @@ public function reverseStockTransfer($warehouse, $outlet, $item): void
|
||||
StockActivityLogService::logWarehouse(
|
||||
logName: StockActivityLogService::LOG_DISTRIBUTION,
|
||||
event: 'increase',
|
||||
description: "Restock #{$restock->hash_id} Deleted: Returned {$quantity} stock to Warehouse.",
|
||||
description: "Restock #{$restock->hash_id} Dihapus: Mengembalikan {$quantity} stok ke Gudang.",
|
||||
performedOn: $item->restockable,
|
||||
warehouse: $warehouse,
|
||||
quantityChange: $quantity,
|
||||
@ -156,7 +156,7 @@ public function reverseStockTransfer($warehouse, $outlet, $item): void
|
||||
StockActivityLogService::logOutlet(
|
||||
logName: StockActivityLogService::LOG_DISTRIBUTION,
|
||||
event: 'decrease',
|
||||
description: "Restock #{$restock->hash_id} Deleted: Removed {$quantity} stock from Outlet.",
|
||||
description: "Restock #{$restock->hash_id} Dihapus: Menghapus {$quantity} stok dari Outlet.",
|
||||
performedOn: $item->restockable,
|
||||
outlet: $outlet,
|
||||
quantityChange: -1 * abs($quantity),
|
||||
|
||||
@ -128,7 +128,7 @@ public function reverseStockTransferBetweenOutlets($sourceOutlet, $destinationOu
|
||||
StockActivityLogService::logOutlet(
|
||||
logName: StockActivityLogService::LOG_TRANSFER,
|
||||
event: 'increase',
|
||||
description: "Stock Transfer #{$stockTransfer->hash_id} Deleted: Returned {$quantity} stock to source outlet.",
|
||||
description: "Transfer Stok #{$stockTransfer->hash_id} Dihapus: Mengembalikan {$quantity} stok ke outlet asal.",
|
||||
performedOn: $item->transferable,
|
||||
outlet: $sourceOutlet,
|
||||
quantityChange: $quantity,
|
||||
@ -156,7 +156,7 @@ public function reverseStockTransferBetweenOutlets($sourceOutlet, $destinationOu
|
||||
StockActivityLogService::logOutlet(
|
||||
logName: StockActivityLogService::LOG_TRANSFER,
|
||||
event: 'decrease',
|
||||
description: "Stock Transfer #{$stockTransfer->hash_id} Deleted: Removed {$quantity} stock from destination outlet.",
|
||||
description: "Transfer Stok #{$stockTransfer->hash_id} Dihapus: Menghapus {$quantity} stok dari outlet tujuan.",
|
||||
performedOn: $item->transferable,
|
||||
outlet: $destinationOutlet,
|
||||
quantityChange: -1 * abs($quantity),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user