- TodayIssue 모델 및 마이그레이션 추가 - TodayIssueController, TodayIssueService 구현 - TodayIssueObserverService 및 Observer 패턴 적용 - DailyReportService 연동 - Swagger API 문서 업데이트 - 라우트 추가
209 lines
5.2 KiB
PHP
209 lines
5.2 KiB
PHP
<?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;
|
|
}
|
|
}
|