feat: Phase 5.1-1 사용자 초대 + Phase 5.2 알림 설정 API 연동
- 사용자 초대 API: role 문자열 지원 추가 (React 호환) - 알림 설정 API: 그룹 기반 계층 구조 구현 - notification_setting_groups 테이블 추가 - notification_setting_group_items 테이블 추가 - notification_setting_group_states 테이블 추가 - GET/PUT /api/v1/settings/notifications 엔드포인트 추가 - Pint 코드 스타일 정리
This commit is contained in:
169
app/Models/NotificationSettingGroup.php
Normal file
169
app/Models/NotificationSettingGroup.php
Normal file
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\BelongsToTenant;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class NotificationSettingGroup extends Model
|
||||
{
|
||||
use BelongsToTenant;
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'code',
|
||||
'name',
|
||||
'sort_order',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'sort_order' => 'integer',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* 기본 그룹 정의 (React 구조 기준)
|
||||
*/
|
||||
public const DEFAULT_GROUPS = [
|
||||
[
|
||||
'code' => 'notice',
|
||||
'name' => '공지 알림',
|
||||
'sort_order' => 1,
|
||||
'items' => [
|
||||
['notification_type' => 'notice', 'label' => '공지사항 알림', 'sort_order' => 1],
|
||||
['notification_type' => 'event', 'label' => '이벤트 알림', 'sort_order' => 2],
|
||||
],
|
||||
],
|
||||
[
|
||||
'code' => 'schedule',
|
||||
'name' => '일정 알림',
|
||||
'sort_order' => 2,
|
||||
'items' => [
|
||||
['notification_type' => 'vat_report', 'label' => '부가세 신고 알림', 'sort_order' => 1],
|
||||
['notification_type' => 'income_tax_report', 'label' => '종합소득세 신고 알림', 'sort_order' => 2],
|
||||
],
|
||||
],
|
||||
[
|
||||
'code' => 'vendor',
|
||||
'name' => '거래처 알림',
|
||||
'sort_order' => 3,
|
||||
'items' => [
|
||||
['notification_type' => 'new_vendor', 'label' => '신규 업체 등록 알림', 'sort_order' => 1],
|
||||
['notification_type' => 'credit_rating', 'label' => '신용등급 등록 알림', 'sort_order' => 2],
|
||||
],
|
||||
],
|
||||
[
|
||||
'code' => 'attendance',
|
||||
'name' => '근태 알림',
|
||||
'sort_order' => 4,
|
||||
'items' => [
|
||||
['notification_type' => 'annual_leave', 'label' => '연차 알림', 'sort_order' => 1],
|
||||
['notification_type' => 'clock_in', 'label' => '출근 알림', 'sort_order' => 2],
|
||||
['notification_type' => 'late', 'label' => '지각 알림', 'sort_order' => 3],
|
||||
['notification_type' => 'absent', 'label' => '결근 알림', 'sort_order' => 4],
|
||||
],
|
||||
],
|
||||
[
|
||||
'code' => 'order',
|
||||
'name' => '수주/발주 알림',
|
||||
'sort_order' => 5,
|
||||
'items' => [
|
||||
['notification_type' => 'sales_order', 'label' => '수주 등록 알림', 'sort_order' => 1],
|
||||
['notification_type' => 'purchase_order', 'label' => '발주 알림', 'sort_order' => 2],
|
||||
],
|
||||
],
|
||||
[
|
||||
'code' => 'approval',
|
||||
'name' => '전자결재 알림',
|
||||
'sort_order' => 6,
|
||||
'items' => [
|
||||
['notification_type' => 'approval_request', 'label' => '결재요청 알림', 'sort_order' => 1],
|
||||
['notification_type' => 'draft_approved', 'label' => '기안 > 승인 알림', 'sort_order' => 2],
|
||||
['notification_type' => 'draft_rejected', 'label' => '기안 > 반려 알림', 'sort_order' => 3],
|
||||
['notification_type' => 'draft_completed', 'label' => '기안 > 완료 알림', 'sort_order' => 4],
|
||||
],
|
||||
],
|
||||
[
|
||||
'code' => 'production',
|
||||
'name' => '생산 알림',
|
||||
'sort_order' => 7,
|
||||
'items' => [
|
||||
['notification_type' => 'safety_stock', 'label' => '안전재고 알림', 'sort_order' => 1],
|
||||
['notification_type' => 'production_complete', 'label' => '생산완료 알림', 'sort_order' => 2],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* snake_case → camelCase 변환 맵
|
||||
*/
|
||||
public const CAMEL_CASE_MAP = [
|
||||
'vat_report' => 'vatReport',
|
||||
'income_tax_report' => 'incomeTaxReport',
|
||||
'new_vendor' => 'newVendor',
|
||||
'credit_rating' => 'creditRating',
|
||||
'annual_leave' => 'annualLeave',
|
||||
'clock_in' => 'clockIn',
|
||||
'sales_order' => 'salesOrder',
|
||||
'purchase_order' => 'purchaseOrder',
|
||||
'approval_request' => 'approvalRequest',
|
||||
'draft_approved' => 'draftApproved',
|
||||
'draft_rejected' => 'draftRejected',
|
||||
'draft_completed' => 'draftCompleted',
|
||||
'safety_stock' => 'safetyStock',
|
||||
'production_complete' => 'productionComplete',
|
||||
];
|
||||
|
||||
/**
|
||||
* 테넌트 관계
|
||||
*/
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Tenant::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 그룹 항목들
|
||||
*/
|
||||
public function items(): HasMany
|
||||
{
|
||||
return $this->hasMany(NotificationSettingGroupItem::class, 'group_id')->orderBy('sort_order');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: 활성화된 그룹만
|
||||
*/
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->where('is_active', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: 정렬순
|
||||
*/
|
||||
public function scopeOrdered($query)
|
||||
{
|
||||
return $query->orderBy('sort_order');
|
||||
}
|
||||
|
||||
/**
|
||||
* snake_case를 camelCase로 변환
|
||||
*/
|
||||
public static function toCamelCase(string $snakeCase): string
|
||||
{
|
||||
return self::CAMEL_CASE_MAP[$snakeCase] ?? $snakeCase;
|
||||
}
|
||||
|
||||
/**
|
||||
* camelCase를 snake_case로 변환
|
||||
*/
|
||||
public static function toSnakeCase(string $camelCase): string
|
||||
{
|
||||
$flipped = array_flip(self::CAMEL_CASE_MAP);
|
||||
|
||||
return $flipped[$camelCase] ?? $camelCase;
|
||||
}
|
||||
}
|
||||
36
app/Models/NotificationSettingGroupItem.php
Normal file
36
app/Models/NotificationSettingGroupItem.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class NotificationSettingGroupItem extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'group_id',
|
||||
'notification_type',
|
||||
'label',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'sort_order' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* 그룹 관계
|
||||
*/
|
||||
public function group(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(NotificationSettingGroup::class, 'group_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: 정렬순
|
||||
*/
|
||||
public function scopeOrdered($query)
|
||||
{
|
||||
return $query->orderBy('sort_order');
|
||||
}
|
||||
}
|
||||
55
app/Models/NotificationSettingGroupState.php
Normal file
55
app/Models/NotificationSettingGroupState.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\BelongsToTenant;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class NotificationSettingGroupState extends Model
|
||||
{
|
||||
use BelongsToTenant;
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'user_id',
|
||||
'group_code',
|
||||
'enabled',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'enabled' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* 테넌트 관계
|
||||
*/
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Tenant::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 관계
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: 특정 사용자의 설정
|
||||
*/
|
||||
public function scopeForUser($query, int $userId)
|
||||
{
|
||||
return $query->where('user_id', $userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: 특정 그룹
|
||||
*/
|
||||
public function scopeForGroup($query, string $groupCode)
|
||||
{
|
||||
return $query->where('group_code', $groupCode);
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* 시스템 필드 정의 모델
|
||||
|
||||
Reference in New Issue
Block a user