Files
sam-api/app/Services/AccountService.php
hskwon 3020026abf feat: Phase 5 API 개발 완료 (사용자 초대, 알림설정, 계정관리, 거래명세서)
5.1 사용자 초대 기능:
- UserInvitation 마이그레이션, 모델, 서비스, 컨트롤러, Swagger
- 초대 발송/수락/취소/재발송 API

5.2 알림설정 확장:
- NotificationSetting 마이그레이션, 모델, 서비스, 컨트롤러, Swagger
- 채널별/유형별 알림 설정 관리

5.3 계정정보 수정 API:
- 회원탈퇴, 사용중지, 약관동의 관리
- AccountService, AccountController, Swagger

5.4 매출 거래명세서 API:
- 거래명세서 조회/발행/이메일발송
- SaleService 확장, Swagger 문서화
2025-12-19 14:52:53 +09:00

241 lines
7.2 KiB
PHP

<?php
namespace App\Services;
use App\Models\Members\User;
use App\Models\Members\UserTenant;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class AccountService extends Service
{
/**
* 회원 탈퇴 (SAM 완전 탈퇴)
* - 모든 테넌트에서 탈퇴
* - 사용자 계정 Soft Delete
*/
public function withdraw(array $data): array
{
$userId = $this->apiUserId();
$user = User::find($userId);
if (! $user) {
throw new NotFoundHttpException(__('error.not_found_resource', ['resource' => '사용자']));
}
// 비밀번호 확인
if (! password_verify($data['password'], $user->password)) {
throw new BadRequestHttpException(__('error.account.invalid_password'));
}
return DB::transaction(function () use ($user, $data) {
// 1. 모든 테넌트 연결 해제 (soft delete)
UserTenant::where('user_id', $user->id)->delete();
// 2. 탈퇴 사유 저장 (options에 기록)
$options = $user->options ?? [];
$options['withdrawal'] = [
'reason' => $data['reason'] ?? null,
'detail' => $data['detail'] ?? null,
'withdrawn_at' => now()->toIso8601String(),
];
$user->options = $options;
$user->save();
// 3. 사용자 soft delete
$user->delete();
// 4. 토큰 삭제
$user->tokens()->delete();
return [
'withdrawn_at' => now()->toIso8601String(),
];
});
}
/**
* 사용 중지 (특정 테넌트에서만 탈퇴)
*/
public function suspend(): array
{
$userId = $this->apiUserId();
$tenantId = $this->tenantId();
$userTenant = UserTenant::where('user_id', $userId)
->where('tenant_id', $tenantId)
->first();
if (! $userTenant) {
throw new NotFoundHttpException(__('error.account.tenant_membership_not_found'));
}
return DB::transaction(function () use ($userTenant, $userId, $tenantId) {
// 1. 현재 테넌트에서 비활성화
$userTenant->is_active = false;
$userTenant->left_at = now();
$userTenant->save();
// 2. 다른 활성 테넌트가 있으면 기본 테넌트 변경
$otherActiveTenant = UserTenant::where('user_id', $userId)
->where('tenant_id', '!=', $tenantId)
->where('is_active', true)
->first();
if ($otherActiveTenant) {
// 다른 테넌트로 기본 변경
UserTenant::where('user_id', $userId)->update(['is_default' => false]);
$otherActiveTenant->is_default = true;
$otherActiveTenant->save();
return [
'suspended' => true,
'new_default_tenant_id' => $otherActiveTenant->tenant_id,
];
}
return [
'suspended' => true,
'new_default_tenant_id' => null,
];
});
}
/**
* 약관 동의 정보 조회
*/
public function getAgreements(): array
{
$userId = $this->apiUserId();
$user = User::find($userId);
if (! $user) {
throw new NotFoundHttpException(__('error.not_found_resource', ['resource' => '사용자']));
}
$options = $user->options ?? [];
$agreements = $options['agreements'] ?? self::getDefaultAgreements();
return [
'agreements' => $agreements,
'types' => self::getAgreementTypes(),
];
}
/**
* 약관 동의 정보 수정
*/
public function updateAgreements(array $data): array
{
$userId = $this->apiUserId();
$user = User::find($userId);
if (! $user) {
throw new NotFoundHttpException(__('error.not_found_resource', ['resource' => '사용자']));
}
$options = $user->options ?? [];
$currentAgreements = $options['agreements'] ?? self::getDefaultAgreements();
// 수신 데이터로 업데이트
foreach ($data['agreements'] as $agreement) {
$type = $agreement['type'];
if (isset($currentAgreements[$type])) {
$currentAgreements[$type]['agreed'] = $agreement['agreed'];
$currentAgreements[$type]['agreed_at'] = $agreement['agreed']
? now()->toIso8601String()
: null;
}
}
$options['agreements'] = $currentAgreements;
$user->options = $options;
$user->save();
return [
'agreements' => $currentAgreements,
];
}
/**
* 기본 약관 동의 항목
*/
public static function getDefaultAgreements(): array
{
return [
'terms' => [
'type' => 'terms',
'label' => '이용약관',
'required' => true,
'agreed' => false,
'agreed_at' => null,
],
'privacy' => [
'type' => 'privacy',
'label' => '개인정보 처리방침',
'required' => true,
'agreed' => false,
'agreed_at' => null,
],
'marketing' => [
'type' => 'marketing',
'label' => '마케팅 정보 수신',
'required' => false,
'agreed' => false,
'agreed_at' => null,
],
'push' => [
'type' => 'push',
'label' => '푸시 알림 수신',
'required' => false,
'agreed' => false,
'agreed_at' => null,
],
'email' => [
'type' => 'email',
'label' => '이메일 수신',
'required' => false,
'agreed' => false,
'agreed_at' => null,
],
'sms' => [
'type' => 'sms',
'label' => 'SMS 수신',
'required' => false,
'agreed' => false,
'agreed_at' => null,
],
];
}
/**
* 약관 유형 레이블
*/
public static function getAgreementTypes(): array
{
return [
'terms' => '이용약관',
'privacy' => '개인정보 처리방침',
'marketing' => '마케팅 정보 수신',
'push' => '푸시 알림 수신',
'email' => '이메일 수신',
'sms' => 'SMS 수신',
];
}
/**
* 탈퇴 사유 목록
*/
public static function getWithdrawalReasons(): array
{
return [
'not_using' => '서비스를 더 이상 사용하지 않음',
'difficult' => '사용하기 어려움',
'alternative' => '다른 서비스 이용',
'privacy' => '개인정보 보호 우려',
'other' => '기타',
];
}
}