581 lines
24 KiB
PHP
581 lines
24 KiB
PHP
<?php
|
|
|
|
namespace App\Services\System;
|
|
|
|
use App\Enums\OrderStatus;
|
|
use App\Models\Attendance;
|
|
use App\Models\CashAccount;
|
|
use App\Models\CashTransaction;
|
|
use App\Models\Employee;
|
|
use App\Models\EmployeeAdvance;
|
|
use App\Models\Expense;
|
|
use App\Models\LeaveRequest;
|
|
use App\Models\Order;
|
|
use App\Models\OrderItem;
|
|
use App\Models\ProductVariant;
|
|
use App\Models\Purchase;
|
|
use App\Models\RawMaterialPrice;
|
|
use App\Models\User;
|
|
use Carbon\Carbon;
|
|
|
|
class AnalysisService
|
|
{
|
|
public function getAttendance(): array
|
|
{
|
|
$totalEmployees = Employee::query()->count();
|
|
$today = Carbon::today();
|
|
|
|
$present = Attendance::query()
|
|
->where('attendance_date', $today)
|
|
->distinct('employee_id')
|
|
->count('employee_id');
|
|
|
|
$onLeave = LeaveRequest::query()
|
|
->approved()
|
|
->where('start_date', '<=', $today)
|
|
->where('end_date', '>=', $today)
|
|
->distinct('employee_id')
|
|
->count('employee_id');
|
|
|
|
$absent = max(0, $totalEmployees - $present - $onLeave);
|
|
|
|
return [
|
|
'total_employees' => $totalEmployees,
|
|
'percentage' => $totalEmployees > 0 ? round($present / $totalEmployees * 100) : 0,
|
|
'present' => $present,
|
|
'absent' => $absent,
|
|
'on_leave' => $onLeave,
|
|
];
|
|
}
|
|
|
|
public function getMyAttendance(User $user, ?Carbon $startDate = null, ?Carbon $endDate = null): array
|
|
{
|
|
$employee = $user->employee;
|
|
|
|
if ($employee === null) {
|
|
return [
|
|
'total_days' => 0,
|
|
'present_days' => 0,
|
|
'absent_days' => 0,
|
|
'leave_days' => 0,
|
|
'percentage' => 0,
|
|
];
|
|
}
|
|
|
|
$start = $startDate ?? ($employee->join_date ? Carbon::parse($employee->join_date) : Carbon::today()->startOfMonth());
|
|
$end = $endDate ?? Carbon::today();
|
|
|
|
$totalDays = 0;
|
|
$presentDays = 0;
|
|
$current = $start->copy()->startOfDay();
|
|
|
|
while ($current->lte($end)) {
|
|
if ($current->isWeekday()) {
|
|
$totalDays++;
|
|
|
|
$hasAttendance = Attendance::query()
|
|
->where('employee_id', $employee->id)
|
|
->whereDate('attendance_date', $current)
|
|
->exists();
|
|
|
|
if ($hasAttendance) {
|
|
$presentDays++;
|
|
}
|
|
}
|
|
|
|
$current->addDay();
|
|
}
|
|
|
|
$leaveDays = LeaveRequest::query()
|
|
->approved()
|
|
->where('employee_id', $employee->id)
|
|
->where('start_date', '<=', $end->toDateString())
|
|
->where('end_date', '>=', $start->toDateString())
|
|
->get()
|
|
->sum(fn ($leave) => max(
|
|
0,
|
|
min($leave->end_date, $end->toDateString())
|
|
- max($leave->start_date, $start->toDateString())
|
|
) / 86400 + 1);
|
|
|
|
$leaveDays = (int) $leaveDays;
|
|
$absentDays = max(0, $totalDays - $presentDays - $leaveDays);
|
|
$percentage = $totalDays > 0 ? round(($presentDays / $totalDays) * 100) : 0;
|
|
|
|
return [
|
|
'total_days' => $totalDays,
|
|
'present_days' => $presentDays,
|
|
'absent_days' => $absentDays,
|
|
'leave_days' => $leaveDays,
|
|
'percentage' => $percentage,
|
|
];
|
|
}
|
|
|
|
public function getCashOverview(): array
|
|
{
|
|
$totalBalance = CashAccount::query()->sum('balance');
|
|
|
|
$summary = CashTransaction::query()
|
|
->whereDate('created_at', Carbon::today())
|
|
->selectRaw("
|
|
COUNT(*) as total_transactions,
|
|
COALESCE(SUM(CASE WHEN type = 'deposit' THEN amount ELSE 0 END), 0) as total_deposit,
|
|
COALESCE(SUM(CASE WHEN type = 'withdrawal' THEN amount ELSE 0 END), 0) as total_withdrawal
|
|
")
|
|
->first();
|
|
|
|
return [
|
|
'total_balance' => (int) $totalBalance,
|
|
'total_transactions' => (int) ($summary->total_transactions ?? 0),
|
|
'total_deposit' => (int) ($summary->total_deposit ?? 0),
|
|
'total_withdrawal' => (int) ($summary->total_withdrawal ?? 0),
|
|
];
|
|
}
|
|
|
|
public function getTopSuppliers(?Carbon $startDate = null, ?Carbon $endDate = null): array
|
|
{
|
|
return Purchase::query()
|
|
->when($startDate && $endDate, fn ($q) => $q->whereBetween('purchases.created_at', [$startDate, $endDate]))
|
|
->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();
|
|
}
|
|
|
|
public function getTopCustomers(?Carbon $startDate = null, ?Carbon $endDate = null): array
|
|
{
|
|
$user = auth()->user();
|
|
$isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false;
|
|
$isCashier = $user?->hasRole('cashier') ?? false;
|
|
|
|
return Order::query()
|
|
->completed()
|
|
->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
|
|
->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id))
|
|
->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate]))
|
|
->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();
|
|
}
|
|
|
|
public function getRevenueSummary(?Carbon $startDate = null, ?Carbon $endDate = null): array
|
|
{
|
|
$user = auth()->user();
|
|
$isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false;
|
|
$isCashier = $user?->hasRole('cashier') ?? false;
|
|
|
|
$revenueSummary = Order::query()
|
|
->completed()
|
|
->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
|
|
->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id))
|
|
->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate]))
|
|
->selectRaw('SUM(total_amount) as total_revenue, SUM(discount) as total_discount, COUNT(*) as total_orders, AVG(total_amount) as avg_order')
|
|
->first();
|
|
|
|
$totalMarketplaceFees = Order::query()
|
|
->completed()
|
|
->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
|
|
->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id))
|
|
->whereNotNull('marketplace_settings_snapshot')
|
|
->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate]))
|
|
->get()
|
|
->sum(fn ($order) => (int) ($order->marketplace_settings_snapshot['total_fee_amount'] ?? 0));
|
|
|
|
return [
|
|
'total_revenue' => (int) ($revenueSummary->total_revenue ?? 0),
|
|
'total_discount' => (int) ($revenueSummary->total_discount ?? 0),
|
|
'total_marketplace_fees' => $totalMarketplaceFees,
|
|
'total_potongan' => (int) ($revenueSummary->total_discount ?? 0) + $totalMarketplaceFees,
|
|
'total_orders' => (int) ($revenueSummary->total_orders ?? 0),
|
|
'avg_order' => (int) ($revenueSummary->avg_order ?? 0),
|
|
];
|
|
}
|
|
|
|
public function getMonthlyRevenue(?Carbon $startDate = null, ?Carbon $endDate = null): array
|
|
{
|
|
$user = auth()->user();
|
|
$isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false;
|
|
$isCashier = $user?->hasRole('cashier') ?? false;
|
|
|
|
$query = Order::query()->completed()
|
|
->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
|
|
->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id));
|
|
$feeQuery = Order::query()->completed()
|
|
->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
|
|
->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id))
|
|
->whereNotNull('marketplace_settings_snapshot');
|
|
|
|
if ($startDate && $endDate) {
|
|
$query->whereBetween('orders.created_at', [$startDate, $endDate]);
|
|
$feeQuery->whereBetween('orders.created_at', [$startDate, $endDate]);
|
|
}
|
|
|
|
$monthlyData = $query
|
|
->selectRaw("
|
|
DATE_FORMAT(orders.created_at, '%Y-%m') as month_key,
|
|
SUM(total_amount) as total_revenue,
|
|
SUM(discount) as total_discount
|
|
")
|
|
->groupBy('month_key')
|
|
->orderBy('month_key')
|
|
->get();
|
|
|
|
$monthlyFees = $feeQuery
|
|
->selectRaw("DATE_FORMAT(orders.created_at, '%Y-%m') as month_key")
|
|
->get()
|
|
->groupBy('month_key')
|
|
->map(fn ($orders) => $orders->sum(fn ($order) => (int) ($order->marketplace_settings_snapshot['total_fee_amount'] ?? 0)));
|
|
|
|
if ($monthlyData->isEmpty()) {
|
|
return [];
|
|
}
|
|
|
|
$start = $startDate ?? Carbon::parse($monthlyData->first()->month_key.'-01');
|
|
$end = $endDate ?? Carbon::parse($monthlyData->last()->month_key.'-01')->endOfMonth();
|
|
|
|
$result = [];
|
|
$current = $start->copy()->startOfMonth();
|
|
while ($current->lte($end)) {
|
|
$key = $current->format('Y-m');
|
|
$monthLabel = $current->locale('id')->translatedFormat('M Y');
|
|
|
|
$revenue = $monthlyData->firstWhere('month_key', $key);
|
|
$fees = $monthlyFees->get($key, 0);
|
|
$discount = (int) ($revenue->total_discount ?? 0);
|
|
$potongan = $discount + $fees;
|
|
|
|
$result[] = [
|
|
'month' => $monthLabel,
|
|
'total' => (int) ($revenue->total_revenue ?? 0),
|
|
'net' => (int) ($revenue->total_revenue ?? 0) - $potongan,
|
|
'potongan' => $potongan,
|
|
];
|
|
|
|
$current->addMonth();
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
public function getExpenseSummary(?Carbon $startDate = null, ?Carbon $endDate = null): array
|
|
{
|
|
$purchase = Purchase::query()
|
|
->when($startDate && $endDate, fn ($q) => $q->whereBetween('purchases.created_at', [$startDate, $endDate]))
|
|
->selectRaw('COALESCE(SUM(total), 0) as total')
|
|
->first();
|
|
|
|
$expenses = Expense::query()
|
|
->when($startDate && $endDate, fn ($q) => $q->whereBetween('expenses.created_at', [$startDate, $endDate]))
|
|
->selectRaw('COALESCE(SUM(amount), 0) as total')
|
|
->first();
|
|
|
|
$employeeAdvance = EmployeeAdvance::query()
|
|
->when($startDate && $endDate, fn ($q) => $q->whereBetween('employee_advances.created_at', [$startDate, $endDate]))
|
|
->selectRaw('COALESCE(SUM(amount), 0) as total')
|
|
->first();
|
|
|
|
$purchaseTotal = (int) ($purchase->total ?? 0);
|
|
if (auth()->user()?->hasRole(\App\Enums\Role::ADMIN_TOKO->value)) {
|
|
$purchaseTotal = 0;
|
|
}
|
|
$expenseTotal = (int) ($expenses->total ?? 0);
|
|
$advanceTotal = (int) ($employeeAdvance->total ?? 0);
|
|
|
|
return [
|
|
'total' => $purchaseTotal + $expenseTotal + $advanceTotal,
|
|
'purchase_total' => $purchaseTotal,
|
|
'expense_total' => $expenseTotal,
|
|
'advance_total' => $advanceTotal,
|
|
];
|
|
}
|
|
|
|
public function getMonthlyExpense(?Carbon $startDate = null, ?Carbon $endDate = null): array
|
|
{
|
|
$purchases = Purchase::query()
|
|
->when($startDate && $endDate, fn ($q) => $q->whereBetween('purchases.created_at', [$startDate, $endDate]))
|
|
->selectRaw("DATE_FORMAT(purchases.created_at, '%Y-%m') as month_key, COALESCE(SUM(total), 0) as total")
|
|
->groupBy('month_key')
|
|
->orderBy('month_key')
|
|
->get();
|
|
|
|
$expenses = Expense::query()
|
|
->when($startDate && $endDate, fn ($q) => $q->whereBetween('expenses.created_at', [$startDate, $endDate]))
|
|
->selectRaw("DATE_FORMAT(expenses.created_at, '%Y-%m') as month_key, COALESCE(SUM(amount), 0) as total")
|
|
->groupBy('month_key')
|
|
->orderBy('month_key')
|
|
->get();
|
|
|
|
$advances = EmployeeAdvance::query()
|
|
->when($startDate && $endDate, fn ($q) => $q->whereBetween('employee_advances.created_at', [$startDate, $endDate]))
|
|
->selectRaw("DATE_FORMAT(employee_advances.created_at, '%Y-%m') as month_key, COALESCE(SUM(amount), 0) as total")
|
|
->groupBy('month_key')
|
|
->orderBy('month_key')
|
|
->get();
|
|
|
|
$allMonths = collect()
|
|
->merge($purchases->pluck('month_key'))
|
|
->merge($expenses->pluck('month_key'))
|
|
->merge($advances->pluck('month_key'))
|
|
->unique()
|
|
->sort()
|
|
->values();
|
|
|
|
if ($allMonths->isEmpty()) {
|
|
return [];
|
|
}
|
|
|
|
$start = $startDate ?? Carbon::parse($allMonths->first().'-01');
|
|
$end = $endDate ?? Carbon::parse($allMonths->last().'-01')->endOfMonth();
|
|
|
|
$result = [];
|
|
$current = $start->copy()->startOfMonth();
|
|
while ($current->lte($end)) {
|
|
$key = $current->format('Y-m');
|
|
$monthLabel = $current->locale('id')->translatedFormat('M Y');
|
|
|
|
$purchaseAmount = (int) ($purchases->firstWhere('month_key', $key)->total ?? 0);
|
|
if (auth()->user()?->hasRole(\App\Enums\Role::ADMIN_TOKO->value)) {
|
|
$purchaseAmount = 0;
|
|
}
|
|
$expenseAmount = (int) ($expenses->firstWhere('month_key', $key)->total ?? 0);
|
|
$advanceAmount = (int) ($advances->firstWhere('month_key', $key)->total ?? 0);
|
|
|
|
$result[] = [
|
|
'month' => $monthLabel,
|
|
'total' => $purchaseAmount + $expenseAmount + $advanceAmount,
|
|
'belanja' => $purchaseAmount,
|
|
'pengeluaran' => $expenseAmount,
|
|
'kasbon' => $advanceAmount,
|
|
];
|
|
|
|
$current->addMonth();
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
public function getProfitMetrics(?Carbon $startDate = null, ?Carbon $endDate = null): array
|
|
{
|
|
$user = auth()->user();
|
|
$isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false;
|
|
$isCashier = $user?->hasRole('cashier') ?? false;
|
|
|
|
$orderQuery = Order::query()->completed()
|
|
->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
|
|
->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id));
|
|
if ($startDate && $endDate) {
|
|
$orderQuery->whereBetween('orders.created_at', [$startDate, $endDate]);
|
|
}
|
|
|
|
$revenueData = (clone $orderQuery)
|
|
->selectRaw('
|
|
COUNT(*) as total_orders,
|
|
SUM(total_amount) as total_revenue,
|
|
SUM(discount) as total_discount,
|
|
SUM(subtotal) as total_subtotal
|
|
')
|
|
->first();
|
|
|
|
$totalRevenue = (int) ($revenueData->total_revenue ?? 0);
|
|
$totalDiscount = (int) ($revenueData->total_discount ?? 0);
|
|
$totalSubtotal = (int) ($revenueData->total_subtotal ?? 0);
|
|
$totalOrders = (int) ($revenueData->total_orders ?? 0);
|
|
|
|
$marketplaceFees = (clone $orderQuery)
|
|
->whereNotNull('marketplace_settings_snapshot')
|
|
->get()
|
|
->sum(fn ($order) => (int) ($order->marketplace_settings_snapshot['total_fee_amount'] ?? 0));
|
|
|
|
$itemsData = OrderItem::query()
|
|
->join('orders', 'order_items.order_id', '=', 'orders.id')
|
|
->where('orders.status', OrderStatus::COMPLETED)
|
|
->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate]))
|
|
->selectRaw('
|
|
SUM(order_items.quantity) as total_qty,
|
|
COUNT(order_items.id) as total_items
|
|
')
|
|
->first();
|
|
|
|
$totalQty = (int) ($itemsData->total_qty ?? 0);
|
|
$totalItems = (int) ($itemsData->total_items ?? 0);
|
|
|
|
// HPP from cutting_result_prices
|
|
$hpp = OrderItem::query()
|
|
->join('orders', 'order_items.order_id', '=', 'orders.id')
|
|
->leftJoin('cutting_result_prices', 'order_items.product_variant_id', '=', 'cutting_result_prices.product_variant_id')
|
|
->where('orders.status', OrderStatus::COMPLETED)
|
|
->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate]))
|
|
->selectRaw('COALESCE(SUM(order_items.quantity * cutting_result_prices.cost_per_unit), 0) as total_hpp')
|
|
->value('total_hpp');
|
|
|
|
$totalHpp = (int) ($hpp ?? 0);
|
|
|
|
// Expenses
|
|
$purchaseTotal = Purchase::query()
|
|
->when($startDate && $endDate, fn ($q) => $q->whereBetween('purchases.created_at', [$startDate, $endDate]))
|
|
->sum('total');
|
|
|
|
if (auth()->user()?->hasRole(\App\Enums\Role::ADMIN_TOKO->value)) {
|
|
$purchaseTotal = 0;
|
|
}
|
|
|
|
$expenseTotal = Expense::query()
|
|
->when($startDate && $endDate, fn ($q) => $q->whereBetween('expenses.created_at', [$startDate, $endDate]))
|
|
->sum('amount');
|
|
|
|
$advanceTotal = EmployeeAdvance::query()
|
|
->when($startDate && $endDate, fn ($q) => $q->whereBetween('employee_advances.created_at', [$startDate, $endDate]))
|
|
->sum('amount');
|
|
|
|
$totalExpenses = (int) $purchaseTotal + (int) $expenseTotal + (int) $advanceTotal;
|
|
|
|
$labaKotor = $totalRevenue - $totalHpp - $totalDiscount - $marketplaceFees;
|
|
$labaBersih = $labaKotor - $totalExpenses;
|
|
$profitMargin = $totalRevenue > 0 ? round(($labaBersih / $totalRevenue) * 100, 1) : 0;
|
|
$aov = $totalOrders > 0 ? (int) round($totalRevenue / $totalOrders) : 0;
|
|
$itemsPerTransaction = $totalOrders > 0 ? round($totalItems / $totalOrders, 1) : 0;
|
|
|
|
return [
|
|
'total_orders' => $totalOrders,
|
|
'total_products_sold' => $totalQty,
|
|
'hpp' => $totalHpp,
|
|
'laba_kotor' => $labaKotor,
|
|
'laba_bersih' => $labaBersih,
|
|
'profit_margin' => $profitMargin,
|
|
'aov' => $aov,
|
|
'items_per_transaction' => $itemsPerTransaction,
|
|
];
|
|
}
|
|
|
|
public function getRawMaterialStock(): array
|
|
{
|
|
$prices = RawMaterialPrice::query()
|
|
->join('raw_materials', 'raw_material_prices.raw_material_id', '=', 'raw_materials.id')
|
|
->selectRaw('
|
|
raw_materials.unit,
|
|
SUM(raw_material_prices.stock) as total_stock,
|
|
SUM(raw_material_prices.stock * raw_material_prices.price) as total_value
|
|
')
|
|
->groupBy('raw_materials.unit')
|
|
->get()
|
|
->keyBy('unit');
|
|
|
|
$totalStock = (float) $prices->sum('total_stock');
|
|
$totalValue = (int) $prices->sum('total_value');
|
|
|
|
return [
|
|
'total_stock' => round($totalStock, 2),
|
|
'total_value' => $totalValue,
|
|
'by_unit' => [
|
|
'yard' => round((float) ($prices->get('yard')->total_stock ?? 0), 2),
|
|
'meter' => round((float) ($prices->get('meter')->total_stock ?? 0), 2),
|
|
'kilogram' => round((float) ($prices->get('kilogram')->total_stock ?? 0), 2),
|
|
],
|
|
];
|
|
}
|
|
|
|
public function getProductStock(): array
|
|
{
|
|
$variants = ProductVariant::query()
|
|
->join('products', 'product_variants.product_id', '=', 'products.id')
|
|
->selectRaw('
|
|
SUM(product_variants.stock) as total_stock,
|
|
SUM(product_variants.reject_stock) as total_reject,
|
|
SUM(product_variants.stock_ecer) as total_ecer,
|
|
COUNT(product_variants.id) as total_variants,
|
|
COUNT(DISTINCT products.id) as total_products
|
|
')
|
|
->first();
|
|
|
|
$totalValue = \DB::table('product_prices')
|
|
->join('product_variants', 'product_prices.variant_id', '=', 'product_variants.id')
|
|
->selectRaw('SUM(product_variants.stock * product_prices.price) as total')
|
|
->value('total');
|
|
|
|
$totalCategories = \DB::table('product_categories')
|
|
->distinct('category_id')
|
|
->count('category_id');
|
|
|
|
return [
|
|
'total_stock' => (int) ($variants->total_stock ?? 0),
|
|
'total_reject' => (int) ($variants->total_reject ?? 0),
|
|
'total_ecer' => (int) ($variants->total_ecer ?? 0),
|
|
'total_value' => (int) ($totalValue ?? 0),
|
|
'total_products' => (int) ($variants->total_products ?? 0),
|
|
'total_variants' => (int) ($variants->total_variants ?? 0),
|
|
'total_categories' => (int) $totalCategories,
|
|
];
|
|
}
|
|
|
|
public function getBusyHours(?Carbon $startDate = null, ?Carbon $endDate = null): array
|
|
{
|
|
$user = auth()->user();
|
|
$isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false;
|
|
$isCashier = $user?->hasRole('cashier') ?? false;
|
|
|
|
$hourlyData = Order::query()
|
|
->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
|
|
->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id))
|
|
->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate]))
|
|
->selectRaw('HOUR(orders.created_at) as hour, COUNT(*) as order_count')
|
|
->groupBy('hour')
|
|
->orderBy('hour')
|
|
->get()
|
|
->keyBy('hour');
|
|
|
|
$result = [];
|
|
for ($h = 0; $h < 24; $h++) {
|
|
$result[] = [
|
|
'hour' => str_pad($h, 2, '0', STR_PAD_LEFT).':00',
|
|
'orders' => (int) ($hourlyData->get($h)->order_count ?? 0),
|
|
];
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
public function getTopProducts(?Carbon $startDate = null, ?Carbon $endDate = null): array
|
|
{
|
|
$user = auth()->user();
|
|
$isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false;
|
|
$isCashier = $user?->hasRole('cashier') ?? false;
|
|
|
|
return OrderItem::query()
|
|
->join('orders', 'order_items.order_id', '=', 'orders.id')
|
|
->join('product_variants', 'order_items.product_variant_id', '=', 'product_variants.id')
|
|
->join('products', 'product_variants.product_id', '=', 'products.id')
|
|
->where('orders.status', OrderStatus::COMPLETED)
|
|
->when($isMarketing, fn ($q) => $q->where('orders.marketing_id', $user->id))
|
|
->when($isCashier, fn ($q) => $q->where('orders.created_by_id', $user->id))
|
|
->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate]))
|
|
->selectRaw("CONCAT(products.name, ' - ', product_variants.name) as full_name, SUM(order_items.quantity) as total_qty, SUM(order_items.subtotal) as total_revenue")
|
|
->groupBy('order_items.product_variant_id', 'products.name', 'product_variants.name')
|
|
->orderByDesc('total_qty')
|
|
->limit(5)
|
|
->get()
|
|
->map(fn ($item) => [
|
|
'name' => $item->full_name,
|
|
'total_qty' => (int) $item->total_qty,
|
|
'total_revenue' => (int) $item->total_revenue,
|
|
])
|
|
->toArray();
|
|
}
|
|
}
|