feat: add total products sold to revenue summary and update dashboard display for marketing roles
This commit is contained in:
parent
6a196002fb
commit
6e7a4044b6
@ -13,6 +13,7 @@
|
|||||||
use App\Models\Expense;
|
use App\Models\Expense;
|
||||||
use App\Models\LeaveRequest;
|
use App\Models\LeaveRequest;
|
||||||
use App\Models\Order;
|
use App\Models\Order;
|
||||||
|
use App\Models\OrderItem;
|
||||||
use App\Models\Payroll;
|
use App\Models\Payroll;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
@ -154,6 +155,9 @@ public function getRevenueSummary(?User $user = null): array
|
|||||||
$gross = $totalRevenue - $totalCogs;
|
$gross = $totalRevenue - $totalCogs;
|
||||||
$net = $gross - $payrollTotal - $expenseTotal;
|
$net = $gross - $payrollTotal - $expenseTotal;
|
||||||
|
|
||||||
|
$orderIds = (clone $baseQuery)->pluck('id');
|
||||||
|
$totalProductsSold = (int) OrderItem::whereIn('order_id', $orderIds)->sum('quantity');
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'total_revenue' => $totalRevenue,
|
'total_revenue' => $totalRevenue,
|
||||||
'total_discount' => (int) $stats->total_discount,
|
'total_discount' => (int) $stats->total_discount,
|
||||||
@ -164,6 +168,7 @@ public function getRevenueSummary(?User $user = null): array
|
|||||||
'payroll_total' => $payrollTotal,
|
'payroll_total' => $payrollTotal,
|
||||||
'expense_total' => $expenseTotal,
|
'expense_total' => $expenseTotal,
|
||||||
'total_orders' => (int) $stats->total_orders,
|
'total_orders' => (int) $stats->total_orders,
|
||||||
|
'total_products_sold' => $totalProductsSold,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -483,6 +483,7 @@ export default function Analysis({
|
|||||||
monthlyRetailRevenue,
|
monthlyRetailRevenue,
|
||||||
}: AnalysisProps) {
|
}: AnalysisProps) {
|
||||||
const { can, hasAnyRole, hasRole } = useCan();
|
const { can, hasAnyRole, hasRole } = useCan();
|
||||||
|
const isMarketing = hasAnyRole(['marketing-offline', 'marketing-online']);
|
||||||
const [startDate, setStartDate] = useState(initialFilters.start_date ?? '');
|
const [startDate, setStartDate] = useState(initialFilters.start_date ?? '');
|
||||||
const [endDate, setEndDate] = useState(initialFilters.end_date ?? '');
|
const [endDate, setEndDate] = useState(initialFilters.end_date ?? '');
|
||||||
const [selectedPreset, setSelectedPreset] = useState('');
|
const [selectedPreset, setSelectedPreset] = useState('');
|
||||||
@ -591,8 +592,8 @@ export default function Analysis({
|
|||||||
return { statCards: 1, revenue: 2, revenueByChannel: 3, orderPie: 4, totalOrder: 5, topProducts: 6, topCustomers: 7 };
|
return { statCards: 1, revenue: 2, revenueByChannel: 3, orderPie: 4, totalOrder: 5, topProducts: 6, topCustomers: 7 };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasRole('marketing')) {
|
if (isMarketing) {
|
||||||
return { statCards: 1, revenue: 2, revenueByChannel: 3, orderPie: 4, totalOrder: 5, revenueTrend: 6, topProducts: 7, topCustomers: 8 };
|
return { statCards: 1, revenueByChannel: 3, orderPie: 4, totalOrder: 5, revenueTrend: 6, topProducts: 7, topCustomers: 8 };
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
@ -753,7 +754,7 @@ export default function Analysis({
|
|||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{can('analysis.revenue') && (
|
{can('analysis.revenue') && !isMarketing && (
|
||||||
<Card className="py-0" style={{ order: sectionOrder.revenue ?? 99 }}>
|
<Card className="py-0" style={{ order: sectionOrder.revenue ?? 99 }}>
|
||||||
<CardHeader className="flex flex-col items-stretch border-b p-0! sm:flex-row">
|
<CardHeader className="flex flex-col items-stretch border-b p-0! sm:flex-row">
|
||||||
<div className="flex flex-1 flex-col justify-center gap-1 px-6 pt-4 pb-3 sm:py-0!">
|
<div className="flex flex-1 flex-col justify-center gap-1 px-6 pt-4 pb-3 sm:py-0!">
|
||||||
@ -911,7 +912,7 @@ export default function Analysis({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{can('analysis.revenue') && (
|
{can('analysis.revenue') && !isMarketing && (
|
||||||
<Card className="py-0" style={{ order: 3 }}>
|
<Card className="py-0" style={{ order: 3 }}>
|
||||||
<CardHeader className="flex flex-col items-stretch border-b p-0! sm:flex-row">
|
<CardHeader className="flex flex-col items-stretch border-b p-0! sm:flex-row">
|
||||||
<div className="flex flex-1 flex-col justify-center gap-1 px-6 pt-4 pb-3 sm:py-0!">
|
<div className="flex flex-1 flex-col justify-center gap-1 px-6 pt-4 pb-3 sm:py-0!">
|
||||||
|
|||||||
@ -47,7 +47,8 @@ export function TransactionCardRow({
|
|||||||
onUpdateStatus,
|
onUpdateStatus,
|
||||||
onPrint,
|
onPrint,
|
||||||
}: TransactionCardRowParams) {
|
}: TransactionCardRowParams) {
|
||||||
const { can } = useCan();
|
const { can, hasAnyRole } = useCan();
|
||||||
|
const isMarketing = hasAnyRole(['marketing-offline', 'marketing-online']);
|
||||||
const [refundDialogOpen, setRefundDialogOpen] = useState(false);
|
const [refundDialogOpen, setRefundDialogOpen] = useState(false);
|
||||||
const [cancelDialogOpen, setCancelDialogOpen] = useState(false);
|
const [cancelDialogOpen, setCancelDialogOpen] = useState(false);
|
||||||
const variantCount = transaction.items_count ?? 0;
|
const variantCount = transaction.items_count ?? 0;
|
||||||
@ -188,18 +189,22 @@ export function TransactionCardRow({
|
|||||||
</span>
|
</span>
|
||||||
{formatCurrency(transaction.total_amount)}
|
{formatCurrency(transaction.total_amount)}
|
||||||
</span>
|
</span>
|
||||||
<span className={transaction.profit >= 0 ? 'text-green-600' : 'text-red-600'}>
|
{!isMarketing && (
|
||||||
<span className="font-normal text-muted-foreground">
|
<span className={transaction.profit >= 0 ? 'text-green-600' : 'text-red-600'}>
|
||||||
Laba:{' '}
|
<span className="font-normal text-muted-foreground">
|
||||||
|
Laba:{' '}
|
||||||
|
</span>
|
||||||
|
{formatCurrency(transaction.profit)}
|
||||||
</span>
|
</span>
|
||||||
{formatCurrency(transaction.profit)}
|
)}
|
||||||
</span>
|
{!isMarketing && (
|
||||||
<span>
|
<span>
|
||||||
<span className="text-muted-foreground">
|
<span className="text-muted-foreground">
|
||||||
HPP:{' '}
|
HPP:{' '}
|
||||||
|
</span>
|
||||||
|
{formatCurrency(transaction.cogs)}
|
||||||
</span>
|
</span>
|
||||||
{formatCurrency(transaction.cogs)}
|
)}
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{transaction.photo_url && (
|
{transaction.photo_url && (
|
||||||
|
|||||||
@ -53,6 +53,7 @@ type DashboardProps = {
|
|||||||
payroll_total: number;
|
payroll_total: number;
|
||||||
expense_total: number;
|
expense_total: number;
|
||||||
total_orders: number;
|
total_orders: number;
|
||||||
|
total_products_sold: number;
|
||||||
};
|
};
|
||||||
expenseSummary: {
|
expenseSummary: {
|
||||||
total: number;
|
total: number;
|
||||||
@ -97,7 +98,8 @@ export default function Dashboard({
|
|||||||
isOnLeave,
|
isOnLeave,
|
||||||
canCheckIn,
|
canCheckIn,
|
||||||
}: DashboardProps) {
|
}: DashboardProps) {
|
||||||
const { can, hasRole } = useCan();
|
const { can, hasRole, hasAnyRole } = useCan();
|
||||||
|
const isMarketing = hasAnyRole(['marketing-offline', 'marketing-online']);
|
||||||
const { auth } = usePage().props as { auth: { user?: { username?: string } } };
|
const { auth } = usePage().props as { auth: { user?: { username?: string } } };
|
||||||
const [currentTime, setCurrentTime] = useState(new Date());
|
const [currentTime, setCurrentTime] = useState(new Date());
|
||||||
const [showCamera, setShowCamera] = useState(false);
|
const [showCamera, setShowCamera] = useState(false);
|
||||||
@ -336,37 +338,46 @@ export default function Dashboard({
|
|||||||
mainLabel="Total"
|
mainLabel="Total"
|
||||||
mainValue={`Rp${formatRupiah(revenueSummary.total_revenue)}`}
|
mainValue={`Rp${formatRupiah(revenueSummary.total_revenue)}`}
|
||||||
subLabel={`${revenueSummary.total_orders} transaksi selesai`}
|
subLabel={`${revenueSummary.total_orders} transaksi selesai`}
|
||||||
items={[
|
items={
|
||||||
{
|
isMarketing
|
||||||
label: 'Kotor',
|
? [
|
||||||
value: `Rp${formatRupiah(revenueSummary.gross)}`,
|
{
|
||||||
},
|
label: 'Produk Terjual',
|
||||||
{
|
value: revenueSummary.total_products_sold.toLocaleString('id-ID'),
|
||||||
label: 'Bersih',
|
},
|
||||||
value: `Rp${formatRupiah(revenueSummary.net)}`,
|
]
|
||||||
},
|
: [
|
||||||
{
|
{
|
||||||
label: 'Gaji',
|
label: 'Kotor',
|
||||||
value: `Rp${formatRupiah(revenueSummary.payroll_total)}`,
|
value: `Rp${formatRupiah(revenueSummary.gross)}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Pengeluaran',
|
label: 'Bersih',
|
||||||
value: `Rp${formatRupiah(revenueSummary.expense_total)}`,
|
value: `Rp${formatRupiah(revenueSummary.net)}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Potongan',
|
label: 'Gaji',
|
||||||
value: `Rp${formatRupiah(revenueSummary.total_deduction)}`,
|
value: `Rp${formatRupiah(revenueSummary.payroll_total)}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Diskon',
|
label: 'Pengeluaran',
|
||||||
value: `Rp${formatRupiah(revenueSummary.total_discount)}`,
|
value: `Rp${formatRupiah(revenueSummary.expense_total)}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'HPP',
|
label: 'Potongan',
|
||||||
value: `Rp${formatRupiah(revenueSummary.total_cogs)}`,
|
value: `Rp${formatRupiah(revenueSummary.total_deduction)}`,
|
||||||
},
|
},
|
||||||
]}
|
{
|
||||||
cols={visibleStatsCount === 1 ? 4 : visibleStatsCount === 2 ? 3 : 2}
|
label: 'Diskon',
|
||||||
|
value: `Rp${formatRupiah(revenueSummary.total_discount)}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'HPP',
|
||||||
|
value: `Rp${formatRupiah(revenueSummary.total_cogs)}`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
cols={isMarketing ? 2 : visibleStatsCount === 1 ? 4 : visibleStatsCount === 2 ? 3 : 2}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user