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
< ? php
namespace App\Swagger\v1 ;
/**
* @ OA\Tag (
* name = " UserInvitation " ,
* description = " 사용자 초대 관리 "
* )
*
* @ OA\Schema (
* schema = " UserInvitation " ,
* type = " object " ,
* required = { " id " , " tenant_id " , " email " , " token " , " status " , " invited_by " , " expires_at " },
*
* @ OA\Property ( property = " id " , type = " integer " , example = 1 ),
* @ OA\Property ( property = " tenant_id " , type = " integer " , example = 1 ),
* @ OA\Property ( property = " email " , type = " string " , format = " email " , example = " user@example.com " ),
* @ OA\Property ( property = " role_id " , type = " integer " , nullable = true , example = 2 ),
* @ OA\Property ( property = " message " , type = " string " , nullable = true , example = " 회사에 합류해 주세요! " ),
* @ OA\Property ( property = " token " , type = " string " , example = " abc123... " ),
* @ OA\Property ( property = " status " , type = " string " , enum = { " pending " , " accepted " , " expired " , " cancelled " }, example = " pending " ),
* @ OA\Property ( property = " status_label " , type = " string " , example = " 대기중 " ),
* @ OA\Property ( property = " invited_by " , type = " integer " , example = 1 ),
* @ OA\Property ( property = " expires_at " , type = " string " , format = " date-time " ),
* @ OA\Property ( property = " accepted_at " , type = " string " , format = " date-time " , nullable = true ),
* @ OA\Property ( property = " created_at " , type = " string " , format = " date-time " ),
* @ OA\Property ( property = " updated_at " , type = " string " , format = " date-time " ),
* @ OA\Property (
* property = " role " ,
* type = " object " ,
* nullable = true ,
* @ OA\Property ( property = " id " , type = " integer " ),
* @ OA\Property ( property = " name " , type = " string " )
* ),
* @ OA\Property (
* property = " inviter " ,
* type = " object " ,
* @ OA\Property ( property = " id " , type = " integer " ),
* @ OA\Property ( property = " name " , type = " string " )
* )
* )
*
* @ OA\Schema (
* schema = " UserInvitationPagination " ,
* type = " object " ,
*
* @ OA\Property (
* property = " data " ,
* type = " array " ,
*
* @ OA\Items ( ref = " #/components/schemas/UserInvitation " )
* ),
*
* @ OA\Property ( property = " current_page " , type = " integer " , example = 1 ),
* @ OA\Property ( property = " last_page " , type = " integer " , example = 5 ),
* @ OA\Property ( property = " per_page " , type = " integer " , example = 20 ),
* @ OA\Property ( property = " total " , type = " integer " , example = 100 )
* )
*
* @ OA\Schema (
* schema = " InviteUserRequest " ,
* type = " object " ,
* required = { " email " },
*
* @ OA\Property ( property = " email " , type = " string " , format = " email " , example = " newuser@example.com " , description = " 초대할 사용자 이메일 " ),
2025-12-22 17:42:59 +09:00
* @ OA\Property ( property = " role " , type = " string " , nullable = true , enum = { " admin " , " manager " , " user " }, example = " user " , description = " 부여할 역할 (role 또는 role_id 중 하나만 사용) " ),
* @ OA\Property ( property = " role_id " , type = " integer " , nullable = true , example = 2 , description = " 부여할 역할 ID (role 또는 role_id 중 하나만 사용) " ),
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
* @ OA\Property ( property = " message " , type = " string " , nullable = true , example = " SAM 시스템에 합류해 주세요! " , description = " 초대 메시지 " ),
* @ OA\Property ( property = " expires_days " , type = " integer " , nullable = true , example = 7 , minimum = 1 , maximum = 30 , description = " 만료 기간(일) " )
* )
*
* @ OA\Schema (
* schema = " AcceptInvitationRequest " ,
* type = " object " ,
* required = { " name " , " password " , " password_confirmation " },
*
* @ OA\Property ( property = " name " , type = " string " , example = " 홍길동 " , description = " 사용자 이름 " ),
* @ OA\Property ( property = " password " , type = " string " , format = " password " , example = " password123 " , description = " 비밀번호 (8자 이상) " ),
* @ OA\Property ( property = " password_confirmation " , type = " string " , format = " password " , example = " password123 " , description = " 비밀번호 확인 " ),
* @ OA\Property ( property = " phone " , type = " string " , nullable = true , example = " 010-1234-5678 " , description = " 연락처 " )
* )
*/
class UserInvitationApi
{
/**
* @ OA\Get (
* path = " /api/v1/users/invitations " ,
* operationId = " getUserInvitations " ,
* tags = { " UserInvitation " },
* summary = " 초대 목록 조회 " ,
* description = " 테넌트의 사용자 초대 목록을 조회합니다. " ,
* security = {{ " ApiKeyAuth " : {}}, { " BearerAuth " : {}}},
*
* @ OA\Parameter (
* name = " status " ,
* in = " query " ,
* description = " 상태 필터 " ,
* required = false ,
*
* @ OA\Schema ( type = " string " , enum = { " pending " , " accepted " , " expired " , " cancelled " })
* ),
*
* @ OA\Parameter (
* name = " search " ,
* in = " query " ,
* description = " 이메일 검색 " ,
* required = false ,
*
* @ OA\Schema ( type = " string " )
* ),
*
* @ OA\Parameter (
* name = " sort_by " ,
* in = " query " ,
* description = " 정렬 기준 " ,
* required = false ,
*
* @ OA\Schema ( type = " string " , enum = { " created_at " , " expires_at " , " email " }, default = " created_at " )
* ),
*
* @ OA\Parameter (
* name = " sort_dir " ,
* in = " query " ,
* description = " 정렬 방향 " ,
* required = false ,
*
* @ OA\Schema ( type = " string " , enum = { " asc " , " desc " }, default = " desc " )
* ),
*
* @ OA\Parameter (
* name = " per_page " ,
* in = " query " ,
* description = " 페이지당 항목 수 " ,
* required = false ,
*
* @ OA\Schema ( type = " integer " , default = 20 , minimum = 1 , maximum = 100 )
* ),
*
* @ OA\Response (
* response = 200 ,
* description = " 성공 " ,
*
* @ OA\JsonContent (
*
* @ OA\Property ( property = " success " , type = " boolean " , example = true ),
* @ OA\Property ( property = " message " , type = " string " , example = " 조회 완료 " ),
* @ OA\Property ( property = " data " , ref = " #/components/schemas/UserInvitationPagination " )
* )
* ),
*
* @ OA\Response ( response = 401 , description = " 인증 실패 " ),
* @ OA\Response ( response = 403 , description = " 권한 없음 " )
* )
*/
public function index () {}
/**
* @ OA\Post (
* path = " /api/v1/users/invite " ,
* operationId = " inviteUser " ,
* tags = { " UserInvitation " },
* summary = " 사용자 초대 " ,
* description = " 새로운 사용자를 테넌트에 초대합니다. 초대 이메일이 발송됩니다. " ,
* security = {{ " ApiKeyAuth " : {}}, { " BearerAuth " : {}}},
*
* @ OA\RequestBody (
* required = true ,
*
* @ OA\JsonContent ( ref = " #/components/schemas/InviteUserRequest " )
* ),
*
* @ OA\Response (
* response = 200 ,
* description = " 초대 발송 성공 " ,
*
* @ OA\JsonContent (
*
* @ OA\Property ( property = " success " , type = " boolean " , example = true ),
* @ OA\Property ( property = " message " , type = " string " , example = " 초대가 발송되었습니다 " ),
* @ OA\Property ( property = " data " , ref = " #/components/schemas/UserInvitation " )
* )
* ),
*
* @ OA\Response ( response = 400 , description = " 이미 가입된 사용자 또는 대기 중인 초대 존재 " ),
* @ OA\Response ( response = 401 , description = " 인증 실패 " ),
* @ OA\Response ( response = 422 , description = " 유효성 검증 실패 " )
* )
*/
public function invite () {}
/**
* @ OA\Post (
* path = " /api/v1/users/invitations/ { token}/accept " ,
* operationId = " acceptInvitation " ,
* tags = { " UserInvitation " },
* summary = " 초대 수락 " ,
* description = " 초대를 수락하고 사용자 계정을 생성합니다. 인증 불필요. " ,
* security = {{ " ApiKeyAuth " : {}}},
*
* @ OA\Parameter (
* name = " token " ,
* in = " path " ,
* description = " 초대 토큰 " ,
* required = true ,
*
* @ OA\Schema ( type = " string " )
* ),
*
* @ OA\RequestBody (
* required = true ,
*
* @ OA\JsonContent ( ref = " #/components/schemas/AcceptInvitationRequest " )
* ),
*
* @ OA\Response (
* response = 200 ,
* description = " 초대 수락 성공 " ,
*
* @ OA\JsonContent (
*
* @ OA\Property ( property = " success " , type = " boolean " , example = true ),
* @ OA\Property ( property = " message " , type = " string " , example = " 초대가 수락되었습니다 " ),
* @ OA\Property ( property = " data " , type = " object " ,
* @ OA\Property ( property = " id " , type = " integer " ),
* @ OA\Property ( property = " name " , type = " string " ),
* @ OA\Property ( property = " email " , type = " string " )
* )
* )
* ),
*
* @ OA\Response ( response = 400 , description = " 만료된 초대 또는 잘못된 상태 " ),
* @ OA\Response ( response = 404 , description = " 초대를 찾을 수 없음 " ),
* @ OA\Response ( response = 422 , description = " 유효성 검증 실패 " )
* )
*/
public function accept () {}
/**
* @ OA\Delete (
* path = " /api/v1/users/invitations/ { id} " ,
* operationId = " cancelInvitation " ,
* tags = { " UserInvitation " },
* summary = " 초대 취소 " ,
* description = " 대기 중인 초대를 취소합니다. " ,
* security = {{ " ApiKeyAuth " : {}}, { " BearerAuth " : {}}},
*
* @ OA\Parameter (
* name = " id " ,
* in = " path " ,
* description = " 초대 ID " ,
* required = true ,
*
* @ OA\Schema ( type = " integer " )
* ),
*
* @ OA\Response (
* response = 200 ,
* description = " 취소 성공 " ,
*
* @ OA\JsonContent (
*
* @ OA\Property ( property = " success " , type = " boolean " , example = true ),
* @ OA\Property ( property = " message " , type = " string " , example = " 초대가 취소되었습니다 " ),
* @ OA\Property ( property = " data " , type = " boolean " , example = true )
* )
* ),
*
* @ OA\Response ( response = 400 , description = " 취소할 수 없는 상태 " ),
* @ OA\Response ( response = 401 , description = " 인증 실패 " ),
* @ OA\Response ( response = 404 , description = " 초대를 찾을 수 없음 " )
* )
*/
public function cancel () {}
/**
* @ OA\Post (
* path = " /api/v1/users/invitations/ { id}/resend " ,
* operationId = " resendInvitation " ,
* tags = { " UserInvitation " },
* summary = " 초대 재발송 " ,
* description = " 대기 중인 초대 이메일을 재발송합니다. 만료 기간이 연장됩니다. " ,
* security = {{ " ApiKeyAuth " : {}}, { " BearerAuth " : {}}},
*
* @ OA\Parameter (
* name = " id " ,
* in = " path " ,
* description = " 초대 ID " ,
* required = true ,
*
* @ OA\Schema ( type = " integer " )
* ),
*
* @ OA\Response (
* response = 200 ,
* description = " 재발송 성공 " ,
*
* @ OA\JsonContent (
*
* @ OA\Property ( property = " success " , type = " boolean " , example = true ),
* @ OA\Property ( property = " message " , type = " string " , example = " 초대가 재발송되었습니다 " ),
* @ OA\Property ( property = " data " , ref = " #/components/schemas/UserInvitation " )
* )
* ),
*
* @ OA\Response ( response = 400 , description = " 재발송할 수 없는 상태 " ),
* @ OA\Response ( response = 401 , description = " 인증 실패 " ),
* @ OA\Response ( response = 404 , description = " 초대를 찾을 수 없음 " )
* )
*/
public function resend () {}
}