Files
sam-api/app/Services/DashboardService.php

385 lines
12 KiB
PHP
Raw Normal View History

<?php
namespace App\Services;
use App\Models\Stats\Monthly\StatFinanceMonthly;
use App\Models\Stats\Monthly\StatSalesMonthly;
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,
];
}
/**
* 재무 요약 데이터 (sam_stat 우선, 폴백: 원본 DB)
*/
private function getFinanceSummary(int $tenantId, Carbon $startOfMonth, Carbon $endOfMonth): array
{
// sam_stat 월간 데이터 시도
$monthly = StatFinanceMonthly::where('tenant_id', $tenantId)
->where('stat_year', $startOfMonth->year)
->where('stat_month', $startOfMonth->month)
->first();
if ($monthly) {
return [
'monthly_deposit' => (float) $monthly->deposit_amount,
'monthly_withdrawal' => (float) $monthly->withdrawal_amount,
'balance' => (float) ($monthly->deposit_amount - $monthly->withdrawal_amount),
'source' => 'sam_stat',
];
}
// 폴백: 원본 DB 실시간 집계
$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,
'source' => 'samdb',
];
}
/**
* 매출/매입 요약 데이터 (sam_stat 우선, 폴백: 원본 DB)
*/
private function getSalesSummary(int $tenantId, Carbon $startOfMonth, Carbon $endOfMonth): array
{
// sam_stat 월간 데이터 시도
$monthly = StatSalesMonthly::where('tenant_id', $tenantId)
->where('stat_year', $startOfMonth->year)
->where('stat_month', $startOfMonth->month)
->first();
if ($monthly) {
return [
'monthly_sales' => (float) $monthly->sales_amount,
'monthly_purchases' => 0,
'source' => 'sam_stat',
];
}
// 폴백: 원본 DB 실시간 집계
$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,
'source' => 'samdb',
];
}
/**
* 요약 데이터
*/
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();
}
}