feat: add payroll calculations to analysis and visualize revenue vs profit trends
This commit is contained in:
parent
a280eb8a64
commit
51f2e65bd2
@ -8,6 +8,7 @@
|
||||
use App\Models\Expense;
|
||||
use App\Models\Order;
|
||||
use App\Models\OrderItem;
|
||||
use App\Models\Payroll;
|
||||
use App\Models\Purchase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Inertia\Inertia;
|
||||
@ -20,6 +21,7 @@ public function __invoke()
|
||||
'total_sales' => $this->getStats(fn () => Order::count()),
|
||||
'total_revenue' => $this->getStats(fn () => Order::sum('total')),
|
||||
'total_expenses' => $this->getStats(fn () => Expense::sum('amount')),
|
||||
'total_payrolls' => $this->getStats(fn () => Payroll::sum('total_salary')),
|
||||
'cogs' => $this->getStats(fn () => Order::sum('hpp')),
|
||||
'total_discount' => $this->getStats(fn () => Order::sum('discount')),
|
||||
'total_purchases' => $this->getStats(fn () => Purchase::sum('total')),
|
||||
@ -29,7 +31,11 @@ public function __invoke()
|
||||
|
||||
$stats['aov'] = $this->calculateAov($stats['total_revenue'], $stats['total_sales']);
|
||||
$stats['gross_profit'] = $this->calculateDiff($stats['total_revenue'], $stats['cogs']);
|
||||
$stats['net_profit'] = $this->calculateDiff($stats['gross_profit'], $stats['total_expenses']);
|
||||
$stats['net_profit'] = [
|
||||
'value' => $stats['gross_profit']['value'] - $stats['total_expenses']['value'] - $stats['total_payrolls']['value'],
|
||||
'yesterday' => null,
|
||||
'change' => null,
|
||||
];
|
||||
$stats['profit_margin'] = $this->calculateMargin($stats['net_profit'], $stats['total_revenue']);
|
||||
|
||||
// Revenue vs Purchases per month (this year)
|
||||
@ -131,9 +137,55 @@ public function __invoke()
|
||||
];
|
||||
});
|
||||
|
||||
// Revenue & Profit per month
|
||||
$ordersByMonth = DB::table('orders')
|
||||
->select(
|
||||
DB::raw('MONTH(created_at) as month'),
|
||||
DB::raw('SUM(total) as revenue'),
|
||||
DB::raw('SUM(hpp) as cogs')
|
||||
)
|
||||
->groupBy(DB::raw('MONTH(created_at)'))
|
||||
->get();
|
||||
|
||||
$expensesByMonth = DB::table('expenses')
|
||||
->select(
|
||||
DB::raw('MONTH(created_at) as month'),
|
||||
DB::raw('SUM(amount) as total')
|
||||
)
|
||||
->groupBy(DB::raw('MONTH(created_at)'))
|
||||
->get();
|
||||
|
||||
$payrollsByMonth = DB::table('payrolls')
|
||||
->select(
|
||||
DB::raw('MONTH(period_month) as month'),
|
||||
DB::raw('SUM(total_salary) as total')
|
||||
)
|
||||
->whereNull('deleted_at')
|
||||
->groupBy(DB::raw('MONTH(period_month)'))
|
||||
->get();
|
||||
|
||||
$revenueVsProfit = collect(range(1, 12))->map(function ($month) use ($ordersByMonth, $expensesByMonth, $payrollsByMonth, $monthNames) {
|
||||
$orderFound = $ordersByMonth->firstWhere('month', $month);
|
||||
$expenseFound = $expensesByMonth->firstWhere('month', $month);
|
||||
$payrollFound = $payrollsByMonth->firstWhere('month', $month);
|
||||
|
||||
$revenue = $orderFound ? (float) $orderFound->revenue : 0;
|
||||
$cogs = $orderFound ? (float) $orderFound->cogs : 0;
|
||||
$expense = $expenseFound ? (float) $expenseFound->total : 0;
|
||||
$payroll = $payrollFound ? (float) $payrollFound->total : 0;
|
||||
$profit = $revenue - $cogs - $expense - $payroll;
|
||||
|
||||
return [
|
||||
'month' => $monthNames[$month - 1],
|
||||
'revenue' => $revenue,
|
||||
'profit' => $profit,
|
||||
];
|
||||
});
|
||||
|
||||
return Inertia::render('analysis', [
|
||||
'stats' => $stats,
|
||||
'salesByMonth' => $salesByMonth,
|
||||
'revenueVsProfit' => $revenueVsProfit,
|
||||
'salesByHour' => $salesByHour,
|
||||
'paymentMethods' => $paymentMethods,
|
||||
'orderStatuses' => $orderStatuses,
|
||||
|
||||
88
resources/js/components/charts/revenue-vs-profit-chart.tsx
Normal file
88
resources/js/components/charts/revenue-vs-profit-chart.tsx
Normal file
@ -0,0 +1,88 @@
|
||||
"use client"
|
||||
|
||||
import { CartesianGrid, Line, LineChart, XAxis } from "recharts"
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle
|
||||
} from "@/components/ui/card"
|
||||
import {
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
type ChartConfig,
|
||||
} from "@/components/ui/chart"
|
||||
import { formatCurrency } from "@/lib/formatters"
|
||||
|
||||
export const description = "Perbandingan Pendapatan vs Laba Bersih per bulan."
|
||||
|
||||
const chartConfig = {
|
||||
revenue: {
|
||||
label: "Pendapatan",
|
||||
color: "var(--chart-1)",
|
||||
},
|
||||
profit: {
|
||||
label: "Laba Bersih",
|
||||
color: "var(--chart-2)",
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export function RevenueVsProfitChart({ data }: { data: any[] }) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Pendapatan vs Laba Bersih</CardTitle>
|
||||
<CardDescription>Perbandingan Pendapatan vs Laba Bersih per bulan.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig} className="aspect-auto h-[300px] w-full">
|
||||
<LineChart
|
||||
accessibilityLayer
|
||||
data={data}
|
||||
margin={{
|
||||
left: 12,
|
||||
right: 12,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={
|
||||
<ChartTooltipContent
|
||||
formatter={(value, name) => [
|
||||
formatCurrency(Number(value)),
|
||||
` - ${chartConfig[name as keyof typeof chartConfig]?.label ?? name}`,
|
||||
]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Line
|
||||
dataKey="revenue"
|
||||
type="monotone"
|
||||
stroke="var(--color-revenue)"
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
/>
|
||||
<Line
|
||||
dataKey="profit"
|
||||
type="monotone"
|
||||
stroke="var(--color-profit)"
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
/>
|
||||
</LineChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@ -4,11 +4,12 @@ import type { AnalysisPageProps } from '@/types';
|
||||
import { Head, usePage } from '@inertiajs/react';
|
||||
import { CustomBarChart } from '../components/charts/bar-chart';
|
||||
import { CustomPieChart } from '../components/charts/pie-chart';
|
||||
import { SalesByHourChart } from '../components/charts/sales-by-hour-chart';
|
||||
import { RevenueVsProfitChart } from '../components/charts/revenue-vs-profit-chart';
|
||||
import { RevenueVsPurchasesChart } from '../components/charts/revenue-vs-purchases-chart';
|
||||
import { SalesByHourChart } from '../components/charts/sales-by-hour-chart';
|
||||
|
||||
export default function Analisa() {
|
||||
const { stats, salesByMonth, salesByHour, paymentMethods, orderStatuses, orderChannels, topProducts, topCustomers } =
|
||||
const { stats, salesByMonth, revenueVsProfit, salesByHour, paymentMethods, orderStatuses, orderChannels, topProducts, topCustomers } =
|
||||
usePage<AnalysisPageProps>().props;
|
||||
|
||||
return (
|
||||
@ -33,6 +34,7 @@ export default function Analisa() {
|
||||
{ title: "Total Diskon", stat: stats.total_discount },
|
||||
{ title: "Laba Kotor", stat: stats.gross_profit },
|
||||
{ title: "Laba Bersih", stat: stats.net_profit },
|
||||
{ title: "Total Gaji", stat: stats.total_payrolls },
|
||||
{ title: "Total Pembelian", stat: stats.total_purchases },
|
||||
{ title: "Produk Terjual", stat: stats.products_sold, isCurrency: false },
|
||||
{ title: "Total Pelanggan", stat: stats.total_customers, isCurrency: false },
|
||||
@ -51,6 +53,9 @@ export default function Analisa() {
|
||||
</div>
|
||||
|
||||
<RevenueVsPurchasesChart data={salesByMonth} />
|
||||
|
||||
<RevenueVsProfitChart data={revenueVsProfit} />
|
||||
|
||||
<SalesByHourChart
|
||||
data={salesByHour}
|
||||
config={{
|
||||
|
||||
@ -16,6 +16,7 @@ export interface DashboardStats {
|
||||
total_discount: StatItem;
|
||||
gross_profit: StatItem;
|
||||
net_profit: StatItem;
|
||||
total_payrolls: StatItem;
|
||||
total_purchases: StatItem;
|
||||
products_sold: StatItem;
|
||||
total_customers: StatItem;
|
||||
@ -31,7 +32,8 @@ export interface AnalysisPageProps {
|
||||
auth: Auth;
|
||||
stats: DashboardStats;
|
||||
salesByMonth: any[];
|
||||
ordersByHour: any[];
|
||||
revenueVsProfit: any[];
|
||||
salesByHour: any[];
|
||||
paymentMethods: any[];
|
||||
orderStatuses: any[];
|
||||
orderChannels: any[];
|
||||
|
||||
Loading…
Reference in New Issue
Block a user