Füge Designator zu Stücklisten-Einträgen für Freitext-Angabe hinzu. EntityExporter: Spaltennamen in lesbaren Export anpassen.

Neue Unterstützung für Designator-Feld in AssemblyBomEntries eingeführt, einschließlich Updates für Migrationen, Übersetzungen und Frontend-Layout. Dies ermöglicht die Verwaltung freier Bezeichnungskennungen in der Stückliste.
This commit is contained in:
Marcel Diegelmann 2025-09-30 15:30:12 +02:00
parent e13803ea16
commit a1390b36a8
24 changed files with 1196 additions and 72 deletions

View file

@ -162,6 +162,12 @@ class AssemblyBomEntriesDataTable implements DataTableTypeInterface
return $html;
},
])
->add('designator', TextColumn::class, [
'label' => 'assembly.bom.designator',
'render' => function ($value, AssemblyBOMEntry $context) {
return htmlspecialchars($context->getDesignator());
},
])
->add('instockAmount', TextColumn::class, [
'label' => 'assembly.bom.instockAmount',
'visible' => false,

View file

@ -84,7 +84,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
normalizationContext: ['groups' => ['bom_entry:read', 'api:basic:read'], 'openapi_definition_name' => 'Read']
)]
#[ApiFilter(PropertyFilter::class)]
#[ApiFilter(LikeFilter::class, properties: ["name", "comment", 'mountnames'])]
#[ApiFilter(LikeFilter::class, properties: ["name", 'mountnames', 'designator', "comment"])]
#[ApiFilter(RangeFilter::class, properties: ['quantity'])]
#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified', 'quantity'])]
class AssemblyBOMEntry extends AbstractDBElement implements UniqueValidatableInterface, TimeStampableInterface
@ -103,6 +103,13 @@ class AssemblyBOMEntry extends AbstractDBElement implements UniqueValidatableInt
#[Groups(['bom_entry:read', 'bom_entry:write', 'import', 'simple', 'extended', 'full'])]
protected string $mountnames = '';
/**
* @var string Reference mark on the circuit diagram/PCB
*/
#[ORM\Column(name: 'designator', type: Types::TEXT)]
#[Groups(['bom_entry:read', 'bom_entry:write', 'import', 'simple', 'extended', 'full'])]
protected string $designator = '';
/**
* @var string|null An optional name describing this BOM entry (useful for non-part entries)
*/
@ -190,6 +197,16 @@ class AssemblyBOMEntry extends AbstractDBElement implements UniqueValidatableInt
return $this;
}
public function getDesignator(): string
{
return $this->designator;
}
public function setDesignator(string $designator): void
{
$this->designator = $designator;
}
/**
* @return string
*/

View file

