feat: Phase 5 API 개발 완료 (사용자 초대, 알림설정, 계정관리, 거래명세서)
5.1 사용자 초대 기능: - UserInvitation 마이그레이션, 모델, 서비스, 컨트롤러, Swagger - 초대 발송/수락/취소/재발송 API 5.2 알림설정 확장: - NotificationSetting 마이그레이션, 모델, 서비스, 컨트롤러, Swagger - 채널별/유형별 알림 설정 관리 5.3 계정정보 수정 API: - 회원탈퇴, 사용중지, 약관동의 관리 - AccountService, AccountController, Swagger 5.4 매출 거래명세서 API: - 거래명세서 조회/발행/이메일발송 - SaleService 확장, Swagger 문서화
This commit is contained in:
240
app/Services/AccountService.php
Normal file
240
app/Services/AccountService.php
Normal file
@@ -0,0 +1,240 @@
|
||||
<?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' => '기타',
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user