5.1 사용자 초대 기능: - UserInvitation 마이그레이션, 모델, 서비스, 컨트롤러, Swagger - 초대 발송/수락/취소/재발송 API 5.2 알림설정 확장: - NotificationSetting 마이그레이션, 모델, 서비스, 컨트롤러, Swagger - 채널별/유형별 알림 설정 관리 5.3 계정정보 수정 API: - 회원탈퇴, 사용중지, 약관동의 관리 - AccountService, AccountController, Swagger 5.4 매출 거래명세서 API: - 거래명세서 조회/발행/이메일발송 - SaleService 확장, Swagger 문서화
241 lines
7.2 KiB
PHP
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' => '기타',
|
|
];
|
|
}
|
|
}
|