feat: 대시보드 API 및 FCM 푸시 알림 API 구현
Dashboard API: - DashboardController, DashboardService 추가 - /dashboard/summary, /charts, /approvals 엔드포인트 Push Notification API: - FCM 토큰 관리 (등록/해제/목록) - 알림 설정 관리 (유형별 on/off, 알림음 설정) - 알림 유형: deposit, withdrawal, order, approval, attendance, notice, system - 알림음: default, deposit, withdrawal, order, approval, urgent - PushDeviceToken, PushNotificationSetting 모델 - Swagger 문서 추가
This commit is contained in:
354
app/Services/DashboardService.php
Normal file
354
app/Services/DashboardService.php
Normal file
@@ -0,0 +1,354 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Tenants\Approval;
|
||||
use App\Models\Tenants\ApprovalStep;
|
||||
use App\Models\Tenants\Attendance;
|
||||
use App\Models\Tenants\Deposit;
|
||||
use App\Models\Tenants\Leave;
|
||||
use App\Models\Tenants\Purchase;
|
||||
use App\Models\Tenants\Sale;
|
||||
use App\Models\Tenants\Withdrawal;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class DashboardService extends Service
|
||||
{
|
||||
/**
|
||||
* 대시보드 요약 데이터 조회
|
||||
*/
|
||||
public function summary(): array
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$userId = $this->apiUserId();
|
||||
$today = Carbon::today();
|
||||
$startOfMonth = Carbon::now()->startOfMonth();
|
||||
$endOfMonth = Carbon::now()->endOfMonth();
|
||||
|
||||
return [
|
||||
'today' => $this->getTodaySummary($tenantId, $today),
|
||||
'finance' => $this->getFinanceSummary($tenantId, $startOfMonth, $endOfMonth),
|
||||
'sales' => $this->getSalesSummary($tenantId, $startOfMonth, $endOfMonth),
|
||||
'tasks' => $this->getTasksSummary($tenantId, $userId),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 대시보드 차트 데이터 조회
|
||||
*
|
||||
* @param array $params [period: week|month|quarter]
|
||||
*/
|
||||
public function charts(array $params): array
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$period = $params['period'] ?? 'month';
|
||||
|
||||
[$startDate, $endDate] = $this->getPeriodRange($period);
|
||||
|
||||
return [
|
||||
'period' => $period,
|
||||
'start_date' => $startDate->toDateString(),
|
||||
'end_date' => $endDate->toDateString(),
|
||||
'deposit_trend' => $this->getDepositTrend($tenantId, $startDate, $endDate),
|
||||
'withdrawal_trend' => $this->getWithdrawalTrend($tenantId, $startDate, $endDate),
|
||||
'sales_by_client' => $this->getSalesByClient($tenantId, $startDate, $endDate),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 결재 현황 조회
|
||||
*
|
||||
* @param array $params [limit: int]
|
||||
*/
|
||||
public function approvals(array $params): array
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$userId = $this->apiUserId();
|
||||
$limit = $params['limit'] ?? 10;
|
||||
|
||||
// 내가 결재할 문서 (결재함)
|
||||
$pendingApprovals = $this->getPendingApprovals($tenantId, $userId, $limit);
|
||||
|
||||
// 내가 기안한 문서 중 진행중인 것
|
||||
$myDrafts = $this->getMyPendingDrafts($tenantId, $userId, $limit);
|
||||
|
||||
return [
|
||||
'pending_approvals' => $pendingApprovals,
|
||||
'my_drafts' => $myDrafts,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 오늘 요약 데이터
|
||||
*/
|
||||
private function getTodaySummary(int $tenantId, Carbon $today): array
|
||||
{
|
||||
// 오늘 출근자 수
|
||||
$attendancesCount = Attendance::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->whereDate('work_date', $today)
|
||||
->whereNotNull('check_in')
|
||||
->count();
|
||||
|
||||
// 오늘 휴가자 수
|
||||
$leavesCount = Leave::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('status', 'approved')
|
||||
->whereDate('start_date', '<=', $today)
|
||||
->whereDate('end_date', '>=', $today)
|
||||
->count();
|
||||
|
||||
// 결재 대기 문서 수 (전체)
|
||||
$approvalsPending = Approval::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('status', 'pending')
|
||||
->count();
|
||||
|
||||
return [
|
||||
'date' => $today->toDateString(),
|
||||
'attendances_count' => $attendancesCount,
|
||||
'leaves_count' => $leavesCount,
|
||||
'approvals_pending' => $approvalsPending,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 재무 요약 데이터
|
||||
*/
|
||||
private function getFinanceSummary(int $tenantId, Carbon $startOfMonth, Carbon $endOfMonth): array
|
||||
{
|
||||
// 월간 입금 합계
|
||||
$monthlyDeposit = Deposit::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->whereBetween('deposit_date', [$startOfMonth, $endOfMonth])
|
||||
->sum('amount');
|
||||
|
||||
// 월간 출금 합계
|
||||
$monthlyWithdrawal = Withdrawal::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->whereBetween('withdrawal_date', [$startOfMonth, $endOfMonth])
|
||||
->sum('amount');
|
||||
|
||||
// 현재 잔액 (전체 입금 - 전체 출금)
|
||||
$totalDeposits = Deposit::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->sum('amount');
|
||||
|
||||
$totalWithdrawals = Withdrawal::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->sum('amount');
|
||||
|
||||
$balance = $totalDeposits - $totalWithdrawals;
|
||||
|
||||
return [
|
||||
'monthly_deposit' => (float) $monthlyDeposit,
|
||||
'monthly_withdrawal' => (float) $monthlyWithdrawal,
|
||||
'balance' => (float) $balance,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 매출/매입 요약 데이터
|
||||
*/
|
||||
private function getSalesSummary(int $tenantId, Carbon $startOfMonth, Carbon $endOfMonth): array
|
||||
{
|
||||
// 월간 매출 합계
|
||||
$monthlySales = Sale::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->whereBetween('sale_date', [$startOfMonth, $endOfMonth])
|
||||
->sum('total_amount');
|
||||
|
||||
// 월간 매입 합계
|
||||
$monthlyPurchases = Purchase::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->whereBetween('purchase_date', [$startOfMonth, $endOfMonth])
|
||||
->sum('total_amount');
|
||||
|
||||
return [
|
||||
'monthly_sales' => (float) $monthlySales,
|
||||
'monthly_purchases' => (float) $monthlyPurchases,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 할 일 요약 데이터
|
||||
*/
|
||||
private function getTasksSummary(int $tenantId, int $userId): array
|
||||
{
|
||||
// 내가 결재해야 할 문서 수
|
||||
$pendingApprovals = ApprovalStep::query()
|
||||
->whereHas('approval', function ($query) use ($tenantId) {
|
||||
$query->where('tenant_id', $tenantId)
|
||||
->where('status', 'pending');
|
||||
})
|
||||
->where('approver_id', $userId)
|
||||
->where('status', 'pending')
|
||||
->count();
|
||||
|
||||
// 승인 대기 휴가 신청 수 (관리자용)
|
||||
$pendingLeaves = Leave::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('status', 'pending')
|
||||
->count();
|
||||
|
||||
return [
|
||||
'pending_approvals' => $pendingApprovals,
|
||||
'pending_leaves' => $pendingLeaves,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 기간 범위 계산
|
||||
*
|
||||
* @return array [Carbon $startDate, Carbon $endDate]
|
||||
*/
|
||||
private function getPeriodRange(string $period): array
|
||||
{
|
||||
$endDate = Carbon::today();
|
||||
|
||||
switch ($period) {
|
||||
case 'week':
|
||||
$startDate = $endDate->copy()->subDays(6);
|
||||
break;
|
||||
case 'quarter':
|
||||
$startDate = $endDate->copy()->subMonths(3)->startOfMonth();
|
||||
break;
|
||||
case 'month':
|
||||
default:
|
||||
$startDate = $endDate->copy()->subDays(29);
|
||||
break;
|
||||
}
|
||||
|
||||
return [$startDate, $endDate];
|
||||
}
|
||||
|
||||
/**
|
||||
* 입금 추이 데이터
|
||||
*/
|
||||
private function getDepositTrend(int $tenantId, Carbon $startDate, Carbon $endDate): array
|
||||
{
|
||||
$deposits = Deposit::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->whereBetween('deposit_date', [$startDate, $endDate])
|
||||
->select(
|
||||
DB::raw('DATE(deposit_date) as date'),
|
||||
DB::raw('SUM(amount) as amount')
|
||||
)
|
||||
->groupBy(DB::raw('DATE(deposit_date)'))
|
||||
->orderBy('date')
|
||||
->get();
|
||||
|
||||
return $deposits->map(function ($item) {
|
||||
return [
|
||||
'date' => $item->date,
|
||||
'amount' => (float) $item->amount,
|
||||
];
|
||||
})->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 출금 추이 데이터
|
||||
*/
|
||||
private function getWithdrawalTrend(int $tenantId, Carbon $startDate, Carbon $endDate): array
|
||||
{
|
||||
$withdrawals = Withdrawal::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->whereBetween('withdrawal_date', [$startDate, $endDate])
|
||||
->select(
|
||||
DB::raw('DATE(withdrawal_date) as date'),
|
||||
DB::raw('SUM(amount) as amount')
|
||||
)
|
||||
->groupBy(DB::raw('DATE(withdrawal_date)'))
|
||||
->orderBy('date')
|
||||
->get();
|
||||
|
||||
return $withdrawals->map(function ($item) {
|
||||
return [
|
||||
'date' => $item->date,
|
||||
'amount' => (float) $item->amount,
|
||||
];
|
||||
})->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 거래처별 매출 데이터
|
||||
*/
|
||||
private function getSalesByClient(int $tenantId, Carbon $startDate, Carbon $endDate): array
|
||||
{
|
||||
$sales = Sale::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->whereBetween('sale_date', [$startDate, $endDate])
|
||||
->with('client:id,name')
|
||||
->select(
|
||||
'client_id',
|
||||
DB::raw('SUM(total_amount) as amount')
|
||||
)
|
||||
->groupBy('client_id')
|
||||
->orderByDesc('amount')
|
||||
->limit(10)
|
||||
->get();
|
||||
|
||||
return $sales->map(function ($item) {
|
||||
return [
|
||||
'client_id' => $item->client_id,
|
||||
'client_name' => $item->client?->name ?? __('message.dashboard.unknown_client'),
|
||||
'amount' => (float) $item->amount,
|
||||
];
|
||||
})->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 내가 결재해야 할 문서 목록
|
||||
*/
|
||||
private function getPendingApprovals(int $tenantId, int $userId, int $limit): array
|
||||
{
|
||||
$steps = ApprovalStep::query()
|
||||
->whereHas('approval', function ($query) use ($tenantId) {
|
||||
$query->where('tenant_id', $tenantId)
|
||||
->where('status', 'pending');
|
||||
})
|
||||
->where('approver_id', $userId)
|
||||
->where('status', 'pending')
|
||||
->with(['approval' => function ($query) {
|
||||
$query->with('drafter:id,name');
|
||||
}])
|
||||
->orderBy('created_at', 'desc')
|
||||
->limit($limit)
|
||||
->get();
|
||||
|
||||
return $steps->map(function ($step) {
|
||||
return [
|
||||
'id' => $step->approval->id,
|
||||
'title' => $step->approval->title,
|
||||
'drafter_name' => $step->approval->drafter?->name ?? '',
|
||||
'status' => $step->approval->status,
|
||||
'created_at' => $step->approval->created_at?->toDateTimeString(),
|
||||
];
|
||||
})->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 내가 기안한 진행중인 문서 목록
|
||||
*/
|
||||
private function getMyPendingDrafts(int $tenantId, int $userId, int $limit): array
|
||||
{
|
||||
$approvals = Approval::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('drafter_id', $userId)
|
||||
->where('status', 'pending')
|
||||
->orderBy('created_at', 'desc')
|
||||
->limit($limit)
|
||||
->get();
|
||||
|
||||
return $approvals->map(function ($approval) {
|
||||
return [
|
||||
'id' => $approval->id,
|
||||
'title' => $approval->title,
|
||||
'status' => $approval->status,
|
||||
'current_step' => $approval->current_step,
|
||||
'created_at' => $approval->created_at?->toDateTimeString(),
|
||||
];
|
||||
})->toArray();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user