From 15f2f0e1d034a1c8c9b1434f1f082b34d51f9db6 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Thu, 2 Jul 2026 13:11:18 +0700 Subject: [PATCH] feat: add marketing sales analysis feature; enhance AnalysisService to retrieve marketing sales data and update Analysis.vue to display marketing sales table with relevant metrics --- app/Enums/Permission.php | 5 +- app/Enums/Role.php | 3 + .../Controllers/Admin/AnalysisController.php | 1 + .../Concerns/HasRawMaterialPriceRules.php | 1 - .../Finance/EmployeeAdvanceService.php | 6 +- app/Services/Finance/ExpenseService.php | 6 +- app/Services/Hr/AttendanceService.php | 6 +- app/Services/Hr/LeaveRequestService.php | 6 +- app/Services/Manage/CuttingService.php | 14 ++-- app/Services/Manage/OrderService.php | 8 +-- .../Manage/OwnerVerificationService.php | 2 +- app/Services/Manage/PurchaseService.php | 2 +- app/Services/Manage/RetailStockService.php | 2 +- app/Services/Manage/StockService.php | 6 +- app/Services/Master/ProductService.php | 2 +- app/Services/Master/RawMaterialService.php | 2 +- app/Services/System/AnalysisService.php | 44 +++++++++++-- .../System/Setting/MarketplaceService.php | 2 +- resources/js/pages/admin/Analysis.vue | 65 ++++++++++++++++--- 19 files changed, 132 insertions(+), 51 deletions(-) diff --git a/app/Enums/Permission.php b/app/Enums/Permission.php index 1d2a6df..b753360 100644 --- a/app/Enums/Permission.php +++ b/app/Enums/Permission.php @@ -33,6 +33,7 @@ enum Permission: string case ANALYSIS_TOP_SUPPLIERS = 'analysis.top_suppliers'; case ANALYSIS_TOP_CUSTOMERS = 'analysis.top_customers'; case ANALYSIS_TOP_PRODUCTS = 'analysis.top_products'; + case ANALYSIS_MARKETING_SALES = 'analysis.marketing_sales'; case EMPLOYEES_VIEW = 'employees.view'; case EMPLOYEES_CREATE = 'employees.create'; @@ -172,6 +173,7 @@ public function label(): string self::ANALYSIS_TOP_SUPPLIERS => 'Lihat Top 5 Supplier', self::ANALYSIS_TOP_CUSTOMERS => 'Lihat Top 5 Pelanggan', self::ANALYSIS_TOP_PRODUCTS => 'Lihat Top 5 Produk', + self::ANALYSIS_MARKETING_SALES => 'Lihat Analisa Penjualan Marketing', self::EMPLOYEES_VIEW => 'Lihat Pegawai', self::EMPLOYEES_CREATE => 'Tambah Pegawai', @@ -295,7 +297,8 @@ public function group(): string self::ANALYSIS_RAW_MATERIALS, self::ANALYSIS_PRODUCT_STOCK, self::ANALYSIS_REVENUE, self::ANALYSIS_EXPENSE, self::ANALYSIS_BUSY_HOURS, self::ANALYSIS_PROFIT_ORDERS, self::ANALYSIS_PROFIT_HPP, self::ANALYSIS_PROFIT_GROSS, self::ANALYSIS_PROFIT_MARGIN, - self::ANALYSIS_TOP_SUPPLIERS, self::ANALYSIS_TOP_CUSTOMERS, self::ANALYSIS_TOP_PRODUCTS => 'Umum', + self::ANALYSIS_TOP_SUPPLIERS, self::ANALYSIS_TOP_CUSTOMERS, self::ANALYSIS_TOP_PRODUCTS, + self::ANALYSIS_MARKETING_SALES => 'Umum', self::EMPLOYEES_VIEW, self::EMPLOYEES_CREATE, self::EMPLOYEES_UPDATE, self::EMPLOYEES_DELETE, self::EMPLOYEES_RESET_PASSWORD, self::EMPLOYEES_TOGGLE_STATUS => 'Pegawai', self::ATTENDANCES_VIEW, self::ATTENDANCES_CREATE, self::ATTENDANCES_DELETE, diff --git a/app/Enums/Role.php b/app/Enums/Role.php index 07a2d5e..0644237 100644 --- a/app/Enums/Role.php +++ b/app/Enums/Role.php @@ -78,6 +78,7 @@ public function permissions(): array Permission::ANALYSIS_PROFIT_MARGIN, Permission::ANALYSIS_TOP_CUSTOMERS, Permission::ANALYSIS_TOP_PRODUCTS, + Permission::ANALYSIS_MARKETING_SALES, Permission::EMPLOYEES_VIEW, @@ -130,9 +131,11 @@ public function permissions(): array Permission::ANALYSIS_REVENUE, Permission::ANALYSIS_EXPENSE, Permission::ANALYSIS_BUSY_HOURS, + Permission::ANALYSIS_PROFIT_ORDERS, Permission::ANALYSIS_TOP_SUPPLIERS, Permission::ANALYSIS_TOP_CUSTOMERS, Permission::ANALYSIS_TOP_PRODUCTS, + Permission::ANALYSIS_MARKETING_SALES, Permission::STOCKS_VIEW, diff --git a/app/Http/Controllers/Admin/AnalysisController.php b/app/Http/Controllers/Admin/AnalysisController.php index 790552d..59fdc3b 100644 --- a/app/Http/Controllers/Admin/AnalysisController.php +++ b/app/Http/Controllers/Admin/AnalysisController.php @@ -41,6 +41,7 @@ public function index(Request $request): Response 'topSuppliers' => $this->analysisService->getTopSuppliers($startDate, $endDate), 'topCustomers' => $this->analysisService->getTopCustomers($startDate, $endDate), 'topProducts' => $this->analysisService->getTopProducts($startDate, $endDate), + 'marketingSales' => $this->analysisService->getMarketingSales($startDate, $endDate), ]); } } diff --git a/app/Http/Requests/Concerns/HasRawMaterialPriceRules.php b/app/Http/Requests/Concerns/HasRawMaterialPriceRules.php index d846727..7eae281 100644 --- a/app/Http/Requests/Concerns/HasRawMaterialPriceRules.php +++ b/app/Http/Requests/Concerns/HasRawMaterialPriceRules.php @@ -2,7 +2,6 @@ namespace App\Http\Requests\Concerns; -use App\Enums\RawMaterialUnit; use Illuminate\Validation\Rule; trait HasRawMaterialPriceRules diff --git a/app/Services/Finance/EmployeeAdvanceService.php b/app/Services/Finance/EmployeeAdvanceService.php index e1a4be9..41eca45 100644 --- a/app/Services/Finance/EmployeeAdvanceService.php +++ b/app/Services/Finance/EmployeeAdvanceService.php @@ -97,7 +97,7 @@ public function create(array $validated, User $user): void $this->pushNotificationService->sendToRoles( '💰 Pengajuan Kasbon Baru', "Karyawan {$user->profil?->full_name} mengajukan kasbon sebesar {$employeeAdvance->amount_formatted} dengan keterangan: {$employeeAdvance->description}.", - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], route('admin.finance.employee_advances.index'), ); } @@ -127,7 +127,7 @@ public function update(EmployeeAdvance $employeeAdvance, array $validated, User $this->pushNotificationService->sendToRoles( '✏️ Kasbon Diperbarui', "Kasbon sebesar {$employeeAdvance->amount_formatted} telah diperbarui oleh {$user->profile?->full_name}.", - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], route('admin.finance.employee_advances.index'), ); } @@ -142,7 +142,7 @@ public function delete(EmployeeAdvance $employeeAdvance, User $user): void $this->pushNotificationService->sendToRoles( '🗑️ Kasbon Dihapus', "Kasbon sebesar {$amount} dengan keterangan {$description} telah dihapus.", - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], route('admin.finance.employee_advances.index'), ); } diff --git a/app/Services/Finance/ExpenseService.php b/app/Services/Finance/ExpenseService.php index 81ecd65..f2562a2 100644 --- a/app/Services/Finance/ExpenseService.php +++ b/app/Services/Finance/ExpenseService.php @@ -92,7 +92,7 @@ public function create(array $validated, User $user): void $this->pushNotificationService->sendToRoles( '💸 Pengeluaran Baru', "Pengeluaran baru sebesar {$expense->amount_formatted} dengan keterangan {$expense->description} telah dicatat oleh {$user->profile?->full_name}.", - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], route('admin.finance.expenses.index'), ); } @@ -134,7 +134,7 @@ public function update(Expense $expense, array $validated): void $this->pushNotificationService->sendToRoles( '✏️ Pengeluaran Diperbarui', "Pengeluaran dengan keterangan {$expense->description} diperbarui menjadi sebesar {$expense->amount_formatted}.", - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], route('admin.finance.expenses.index'), ); } @@ -165,7 +165,7 @@ public function delete(Expense $expense): void $this->pushNotificationService->sendToRoles( '🗑️ Pengeluaran Dihapus', "Pengeluaran sebesar {$expense->amount_formatted} dengan keterangan {$expense->description} telah dihapus.", - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], route('admin.finance.expenses.index'), ); } diff --git a/app/Services/Hr/AttendanceService.php b/app/Services/Hr/AttendanceService.php index 8167e27..5c51da6 100644 --- a/app/Services/Hr/AttendanceService.php +++ b/app/Services/Hr/AttendanceService.php @@ -100,7 +100,7 @@ public function checkIn(array $validated, User $user): void $this->pushNotificationService->sendToRoles( '⏰ Presensi Masuk', "Karyawan {$user->profile?->full_name} melakukan presensi masuk.", - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], route('admin.hr.attendances.index'), ); } @@ -146,7 +146,7 @@ public function checkOut(array $validated, User $user): void $this->pushNotificationService->sendToRoles( '⏰ Presensi Pulang', "Karyawan {$user->profile?->full_name} melakukan presensi pulang (Durasi kerja: ".round($workDurationMinutes / 60, 1).' jam).', - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], route('admin.hr.attendances.index'), ); } @@ -164,7 +164,7 @@ public function delete(Attendance $attendance): void $this->pushNotificationService->sendToRoles( '🗑️ Presensi Dihapus', "Data presensi {$employeeName} tanggal {$date} telah dihapus.", - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], route('admin.hr.attendances.index'), ); } diff --git a/app/Services/Hr/LeaveRequestService.php b/app/Services/Hr/LeaveRequestService.php index 20b5370..631449e 100644 --- a/app/Services/Hr/LeaveRequestService.php +++ b/app/Services/Hr/LeaveRequestService.php @@ -78,7 +78,7 @@ public function create(array $validated, User $user): void $this->pushNotificationService->sendToRoles( '🏖️ Pengajuan Cuti Baru', "Karyawan {$user->profile?->full_name} mengajukan cuti selama {$leaveRequest->total_days} hari mulai dari tanggal {$leaveRequest->start_date_formatted}.", - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], route('admin.hr.leave_requests.index'), ); } @@ -99,7 +99,7 @@ public function update(LeaveRequest $leaveRequest, array $validated, User $user) $this->pushNotificationService->sendToRoles( '✏️ Pengajuan Cuti Diperbarui', "Pengajuan cuti oleh {$user->profile?->full_name} telah diperbarui.", - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], route('admin.hr.leave_requests.index'), ); } @@ -114,7 +114,7 @@ public function delete(LeaveRequest $leaveRequest): void $this->pushNotificationService->sendToRoles( '🗑️ Pengajuan Cuti Dihapus', "Pengajuan cuti {$totalDays} hari oleh {$employeeName} telah dihapus.", - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], route('admin.hr.leave_requests.index'), ); } diff --git a/app/Services/Manage/CuttingService.php b/app/Services/Manage/CuttingService.php index d9f049c..f787005 100644 --- a/app/Services/Manage/CuttingService.php +++ b/app/Services/Manage/CuttingService.php @@ -455,7 +455,7 @@ public function create(array $validated, User $user): Cutting $this->pushNotificationService->sendToRoles( '✂️ Proses Cutting Baru', "Proses cutting dengan deskripsi ({$cutting->description}) telah dimulai oleh {$user->profile?->full_name}.", - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], route('admin.manage.cuttings.index'), ); @@ -524,7 +524,7 @@ public function update(Cutting $cutting, array $validated): void $this->pushNotificationService->sendToRoles( '✏️ Proses Cutting Diperbarui', "Proses cutting dengan deskripsi '{$description}' telah diperbarui.", - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], route('admin.manage.cuttings.index'), ); } @@ -568,7 +568,7 @@ public function delete(Cutting $cutting): void $this->pushNotificationService->sendToRoles( '🗑️ Proses Cutting Dihapus', "Proses cutting dengan deskripsi {$description} telah dihapus.", - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], route('admin.manage.cuttings.index'), ); } @@ -661,8 +661,8 @@ public function transitionStatus( }; $roles = $status === CuttingStatus::COMPLETED - ? ['owner', 'developer', 'admin-toko'] - : ['owner', 'developer']; + ? ['owner', 'developer', 'direktur', 'admin-toko'] + : ['owner', 'developer', 'direktur']; $this->pushNotificationService->sendToRoles( $title, @@ -1105,7 +1105,7 @@ public function quickCreateProduct(array $validated): array 'is_active' => true, ]); - if (!empty($validated['category_ids'])) { + if (! empty($validated['category_ids'])) { $product->categories()->sync($validated['category_ids']); } @@ -1119,7 +1119,7 @@ public function quickCreateProduct(array $validated): array 'retail_stock' => $variantData['retail_stock'] ?? 0, ]); - if (!empty($variantData['prices'])) { + if (! empty($variantData['prices'])) { foreach ($variantData['prices'] as $type => $priceValue) { $variant->prices()->create([ 'type' => $type, diff --git a/app/Services/Manage/OrderService.php b/app/Services/Manage/OrderService.php index 408efcd..7e1d175 100644 --- a/app/Services/Manage/OrderService.php +++ b/app/Services/Manage/OrderService.php @@ -498,7 +498,7 @@ public function create(array $validated, User $user): Order $this->pushNotificationService->sendToRoles( '📦 Pesanan Baru', "Pesanan baru {$order->order_number} senilai {$order->total_amount_formatted} telah dibuat oleh {$user->profile?->full_name}.", - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], route('admin.manage.orders.index'), ); @@ -596,7 +596,7 @@ public function update(Order $order, array $validated): void $this->pushNotificationService->sendToRoles( '✏️ Pesanan Diperbarui', "Pesanan {$order->order_number} senilai {$order->total_amount_formatted} telah diperbarui.", - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], route('admin.manage.orders.index'), ); } @@ -638,7 +638,7 @@ public function delete(Order $order): void $this->pushNotificationService->sendToRoles( '🗑️ Pesanan Dihapus', "Pesanan {$orderNumber} senilai {$order->total_amount_formatted} telah dihapus.", - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], route('admin.manage.orders.index'), ); } @@ -684,7 +684,7 @@ public function transitionStatus(Order $order, OrderStatus $status): void $this->pushNotificationService->sendToRoles( '📦 Status Pesanan Diubah', "Pesanan {$order->order_number} diubah statusnya menjadi {$status->label()}.", - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], route('admin.manage.orders.index'), ); } diff --git a/app/Services/Manage/OwnerVerificationService.php b/app/Services/Manage/OwnerVerificationService.php index fb3e1a9..f62d046 100644 --- a/app/Services/Manage/OwnerVerificationService.php +++ b/app/Services/Manage/OwnerVerificationService.php @@ -265,7 +265,7 @@ public function rejectRequest( $this->pushNotificationService->sendToRoles( '❌ Pengajuan Ditolak', "Pengajuan {$request->action->label()} {$subjectLabel} '{$title}' ditolak dengan alasan: '{$reason}'.", - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], route('admin.dashboard'), ); diff --git a/app/Services/Manage/PurchaseService.php b/app/Services/Manage/PurchaseService.php index 1988905..40db777 100644 --- a/app/Services/Manage/PurchaseService.php +++ b/app/Services/Manage/PurchaseService.php @@ -593,7 +593,7 @@ private function notifyForPendingRequest(User $user, string $typeLabel, string $ $this->pushNotificationService->sendToRoles( "📦 {$typeLabel} Menunggu Persetujuan Owner", $body, - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], $ownerUrl, ); diff --git a/app/Services/Manage/RetailStockService.php b/app/Services/Manage/RetailStockService.php index a3b23f0..4083ce8 100644 --- a/app/Services/Manage/RetailStockService.php +++ b/app/Services/Manage/RetailStockService.php @@ -85,7 +85,7 @@ public function transfer(int $variantId, int $quantity, User $user, ?string $not $this->pushNotificationService->sendToRoles( '📦 Transfer Stok Ecer Menunggu Persetujuan Owner', "Pengajuan transfer {$quantity} pcs stok ecer untuk varian '{$variant->name}' menunggu verifikasi owner.", - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], route('admin.master.products.index', ['search' => $productName]), ); diff --git a/app/Services/Manage/StockService.php b/app/Services/Manage/StockService.php index 86fcf41..2525593 100644 --- a/app/Services/Manage/StockService.php +++ b/app/Services/Manage/StockService.php @@ -126,7 +126,7 @@ public function submitVerification( $this->pushNotificationService->sendToRoles( '📦 Verifikasi Stok Menunggu Persetujuan', "Cutting dengan deskripsi '{$description}' telah diajukan verifikasi dan menunggu persetujuan owner.", - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], route('admin.manage.stocks.index'), ); } @@ -176,7 +176,7 @@ public function approveVerification( $this->pushNotificationService->sendToRoles( '📦 Stok Cutting Diverifikasi', "Cutting dengan deskripsi '{$description}' telah disetujui owner dan stok produk telah ditambahkan ke toko.", - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], route('admin.manage.stocks.index'), ); } @@ -218,7 +218,7 @@ public function rejectVerification( $this->pushNotificationService->sendToRoles( '📦 Verifikasi Cutting Ditolak Owner', "Cutting dengan deskripsi '{$description}' ditolak oleh owner dengan alasan: '{$reason}'.", - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], route('admin.manage.stocks.index'), ); diff --git a/app/Services/Master/ProductService.php b/app/Services/Master/ProductService.php index 34306b1..d496f47 100644 --- a/app/Services/Master/ProductService.php +++ b/app/Services/Master/ProductService.php @@ -554,7 +554,7 @@ private function notifyForPendingRequest(User $user, string $typeLabel, string $ $this->pushNotificationService->sendToRoles( "📦 {$typeLabel} Menunggu Persetujuan Owner", $body, - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], $ownerUrl, ); diff --git a/app/Services/Master/RawMaterialService.php b/app/Services/Master/RawMaterialService.php index 6e2e583..c1c5937 100644 --- a/app/Services/Master/RawMaterialService.php +++ b/app/Services/Master/RawMaterialService.php @@ -363,7 +363,7 @@ private function notifyForPendingRequest(User $user, string $typeLabel, string $ $this->pushNotificationService->sendToRoles( "📦 {$typeLabel} Menunggu Persetujuan Owner", $body, - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], $ownerUrl, ); diff --git a/app/Services/System/AnalysisService.php b/app/Services/System/AnalysisService.php index 0b1e45e..1c880e3 100644 --- a/app/Services/System/AnalysisService.php +++ b/app/Services/System/AnalysisService.php @@ -243,7 +243,7 @@ public function getRevenueSummary(?Carbon $startDate = null, ?Carbon $endDate = 'total_discount' => (int) ($revenueSummary->total_discount ?? 0), 'total_marketplace_fees' => $totalMarketplaceFees, 'total_cost_price' => (int) ($totalCostPrice ?? 0), - 'total_deduction' => (int) ($revenueSummary->total_discount ?? 0) + $totalMarketplaceFees + (int) ($totalCostPrice ?? 0), + 'total_deduction' => (int) ($revenueSummary->total_discount ?? 0) + $totalMarketplaceFees, 'total_orders' => (int) ($revenueSummary->total_orders ?? 0), 'avg_order' => (int) ($revenueSummary->avg_order ?? 0), ]; @@ -318,9 +318,8 @@ public function getMonthlyRevenue(?Carbon $startDate = null, ?Carbon $endDate = $revenue = $monthlyData->firstWhere('month_key', $key); $fees = $monthlyFees->get($key, 0); - $hppVal = (int) $monthlyHpp->get($key, 0); $discount = (int) ($revenue->total_discount ?? 0); - $deduction = $discount + $fees + $hppVal; + $deduction = $discount + $fees; $result[] = [ 'month' => $monthLabel, @@ -361,7 +360,7 @@ public function getExpenseSummary(?Carbon $startDate = null, ?Carbon $endDate = ->first(); $purchaseTotal = (int) ($purchase->total ?? 0); - if ($user?->hasRole(Role::ADMIN_TOKO->value)) { + if (! $user?->hasAnyRole([Role::DEVELOPER->value, Role::OWNER->value])) { $purchaseTotal = 0; } $expenseTotal = (int) ($expenses->total ?? 0); @@ -428,7 +427,7 @@ public function getMonthlyExpense(?Carbon $startDate = null, ?Carbon $endDate = $monthLabel = $current->locale('id')->translatedFormat('M Y'); $purchaseAmount = (int) ($purchases->firstWhere('month_key', $key)->total ?? 0); - if ($user?->hasRole(Role::ADMIN_TOKO->value)) { + if (! $user?->hasAnyRole([Role::DEVELOPER->value, Role::OWNER->value])) { $purchaseAmount = 0; } $expenseAmount = (int) ($expenses->firstWhere('month_key', $key)->total ?? 0); @@ -518,7 +517,7 @@ public function getProfitMetrics(?Carbon $startDate = null, ?Carbon $endDate = n ->when(! $isSuper, fn ($q) => $q->where('purchases.created_by_id', $user->id)) ->sum('total'); - if ($user?->hasRole(Role::ADMIN_TOKO->value)) { + if (! $user?->hasAnyRole([Role::DEVELOPER->value, Role::OWNER->value])) { $purchaseTotal = 0; } @@ -536,7 +535,7 @@ public function getProfitMetrics(?Carbon $startDate = null, ?Carbon $endDate = n $totalExpenses = (int) $purchaseTotal + (int) $expenseTotal + (int) $advanceTotal; $labaKotor = $totalRevenue - $totalHpp - $totalDiscount - $marketplaceFees; - $labaBersih = $labaKotor - $totalExpenses; + $labaBersih = $labaKotor - (int) $expenseTotal - (int) $advanceTotal; $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; @@ -672,6 +671,37 @@ public function getTopProducts(?Carbon $startDate = null, ?Carbon $endDate = nul ->toArray(); } + public function getMarketingSales(?Carbon $startDate = null, ?Carbon $endDate = null): array + { + return Order::query() + ->completed() + ->whereNotNull('marketing_id') + ->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate])) + ->join('users', 'orders.marketing_id', '=', 'users.id') + ->leftJoin('user_profiles', 'users.id', '=', 'user_profiles.user_id') + ->selectRaw(' + users.id as marketing_id, + COALESCE(user_profiles.full_name, users.username) as marketing_name, + COUNT(*) as total_orders, + SUM(orders.total_amount) as total_revenue, + SUM(orders.subtotal) as total_subtotal, + SUM(orders.discount) as total_discount, + AVG(orders.total_amount) as avg_order + ') + ->groupBy('users.id', 'user_profiles.full_name', 'users.username') + ->orderByDesc('total_revenue') + ->get() + ->map(fn ($item) => [ + 'marketing_name' => $item->marketing_name, + 'total_orders' => (int) $item->total_orders, + 'total_revenue' => (int) $item->total_revenue, + 'total_subtotal' => (int) $item->total_subtotal, + 'total_discount' => (int) $item->total_discount, + 'avg_order' => (int) $item->avg_order, + ]) + ->toArray(); + } + public function isManager(?User $user): bool { return $user?->hasAnyRole([Role::OWNER->value, Role::DEVELOPER->value, Role::DIREKTUR->value]) ?? false; diff --git a/app/Services/System/Setting/MarketplaceService.php b/app/Services/System/Setting/MarketplaceService.php index fded44c..a3d3d8b 100644 --- a/app/Services/System/Setting/MarketplaceService.php +++ b/app/Services/System/Setting/MarketplaceService.php @@ -118,7 +118,7 @@ public function updateMarketplace(array $validated, User $user): void $this->pushNotificationService->sendToRoles( '⚙️ Pengaturan Marketplace Menunggu Persetujuan Owner', "Pengajuan ubah pengaturan marketplace oleh '{$user->username}' menunggu verifikasi owner.", - ['owner', 'developer'], + ['owner', 'developer', 'direktur'], route('admin.system.settings.index'), ); diff --git a/resources/js/pages/admin/Analysis.vue b/resources/js/pages/admin/Analysis.vue index 0c519f2..8edc6f5 100644 --- a/resources/js/pages/admin/Analysis.vue +++ b/resources/js/pages/admin/Analysis.vue @@ -22,7 +22,7 @@ import AdminLayout from '@/layouts/AdminLayout.vue'; import { formatRupiah, formatRupiahShort } from '@/lib/rupiah'; import admin from '@/routes/admin'; -const { can, hasRole } = useCan(); +const { can, hasRole, hasAnyRole } = useCan(); const props = defineProps<{ filters: { @@ -123,6 +123,14 @@ const props = defineProps<{ total_qty: number; total_revenue: number; }>; + marketingSales: Array<{ + marketing_name: string; + total_orders: number; + total_revenue: number; + total_subtotal: number; + total_discount: number; + avg_order: number; + }>; }>(); const startDate = ref(props.filters.start_date ?? ''); @@ -222,17 +230,18 @@ const expenseTotals = computed(() => ({ const visibleExpenseCharts = computed(() => { const charts = ['total', 'purchase', 'expense', 'advance'] as const; - if (hasRole('admin-toko')) { - return charts.filter((c) => c !== 'purchase'); + if (hasAnyRole(['owner', 'developer'])) { + return charts; } - return charts; + return charts.filter((c) => c !== 'purchase'); }); const expenseYAccessors = computed(() => { - if (hasRole('admin-toko')) { + if (hasAnyRole(['owner', 'developer'])) { return [ (d: MonthlyExpenseData) => d.total, + (d: MonthlyExpenseData) => d.purchase, (d: MonthlyExpenseData) => d.expense, (d: MonthlyExpenseData) => d.advance, ]; @@ -240,16 +249,16 @@ const expenseYAccessors = computed(() => { return [ (d: MonthlyExpenseData) => d.total, - (d: MonthlyExpenseData) => d.purchase, (d: MonthlyExpenseData) => d.expense, (d: MonthlyExpenseData) => d.advance, ]; }); const expenseColors = computed(() => { - if (hasRole('admin-toko')) { + if (hasAnyRole(['owner', 'developer'])) { return [ expenseChartConfig.total.color, + expenseChartConfig.purchase.color, expenseChartConfig.expense.color, expenseChartConfig.advance.color, ]; @@ -257,7 +266,6 @@ const expenseColors = computed(() => { return [ expenseChartConfig.total.color, - expenseChartConfig.purchase.color, expenseChartConfig.expense.color, expenseChartConfig.advance.color, ]; @@ -739,8 +747,8 @@ watch([startDate, endDate], () => { value: 'Rp' + formatRupiah(revenueSummary.total_revenue), }, { - label: 'Pengeluaran', - value: 'Rp' + formatRupiah(expenseSummary.total), + label: 'HPP', + value: 'Rp' + formatRupiah(profitMetrics.hpp), }, { label: 'Laba Bersih', @@ -832,6 +840,43 @@ watch([startDate, endDate], () => { + + + + + Penjualan Marketing + Rekap penjualan per marketing + + +
+ + + + + + + + + + + + + + + + + + + + + +
MarketingTotal OrderTotal PendapatanTotal SubtotalTotal DiskonRata-rata Order
{{ item.marketing_name }}{{ item.total_orders.toLocaleString('id-ID') }}Rp{{ formatRupiah(item.total_revenue) }}Rp{{ formatRupiah(item.total_subtotal) }}Rp{{ formatRupiah(item.total_discount) }}Rp{{ formatRupiah(item.avg_order) }}
+
+
+ Belum ada data penjualan marketing +
+
+