"""PDF плана на день (reportlab).""" from __future__ import annotations import io from typing import Any, Dict, List from reportlab.lib import colors from reportlab.lib.pagesizes import A4 from reportlab.lib.styles import getSampleStyleSheet from reportlab.lib.units import mm from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer, Table, TableStyle from app.services.feed_accounting.pdf_feed_accounting import _register_cyrillic_font def _styles(): font = _register_cyrillic_font() styles = getSampleStyleSheet() styles["Title"].fontName = font styles["Normal"].fontName = font styles["Heading2"].fontName = font return styles, font def build_daily_plan_pdf(plan: Dict[str, Any]) -> bytes: buf = io.BytesIO() doc = SimpleDocTemplate(buf, pagesize=A4, leftMargin=14 * mm, rightMargin=14 * mm) styles, font = _styles() story: List[Any] = [] story.append(Paragraph("План на день", styles["Title"])) story.append( Paragraph( f"Дата: {plan.get('date', '—')} · {plan.get('dispenserName', '—')} · {plan.get('farm', '')}", styles["Normal"], ) ) story.append(Spacer(1, 8)) periods = plan.get("periods") or [] if not periods: story.append(Paragraph("Нет периодов или рейсов для выбранного кормораздатчика.", styles["Normal"])) else: for period in periods: story.append(Paragraph(str(period.get("name") or "Период"), styles["Heading2"])) for trip in period.get("trips") or []: story.append( Paragraph( f"Рейс {trip.get('order', '')}: {trip.get('recipeName', '—')} " f"({trip.get('headsPerTrip', 0)} гол., смеш. {trip.get('mixingTimeSec', 0)} с)", styles["Normal"], ) ) data = [["Компонент", "кг/гол", "Всего, кг"]] for ing in trip.get("ingredients") or []: data.append( [ str(ing.get("name") or "—"), str(ing.get("weightPerHead") or ""), str(ing.get("totalKg") or ""), ] ) if len(data) == 1: data.append(["—", "", ""]) tbl = Table(data, colWidths=[80 * mm, 35 * mm, 35 * mm]) tbl.setStyle( TableStyle( [ ("FONTNAME", (0, 0), (-1, -1), font), ("FONTSIZE", (0, 0), (-1, -1), 8), ("BACKGROUND", (0, 0), (-1, 0), colors.lightgrey), ("GRID", (0, 0), (-1, -1), 0.25, colors.grey), ] ) ) story.append(tbl) groups = trip.get("unloadingGroups") or [] if groups: gdata = [["Группа", "кг", "Распределение"]] for g in groups: gdata.append( [ str(g.get("name") or "—"), str(g.get("weightKg") or ""), str( g.get("distributionLabel") or g.get("distributionType") or "" ), ] ) gtbl = Table(gdata, colWidths=[55 * mm, 30 * mm, 40 * mm]) gtbl.setStyle( TableStyle( [ ("FONTNAME", (0, 0), (-1, -1), font), ("FONTSIZE", (0, 0), (-1, -1), 8), ("GRID", (0, 0), (-1, -1), 0.25, colors.grey), ] ) ) story.append(gtbl) story.append(Spacer(1, 6)) totals = plan.get("ingredientTotals") or [] if totals: story.append(Paragraph("Итого по компонентам", styles["Heading2"])) tdata = [["Компонент", "Всего, кг"]] for row in totals: tdata.append([str(row.get("name") or "—"), str(row.get("totalKg") or "")]) grand = plan.get("ingredientGrandTotalKg") if grand is not None: tdata.append(["Итого", str(grand)]) ttbl = Table(tdata, colWidths=[100 * mm, 40 * mm]) ttbl.setStyle( TableStyle( [ ("FONTNAME", (0, 0), (-1, -1), font), ("FONTSIZE", (0, 0), (-1, -1), 9), ("BACKGROUND", (0, 0), (-1, 0), colors.lightgrey), ("GRID", (0, 0), (-1, -1), 0.25, colors.grey), ] ) ) story.append(ttbl) doc.build(story) return buf.getvalue()