Files
sam-api/app/Models/Tenants/TodayIssue.php
김보곤 4870b7e6eb fix: [models] User 모델 import 누락/오류 수정
- Loan.php: User import 누락 → App\Models\Members\User 추가
- TodayIssue.php: App\Models\Users\User → App\Models\Members\User 수정
- Tenants 네임스페이스에서 User::class가 App\Models\Tenants\User로 잘못 해석되는 문제 해결
2026-03-04 11:06:26 +09:00

292 lines
8.1 KiB
PHP

<?php
namespace App\Models\Tenants;
use App\Models\Members\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',
'target_user_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 SOURCE_DEPOSIT = 'deposit';
public const SOURCE_WITHDRAWAL = 'withdrawal';
// 뱃지 타입 상수 (최대 4자, 띄어쓰기 없음)
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 = '신규업체';
public const BADGE_DEPOSIT = '입금';
public const BADGE_WITHDRAWAL = '출금';
// 기안 상태 변경 알림 뱃지
public const BADGE_DRAFT_APPROVED = '기안승인';
public const BADGE_DRAFT_REJECTED = '기안반려';
public const BADGE_DRAFT_COMPLETED = '기안완료';
// source_type → badge 매핑
public const SOURCE_TO_BADGE = [
self::SOURCE_ORDER => self::BADGE_ORDER_REGISTER,
self::SOURCE_BAD_DEBT => self::BADGE_COLLECTION_ISSUE,
self::SOURCE_STOCK => self::BADGE_SAFETY_STOCK,
self::SOURCE_EXPENSE => self::BADGE_EXPENSE_PENDING,
self::SOURCE_TAX => self::BADGE_TAX_REPORT,
self::SOURCE_APPROVAL => self::BADGE_APPROVAL_REQUEST,
self::SOURCE_CLIENT => self::BADGE_NEW_CLIENT,
self::SOURCE_DEPOSIT => self::BADGE_DEPOSIT,
self::SOURCE_WITHDRAWAL => self::BADGE_WITHDRAWAL,
];
// 뱃지 → 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_DRAFT_APPROVED => 'approval_request',
self::BADGE_DRAFT_REJECTED => 'approval_request',
self::BADGE_DRAFT_COMPLETED => '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',
self::BADGE_DEPOSIT => 'deposit',
self::BADGE_WITHDRAWAL => 'withdrawal',
];
// 중요 알림 (긴급 푸시) - 수주등록, 신규거래처, 결재요청
// 입금, 출금, 카드 등은 일반 푸시(push_default)로 발송
public const IMPORTANT_NOTIFICATIONS = [
'sales_order',
'new_vendor',
'approval_request',
];
/**
* 확인한 사용자
*/
public function reader(): BelongsTo
{
return $this->belongsTo(User::class, 'read_by');
}
/**
* 대상 사용자 (특정 사용자에게만 발송할 경우)
*/
public function targetUser(): BelongsTo
{
return $this->belongsTo(User::class, 'target_user_id');
}
/**
* 읽지 않은 이슈 스코프
*/
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 scopeToday($query)
{
return $query->whereDate('created_at', today());
}
/**
* 뱃지별 필터 스코프
*/
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;
}
/**
* 특정 사용자 대상 이슈 스코프
* target_user_id가 null이거나 지정된 사용자인 경우
*/
public function scopeForUser($query, int $userId)
{
return $query->where(function ($q) use ($userId) {
$q->whereNull('target_user_id')
->orWhere('target_user_id', $userId);
});
}
/**
* 특정 사용자만 대상인 이슈 스코프
*/
public function scopeTargetedTo($query, int $userId)
{
return $query->where('target_user_id', $userId);
}
/**
* 이슈 확인 처리
*/
public function markAsRead(int $userId): bool
{
return $this->update([
'is_read' => true,
'read_by' => $userId,
'read_at' => now(),
]);
}
/**
* 이슈 생성 헬퍼 (정적 메서드)
*
* @param int $tenantId 테넌트 ID
* @param string $sourceType 소스 타입 (order, approval, etc.)
* @param int|null $sourceId 소스 ID
* @param string $badge 뱃지 타입
* @param string $content 알림 내용
* @param string|null $path 이동 경로
* @param bool $needsApproval 승인 필요 여부
* @param \DateTime|null $expiresAt 만료 시간
* @param int|null $targetUserId 특정 대상 사용자 ID (null이면 테넌트 전체)
*/
public static function createIssue(
int $tenantId,
string $sourceType,
?int $sourceId,
string $badge,
string $content,
?string $path = null,
bool $needsApproval = false,
?\DateTime $expiresAt = null,
?int $targetUserId = 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,
'target_user_id' => $targetUserId,
],
[
'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;
}
}