feat: 구매/안전 시스템 전면 개편 — tkpurchase 개편 + tksafety 신규 + 권한 보강
Phase 1: tkuser 협력업체 CRUD 이관 (읽기전용 → 전체 CRUD) Phase 2: tkpurchase 개편 — 일용공 신청/확정, 작업일정, 업무현황, 계정관리, 협력업체 포털 Phase 3: tksafety 신규 시스템 — 방문관리 + 안전교육 신고 Phase 4: SSO 인증 보강 (partner_company_id JWT, 만료일 체크), 권한 테이블 기반 접근 제어 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,47 @@ async function getById(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
async function create(req, res) {
|
||||
try {
|
||||
const { company_name } = req.body;
|
||||
if (!company_name || !company_name.trim()) {
|
||||
return res.status(400).json({ success: false, error: '업체명은 필수입니다' });
|
||||
}
|
||||
const company = await partnerModel.create(req.body);
|
||||
res.status(201).json({ success: true, data: company });
|
||||
} catch (err) {
|
||||
if (err.code === 'ER_DUP_ENTRY') {
|
||||
return res.status(400).json({ success: false, error: '이미 등록된 사업자번호입니다' });
|
||||
}
|
||||
console.error('Partner create error:', err);
|
||||
res.status(500).json({ success: false, error: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
async function update(req, res) {
|
||||
try {
|
||||
const company = await partnerModel.update(req.params.id, req.body);
|
||||
if (!company) return res.status(404).json({ success: false, error: '업체를 찾을 수 없습니다' });
|
||||
res.json({ success: true, data: company });
|
||||
} catch (err) {
|
||||
if (err.code === 'ER_DUP_ENTRY') {
|
||||
return res.status(400).json({ success: false, error: '이미 등록된 사업자번호입니다' });
|
||||
}
|
||||
console.error('Partner update error:', err);
|
||||
res.status(500).json({ success: false, error: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
async function deactivate(req, res) {
|
||||
try {
|
||||
await partnerModel.deactivate(req.params.id);
|
||||
res.json({ success: true, message: '비활성화 완료' });
|
||||
} catch (err) {
|
||||
console.error('Partner deactivate error:', err);
|
||||
res.status(500).json({ success: false, error: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
async function listWorkers(req, res) {
|
||||
try {
|
||||
const rows = await partnerModel.findWorkersByCompany(req.params.id);
|
||||
@@ -36,4 +77,49 @@ async function listWorkers(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { list, getById, listWorkers };
|
||||
async function createWorker(req, res) {
|
||||
try {
|
||||
const { worker_name, is_team_leader, phone } = req.body;
|
||||
if (!worker_name || !worker_name.trim()) {
|
||||
return res.status(400).json({ success: false, error: '작업자명은 필수입니다' });
|
||||
}
|
||||
if (is_team_leader && (!phone || !phone.trim())) {
|
||||
return res.status(400).json({ success: false, error: '팀장급은 연락처 필수입니다' });
|
||||
}
|
||||
const worker = await partnerModel.createWorker(req.params.id, req.body);
|
||||
res.status(201).json({ success: true, data: worker });
|
||||
} catch (err) {
|
||||
console.error('Worker create error:', err);
|
||||
res.status(500).json({ success: false, error: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
async function updateWorker(req, res) {
|
||||
try {
|
||||
const { is_team_leader, phone } = req.body;
|
||||
if (is_team_leader && (!phone || !phone.trim())) {
|
||||
return res.status(400).json({ success: false, error: '팀장급은 연락처 필수입니다' });
|
||||
}
|
||||
const worker = await partnerModel.updateWorker(req.params.id, req.body);
|
||||
if (!worker) return res.status(404).json({ success: false, error: '작업자를 찾을 수 없습니다' });
|
||||
res.json({ success: true, data: worker });
|
||||
} catch (err) {
|
||||
console.error('Worker update error:', err);
|
||||
res.status(500).json({ success: false, error: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
async function deactivateWorker(req, res) {
|
||||
try {
|
||||
await partnerModel.deactivateWorker(req.params.id);
|
||||
res.json({ success: true, message: '비활성화 완료' });
|
||||
} catch (err) {
|
||||
console.error('Worker deactivate error:', err);
|
||||
res.status(500).json({ success: false, error: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
list, getById, create, update, deactivate,
|
||||
listWorkers, createWorker, updateWorker, deactivateWorker
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
const { getPool } = require('./userModel');
|
||||
|
||||
// ===== 협력업체 =====
|
||||
|
||||
async function findAll({ search, is_active } = {}) {
|
||||
const db = getPool();
|
||||
let sql = 'SELECT * FROM partner_companies WHERE 1=1';
|
||||
@@ -17,6 +19,47 @@ async function findById(id) {
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
async function create(data) {
|
||||
const db = getPool();
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO partner_companies (company_name, business_number, representative, contact_name, contact_phone, address, business_type, insurance_number, insurance_expiry, notes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[data.company_name, data.business_number || null, data.representative || null,
|
||||
data.contact_name || null, data.contact_phone || null, data.address || null,
|
||||
data.business_type ? JSON.stringify(data.business_type) : null,
|
||||
data.insurance_number || null, data.insurance_expiry || null, data.notes || null]
|
||||
);
|
||||
return findById(result.insertId);
|
||||
}
|
||||
|
||||
async function update(id, data) {
|
||||
const db = getPool();
|
||||
const fields = [];
|
||||
const values = [];
|
||||
if (data.company_name !== undefined) { fields.push('company_name = ?'); values.push(data.company_name); }
|
||||
if (data.business_number !== undefined) { fields.push('business_number = ?'); values.push(data.business_number || null); }
|
||||
if (data.representative !== undefined) { fields.push('representative = ?'); values.push(data.representative || null); }
|
||||
if (data.contact_name !== undefined) { fields.push('contact_name = ?'); values.push(data.contact_name || null); }
|
||||
if (data.contact_phone !== undefined) { fields.push('contact_phone = ?'); values.push(data.contact_phone || null); }
|
||||
if (data.address !== undefined) { fields.push('address = ?'); values.push(data.address || null); }
|
||||
if (data.business_type !== undefined) { fields.push('business_type = ?'); values.push(data.business_type ? JSON.stringify(data.business_type) : null); }
|
||||
if (data.insurance_number !== undefined) { fields.push('insurance_number = ?'); values.push(data.insurance_number || null); }
|
||||
if (data.insurance_expiry !== undefined) { fields.push('insurance_expiry = ?'); values.push(data.insurance_expiry || null); }
|
||||
if (data.notes !== undefined) { fields.push('notes = ?'); values.push(data.notes || null); }
|
||||
if (data.is_active !== undefined) { fields.push('is_active = ?'); values.push(data.is_active); }
|
||||
if (fields.length === 0) return findById(id);
|
||||
values.push(id);
|
||||
await db.query(`UPDATE partner_companies SET ${fields.join(', ')} WHERE id = ?`, values);
|
||||
return findById(id);
|
||||
}
|
||||
|
||||
async function deactivate(id) {
|
||||
const db = getPool();
|
||||
await db.query('UPDATE partner_companies SET is_active = FALSE WHERE id = ?', [id]);
|
||||
}
|
||||
|
||||
// ===== 작업자 =====
|
||||
|
||||
async function findWorkersByCompany(companyId) {
|
||||
const db = getPool();
|
||||
const [rows] = await db.query(
|
||||
@@ -26,4 +69,47 @@ async function findWorkersByCompany(companyId) {
|
||||
return rows;
|
||||
}
|
||||
|
||||
module.exports = { findAll, findById, findWorkersByCompany };
|
||||
async function findWorkerById(id) {
|
||||
const db = getPool();
|
||||
const [rows] = await db.query('SELECT * FROM partner_workers WHERE id = ?', [id]);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
async function createWorker(companyId, data) {
|
||||
const db = getPool();
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO partner_workers (company_id, worker_name, position, is_team_leader, phone, safety_training_date, notes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
[companyId, data.worker_name, data.position || null,
|
||||
data.is_team_leader || false, data.phone || null,
|
||||
data.safety_training_date || null, data.notes || null]
|
||||
);
|
||||
return findWorkerById(result.insertId);
|
||||
}
|
||||
|
||||
async function updateWorker(id, data) {
|
||||
const db = getPool();
|
||||
const fields = [];
|
||||
const values = [];
|
||||
if (data.worker_name !== undefined) { fields.push('worker_name = ?'); values.push(data.worker_name); }
|
||||
if (data.position !== undefined) { fields.push('position = ?'); values.push(data.position || null); }
|
||||
if (data.is_team_leader !== undefined) { fields.push('is_team_leader = ?'); values.push(data.is_team_leader); }
|
||||
if (data.phone !== undefined) { fields.push('phone = ?'); values.push(data.phone || null); }
|
||||
if (data.safety_training_date !== undefined) { fields.push('safety_training_date = ?'); values.push(data.safety_training_date || null); }
|
||||
if (data.notes !== undefined) { fields.push('notes = ?'); values.push(data.notes || null); }
|
||||
if (data.is_active !== undefined) { fields.push('is_active = ?'); values.push(data.is_active); }
|
||||
if (fields.length === 0) return findWorkerById(id);
|
||||
values.push(id);
|
||||
await db.query(`UPDATE partner_workers SET ${fields.join(', ')} WHERE id = ?`, values);
|
||||
return findWorkerById(id);
|
||||
}
|
||||
|
||||
async function deactivateWorker(id) {
|
||||
const db = getPool();
|
||||
await db.query('UPDATE partner_workers SET is_active = FALSE WHERE id = ?', [id]);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
findAll, findById, create, update, deactivate,
|
||||
findWorkersByCompany, findWorkerById, createWorker, updateWorker, deactivateWorker
|
||||
};
|
||||
|
||||
@@ -55,8 +55,16 @@ const DEFAULT_PAGES = {
|
||||
'ai_assistant': { title: 'AI 어시스턴트', system: 'system3', group: 'AI', default_access: false },
|
||||
|
||||
// ===== tkpurchase - 구매 관리 =====
|
||||
'purchasing_visit': { title: '방문 관리', system: 'tkpurchase', group: '구매 관리', default_access: false },
|
||||
'purchasing_partner': { title: '협력업체 관리', system: 'tkpurchase', group: '구매 관리', default_access: false },
|
||||
'purchasing_daylabor': { title: '일용공 관리', system: 'tkpurchase', group: '구매 관리', default_access: false },
|
||||
'purchasing_schedule': { title: '작업일정 관리', system: 'tkpurchase', group: '구매 관리', default_access: false },
|
||||
'purchasing_workreport': { title: '업무현황 관리', system: 'tkpurchase', group: '구매 관리', default_access: false },
|
||||
'purchasing_accounts': { title: '협력업체 계정', system: 'tkpurchase', group: '구매 관리', default_access: false },
|
||||
'purchasing_partner_portal': { title: '협력업체 포털', system: 'tkpurchase', group: '협력업체', default_access: false },
|
||||
'purchasing_partner_checkin': { title: '협력업체 체크인', system: 'tkpurchase', group: '협력업체', default_access: false },
|
||||
|
||||
// ===== tksafety - 안전 관리 =====
|
||||
'safety_visit': { title: '방문 관리', system: 'tksafety', group: '안전 관리', default_access: false },
|
||||
'safety_education': { title: '안전교육 관리', system: 'tksafety', group: '안전 관리', default_access: false },
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -97,7 +97,7 @@ async function findById(userId) {
|
||||
async function findAll() {
|
||||
const db = getPool();
|
||||
const [rows] = await db.query(
|
||||
'SELECT user_id, username, name, department, department_id, role, system1_access, system2_access, system3_access, is_active, last_login, created_at FROM sso_users ORDER BY user_id'
|
||||
'SELECT user_id, username, name, department, department_id, role, system1_access, system2_access, system3_access, is_active, last_login, created_at FROM sso_users WHERE partner_company_id IS NULL ORDER BY user_id'
|
||||
);
|
||||
return rows;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { requireAuth } = require('../middleware/auth');
|
||||
const { requireAuth, requireAdmin } = require('../middleware/auth');
|
||||
const ctrl = require('../controllers/partnerController');
|
||||
|
||||
router.use(requireAuth);
|
||||
|
||||
router.get('/', ctrl.list);
|
||||
router.get('/:id', ctrl.getById);
|
||||
router.post('/', requireAdmin, ctrl.create);
|
||||
router.put('/:id', requireAdmin, ctrl.update);
|
||||
router.delete('/:id', requireAdmin, ctrl.deactivate);
|
||||
|
||||
router.get('/:id/workers', ctrl.listWorkers);
|
||||
router.post('/:id/workers', requireAdmin, ctrl.createWorker);
|
||||
router.put('/workers/:id', requireAdmin, ctrl.updateWorker);
|
||||
router.delete('/workers/:id', requireAdmin, ctrl.deactivateWorker);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user