feat(tkuser): 협력업체 완전삭제 기능 추가 (admin 전용)
- 관련 데이터 cascade 삭제 (workers, schedules, checkins, reports, SSO 계정 등)
- 구매 이력 있는 업체는 삭제 차단
- 프론트엔드: 목록/상세에 완전삭제 버튼 + prompt("삭제") 안전장치
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -119,7 +119,32 @@ async function deactivateWorker(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getDeleteInfo(req, res) {
|
||||
try {
|
||||
const company = await partnerModel.findById(req.params.id);
|
||||
if (!company) return res.status(404).json({ success: false, error: '업체를 찾을 수 없습니다' });
|
||||
const info = await partnerModel.getDeleteInfo(req.params.id);
|
||||
res.json({ success: true, data: { company_name: company.company_name, ...info } });
|
||||
} catch (err) {
|
||||
console.error('Partner getDeleteInfo error:', err);
|
||||
res.status(500).json({ success: false, error: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
async function permanentDelete(req, res) {
|
||||
try {
|
||||
const company = await partnerModel.findById(req.params.id);
|
||||
if (!company) return res.status(404).json({ success: false, error: '업체를 찾을 수 없습니다' });
|
||||
await partnerModel.permanentDelete(req.params.id, req.user.id);
|
||||
res.json({ success: true, message: `"${company.company_name}" 업체가 완전히 삭제되었습니다` });
|
||||
} catch (err) {
|
||||
console.error('Partner permanentDelete error:', err);
|
||||
res.status(500).json({ success: false, error: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
list, getById, create, update, deactivate,
|
||||
listWorkers, createWorker, updateWorker, deactivateWorker
|
||||
listWorkers, createWorker, updateWorker, deactivateWorker,
|
||||
getDeleteInfo, permanentDelete
|
||||
};
|
||||
|
||||
@@ -109,7 +109,108 @@ async function deactivateWorker(id) {
|
||||
await db.query('UPDATE partner_workers SET is_active = FALSE WHERE id = ?', [id]);
|
||||
}
|
||||
|
||||
async function getDeleteInfo(companyId) {
|
||||
const db = getPool();
|
||||
const [[workers]] = await db.query('SELECT COUNT(*) as cnt FROM partner_workers WHERE company_id = ?', [companyId]);
|
||||
const [[schedules]] = await db.query('SELECT COUNT(*) as cnt FROM partner_schedules WHERE company_id = ?', [companyId]);
|
||||
const [[checkins]] = await db.query('SELECT COUNT(*) as cnt FROM partner_work_checkins WHERE company_id = ?', [companyId]);
|
||||
const [[reports]] = await db.query('SELECT COUNT(*) as cnt FROM partner_work_reports WHERE company_id = ?', [companyId]);
|
||||
const [[visits]] = await db.query('SELECT COUNT(*) as cnt FROM daily_visits WHERE company_id = ?', [companyId]);
|
||||
const [[accounts]] = await db.query('SELECT COUNT(*) as cnt FROM sso_users WHERE partner_company_id = ?', [companyId]);
|
||||
|
||||
// 삭제 차단 조건: 해당 업체 SSO 계정이 구매 이력을 가지고 있는지
|
||||
const [userRows] = await db.query('SELECT id FROM sso_users WHERE partner_company_id = ?', [companyId]);
|
||||
const userIds = userRows.map(r => r.id);
|
||||
let purchaseRequests = 0;
|
||||
let purchases = 0;
|
||||
if (userIds.length > 0) {
|
||||
const [[pr]] = await db.query('SELECT COUNT(*) as cnt FROM purchase_requests WHERE requester_id IN (?)', [userIds]);
|
||||
const [[pu]] = await db.query('SELECT COUNT(*) as cnt FROM purchases WHERE purchaser_id IN (?)', [userIds]);
|
||||
purchaseRequests = pr.cnt;
|
||||
purchases = pu.cnt;
|
||||
}
|
||||
|
||||
return {
|
||||
workers: workers.cnt,
|
||||
schedules: schedules.cnt,
|
||||
checkins: checkins.cnt,
|
||||
reports: reports.cnt,
|
||||
visits: visits.cnt,
|
||||
accounts: accounts.cnt,
|
||||
purchaseRequests,
|
||||
purchases
|
||||
};
|
||||
}
|
||||
|
||||
async function permanentDelete(companyId, requestUserId) {
|
||||
const db = getPool();
|
||||
|
||||
// 사전 체크: 구매 이력 확인
|
||||
const [userRows] = await db.query('SELECT id FROM sso_users WHERE partner_company_id = ?', [companyId]);
|
||||
const userIds = userRows.map(r => r.id);
|
||||
if (userIds.length > 0) {
|
||||
const [[pr]] = await db.query('SELECT COUNT(*) as cnt FROM purchase_requests WHERE requester_id IN (?)', [userIds]);
|
||||
const [[pu]] = await db.query('SELECT COUNT(*) as cnt FROM purchases WHERE purchaser_id IN (?)', [userIds]);
|
||||
if (pr.cnt > 0 || pu.cnt > 0) {
|
||||
throw new Error('구매 이력이 있어 삭제할 수 없습니다. 비활성화를 이용해주세요.');
|
||||
}
|
||||
}
|
||||
|
||||
// 작업자 ID 조회
|
||||
const [workerRows] = await db.query('SELECT id FROM partner_workers WHERE company_id = ?', [companyId]);
|
||||
const workerIds = workerRows.map(r => r.id);
|
||||
|
||||
const conn = await db.getConnection();
|
||||
try {
|
||||
await conn.beginTransaction();
|
||||
|
||||
// 1. work_report_workers에서 해당 업체 작업자 참조 SET NULL
|
||||
if (workerIds.length > 0) {
|
||||
await conn.query('UPDATE work_report_workers SET partner_worker_id = NULL WHERE partner_worker_id IN (?)', [workerIds]);
|
||||
}
|
||||
|
||||
// 2. partner_work_reports 삭제 (→ work_report_workers CASCADE 자동)
|
||||
await conn.query('DELETE FROM partner_work_reports WHERE company_id = ?', [companyId]);
|
||||
|
||||
// 3. partner_work_checkins 삭제
|
||||
await conn.query('DELETE FROM partner_work_checkins WHERE company_id = ?', [companyId]);
|
||||
|
||||
// 4. partner_schedules 삭제
|
||||
await conn.query('DELETE FROM partner_schedules WHERE company_id = ?', [companyId]);
|
||||
|
||||
// 5. daily_visit_workers에서 해당 업체 작업자 참조 SET NULL
|
||||
if (workerIds.length > 0) {
|
||||
await conn.query('UPDATE daily_visit_workers SET partner_worker_id = NULL WHERE partner_worker_id IN (?)', [workerIds]);
|
||||
}
|
||||
|
||||
// 6. daily_visits는 SET NULL 자동 (방문기록 보존)
|
||||
|
||||
// 7. partner_workers 삭제
|
||||
await conn.query('DELETE FROM partner_workers WHERE company_id = ?', [companyId]);
|
||||
|
||||
// 8. 협력업체 SSO 계정 처리
|
||||
if (userIds.length > 0) {
|
||||
await conn.query('UPDATE sp_vacation_requests SET reviewed_by = NULL WHERE reviewed_by IN (?)', [userIds]);
|
||||
await conn.query('DELETE FROM sp_vacation_requests WHERE user_id IN (?)', [userIds]);
|
||||
await conn.query('UPDATE sp_vacation_balances SET created_by = ? WHERE created_by IN (?)', [requestUserId, userIds]);
|
||||
await conn.query('DELETE FROM sp_vacation_balances WHERE user_id IN (?)', [userIds]);
|
||||
await conn.query('DELETE FROM sso_users WHERE partner_company_id = ?', [companyId]);
|
||||
}
|
||||
|
||||
// 9. partner_companies 삭제
|
||||
await conn.query('DELETE FROM partner_companies WHERE id = ?', [companyId]);
|
||||
|
||||
await conn.commit();
|
||||
} catch (err) {
|
||||
await conn.rollback();
|
||||
throw err;
|
||||
} finally {
|
||||
conn.release();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
findAll, findById, create, update, deactivate,
|
||||
findWorkersByCompany, findWorkerById, createWorker, updateWorker, deactivateWorker
|
||||
findWorkersByCompany, findWorkerById, createWorker, updateWorker, deactivateWorker,
|
||||
getDeleteInfo, permanentDelete
|
||||
};
|
||||
|
||||
@@ -6,6 +6,8 @@ const ctrl = require('../controllers/partnerController');
|
||||
router.use(requireAuth);
|
||||
|
||||
router.get('/', ctrl.list);
|
||||
router.get('/:id/delete-info', requireAdmin, ctrl.getDeleteInfo);
|
||||
router.delete('/:id/permanent', requireAdmin, ctrl.permanentDelete);
|
||||
router.get('/:id', ctrl.getById);
|
||||
router.post('/', requireAdmin, ctrl.create);
|
||||
router.put('/:id', requireAdmin, ctrl.update);
|
||||
|
||||
Reference in New Issue
Block a user