fix(tkpurchase): 협력업체 포탈 활성 일정 전체 표시로 변경
오늘 날짜 범위 필터 제거 → 마감/취소되지 않은 모든 일정 표시. 체크인 날짜 제한도 상태 기반 검증으로 변경하여 일정 기간 외에도 체크인 가능. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,7 @@ async function myCheckins(req, res) {
|
|||||||
if (!companyId) {
|
if (!companyId) {
|
||||||
return res.status(403).json({ success: false, error: '협력업체 계정이 아닙니다' });
|
return res.status(403).json({ success: false, error: '협력업체 계정이 아닙니다' });
|
||||||
}
|
}
|
||||||
const rows = await checkinModel.findTodayByCompany(companyId);
|
const rows = await checkinModel.findActiveByCompany(companyId);
|
||||||
res.json({ success: true, data: rows });
|
res.json({ success: true, data: rows });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Checkin myCheckins error:', err);
|
console.error('Checkin myCheckins error:', err);
|
||||||
@@ -39,19 +39,13 @@ async function checkIn(req, res) {
|
|||||||
if (!resolvedCompanyId) {
|
if (!resolvedCompanyId) {
|
||||||
return res.status(400).json({ success: false, error: '업체 정보가 필요합니다' });
|
return res.status(400).json({ success: false, error: '업체 정보가 필요합니다' });
|
||||||
}
|
}
|
||||||
// 일정 기간 내 체크인 검증
|
// 일정 유효성 검증
|
||||||
const schedule = await scheduleModel.findById(schedule_id);
|
const schedule = await scheduleModel.findById(schedule_id);
|
||||||
if (!schedule) {
|
if (!schedule) {
|
||||||
return res.status(404).json({ success: false, error: '일정을 찾을 수 없습니다' });
|
return res.status(404).json({ success: false, error: '일정을 찾을 수 없습니다' });
|
||||||
}
|
}
|
||||||
const today = new Date();
|
if (['cancelled', 'rejected', 'completed'].includes(schedule.status)) {
|
||||||
today.setHours(0, 0, 0, 0);
|
return res.status(400).json({ success: false, error: '마감되었거나 취소된 일정입니다' });
|
||||||
const startDate = new Date(schedule.start_date);
|
|
||||||
startDate.setHours(0, 0, 0, 0);
|
|
||||||
const endDate = new Date(schedule.end_date);
|
|
||||||
endDate.setHours(0, 0, 0, 0);
|
|
||||||
if (today < startDate || today > endDate) {
|
|
||||||
return res.status(400).json({ success: false, error: '오늘은 해당 일정의 작업 기간이 아닙니다' });
|
|
||||||
}
|
}
|
||||||
const data = {
|
const data = {
|
||||||
schedule_id,
|
schedule_id,
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ async function mySchedules(req, res) {
|
|||||||
return res.status(403).json({ success: false, error: '협력업체 계정이 아닙니다' });
|
return res.status(403).json({ success: false, error: '협력업체 계정이 아닙니다' });
|
||||||
}
|
}
|
||||||
const [schedules, requests] = await Promise.all([
|
const [schedules, requests] = await Promise.all([
|
||||||
scheduleModel.findByCompanyToday(companyId),
|
scheduleModel.findActiveByCompany(companyId),
|
||||||
scheduleModel.findRequestsByCompany(companyId)
|
scheduleModel.findRequestsByCompany(companyId)
|
||||||
]);
|
]);
|
||||||
res.json({ success: true, data: { schedules, requests } });
|
res.json({ success: true, data: { schedules, requests } });
|
||||||
|
|||||||
@@ -216,4 +216,17 @@ async function findHistoryByCompany(companyId, { dateFrom, dateTo, page = 1, lim
|
|||||||
return { data: checkins, total, page, limit };
|
return { data: checkins, total, page, limit };
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { findBySchedule, findById, findTodayByCompany, checkIn, checkOut, update, resetCheckout, countActive, deleteCheckin, checkOutWithReport, findHistoryByCompany };
|
async function findActiveByCompany(companyId) {
|
||||||
|
const db = getPool();
|
||||||
|
const [rows] = await db.query(
|
||||||
|
`SELECT pc.*, ps.work_description, ps.workplace_name,
|
||||||
|
(SELECT COUNT(*) FROM partner_work_reports WHERE checkin_id = pc.id) AS work_report_count
|
||||||
|
FROM partner_work_checkins pc
|
||||||
|
LEFT JOIN partner_schedules ps ON pc.schedule_id = ps.id
|
||||||
|
WHERE pc.company_id = ?
|
||||||
|
AND (pc.check_out_time IS NULL OR DATE(pc.check_in_time) = CURDATE())
|
||||||
|
ORDER BY pc.check_in_time DESC`, [companyId]);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { findBySchedule, findById, findTodayByCompany, findActiveByCompany, checkIn, checkOut, update, resetCheckout, countActive, deleteCheckin, checkOutWithReport, findHistoryByCompany };
|
||||||
|
|||||||
@@ -117,4 +117,17 @@ async function deleteSchedule(id) {
|
|||||||
await db.query('DELETE FROM partner_schedules WHERE id = ?', [id]);
|
await db.query('DELETE FROM partner_schedules WHERE id = ?', [id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { findAll, findById, findByCompanyToday, findRequestsByCompany, findByProject, create, update, updateStatus, deleteSchedule };
|
async function findActiveByCompany(companyId) {
|
||||||
|
const db = getPool();
|
||||||
|
const [rows] = await db.query(
|
||||||
|
`SELECT ps.*, pc.company_name, p.project_name, p.job_no
|
||||||
|
FROM partner_schedules ps
|
||||||
|
LEFT JOIN partner_companies pc ON ps.company_id = pc.id
|
||||||
|
LEFT JOIN projects p ON ps.project_id = p.project_id
|
||||||
|
WHERE ps.company_id = ?
|
||||||
|
AND ps.status NOT IN ('cancelled','rejected','requested','completed')
|
||||||
|
ORDER BY ps.start_date ASC, ps.created_at DESC`, [companyId]);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { findAll, findById, findByCompanyToday, findActiveByCompany, findRequestsByCompany, findByProject, create, update, updateStatus, deleteSchedule };
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<h2 class="text-lg font-semibold text-emerald-800" id="welcomeCompanyName">-</h2>
|
<h2 class="text-lg font-semibold text-emerald-800" id="welcomeCompanyName">-</h2>
|
||||||
<p class="text-sm text-emerald-600">오늘의 작업 일정을 확인하세요.</p>
|
<p class="text-sm text-emerald-600">작업 일정을 확인하세요.</p>
|
||||||
</div>
|
</div>
|
||||||
<a href="/partner-history.html" class="flex items-center gap-1 px-3 py-2 bg-white text-emerald-700 rounded-lg text-sm hover:bg-emerald-100 border border-emerald-200 flex-shrink-0">
|
<a href="/partner-history.html" class="flex items-center gap-1 px-3 py-2 bg-white text-emerald-700 rounded-lg text-sm hover:bg-emerald-100 border border-emerald-200 flex-shrink-0">
|
||||||
<i class="fas fa-history"></i><span class="hidden sm:inline">작업 이력</span>
|
<i class="fas fa-history"></i><span class="hidden sm:inline">작업 이력</span>
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/js/tkpurchase-core.js?v=20260313b"></script>
|
<script src="/static/js/tkpurchase-core.js?v=20260313b"></script>
|
||||||
<script src="/static/js/tkpurchase-partner-portal.js?v=20260313a"></script>
|
<script src="/static/js/tkpurchase-partner-portal.js?v=20260313b"></script>
|
||||||
<script>initPartnerPortal();</script>
|
<script>initPartnerPortal();</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ async function loadMyCheckins() {
|
|||||||
const list = r.data || [];
|
const list = r.data || [];
|
||||||
portalCheckins = {};
|
portalCheckins = {};
|
||||||
list.forEach(c => {
|
list.forEach(c => {
|
||||||
if (c.schedule_id) portalCheckins[c.schedule_id] = c;
|
if (c.schedule_id && !portalCheckins[c.schedule_id]) portalCheckins[c.schedule_id] = c;
|
||||||
});
|
});
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.warn('Load checkins error:', e);
|
console.warn('Load checkins error:', e);
|
||||||
@@ -91,7 +91,7 @@ async function renderScheduleCards() {
|
|||||||
} else {
|
} else {
|
||||||
requestCardsEl.classList.add('hidden');
|
requestCardsEl.classList.add('hidden');
|
||||||
workRequestFormEl.classList.remove('hidden');
|
workRequestFormEl.classList.remove('hidden');
|
||||||
workRequestFormEl.querySelector('p').textContent = '오늘 예정된 작업 일정이 없습니다. 작업이 필요하시면 아래에서 신청해주세요.';
|
workRequestFormEl.querySelector('p').textContent = '등록된 작업 일정이 없습니다. 작업이 필요하시면 아래에서 신청해주세요.';
|
||||||
}
|
}
|
||||||
// 기본 날짜 설정
|
// 기본 날짜 설정
|
||||||
const today = new Date().toISOString().substring(0, 10);
|
const today = new Date().toISOString().substring(0, 10);
|
||||||
@@ -104,22 +104,32 @@ async function renderScheduleCards() {
|
|||||||
requestCardsEl.classList.add('hidden');
|
requestCardsEl.classList.add('hidden');
|
||||||
workRequestFormEl.classList.add('hidden');
|
workRequestFormEl.classList.add('hidden');
|
||||||
|
|
||||||
|
const today = new Date().toISOString().substring(0, 10);
|
||||||
|
|
||||||
container.innerHTML = portalSchedules.map(s => {
|
container.innerHTML = portalSchedules.map(s => {
|
||||||
const checkin = portalCheckins[s.id];
|
const checkin = portalCheckins[s.id];
|
||||||
const isCheckedIn = checkin && !checkin.check_out_time;
|
const isCheckedIn = checkin && !checkin.check_out_time;
|
||||||
const isCheckedOut = checkin && checkin.check_out_time;
|
const isCheckedOut = checkin && checkin.check_out_time;
|
||||||
|
const sStart = s.start_date ? s.start_date.substring(0, 10) : '';
|
||||||
|
const sEnd = s.end_date ? s.end_date.substring(0, 10) : '';
|
||||||
|
const isToday = sStart <= today && sEnd >= today;
|
||||||
|
|
||||||
// 2-step indicators
|
// 2-step indicators
|
||||||
const step1Class = checkin ? 'text-emerald-600' : 'text-gray-400';
|
const step1Class = checkin ? 'text-emerald-600' : 'text-gray-400';
|
||||||
const step2Class = isCheckedOut ? 'text-emerald-600' : 'text-gray-400';
|
const step2Class = isCheckedOut ? 'text-emerald-600' : 'text-gray-400';
|
||||||
|
|
||||||
return `<div class="bg-white rounded-xl shadow-sm overflow-hidden">
|
const dateBadge = isToday
|
||||||
|
? `<span class="text-xs text-white bg-emerald-500 px-2 py-0.5 rounded-full font-medium">오늘</span>`
|
||||||
|
: `<span class="text-xs text-gray-500">${formatDate(s.start_date) === formatDate(s.end_date) ? formatDate(s.start_date) : formatDate(s.start_date) + ' ~ ' + formatDate(s.end_date)}</span>`;
|
||||||
|
|
||||||
|
return `<div class="bg-white rounded-xl shadow-sm overflow-hidden ${!isToday ? 'border border-gray-200' : ''}">
|
||||||
<!-- 일정 정보 -->
|
<!-- 일정 정보 -->
|
||||||
<div class="p-5 border-b">
|
<div class="p-5 border-b">
|
||||||
<div class="flex items-center justify-between mb-2">
|
<div class="flex items-center justify-between mb-2">
|
||||||
<h3 class="text-base font-semibold text-gray-800">${escapeHtml(s.workplace_name || '작업장 미지정')}</h3>
|
<h3 class="text-base font-semibold text-gray-800">${escapeHtml(s.workplace_name || '작업장 미지정')}</h3>
|
||||||
<span class="text-xs text-gray-500">${formatDate(s.start_date) === formatDate(s.end_date) ? formatDate(s.start_date) : formatDate(s.start_date) + ' ~ ' + formatDate(s.end_date)}</span>
|
${dateBadge}
|
||||||
</div>
|
</div>
|
||||||
|
${!isToday ? `<div class="text-xs text-gray-400 mb-1"><i class="fas fa-calendar-alt mr-1"></i>${formatDate(s.start_date)}${formatDate(s.start_date) !== formatDate(s.end_date) ? ' ~ ' + formatDate(s.end_date) : ''}</div>` : ''}
|
||||||
${s.work_description ? `<p class="text-sm text-gray-600 mb-2">${escapeHtml(s.work_description)}</p>` : ''}
|
${s.work_description ? `<p class="text-sm text-gray-600 mb-2">${escapeHtml(s.work_description)}</p>` : ''}
|
||||||
<div class="flex gap-4 text-xs text-gray-500">
|
<div class="flex gap-4 text-xs text-gray-500">
|
||||||
<span><i class="fas fa-users mr-1"></i>예상 ${s.expected_workers || 0}명</span>
|
<span><i class="fas fa-users mr-1"></i>예상 ${s.expected_workers || 0}명</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user