130 lines
5.1 KiB
Python
130 lines
5.1 KiB
Python
"""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()
|