Erweitere Exportfunktion um lesbare BOM-Option (PDF-Ausgabe).

Neue Auswahloption "Lesbarer Export" hinzugefügt, die den Export hierarchischer Baugruppen als PDF ermöglicht.
This commit is contained in:
Marcel Diegelmann 2025-09-15 16:04:00 +02:00
parent 4251b2ab46
commit a682b87070
19 changed files with 571 additions and 44 deletions

View file

@ -24,9 +24,16 @@ namespace App\Helpers\Assemblies;
use App\Entity\AssemblySystem\Assembly;
use App\Entity\Parts\Part;
use Dompdf\Dompdf;
use Dompdf\Options;
use Twig\Environment;
class AssemblyPartAggregator
{
public function __construct(private readonly Environment $twig)
{
}
/**
* Aggregate the required parts and their total quantities for an assembly.
*
@ -80,4 +87,181 @@ class AssemblyPartAggregator
}
}
}
/**
* Exports a hierarchical Bill of Materials (BOM) for assemblies and parts in a readable format,
* including the multiplier for each part and assembly.
*
* @param Assembly $assembly The root assembly to export.
* @param string $indentationSymbol The symbol used for indentation (e.g., ' ').
* @param int $initialDepth The starting depth for formatting (default: 0).
* @return string Human-readable hierarchical BOM list.
*/
public function exportReadableHierarchy(Assembly $assembly, string $indentationSymbol = ' ', int $initialDepth = 0): string
{
// Start building the hierarchy
$output = '';
$this->processAssemblyHierarchy($assembly, $initialDepth, 1, $indentationSymbol, $output);
return $output;
}
public function exportReadableHierarchyForPdf(array $assemblyHierarchies): string
{
$html = $this->twig->render('assemblies/export_bom_pdf.html.twig', [
'assemblies' => $assemblyHierarchies,
]);
$options = new Options();
$options->set('isHtml5ParserEnabled', true);
$options->set('isPhpEnabled', true);
$dompdf = new Dompdf($options);
$dompdf->loadHtml($html);
$dompdf->setPaper('A4');
$dompdf->render();
$canvas = $dompdf->getCanvas();
$font = $dompdf->getFontMetrics()->getFont('Arial', 'normal');
return $dompdf->output();
}
/**
* Recursive method to process assemblies and their parts.
*
* @param Assembly $assembly The current assembly to process.
* @param int $depth The current depth in the hierarchy.
* @param float $parentMultiplier The multiplier inherited from the parent (default is 1 for root).
* @param string $indentationSymbol The symbol used for indentation.
* @param string &$output The cumulative output string.
*/
private function processAssemblyHierarchy(Assembly $assembly, int $depth, float $parentMultiplier, string $indentationSymbol, string &$output): void
{
// Add the current assembly to the output
if ($depth === 0) {
$output .= sprintf(
"%sAssembly: %s [IPN: %s]\n\n",
str_repeat($indentationSymbol, $depth),
$assembly->getName(),
$assembly->getIpn(),
);
} else {
$output .= sprintf(
"%sAssembly: %s [IPN: %s, Multiplier: %.2f]\n\n",
str_repeat($indentationSymbol, $depth),
$assembly->getName(),
$assembly->getIpn(),
$parentMultiplier
);
}
// Gruppiere BOM-Einträge in Kategorien
$parts = [];
$referencedAssemblies = [];
$others = [];
foreach ($assembly->getBomEntries() as $bomEntry) {
if ($bomEntry->getPart() instanceof Part) {
$parts[] = $bomEntry;
} elseif ($bomEntry->getReferencedAssembly() instanceof Assembly) {
$referencedAssemblies[] = $bomEntry;
} else {
$others[] = $bomEntry;
}
}
if (!empty($parts)) {
// Process each BOM entry for the current assembly
foreach ($parts as $bomEntry) {
$effectiveQuantity = $bomEntry->getQuantity() * $parentMultiplier;
$output .= sprintf(
"%sPart: %s [IPN: %s, MPNR: %s, Quantity: %.2f%s, EffectiveQuantity: %.2f]\n",
str_repeat($indentationSymbol, $depth + 1),
$bomEntry->getPart()?->getName(),
$bomEntry->getPart()?->getIpn() ?? '-',
$bomEntry->getPart()?->getManufacturerProductNumber() ?? '-',
$bomEntry->getQuantity(),
$parentMultiplier > 1 ? sprintf(", Multiplier: %.2f", $parentMultiplier) : '',
$effectiveQuantity,
);
}
$output .= "\n";
}
foreach ($referencedAssemblies as $bomEntry) {
// Add referenced assembly details
$referencedQuantity = $bomEntry->getQuantity() * $parentMultiplier;
$output .= sprintf(
"%sReferenced Assembly: %s [IPN: %s, Quantity: %.2f%s, EffectiveQuantity: %.2f]\n",
str_repeat($indentationSymbol, $depth + 1),
$bomEntry->getReferencedAssembly()->getName(),
$bomEntry->getReferencedAssembly()->getIpn() ?? '-',
$bomEntry->getQuantity(),
$parentMultiplier > 1 ? sprintf(", Multiplier: %.2f", $parentMultiplier) : '',
$referencedQuantity,
);
// Recurse into the referenced assembly
$this->processAssemblyHierarchy(
$bomEntry->getReferencedAssembly(),
$depth + 2, // Increase depth for nested assemblies
$referencedQuantity, // Pass the calculated multiplier
$indentationSymbol,
$output
);
}
foreach ($others as $bomEntry) {
$output .= sprintf(
"%sOther: %s [Quantity: %.2f, Multiplier: %.2f]\n",
str_repeat($indentationSymbol, $depth + 1),
$bomEntry->getName(),
$bomEntry->getQuantity(),
$parentMultiplier,
);
}
}
public function processAssemblyHierarchyForPdf(Assembly $assembly, int $depth, float $quantity, float $parentMultiplier): array
{
$result = [
'name' => $assembly->getName(),
'ipn' => $assembly->getIpn(),
'quantity' => $quantity,
'multiplier' => $depth === 0 ? null : $parentMultiplier,
'parts' => [],
'referencedAssemblies' => [],
'others' => [],
];
foreach ($assembly->getBomEntries() as $bomEntry) {
if ($bomEntry->getPart() instanceof Part) {
$result['parts'][] = [
'name' => $bomEntry->getPart()->getName(),
'ipn' => $bomEntry->getPart()->getIpn(),
'quantity' => $bomEntry->getQuantity(),
'effectiveQuantity' => $bomEntry->getQuantity() * $parentMultiplier,
];
} elseif ($bomEntry->getReferencedAssembly() instanceof Assembly) {
$result['referencedAssemblies'][] = $this->processAssemblyHierarchyForPdf(
$bomEntry->getReferencedAssembly(),
$depth + 1,
$bomEntry->getQuantity(),
$parentMultiplier * $bomEntry->getQuantity()
);
} else {
$result['others'][] = [
'name' => $bomEntry->getName(),
'quantity' => $bomEntry->getQuantity(),
'multiplier' => $parentMultiplier,
];
}
}
return $result;
}
}

