129 lines
3.9 KiB
PHP
129 lines
3.9 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
|
|
class Location extends Model
|
|
{
|
|
protected $table = 'locations';
|
|
|
|
protected $fillable = [
|
|
'display_name',
|
|
'place_name',
|
|
'raw_address',
|
|
'street',
|
|
'city',
|
|
'state',
|
|
'postal',
|
|
'country',
|
|
'lat',
|
|
'lon',
|
|
'phone',
|
|
'hours_json',
|
|
];
|
|
|
|
protected $casts = [
|
|
'lat' => 'float',
|
|
'lon' => 'float',
|
|
];
|
|
|
|
/**
|
|
* find an existing location by its normalized components or create it.
|
|
* backfills lat/lon/raw_address/place_name if the found row is missing them.
|
|
*/
|
|
public static function findOrCreateNormalized(array $norm, ?string $raw = null): self
|
|
{
|
|
$lookup = [
|
|
'display_name' => $norm['display_name'] ?? null,
|
|
'street' => $norm['street'] ?? null,
|
|
'city' => $norm['city'] ?? null,
|
|
'state' => $norm['state'] ?? null,
|
|
'postal' => $norm['postal'] ?? null,
|
|
'country' => $norm['country'] ?? null,
|
|
];
|
|
|
|
$existing = static::where($lookup)->first();
|
|
|
|
// fallback: try matching by the raw label (useful for seeds like "Home")
|
|
if (!$existing && $raw) {
|
|
$existing = static::where('display_name', $raw)
|
|
->orWhere('raw_address', $raw)
|
|
->first();
|
|
}
|
|
|
|
if ($existing) {
|
|
$changed = false;
|
|
|
|
// backfill coords if missing (and we have them)
|
|
$hasNewCoords = (!empty($norm['lat']) || !empty($norm['lon']));
|
|
if (($existing->lat === null || $existing->lon === null) && $hasNewCoords) {
|
|
$existing->lat = $norm['lat'] ?? $existing->lat;
|
|
$existing->lon = $norm['lon'] ?? $existing->lon;
|
|
$changed = true;
|
|
}
|
|
|
|
// backfill raw address if missing
|
|
if ($raw && $existing->raw_address === null) {
|
|
$existing->raw_address = $raw;
|
|
$changed = true;
|
|
}
|
|
|
|
// backfill place_name if missing
|
|
if ($existing->place_name === null && !empty($norm['place_name'])) {
|
|
$existing->place_name = $norm['place_name'];
|
|
$changed = true;
|
|
}
|
|
|
|
if ($changed) {
|
|
$existing->save();
|
|
}
|
|
|
|
return $existing;
|
|
}
|
|
|
|
return static::create([
|
|
'display_name' => $norm['display_name'] ?? ($raw ?: 'Unknown'),
|
|
'place_name' => $norm['place_name'] ?? null,
|
|
'raw_address' => $norm['raw_address'] ?? $raw,
|
|
'street' => $norm['street'] ?? null,
|
|
'city' => $norm['city'] ?? null,
|
|
'state' => $norm['state'] ?? null,
|
|
'postal' => $norm['postal'] ?? null,
|
|
'country' => $norm['country'] ?? null,
|
|
'lat' => $norm['lat'] ?? null,
|
|
'lon' => $norm['lon'] ?? null,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* thin alias for backwards compatibility with older call-sites.
|
|
* prefer using findOrCreateNormalized() directly.
|
|
*/
|
|
public static function firstOrCreateNormalized(array $norm, ?string $raw = null): self
|
|
{
|
|
return static::findOrCreateNormalized($norm, $raw);
|
|
}
|
|
|
|
/**
|
|
* create/find a label-only location (no geocode)
|
|
*/
|
|
public static function labelOnly(string $label): self
|
|
{
|
|
return static::firstOrCreate(
|
|
['display_name' => $label],
|
|
['raw_address' => null]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* accessor: short label for ui (falls back gracefully)
|
|
*/
|
|
public function getShortLabelAttribute(): string
|
|
{
|
|
return $this->place_name
|
|
?? $this->display_name
|
|
?? trim(collect([$this->street, $this->city])->filter()->join(', '));
|
|
}
|
|
}
|