feat: add analysis feature tests and support for cross-database date extraction in AnalysisController

This commit is contained in:
Yoga Pangestu 2026-04-30 09:12:05 +07:00
parent b62dbf32d9
commit 120c5d28e8
2 changed files with 116 additions and 12 deletions

View File

@ -56,21 +56,24 @@ public function __invoke(Request $request)
]; ];
$stats['profit_margin'] = $this->calculateMargin($stats['net_profit'], $stats['total_revenue']); $stats['profit_margin'] = $this->calculateMargin($stats['net_profit'], $stats['total_revenue']);
$isSqlite = DB::getDriverName() === 'sqlite';
$monthSelect = $isSqlite ? "CAST(strftime('%m', created_at) AS INTEGER)" : 'MONTH(created_at)';
// Revenue vs Purchases per month // Revenue vs Purchases per month
$revenueByMonth = $applyFilter(DB::table('orders')) $revenueByMonth = $applyFilter(DB::table('orders'))
->select( ->select(
DB::raw('MONTH(created_at) as month'), DB::raw("$monthSelect as month"),
DB::raw('SUM(total) as total') DB::raw('SUM(total) as total')
) )
->groupBy(DB::raw('MONTH(created_at)')) ->groupBy(DB::raw($monthSelect))
->get(); ->get();
$purchasesByMonth = $applyFilter(DB::table('purchases')) $purchasesByMonth = $applyFilter(DB::table('purchases'))
->select( ->select(
DB::raw('MONTH(created_at) as month'), DB::raw("$monthSelect as month"),
DB::raw('SUM(total) as total') DB::raw('SUM(total) as total')
) )
->groupBy(DB::raw('MONTH(created_at)')) ->groupBy(DB::raw($monthSelect))
->get(); ->get();
$monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des']; $monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'];
@ -138,12 +141,14 @@ public function __invoke(Request $request)
->limit(5) ->limit(5)
->get(); ->get();
$hourSelect = $isSqlite ? "CAST(strftime('%H', created_at) AS INTEGER)" : 'HOUR(created_at)';
$salesByHourRaw = $applyFilter(DB::table('orders')) $salesByHourRaw = $applyFilter(DB::table('orders'))
->select( ->select(
DB::raw('HOUR(created_at) as hour'), DB::raw("$hourSelect as hour"),
DB::raw('COUNT(*) as total') DB::raw('COUNT(*) as total')
) )
->groupBy(DB::raw('HOUR(created_at)')) ->groupBy(DB::raw($hourSelect))
->get(); ->get();
$salesByHour = collect(range(0, 23))->map(function ($hour) use ($salesByHourRaw) { $salesByHour = collect(range(0, 23))->map(function ($hour) use ($salesByHourRaw) {
@ -158,29 +163,30 @@ public function __invoke(Request $request)
// Revenue & Profit & Volume per month // Revenue & Profit & Volume per month
$ordersByMonth = $applyFilter(DB::table('orders')) $ordersByMonth = $applyFilter(DB::table('orders'))
->select( ->select(
DB::raw('MONTH(created_at) as month'), DB::raw("$monthSelect as month"),
DB::raw('SUM(total) as revenue'), DB::raw('SUM(total) as revenue'),
DB::raw('SUM(cogs) as cogs'), DB::raw('SUM(cogs) as cogs'),
DB::raw('COUNT(*) as count') DB::raw('COUNT(*) as count')
) )
->groupBy(DB::raw('MONTH(created_at)')) ->groupBy(DB::raw($monthSelect))
->get(); ->get();
$expensesByMonth = $applyFilter(DB::table('expenses')) $expensesByMonth = $applyFilter(DB::table('expenses'))
->select( ->select(
DB::raw('MONTH(created_at) as month'), DB::raw("$monthSelect as month"),
DB::raw('SUM(amount) as total') DB::raw('SUM(amount) as total')
) )
->groupBy(DB::raw('MONTH(created_at)')) ->groupBy(DB::raw($monthSelect))
->get(); ->get();
$monthSelectPeriod = $isSqlite ? "CAST(strftime('%m', period_month) AS INTEGER)" : 'MONTH(period_month)';
$payrollsByMonth = $applyFilter(DB::table('payrolls'), 'period_month') $payrollsByMonth = $applyFilter(DB::table('payrolls'), 'period_month')
->select( ->select(
DB::raw('MONTH(period_month) as month'), DB::raw("$monthSelectPeriod as month"),
DB::raw('SUM(total_salary) as total') DB::raw('SUM(total_salary) as total')
) )
->whereNull('deleted_at') ->whereNull('deleted_at')
->groupBy(DB::raw('MONTH(period_month)')) ->groupBy(DB::raw($monthSelectPeriod))
->get(); ->get();
$revenueVsProfit = collect(range(1, 12))->map(function ($month) use ($ordersByMonth, $expensesByMonth, $payrollsByMonth, $monthNames) { $revenueVsProfit = collect(range(1, 12))->map(function ($month) use ($ordersByMonth, $expensesByMonth, $payrollsByMonth, $monthNames) {

View File

@ -0,0 +1,98 @@
<?php
use App\Models\Expense;
use App\Models\Order;
use App\Models\Purchase;
use function Pest\Laravel\actingAs;
use function Pest\Laravel\get;
/*
|--------------------------------------------------------------------------
| Analysis Module Tests
|--------------------------------------------------------------------------
*/
describe('Analysis Module - Authorization', function () {
it('redirects to login when accessing analysis unauthenticated', function () {
get(route('analysis'))
->assertRedirect(route('login'));
});
it('returns 403 when user has no permission to view analysis', function () {
actingAs(createUnauthorizedUser())
->get(route('analysis'))
->assertStatus(403);
});
});
describe('Analysis Module - Authorized Actions', function () {
beforeEach(function () {
$user = createAuthorizedUser([
'View:Analysis',
]);
actingAs($user);
});
it('can access analysis page and view statistics', function () {
// Create dummy data
Order::factory()->count(2)->create([
'created_at' => now(),
'total' => 150000,
'cogs' => 75000,
]);
Expense::factory()->create([
'created_at' => now(),
'amount' => 10000,
]);
Purchase::factory()->create([
'created_at' => now(),
'total' => 50000,
]);
get(route('analysis'))
->assertOk()
->assertInertia(fn ($page) => $page
->component('analysis')
->has('stats')
->has('salesByMonth')
->has('revenueVsProfit')
->has('transactionVolume')
->has('salesByHour')
->has('paymentMethods')
->has('orderStatuses')
->has('orderChannels')
->has('topProducts')
->has('topCustomers')
->has('topCategories')
);
});
it('can filter analysis by period', function () {
get(route('analysis', ['period' => 'month']))
->assertOk()
->assertInertia(fn ($page) => $page
->component('analysis')
->has('filters', fn ($f) => $f
->where('period', 'month')
->where('start_date', null)
->where('end_date', null)
)
);
});
it('can filter analysis by date range', function () {
get(route('analysis', ['start_date' => '2026-01-01', 'end_date' => '2026-01-31']))
->assertOk()
->assertInertia(fn ($page) => $page
->component('analysis')
->has('filters', fn ($f) => $f
->where('period', 'all')
->where('start_date', '2026-01-01')
->where('end_date', '2026-01-31')
)
);
});
});