feat: [pmis] PmisWorker 모델 분리 및 개인정보 관리 개선
- pmis_workers 전용 모델 생성 (SAM 사원관리와 분리) - 프로필 API 응답 worker 키로 변경 - 직책/소속업체 편집 기능 추가 - React 컴포넌트 data.user → data.worker 전환
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
namespace App\Http\Controllers\Juil;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Juil\PmisWorker;
|
||||
use App\Services\WeatherService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -58,24 +59,24 @@ public function pmisProfile(): JsonResponse
|
||||
{
|
||||
$user = auth()->user();
|
||||
$tenantId = session('current_tenant_id', 1);
|
||||
|
||||
$departments = $user->getDepartmentsForTenant($tenantId);
|
||||
$roles = $user->getRolesForTenant($tenantId);
|
||||
$worker = PmisWorker::findOrCreateFromUser($user, $tenantId);
|
||||
|
||||
return response()->json([
|
||||
'user' => [
|
||||
'name' => $user->name,
|
||||
'user_id' => $user->user_id,
|
||||
'email' => $user->email,
|
||||
'phone' => $user->phone,
|
||||
'role' => $user->role,
|
||||
'profile_photo_path' => $user->profile_photo_path,
|
||||
'department' => $departments->first()?->name ?? '-',
|
||||
'position' => $user->getOption('position') ?? '-',
|
||||
'gender' => data_get($user->options, 'gender', ''),
|
||||
'role_names' => $roles->pluck('name')->join(', ') ?: '-',
|
||||
'created_at' => $user->created_at?->format('Y-m-d'),
|
||||
'last_login_at' => $user->last_login_at?->format('Y-m-d H:i'),
|
||||
'worker' => [
|
||||
'id' => $worker->id,
|
||||
'name' => $worker->name,
|
||||
'login_id' => $worker->login_id,
|
||||
'phone' => $worker->phone,
|
||||
'email' => $worker->email,
|
||||
'department' => $worker->department ?? '-',
|
||||
'position' => $worker->position ?? '-',
|
||||
'role_type' => $worker->role_type ?? '-',
|
||||
'gender' => $worker->gender ?? '',
|
||||
'company' => $worker->company ?? '-',
|
||||
'profile_photo_path' => $worker->profile_photo_path,
|
||||
'created_at' => $worker->created_at?->format('Y-m-d'),
|
||||
'last_login_at' => $worker->last_login_at?->format('Y-m-d H:i')
|
||||
?? $user->last_login_at?->format('Y-m-d H:i'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
@@ -86,18 +87,21 @@ public function pmisProfileUpdate(Request $request): JsonResponse
|
||||
'phone' => ['nullable', 'string', 'max:20'],
|
||||
'email' => ['nullable', 'email', 'max:255'],
|
||||
'gender' => ['nullable', 'string', 'in:남,여'],
|
||||
'position' => ['nullable', 'string', 'max:50'],
|
||||
'company' => ['nullable', 'string', 'max:100'],
|
||||
]);
|
||||
|
||||
$user = auth()->user();
|
||||
$user->phone = $request->input('phone');
|
||||
$user->email = $request->input('email');
|
||||
$tenantId = session('current_tenant_id', 1);
|
||||
$worker = PmisWorker::findOrCreateFromUser($user, $tenantId);
|
||||
|
||||
$options = $user->options ?? [];
|
||||
$options['gender'] = $request->input('gender');
|
||||
$user->options = $options;
|
||||
|
||||
$user->updated_by = $user->id;
|
||||
$user->save();
|
||||
$worker->update([
|
||||
'phone' => $request->input('phone'),
|
||||
'email' => $request->input('email'),
|
||||
'gender' => $request->input('gender'),
|
||||
'position' => $request->input('position'),
|
||||
'company' => $request->input('company'),
|
||||
]);
|
||||
|
||||
return response()->json(['success' => true, 'message' => '개인정보가 저장되었습니다.']);
|
||||
}
|
||||
|
||||
84
app/Models/Juil/PmisWorker.php
Normal file
84
app/Models/Juil/PmisWorker.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Juil;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class PmisWorker extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'user_id',
|
||||
'name',
|
||||
'login_id',
|
||||
'phone',
|
||||
'email',
|
||||
'department',
|
||||
'position',
|
||||
'role_type',
|
||||
'gender',
|
||||
'company',
|
||||
'profile_photo_path',
|
||||
'options',
|
||||
'last_login_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'options' => 'array',
|
||||
'last_login_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* SAM 로그인 사용자로부터 PMIS Worker 자동 생성 (최초 접근 시)
|
||||
*/
|
||||
public static function findOrCreateFromUser(User $user, int $tenantId = 1): self
|
||||
{
|
||||
$worker = self::where('tenant_id', $tenantId)
|
||||
->where('user_id', $user->id)
|
||||
->first();
|
||||
|
||||
if ($worker) {
|
||||
return $worker;
|
||||
}
|
||||
|
||||
// 부서명 조회
|
||||
$departments = $user->getDepartmentsForTenant($tenantId);
|
||||
$deptName = $departments->first()?->name ?? null;
|
||||
|
||||
return self::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'user_id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'login_id' => $user->user_id,
|
||||
'phone' => $user->phone,
|
||||
'email' => $user->email,
|
||||
'department' => $deptName,
|
||||
'role_type' => $user->role ?? '사용자',
|
||||
'profile_photo_path' => $user->profile_photo_path,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getOption(string $key, mixed $default = null): mixed
|
||||
{
|
||||
return data_get($this->options, $key, $default);
|
||||
}
|
||||
|
||||
public function setOption(string $key, mixed $value): self
|
||||
{
|
||||
$options = $this->options ?? [];
|
||||
$options[$key] = $value;
|
||||
$this->options = $options;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ function PmisSidebar({ onOpenProfile }) {
|
||||
useEffect(() => {
|
||||
fetch('/juil/construction-pmis/profile', { headers: { 'Accept': 'application/json' } })
|
||||
.then(r => r.json())
|
||||
.then(data => setProfile(data.user))
|
||||
.then(data => setProfile(data.worker))
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
@@ -94,7 +94,7 @@ function ProfileModal({ isOpen, onClose }) {
|
||||
const [profile, setProfile] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [form, setForm] = useState({ phone: '', email: '', gender: '' });
|
||||
const [form, setForm] = useState({ phone: '', email: '', gender: '', position: '', company: '' });
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
const loadProfile = useCallback(() => {
|
||||
@@ -102,8 +102,8 @@ function ProfileModal({ isOpen, onClose }) {
|
||||
fetch('/juil/construction-pmis/profile', { headers: { 'Accept': 'application/json' } })
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
setProfile(data.user);
|
||||
setForm({ phone: data.user.phone || '', email: data.user.email || '', gender: data.user.gender || '' });
|
||||
setProfile(data.worker);
|
||||
setForm({ phone: data.worker.phone || '', email: data.worker.email || '', gender: data.worker.gender || '', position: data.worker.position || '', company: data.worker.company || '' });
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => setLoading(false));
|
||||
@@ -195,11 +195,18 @@ className="w-full px-2 py-1 border border-gray-300 rounded text-sm" />
|
||||
<td className="bg-gray-50 px-4 py-3 font-semibold text-gray-600">부서</td>
|
||||
<td className="px-4 py-3 text-gray-800">{profile.department}</td>
|
||||
<td className="bg-gray-50 px-4 py-3 font-semibold text-gray-600">아이디</td>
|
||||
<td className="px-4 py-3 text-gray-800">{profile.user_id}</td>
|
||||
<td className="px-4 py-3 text-gray-800">{profile.login_id}</td>
|
||||
</tr>
|
||||
<tr className="border-t border-gray-200">
|
||||
<td className="bg-gray-50 px-4 py-3 font-semibold text-gray-600">직책</td>
|
||||
<td className="px-4 py-3 text-gray-800">{profile.position}</td>
|
||||
<td className="px-4 py-3">
|
||||
{editMode ? (
|
||||
<input type="text" value={form.position} onChange={e => setForm(f => ({...f, position: e.target.value}))}
|
||||
className="w-full px-2 py-1 border border-gray-300 rounded text-sm" />
|
||||
) : (
|
||||
<span className="text-gray-800">{profile.position || '-'}</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="bg-gray-50 px-4 py-3 font-semibold text-gray-600">이메일</td>
|
||||
<td className="px-4 py-3">
|
||||
{editMode ? (
|
||||
@@ -210,9 +217,9 @@ className="w-full px-2 py-1 border border-gray-300 rounded text-sm" />
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr className="border-t border-b border-gray-200">
|
||||
<tr className="border-t border-gray-200">
|
||||
<td className="bg-gray-50 px-4 py-3 font-semibold text-gray-600">권한</td>
|
||||
<td className="px-4 py-3 text-gray-800">{profile.role_names}</td>
|
||||
<td className="px-4 py-3 text-gray-800">{profile.role_type}</td>
|
||||
<td className="bg-gray-50 px-4 py-3 font-semibold text-gray-600">성별</td>
|
||||
<td className="px-4 py-3">
|
||||
{editMode ? (
|
||||
@@ -227,6 +234,17 @@ className="px-2 py-1 border border-gray-300 rounded text-sm">
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr className="border-t border-b border-gray-200">
|
||||
<td className="bg-gray-50 px-4 py-3 font-semibold text-gray-600">소속업체</td>
|
||||
<td className="px-4 py-3" colSpan={3}>
|
||||
{editMode ? (
|
||||
<input type="text" value={form.company} onChange={e => setForm(f => ({...f, company: e.target.value}))}
|
||||
className="px-2 py-1 border border-gray-300 rounded text-sm" style={{ width: '60%' }} />
|
||||
) : (
|
||||
<span className="text-gray-800">{profile.company || '-'}</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user