오늘 이슈(TodayIssue) 기능 구현
- TodayIssue 모델 및 마이그레이션 추가 - TodayIssueController, TodayIssueService 구현 - TodayIssueObserverService 및 Observer 패턴 적용 - DailyReportService 연동 - Swagger API 문서 업데이트 - 라우트 추가
This commit is contained in:
@@ -95,6 +95,15 @@ class NotificationSettingGroup extends Model
|
||||
['notification_type' => 'production_complete', 'label' => '생산완료 알림', 'sort_order' => 2],
|
||||
],
|
||||
],
|
||||
[
|
||||
'code' => 'collection',
|
||||
'name' => '채권/지출 알림',
|
||||
'sort_order' => 8,
|
||||
'items' => [
|
||||
['notification_type' => 'bad_debt', 'label' => '추심이슈 알림', 'sort_order' => 1],
|
||||
['notification_type' => 'expected_expense', 'label' => '지출 승인대기 알림', 'sort_order' => 2],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -115,6 +124,8 @@ class NotificationSettingGroup extends Model
|
||||
'draft_completed' => 'draftCompleted',
|
||||
'safety_stock' => 'safetyStock',
|
||||
'production_complete' => 'productionComplete',
|
||||
'bad_debt' => 'badDebt',
|
||||
'expected_expense' => 'expectedExpense',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
208
app/Models/Tenants/TodayIssue.php
Normal file
208
app/Models/Tenants/TodayIssue.php
Normal file
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Tenants;
|
||||
|
||||
use App\Models\Users\User;
|
||||
use App\Traits\BelongsToTenant;
|
||||
use App\Traits\ModelTrait;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* 오늘의 이슈 모델
|
||||
*
|
||||
* CEO 대시보드용 실시간 이슈 저장
|
||||
*/
|
||||
class TodayIssue extends Model
|
||||
{
|
||||
use BelongsToTenant, HasFactory, ModelTrait;
|
||||
|
||||
protected $table = 'today_issues';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'source_type',
|
||||
'source_id',
|
||||
'badge',
|
||||
'notification_type',
|
||||
'content',
|
||||
'path',
|
||||
'needs_approval',
|
||||
'is_read',
|
||||
'read_by',
|
||||
'read_at',
|
||||
'expires_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'needs_approval' => 'boolean',
|
||||
'is_read' => 'boolean',
|
||||
'read_at' => 'datetime',
|
||||
'expires_at' => 'datetime',
|
||||
];
|
||||
|
||||
// 소스 타입 상수
|
||||
public const SOURCE_ORDER = 'order';
|
||||
|
||||
public const SOURCE_BAD_DEBT = 'bad_debt';
|
||||
|
||||
public const SOURCE_STOCK = 'stock';
|
||||
|
||||
public const SOURCE_EXPENSE = 'expense';
|
||||
|
||||
public const SOURCE_TAX = 'tax';
|
||||
|
||||
public const SOURCE_APPROVAL = 'approval';
|
||||
|
||||
public const SOURCE_CLIENT = 'client';
|
||||
|
||||
// 뱃지 타입 상수
|
||||
public const BADGE_ORDER_REGISTER = '수주등록';
|
||||
|
||||
public const BADGE_COLLECTION_ISSUE = '추심이슈';
|
||||
|
||||
public const BADGE_SAFETY_STOCK = '안전재고';
|
||||
|
||||
public const BADGE_EXPENSE_PENDING = '지출 승인대기';
|
||||
|
||||
public const BADGE_TAX_REPORT = '세금 신고';
|
||||
|
||||
public const BADGE_APPROVAL_REQUEST = '결재 요청';
|
||||
|
||||
public const BADGE_NEW_CLIENT = '신규거래처';
|
||||
|
||||
// 뱃지 → notification_type 매핑
|
||||
public const BADGE_TO_NOTIFICATION_TYPE = [
|
||||
self::BADGE_ORDER_REGISTER => 'sales_order',
|
||||
self::BADGE_NEW_CLIENT => 'new_vendor',
|
||||
self::BADGE_APPROVAL_REQUEST => 'approval_request',
|
||||
self::BADGE_COLLECTION_ISSUE => 'bad_debt',
|
||||
self::BADGE_SAFETY_STOCK => 'safety_stock',
|
||||
self::BADGE_EXPENSE_PENDING => 'expected_expense',
|
||||
self::BADGE_TAX_REPORT => 'vat_report',
|
||||
];
|
||||
|
||||
// 중요 알림 (푸시 알림음) - 수주등록, 신규거래처, 결재요청
|
||||
public const IMPORTANT_NOTIFICATIONS = [
|
||||
'sales_order',
|
||||
'new_vendor',
|
||||
'approval_request',
|
||||
];
|
||||
|
||||
/**
|
||||
* 확인한 사용자
|
||||
*/
|
||||
public function reader(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'read_by');
|
||||
}
|
||||
|
||||
/**
|
||||
* 읽지 않은 이슈 스코프
|
||||
*/
|
||||
public function scopeUnread($query)
|
||||
{
|
||||
return $query->where('is_read', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 만료되지 않은 이슈 스코프
|
||||
*/
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->where(function ($q) {
|
||||
$q->whereNull('expires_at')
|
||||
->orWhere('expires_at', '>', now());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 뱃지별 필터 스코프
|
||||
*/
|
||||
public function scopeByBadge($query, string $badge)
|
||||
{
|
||||
return $query->where('badge', $badge);
|
||||
}
|
||||
|
||||
/**
|
||||
* 소스별 필터 스코프
|
||||
*/
|
||||
public function scopeBySource($query, string $sourceType, ?int $sourceId = null)
|
||||
{
|
||||
$query->where('source_type', $sourceType);
|
||||
|
||||
if ($sourceId !== null) {
|
||||
$query->where('source_id', $sourceId);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* 이슈 확인 처리
|
||||
*/
|
||||
public function markAsRead(int $userId): bool
|
||||
{
|
||||
return $this->update([
|
||||
'is_read' => true,
|
||||
'read_by' => $userId,
|
||||
'read_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 이슈 생성 헬퍼 (정적 메서드)
|
||||
*/
|
||||
public static function createIssue(
|
||||
int $tenantId,
|
||||
string $sourceType,
|
||||
?int $sourceId,
|
||||
string $badge,
|
||||
string $content,
|
||||
?string $path = null,
|
||||
bool $needsApproval = false,
|
||||
?\DateTime $expiresAt = null
|
||||
): self {
|
||||
// badge에서 notification_type 자동 매핑
|
||||
$notificationType = self::BADGE_TO_NOTIFICATION_TYPE[$badge] ?? null;
|
||||
|
||||
return self::updateOrCreate(
|
||||
[
|
||||
'tenant_id' => $tenantId,
|
||||
'source_type' => $sourceType,
|
||||
'source_id' => $sourceId,
|
||||
],
|
||||
[
|
||||
'badge' => $badge,
|
||||
'notification_type' => $notificationType,
|
||||
'content' => $content,
|
||||
'path' => $path,
|
||||
'needs_approval' => $needsApproval,
|
||||
'expires_at' => $expiresAt,
|
||||
'is_read' => false,
|
||||
'read_by' => null,
|
||||
'read_at' => null,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 중요 알림 여부 확인
|
||||
*/
|
||||
public function isImportantNotification(): bool
|
||||
{
|
||||
return in_array($this->notification_type, self::IMPORTANT_NOTIFICATIONS, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 소스 기준 이슈 삭제 헬퍼 (정적 메서드)
|
||||
*/
|
||||
public static function removeBySource(int $tenantId, string $sourceType, int $sourceId): bool
|
||||
{
|
||||
return self::where('tenant_id', $tenantId)
|
||||
->where('source_type', $sourceType)
|
||||
->where('source_id', $sourceId)
|
||||
->delete() > 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user