View file

@ -81,8 +81,9 @@ class EntityExporter
$resolver->setDefault('include_children', false);
$resolver->setAllowedTypes('include_children', 'bool');
$resolver->setDefault('readable', false);
$resolver->setAllowedTypes('readable', 'bool');
$resolver->setDefault('readableSelect', null);
$resolver->setAllowedValues('readableSelect', [null, 'readable', 'readable_bom']);
}
/**
@ -240,7 +241,7 @@ class EntityExporter
$entities = [$entities];
}
if ($request->get('readable', false)) {
if ($request->get('readableSelect', false) === 'readable') {
// Map entity classes to export functions
$entityExportMap = [
AttachmentType::class => fn($entities) => $this->exportReadable($entities, AttachmentType::class),
@ -273,6 +274,23 @@ class EntityExporter
$options['format'] = 'csv';
$options['level'] = 'readable';
} if ($request->get('readableSelect', false) === 'readable_bom') {
$hierarchies = [];
foreach ($entities as $entity) {
if (!$entity instanceof Assembly) {
throw new InvalidArgumentException('Only assemblies can be exported in readable BOM format');
}
$hierarchies[] = $this->assemblyPartAggregator->processAssemblyHierarchyForPdf($entity, 0, 1, 1);
}
$pdfContent = $this->assemblyPartAggregator->exportReadableHierarchyForPdf($hierarchies);
$response = new Response($pdfContent);
$options['format'] = 'pdf';
$options['level'] = 'readable_bom';
} else {
//Do the serialization with the given options
$serialized_data = $this->exportEntities($entities, $options);
@ -294,6 +312,7 @@ class EntityExporter
'json' => 'application/json',
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'xls' => 'application/vnd.ms-excel',
'pdf' => 'application/pdf',
default => 'text/plain',
};
$response->headers->set('Content-Type', $content_type);