feat: Phase 8 SaaS 확장 - 구독관리/결제내역 API 추가
- 사용량 조회 API (GET /subscriptions/usage)
- 데이터 내보내기 API (POST/GET /subscriptions/export)
- 결제 명세서 API (GET /payments/{id}/statement)
- DataExport 모델 및 마이그레이션 추가
This commit is contained in:
@@ -2,12 +2,15 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Tenants\DataExport;
|
||||
use App\Models\Tenants\Payment;
|
||||
use App\Models\Tenants\Plan;
|
||||
use App\Models\Tenants\Subscription;
|
||||
use App\Models\Tenants\Tenant;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
class SubscriptionService extends Service
|
||||
{
|
||||
@@ -294,4 +297,136 @@ public function resume(int $id): Subscription
|
||||
|
||||
return $subscription->fresh(['plan']);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 사용량 조회
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* 사용량 조회
|
||||
*/
|
||||
public function usage(): array
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$tenant = Tenant::with(['subscription.plan'])->findOrFail($tenantId);
|
||||
|
||||
// 사용자 수
|
||||
$userCount = $tenant->users()->count();
|
||||
$maxUsers = $tenant->max_users ?? 0;
|
||||
|
||||
// 저장공간
|
||||
$storageUsed = $tenant->storage_used ?? 0;
|
||||
$storageLimit = $tenant->storage_limit ?? 0;
|
||||
|
||||
// 구독 정보
|
||||
$subscription = $tenant->subscription;
|
||||
$remainingDays = null;
|
||||
$planName = null;
|
||||
|
||||
if ($subscription && $subscription->is_valid) {
|
||||
$remainingDays = $subscription->remaining_days;
|
||||
$planName = $subscription->plan?->name;
|
||||
}
|
||||
|
||||
return [
|
||||
'users' => [
|
||||
'used' => $userCount,
|
||||
'limit' => $maxUsers,
|
||||
'percentage' => $maxUsers > 0 ? round(($userCount / $maxUsers) * 100, 1) : 0,
|
||||
],
|
||||
'storage' => [
|
||||
'used' => $storageUsed,
|
||||
'used_formatted' => $tenant->getStorageUsedFormatted(),
|
||||
'limit' => $storageLimit,
|
||||
'limit_formatted' => $tenant->getStorageLimitFormatted(),
|
||||
'percentage' => $storageLimit > 0 ? round(($storageUsed / $storageLimit) * 100, 1) : 0,
|
||||
],
|
||||
'subscription' => [
|
||||
'plan' => $planName,
|
||||
'status' => $subscription?->status,
|
||||
'remaining_days' => $remainingDays,
|
||||
'started_at' => $subscription?->started_at?->toDateString(),
|
||||
'ended_at' => $subscription?->ended_at?->toDateString(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 데이터 내보내기
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* 내보내기 요청 생성
|
||||
*/
|
||||
public function createExport(array $data): DataExport
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$userId = $this->apiUserId();
|
||||
|
||||
// 진행 중인 내보내기가 있는지 확인
|
||||
$pendingExport = DataExport::where('tenant_id', $tenantId)
|
||||
->whereIn('status', [DataExport::STATUS_PENDING, DataExport::STATUS_PROCESSING])
|
||||
->first();
|
||||
|
||||
if ($pendingExport) {
|
||||
throw new BadRequestHttpException(__('error.export.already_in_progress'));
|
||||
}
|
||||
|
||||
$export = DataExport::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'export_type' => $data['export_type'] ?? DataExport::TYPE_ALL,
|
||||
'status' => DataExport::STATUS_PENDING,
|
||||
'options' => $data['options'] ?? null,
|
||||
'created_by' => $userId,
|
||||
]);
|
||||
|
||||
// TODO: 비동기 Job 디스패치
|
||||
// dispatch(new ProcessDataExport($export));
|
||||
|
||||
return $export;
|
||||
}
|
||||
|
||||
/**
|
||||
* 내보내기 상태 조회
|
||||
*/
|
||||
public function getExport(int $id): DataExport
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$export = DataExport::where('tenant_id', $tenantId)->find($id);
|
||||
|
||||
if (! $export) {
|
||||
throw new NotFoundHttpException(__('error.export.not_found'));
|
||||
}
|
||||
|
||||
return $export;
|
||||
}
|
||||
|
||||
/**
|
||||
* 내보내기 목록 조회
|
||||
*/
|
||||
public function getExports(array $params = []): LengthAwarePaginator
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$query = DataExport::where('tenant_id', $tenantId)
|
||||
->with('creator:id,name,email');
|
||||
|
||||
// 상태 필터
|
||||
if (! empty($params['status'])) {
|
||||
$query->where('status', $params['status']);
|
||||
}
|
||||
|
||||
// 유형 필터
|
||||
if (! empty($params['export_type'])) {
|
||||
$query->where('export_type', $params['export_type']);
|
||||
}
|
||||
|
||||
$query->orderBy('created_at', 'desc');
|
||||
|
||||
$perPage = $params['per_page'] ?? 20;
|
||||
|
||||
return $query->paginate($perPage);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user