feat: Enhance leave request and transaction management with user-specific data
- Added current user ID to leave request index for better context. - Updated transaction management to include user-specific filters and summaries. - Enhanced analysis service methods to incorporate user context for attendance and revenue metrics. - Modified dashboard service to reflect user-specific attendance and financial summaries. - Implemented role-based access control in leave request service for update and delete actions. - Adjusted frontend components to utilize user-specific data for leave requests and transactions. - Removed unused permissions from role seeder for cleaner access control.
This commit is contained in:
parent
5e07a1b910
commit
8053ea2db4
@ -29,6 +29,7 @@ public function index(PaginatedRequest $request): Response
|
|||||||
'filterOptions' => [
|
'filterOptions' => [
|
||||||
'statusOptions' => LeaveRequestStatus::toSelect(),
|
'statusOptions' => LeaveRequestStatus::toSelect(),
|
||||||
],
|
],
|
||||||
|
'currentUserId' => $request->user()->id,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -28,13 +28,15 @@ public function __construct(
|
|||||||
public function index(PaginatedRequest $request): Response
|
public function index(PaginatedRequest $request): Response
|
||||||
{
|
{
|
||||||
$filters = $request->only(['status', 'channel', 'payment_type', 'customer_id', 'marketing_id', 'created_by_id', 'date_from', 'date_to']);
|
$filters = $request->only(['status', 'channel', 'payment_type', 'customer_id', 'marketing_id', 'created_by_id', 'date_from', 'date_to']);
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
return Inertia::render('admin/manage/transaction/index', [
|
return Inertia::render('admin/manage/transaction/index', [
|
||||||
'transactions' => $this->service->paginated(
|
'transactions' => $this->service->paginated(
|
||||||
...$request->validatedWithDefaults(),
|
...$request->validatedWithDefaults(),
|
||||||
filters: $filters,
|
filters: $filters,
|
||||||
|
user: $user,
|
||||||
),
|
),
|
||||||
'summary' => $this->service->getSummary($filters),
|
'summary' => $this->service->getSummary($filters, $user),
|
||||||
'filters' => $filters,
|
'filters' => $filters,
|
||||||
'filterOptions' => $this->service->getFilterOptions(),
|
'filterOptions' => $this->service->getFilterOptions(),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -25,25 +25,25 @@ public function index(Request $request): Response
|
|||||||
$startDate = $request->input('start_date');
|
$startDate = $request->input('start_date');
|
||||||
$endDate = $request->input('end_date');
|
$endDate = $request->input('end_date');
|
||||||
|
|
||||||
$attendance = $this->service->getAttendanceStats($startDate, $endDate);
|
$attendance = $this->service->getAttendanceStats($startDate, $endDate, $user);
|
||||||
$myAttendance = $this->service->getMyAttendance($user, $startDate, $endDate);
|
$myAttendance = $this->service->getMyAttendance($user, $startDate, $endDate);
|
||||||
$cashOverview = $this->service->getCashOverview($startDate, $endDate);
|
$cashOverview = $this->service->getCashOverview($startDate, $endDate);
|
||||||
$rawMaterialStock = $this->service->getRawMaterialStock();
|
$rawMaterialStock = $this->service->getRawMaterialStock();
|
||||||
$productStock = $this->service->getProductStock();
|
$productStock = $this->service->getProductStock();
|
||||||
$revenueSummary = $this->service->getRevenueSummary($startDate, $endDate);
|
$revenueSummary = $this->service->getRevenueSummary($startDate, $endDate, $user);
|
||||||
$monthlyRevenue = $this->service->getMonthlyRevenue($startDate, $endDate);
|
$monthlyRevenue = $this->service->getMonthlyRevenue($startDate, $endDate, $user);
|
||||||
$monthlyRevenueByChannel = $this->service->getMonthlyRevenueByChannel($startDate, $endDate);
|
$monthlyRevenueByChannel = $this->service->getMonthlyRevenueByChannel($startDate, $endDate, $user);
|
||||||
$revenueByPaymentType = $this->service->getRevenueByPaymentType($startDate, $endDate);
|
$revenueByPaymentType = $this->service->getRevenueByPaymentType($startDate, $endDate, $user);
|
||||||
$expenseSummary = $this->service->getExpenseSummary($startDate, $endDate);
|
$expenseSummary = $this->service->getExpenseSummary($startDate, $endDate, $user);
|
||||||
$monthlyExpense = $this->service->getMonthlyExpense($startDate, $endDate);
|
$monthlyExpense = $this->service->getMonthlyExpense($startDate, $endDate, $user);
|
||||||
$busyHours = $this->service->getBusyHours($startDate, $endDate);
|
$busyHours = $this->service->getBusyHours($startDate, $endDate, $user);
|
||||||
$profitMetrics = $this->service->getProfitMetrics($startDate, $endDate);
|
$profitMetrics = $this->service->getProfitMetrics($startDate, $endDate, $user);
|
||||||
$topSuppliers = $this->service->getTopSuppliers($startDate, $endDate);
|
$topSuppliers = $this->service->getTopSuppliers($startDate, $endDate);
|
||||||
$topCustomers = $this->service->getTopCustomers($startDate, $endDate);
|
$topCustomers = $this->service->getTopCustomers($startDate, $endDate, $user);
|
||||||
$topProducts = $this->service->getTopProducts($startDate, $endDate);
|
$topProducts = $this->service->getTopProducts($startDate, $endDate, $user);
|
||||||
$marketingSales = $this->service->getMarketingSales($startDate, $endDate);
|
$marketingSales = $this->service->getMarketingSales($startDate, $endDate, $user);
|
||||||
$orderStats = $this->service->getOrderStats($startDate, $endDate);
|
$orderStats = $this->service->getOrderStats($startDate, $endDate, $user);
|
||||||
$revenueTrend = $this->service->getRevenueTrend($startDate, $endDate);
|
$revenueTrend = $this->service->getRevenueTrend($startDate, $endDate, $user);
|
||||||
|
|
||||||
return Inertia::render('admin/analysis/index', [
|
return Inertia::render('admin/analysis/index', [
|
||||||
'filters' => [
|
'filters' => [
|
||||||
|
|||||||
@ -18,10 +18,10 @@ public function __invoke(Request $request): Response
|
|||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
|
||||||
return Inertia::render('dashboard', [
|
return Inertia::render('dashboard', [
|
||||||
'attendance' => $this->service->getAttendanceStats(),
|
'attendance' => $this->service->getAttendanceStats($user),
|
||||||
'revenueSummary' => $this->service->getRevenueSummary(),
|
'revenueSummary' => $this->service->getRevenueSummary($user),
|
||||||
'expenseSummary' => $this->service->getExpenseSummary(),
|
'expenseSummary' => $this->service->getExpenseSummary($user),
|
||||||
'orderStats' => $this->service->getOrderStats(),
|
'orderStats' => $this->service->getOrderStats($user),
|
||||||
'todayAttendance' => $this->service->getTodayAttendance($user),
|
'todayAttendance' => $this->service->getTodayAttendance($user),
|
||||||
'isOnLeave' => $this->service->isOnLeave($user),
|
'isOnLeave' => $this->service->isOnLeave($user),
|
||||||
'canCheckIn' => $user->employee !== null,
|
'canCheckIn' => $user->employee !== null,
|
||||||
|
|||||||
@ -69,6 +69,12 @@ public function store(array $data): LeaveRequest
|
|||||||
|
|
||||||
public function update(LeaveRequest $leaveRequest, array $data): LeaveRequest
|
public function update(LeaveRequest $leaveRequest, array $data): LeaveRequest
|
||||||
{
|
{
|
||||||
|
if (! $this->canModify($leaveRequest)) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'employee' => 'Anda tidak memiliki akses untuk mengubah cuti ini.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
return DB::transaction(function () use ($leaveRequest, $data) {
|
return DB::transaction(function () use ($leaveRequest, $data) {
|
||||||
$startDate = new Carbon($data['start_date']);
|
$startDate = new Carbon($data['start_date']);
|
||||||
$endDate = new Carbon($data['end_date']);
|
$endDate = new Carbon($data['end_date']);
|
||||||
@ -86,6 +92,12 @@ public function update(LeaveRequest $leaveRequest, array $data): LeaveRequest
|
|||||||
|
|
||||||
public function destroy(LeaveRequest $leaveRequest): bool
|
public function destroy(LeaveRequest $leaveRequest): bool
|
||||||
{
|
{
|
||||||
|
if (! $this->canModify($leaveRequest)) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'employee' => 'Anda tidak memiliki akses untuk menghapus cuti ini.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
return $leaveRequest->delete();
|
return $leaveRequest->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,4 +142,13 @@ public function reject(LeaveRequest $leaveRequest): LeaveRequest
|
|||||||
|
|
||||||
return $leaveRequest;
|
return $leaveRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function canModify(LeaveRequest $leaveRequest): bool
|
||||||
|
{
|
||||||
|
if (self::hasAnyRole([Role::DEVELOPER, Role::OWNER])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $leaveRequest->employee->user_id === auth()->id();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,7 +39,7 @@ public function __construct(
|
|||||||
private S3PresignedService $s3Service,
|
private S3PresignedService $s3Service,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = []): LengthAwarePaginator
|
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = [], ?User $user = null): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
$paginator = Order::query()
|
$paginator = Order::query()
|
||||||
->select(['id', 'created_by_id', 'customer_id', 'marketing_id', 'order_number', 'channel', 'price_type', 'status', 'payment_type', 'tiktok_order_id', 'shopee_order_id', 'subtotal', 'discount', 'nego_price', 'total_amount', 'cogs', 'notes', 'created_at'])
|
->select(['id', 'created_by_id', 'customer_id', 'marketing_id', 'order_number', 'channel', 'price_type', 'status', 'payment_type', 'tiktok_order_id', 'shopee_order_id', 'subtotal', 'discount', 'nego_price', 'total_amount', 'cogs', 'notes', 'created_at'])
|
||||||
@ -53,6 +53,7 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
|
|||||||
'orderItems.productVariant:id,product_id,name,stock,reject_stock,retail_stock',
|
'orderItems.productVariant:id,product_id,name,stock,reject_stock,retail_stock',
|
||||||
'orderItems.productVariant.product:id,name',
|
'orderItems.productVariant.product:id,name',
|
||||||
])
|
])
|
||||||
|
->when($user && $this->isMarketingUser($user), fn ($q) => $q->where('marketing_id', $user->id))
|
||||||
->when($search, function ($q) use ($search) {
|
->when($search, function ($q) use ($search) {
|
||||||
$q->whereHas('orderItems.productVariant.product', fn ($sq) => $sq->where('name', 'like', "%{$search}%"))
|
$q->whereHas('orderItems.productVariant.product', fn ($sq) => $sq->where('name', 'like', "%{$search}%"))
|
||||||
->orWhere('order_number', 'like', "%{$search}%")
|
->orWhere('order_number', 'like', "%{$search}%")
|
||||||
@ -103,14 +104,15 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
|
|||||||
return $paginator;
|
return $paginator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSummary(array $filters = []): array
|
public function getSummary(array $filters = [], ?User $user = null): array
|
||||||
{
|
{
|
||||||
$query = Order::query()
|
$query = Order::query()
|
||||||
->selectRaw('COUNT(*) as total_orders')
|
->selectRaw('COUNT(*) as total_orders')
|
||||||
->selectRaw('COALESCE(SUM(subtotal), 0) as total_subtotal')
|
|
||||||
->selectRaw('COALESCE(SUM(discount) + SUM(COALESCE(nego_price, 0)), 0) as total_discount')
|
|
||||||
->selectRaw('COALESCE(SUM(total_amount), 0) as total_amount')
|
->selectRaw('COALESCE(SUM(total_amount), 0) as total_amount')
|
||||||
|
->selectRaw('COALESCE(SUM(discount), 0) as total_discount')
|
||||||
|
->selectRaw('COALESCE(SUM(nego_price), 0) as total_deduction')
|
||||||
->selectRaw('COALESCE(SUM(cogs), 0) as total_cogs')
|
->selectRaw('COALESCE(SUM(cogs), 0) as total_cogs')
|
||||||
|
->when($user && $this->isMarketingUser($user), fn ($q) => $q->where('marketing_id', $user->id))
|
||||||
->when($filters['status'] ?? null, fn ($q, $status) => $q->where('status', $status))
|
->when($filters['status'] ?? null, fn ($q, $status) => $q->where('status', $status))
|
||||||
->when($filters['channel'] ?? null, fn ($q, $channel) => $q->where('channel', $channel))
|
->when($filters['channel'] ?? null, fn ($q, $channel) => $q->where('channel', $channel))
|
||||||
->when($filters['payment_type'] ?? null, fn ($q, $paymentType) => $q->where('payment_type', $paymentType))
|
->when($filters['payment_type'] ?? null, fn ($q, $paymentType) => $q->where('payment_type', $paymentType))
|
||||||
@ -123,9 +125,9 @@ public function getSummary(array $filters = []): array
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'total_orders' => $query->total_orders,
|
'total_orders' => $query->total_orders,
|
||||||
'total_subtotal' => $query->total_subtotal,
|
|
||||||
'total_discount' => $query->total_discount,
|
|
||||||
'total_amount' => $query->total_amount,
|
'total_amount' => $query->total_amount,
|
||||||
|
'total_discount' => $query->total_discount,
|
||||||
|
'total_deduction' => $query->total_deduction,
|
||||||
'net_total' => $query->total_amount - $query->total_cogs,
|
'net_total' => $query->total_amount - $query->total_cogs,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -397,4 +399,12 @@ private function generateOrderNumber(): string
|
|||||||
|
|
||||||
return $prefix.$date.str_pad($sequence, 4, '0', STR_PAD_LEFT);
|
return $prefix.$date.str_pad($sequence, 4, '0', STR_PAD_LEFT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function isMarketingUser(User $user): bool
|
||||||
|
{
|
||||||
|
return $user->hasAnyRole([
|
||||||
|
Role::MARKETING_OFFLINE->value,
|
||||||
|
Role::MARKETING_ONLINE->value,
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
use App\Enums\OrderStatus;
|
use App\Enums\OrderStatus;
|
||||||
use App\Enums\PaymentType;
|
use App\Enums\PaymentType;
|
||||||
use App\Enums\RawMaterialUnit;
|
use App\Enums\RawMaterialUnit;
|
||||||
|
use App\Enums\Role;
|
||||||
use App\Models\Attendance;
|
use App\Models\Attendance;
|
||||||
use App\Models\CashAccount;
|
use App\Models\CashAccount;
|
||||||
use App\Models\Employee;
|
use App\Models\Employee;
|
||||||
@ -20,15 +21,65 @@
|
|||||||
use App\Models\RestockItem;
|
use App\Models\RestockItem;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class AnalysisService
|
class AnalysisService
|
||||||
{
|
{
|
||||||
public function getAttendanceStats(?string $startDate, ?string $endDate): array
|
public function getAttendanceStats(?string $startDate, ?string $endDate, ?User $user = null): array
|
||||||
{
|
{
|
||||||
$start = $startDate ? Carbon::parse($startDate) : Carbon::now()->startOfMonth();
|
$start = $startDate ? Carbon::parse($startDate) : Carbon::now()->startOfMonth();
|
||||||
$end = $endDate ? Carbon::parse($endDate) : Carbon::now();
|
$end = $endDate ? Carbon::parse($endDate) : Carbon::now();
|
||||||
|
|
||||||
|
if ($user && $this->isMarketingUser($user)) {
|
||||||
|
$employee = $user->employee;
|
||||||
|
|
||||||
|
if (! $employee) {
|
||||||
|
return [
|
||||||
|
'total_employees' => 0,
|
||||||
|
'present' => 0,
|
||||||
|
'absent' => 0,
|
||||||
|
'on_leave' => 0,
|
||||||
|
'percentage' => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$isWithinRange = $employee->join_date <= $end
|
||||||
|
&& (is_null($employee->resign_date) || $employee->resign_date >= $start);
|
||||||
|
|
||||||
|
if (! $isWithinRange) {
|
||||||
|
return [
|
||||||
|
'total_employees' => 0,
|
||||||
|
'present' => 0,
|
||||||
|
'absent' => 0,
|
||||||
|
'on_leave' => 0,
|
||||||
|
'percentage' => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$present = Attendance::where('employee_id', $employee->id)
|
||||||
|
->whereBetween('attendance_date', [$start->toDateString(), $end->toDateString()])
|
||||||
|
->distinct('employee_id')
|
||||||
|
->count('employee_id');
|
||||||
|
|
||||||
|
$onLeave = LeaveRequest::approved()
|
||||||
|
->where('start_date', '<=', $end)
|
||||||
|
->where('end_date', '>=', $start)
|
||||||
|
->where('employee_id', $employee->id)
|
||||||
|
->count();
|
||||||
|
|
||||||
|
$workingDays = $this->countWorkingDays($start, $end);
|
||||||
|
$absent = max(0, $workingDays - $present - $onLeave);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'total_employees' => 1,
|
||||||
|
'present' => $present,
|
||||||
|
'absent' => $absent,
|
||||||
|
'on_leave' => $onLeave,
|
||||||
|
'percentage' => $workingDays > 0 ? round(($present / $workingDays) * 100) : 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
$employees = Employee::whereHas('user', fn ($q) => $q
|
$employees = Employee::whereHas('user', fn ($q) => $q
|
||||||
->where('is_active', true)
|
->where('is_active', true)
|
||||||
->whereHas('roles', fn ($r) => $r
|
->whereHas('roles', fn ($r) => $r
|
||||||
@ -83,7 +134,7 @@ public function getMyAttendance(User $user, ?string $startDate, ?string $endDate
|
|||||||
$current = $start->copy();
|
$current = $start->copy();
|
||||||
|
|
||||||
while ($current->lte($end)) {
|
while ($current->lte($end)) {
|
||||||
if (! in_array($current->dayOfWeek, [Carbon::SATURDAY, Carbon::SUNDAY])) {
|
if ($current->dayOfWeek !== Carbon::SUNDAY) {
|
||||||
$workingDays++;
|
$workingDays++;
|
||||||
}
|
}
|
||||||
$current->addDay();
|
$current->addDay();
|
||||||
@ -189,11 +240,15 @@ public function getProductStock(): array
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRevenueSummary(?string $startDate, ?string $endDate): array
|
public function getRevenueSummary(?string $startDate, ?string $endDate, ?User $user = null): array
|
||||||
{
|
{
|
||||||
$query = Order::where('orders.status', OrderStatus::COMPLETED);
|
$query = Order::where('orders.status', OrderStatus::COMPLETED);
|
||||||
$this->applyDateFilter($query, $startDate, $endDate, 'orders.created_at');
|
$this->applyDateFilter($query, $startDate, $endDate, 'orders.created_at');
|
||||||
|
|
||||||
|
if ($user && $this->isMarketingUser($user)) {
|
||||||
|
$query->where('orders.marketing_id', $user->id);
|
||||||
|
}
|
||||||
|
|
||||||
$stats = (clone $query)
|
$stats = (clone $query)
|
||||||
->selectRaw('COUNT(*) as total_orders')
|
->selectRaw('COUNT(*) as total_orders')
|
||||||
->selectRaw('COALESCE(SUM(total_amount), 0) as total_revenue')
|
->selectRaw('COALESCE(SUM(total_amount), 0) as total_revenue')
|
||||||
@ -213,11 +268,15 @@ public function getRevenueSummary(?string $startDate, ?string $endDate): array
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMonthlyRevenue(?string $startDate, ?string $endDate): array
|
public function getMonthlyRevenue(?string $startDate, ?string $endDate, ?User $user = null): array
|
||||||
{
|
{
|
||||||
$query = Order::where('orders.status', OrderStatus::COMPLETED);
|
$query = Order::where('orders.status', OrderStatus::COMPLETED);
|
||||||
$this->applyDateFilter($query, $startDate, $endDate, 'orders.created_at');
|
$this->applyDateFilter($query, $startDate, $endDate, 'orders.created_at');
|
||||||
|
|
||||||
|
if ($user && $this->isMarketingUser($user)) {
|
||||||
|
$query->where('orders.marketing_id', $user->id);
|
||||||
|
}
|
||||||
|
|
||||||
$monthly = (clone $query)
|
$monthly = (clone $query)
|
||||||
->selectRaw("DATE_FORMAT(created_at, '%b %Y') as month")
|
->selectRaw("DATE_FORMAT(created_at, '%b %Y') as month")
|
||||||
->selectRaw('COALESCE(SUM(total_amount), 0) as total')
|
->selectRaw('COALESCE(SUM(total_amount), 0) as total')
|
||||||
@ -240,11 +299,15 @@ public function getMonthlyRevenue(?string $startDate, ?string $endDate): array
|
|||||||
return $monthly->values()->toArray();
|
return $monthly->values()->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMonthlyRevenueByChannel(?string $startDate, ?string $endDate): array
|
public function getMonthlyRevenueByChannel(?string $startDate, ?string $endDate, ?User $user = null): array
|
||||||
{
|
{
|
||||||
$query = Order::where('orders.status', OrderStatus::COMPLETED);
|
$query = Order::where('orders.status', OrderStatus::COMPLETED);
|
||||||
$this->applyDateFilter($query, $startDate, $endDate, 'orders.created_at');
|
$this->applyDateFilter($query, $startDate, $endDate, 'orders.created_at');
|
||||||
|
|
||||||
|
if ($user && $this->isMarketingUser($user)) {
|
||||||
|
$query->where('orders.marketing_id', $user->id);
|
||||||
|
}
|
||||||
|
|
||||||
$monthly = (clone $query)
|
$monthly = (clone $query)
|
||||||
->selectRaw("DATE_FORMAT(created_at, '%b %Y') as month")
|
->selectRaw("DATE_FORMAT(created_at, '%b %Y') as month")
|
||||||
->selectRaw("COALESCE(SUM(CASE WHEN channel = 'store' THEN total_amount ELSE 0 END), 0) as store")
|
->selectRaw("COALESCE(SUM(CASE WHEN channel = 'store' THEN total_amount ELSE 0 END), 0) as store")
|
||||||
@ -263,11 +326,15 @@ public function getMonthlyRevenueByChannel(?string $startDate, ?string $endDate)
|
|||||||
return $monthly->values()->toArray();
|
return $monthly->values()->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRevenueByPaymentType(?string $startDate, ?string $endDate): array
|
public function getRevenueByPaymentType(?string $startDate, ?string $endDate, ?User $user = null): array
|
||||||
{
|
{
|
||||||
$query = Order::where('orders.status', OrderStatus::COMPLETED);
|
$query = Order::where('orders.status', OrderStatus::COMPLETED);
|
||||||
$this->applyDateFilter($query, $startDate, $endDate, 'orders.created_at');
|
$this->applyDateFilter($query, $startDate, $endDate, 'orders.created_at');
|
||||||
|
|
||||||
|
if ($user && $this->isMarketingUser($user)) {
|
||||||
|
$query->where('orders.marketing_id', $user->id);
|
||||||
|
}
|
||||||
|
|
||||||
$data = (clone $query)
|
$data = (clone $query)
|
||||||
->select('payment_type')
|
->select('payment_type')
|
||||||
->selectRaw('COALESCE(SUM(total_amount), 0) as total')
|
->selectRaw('COALESCE(SUM(total_amount), 0) as total')
|
||||||
@ -282,8 +349,33 @@ public function getRevenueByPaymentType(?string $startDate, ?string $endDate): a
|
|||||||
return $data->toArray();
|
return $data->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getExpenseSummary(?string $startDate, ?string $endDate): array
|
public function getExpenseSummary(?string $startDate, ?string $endDate, ?User $user = null): array
|
||||||
{
|
{
|
||||||
|
if ($user && $this->isMarketingUser($user)) {
|
||||||
|
$employee = $user->employee;
|
||||||
|
|
||||||
|
$expenseQuery = Expense::where('created_by_id', $user->id);
|
||||||
|
$this->applyDateFilter($expenseQuery, $startDate, $endDate, 'expenses.created_at');
|
||||||
|
|
||||||
|
$advanceQuery = EmployeeAdvance::where('status', EmployeeAdvanceStatus::REPAID);
|
||||||
|
if ($employee) {
|
||||||
|
$advanceQuery->where('employee_id', $employee->id);
|
||||||
|
} else {
|
||||||
|
$advanceQuery->whereRaw('0 = 1');
|
||||||
|
}
|
||||||
|
$this->applyDateFilter($advanceQuery, $startDate, $endDate, 'employee_advances.created_at');
|
||||||
|
|
||||||
|
$expenseTotal = (clone $expenseQuery)->sum('amount');
|
||||||
|
$advanceTotal = (clone $advanceQuery)->sum('amount');
|
||||||
|
|
||||||
|
return [
|
||||||
|
'total' => (int) ($expenseTotal + $advanceTotal),
|
||||||
|
'purchase_total' => 0,
|
||||||
|
'expense_total' => (int) $expenseTotal,
|
||||||
|
'advance_total' => (int) $advanceTotal,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
$expenseQuery = Expense::query();
|
$expenseQuery = Expense::query();
|
||||||
$this->applyDateFilter($expenseQuery, $startDate, $endDate, 'expenses.created_at');
|
$this->applyDateFilter($expenseQuery, $startDate, $endDate, 'expenses.created_at');
|
||||||
|
|
||||||
@ -305,8 +397,54 @@ public function getExpenseSummary(?string $startDate, ?string $endDate): array
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMonthlyExpense(?string $startDate, ?string $endDate): array
|
public function getMonthlyExpense(?string $startDate, ?string $endDate, ?User $user = null): array
|
||||||
{
|
{
|
||||||
|
if ($user && $this->isMarketingUser($user)) {
|
||||||
|
$employee = $user->employee;
|
||||||
|
|
||||||
|
$expenseMonthly = Expense::where('created_by_id', $user->id);
|
||||||
|
$this->applyDateFilter($expenseMonthly, $startDate, $endDate, 'expenses.created_at');
|
||||||
|
|
||||||
|
$expenseByMonth = (clone $expenseMonthly)->toBase()
|
||||||
|
->selectRaw("DATE_FORMAT(created_at, '%b %Y') as month")
|
||||||
|
->selectRaw('COALESCE(SUM(amount), 0) as expense')
|
||||||
|
->groupBy(DB::raw("DATE_FORMAT(created_at, '%Y-%m')"), DB::raw("DATE_FORMAT(created_at, '%b %Y')"))
|
||||||
|
->get()
|
||||||
|
->keyBy('month');
|
||||||
|
|
||||||
|
$advanceMonthly = EmployeeAdvance::where('status', EmployeeAdvanceStatus::REPAID);
|
||||||
|
if ($employee) {
|
||||||
|
$advanceMonthly->where('employee_id', $employee->id);
|
||||||
|
} else {
|
||||||
|
$advanceMonthly->whereRaw('0 = 1');
|
||||||
|
}
|
||||||
|
$this->applyDateFilter($advanceMonthly, $startDate, $endDate, 'employee_advances.created_at');
|
||||||
|
|
||||||
|
$advanceByMonth = (clone $advanceMonthly)->toBase()
|
||||||
|
->selectRaw("DATE_FORMAT(created_at, '%b %Y') as month")
|
||||||
|
->selectRaw('COALESCE(SUM(amount), 0) as advance')
|
||||||
|
->groupBy(DB::raw("DATE_FORMAT(created_at, '%Y-%m')"), DB::raw("DATE_FORMAT(created_at, '%b %Y')"))
|
||||||
|
->get()
|
||||||
|
->keyBy('month');
|
||||||
|
|
||||||
|
$allMonths = [];
|
||||||
|
foreach ([$expenseByMonth, $advanceByMonth] as $data) {
|
||||||
|
foreach ($data as $month => $row) {
|
||||||
|
if (! array_key_exists($month, $allMonths)) {
|
||||||
|
$allMonths[$month] = ['month' => $month, 'total' => 0, 'purchase' => 0, 'expense' => 0, 'advance' => 0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($allMonths as $month => &$row) {
|
||||||
|
$row['expense'] = (int) ($expenseByMonth[$month]->expense ?? 0);
|
||||||
|
$row['advance'] = (int) ($advanceByMonth[$month]->advance ?? 0);
|
||||||
|
$row['total'] = $row['expense'] + $row['advance'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values($allMonths);
|
||||||
|
}
|
||||||
|
|
||||||
$expenseMonthly = Expense::query();
|
$expenseMonthly = Expense::query();
|
||||||
$this->applyDateFilter($expenseMonthly, $startDate, $endDate, 'expenses.created_at');
|
$this->applyDateFilter($expenseMonthly, $startDate, $endDate, 'expenses.created_at');
|
||||||
|
|
||||||
@ -356,11 +494,15 @@ public function getMonthlyExpense(?string $startDate, ?string $endDate): array
|
|||||||
return array_values($allMonths);
|
return array_values($allMonths);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBusyHours(?string $startDate, ?string $endDate): array
|
public function getBusyHours(?string $startDate, ?string $endDate, ?User $user = null): array
|
||||||
{
|
{
|
||||||
$query = Order::where('orders.status', OrderStatus::COMPLETED);
|
$query = Order::where('orders.status', OrderStatus::COMPLETED);
|
||||||
$this->applyDateFilter($query, $startDate, $endDate, 'orders.created_at');
|
$this->applyDateFilter($query, $startDate, $endDate, 'orders.created_at');
|
||||||
|
|
||||||
|
if ($user && $this->isMarketingUser($user)) {
|
||||||
|
$query->where('orders.marketing_id', $user->id);
|
||||||
|
}
|
||||||
|
|
||||||
$hours = range(0, 23);
|
$hours = range(0, 23);
|
||||||
$hourCounts = (clone $query)
|
$hourCounts = (clone $query)
|
||||||
->selectRaw('HOUR(created_at) as hour')
|
->selectRaw('HOUR(created_at) as hour')
|
||||||
@ -377,11 +519,15 @@ public function getBusyHours(?string $startDate, ?string $endDate): array
|
|||||||
}, $hours);
|
}, $hours);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getProfitMetrics(?string $startDate, ?string $endDate): array
|
public function getProfitMetrics(?string $startDate, ?string $endDate, ?User $user = null): array
|
||||||
{
|
{
|
||||||
$query = Order::where('orders.status', OrderStatus::COMPLETED);
|
$query = Order::where('orders.status', OrderStatus::COMPLETED);
|
||||||
$this->applyDateFilter($query, $startDate, $endDate, 'orders.created_at');
|
$this->applyDateFilter($query, $startDate, $endDate, 'orders.created_at');
|
||||||
|
|
||||||
|
if ($user && $this->isMarketingUser($user)) {
|
||||||
|
$query->where('orders.marketing_id', $user->id);
|
||||||
|
}
|
||||||
|
|
||||||
$stats = (clone $query)
|
$stats = (clone $query)
|
||||||
->selectRaw('COUNT(*) as total_orders')
|
->selectRaw('COUNT(*) as total_orders')
|
||||||
->selectRaw('COALESCE(SUM(total_amount), 0) as total_revenue')
|
->selectRaw('COALESCE(SUM(total_amount), 0) as total_revenue')
|
||||||
@ -427,11 +573,15 @@ public function getTopSuppliers(?string $startDate, ?string $endDate): array
|
|||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTopCustomers(?string $startDate, ?string $endDate): array
|
public function getTopCustomers(?string $startDate, ?string $endDate, ?User $user = null): array
|
||||||
{
|
{
|
||||||
$query = Order::where('orders.status', OrderStatus::COMPLETED)->whereNotNull('orders.customer_id');
|
$query = Order::where('orders.status', OrderStatus::COMPLETED)->whereNotNull('orders.customer_id');
|
||||||
$this->applyDateFilter($query, $startDate, $endDate, 'orders.created_at');
|
$this->applyDateFilter($query, $startDate, $endDate, 'orders.created_at');
|
||||||
|
|
||||||
|
if ($user && $this->isMarketingUser($user)) {
|
||||||
|
$query->where('orders.marketing_id', $user->id);
|
||||||
|
}
|
||||||
|
|
||||||
return (clone $query)->toBase()
|
return (clone $query)->toBase()
|
||||||
->join('customers', 'orders.customer_id', '=', 'customers.id')
|
->join('customers', 'orders.customer_id', '=', 'customers.id')
|
||||||
->select('customers.name')
|
->select('customers.name')
|
||||||
@ -444,11 +594,15 @@ public function getTopCustomers(?string $startDate, ?string $endDate): array
|
|||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTopProducts(?string $startDate, ?string $endDate): array
|
public function getTopProducts(?string $startDate, ?string $endDate, ?User $user = null): array
|
||||||
{
|
{
|
||||||
$query = Order::where('orders.status', OrderStatus::COMPLETED);
|
$query = Order::where('orders.status', OrderStatus::COMPLETED);
|
||||||
$this->applyDateFilter($query, $startDate, $endDate, 'orders.created_at');
|
$this->applyDateFilter($query, $startDate, $endDate, 'orders.created_at');
|
||||||
|
|
||||||
|
if ($user && $this->isMarketingUser($user)) {
|
||||||
|
$query->where('orders.marketing_id', $user->id);
|
||||||
|
}
|
||||||
|
|
||||||
return (clone $query)->toBase()
|
return (clone $query)->toBase()
|
||||||
->join('order_items', 'orders.id', '=', 'order_items.order_id')
|
->join('order_items', 'orders.id', '=', 'order_items.order_id')
|
||||||
->join('product_variants', 'order_items.product_variant_id', '=', 'product_variants.id')
|
->join('product_variants', 'order_items.product_variant_id', '=', 'product_variants.id')
|
||||||
@ -463,11 +617,15 @@ public function getTopProducts(?string $startDate, ?string $endDate): array
|
|||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRevenueTrend(?string $startDate, ?string $endDate): array
|
public function getRevenueTrend(?string $startDate, ?string $endDate, ?User $user = null): array
|
||||||
{
|
{
|
||||||
$query = Order::where('orders.status', OrderStatus::COMPLETED);
|
$query = Order::where('orders.status', OrderStatus::COMPLETED);
|
||||||
$this->applyDateFilter($query, $startDate, $endDate, 'orders.created_at');
|
$this->applyDateFilter($query, $startDate, $endDate, 'orders.created_at');
|
||||||
|
|
||||||
|
if ($user && $this->isMarketingUser($user)) {
|
||||||
|
$query->where('orders.marketing_id', $user->id);
|
||||||
|
}
|
||||||
|
|
||||||
return (clone $query)
|
return (clone $query)
|
||||||
->join('order_items', 'orders.id', '=', 'order_items.order_id')
|
->join('order_items', 'orders.id', '=', 'order_items.order_id')
|
||||||
->selectRaw('DATE(orders.created_at) as date')
|
->selectRaw('DATE(orders.created_at) as date')
|
||||||
@ -482,12 +640,16 @@ public function getRevenueTrend(?string $startDate, ?string $endDate): array
|
|||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMarketingSales(?string $startDate, ?string $endDate): array
|
public function getMarketingSales(?string $startDate, ?string $endDate, ?User $user = null): array
|
||||||
{
|
{
|
||||||
$query = Order::where('orders.status', OrderStatus::COMPLETED)
|
$query = Order::where('orders.status', OrderStatus::COMPLETED)
|
||||||
->whereNotNull('orders.marketing_id');
|
->whereNotNull('orders.marketing_id');
|
||||||
$this->applyDateFilter($query, $startDate, $endDate, 'orders.created_at');
|
$this->applyDateFilter($query, $startDate, $endDate, 'orders.created_at');
|
||||||
|
|
||||||
|
if ($user && $this->isMarketingUser($user)) {
|
||||||
|
$query->where('orders.marketing_id', $user->id);
|
||||||
|
}
|
||||||
|
|
||||||
$orders = (clone $query)
|
$orders = (clone $query)
|
||||||
->join('users', 'orders.marketing_id', '=', 'users.id')
|
->join('users', 'orders.marketing_id', '=', 'users.id')
|
||||||
->leftJoin('user_profiles', 'users.id', '=', 'user_profiles.user_id')
|
->leftJoin('user_profiles', 'users.id', '=', 'user_profiles.user_id')
|
||||||
@ -521,11 +683,15 @@ public function getMarketingSales(?string $startDate, ?string $endDate): array
|
|||||||
})->toArray();
|
})->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getOrderStats(?string $startDate, ?string $endDate): array
|
public function getOrderStats(?string $startDate, ?string $endDate, ?User $user = null): array
|
||||||
{
|
{
|
||||||
$baseQuery = Order::query();
|
$baseQuery = Order::query();
|
||||||
$this->applyDateFilter($baseQuery, $startDate, $endDate, 'orders.created_at');
|
$this->applyDateFilter($baseQuery, $startDate, $endDate, 'orders.created_at');
|
||||||
|
|
||||||
|
if ($user && $this->isMarketingUser($user)) {
|
||||||
|
$baseQuery->where('orders.marketing_id', $user->id);
|
||||||
|
}
|
||||||
|
|
||||||
$byChannel = collect(OrderChannel::values())->map(function ($channel) use ($baseQuery) {
|
$byChannel = collect(OrderChannel::values())->map(function ($channel) use ($baseQuery) {
|
||||||
$count = (clone $baseQuery)->where('channel', $channel)->count();
|
$count = (clone $baseQuery)->where('channel', $channel)->count();
|
||||||
$label = OrderChannel::from($channel)->label();
|
$label = OrderChannel::from($channel)->label();
|
||||||
@ -592,4 +758,36 @@ private function applyDateFilter($query, ?string $startDate, ?string $endDate, s
|
|||||||
$query->whereDate($dateColumn, '<=', $endDate);
|
$query->whereDate($dateColumn, '<=', $endDate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function isMarketingUser(User $user): bool
|
||||||
|
{
|
||||||
|
return $user->hasAnyRole([
|
||||||
|
Role::MARKETING_OFFLINE->value,
|
||||||
|
Role::MARKETING_ONLINE->value,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function applyMarketingFilter(Builder $query, User $user, string $column = 'marketing_id'): Builder
|
||||||
|
{
|
||||||
|
if ($this->isMarketingUser($user)) {
|
||||||
|
$query->where($column, $user->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function countWorkingDays(Carbon $start, Carbon $end): int
|
||||||
|
{
|
||||||
|
$workingDays = 0;
|
||||||
|
$current = $start->copy();
|
||||||
|
|
||||||
|
while ($current->lte($end)) {
|
||||||
|
if ($current->dayOfWeek !== Carbon::SUNDAY) {
|
||||||
|
$workingDays++;
|
||||||
|
}
|
||||||
|
$current->addDay();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $workingDays;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
use App\Enums\OrderChannel;
|
use App\Enums\OrderChannel;
|
||||||
use App\Enums\OrderStatus;
|
use App\Enums\OrderStatus;
|
||||||
use App\Enums\PaymentType;
|
use App\Enums\PaymentType;
|
||||||
|
use App\Enums\Role;
|
||||||
use App\Models\Attendance;
|
use App\Models\Attendance;
|
||||||
use App\Models\Employee;
|
use App\Models\Employee;
|
||||||
use App\Models\EmployeeAdvance;
|
use App\Models\EmployeeAdvance;
|
||||||
@ -13,13 +14,49 @@
|
|||||||
use App\Models\Order;
|
use App\Models\Order;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
class DashboardService
|
class DashboardService
|
||||||
{
|
{
|
||||||
public function getAttendanceStats(): array
|
public function getAttendanceStats(?User $user = null): array
|
||||||
{
|
{
|
||||||
$today = Carbon::now()->toDateString();
|
$today = Carbon::now()->toDateString();
|
||||||
|
|
||||||
|
if ($user && $this->isMarketingUser($user)) {
|
||||||
|
$employee = $user->employee;
|
||||||
|
|
||||||
|
if (! $employee) {
|
||||||
|
return [
|
||||||
|
'total_employees' => 0,
|
||||||
|
'present' => 0,
|
||||||
|
'absent' => 0,
|
||||||
|
'on_leave' => 0,
|
||||||
|
'percentage' => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$present = Attendance::where('employee_id', $employee->id)
|
||||||
|
->where('attendance_date', $today)
|
||||||
|
->count();
|
||||||
|
|
||||||
|
$onLeave = LeaveRequest::approved()
|
||||||
|
->where('employee_id', $employee->id)
|
||||||
|
->where('start_date', '<=', $today)
|
||||||
|
->where('end_date', '>=', $today)
|
||||||
|
->count();
|
||||||
|
|
||||||
|
$isWorking = ! in_array(Carbon::now()->dayOfWeek, [Carbon::SATURDAY, Carbon::SUNDAY]);
|
||||||
|
$absent = $isWorking && $present === 0 && $onLeave === 0 ? 1 : 0;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'total_employees' => 1,
|
||||||
|
'present' => $present,
|
||||||
|
'absent' => $absent,
|
||||||
|
'on_leave' => $onLeave,
|
||||||
|
'percentage' => $present > 0 ? 100 : 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
$totalEmployees = Employee::whereHas(
|
$totalEmployees = Employee::whereHas(
|
||||||
'user',
|
'user',
|
||||||
fn ($q) => $q
|
fn ($q) => $q
|
||||||
@ -80,12 +117,16 @@ public function getAttendanceStats(): array
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRevenueSummary(): array
|
public function getRevenueSummary(?User $user = null): array
|
||||||
{
|
{
|
||||||
$today = Carbon::now()->toDateString();
|
$today = Carbon::now()->toDateString();
|
||||||
$baseQuery = Order::where('status', OrderStatus::COMPLETED)
|
$baseQuery = Order::where('status', OrderStatus::COMPLETED)
|
||||||
->whereDate('created_at', $today);
|
->whereDate('created_at', $today);
|
||||||
|
|
||||||
|
if ($user && $this->isMarketingUser($user)) {
|
||||||
|
$baseQuery->where('marketing_id', $user->id);
|
||||||
|
}
|
||||||
|
|
||||||
$stats = (clone $baseQuery)
|
$stats = (clone $baseQuery)
|
||||||
->selectRaw('COUNT(*) as total_orders')
|
->selectRaw('COUNT(*) as total_orders')
|
||||||
->selectRaw('COALESCE(SUM(total_amount), 0) as total_revenue')
|
->selectRaw('COALESCE(SUM(total_amount), 0) as total_revenue')
|
||||||
@ -104,10 +145,38 @@ public function getRevenueSummary(): array
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getExpenseSummary(): array
|
public function getExpenseSummary(?User $user = null): array
|
||||||
{
|
{
|
||||||
$today = Carbon::now()->toDateString();
|
$today = Carbon::now()->toDateString();
|
||||||
|
|
||||||
|
if ($user && $this->isMarketingUser($user)) {
|
||||||
|
$employee = $user->employee;
|
||||||
|
|
||||||
|
$expenseTotal = Expense::whereDate('created_at', $today)
|
||||||
|
->where('created_by_id', $user->id)
|
||||||
|
->selectRaw('COALESCE(SUM(amount), 0) as total')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
$advanceQuery = EmployeeAdvance::whereDate('created_at', $today)
|
||||||
|
->disbursed();
|
||||||
|
|
||||||
|
if ($employee) {
|
||||||
|
$advanceQuery->where('employee_id', $employee->id);
|
||||||
|
} else {
|
||||||
|
$advanceQuery->whereRaw('0 = 1');
|
||||||
|
}
|
||||||
|
|
||||||
|
$advanceTotal = $advanceQuery
|
||||||
|
->selectRaw('COALESCE(SUM(amount), 0) as total')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'total' => (int) $expenseTotal->total + (int) $advanceTotal->total,
|
||||||
|
'expense_total' => (int) $expenseTotal->total,
|
||||||
|
'advance_total' => (int) $advanceTotal->total,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
$expenseTotal = Expense::whereDate('created_at', $today)
|
$expenseTotal = Expense::whereDate('created_at', $today)
|
||||||
->selectRaw('COALESCE(SUM(amount), 0) as total')
|
->selectRaw('COALESCE(SUM(amount), 0) as total')
|
||||||
->first();
|
->first();
|
||||||
@ -124,11 +193,15 @@ public function getExpenseSummary(): array
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getOrderStats(): array
|
public function getOrderStats(?User $user = null): array
|
||||||
{
|
{
|
||||||
$today = Carbon::now()->toDateString();
|
$today = Carbon::now()->toDateString();
|
||||||
$baseQuery = Order::whereDate('created_at', $today);
|
$baseQuery = Order::whereDate('created_at', $today);
|
||||||
|
|
||||||
|
if ($user && $this->isMarketingUser($user)) {
|
||||||
|
$baseQuery->where('marketing_id', $user->id);
|
||||||
|
}
|
||||||
|
|
||||||
$byChannel = collect(OrderChannel::values())->map(function ($channel) use ($baseQuery) {
|
$byChannel = collect(OrderChannel::values())->map(function ($channel) use ($baseQuery) {
|
||||||
$count = (clone $baseQuery)->where('channel', $channel)->count();
|
$count = (clone $baseQuery)->where('channel', $channel)->count();
|
||||||
$label = OrderChannel::from($channel)->label();
|
$label = OrderChannel::from($channel)->label();
|
||||||
@ -231,4 +304,21 @@ public function isOnLeave(User $user): bool
|
|||||||
->where('end_date', '>=', now()->toDateString())
|
->where('end_date', '>=', now()->toDateString())
|
||||||
->exists();
|
->exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function isMarketingUser(User $user): bool
|
||||||
|
{
|
||||||
|
return $user->hasAnyRole([
|
||||||
|
Role::MARKETING_OFFLINE->value,
|
||||||
|
Role::MARKETING_ONLINE->value,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function applyMarketingFilter(Builder $query, User $user, string $column = 'marketing_id'): Builder
|
||||||
|
{
|
||||||
|
if ($this->isMarketingUser($user)) {
|
||||||
|
$query->where($column, $user->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -92,7 +92,6 @@ public function run(): void
|
|||||||
'analysis.profit_margin',
|
'analysis.profit_margin',
|
||||||
'analysis.top_customers',
|
'analysis.top_customers',
|
||||||
'analysis.top_products',
|
'analysis.top_products',
|
||||||
'analysis.top_suppliers',
|
|
||||||
'analysis.marketing_sales',
|
'analysis.marketing_sales',
|
||||||
|
|
||||||
'stok_opnames.view',
|
'stok_opnames.view',
|
||||||
@ -381,7 +380,6 @@ public function run(): void
|
|||||||
'analysis.profit_margin',
|
'analysis.profit_margin',
|
||||||
'analysis.top_customers',
|
'analysis.top_customers',
|
||||||
'analysis.top_products',
|
'analysis.top_products',
|
||||||
'analysis.top_suppliers',
|
|
||||||
'analysis.marketing_sales',
|
'analysis.marketing_sales',
|
||||||
|
|
||||||
'employees.view',
|
'employees.view',
|
||||||
|
|||||||
@ -105,7 +105,7 @@ const hrItems: NavMenuItem[] = [
|
|||||||
const sistemItems: NavMenuItem[] = [
|
const sistemItems: NavMenuItem[] = [
|
||||||
{ title: 'Pengaturan', href: '/admin/settings', icon: Settings, permission: ['settings.view_system', 'settings.view_homepage', 'settings.view_social_media', 'settings.view_marketplace', 'settings.view_hr'] },
|
{ title: 'Pengaturan', href: '/admin/settings', icon: Settings, permission: ['settings.view_system', 'settings.view_homepage', 'settings.view_social_media', 'settings.view_marketplace', 'settings.view_hr'] },
|
||||||
{ title: 'Role & Permission', href: rolesIndex.url(), icon: Shield, permission: 'roles.view' },
|
{ title: 'Role & Permission', href: rolesIndex.url(), icon: Shield, permission: 'roles.view' },
|
||||||
{ title: 'Log Aktivitas', href: '#', icon: Activity, permission: 'activity_logs.view' },
|
// { title: 'Log Aktivitas', href: '#', icon: Activity, permission: 'activity_logs.view' },
|
||||||
];
|
];
|
||||||
|
|
||||||
function MenuGroup({ label, items }: { label: string; items: NavMenuItem[] }) {
|
function MenuGroup({ label, items }: { label: string; items: NavMenuItem[] }) {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ export type LeaveRequest = {
|
|||||||
status: 'pending' | 'approved' | 'rejected' | 'cancelled';
|
status: 'pending' | 'approved' | 'rejected' | 'cancelled';
|
||||||
created_at: string;
|
created_at: string;
|
||||||
employee: {
|
employee: {
|
||||||
|
user_id: number;
|
||||||
user: {
|
user: {
|
||||||
user_profile: {
|
user_profile: {
|
||||||
full_name: string;
|
full_name: string;
|
||||||
@ -56,12 +57,13 @@ type CreateColumnsParams = {
|
|||||||
handleApprove: (leaveRequest: LeaveRequest) => void;
|
handleApprove: (leaveRequest: LeaveRequest) => void;
|
||||||
handleReject: (leaveRequest: LeaveRequest) => void;
|
handleReject: (leaveRequest: LeaveRequest) => void;
|
||||||
can: (permission: string) => boolean;
|
can: (permission: string) => boolean;
|
||||||
|
currentUserId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createLeaveRequestColumns(
|
export function createLeaveRequestColumns(
|
||||||
params: CreateColumnsParams,
|
params: CreateColumnsParams,
|
||||||
): ColumnDef<LeaveRequest>[] {
|
): ColumnDef<LeaveRequest>[] {
|
||||||
const { handleEdit, handleDeleteClick, handleApprove, handleReject, can } =
|
const { handleEdit, handleDeleteClick, handleApprove, handleReject, can, currentUserId } =
|
||||||
params;
|
params;
|
||||||
|
|
||||||
const columns: ColumnDef<LeaveRequest>[] = [
|
const columns: ColumnDef<LeaveRequest>[] = [
|
||||||
@ -157,7 +159,8 @@ export function createLeaveRequestColumns(
|
|||||||
icon: <Pencil className="h-4 w-4" />,
|
icon: <Pencil className="h-4 w-4" />,
|
||||||
show:
|
show:
|
||||||
can('leave_requests.update') &&
|
can('leave_requests.update') &&
|
||||||
leaveRequest.status === 'pending',
|
leaveRequest.status === 'pending' &&
|
||||||
|
leaveRequest.employee?.user_id === currentUserId,
|
||||||
onClick: () => handleEdit(leaveRequest),
|
onClick: () => handleEdit(leaveRequest),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -167,7 +170,8 @@ export function createLeaveRequestColumns(
|
|||||||
),
|
),
|
||||||
show:
|
show:
|
||||||
can('leave_requests.delete') &&
|
can('leave_requests.delete') &&
|
||||||
leaveRequest.status === 'pending',
|
leaveRequest.status === 'pending' &&
|
||||||
|
leaveRequest.employee?.user_id === currentUserId,
|
||||||
onClick: () => handleDeleteClick(leaveRequest),
|
onClick: () => handleDeleteClick(leaveRequest),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|||||||
@ -50,6 +50,7 @@ type Props = {
|
|||||||
filterOptions: {
|
filterOptions: {
|
||||||
statusOptions: StatusOption[];
|
statusOptions: StatusOption[];
|
||||||
};
|
};
|
||||||
|
currentUserId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
function toLocalDateString(date: Date): string {
|
function toLocalDateString(date: Date): string {
|
||||||
@ -59,7 +60,7 @@ function toLocalDateString(date: Date): string {
|
|||||||
return `${year}-${month}-${day}`;
|
return `${year}-${month}-${day}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LeaveRequestIndex({ leaveRequests, filters, filterOptions }: Props) {
|
export default function LeaveRequestIndex({ leaveRequests, filters, filterOptions, currentUserId }: Props) {
|
||||||
const { can } = useCan();
|
const { can } = useCan();
|
||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<LeaveRequest | null>(null);
|
const [editing, setEditing] = useState<LeaveRequest | null>(null);
|
||||||
@ -151,6 +152,7 @@ export default function LeaveRequestIndex({ leaveRequests, filters, filterOption
|
|||||||
handleApprove: (leaveRequest) => setApproving(leaveRequest),
|
handleApprove: (leaveRequest) => setApproving(leaveRequest),
|
||||||
handleReject: (leaveRequest) => setRejecting(leaveRequest),
|
handleReject: (leaveRequest) => setRejecting(leaveRequest),
|
||||||
can,
|
can,
|
||||||
|
currentUserId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const filterToolbar = (
|
const filterToolbar = (
|
||||||
|
|||||||
@ -67,9 +67,9 @@ type Props = {
|
|||||||
};
|
};
|
||||||
summary: {
|
summary: {
|
||||||
total_orders: number;
|
total_orders: number;
|
||||||
total_subtotal: number;
|
|
||||||
total_discount: number;
|
|
||||||
total_amount: number;
|
total_amount: number;
|
||||||
|
total_discount: number;
|
||||||
|
total_deduction: number;
|
||||||
net_total: number;
|
net_total: number;
|
||||||
};
|
};
|
||||||
filters: {
|
filters: {
|
||||||
|
|||||||
@ -122,6 +122,15 @@ export function TransactionCardRow({
|
|||||||
?.full_name ?? '-'}
|
?.full_name ?? '-'}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
{transaction.marketing && (
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
Marketing:{' '}
|
||||||
|
<span className="font-medium text-foreground">
|
||||||
|
{transaction.marketing.user_profile
|
||||||
|
?.full_name ?? '-'}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{transaction.customer && (
|
{transaction.customer && (
|
||||||
<span className="text-muted-foreground">
|
<span className="text-muted-foreground">
|
||||||
Pelanggan:{' '}
|
Pelanggan:{' '}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import {
|
|||||||
Banknote,
|
Banknote,
|
||||||
CircleDollarSign,
|
CircleDollarSign,
|
||||||
FileText,
|
FileText,
|
||||||
|
Minus,
|
||||||
Percent,
|
Percent,
|
||||||
TrendingUp,
|
TrendingUp,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
@ -10,9 +11,9 @@ import { formatCurrency } from '@/lib/utils';
|
|||||||
|
|
||||||
type Summary = {
|
type Summary = {
|
||||||
total_orders: number;
|
total_orders: number;
|
||||||
total_subtotal: number;
|
|
||||||
total_discount: number;
|
|
||||||
total_amount: number;
|
total_amount: number;
|
||||||
|
total_discount: number;
|
||||||
|
total_deduction: number;
|
||||||
net_total: number;
|
net_total: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -30,8 +31,8 @@ const summaryItems = [
|
|||||||
format: (value: number) => value.toLocaleString('id-ID'),
|
format: (value: number) => value.toLocaleString('id-ID'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'total_subtotal',
|
key: 'total_amount',
|
||||||
label: 'Subtotal',
|
label: 'Total',
|
||||||
icon: Banknote,
|
icon: Banknote,
|
||||||
color: 'bg-emerald-100',
|
color: 'bg-emerald-100',
|
||||||
iconColor: 'text-emerald-600',
|
iconColor: 'text-emerald-600',
|
||||||
@ -46,11 +47,11 @@ const summaryItems = [
|
|||||||
format: formatCurrency,
|
format: formatCurrency,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'total_amount',
|
key: 'total_deduction',
|
||||||
label: 'Total Uang',
|
label: 'Potongan',
|
||||||
icon: CircleDollarSign,
|
icon: Minus,
|
||||||
color: 'bg-purple-100',
|
color: 'bg-orange-100',
|
||||||
iconColor: 'text-purple-600',
|
iconColor: 'text-orange-600',
|
||||||
format: formatCurrency,
|
format: formatCurrency,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user