ТОиР ГРУЗОВЫХ ПОДЪЕМНИКОВ

Диагностика, ежедневный чек-лист и плановое ТО: месячное, квартальное, полугодовое и годовое.

Что запрещено делать

    Текущие дата и время
    --:--:--
    --

    Как работать

    1. Откройте нужный раздел: «Диагностика», «Ежедневный осмотр» или «Плановое ТО».
    2. Заполните обязательные поля в верхней части формы.
    3. Отметьте результаты проверки или выберите нужную запись.
    4. Подтвердите форму, затем при необходимости распечатайте или выгрузите её в Excel/PDF.

    Статус работ по графику

    На сегодня

    Проверка статуса...

    На текущий месяц

    Проверка статуса...
    Текущие дата и время
    --:--:--
    --

    Инструкция по разделу «Диагностика»

    1. Заполните дату, ФИО, наименование объекта, адрес объекта и номер ГП.
    2. Последовательно выберите группу неисправности, симптом и причину.
    3. После выбора причины проверьте предложенный способ устранения и послеремонтную проверку.
    4. При необходимости распечатайте готовую запись диагностики.

    Диагностика неисправности

    Заполняйте поля сверху вниз. Это упрощает подбор причины и делает итоговую запись более понятной для чтения и печати.

    Результаты диагностики

    Способ устранения

    Выберите группу неисправности, затем симптом и причину.

    Послеремонтная проверка

    После выбора причины здесь появится порядок контрольной проверки.

    Фотографии диагностики

    Можно добавить до 5 фото с камеры или из галереи.
    Фото не добавлены.
    Текущие дата и время
    --:--:--
    --

    Инструкция по разделу «Ежедневный осмотр»

    1. Обязательно заполните дату, ФИО, наименование объекта, адрес объекта и номер ГП.
    2. По каждому пункту выберите результат проверки и при необходимости внесите замечание.
    3. Установите итог: «ДОПУЩЕН» или «НЕ ДОПУЩЕН».
    4. Нажмите «Подтвердить день», после чего можно распечатать чек-лист или перейти к следующему дню.

    Чек-лист ежедневного осмотра

    ЭлементПроверкаДопускРезультатЗамеч.Подпись

    Фотографии осмотра

    Можно добавить до 5 фото дефектов, замечаний или общего состояния оборудования.
    Фото не добавлены.
    Текущие дата и время
    --:--:--
    --

    Инструкция по разделу «Плановое ТО»

    1. Выберите вид планового обслуживания и проверьте блок «Время проведения по план-графику».
    2. Обязательно заполните дату, ФИО, наименование объекта, адрес объекта и номер ГП.
    3. Заполните отметки по всем операциям и укажите итог формы.
    4. После подтверждения форму можно распечатать, а все заполненные формы выгрузить в Excel или PDF.

    Плановое ТО

    Выберите подвид обслуживания и заполните обязательные поля: дата, ФИО, наименование объекта, адрес объекта, номер ГП.

    Время проведения по план-графику

    `); w.document.close(); w.focus(); setTimeout(() => w.print(), 300); return true; } function printablePhotosHtml(scope, title='Фотографии'){ const items = getPhotoItems(scope); if (!items.length) return ''; const cards = items.map((item, idx) => `
    ${esc(item.name || `Фото ${idx+1}`)}
    Фото ${idx+1}
    `).join(''); return `

    ${esc(title)}

    ${cards}
    `; } $('printChecklist').addEventListener('click', () => { const rows = dailyRows().map(r => ({ id:r.children[0].textContent, object:r.children[1].textContent, check:r.children[2].textContent, criteria:r.children[3].textContent, result:r.querySelector('.result-select').value || '—', remark:r.querySelector('.remark').value || '—', sign:r.querySelector('.sign').value || '—' })); const tableRows = rows.map(r => `${esc(r.id)}${esc(r.object)}${esc(r.check)}${esc(r.criteria)}${esc(r.result)}${esc(r.remark)}${esc(r.sign)}`).join(''); const photoBlock = printablePhotosHtml(dailyPhotoScope(), 'Фотографии ежедневного осмотра'); const html = `

    ЧЕК-ЛИСТ ЕЖЕДНЕВНОГО ОСМОТРА ГРУЗОВОГО ПОДЪЁМНИКА

    Дата
    ${esc($('inspectionDate').value||'—')}
    Наименование объекта
    ${esc($('shift').value||'—')}
    ФИО
    ${esc($('operator').value||'—')}
    Номер ГП
    ${esc($('inventory').value||'—')}
    Адрес объекта
    ${esc($('objectName').value||'—')}
    ${tableRows}
    Объект проверкиЧто проверяетсяКритерий допускаРезультатЗамечаниеПодпись

    Заключение: ${esc($('conclusion').value||'—')}

    Причина при недопуске: ${esc($('reason').value||'—')}

    ${photoBlock}
    Машинист / оператор: __________________
    Механик: __________________
    `; if (!printHtml('Печать чек-листа', html)) showAlert($('dailyAlert'),'Браузер заблокировал окно печати.','error'); }); // Planned maintenance function mergePlanRows(parts){ const result = []; parts.forEach(([prefix, sectionName]) => { (PLANNED[sectionName] || []).forEach((item, idx) => { result.push({ uid:`${prefix}-${idx+1}-${item.id}`, operation:item.operation, criteria:item.criteria }); }); }); return result.map((item, idx) => ({ ...item, displayId: idx + 1 })); } const planMap = { monthly: {title:'Месячный осмотр', rows:mergePlanRows([['m','Месячный осмотр']])}, quarterly: {title:'Квартальный осмотр', rows:mergePlanRows([['m','Месячный осмотр'],['q','Квартальный осмотр']])}, halfyear: {title:'Полугодовое ТО', rows:mergePlanRows([['m','Месячный осмотр'],['q','Квартальный осмотр'],['h','Полугодовое ТО']])}, annual: {title:'Годовое ТО', rows:mergePlanRows([['m','Месячный осмотр'],['q','Квартальный осмотр'],['h','Полугодовое ТО'],['a','Годовое ТО']])} }; function planStorageKey(key){ return `${PLAN_STORAGE_PREFIX}${key}`; } function collectPlanState(key, body){ return { date:$(`${key}Date`).value || '', object:$(`${key}Obj`).value || '', fio:$(`${key}Fio`).value || '', gp:$(`${key}Inv`).value || '', address:$(`${key}Address`).value || '', conclusion:$(`${key}Conclusion`).value || '', note:$(`${key}Note`).value || '', rows:[...body.querySelectorAll('tr[data-id]')].map(r => ({ id:r.dataset.id, mark:r.querySelector('.plan-mark').value || '', remark:r.querySelector('.plan-remark').value || '', sign:r.querySelector('.plan-sign').value || '' })) }; } function savePlanState(key, body){ saveJson(planStorageKey(key), collectPlanState(key, body)); } function applyPlanState(key, body){ const state = loadJson(planStorageKey(key)); $(`${key}Date`).value = fixedToday(); if (!state) return; $(`${key}Obj`).value = state.object || ''; $(`${key}Fio`).value = state.fio || ''; $(`${key}Inv`).value = state.gp || ''; $(`${key}Address`).value = state.address || ''; $(`${key}Conclusion`).value = state.conclusion || ''; $(`${key}Note`).value = state.note || ''; const byId = new Map((state.rows || []).map(r => [String(r.id), r])); [...body.querySelectorAll('tr[data-id]')].forEach(r => { const saved = byId.get(String(r.dataset.id)); if (!saved) return; r.querySelector('.plan-mark').value = saved.mark || ''; r.querySelector('.plan-remark').value = saved.remark || ''; r.querySelector('.plan-sign').value = saved.sign || ''; }); } function bindPlanAutosave(key, body){ $(key).querySelectorAll('input, textarea, select').forEach(el => { el.addEventListener('input', () => savePlanState(key, body)); el.addEventListener('change', () => savePlanState(key, body)); }); } function buildPlanSection(key){ const cfg = planMap[key]; const host = $(key); host.innerHTML = `
    Контрольная операцияНорма / критерийОтметкаЗамечаниеПодпись

    Фотографии ТО

    Добавьте фото дефектов, выполненных работ или итогового состояния.
    Фото не добавлены.
    `; const body = $(`${key}Body`); cfg.rows.forEach(item => { const tr = document.createElement('tr'); tr.dataset.id = item.uid; tr.innerHTML = `${item.displayId}${item.operation}${item.criteria}`; body.appendChild(tr); }); $(`${key}Date`).value = fixedToday(); applyPlanState(key, body); initPlanPhotos(key); bindPlanAutosave(key, body); $(`${key}Confirm`).addEventListener('click', () => { if (!$(`${key}Date`).value || !$(`${key}Inv`).value.trim()) { showAlert($(`${key}Alert`), 'Заполните дату и номер ГП.', 'error'); return; } const rows = [...body.querySelectorAll('tr[data-id]')]; const empty = rows.filter(r => !r.querySelector('.plan-mark').value).length; if (empty) { showAlert($(`${key}Alert`), `Не заполнены отметки по ${empty} пунктам.`, 'error'); return; } if (!$(`${key}Conclusion`).value) { showAlert($(`${key}Alert`), 'Выберите итог формы.', 'error'); return; } savePlanState(key, body); updateHomeStatus(); showAlert($(`${key}Alert`), `${cfg.title} подтвержден и сохранён автоматически.`, 'success'); }); $(`${key}Reset`).addEventListener('click', () => { removeJson(planStorageKey(key)); removeJson(photoStorageKey(planPhotoScope(key))); buildPlanSection(key); updateHomeStatus(); }); $(`${key}Print`).addEventListener('click', () => { const rows = [...body.querySelectorAll('tr[data-id]')].map(r => ({ id:r.children[0].textContent, operation:r.children[1].textContent, criteria:r.children[2].textContent, mark:r.querySelector('.plan-mark').value || '—', remark:r.querySelector('.plan-remark').value || '—', sign:r.querySelector('.plan-sign').value || '—' })); const htmlRows = rows.map(r => `${esc(r.id)}${esc(r.operation)}${esc(r.criteria)}${esc(r.mark)}${esc(r.remark)}${esc(r.sign)}`).join(''); const photoBlock = printablePhotosHtml(planPhotoScope(key), `Фотографии: ${cfg.title}`); const html = `

    ${esc(cfg.title)}

    Дата
    ${esc($(`${key}Date`).value||'—')}
    ФИО
    ${esc($(`${key}Fio`)?.value||'—')}
    Наименование объекта
    ${esc($(`${key}Obj`)?.value||'—')}
    Номер ГП
    ${esc($(`${key}Inv`).value||'—')}
    Адрес объекта
    ${esc($(`${key}Address`)?.value||'—')}
    Итог
    ${esc($(`${key}Conclusion`).value||'—')}
    Примечание
    ${esc($(`${key}Note`).value||'—')}
    ${htmlRows}
    Контрольная операцияНорма / критерийОтметкаЗамечаниеПодпись
    ${photoBlock}
    Исполнитель: __________________
    Механик: __________________
    `; if (!printHtml(cfg.title, html)) showAlert($(`${key}Alert`), 'Браузер заблокировал окно печати.', 'error'); }); } buildPlanSection('monthly'); buildPlanSection('quarterly'); buildPlanSection('halfyear'); buildPlanSection('annual'); // Schedule $('schedule').innerHTML = `
    ОперацияПериодичностьЯнвФевМарАпрМайИюнИюлАвгСенОктНояДекОтветственныйПримечание
    `; const scheduleBody = $('scheduleBody'); SCHEDULE.forEach(row => { const tr = document.createElement('tr'); const currentMonth = new Date().getMonth(); tr.innerHTML = `${row.operation}${row.periodicity}${row.months.map((m, idx) => `${m}`).join('')}${row.assignee}${row.note}`; scheduleBody.appendChild(tr); }); const timingMap = { monthly: 'Месячный осмотр', quarterly: 'Квартальный осмотр', halfyear: 'Полугодовое ТО', annual: 'Годовое ТО' }; const monthNames = ['Январь','Февраль','Март','Апрель','Май','Июнь','Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь']; function getScheduleInfo(operationName){ return SCHEDULE.find(row => row.operation === operationName); } function sameMonth(dateValue, baseDate){ if (!dateValue) return false; const d = new Date(`${dateValue}T00:00:00`); return !Number.isNaN(d.getTime()) && d.getFullYear() === baseDate.getFullYear() && d.getMonth() === baseDate.getMonth(); } function updateHomeStatus(){ const todayEl = $('todayStatus'); const todayNoteEl = $('todayStatusNote'); const monthSummaryEl = $('monthSummary'); const monthListEl = $('monthStatusList'); if (!todayEl || !todayNoteEl || !monthSummaryEl || !monthListEl) return; const now = new Date(); const todayIso = iso(now); const todayState = loadJson(dailyStorageKey(todayIso)); const dailyDone = !!(todayState && todayState.confirmed); todayEl.className = `status-big ${dailyDone ? 'ok' : 'danger'}`; todayEl.textContent = dailyDone ? 'Ежедневный осмотр на сегодня выполнен.' : 'Ежедневный осмотр на сегодня не выполнен.'; todayNoteEl.textContent = dailyDone ? `Подтверждён за ${todayIso}.` : `Требуется подтвердить чек-лист за ${todayIso}.`; const dueKeys = Object.entries(timingMap).filter(([key, operationName]) => { const info = getScheduleInfo(operationName); return !!(info && info.months && info.months[now.getMonth()]); }); const items = dueKeys.map(([key, operationName]) => { const state = loadJson(planStorageKey(key)); const done = !!(state && state.conclusion === 'Выполнено' && sameMonth(state.date, now)); const info = getScheduleInfo(operationName); return { key, title: operationName, done, date: state && state.date ? state.date : '', periodicity: info && info.periodicity ? info.periodicity : '—' }; }); monthSummaryEl.textContent = items.length ? `По графику на ${monthNames[now.getMonth()].toLowerCase()} запланировано: ${items.length}. Выполнено: ${items.filter(x => x.done).length}. Не выполнено: ${items.filter(x => !x.done).length}.` : 'На текущий месяц плановых работ по графику нет.'; monthListEl.innerHTML = items.length ? items.map(item => `
    ${esc(item.title)}
    ${item.done ? 'Выполнено' : 'Не выполнено'}
    Периодичность: ${esc(item.periodicity)}${item.date ? `, дата формы: ${esc(item.date)}` : ''}
    `).join('') : '
    Нет обязательных работ на текущий месяц.
    '; } function updateTimingCard(key){ const card = $('timingCard'); if (!card) return; if (key === 'schedule') { card.style.display = 'none'; return; } card.style.display = 'block'; const info = getScheduleInfo(timingMap[key]); if (!info) { $('timingPeriod').textContent = '—'; $('timingResp').textContent = '—'; $('timingMonths').textContent = '—'; return; } const currentMonth = new Date().getMonth(); const isDueNow = !!info.months[currentMonth]; const months = info.months.map((mark, idx) => { if (!mark) return ''; const cls = idx === currentMonth ? (isDueNow ? 'month-chip current-due' : 'month-chip current-ok') : 'month-chip'; const suffix = idx === currentMonth ? (isDueNow ? ' — срок наступил' : ' — текущий месяц') : ''; return `${monthNames[idx]} (${mark})${suffix}`; }).filter(Boolean); $('timingPeriod').textContent = info.periodicity || '—'; $('timingResp').textContent = info.assignee || '—'; $('timingMonths').innerHTML = months.length ? months.join(' ') : 'По локальному графику'; } updateTimingCard('monthly'); updateHomeStatus(); function getDailyData(){ const rows = dailyRows().map(r => ({ id:r.children[0].textContent.trim(), object:r.children[1].textContent.trim(), check:r.children[2].textContent.trim(), criteria:r.children[3].textContent.trim(), result:r.querySelector('.result-select').value || '', remark:r.querySelector('.remark').value || '', sign:r.querySelector('.sign').value || '' })); const meaningful = $('shift').value.trim() || $('operator').value.trim() || $('inventory').value.trim() || $('objectName').value.trim() || $('conclusion').value || $('reason').value.trim() || rows.some(r => r.result || r.remark || r.sign); return { kind:'daily', title:'Ежедневный осмотр', meaningful: !!meaningful, meta:{ date:$('inspectionDate').value || '', shift:$('shift').value || '', operator:$('operator').value || '', inventory:$('inventory').value || '', objectName:$('objectName').value || '', conclusion:$('conclusion').value || '', reason:$('reason').value || '' }, rows }; } function getPlannedData(key){ const cfg = planMap[key]; const body = $(`${key}Body`); if (!cfg || !body) return null; const rows = [...body.querySelectorAll('tr[data-id]')].map(r => ({ id:r.children[0].textContent.trim(), operation:r.children[1].textContent.trim(), criteria:r.children[2].textContent.trim(), mark:r.querySelector('.plan-mark').value || '', remark:r.querySelector('.plan-remark').value || '', date:r.querySelector('.plan-date').value || '', sign:r.querySelector('.plan-sign').value || '' })); const meaningful = $(`${key}Fio`)?.value.trim() || $(`${key}Obj`)?.value.trim() || $(`${key}Address`)?.value.trim() || $(`${key}Inv`).value.trim() || $(`${key}Conclusion`).value || $(`${key}Note`).value.trim() || rows.some(r => r.mark || r.remark || r.date || r.sign); return { kind:'planned', key, title:cfg.title, meaningful: !!meaningful, meta:{ date:$(`${key}Date`).value || '', fio:$(`${key}Fio`)?.value || '', objectName:$(`${key}Obj`)?.value || '', address:$(`${key}Address`)?.value || '', inventory:$(`${key}Inv`).value || '', conclusion:$(`${key}Conclusion`).value || '', note:$(`${key}Note`).value || '' }, rows }; } function collectFilledForms(){ const forms = []; const daily = getDailyData(); if (daily.meaningful) forms.push(daily); ['monthly','quarterly','halfyear','annual'].forEach(key => { const form = getPlannedData(key); if (form && form.meaningful) forms.push(form); }); return forms; } function xmlEsc(v){ return String(v ?? '').replaceAll('&','&').replaceAll('<','<').replaceAll('>','>').replaceAll('"','"').replaceAll("'",'''); } function safeSheetName(name){ return String(name).replace(/[\\\/?*\[\]:]/g,' ').slice(0,31) || 'Лист'; } function xmlCell(v, style=''){ return `${xmlEsc(v)}`; } function xmlRow(cells, style=''){ return `${cells.map(v => xmlCell(v, style)).join('')}`; } function buildWorkbookXml(forms){ const worksheets = forms.map(form => { const rows = []; rows.push([form.title]); if (form.kind === 'daily'){ rows.push(['Дата', form.meta.date, 'Наименование объекта', form.meta.shift]); rows.push(['ФИО', form.meta.operator, 'Номер ГП', form.meta.inventory]); rows.push(['Адрес объекта', form.meta.objectName, 'Заключение', form.meta.conclusion]); rows.push(['Причина недопуска', form.meta.reason]); rows.push([]); rows.push(['№','Элемент','Проверка','Допуск','Результат','Замеч.','Подпись']); form.rows.forEach(r => rows.push([r.id,r.object,r.check,r.criteria,r.result || '—',r.remark || '—',r.sign || '—'])); } else { rows.push(['Дата', form.meta.date, 'ФИО', form.meta.fio]); rows.push(['Наименование объекта', form.meta.objectName, 'Номер ГП', form.meta.inventory]); rows.push(['Адрес объекта', form.meta.address]); rows.push(['Итог', form.meta.conclusion, 'Примечание', form.meta.note]); rows.push([]); rows.push(['№','Контрольная операция','Норма / критерий','Отметка','Замечание','Дата','Исполнитель','Подпись']); form.rows.forEach(r => rows.push([r.id,r.operation,r.criteria,r.mark || '—',r.remark || '—',r.date || '—',r.worker || '—',r.sign || '—'])); } const tableXml = rows.map((r, i) => xmlRow(r, i === 0 ? 'title' : (i === 5 || (form.kind !== 'daily' && i === 3) ? 'head' : ''))).join(''); return `${tableXml}
    `; }).join(''); return ` ${worksheets} `; } function downloadBlob(content, mime, filename){ const blob = new Blob([content], {type:mime}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); a.remove(); setTimeout(() => URL.revokeObjectURL(url), 1000); } function exportAllToExcel(){ const forms = collectFilledForms(); if (!forms.length){ showAlert($('globalAlert'), 'Нет заполненных форм для выгрузки в Excel.', 'error'); return; } const xml = buildWorkbookXml(forms); const stamp = new Date().toISOString().slice(0,19).replaceAll(':','-'); downloadBlob(xml, 'application/vnd.ms-excel', `формы_осмотров_${stamp}.xls`); showAlert($('globalAlert'), `В Excel выгружено форм: ${forms.length}.`, 'success'); } function renderFormBlock(form){ if (form.kind === 'daily'){ const rows = form.rows.map(r => `${esc(r.id)}${esc(r.object)}${esc(r.check)}${esc(r.criteria)}${esc(r.result || '—')}${esc(r.remark || '—')}${esc(r.sign || '—')}`).join(''); return `

    ${esc(form.title)}

    Дата
    ${esc(form.meta.date || '—')}
    Наименование объекта
    ${esc(form.meta.shift || '—')}
    ФИО
    ${esc(form.meta.operator || '—')}
    Номер ГП
    ${esc(form.meta.inventory || '—')}
    Адрес объекта
    ${esc(form.meta.objectName || '—')}
    ${rows}
    ЭлементПроверкаДопускРезультатЗамеч.Подпись

    Заключение: ${esc(form.meta.conclusion || '—')}

    Причина при недопуске: ${esc(form.meta.reason || '—')}

    Машинист / оператор: __________________
    Механик: __________________
    `; } const rows = form.rows.map(r => `${esc(r.id)}${esc(r.operation)}${esc(r.criteria)}${esc(r.mark || '—')}${esc(r.remark || '—')}${esc(r.date || '—')}${esc(r.sign || '—')}`).join(''); return `

    ${esc(form.title)}

    Дата
    ${esc(form.meta.date || '—')}
    ФИО
    ${esc(form.meta.fio || '—')}
    Наименование объекта
    ${esc(form.meta.objectName || '—')}
    Номер ГП
    ${esc(form.meta.inventory || '—')}
    Адрес объекта
    ${esc(form.meta.address || '—')}
    Итог
    ${esc(form.meta.conclusion || '—')}
    Примечание
    ${esc(form.meta.note || '—')}
    ${rows}
    Контрольная операцияНорма / критерийОтметкаЗамечаниеПодпись
    Исполнитель: __________________
    Механик: __________________
    `; } function exportAllToPdf(){ const forms = collectFilledForms(); if (!forms.length){ showAlert($('globalAlert'), 'Нет заполненных форм для выгрузки в PDF.', 'error'); return; } const html = `

    Сводная выгрузка заполненных форм

    ${forms.map(renderFormBlock).join('')}
    Файл формируется через системную печать браузера. Для сохранения выберите принтер «Сохранить как PDF».
    `; const ok = printHtml('Сводная выгрузка форм', html); if (!ok){ showAlert($('globalAlert'), 'Браузер заблокировал окно выгрузки PDF.', 'error'); return; } showAlert($('globalAlert'), `Подготовлено форм для PDF: ${forms.length}.`, 'success'); } $('exportAllExcel').addEventListener('click', exportAllToExcel); $('exportAllPdf').addEventListener('click', exportAllToPdf);