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 5e3a9ec90c
commit 82d867f5be
19 changed files with 571 additions and 44 deletions

View file

@ -7,30 +7,41 @@ export default class extends Controller {
};
connect() {
this.readableCheckbox = this.element.querySelector("#readable");
this.displayCheckbox = this.element.querySelector("#display");
this.displaySelect = this.element.querySelector("select#display");
if (!this.readableCheckbox) {
return;
if (this.displayCheckbox) {
this.toggleContainers(this.displayCheckbox.checked);
this.displayCheckbox.addEventListener("change", (event) => {
this.toggleContainers(event.target.checked);
});
}
// Apply the initial visibility state based on the checkbox being checked or not
this.toggleContainers(this.readableCheckbox.checked);
if (this.displaySelect) {
this.toggleContainers(this.hasDisplaySelectValue());
this.displaySelect.addEventListener("change", () => {
this.toggleContainers(this.hasDisplaySelectValue());
});
}
// Add a change event listener to the 'readable' checkbox
this.readableCheckbox.addEventListener("change", (event) => {
// Toggle container visibility when the checkbox value changes
this.toggleContainers(event.target.checked);
});
}
/**
* Toggles the visibility of containers based on the checkbox state.
* Hides specified containers if the checkbox is checked and shows them otherwise.
*
* @param {boolean} isChecked - The current state of the checkbox:
* true if checked (hide elements), false if unchecked (show them).
* Check whether a value was selected in the selectbox
* @returns {boolean} True when a value has not been selected that is not empty
*/
toggleContainers(isChecked) {
hasDisplaySelectValue() {
return this.displaySelect && this.displaySelect.value !== "";
}
/**
* Hides specified containers if the state is active (checkbox checked or select with value).
*
* @param {boolean} isActive - True when the checkbox is activated or the selectbox has a value.
*/
toggleContainers(isActive) {
if (!Array.isArray(this.classesValue) || this.classesValue.length === 0) {
return;
}
@ -42,11 +53,10 @@ export default class extends Controller {
return;
}
// Update the visibility for each selected element
elements.forEach((element) => {
// If the checkbox is checked, hide the container; otherwise, show it
element.style.display = isChecked ? "none" : "";
element.style.display = isActive ? "none" : "";
});
});
}
}
}

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);

View file

@ -35,13 +35,13 @@
</div>
<div class="row mt-2">
<div class="offset-md-3 col-sm">
<div class="form-check">
<input class="form-check-input form-check-input" name="readable" id="readable" type="checkbox" data-action="change->toggle-visibility#toggle">
<label class="form-check-label form-check-label" for="readable">
{% trans %}export.readable{% endtrans %}
</label>
</div>
<label class="col-form-label col-md-3" for="readableSelect" >{% trans %}export.readable.label{% endtrans %}</label>
<div class="col-md-9">
<select id="display" name="readableSelect" class="form-select" data-action="change->action-handler#handleAction">
<option value="" selected></option>
<option value="readable">{% trans %}export.readable{% endtrans %}</option>
<option value="readable_bom">{% trans %}export.readable_bom{% endtrans %}</option>
</select>
</div>
</div>
@ -50,4 +50,4 @@
<button type="submit" class="btn btn-primary">{% trans %}export.btn{% endtrans %}</button>
</div>
</div>
</form>
</form>

View file

