refactor: 작업자 관리 개선 - 계정 연동 기능으로 변경
작업 보고서 표시 여부 대신 계정 연동 기능으로 개선했습니다.
## 주요 변경사항
### 개념 변경
- **이전**: 작업 보고서 표시 여부 (show_in_work_reports)
- **이후**: 계정 생성/연동 기능
### 데이터베이스
- **마이그레이션**: 20260119095549_add_worker_display_fields.js
- show_in_work_reports 컬럼 제거
- employment_status만 유지 (employed/resigned)
- **workerModel**:
- getAll, getById에서 users 테이블 JOIN하여 user_id 조회
- create, update에서 show_in_work_reports 필드 제거
### 백엔드 API
- **workerController.js**:
- createWorker: create_account 체크 시 자동으로 users 테이블에 계정 생성
- username: hangulToRoman으로 한글 이름 변환
- password: 초기 비밀번호 '1234' (bcrypt 해시)
- role: User 역할 자동 할당
- updateWorker:
- create_account=true & 계정 없음 → 계정 생성
- create_account=false & 계정 있음 → 계정 연동 해제 (users.worker_id=NULL)
### 프론트엔드
- **worker-management.html**:
- "작업 보고서 표시" → "🔐 계정 생성/연동"으로 변경
- 체크 시 로그인 계정 자동 생성 안내
- **worker-management.js**:
- 카드 렌더링: user_id 존재 여부로 계정 연동 상태 표시 (🔐 아이콘)
- saveWorker: create_account 필드 전송
- show_in_work_reports 관련 로직 모두 제거
- **daily-work-report.js**:
- 필터링 조건 단순화: 퇴사자만 제외 (employment_status≠resigned)
- 계정 여부와 무관하게 모든 재직자 표시
## 사용 방법
1. 작업자 등록/수정 시 "계정 생성/연동" 체크
2. 자동으로 로그인 계정 생성 (초기 비밀번호: 1234)
3. 계정이 있는 작업자는 나의 대시보드, 연차/출퇴근 관리 가능
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -13,14 +13,18 @@ const { asyncHandler } = require('../middlewares/errorHandler');
|
|||||||
const logger = require('../utils/logger');
|
const logger = require('../utils/logger');
|
||||||
const cache = require('../utils/cache');
|
const cache = require('../utils/cache');
|
||||||
const { optimizedQueries } = require('../utils/queryOptimizer');
|
const { optimizedQueries } = require('../utils/queryOptimizer');
|
||||||
|
const { hangulToRoman, generateUniqueUsername } = require('../utils/hangulToRoman');
|
||||||
|
const bcrypt = require('bcrypt');
|
||||||
|
const { getDb } = require('../dbPool');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 작업자 생성
|
* 작업자 생성
|
||||||
*/
|
*/
|
||||||
exports.createWorker = asyncHandler(async (req, res) => {
|
exports.createWorker = asyncHandler(async (req, res) => {
|
||||||
const workerData = req.body;
|
const workerData = req.body;
|
||||||
|
const createAccount = req.body.create_account;
|
||||||
|
|
||||||
logger.info('작업자 생성 요청', { name: workerData.name });
|
logger.info('작업자 생성 요청', { name: workerData.worker_name, create_account: createAccount });
|
||||||
|
|
||||||
const lastID = await new Promise((resolve, reject) => {
|
const lastID = await new Promise((resolve, reject) => {
|
||||||
workerModel.create(workerData, (err, id) => {
|
workerModel.create(workerData, (err, id) => {
|
||||||
@@ -29,6 +33,30 @@ exports.createWorker = asyncHandler(async (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 계정 생성 요청이 있으면 users 테이블에 계정 생성
|
||||||
|
if (createAccount && workerData.worker_name) {
|
||||||
|
try {
|
||||||
|
const db = await getDb();
|
||||||
|
const username = await generateUniqueUsername(workerData.worker_name, db);
|
||||||
|
const hashedPassword = await bcrypt.hash('1234', 10);
|
||||||
|
|
||||||
|
// User 역할 조회
|
||||||
|
const [userRole] = await db.query('SELECT id FROM roles WHERE name = ?', ['User']);
|
||||||
|
|
||||||
|
if (userRole && userRole.length > 0) {
|
||||||
|
await db.query(
|
||||||
|
`INSERT INTO users (username, password, name, worker_id, role_id, created_at, updated_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?, NOW(), NOW())`,
|
||||||
|
[username, hashedPassword, workerData.worker_name, lastID, userRole[0].id]
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.info('작업자 계정 자동 생성 성공', { worker_id: lastID, username });
|
||||||
|
}
|
||||||
|
} catch (accountError) {
|
||||||
|
logger.error('계정 생성 실패 (작업자는 생성됨)', { worker_id: lastID, error: accountError.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 작업자 관련 캐시 무효화
|
// 작업자 관련 캐시 무효화
|
||||||
await cache.invalidateCache.worker();
|
await cache.invalidateCache.worker();
|
||||||
|
|
||||||
@@ -115,13 +143,28 @@ exports.updateWorker = asyncHandler(async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const workerData = { ...req.body, worker_id: id };
|
const workerData = { ...req.body, worker_id: id };
|
||||||
|
const createAccount = req.body.create_account;
|
||||||
|
|
||||||
console.log('🔧 작업자 수정 요청:', {
|
console.log('🔧 작업자 수정 요청:', {
|
||||||
worker_id: id,
|
worker_id: id,
|
||||||
받은데이터: req.body,
|
받은데이터: req.body,
|
||||||
처리할데이터: workerData
|
처리할데이터: workerData,
|
||||||
|
create_account: createAccount
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 먼저 현재 작업자 정보 조회 (계정 여부 확인용)
|
||||||
|
const currentWorker = await new Promise((resolve, reject) => {
|
||||||
|
workerModel.getById(id, (err, data) => {
|
||||||
|
if (err) reject(new DatabaseError('작업자 조회 중 오류가 발생했습니다'));
|
||||||
|
else resolve(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!currentWorker) {
|
||||||
|
throw new NotFoundError('작업자를 찾을 수 없습니다');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 작업자 정보 업데이트
|
||||||
const changes = await new Promise((resolve, reject) => {
|
const changes = await new Promise((resolve, reject) => {
|
||||||
workerModel.update(workerData, (err, affected) => {
|
workerModel.update(workerData, (err, affected) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -132,8 +175,39 @@ exports.updateWorker = asyncHandler(async (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (changes === 0) {
|
// 계정 생성/해제 처리
|
||||||
throw new NotFoundError('작업자를 찾을 수 없습니다');
|
const db = await getDb();
|
||||||
|
const hasAccount = currentWorker.user_id !== null && currentWorker.user_id !== undefined;
|
||||||
|
|
||||||
|
if (createAccount && !hasAccount && workerData.worker_name) {
|
||||||
|
// 계정 생성
|
||||||
|
try {
|
||||||
|
const username = await generateUniqueUsername(workerData.worker_name, db);
|
||||||
|
const hashedPassword = await bcrypt.hash('1234', 10);
|
||||||
|
|
||||||
|
// User 역할 조회
|
||||||
|
const [userRole] = await db.query('SELECT id FROM roles WHERE name = ?', ['User']);
|
||||||
|
|
||||||
|
if (userRole && userRole.length > 0) {
|
||||||
|
await db.query(
|
||||||
|
`INSERT INTO users (username, password, name, worker_id, role_id, created_at, updated_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?, NOW(), NOW())`,
|
||||||
|
[username, hashedPassword, workerData.worker_name, id, userRole[0].id]
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.info('작업자 계정 생성 성공', { worker_id: id, username });
|
||||||
|
}
|
||||||
|
} catch (accountError) {
|
||||||
|
logger.error('계정 생성 실패', { worker_id: id, error: accountError.message });
|
||||||
|
}
|
||||||
|
} else if (!createAccount && hasAccount) {
|
||||||
|
// 계정 연동 해제 (users.worker_id = NULL)
|
||||||
|
try {
|
||||||
|
await db.query('UPDATE users SET worker_id = NULL WHERE worker_id = ?', [id]);
|
||||||
|
logger.info('작업자 계정 연동 해제 성공', { worker_id: id });
|
||||||
|
} catch (unlinkError) {
|
||||||
|
logger.error('계정 연동 해제 실패', { worker_id: id, error: unlinkError.message });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 작업자 관련 캐시 무효화
|
// 작업자 관련 캐시 무효화
|
||||||
|
|||||||
@@ -4,20 +4,14 @@
|
|||||||
*/
|
*/
|
||||||
exports.up = async function(knex) {
|
exports.up = async function(knex) {
|
||||||
await knex.schema.alterTable('workers', (table) => {
|
await knex.schema.alterTable('workers', (table) => {
|
||||||
// 작업 보고서 표시 여부 (기본값: true, 작업자는 표시, 관리자는 선택 가능)
|
|
||||||
table.boolean('show_in_work_reports')
|
|
||||||
.defaultTo(true)
|
|
||||||
.notNullable()
|
|
||||||
.comment('작업 보고서에 표시 여부');
|
|
||||||
|
|
||||||
// 재직 상태 (employed: 재직, resigned: 퇴사)
|
// 재직 상태 (employed: 재직, resigned: 퇴사)
|
||||||
table.enum('employment_status', ['employed', 'resigned'])
|
table.enum('employment_status', ['employed', 'resigned'])
|
||||||
.defaultTo('employed')
|
.defaultTo('employed')
|
||||||
.notNullable()
|
.notNullable()
|
||||||
.comment('재직 상태');
|
.comment('재직 상태 (employed: 재직, resigned: 퇴사)');
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('✅ workers 테이블에 show_in_work_reports, employment_status 컬럼 추가 완료');
|
console.log('✅ workers 테이블에 employment_status 컬럼 추가 완료');
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,9 +20,8 @@ exports.up = async function(knex) {
|
|||||||
*/
|
*/
|
||||||
exports.down = async function(knex) {
|
exports.down = async function(knex) {
|
||||||
await knex.schema.alterTable('workers', (table) => {
|
await knex.schema.alterTable('workers', (table) => {
|
||||||
table.dropColumn('show_in_work_reports');
|
|
||||||
table.dropColumn('employment_status');
|
table.dropColumn('employment_status');
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('✅ workers 테이블에서 show_in_work_reports, employment_status 컬럼 삭제 완료');
|
console.log('✅ workers 테이블에서 employment_status 컬럼 삭제 완료');
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,15 +20,14 @@ const create = async (worker, callback) => {
|
|||||||
salary = null,
|
salary = null,
|
||||||
annual_leave = null,
|
annual_leave = null,
|
||||||
status = 'active',
|
status = 'active',
|
||||||
show_in_work_reports = true,
|
|
||||||
employment_status = 'employed'
|
employment_status = 'employed'
|
||||||
} = worker;
|
} = worker;
|
||||||
|
|
||||||
const [result] = await db.query(
|
const [result] = await db.query(
|
||||||
`INSERT INTO workers
|
`INSERT INTO workers
|
||||||
(worker_name, job_type, join_date, salary, annual_leave, status, show_in_work_reports, employment_status)
|
(worker_name, job_type, join_date, salary, annual_leave, status, employment_status)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||||
[worker_name, job_type, formatDate(join_date), salary, annual_leave, status, show_in_work_reports, employment_status]
|
[worker_name, job_type, formatDate(join_date), salary, annual_leave, status, employment_status]
|
||||||
);
|
);
|
||||||
|
|
||||||
callback(null, result.insertId);
|
callback(null, result.insertId);
|
||||||
@@ -44,10 +43,12 @@ const getAll = async (callback) => {
|
|||||||
const db = await getDb();
|
const db = await getDb();
|
||||||
const [rows] = await db.query(`
|
const [rows] = await db.query(`
|
||||||
SELECT
|
SELECT
|
||||||
*,
|
w.*,
|
||||||
CASE WHEN status = 'active' THEN 1 ELSE 0 END AS is_active
|
CASE WHEN w.status = 'active' THEN 1 ELSE 0 END AS is_active,
|
||||||
FROM workers
|
u.user_id
|
||||||
ORDER BY worker_id DESC
|
FROM workers w
|
||||||
|
LEFT JOIN users u ON w.worker_id = u.worker_id
|
||||||
|
ORDER BY w.worker_id DESC
|
||||||
`);
|
`);
|
||||||
callback(null, rows);
|
callback(null, rows);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -61,10 +62,12 @@ const getById = async (worker_id, callback) => {
|
|||||||
const db = await getDb();
|
const db = await getDb();
|
||||||
const [rows] = await db.query(`
|
const [rows] = await db.query(`
|
||||||
SELECT
|
SELECT
|
||||||
*,
|
w.*,
|
||||||
CASE WHEN status = 'active' THEN 1 ELSE 0 END AS is_active
|
CASE WHEN w.status = 'active' THEN 1 ELSE 0 END AS is_active,
|
||||||
FROM workers
|
u.user_id
|
||||||
WHERE worker_id = ?
|
FROM workers w
|
||||||
|
LEFT JOIN users u ON w.worker_id = u.worker_id
|
||||||
|
WHERE w.worker_id = ?
|
||||||
`, [worker_id]);
|
`, [worker_id]);
|
||||||
callback(null, rows[0]);
|
callback(null, rows[0]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -84,7 +87,6 @@ const update = async (worker, callback) => {
|
|||||||
join_date,
|
join_date,
|
||||||
salary,
|
salary,
|
||||||
annual_leave,
|
annual_leave,
|
||||||
show_in_work_reports,
|
|
||||||
employment_status
|
employment_status
|
||||||
} = worker;
|
} = worker;
|
||||||
|
|
||||||
@@ -116,10 +118,6 @@ const update = async (worker, callback) => {
|
|||||||
updates.push('annual_leave = ?');
|
updates.push('annual_leave = ?');
|
||||||
values.push(annual_leave);
|
values.push(annual_leave);
|
||||||
}
|
}
|
||||||
if (show_in_work_reports !== undefined) {
|
|
||||||
updates.push('show_in_work_reports = ?');
|
|
||||||
values.push(show_in_work_reports);
|
|
||||||
}
|
|
||||||
if (employment_status !== undefined) {
|
if (employment_status !== undefined) {
|
||||||
updates.push('employment_status = ?');
|
updates.push('employment_status = ?');
|
||||||
values.push(employment_status);
|
values.push(employment_status);
|
||||||
|
|||||||
@@ -211,16 +211,14 @@ async function loadWorkers() {
|
|||||||
const allWorkers = Array.isArray(data) ? data : (data.data || data.workers || []);
|
const allWorkers = Array.isArray(data) ? data : (data.data || data.workers || []);
|
||||||
|
|
||||||
// 작업 보고서에 표시할 작업자만 필터링
|
// 작업 보고서에 표시할 작업자만 필터링
|
||||||
// 1. show_in_work_reports가 true
|
// 퇴사자만 제외 (계정 여부와 무관하게 재직자는 모두 표시)
|
||||||
// 2. employment_status가 resigned가 아님
|
|
||||||
workers = allWorkers.filter(worker => {
|
workers = allWorkers.filter(worker => {
|
||||||
const showInReports = worker.show_in_work_reports !== 0 && worker.show_in_work_reports !== false;
|
|
||||||
const notResigned = worker.employment_status !== 'resigned';
|
const notResigned = worker.employment_status !== 'resigned';
|
||||||
return showInReports && notResigned;
|
return notResigned;
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`✅ Workers 로드 성공: ${workers.length}명 (전체: ${allWorkers.length}명)`);
|
console.log(`✅ Workers 로드 성공: ${workers.length}명 (전체: ${allWorkers.length}명)`);
|
||||||
console.log(`📊 필터링 조건: show_in_work_reports=true, employment_status≠resigned`);
|
console.log(`📊 필터링 조건: employment_status≠resigned (퇴사자만 제외)`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('작업자 로딩 오류:', error);
|
console.error('작업자 로딩 오류:', error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ function renderWorkers() {
|
|||||||
const jobType = jobTypeMap[worker.job_type] || jobTypeMap['worker'];
|
const jobType = jobTypeMap[worker.job_type] || jobTypeMap['worker'];
|
||||||
const isInactive = worker.status === 'inactive' || worker.is_active === 0 || worker.is_active === false;
|
const isInactive = worker.status === 'inactive' || worker.is_active === 0 || worker.is_active === false;
|
||||||
const isResigned = worker.employment_status === 'resigned';
|
const isResigned = worker.employment_status === 'resigned';
|
||||||
const showInWorkReports = worker.show_in_work_reports !== 0 && worker.show_in_work_reports !== false;
|
const hasAccount = worker.user_id !== null && worker.user_id !== undefined;
|
||||||
|
|
||||||
console.log('🎨 카드 렌더링:', {
|
console.log('🎨 카드 렌더링:', {
|
||||||
worker_id: worker.worker_id,
|
worker_id: worker.worker_id,
|
||||||
@@ -207,7 +207,8 @@ function renderWorkers() {
|
|||||||
is_active: worker.is_active,
|
is_active: worker.is_active,
|
||||||
isInactive: isInactive,
|
isInactive: isInactive,
|
||||||
isResigned: isResigned,
|
isResigned: isResigned,
|
||||||
showInWorkReports: showInWorkReports
|
user_id: worker.user_id,
|
||||||
|
hasAccount: hasAccount
|
||||||
});
|
});
|
||||||
|
|
||||||
return `
|
return `
|
||||||
@@ -221,17 +222,18 @@ function renderWorkers() {
|
|||||||
</div>
|
</div>
|
||||||
<h3 class="project-name">
|
<h3 class="project-name">
|
||||||
${worker.worker_name}
|
${worker.worker_name}
|
||||||
|
${hasAccount ? '<span style="color: #10b981; font-size: 0.8rem; margin-left: 0.5rem;">🔐</span>' : ''}
|
||||||
${isResigned ? '<span class="inactive-label" style="color: #dc2626;">(퇴사)</span>' :
|
${isResigned ? '<span class="inactive-label" style="color: #dc2626;">(퇴사)</span>' :
|
||||||
isInactive ? '<span class="inactive-label">(사무직)</span>' : ''}
|
isInactive ? '<span class="inactive-label">(사무직)</span>' : ''}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="project-meta">
|
<div class="project-meta">
|
||||||
<span style="color: ${jobType.color}; font-weight: 500;">${jobType.icon} ${jobType.text}</span>
|
<span style="color: ${jobType.color}; font-weight: 500;">${jobType.icon} ${jobType.text}</span>
|
||||||
|
${hasAccount ? '<span style="color: #10b981;">🔐 계정 연동됨</span>' : '<span style="color: #9ca3af;">⚪ 계정 없음</span>'}
|
||||||
${worker.phone_number ? `<span>📞 ${worker.phone_number}</span>` : ''}
|
${worker.phone_number ? `<span>📞 ${worker.phone_number}</span>` : ''}
|
||||||
${worker.email ? `<span>📧 ${worker.email}</span>` : ''}
|
${worker.email ? `<span>📧 ${worker.email}</span>` : ''}
|
||||||
${worker.department ? `<span>🏢 ${worker.department}</span>` : ''}
|
${worker.department ? `<span>🏢 ${worker.department}</span>` : ''}
|
||||||
${worker.hire_date ? `<span>📅 입사: ${formatDate(worker.hire_date)}</span>` : ''}
|
${worker.hire_date ? `<span>📅 입사: ${formatDate(worker.hire_date)}</span>` : ''}
|
||||||
${isResigned ? '<span class="inactive-notice" style="color: #dc2626;">⚠️ 퇴사 처리됨</span>' :
|
${isResigned ? '<span class="inactive-notice" style="color: #dc2626;">⚠️ 퇴사 처리됨</span>' : ''}
|
||||||
!showInWorkReports ? '<span class="inactive-notice">⚠️ 작업보고서에서 숨김</span>' : ''}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="project-actions">
|
<div class="project-actions">
|
||||||
@@ -456,9 +458,9 @@ function openWorkerModal(worker = null) {
|
|||||||
const isActiveValue = worker.status !== 'inactive' && worker.is_active !== 0 && worker.is_active !== false;
|
const isActiveValue = worker.status !== 'inactive' && worker.is_active !== 0 && worker.is_active !== false;
|
||||||
document.getElementById('isActive').checked = isActiveValue;
|
document.getElementById('isActive').checked = isActiveValue;
|
||||||
|
|
||||||
// show_in_work_reports 값 처리
|
// 계정 연동 여부 확인 (user_id가 있으면 계정 있음)
|
||||||
const showInWorkReportsValue = worker.show_in_work_reports !== 0 && worker.show_in_work_reports !== false;
|
const hasAccountValue = worker.user_id !== null && worker.user_id !== undefined;
|
||||||
document.getElementById('showInWorkReports').checked = showInWorkReportsValue;
|
document.getElementById('hasAccount').checked = hasAccountValue;
|
||||||
|
|
||||||
// employment_status 값 처리 (resigned이면 체크)
|
// employment_status 값 처리 (resigned이면 체크)
|
||||||
const isResignedValue = worker.employment_status === 'resigned';
|
const isResignedValue = worker.employment_status === 'resigned';
|
||||||
@@ -471,7 +473,8 @@ function openWorkerModal(worker = null) {
|
|||||||
status: worker.status,
|
status: worker.status,
|
||||||
is_active_raw: worker.is_active,
|
is_active_raw: worker.is_active,
|
||||||
is_active_processed: isActiveValue,
|
is_active_processed: isActiveValue,
|
||||||
show_in_work_reports: showInWorkReportsValue,
|
user_id: worker.user_id,
|
||||||
|
has_account: hasAccountValue,
|
||||||
employment_status: worker.employment_status,
|
employment_status: worker.employment_status,
|
||||||
is_resigned: isResignedValue
|
is_resigned: isResignedValue
|
||||||
});
|
});
|
||||||
@@ -484,7 +487,7 @@ function openWorkerModal(worker = null) {
|
|||||||
document.getElementById('workerForm').reset();
|
document.getElementById('workerForm').reset();
|
||||||
document.getElementById('workerId').value = '';
|
document.getElementById('workerId').value = '';
|
||||||
document.getElementById('isActive').checked = true;
|
document.getElementById('isActive').checked = true;
|
||||||
document.getElementById('showInWorkReports').checked = true;
|
document.getElementById('hasAccount').checked = false;
|
||||||
document.getElementById('isResigned').checked = false;
|
document.getElementById('isResigned').checked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,8 +534,8 @@ async function saveWorker() {
|
|||||||
salary: document.getElementById('salary')?.value || null,
|
salary: document.getElementById('salary')?.value || null,
|
||||||
annual_leave: document.getElementById('annualLeave')?.value || null,
|
annual_leave: document.getElementById('annualLeave')?.value || null,
|
||||||
status: document.getElementById('isActive').checked ? 'active' : 'inactive',
|
status: document.getElementById('isActive').checked ? 'active' : 'inactive',
|
||||||
show_in_work_reports: document.getElementById('showInWorkReports').checked,
|
employment_status: document.getElementById('isResigned').checked ? 'resigned' : 'employed',
|
||||||
employment_status: document.getElementById('isResigned').checked ? 'resigned' : 'employed'
|
create_account: document.getElementById('hasAccount').checked // 계정 생성 여부
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('💾 저장할 작업자 데이터:', JSON.stringify(workerData, null, 2));
|
console.log('💾 저장할 작업자 데이터:', JSON.stringify(workerData, null, 2));
|
||||||
@@ -597,7 +600,6 @@ async function toggleWorkerStatus(workerId) {
|
|||||||
join_date: worker.join_date || null,
|
join_date: worker.join_date || null,
|
||||||
salary: worker.salary || null,
|
salary: worker.salary || null,
|
||||||
annual_leave: worker.annual_leave || null,
|
annual_leave: worker.annual_leave || null,
|
||||||
show_in_work_reports: worker.show_in_work_reports !== 0 && worker.show_in_work_reports !== false,
|
|
||||||
employment_status: worker.employment_status || 'employed'
|
employment_status: worker.employment_status || 'employed'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -176,19 +176,19 @@
|
|||||||
<label class="form-label" style="font-weight: 600; margin-bottom: 0.75rem; display: block;">상태 관리</label>
|
<label class="form-label" style="font-weight: 600; margin-bottom: 0.75rem; display: block;">상태 관리</label>
|
||||||
|
|
||||||
<div style="display: flex; flex-direction: column; gap: 0.75rem;">
|
<div style="display: flex; flex-direction: column; gap: 0.75rem;">
|
||||||
<!-- 작업 보고서 표시 -->
|
<!-- 계정 생성/연동 -->
|
||||||
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
|
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
|
||||||
<input type="checkbox" id="showInWorkReports" checked style="margin: 0; cursor: pointer;">
|
<input type="checkbox" id="hasAccount" style="margin: 0; cursor: pointer;">
|
||||||
<span>작업 보고서에 표시</span>
|
<span>🔐 계정 생성/연동</span>
|
||||||
</label>
|
</label>
|
||||||
<small style="color: #6b7280; font-size: 0.75rem; margin-top: -0.5rem; margin-left: 1.5rem;">
|
<small style="color: #6b7280; font-size: 0.75rem; margin-top: -0.5rem; margin-left: 1.5rem;">
|
||||||
체크 해제 시 일일 작업보고서 작성 시 이 작업자가 목록에 나타나지 않습니다
|
체크 시 로그인 계정이 자동 생성됩니다 (나의 대시보드, 연차/출퇴근 관리 가능)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
<!-- 현장직/사무직 구분 -->
|
<!-- 현장직/사무직 구분 -->
|
||||||
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
|
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
|
||||||
<input type="checkbox" id="isActive" checked style="margin: 0; cursor: pointer;">
|
<input type="checkbox" id="isActive" checked style="margin: 0; cursor: pointer;">
|
||||||
<span>현장직 (활성화)</span>
|
<span>🏭 현장직 (활성화)</span>
|
||||||
</label>
|
</label>
|
||||||
<small style="color: #6b7280; font-size: 0.75rem; margin-top: -0.5rem; margin-left: 1.5rem;">
|
<small style="color: #6b7280; font-size: 0.75rem; margin-top: -0.5rem; margin-left: 1.5rem;">
|
||||||
체크: 현장직 (출퇴근 관리 필요) / 체크 해제: 사무직 (출퇴근 관리 불필요)
|
체크: 현장직 (출퇴근 관리 필요) / 체크 해제: 사무직 (출퇴근 관리 불필요)
|
||||||
@@ -197,10 +197,10 @@
|
|||||||
<!-- 퇴사 처리 -->
|
<!-- 퇴사 처리 -->
|
||||||
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
|
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
|
||||||
<input type="checkbox" id="isResigned" style="margin: 0; cursor: pointer;">
|
<input type="checkbox" id="isResigned" style="margin: 0; cursor: pointer;">
|
||||||
<span style="color: #ef4444;">퇴사 처리</span>
|
<span style="color: #ef4444;">🚪 퇴사 처리</span>
|
||||||
</label>
|
</label>
|
||||||
<small style="color: #ef4444; font-size: 0.75rem; margin-top: -0.5rem; margin-left: 1.5rem;">
|
<small style="color: #ef4444; font-size: 0.75rem; margin-top: -0.5rem; margin-left: 1.5rem;">
|
||||||
퇴사한 작업자로 표시됩니다. 작업자 목록에서 별도로 표시됩니다
|
퇴사한 작업자로 표시됩니다. 작업 보고서에서 제외됩니다
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user