diff --git a/generate_pdf.py b/generate_pdf.py new file mode 100644 index 0000000..496f2df --- /dev/null +++ b/generate_pdf.py @@ -0,0 +1,458 @@ +#!/usr/bin/env python3 +"""Генерация PDF версии инструктажа по безопасности ТС.""" + +from fpdf import FPDF +from fpdf.enums import XPos, YPos + +class SafetyBriefingPDF(FPDF): + def __init__(self): + super().__init__(orientation="P", unit="mm", format="A4") + self.set_auto_page_break(True, 20) + + color_ink = (15, 18, 24) + color_red = (229, 57, 53) + color_gray = (91, 101, 115) + color_gray_100 = (242, 244, 247) + color_white = (255, 255, 255) + color_amber = (230, 81, 0) + color_green = (46, 125, 50) + color_cyan = (0, 229, 255) + + # We register DejaVu which supports Cyrillic. + font_dir = "/usr/share/fonts/truetype/dejavu/" + self.add_font("DejaVu", "", font_dir + "DejaVuSans.ttf") + self.add_font("DejaVu", "B", font_dir + "DejaVuSans-Bold.ttf") + + self.c = { + "ink": color_ink, + "red": color_red, + "gray": color_gray, + "gray_bg": color_gray_100, + "white": color_white, + "amber": color_amber, + "green": color_green, + "cyan": color_cyan, + } + + # ── helpers ────────────────────────────────────────────── + + def add_header_page(self): + """Первая страница — титульный заголовок.""" + self.add_page() + m = 15 # боковые поля + w = self.w - 2 * m + + # Тёмный заголовок + self.set_fill_color(*self.c["ink"]) + self.rect(0, 0, self.w, 72, "F") + + # Красная полоса + self.set_fill_color(*self.c["red"]) + self.rect(0, 72, self.w, 3, "F") + + # Плашка «ВНЕПЛАНОВЫЙ ИНСТРУКТАЖ» + self.set_y(16) + self.set_font("DejaVu", "B", 9) + self.set_text_color(*self.c["white"]) + self.set_fill_color(*self.c["red"]) + badge_w = self.get_string_width(" ВНЕПЛАНОВЫЙ ИНСТРУКТАЖ ") + 6 + self.set_x(m) + self.cell(badge_w, 6, " ВНЕПЛАНОВЫЙ ИНСТРУКТАЖ ", fill=True, new_x=XPos.LMARGIN, new_y=YPos.NEXT) + + # Заголовок + self.set_y(28) + self.set_x(m) + self.set_font("DejaVu", "B", 18) + self.set_text_color(*self.c["white"]) + self.multi_cell(w, 8, "Обеспечение безопасности эксплуатации транспортных средств", align="L") + + # Подзаголовок + self.set_y(52) + self.set_x(m) + self.set_font("DejaVu", "", 9) + self.set_text_color(154, 163, 178) + self.multi_cell(w, 5, + "Недопущение оставления ключей в замке зажигания и свободного доступа\n" + "посторонних лиц к управлению транспортными средствами") + + # Мета + self.set_y(90) + self.set_x(m) + self.set_text_color(*self.c["ink"]) + self.set_font("DejaVu", "", 9) + self.cell(90, 6, "Дата инструктажа: _______________") + self.cell(0, 6, "Провёл: _______________", new_x=XPos.LMARGIN, new_y=YPos.NEXT) + + def section_title(self, title): + """Заголовок секции с подчёркиванием.""" + self.ln(4) + self.set_font("DejaVu", "B", 13) + self.set_text_color(*self.c["ink"]) + self.cell(0, 8, title, new_x="LMARGIN", new_y="NEXT") + y = self.get_y() + self.set_draw_color(*self.c["gray_bg"]) + self.set_line_width(0.4) + self.line(self.l_margin, y + 2, self.w - self.r_margin, y + 2) + self.ln(5) + + def body_text(self, text): + self.set_font("DejaVu", "", 10) + self.set_text_color(*self.c["ink"]) + self.multi_cell(0, 5.5, text, align="L") + self.ln(1) + + def bold_text(self, text): + self.set_font("DejaVu", "B", 10) + self.set_text_color(*self.c["ink"]) + self.multi_cell(0, 5.5, text, align="L") + + def sub_title(self, title): + self.ln(2) + self.set_font("DejaVu", "B", 10.5) + self.set_text_color(*self.c["ink"]) + self.cell(0, 6, title, new_x="LMARGIN", new_y="NEXT") + self.ln(2) + + def bullet(self, text, color=None): + x0 = self.get_x() + self.set_font("DejaVu", "", 10) + self.set_text_color(*self.c["ink"]) + bx = self.l_margin + 4 + self.set_x(bx) + # bullet marker + self.set_font("DejaVu", "", 10) + marker_color = color or self.c["ink"] + self.set_text_color(*marker_color) + self.set_x(self.l_margin + 1) + self.cell(5, 5.5, "\u25a0") + # text + self.set_text_color(*self.c["ink"]) + self.set_x(bx + 6) + self.multi_cell(self.w - self.r_margin - bx - 6, 5.5, text) + self.ln(1) + + def callout_box(self, text, border_color, bg_color): + self.set_fill_color(*bg_color) + self.set_draw_color(*border_color) + self.set_line_width(0.6) + m = self.l_margin + w = self.w - m - self.r_margin + y0 = self.get_y() + + lines = text.split("\n") + n = len(lines) + h = 10 + n * 5.5 + 4 + self.rect(m, y0, w, h, "DF") + + self.set_draw_color(*border_color) + self.set_line_width(0.6) + self.line(m, y0, m, y0 + h) + + self.set_xy(m + 5, y0 + 3) + self.set_font("DejaVu", "", 9.5) + self.set_text_color(*self.c["ink"]) + for line in lines: + self.set_x(m + 5) + self.cell(w - 10, 5.5, line, new_x="LMARGIN", new_y="NEXT") + self.set_y(y0 + h + 3) + + def checkbox_line(self, text="", x=None): + self.set_font("DejaVu", "", 10) + self.set_text_color(*self.c["ink"]) + self.cell(6, 6, "\u2610") + self.cell(0, 6, text, new_x="LMARGIN", new_y="NEXT") + + def centered_text(self, text, font_style="", size=10): + self.set_font("DejaVu", font_style, size) + self.set_text_color(*self.c["ink"]) + self.cell(0, 7, text, align="C", new_x="LMARGIN", new_y="NEXT") + + def _measure_lines(self, w, line_h, text): + """Return number of lines multi_cell would produce.""" + return len(self.multi_cell(w, line_h, text, dry_run=True, output="LINES")) + + +# ══════════════════════════════════════════════════════════ +def build(): + pdf = SafetyBriefingPDF() + m = 15 # боковое поле + + # ── Титул ── + pdf.add_header_page() + + # ══ 1. ОСНОВАНИЕ И ЦЕЛЬ ══ + pdf.section_title("1. Основание и цель инструктажа") + pdf.body_text( + "Настоящий внеплановый инструктаж проводится в связи с необходимостью усиления мер " + "по обеспечению безопасности дорожного движения и сохранности имущества организации." + ) + pdf.callout_box( + "Повод: выявленные случаи / риск оставления ключей в замке зажигания\n" + "транспортных средств (ТС), что создаёт свободный доступ посторонних лиц\n" + "к управлению ТС, угон, а также использование ТС не по назначению.", + pdf.c["red"], (255, 240, 240), + ) + pdf.body_text( + "Цель: довести до всех категорий водителей и ответственных лиц категорические " + "требования по исключению практики оставления ключей в замке зажигания и обеспечению " + "контроля доступа к транспортным средствам." + ) + + # ══ 2. НОРМАТИВНАЯ БАЗА ══ + pdf.section_title("2. Нормативная база") + for item in [ + "Федеральный закон № 196-ФЗ «О безопасности дорожного движения»", + "Правила дорожного движения РФ (утв. Постановлением Правительства РФ № 1090)", + "Приказ Минтранса РФ от 28.09.2015 № 287 «Об утверждении профессиональных и квалификационных требований к работникам»", + "Правила внутреннего трудового распорядка и должностные инструкции организации", + "Локальные акты организации по охране труда и безопасности на транспорте", + ]: + pdf.bullet(item, pdf.c["green"]) + + # ══ 3. КАТЕГОРИИ ЛИЦ ══ + pdf.section_title("3. Категории инструктируемых лиц") + pdf.sub_title("Водители") + pdf.body_text("Штатные водители, закреплённые за конкретными ТС, выполняющие рейсы по маршрутам и заданиям организации.") + pdf.sub_title("Водители-совместители") + pdf.body_text("Сотрудники, совмещающие обязанности водителя с основной должностью, допущенные к управлению ТС организации.") + pdf.sub_title("Ответственные лица") + pdf.body_text("Лица, ответственные за выпуск ТС на линию, техническое состояние, хранение ТС и ведение документации.") + + # ══ 4. ОПИСАНИЕ ПРОБЛЕМЫ И РИСКОВ ══ + pdf.section_title("4. Описание проблемы и рисков") + pdf.body_text( + "Оставление ключей в замке зажигания транспортного средства вне зависимости от места нахождения ТС " + "(на территории организации, стоянке, в гараже, на линии) создаёт следующие угрозы:" + ) + + # таблица рисков + risk_data = [ + ("Угон ТС посторонними лицами", + "Материальный ущерб организации, уголовная ответственность, срыв производственных задач"), + ("Использование ТС не по назначению", + "Расход ГСМ, износ ТС, нарушение трудовой дисциплины"), + ("ДТП, совершённое лицом без допуска", + "Ущерб жизни и здоровью третьих лиц, гражданская и уголовная ответственность"), + ("Хищение груза / имущества из ТС", + "Прямые финансовые потери, утрата доверия контрагентов"), + ("Нарушение требований ОТ и ПДД", + "Штрафы, дисциплинарные взыскания, предписания контролирующих органов"), + ] + col1_w = 75 + col2_w = pdf.w - pdf.l_margin - pdf.r_margin - col1_w + # header + pdf.set_fill_color(*pdf.c["ink"]) + pdf.set_text_color(*pdf.c["white"]) + pdf.set_font("DejaVu", "B", 9) + pdf.cell(col1_w, 7, " Риск", border=1, fill=True) + pdf.cell(col2_w, 7, " Последствия", border=1, fill=True, new_x="LMARGIN", new_y="NEXT") + # rows + for i, (risk, consequence) in enumerate(risk_data): + pdf.set_font("DejaVu", "", 8.5) + pdf.set_text_color(*pdf.c["ink"]) + if i % 2 == 0: + pdf.set_fill_color(*pdf.c["gray_bg"]) + else: + pdf.set_fill_color(*pdf.c["white"]) + y0 = pdf.get_y() + # measure multi_cell heights + pdf.set_font("DejaVu", "", 8.5) + # compute height + h1 = pdf._measure_lines(col1_w - 2, 4.5, risk) * 4.5 + h2 = pdf._measure_lines(col2_w - 2, 4.5, consequence) * 4.5 + h = max(h1, h2, 13) + if pdf.get_y() + h > pdf.h - pdf.b_margin: + pdf.add_page() + y0 = pdf.get_y() + # draw + pdf.set_fill_color(pdf.c["gray_bg"] if i % 2 == 0 else pdf.c["white"]) + pdf.rect(pdf.l_margin, y0, col1_w, h, "DF") + pdf.rect(pdf.l_margin + col1_w, y0, col2_w, h, "DF") + pdf.set_xy(pdf.l_margin + 1, y0 + 1) + pdf.set_text_color(*pdf.c["ink"]) + pdf.set_font("DejaVu", "B", 8.5) + pdf.multi_cell(col1_w - 2, 4.5, risk) + pdf.set_xy(pdf.l_margin + col1_w + 1, y0 + 1) + pdf.set_font("DejaVu", "", 8.5) + pdf.multi_cell(col2_w - 2, 4.5, consequence) + pdf.set_y(y0 + h) + pdf.ln(4) + + # ══ 5. КАТЕГОРИЧЕСКИЕ ТРЕБОВАНИЯ ══ + pdf.section_title("5. Категорические требования для всех категорий") + + pdf.callout_box( + "ЗАПРЕЩЕНО: оставлять ключи в замке зажигания ТС при любых обстоятельствах:\n" + " \u2022 при кратковременной остановке (заправка, погрузка/разгрузка)\n" + " \u2022 при стоянке на охраняемой территории организации\n" + " \u2022 при постановке ТС в гараж / бокс\n" + " \u2022 при прогреве двигателя без присутствия водителя в кабине\n" + " \u2022 при передаче ТС другому лицу без оформления путевой документации", + pdf.c["red"], (255, 240, 240), + ) + + pdf.callout_box( + "ОБЯЗАНОСТЬ: при покидании транспортного средства водитель обязан:\n" + " \u2022 заглушить двигатель\n" + " \u2022 извлечь ключ из замка зажигания\n" + " \u2022 убрать ключ в надёжное место (карман одежды, сумку)\n" + " \u2022 убедиться, что ТС обездвижено (стояночный тормоз, передача)\n" + " \u2022 закрыть двери кабины/салона на замок", + pdf.c["amber"], (255, 248, 225), + ) + + # ══ 6. ТРЕБОВАНИЯ ПО КАТЕГОРИЯМ ══ + pdf.section_title("6. Требования по категориям") + + pdf.sub_title("6.1 Водители (штатные)") + for item in [ + "Ключи от закреплённого ТС хранятся только у водителя, которому выданы под подпись.", + "При возвращении на базу — сдать ключи диспетчеру / ответственному лицу под роспись в журнале.", + "Запрещена передача ключей третьим лицам (включая коллег) без разрешения ответственного лица.", + "При обнаружении ключей, оставленных в замке зажигания другого ТС — немедленно сообщить диспетчеру и изъять ключи.", + ]: + pdf.bullet(item) + + pdf.sub_title("6.2 Водители-совместители") + for item in [ + "Ключи от ТС получать у ответственного лица на конкретный рейс/задание под роспись.", + "По возвращении сдать ключи немедленно, не оставляя их в кабине.", + "Запрещено брать ТС без оформленного путевого листа и отметки механика о допуске.", + "Запрещено хранить дубликаты ключей от ТС в личных вещах без согласования с ответственным лицом.", + ]: + pdf.bullet(item) + + pdf.sub_title("6.3 Ответственные лица (диспетчеры, механики, начальники колонн)") + for item in [ + "Вести журнал выдачи-возврата ключей от ТС с подписями водителей и отметками времени.", + "Ежедневно проверять наличие ключей в месте хранения (запираемый ящик / сейф).", + "Контролировать, что выпуск ТС осуществляется только при наличии путевого листа и отметки о медосмотре.", + "Незамедлительно докладывать руководству о фактах оставления ключей в замке зажигания.", + "Обеспечить хранение резервных (дублирующих) ключей в опечатанном виде с записью в журнале.", + "Проводить внезапные проверки стоянки ТС на предмет наличия ключей в замке зажигания.", + ]: + pdf.bullet(item) + + # ══ 7. ПОРЯДОК ХРАНЕНИЯ И УЧЁТА КЛЮЧЕЙ ══ + pdf.section_title("7. Порядок хранения и учёта ключей от ТС") + + table_data = [ + ("Операция", "Действие", "Документ", "Ответственный"), + ("Выдача ключей перед рейсом", "Выдать водителю лично под роспись", "Журнал выдачи ключей", "Диспетчер"), + ("Возврат ключей после рейса", "Принять, проверить, сделать запись", "Журнал выдачи ключей", "Диспетчер"), + ("Хранение в нерабочее время", "Запираемый шкаф / сейф в диспетчерской", "—", "Диспетчер / охрана"), + ("Дубликаты ключей", "Опечатанный пенал с биркой (номер ТС)", "Опечатанный пенал", "Руководитель транспортного отдела"), + ("Утеря / поломка ключа", "Акт + выдача дубликата + замена замка", "Акт об утере", "Комиссия (не менее 2 чел.)"), + ] + cols = [45, 55, 42, 52] + col_total = sum(cols) + cw = [] + avail = pdf.w - pdf.l_margin - pdf.r_margin + for c in cols: + cw.append(avail * c / col_total) + + for ri, row in enumerate(table_data): + if pdf.get_y() > pdf.h - 20: + pdf.add_page() + for ci, cell in enumerate(row): + if ri == 0: + pdf.set_fill_color(*pdf.c["ink"]) + pdf.set_text_color(*pdf.c["white"]) + pdf.set_font("DejaVu", "B", 7.5) + else: + pdf.set_fill_color(pdf.c["gray_bg"] if ri % 2 == 0 else pdf.c["white"]) + pdf.set_text_color(*pdf.c["ink"]) + pdf.set_font("DejaVu", "", 7.5) + pdf.cell(cw[ci], 7, " " + cell, border=1, fill=True) + pdf.ln() + + pdf.ln(4) + + # ══ 8. ОТВЕТСТВЕННОСТЬ ══ + pdf.section_title("8. Ответственность за нарушения") + + resp_data = [ + ("Первичное оставление ключей (без последствий)", + "Дисциплинарное взыскание (замечание / выговор) + внеочередной инструктаж"), + ("Повторное оставление ключей", + "Строгий выговор + лишение премии"), + ("Систематические нарушения (3 и более)", + "Отстранение от управления ТС, расторжение трудового договора"), + ("Оставление ключей, повлёкшее угон / ДТП", + "Материальная ответственность в полном объёме ущерба + передача материалов в правоохранительные органы"), + ("Сокрытие факта оставления ключей", + "Увольнение + материальная ответственность"), + ] + col1_w_r = 80 + col2_w_r = pdf.w - pdf.l_margin - pdf.r_margin - col1_w_r + # header + pdf.set_fill_color(*pdf.c["ink"]) + pdf.set_text_color(*pdf.c["white"]) + pdf.set_font("DejaVu", "B", 9) + pdf.cell(col1_w_r, 7, " Нарушение", border=1, fill=True) + pdf.cell(col2_w_r, 7, " Мера ответственности", border=1, fill=True, new_x="LMARGIN", new_y="NEXT") + for i, (violation, measure) in enumerate(resp_data): + if pdf.get_y() > pdf.h - 20: + pdf.add_page() + # compute height + pdf.set_font("DejaVu", "", 8.5) + h1 = pdf._measure_lines(col1_w_r - 2, 4.5, violation) * 4.5 + h2 = pdf._measure_lines(col2_w_r - 2, 4.5, measure) * 4.5 + h = max(h1, h2, 13) + y0 = pdf.get_y() + if y0 + h > pdf.h - pdf.b_margin: + pdf.add_page() + y0 = pdf.get_y() + pdf.set_fill_color(pdf.c["gray_bg"] if i % 2 == 0 else pdf.c["white"]) + pdf.rect(pdf.l_margin, y0, col1_w_r, h, "DF") + pdf.rect(pdf.l_margin + col1_w_r, y0, col2_w_r, h, "DF") + pdf.set_xy(pdf.l_margin + 1, y0 + 1) + pdf.set_font("DejaVu", "B", 8.5) + pdf.set_text_color(*pdf.c["ink"]) + pdf.multi_cell(col1_w_r - 2, 4.5, violation) + pdf.set_xy(pdf.l_margin + col1_w_r + 1, y0 + 1) + pdf.set_font("DejaVu", "", 8.5) + pdf.multi_cell(col2_w_r - 2, 4.5, measure) + pdf.set_y(y0 + h) + pdf.ln(4) + + # ══ 9. ЛИСТ ОЗНАКОМЛЕНИЯ ══ + pdf.section_title("9. Лист ознакомления с инструктажом") + pdf.body_text( + "Своей подписью подтверждаю, что с содержанием внепланового инструктажа ознакомлен(а), " + "требования и меру ответственности осознаю, обязуюсь выполнять." + ) + + pdf.ln(4) + # Таблица подписей + sig_header = ["Фамилия И.О.", "Должность / Категория", "Подпись и дата"] + sig_w = [55, 75, (pdf.w - pdf.l_margin - pdf.r_margin - 130)] # dynamic + + # header + pdf.set_fill_color(*pdf.c["ink"]) + pdf.set_text_color(*pdf.c["white"]) + pdf.set_font("DejaVu", "B", 9) + pdf.cell(sig_w[0], 7, " " + sig_header[0], border=1, fill=True) + pdf.cell(sig_w[1], 7, " " + sig_header[1], border=1, fill=True) + pdf.cell(sig_w[2], 7, " " + sig_header[2], border=1, fill=True, new_x="LMARGIN", new_y="NEXT") + + for i in range(12): + if pdf.get_y() > pdf.h - 20: + pdf.add_page() + pdf.set_fill_color(pdf.c["gray_bg"] if i % 2 == 0 else pdf.c["white"]) + pdf.set_text_color(*pdf.c["ink"]) + pdf.set_font("DejaVu", "", 9) + pdf.cell(sig_w[0], 9, "", border=1, fill=True) + pdf.cell(sig_w[1], 9, "", border=1, fill=True) + pdf.cell(sig_w[2], 9, "", border=1, fill=True, new_x="LMARGIN", new_y="NEXT") + + pdf.ln(6) + pdf.body_text("Инструктаж провёл: _______________________________________ (должность, Ф.И.О., подпись, дата)") + + # ── SAVE ── + out = "Инструктаж_Безопасность_ТС_Ключи_зажигания.pdf" + pdf.output(out) + print(f"PDF сохранён: {out}") + +if __name__ == "__main__": + build() diff --git a/Инструктаж_Безопасность_ТС_Ключи_зажигания.pdf b/Инструктаж_Безопасность_ТС_Ключи_зажигания.pdf new file mode 100644 index 0000000..8ee9b1c Binary files /dev/null and b/Инструктаж_Безопасность_ТС_Ключи_зажигания.pdf differ