@ -0,0 +1,103 @@
<!DOCTYPE html>
<html>
<head>
<title>Assembly Hierarchy</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
table { border-collapse: collapse; width: 100%; margin-top: 10px; font-size: 10px;}
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
.assembly-header { font-size: 14px; font-weight: bold; margin-top: 10px; margin-bottom: 10px; }
.toc-table { margin-bottom: 40px; width: auto; font-size: 12px; }
.toc-table th, .toc-table td { border: none; padding: 5px; text-align: left; }
.referenced-assembly-table { margin-left: 20px; margin-top: 10px; }
.page-break { page-break-after: always; }
#footer { position: fixed; right: 0px; bottom: 10px; text-align: center; font-size: 10px; }
#footer .page:after { content: counter(page, decimal); }
@page { margin: 20px 30px 40px 50px; }
</style>
</head>
<body>
<!-- Inhaltsverzeichnis -->
<h1>Table of Contents</h1>
<table class="toc-table">
<thead>
<tr>
<th>#</th>
<th>Assembly Name</th>
<th>IPN</th>
<th>Section</th>
</tr>
</thead>
<tbody>
{% for assembly in assemblies %}
<tr>
<td>{{ loop.index }}</td>
<td>Assembly: {{ assembly.name }}</td>
<td>{% if assembly.ipn != '' %}{{ assembly.ipn }}{% else %}-{% endif %}</td>
<td>{{ loop.index + 1 }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="page-break"></div>
<!-- Assembly Details -->
{% for assembly in assemblies %}
<div class="assembly-header">Assembly: {{ assembly.name }}</div>
<table>
<thead>
<tr>
<th>Name</th>
<th>IPN</th>
<th>Quantity</th>
<th>Multiplier</th>
<th>Effective Quantity</th>
</tr>
</thead>
<tbody>
{% for part in assembly.parts %}
<tr>
<td>{{ part.name }}</td>
<td>{{ part.ipn }}</td>
<td>{{ part.quantity }}</td>
<td>{% if assembly.multiplier %}{{ assembly.multiplier }}{% else %}-{% endif %}</td>
<td>{{ part.effectiveQuantity }}</td>
</tr>
{% endfor %}
{% for other in assembly.others %}
<tr>
<td>{{ other.name }}</td>
<td>{{ other.ipn }}</td>
<td>{{ other.quantity }}</td>
<td>{{ other.multiplier }}</td>
<td>{{ other.effectiveQuantity }}</td>
</tr>
{% endfor %}
{% for referencedAssembly in assembly.referencedAssemblies %}
<tr>
<td>{{ referencedAssembly.name }}</td>
<td>{{ referencedAssembly.ipn }}</td>
<td>{{ referencedAssembly.quantity }}</td>
<td></td>
<td>{{ referencedAssembly.quantity }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% for refAssembly in assembly.referencedAssemblies %}
{% include 'assemblies/export_bom_referenced_assembly_pdf.html.twig' with {'assembly': refAssembly} only %}
{% endfor %}
{% if not loop.last %}
<div style="page-break-after: always;"></div>
{% endif %}
<div id="footer">
<p class="page">Section </p>
</div>
{% endfor %}
</body>
</html>

View file

@ -0,0 +1,55 @@
<div class="referenced-assembly-table">
<div class="assembly-header">Referenced Assembly: {{ assembly.name }} [IPN: {% if assembly.ipn != '' %}{{ assembly.ipn }}{% else %}-{% endif %}, quantity: {{ assembly.quantity }}]</div>
<table>
<thead>
<tr>
<th>Type</th>
<th>Name</th>
<th>IPN</th>
<th>Quantity</th>
<th>Multiplier</th>
<th>Effective Quantity</th>
</tr>
</thead>
<tbody>
{% for part in assembly.parts %}
<tr>
<td>Part</td>
<td>{{ part.name }}</td>
<td>{{ part.ipn }}</td>
<td>{{ part.quantity }}</td>
<td>{% if assembly.multiplier %}{{ assembly.multiplier }}{% else %}-{% endif %}</td>
<td>{{ part.effectiveQuantity }}</td>
</tr>
{% endfor %}
{% for other in assembly.others %}
<tr>
<td>Other</td>
<td>{{ other.name }}</td>
<td>-</td>
<td>{{ other.quantity }}</td>
<td>{{ other.multiplier }}</td>
<td>-</td>
</tr>
{% endfor %}
{% for referencedAssembly in assembly.referencedAssemblies %}
<tr>
<td>Referenced assembly</td>
<td>{{ referencedAssembly.name }}</td>
<td>-</td>
<td>{{ referencedAssembly.quantity }}</td>
<td></td>
<td>{{ referencedAssembly.multiplier }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<!-- Rekursive Auflistung weiterer Assemblies -->
{% for refAssembly in assembly.referencedAssemblies %}
{% include 'assemblies/export_bom_referenced_assembly_pdf.html.twig' with {'assembly': refAssembly} only %}
{% endfor %}
</div>

View file

@ -351,10 +351,22 @@
<target>Exportovat všechny prvky</target>
</segment>
</unit>
<unit id="Ujhd6e3" name="export.readable.label">
<segment state="translated">
<source>export.readable.label</source>
<target>Čitelný export</target>
</segment>
</unit>
<unit id="a5h8ltP" name="export.readable">
<segment state="translated">
<source>export.readable</source>
<target>Čitelné CSV</target>
<target>CSV</target>
</segment>
</unit>
<unit id="keJnq8f" name="export.readable_bom">
<segment state="translated">
<source>export.readable_bom</source>
<target>PDF</target>
</segment>
</unit>
<unit id="M.rXmnA" name="mass_creation.help">

View file

@ -351,10 +351,22 @@
<target>Eksportér alle elementer</target>
</segment>
</unit>
<unit id="Ujhd6e3" name="export.readable.label">
<segment state="translated">
<source>export.readable.label</source>
<target>Læsbar eksport</target>
</segment>
</unit>
<unit id="a5h8ltP" name="export.readable">
<segment state="translated">
<source>export.readable</source>
<target>Læsbar CSV</target>
<target>CSV</target>
</segment>
</unit>
<unit id="keJnq8f" name="export.readable_bom">
<segment state="translated">
<source>export.readable_bom</source>
<target>PDF</target>
</segment>
</unit>
<unit id="zPSdxU4" name="mass_creation.help">

View file

@ -1010,10 +1010,22 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
<target>Unterelemente auch exportieren</target>
</segment>
</unit>
<unit id="Ujhd6e3" name="export.readable.label">
<segment state="translated">
<source>export.readable.label</source>
<target>Lesbarer Export</target>
</segment>
</unit>
<unit id="a5h8ltP" name="export.readable">
<segment state="translated">
<source>export.readable</source>
<target>Lesbares CSV</target>
<target>CSV</target>
</segment>
</unit>
<unit id="keJnq8f" name="export.readable_bom">
<segment state="translated">
<source>export.readable_bom</source>
<target>PDF</target>
</segment>
</unit>
<unit id="Pdmfku8" name="export.btn">

View file

@ -228,10 +228,22 @@
<target>Εξαγωγή όλων των στοιχείων</target>
</segment>
</unit>
<unit id="Ujhd6e3" name="export.readable.label">
<segment state="translated">
<source>export.readable.label</source>
<target>Αναγνώσιμη εξαγωγή</target>
</segment>
</unit>
<unit id="a5h8ltP" name="export.readable">
<segment state="translated">
<source>export.readable</source>
<target>Αναγνώσιμο CSV</target>
<target>CSV</target>
</segment>
</unit>
<unit id="keJnq8f" name="export.readable_bom">
<segment state="translated">
<source>export.readable_bom</source>
<target>PDF</target>
</segment>
</unit>
<unit id="zPSdxU4" name="mass_creation.help">

View file

@ -351,10 +351,22 @@
<target>Export all elements</target>
</segment>
</unit>
<unit id="Ujhd6e3" name="export.readable.label">
<segment state="translated">
<source>export.readable.label</source>
<target>Readable Export</target>
</segment>
</unit>
<unit id="a5h8ltP" name="export.readable">
<segment state="translated">
<source>export.readable</source>
<target>Readable CSV</target>
<target>CSV</target>
</segment>
</unit>
<unit id="keJnq8f" name="export.readable_bom">
<segment state="translated">
<source>export.readable_bom</source>
<target>PDF</target>
</segment>
</unit>
<unit id="M.rXmnA" name="mass_creation.help">

View file

@ -351,10 +351,22 @@
<target>Exportar todos los elementos</target>
</segment>
</unit>
<unit id="Ujhd6e3" name="export.readable.label">
<segment state="translated">
<source>export.readable.label</source>
<target>Exportación legible</target>
</segment>
</unit>
<unit id="a5h8ltP" name="export.readable">
<segment state="translated">
<source>export.readable</source>
<target>CSV legible</target>
<target>CSV</target>
</segment>
</unit>
<unit id="keJnq8f" name="export.readable_bom">
<segment state="translated">
<source>export.readable_bom</source>
<target>PDF</target>
</segment>
</unit>
<unit id="M.rXmnA" name="mass_creation.help">

View file

@ -320,10 +320,22 @@
<target>Exporter tous les éléments</target>
</segment>
</unit>
<unit id="Ujhd6e3" name="export.readable.label">
<segment state="translated">
<source>export.readable.label</source>
<target>Export lisible</target>
</segment>
</unit>
<unit id="a5h8ltP" name="export.readable">
<segment state="translated">
<source>export.readable</source>
<target>CSV lisible</target>
<target>CSV</target>
</segment>
</unit>
<unit id="keJnq8f" name="export.readable_bom">
<segment state="translated">
<source>export.readable_bom</source>
<target>PDF</target>
</segment>
</unit>
<unit id="zPSdxU4" name="mass_creation.help">

View file

@ -351,10 +351,22 @@
<target>Esportare tutti gli elementi</target>
</segment>
</unit>
<unit id="Ujhd6e3" name="export.readable.label">
<segment state="translated">
<source>export.readable.label</source>
<target>Esporta leggibile</target>
</segment>
</unit>
<unit id="a5h8ltP" name="export.readable">
<segment state="translated">
<source>export.readable</source>
<target>CSV leggibile</target>
<target>CSV</target>
</segment>
</unit>
<unit id="keJnq8f" name="export.readable_bom">
<segment state="translated">
<source>export.readable_bom</source>
<target>PDF</target>
</segment>
</unit>
<unit id="M.rXmnA" name="mass_creation.help">

View file

@ -320,10 +320,22 @@
<target>すべてエクスポートする</target>
</segment>
</unit>
<unit id="Ujhd6e3" name="export.readable.label">
<segment state="translated">
<source>export.readable.label</source>
<target>読みやすいエクスポート</target>
</segment>
</unit>
<unit id="a5h8ltP" name="export.readable">
<segment state="translated">
<source>export.readable</source>
<target>読みやすいCSV</target>
<target>CSV</target>
</segment>
</unit>
<unit id="keJnq8f" name="export.readable_bom">
<segment state="translated">
<source>export.readable_bom</source>
<target>PDF</target>
</segment>
</unit>
<unit id="zPSdxU4" name="mass_creation.help">

View file

@ -351,10 +351,22 @@
<target>Exporteer alle elementen</target>
</segment>
</unit>
<unit id="Ujhd6e3" name="export.readable.label">
<segment state="translated">
<source>export.readable.label</source>
<target>Leesbare export</target>
</segment>
</unit>
<unit id="a5h8ltP" name="export.readable">
<segment state="translated">
<source>export.readable</source>
<target>Leesbare CSV</target>
<target>CSV</target>
</segment>
</unit>
<unit id="keJnq8f" name="export.readable_bom">
<segment state="translated">
<source>export.readable_bom</source>
<target>PDF</target>
</segment>
</unit>
<unit id="zPSdxU4" name="mass_creation.help">

View file

@ -351,10 +351,22 @@
<target>Eksportuj wszystkie elementy</target>
</segment>
</unit>
<unit id="Ujhd6e3" name="export.readable.label">
<segment state="translated">
<source>export.readable.label</source>
<target>Eksport czytelny</target>
</segment>
</unit>
<unit id="a5h8ltP" name="export.readable">
<segment state="translated">
<source>export.readable</source>
<target>Czytelny CSV</target>
<target>CSV</target>
</segment>
</unit>
<unit id="keJnq8f" name="export.readable_bom">
<segment state="translated">
<source>export.readable_bom</source>
<target>PDF</target>
</segment>
</unit>
<unit id="M.rXmnA" name="mass_creation.help">

View file

@ -351,10 +351,22 @@
<target>Экспортировать всё</target>
</segment>
</unit>
<unit id="Ujhd6e3" name="export.readable.label">
<segment state="translated">
<source>export.readable.label</source>
<target>Читаемый экспорт</target>
</segment>
</unit>
<unit id="a5h8ltP" name="export.readable">
<segment state="translated">
<source>export.readable</source>
<target>Читаемый CSV</target>
<target>CSV</target>
</segment>
</unit>
<unit id="keJnq8f" name="export.readable_bom">
<segment state="translated">
<source>export.readable_bom</source>
<target>PDF</target>
</segment>
</unit>
<unit id="M.rXmnA" name="mass_creation.help">

View file

@ -351,10 +351,22 @@
<target>导出所有元素</target>
</segment>
</unit>
<unit id="Ujhd6e3" name="export.readable.label">
<segment state="translated">
<source>export.readable.label</source>
<target>可读导出</target>
</segment>
</unit>
<unit id="a5h8ltP" name="export.readable">
<segment state="translated">
<source>export.readable</source>
<target>可读的 CSV</target>
<target>CSV</target>
</segment>
</unit>
<unit id="keJnq8f" name="export.readable_bom">
<segment state="translated">
<source>export.readable_bom</source>
<target>PDF</target>
</segment>
</unit>
<unit id="zPSdxU4" name="mass_creation.help">