@ -45,12 +45,17 @@ class AssemblyBOMEntryType extends AbstractType
])
->add('name', TextType::class, [
'label' => 'assembly.bom.name',
'help' => 'assembly.bom.name.help',
'required' => false,
])
->add('designator', TextType::class, [
'label' => 'assembly.bom.designator',
'help' => 'assembly.bom.designator.help',
'required' => false
])
->add('mountnames', TextType::class, [
'required' => false,
'label' => 'assembly.bom.mountnames',
'empty_data' => '',
'attr' => [
'class' => 'tagsinput',
'data-controller' => 'elements--tagsinput',

View file

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace App\Helpers\Assemblies;
use App\Entity\AssemblySystem\Assembly;
use App\Entity\AssemblySystem\AssemblyBOMEntry;
use App\Entity\Parts\Part;
use Dompdf\Dompdf;
use Dompdf\Options;
@ -61,6 +62,7 @@ class AssemblyPartAggregator
*/
private function processAssembly(Assembly $assembly, float $multiplier, array &$aggregatedParts): void
{
/** @var AssemblyBOMEntry $bomEntry */
foreach ($assembly->getBomEntries() as $bomEntry) {
// If the BOM entry refers to a part, add its quantity
if ($bomEntry->getPart() instanceof Part) {
@ -70,6 +72,8 @@ class AssemblyPartAggregator
$aggregatedParts[$part->getId()] = [
'part' => $part,
'assembly' => $assembly,
'name' => $bomEntry->getName(),
'designator' => $bomEntry->getDesignator(),
'quantity' => $bomEntry->getQuantity(),
'multiplier' => $multiplier,
];
@ -81,6 +85,8 @@ class AssemblyPartAggregator
$aggregatedParts[] = [
'part' => null,
'assembly' => $assembly,
'name' => $bomEntry->getName(),
'designator' => $bomEntry->getDesignator(),
'quantity' => $bomEntry->getQuantity(),
'multiplier' => $multiplier,
];

View file

@ -866,7 +866,11 @@ class BOMImporter
}
if (isset($entry['designator'])) {
$bomEntry->setMountnames(trim($entry['designator']) === '' ? '' : trim($entry['designator']));
if ($bomEntry instanceof ProjectBOMEntry) {
$bomEntry->setMountnames(trim($entry['designator']) === '' ? '' : trim($entry['designator']));
} elseif ($bomEntry instanceof AssemblyBOMEntry) {
$bomEntry->setDesignator(trim($entry['designator']) === '' ? '' : trim($entry['designator']));
}
}
$bomEntry->setPart($part);

View file

@ -380,25 +380,31 @@ class EntityExporter
],
Project::class => [
'header' => [
'Id', 'ParentId', 'Type', 'ProjectNameHierarchical', 'ProjectName', 'ProjectFullName', 'BomQuantity',
'BomPartId', 'BomPartIpn', 'BomPartMpnr', 'BomPartName', 'BomDesignator', 'BomPartDescription',
'BomMountNames'
'Id', 'ParentId', 'Type', 'ProjectNameHierarchical', 'ProjectName', 'ProjectFullName',
//BOM relevant attributes
'Quantity', 'PartId', 'PartName', 'Ipn', 'Manufacturer', 'Mpn', 'Name', 'Designator',
'Description', 'MountNames'
],
'processEntity' => fn($entity, $depth) => [
'ProjectId' => $entity->getId(),
'ParentProjectId' => $entity->getParent()?->getId() ?? '',
'Id' => $entity->getId(),
'ParentId' => $entity->getParent()?->getId() ?? '',
'Type' => 'project',
'ProjectNameHierarchical' => str_repeat('--', $depth) . ' ' . $entity->getName(),
'ProjectName' => $entity->getName(),
'ProjectFullName' => $this->getFullName($entity),
'BomQuantity' => '-',
'BomPartId' => '-',
'BomPartIpn' => '-',
'BomPartMpnr' => '-',
'BomPartName' => '-',
'BomDesignator' => '-',
'BomPartDescription' => '-',
'BomMountNames' => '-',
//BOM relevant attributes
'Quantity' => '-',
'PartId' => '-',
'PartName' => '-',
'Ipn' => '-',
'Manufacturer' => '-',
'Mpn' => '-',
'Name' => '-',
'Designator' => '-',
'Description' => '-',
'MountNames' => '-',
],
'processBomEntries' => fn($entity, $depth) => array_map(fn(ProjectBOMEntry $bomEntry) => [
'Id' => $entity->getId(),
@ -407,22 +413,29 @@ class EntityExporter
'ProjectNameHierarchical' => str_repeat('--', $depth) . '> ' . $entity->getName(),
'ProjectName' => $entity->getName(),
'ProjectFullName' => $this->getFullName($entity),
'BomQuantity' => $bomEntry->getQuantity() ?? '',
'BomPartId' => $bomEntry->getPart()?->getId() ?? '',
'BomPartIpn' => $bomEntry->getPart()?->getIpn() ?? '',
'BomPartMpnr' => $bomEntry->getPart()?->getManufacturerProductNumber() ?? '',
'BomPartName' => $bomEntry->getPart()?->getName() ?? '',
'BomDesignator' => $bomEntry->getName() ?? '',
'BomPartDescription' => $bomEntry->getPart()?->getDescription() ?? '',
'BomMountNames' => $bomEntry->getMountNames(),
//BOM relevant attributes
'Quantity' => $bomEntry->getQuantity() ?? '',
'PartId' => $bomEntry->getPart()?->getId() ?? '',
'PartName' => $bomEntry->getPart()?->getName() ?? '',
'Ipn' => $bomEntry->getPart()?->getIpn() ?? '',
'Manufacturer' => $bomEntry->getPart()?->getManufacturer()?->getName() ?? '',
'Mpn' => $bomEntry->getPart()?->getManufacturerProductNumber() ?? '',
'Name' => $bomEntry->getPart()?->getName() ?? '',
'Designator' => $bomEntry->getMountnames() ?? '',
'Description' => $bomEntry->getPart()?->getDescription() ?? '',
'MountNames' => $bomEntry->getMountNames(),
], $entity->getBomEntries()->toArray()),
],
Assembly::class => [
'header' => [
'Id', 'ParentId', 'Type', 'AssemblyIpn', 'AssemblyNameHierarchical', 'AssemblyName',
'AssemblyFullName', 'BomQuantity', 'BomMultiplier', 'BomPartId', 'BomPartIpn', 'BomPartMpnr',
'BomPartName', 'BomDesignator', 'BomPartDescription', 'BomMountNames', 'BomReferencedAssemblyId',
'BomReferencedAssemblyIpn', 'BomReferencedAssemblyFullName'
'AssemblyFullName',
//BOM relevant attributes
'Quantity', 'PartId', 'PartName', 'Ipn', 'Manufacturer', 'Mpn', 'Name', 'Designator',
'Description', 'MountNames', 'ReferencedAssemblyId', 'ReferencedAssemblyIpn',
'ReferencedAssemblyFullName'
],
'processEntity' => fn($entity, $depth) => [
'Id' => $entity->getId(),
@ -432,18 +445,21 @@ class EntityExporter
'AssemblyNameHierarchical' => str_repeat('--', $depth) . ' ' . $entity->getName(),
'AssemblyName' => $entity->getName(),
'AssemblyFullName' => $this->getFullName($entity),
'BomQuantity' => '-',
'BomMultiplier' => '-',
'BomPartId' => '-',
'BomPartIpn' => '-',
'BomPartMpnr' => '-',
'BomPartName' => '-',
'BomDesignator' => '-',
'BomPartDescription' => '-',
'BomMountNames' => '-',
'BomReferencedAssemblyId' => '-',
'BomReferencedAssemblyIpn' => '-',
'BomReferencedAssemblyFullName' => '-',
//BOM relevant attributes
'Quantity' => '-',
'PartId' => '-',
'PartName' => '-',
'Ipn' => '-',
'Manufacturer' => '-',
'Mpn' => '-',
'Name' => '-',
'Designator' => '-',
'Description' => '-',
'MountNames' => '-',
'ReferencedAssemblyId' => '-',
'ReferencedAssemblyIpn' => '-',
'ReferencedAssemblyFullName' => '-',
],
'processBomEntries' => fn($entity, $depth) => $this->processBomEntriesWithAggregatedParts($entity, $depth),
],
@ -556,6 +572,7 @@ class EntityExporter
{
$rows = [];
/** @var AssemblyBOMEntry $bomEntry */
foreach ($assembly->getBomEntries() as $bomEntry) {
// Add the BOM entry itself
$rows[] = [
@ -566,18 +583,21 @@ class EntityExporter
'AssemblyNameHierarchical' => str_repeat('--', $depth) . '> ' . $assembly->getName(),
'AssemblyName' => $assembly->getName(),
'AssemblyFullName' => $this->getFullName($assembly),
'BomQuantity' => $bomEntry->getQuantity() ?? '',
'BomMultiplier' => '',
'BomPartId' => $bomEntry->getPart()?->getId() ?? '-',
'BomPartIpn' => $bomEntry->getPart()?->getIpn() ?? '-',
'BomPartMpnr' => $bomEntry->getPart()?->getManufacturerProductNumber() ?? '-',
'BomPartName' => $bomEntry->getPart()?->getName() ?? '-',
'BomDesignator' => $bomEntry->getName() ?? '-',
'BomPartDescription' => $bomEntry->getPart()?->getDescription() ?? '-',
'BomMountNames' => $bomEntry->getMountNames(),
'BomReferencedAssemblyId' => $bomEntry->getReferencedAssembly()?->getId() ?? '-',
'BomReferencedAssemblyIpn' => $bomEntry->getReferencedAssembly()?->getIpn() ?? '-',
'BomReferencedAssemblyFullName' => $this->getFullName($bomEntry->getReferencedAssembly() ?? null),
//BOM relevant attributes
'Quantity' => $bomEntry->getQuantity() ?? '',
'PartId' => $bomEntry->getPart()?->getId() ?? '-',
'PartName' => $bomEntry->getPart()?->getName() ?? '-',
'Ipn' => $bomEntry->getPart()?->getIpn() ?? '-',
'Manufacturer' => $bomEntry->getPart()?->getManufacturer()?->getName() ?? '-',
'Mpn' => $bomEntry->getPart()?->getManufacturerProductNumber() ?? '-',
'Name' => $bomEntry->getName() ?? '-',
'Designator' => $bomEntry->getDesignator(),
'MountNames' => $bomEntry->getMountNames(),
'Description' => $bomEntry->getPart()?->getDescription() ?? '-',
'ReferencedAssemblyId' => $bomEntry->getReferencedAssembly()?->getId() ?? '-',
'ReferencedAssemblyIpn' => $bomEntry->getReferencedAssembly()?->getIpn() ?? '-',
'ReferencedAssemblyFullName' => $this->getFullName($bomEntry->getReferencedAssembly() ?? null),
];
// If a referenced assembly exists, add aggregated parts
@ -598,18 +618,21 @@ class EntityExporter
'AssemblyNameHierarchical' => '',
'AssemblyName' => $partAssembly ? $partAssembly->getName() : '',
'AssemblyFullName' => $this->getFullName($partAssembly),
'BomQuantity' => $partData['quantity'],
'BomMultiplier' => $partData['multiplier'],
'BomPartId' => $partData['part']?->getId(),
'BomPartIpn' => $partData['part']?->getIpn(),
'BomPartMpnr' => $partData['part']?->getManufacturerProductNumber(),
'BomPartName' => $partData['part']?->getName(),
'BomDesignator' => $partData['part']?->getName(),
'BomPartDescription' => $partData['part']?->getDescription(),
'BomMountNames' => '-',
'BomReferencedAssemblyId' => '-',
'BomReferencedAssemblyIpn' => '-',
'BomReferencedAssemblyFullName' => '-',
//BOM relevant attributes
'Quantity' => $partData['quantity'],
'PartId' => $partData['part']?->getId(),
'PartName' => $partData['part']?->getName(),
'Ipn' => $partData['part']?->getIpn(),
'Manufacturer' => $partData['part']?->getManufacturer()?->getName(),
'Mpn' => $partData['part']?->getManufacturerProductNumber(),
'Name' => $partData['name'] ?? '',
'Designator' => $partData['designator']?->getName(),
'Description' => $partData['part']?->getDescription(),
'MountNames' => '-',
'ReferencedAssemblyId' => '-',
'ReferencedAssemblyIpn' => '-',
'ReferencedAssemblyFullName' => '-',
];
}
}

View file

@ -28,12 +28,20 @@ use Symfony\Contracts\Translation\TranslatorInterface;
enum AssemblyBomTableColumns : string implements TranslatableInterface
{
case NAME = "name";
case ID = "id";
case QUANTITY = "quantity";
case IPN = "ipn";
case DESCRIPTION = "description";
case CATEGORY = "category";
case MANUFACTURER = "manufacturer";
case DESIGNATOR = "designator";
case MOUNTNAMES = "mountnames";
case STORAGE_LOCATION = "storage_location";
case AMOUNT = "amount";
case ADDED_DATE = "addedDate";
case LAST_MODIFIED = "lastModified";
case EDIT = "edit";
public function trans(TranslatorInterface $translator, ?string $locale = null): string
{

View file

@ -97,8 +97,8 @@ class TableSettings
#[Assert\Unique()]
#[Assert\All([new Assert\Type(AssemblyBomTableColumns::class)])]
public array $assembliesBomDefaultColumns = [AssemblyBomTableColumns::QUANTITY, AssemblyTableColumns::ID, AssemblyTableColumns::IPN,
AssemblyTableColumns::NAME, AssemblyTableColumns::DESCRIPTION];
public array $assembliesBomDefaultColumns = [AssemblyBomTableColumns::QUANTITY, AssemblyBomTableColumns::ID,
AssemblyBomTableColumns::IPN, AssemblyBomTableColumns::NAME, AssemblyBomTableColumns::DESCRIPTION];
#[SettingsParameter(label: new TM("settings.behavior.table.preview_image_min_width"),
formOptions: ['attr' => ['min' => 1, 'max' => 100]],