- Auditable 트레이트 신규 생성 (bootAuditable 패턴) - creating: created_by/updated_by 자동 채우기 - updating: updated_by 자동 채우기 - deleting: deleted_by 채우기 + saveQuietly() - created/updated/deleted: audit_logs 자동 기록 - 기존 AuditLogger 패턴과 동일한 try/catch 조용한 실패 - 변경된 필드만 before/after 기록 (updated 이벤트) - auditExclude 프로퍼티로 모델별 제외 필드 설정 가능 - 제외 대상: Attendance, StockTransaction, TodayIssue 등 고빈도/시스템 모델 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
160 lines
3.9 KiB
PHP
160 lines
3.9 KiB
PHP
<?php
|
|
|
|
namespace App\Models\Tenants;
|
|
|
|
use App\Models\Members\User;
|
|
use App\Models\Orders\Client;
|
|
use App\Traits\Auditable;
|
|
use App\Traits\BelongsToTenant;
|
|
use App\Traits\ModelTrait;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
|
|
/**
|
|
* 현장 모델
|
|
*
|
|
* @property int $id
|
|
* @property int $tenant_id
|
|
* @property string|null $site_code 현장코드
|
|
* @property int|null $client_id 거래처 ID
|
|
* @property string $name
|
|
* @property string|null $address
|
|
* @property float|null $latitude
|
|
* @property float|null $longitude
|
|
* @property bool $is_active
|
|
* @property string $status 상태: unregistered|suspended|active|pending
|
|
* @property int|null $created_by
|
|
* @property int|null $updated_by
|
|
* @property int|null $deleted_by
|
|
* @property-read Client|null $client
|
|
*/
|
|
class Site extends Model
|
|
{
|
|
use Auditable, BelongsToTenant, ModelTrait, SoftDeletes;
|
|
|
|
protected $table = 'sites';
|
|
|
|
// 상태 상수
|
|
public const STATUS_UNREGISTERED = 'unregistered';
|
|
|
|
public const STATUS_SUSPENDED = 'suspended';
|
|
|
|
public const STATUS_ACTIVE = 'active';
|
|
|
|
public const STATUS_PENDING = 'pending';
|
|
|
|
public const STATUSES = [
|
|
self::STATUS_UNREGISTERED,
|
|
self::STATUS_SUSPENDED,
|
|
self::STATUS_ACTIVE,
|
|
self::STATUS_PENDING,
|
|
];
|
|
|
|
protected $fillable = [
|
|
'tenant_id',
|
|
'site_code',
|
|
'client_id',
|
|
'name',
|
|
'address',
|
|
'latitude',
|
|
'longitude',
|
|
'is_active',
|
|
'status',
|
|
'created_by',
|
|
'updated_by',
|
|
'deleted_by',
|
|
];
|
|
|
|
protected $casts = [
|
|
'latitude' => 'decimal:8',
|
|
'longitude' => 'decimal:8',
|
|
'is_active' => 'boolean',
|
|
];
|
|
|
|
protected $attributes = [
|
|
'is_active' => true,
|
|
'status' => self::STATUS_UNREGISTERED,
|
|
];
|
|
|
|
// =========================================================================
|
|
// 관계 정의
|
|
// =========================================================================
|
|
|
|
/**
|
|
* 생성자
|
|
*/
|
|
public function creator(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'created_by');
|
|
}
|
|
|
|
/**
|
|
* 수정자
|
|
*/
|
|
public function updater(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'updated_by');
|
|
}
|
|
|
|
/**
|
|
* 거래처
|
|
*/
|
|
public function client(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Client::class);
|
|
}
|
|
|
|
// =========================================================================
|
|
// 헬퍼 메서드
|
|
// =========================================================================
|
|
|
|
/**
|
|
* GPS 좌표 설정 여부
|
|
*/
|
|
public function hasCoordinates(): bool
|
|
{
|
|
return $this->latitude !== null && $this->longitude !== null;
|
|
}
|
|
|
|
/**
|
|
* 좌표가 허용 범위 내인지 확인
|
|
*/
|
|
public function isWithinRadius(float $latitude, float $longitude, int $radiusMeters = 100): bool
|
|
{
|
|
if (! $this->hasCoordinates()) {
|
|
return true; // 좌표 미설정 시 항상 허용
|
|
}
|
|
|
|
$distance = $this->calculateDistance(
|
|
$this->latitude,
|
|
$this->longitude,
|
|
$latitude,
|
|
$longitude
|
|
);
|
|
|
|
return $distance <= $radiusMeters;
|
|
}
|
|
|
|
/**
|
|
* 두 좌표 간 거리 계산 (미터)
|
|
*/
|
|
private function calculateDistance(float $lat1, float $lon1, float $lat2, float $lon2): float
|
|
{
|
|
$earthRadius = 6371000;
|
|
|
|
$lat1Rad = deg2rad($lat1);
|
|
$lat2Rad = deg2rad($lat2);
|
|
$deltaLat = deg2rad($lat2 - $lat1);
|
|
$deltaLon = deg2rad($lon2 - $lon1);
|
|
|
|
$a = sin($deltaLat / 2) * sin($deltaLat / 2) +
|
|
cos($lat1Rad) * cos($lat2Rad) *
|
|
sin($deltaLon / 2) * sin($deltaLon / 2);
|
|
|
|
$c = 2 * atan2(sqrt($a), sqrt(1 - $a));
|
|
|
|
return $earthRadius * $c;
|
|
}
|
|
}
|