mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-01-12 13:19:33 +00:00
Projekt BOM-Konfiguration um Assemblies bereinigen.
Assembly BOM-Konfiguration um Projektauswahl erweitern (APS-3, APS-4)
This commit is contained in:
parent
f79dc3a102
commit
df2ce45e4c
53 changed files with 738 additions and 1541 deletions
|
|
@ -22,9 +22,9 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\AssemblySystem\Assembly;
|
||||
use App\Entity\Parameters\AbstractParameter;
|
||||
use App\Services\Attachments\AssemblyPreviewGenerator;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Services\Attachments\ProjectPreviewGenerator;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Parts\Category;
|
||||
|
|
@ -151,29 +151,29 @@ class TypeaheadController extends AbstractController
|
|||
'footprint' => $part->getFootprint() instanceof Footprint ? $part->getFootprint()->getName() : '',
|
||||
'description' => mb_strimwidth($part->getDescription(), 0, 127, '...'),
|
||||
'image' => $preview_url,
|
||||
];
|
||||
];
|
||||
}
|
||||
|
||||
return new JsonResponse($data);
|
||||
}
|
||||
|
||||
#[Route(path: '/assemblies/search/{query}', name: 'typeahead_assemblies')]
|
||||
public function assemblies(
|
||||
EntityManagerInterface $entityManager,
|
||||
AssemblyPreviewGenerator $assemblyPreviewGenerator,
|
||||
AttachmentURLGenerator $attachmentURLGenerator,
|
||||
string $query = ""
|
||||
#[Route(path: '/projects/search/{query}', name: 'typeahead_projects')]
|
||||
public function projects(
|
||||
EntityManagerInterface $entityManager,
|
||||
ProjectPreviewGenerator $projectPreviewGenerator,
|
||||
AttachmentURLGenerator $attachmentURLGenerator,
|
||||
string $query = ""
|
||||
): JsonResponse {
|
||||
$this->denyAccessUnlessGranted('@assemblies.read');
|
||||
$this->denyAccessUnlessGranted('@projects.read');
|
||||
|
||||
$result = [];
|
||||
|
||||
$assemblyRepository = $entityManager->getRepository(Assembly::class);
|
||||
$projectRepository = $entityManager->getRepository(Project::class);
|
||||
|
||||
$assemblies = $assemblyRepository->autocompleteSearch($query, 100);
|
||||
$projects = $projectRepository->autocompleteSearch($query, 100);
|
||||
|
||||
foreach ($assemblies as $assembly) {
|
||||
$preview_attachment = $assemblyPreviewGenerator->getTablePreviewAttachment($assembly);
|
||||
foreach ($projects as $project) {
|
||||
$preview_attachment = $projectPreviewGenerator->getTablePreviewAttachment($project);
|
||||
|
||||
if($preview_attachment instanceof Attachment) {
|
||||
$preview_url = $attachmentURLGenerator->getThumbnailURL($preview_attachment, 'thumbnail_sm');
|
||||
|
|
@ -181,13 +181,13 @@ class TypeaheadController extends AbstractController
|
|||
$preview_url = '';
|
||||
}
|
||||
|
||||
/** @var Assembly $assembly */
|
||||
/** @var Project $project */
|
||||
$result[] = [
|
||||
'id' => $assembly->getID(),
|
||||
'name' => $this->translator->trans('typeahead.parts.assembly.name', ['%name%' => $assembly->getName()]),
|
||||
'id' => $project->getID(),
|
||||
'name' => $project->getName(),
|
||||
'category' => '',
|
||||
'footprint' => '',
|
||||
'description' => mb_strimwidth($assembly->getDescription(), 0, 127, '...'),
|
||||
'description' => mb_strimwidth($project->getDescription(), 0, 127, '...'),
|
||||
'image' => $preview_url,
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,11 +25,13 @@ namespace App\DataTables;
|
|||
use App\DataTables\Column\EntityColumn;
|
||||
use App\DataTables\Column\LocaleDateTimeColumn;
|
||||
use App\DataTables\Column\MarkdownColumn;
|
||||
use App\DataTables\Helpers\ProjectDataTableHelper;
|
||||
use App\DataTables\Helpers\ColumnSortHelper;
|
||||
use App\DataTables\Helpers\PartDataTableHelper;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\AssemblySystem\AssemblyBOMEntry;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Services\EntityURLGenerator;
|
||||
use App\Services\Formatters\AmountFormatter;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
|
@ -43,12 +45,13 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
|||
class AssemblyBomEntriesDataTable implements DataTableTypeInterface
|
||||
{
|
||||
public function __construct(
|
||||
protected TranslatorInterface $translator,
|
||||
protected PartDataTableHelper $partDataTableHelper,
|
||||
protected EntityURLGenerator $entityURLGenerator,
|
||||
protected AmountFormatter $amountFormatter,
|
||||
private string $visible_columns,
|
||||
private ColumnSortHelper $csh
|
||||
protected TranslatorInterface $translator,
|
||||
protected PartDataTableHelper $partDataTableHelper,
|
||||
protected ProjectDataTableHelper $projectDataTableHelper,
|
||||
protected EntityURLGenerator $entityURLGenerator,
|
||||
protected AmountFormatter $amountFormatter,
|
||||
private string $visible_columns,
|
||||
private ColumnSortHelper $csh
|
||||
){
|
||||
}
|
||||
|
||||
|
|
@ -86,18 +89,29 @@ class AssemblyBomEntriesDataTable implements DataTableTypeInterface
|
|||
'label' => $this->translator->trans('part.table.name'),
|
||||
'orderField' => 'NATSORT(part.name)',
|
||||
'render' => function ($value, AssemblyBOMEntry $context) {
|
||||
if(!$context->getPart() instanceof Part) {
|
||||
if(!$context->getPart() instanceof Part && !$context->getProject() instanceof Project) {
|
||||
return htmlspecialchars((string) $context->getName());
|
||||
}
|
||||
|
||||
//Part exists if we reach this point
|
||||
if ($context->getPart() !== null) {
|
||||
$tmp = $this->partDataTableHelper->renderName($context->getPart());
|
||||
$tmp = $this->translator->trans('part.table.name.value.for_part', ['%value%' => $tmp]);
|
||||
|
||||
$tmp = $this->partDataTableHelper->renderName($context->getPart());
|
||||
if($context->getName() !== null && $context->getName() !== '') {
|
||||
$tmp .= '<br><b>'.htmlspecialchars($context->getName()).'</b>';
|
||||
if($context->getName() !== null && $context->getName() !== '') {
|
||||
$tmp .= '<br><b>'.htmlspecialchars($context->getName()).'</b>';
|
||||
}
|
||||
} elseif ($context->getProject() !== null) {
|
||||
$tmp = $this->projectDataTableHelper->renderName($context->getProject());
|
||||
$tmp = $this->translator->trans('part.table.name.value.for_project', ['%value%' => $tmp]);
|
||||
|
||||
if($context->getName() !== null && $context->getName() !== '') {
|
||||
$tmp .= '<br><b>'.htmlspecialchars($context->getName()).'</b>';
|
||||
}
|
||||
}
|
||||
|
||||
return $tmp;
|
||||
},
|
||||
|
||||
])
|
||||
->add('ipn', TextColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.ipn'),
|
||||
|
|
|
|||
|
|
@ -23,18 +23,18 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\DataTables\Helpers;
|
||||
|
||||
use App\Entity\AssemblySystem\Assembly;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Services\EntityURLGenerator;
|
||||
|
||||
/**
|
||||
* A helper service which contains common code to render columns for assembly related tables
|
||||
*/
|
||||
class AssemblyDataTableHelper
|
||||
class ProjectDataTableHelper
|
||||
{
|
||||
public function __construct(private readonly EntityURLGenerator $entityURLGenerator) {
|
||||
}
|
||||
|
||||
public function renderName(Assembly $context): string
|
||||
public function renderName(Project $context): string
|
||||
{
|
||||
$icon = '';
|
||||
|
||||
|
|
@ -25,9 +25,7 @@ namespace App\DataTables;
|
|||
use App\DataTables\Column\EntityColumn;
|
||||
use App\DataTables\Column\LocaleDateTimeColumn;
|
||||
use App\DataTables\Column\MarkdownColumn;
|
||||
use App\DataTables\Helpers\AssemblyDataTableHelper;
|
||||
use App\DataTables\Helpers\PartDataTableHelper;
|
||||
use App\Entity\AssemblySystem\Assembly;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\ProjectSystem\ProjectBOMEntry;
|
||||
|
|
@ -46,7 +44,6 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
|
|||
public function __construct(
|
||||
protected TranslatorInterface $translator,
|
||||
protected PartDataTableHelper $partDataTableHelper,
|
||||
protected AssemblyDataTableHelper $assemblyDataTableHelper,
|
||||
protected EntityURLGenerator $entityURLGenerator,
|
||||
protected AmountFormatter $amountFormatter
|
||||
) {
|
||||
|
|
@ -90,26 +87,16 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
|
|||
'label' => $this->translator->trans('part.table.name'),
|
||||
'orderField' => 'NATSORT(part.name)',
|
||||
'render' => function ($value, ProjectBOMEntry $context) {
|
||||
if(!$context->getPart() instanceof Part && !$context->getAssembly() instanceof Assembly) {
|
||||
if(!$context->getPart() instanceof Part) {
|
||||
return htmlspecialchars((string) $context->getName());
|
||||
}
|
||||
|
||||
if ($context->getPart() !== null) {
|
||||
$tmp = $this->partDataTableHelper->renderName($context->getPart());
|
||||
$tmp = $this->translator->trans('part.table.name.value.for_part', ['%value%' => $tmp]);
|
||||
//Part exists if we reach this point
|
||||
|
||||
if($context->getName() !== null && $context->getName() !== '') {
|
||||
$tmp .= '<br><b>'.htmlspecialchars($context->getName()).'</b>';
|
||||
}
|
||||
} elseif ($context->getAssembly() !== null) {
|
||||
$tmp = $this->assemblyDataTableHelper->renderName($context->getAssembly());
|
||||
$tmp = $this->translator->trans('part.table.name.value.for_assembly', ['%value%' => $tmp]);
|
||||
|
||||
if($context->getName() !== null && $context->getName() !== '') {
|
||||
$tmp .= '<br><b>'.htmlspecialchars($context->getName()).'</b>';
|
||||
}
|
||||
$tmp = $this->partDataTableHelper->renderName($context->getPart());
|
||||
if($context->getName() !== null && $context->getName() !== '') {
|
||||
$tmp .= '<br><b>'.htmlspecialchars($context->getName()).'</b>';
|
||||
}
|
||||
|
||||
return $tmp;
|
||||
},
|
||||
])
|
||||
|
|
@ -121,6 +108,8 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
|
|||
if($context->getPart() instanceof Part) {
|
||||
return $context->getPart()->getIpn();
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
])
|
||||
->add('description', MarkdownColumn::class, [
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Entity\AssemblySystem;
|
||||
|
||||
use App\Repository\AssemblyRepository;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
|
||||
use ApiPlatform\Metadata\ApiFilter;
|
||||
|
|
@ -57,7 +56,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
|||
*
|
||||
* @extends AbstractStructuralDBElement<AssemblyAttachment, AssemblyParameter>
|
||||
*/
|
||||
#[ORM\Entity(repositoryClass: AssemblyRepository::class)]
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'assemblies')]
|
||||
#[ApiResource(
|
||||
operations: [
|
||||
|
|
@ -108,6 +107,7 @@ class Assembly extends AbstractStructuralDBElement
|
|||
#[Groups(['extended', 'full', 'import'])]
|
||||
#[ORM\OneToMany(mappedBy: 'assembly', targetEntity: AssemblyBOMEntry::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
#[UniqueObjectCollection(message: 'assembly.bom_entry.part_already_in_bom', fields: ['part'])]
|
||||
#[UniqueObjectCollection(message: 'assembly.bom_entry.project_already_in_bom', fields: ['project'])]
|
||||
#[UniqueObjectCollection(message: 'assembly.bom_entry.name_already_in_bom', fields: ['name'])]
|
||||
protected Collection $bom_entries;
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ use ApiPlatform\OpenApi\Model\Operation;
|
|||
use ApiPlatform\Serializer\Filter\PropertyFilter;
|
||||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\Entity\Contracts\TimeStampableInterface;
|
||||
use App\Entity\AssemblySystem\Assembly;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Repository\DBElementRepository;
|
||||
use App\Validator\UniqueValidatableInterface;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
|
|
@ -133,6 +133,18 @@ class AssemblyBOMEntry extends AbstractDBElement implements UniqueValidatableInt
|
|||
#[Groups(['bom_entry:read', 'bom_entry:write', 'full'])]
|
||||
protected ?Part $part = null;
|
||||
|
||||
/**
|
||||
* @var Project|null The associated project
|
||||
*/
|
||||
#[Assert\Expression(
|
||||
'(this.getPart() === null or this.getProject() === null) and (this.getName() === null or (this.getName() != null and this.getName() != ""))',
|
||||
message: 'validator.project.bom_entry.only_part_or_assembly_allowed'
|
||||
)]
|
||||
#[ORM\ManyToOne(targetEntity: Project::class)]
|
||||
#[ORM\JoinColumn(name: 'id_project', nullable: true)]
|
||||
#[Groups(['bom_entry:read', 'bom_entry:write', ])]
|
||||
protected ?Project $project = null;
|
||||
|
||||
/**
|
||||
* @var BigDecimal|null The price of this non-part BOM entry
|
||||
*/
|
||||
|
|
@ -225,6 +237,17 @@ class AssemblyBOMEntry extends AbstractDBElement implements UniqueValidatableInt
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function getProject(): ?Project
|
||||
{
|
||||
return $this->project;
|
||||
}
|
||||
|
||||
public function setProject(?Project $project): AssemblyBOMEntry
|
||||
{
|
||||
$this->project = $project;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the price of this BOM entry, if existing.
|
||||
* Prices are only valid on non-Part BOM entries.
|
||||
|
|
@ -297,6 +320,7 @@ class AssemblyBOMEntry extends AbstractDBElement implements UniqueValidatableInt
|
|||
return [
|
||||
'name' => $this->getName(),
|
||||
'part' => $this->getPart()?->getID(),
|
||||
'project' => $this->getProject()?->getID(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,7 +108,6 @@ class Project extends AbstractStructuralDBElement
|
|||
#[Groups(['extended', 'full', 'import'])]
|
||||
#[ORM\OneToMany(mappedBy: 'project', targetEntity: ProjectBOMEntry::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
#[UniqueObjectCollection(message: 'project.bom_entry.part_already_in_bom', fields: ['part'])]
|
||||
#[UniqueObjectCollection(message: 'project.bom_entry.assembly_already_in_bom', fields: ['assembly'])]
|
||||
#[UniqueObjectCollection(message: 'project.bom_entry.name_already_in_bom', fields: ['name'])]
|
||||
protected Collection $bom_entries;
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ use ApiPlatform\Metadata\Post;
|
|||
use ApiPlatform\OpenApi\Model\Operation;
|
||||
use ApiPlatform\Serializer\Filter\PropertyFilter;
|
||||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\Entity\AssemblySystem\Assembly;
|
||||
use App\Entity\Contracts\TimeStampableInterface;
|
||||
use App\Validator\UniqueValidatableInterface;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
|
|
@ -104,10 +103,7 @@ class ProjectBOMEntry extends AbstractDBElement implements UniqueValidatableInte
|
|||
/**
|
||||
* @var string|null An optional name describing this BOM entry (useful for non-part entries)
|
||||
*/
|
||||
#[Assert\Expression(
|
||||
'this.getPart() !== null or this.getAssembly() !== null or (this.getName() !== null and this.getName() != "")',
|
||||
message: 'validator.project.bom_entry.part_or_assembly_needed'
|
||||
)]
|
||||
#[Assert\Expression('this.getPart() !== null or this.getName() !== null', message: 'validator.project.bom_entry.name_or_part_needed')]
|
||||
#[ORM\Column(type: Types::STRING, nullable: true)]
|
||||
#[Groups(['bom_entry:read', 'bom_entry:write', 'import', 'simple', 'extended', 'full'])]
|
||||
protected ?string $name = null;
|
||||
|
|
@ -135,18 +131,6 @@ class ProjectBOMEntry extends AbstractDBElement implements UniqueValidatableInte
|
|||
#[Groups(['bom_entry:read', 'bom_entry:write', 'full'])]
|
||||
protected ?Part $part = null;
|
||||
|
||||
/**
|
||||
* @var Assembly|null The associated assembly
|
||||
*/
|
||||
#[Assert\Expression(
|
||||
'(this.getPart() === null or this.getAssembly() === null) and (this.getName() === null or (this.getName() != null and this.getName() != ""))',
|
||||
message: 'validator.project.bom_entry.only_part_or_assembly_allowed'
|
||||
)]
|
||||
#[ORM\ManyToOne(targetEntity: Assembly::class, inversedBy: 'assembly_bom_entries')]
|
||||
#[ORM\JoinColumn(name: 'id_assembly')]
|
||||
#[Groups(['bom_entry:read', 'bom_entry:write', ])]
|
||||
protected ?Assembly $assembly = null;
|
||||
|
||||
/**
|
||||
* @var BigDecimal|null The price of this non-part BOM entry
|
||||
*/
|
||||
|
|
@ -239,16 +223,6 @@ class ProjectBOMEntry extends AbstractDBElement implements UniqueValidatableInte
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function getAssembly(): ?Assembly
|
||||
{
|
||||
return $this->assembly;
|
||||
}
|
||||
|
||||
public function setAssembly(?Assembly $assembly): void
|
||||
{
|
||||
$this->assembly = $assembly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the price of this BOM entry, if existing.
|
||||
* Prices are only valid on non-Part BOM entries.
|
||||
|
|
@ -286,15 +260,6 @@ class ProjectBOMEntry extends AbstractDBElement implements UniqueValidatableInte
|
|||
return $this->part instanceof Part;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this BOM entry is a assembly associated BOM entry or not.
|
||||
* @return bool True if this BOM entry is a assembly associated BOM entry, false otherwise.
|
||||
*/
|
||||
public function isAssemblyBomEntry(): bool
|
||||
{
|
||||
return $this->assembly instanceof Assembly;
|
||||
}
|
||||
|
||||
#[Assert\Callback]
|
||||
public function validate(ExecutionContextInterface $context, $payload): void
|
||||
{
|
||||
|
|
@ -356,7 +321,6 @@ class ProjectBOMEntry extends AbstractDBElement implements UniqueValidatableInte
|
|||
return [
|
||||
'name' => $this->getName(),
|
||||
'part' => $this->getPart()?->getID(),
|
||||
'assembly' => $this->getAssembly()?->getID(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use App\Entity\AssemblySystem\AssemblyBOMEntry;
|
|||
use App\Form\Type\BigDecimalNumberType;
|
||||
use App\Form\Type\CurrencyEntityType;
|
||||
use App\Form\Type\PartSelectType;
|
||||
use App\Form\Type\ProjectSelectType;
|
||||
use App\Form\Type\RichTextEditorType;
|
||||
use App\Form\Type\SIUnitType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
|
|
@ -34,11 +35,13 @@ class AssemblyBOMEntryType extends AbstractType
|
|||
});
|
||||
|
||||
$builder
|
||||
|
||||
->add('part', PartSelectType::class, [
|
||||
'required' => false,
|
||||
])
|
||||
|
||||
->add('project', ProjectSelectType::class, [
|
||||
'label' => 'assembly.bom.project',
|
||||
'required' => false,
|
||||
])
|
||||
->add('name', TextType::class, [
|
||||
'label' => 'assembly.bom.name',
|
||||
'required' => false,
|
||||
|
|
@ -75,10 +78,8 @@ class AssemblyBOMEntryType extends AbstractType
|
|||
'required' => false,
|
||||
'label' => false,
|
||||
'short' => true,
|
||||
])
|
||||
|
||||
;
|
||||
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
|
|
|
|||
|
|
@ -78,35 +78,34 @@ class AssemblyBuildType extends AbstractType implements DataMapperInterface
|
|||
'required' => false,
|
||||
]);
|
||||
|
||||
|
||||
//The form is initially empty, we have to define the fields after we know the data
|
||||
//The form is initially empty, define the fields after we know the data
|
||||
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (PreSetDataEvent $event) {
|
||||
$form = $event->getForm();
|
||||
/** @var AssemblyBuildRequest $build_request */
|
||||
$build_request = $event->getData();
|
||||
/** @var AssemblyBuildRequest $assemblyBuildRequest */
|
||||
$assemblyBuildRequest = $event->getData();
|
||||
|
||||
$form->add('addBuildsToBuildsPart', CheckboxType::class, [
|
||||
'label' => 'assembly.build.add_builds_to_builds_part',
|
||||
'required' => false,
|
||||
'disabled' => !$build_request->getAssembly()->getBuildPart() instanceof Part,
|
||||
'disabled' => !$assemblyBuildRequest->getAssembly()->getBuildPart() instanceof Part,
|
||||
]);
|
||||
|
||||
if ($build_request->getAssembly()->getBuildPart() instanceof Part) {
|
||||
if ($assemblyBuildRequest->getAssembly()->getBuildPart() instanceof Part) {
|
||||
$form->add('buildsPartLot', PartLotSelectType::class, [
|
||||
'label' => 'assembly.build.builds_part_lot',
|
||||
'required' => false,
|
||||
'part' => $build_request->getAssembly()->getBuildPart(),
|
||||
'part' => $assemblyBuildRequest->getAssembly()->getBuildPart(),
|
||||
'placeholder' => 'assembly.build.buildsPartLot.new_lot'
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($build_request->getPartBomEntries() as $bomEntry) {
|
||||
foreach ($assemblyBuildRequest->getPartBomEntries() as $bomEntry) {
|
||||
//Every part lot has a field to specify the number of parts to take from this lot
|
||||
foreach ($build_request->getPartLotsForBOMEntry($bomEntry) as $lot) {
|
||||
foreach ($assemblyBuildRequest->getPartLotsForBOMEntry($bomEntry) as $lot) {
|
||||
$form->add('lot_' . $lot->getID(), SIUnitType::class, [
|
||||
'label' => false,
|
||||
'measurement_unit' => $bomEntry->getPart()->getPartUnit(),
|
||||
'max' => min($build_request->getNeededAmountForBOMEntry($bomEntry), $lot->getAmount()),
|
||||
'max' => min($assemblyBuildRequest->getNeededAmountForBOMEntry($bomEntry), $lot->getAmount()),
|
||||
'disabled' => !$this->security->isGranted('withdraw', $lot),
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,6 @@ class ProjectAddPartsType extends AbstractType
|
|||
],
|
||||
'constraints' => [
|
||||
new UniqueObjectCollection(message: 'project.bom_entry.part_already_in_bom', fields: ['part']),
|
||||
new UniqueObjectCollection(message: 'project.bom_entry.assembly_already_in_bom', fields: ['assembly']),
|
||||
new UniqueObjectCollection(message: 'project.bom_entry.name_already_in_bom', fields: ['name']),
|
||||
]
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ declare(strict_types=1);
|
|||
namespace App\Form\ProjectSystem;
|
||||
|
||||
use App\Entity\ProjectSystem\ProjectBOMEntry;
|
||||
use App\Form\Type\AssemblySelectType;
|
||||
use App\Form\Type\BigDecimalNumberType;
|
||||
use App\Form\Type\CurrencyEntityType;
|
||||
use App\Form\Type\PartSelectType;
|
||||
|
|
@ -39,10 +38,6 @@ class ProjectBOMEntryType extends AbstractType
|
|||
'label' => 'project.bom.part',
|
||||
'required' => false,
|
||||
])
|
||||
->add('assembly', AssemblySelectType::class, [
|
||||
'label' => 'project.bom.assembly',
|
||||
'required' => false,
|
||||
])
|
||||
->add('name', TextType::class, [
|
||||
'label' => 'project.bom.name',
|
||||
'required' => false,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ declare(strict_types=1);
|
|||
*/
|
||||
namespace App\Form\ProjectSystem;
|
||||
|
||||
use App\Helpers\Assemblies\AssemblyBuildRequest;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\Parts\PartLot;
|
||||
|
|
@ -39,11 +38,10 @@ use Symfony\Component\Form\FormBuilderInterface;
|
|||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class ProjectBuildType extends AbstractType implements DataMapperInterface
|
||||
{
|
||||
public function __construct(private readonly Security $security, private readonly TranslatorInterface $translator)
|
||||
public function __construct(private readonly Security $security)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -113,25 +111,6 @@ class ProjectBuildType extends AbstractType implements DataMapperInterface
|
|||
]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($projectBuildRequest->getAssemblyBomEntries() as $bomEntry) {
|
||||
$assemblyBuildRequest = new AssemblyBuildRequest($bomEntry->getAssembly(), $projectBuildRequest->getNumberOfBuilds());
|
||||
|
||||
//Add fields for assembly bom entries
|
||||
foreach ($assemblyBuildRequest->getPartBomEntries() as $partBomEntry) {
|
||||
foreach ($assemblyBuildRequest->getPartLotsForBOMEntry($partBomEntry) as $lot) {
|
||||
$form->add('lot_' . $lot->getID(), SIUnitType::class, [
|
||||
'label' => $this->translator->trans('project.build.builds_part_lot_label', [
|
||||
'%name%' => $partBomEntry->getPart()->getName(),
|
||||
'%quantity%' => $partBomEntry->getQuantity() * $projectBuildRequest->getNumberOfBuilds()
|
||||
]),
|
||||
'measurement_unit' => $partBomEntry->getPart()->getPartUnit(),
|
||||
'max' => min($assemblyBuildRequest->getNeededAmountForBOMEntry($partBomEntry), $lot->getAmount()),
|
||||
'disabled' => !$this->security->isGranted('withdraw', $lot),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Form\Type;
|
||||
|
||||
use App\Entity\AssemblySystem\Assembly;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Services\Attachments\AssemblyPreviewGenerator;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Services\Attachments\ProjectPreviewGenerator;
|
||||
use App\Services\Attachments\AttachmentURLGenerator;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
|
|
@ -20,9 +20,9 @@ use Symfony\Component\Form\FormEvents;
|
|||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
class AssemblySelectType extends AbstractType implements DataMapperInterface
|
||||
class ProjectSelectType extends AbstractType implements DataMapperInterface
|
||||
{
|
||||
public function __construct(private readonly UrlGeneratorInterface $urlGenerator, private readonly EntityManagerInterface $em, private readonly AssemblyPreviewGenerator $previewGenerator, private readonly AttachmentURLGenerator $attachmentURLGenerator)
|
||||
public function __construct(private readonly UrlGeneratorInterface $urlGenerator, private readonly EntityManagerInterface $em, private readonly ProjectPreviewGenerator $previewGenerator, private readonly AttachmentURLGenerator $attachmentURLGenerator)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -69,28 +69,28 @@ class AssemblySelectType extends AbstractType implements DataMapperInterface
|
|||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'class' => Assembly::class,
|
||||
'class' => Project::class,
|
||||
'choice_label' => 'name',
|
||||
'compound' => true,
|
||||
'error_bubbling' => false,
|
||||
]);
|
||||
|
||||
error_log($this->urlGenerator->generate('typeahead_assemblies', ['query' => '__QUERY__']));
|
||||
error_log($this->urlGenerator->generate('typeahead_projects', ['query' => '__QUERY__']));
|
||||
|
||||
$resolver->setDefaults([
|
||||
'attr' => [
|
||||
'data-controller' => 'elements--assembly-select',
|
||||
'data-autocomplete' => $this->urlGenerator->generate('typeahead_assemblies', ['query' => '__QUERY__']),
|
||||
'data-controller' => 'elements--project-select',
|
||||
'data-autocomplete' => $this->urlGenerator->generate('typeahead_projects', ['query' => '__QUERY__']),
|
||||
'autocomplete' => 'off',
|
||||
],
|
||||
]);
|
||||
|
||||
$resolver->setDefaults([
|
||||
//Prefill the selected choice with the needed data, so the user can see it without an additional Ajax request
|
||||
'choice_attr' => ChoiceList::attr($this, function (?Assembly $assembly) {
|
||||
if($assembly instanceof Assembly) {
|
||||
'choice_attr' => ChoiceList::attr($this, function (?Project $project) {
|
||||
if($project instanceof Project) {
|
||||
//Determine the picture to show:
|
||||
$preview_attachment = $this->previewGenerator->getTablePreviewAttachment($assembly);
|
||||
$preview_attachment = $this->previewGenerator->getTablePreviewAttachment($project);
|
||||
if ($preview_attachment instanceof Attachment) {
|
||||
$preview_url = $this->attachmentURLGenerator->getThumbnailURL($preview_attachment,
|
||||
'thumbnail_sm');
|
||||
|
|
@ -99,8 +99,8 @@ class AssemblySelectType extends AbstractType implements DataMapperInterface
|
|||
}
|
||||
}
|
||||
|
||||
return $assembly instanceof Assembly ? [
|
||||
'data-description' => $assembly->getDescription() ? mb_strimwidth($assembly->getDescription(), 0, 127, '...') : '',
|
||||
return $project instanceof Project ? [
|
||||
'data-description' => $project->getDescription() ? mb_strimwidth($project->getDescription(), 0, 127, '...') : '',
|
||||
'data-category' => '',
|
||||
'data-footprint' => '',
|
||||
'data-image' => $preview_url,
|
||||
|
|
@ -22,13 +22,10 @@ declare(strict_types=1);
|
|||
*/
|
||||
namespace App\Helpers\Projects;
|
||||
|
||||
use App\Entity\AssemblySystem\Assembly;
|
||||
use App\Entity\AssemblySystem\AssemblyBOMEntry;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\Parts\PartLot;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Entity\ProjectSystem\ProjectBOMEntry;
|
||||
use App\Helpers\Assemblies\AssemblyBuildRequest;
|
||||
use App\Validator\Constraints\ProjectSystem\ValidProjectBuildRequest;
|
||||
|
||||
/**
|
||||
|
|
@ -82,7 +79,7 @@ final class ProjectBuildRequest
|
|||
//Completely reset the array
|
||||
$this->withdraw_amounts = [];
|
||||
|
||||
//Now create an array for each part BOM entry
|
||||
//Now create an array for each BOM entry
|
||||
foreach ($this->getPartBomEntries() as $bom_entry) {
|
||||
$remaining_amount = $this->getNeededAmountForBOMEntry($bom_entry);
|
||||
foreach($this->getPartLotsForBOMEntry($bom_entry) as $lot) {
|
||||
|
|
@ -91,21 +88,6 @@ final class ProjectBuildRequest
|
|||
$remaining_amount -= max(0, $this->withdraw_amounts[$lot->getID()]);
|
||||
}
|
||||
}
|
||||
|
||||
//Now create an array for each assembly BOM entry
|
||||
foreach ($this->getAssemblyBomEntries() as $assemblyBomEntry) {
|
||||
$assemblyBuildRequest = new AssemblyBuildRequest($assemblyBomEntry->getAssembly(), $this->number_of_builds);
|
||||
|
||||
//Add fields for assembly bom entries
|
||||
foreach ($assemblyBuildRequest->getPartBomEntries() as $partBomEntry) {
|
||||
$remaining_amount = $assemblyBuildRequest->getNeededAmountForBOMEntry($partBomEntry) * $assemblyBomEntry->getQuantity();
|
||||
|
||||
foreach ($assemblyBuildRequest->getPartLotsForBOMEntry($partBomEntry) as $lot) {
|
||||
$this->withdraw_amounts[$lot->getID()] = min($remaining_amount, $lot->getAmount());
|
||||
$remaining_amount -= max(0, $this->withdraw_amounts[$lot->getID()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -248,77 +230,12 @@ final class ProjectBuildRequest
|
|||
{
|
||||
$this->ensureBOMEntryValid($projectBOMEntry);
|
||||
|
||||
if (!$projectBOMEntry->getPart() instanceof Part && !$projectBOMEntry->getAssembly() instanceof Assembly) {
|
||||
if (!$projectBOMEntry->getPart() instanceof Part) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//Filter out all lots which have unknown instock
|
||||
if ($projectBOMEntry->getPart() instanceof Part) {
|
||||
return $projectBOMEntry->getPart()->getPartLots()->filter(fn (PartLot $lot) => !$lot->isInstockUnknown())->toArray();
|
||||
} elseif ($projectBOMEntry->getAssembly() instanceof Assembly) {
|
||||
$assemblyBuildRequest = new AssemblyBuildRequest($projectBOMEntry->getAssembly(), $this->number_of_builds);
|
||||
|
||||
//Add fields for assembly bom entries
|
||||
$result = [];
|
||||
foreach ($assemblyBuildRequest->getPartBomEntries() as $assemblyBOMEntry) {
|
||||
$tmp = $assemblyBOMEntry->getPart()->getPartLots()->filter(fn (PartLot $lot) => !$lot->isInstockUnknown())->toArray();
|
||||
$result = array_merge($result, $tmp);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all available assembly BOM-entries with no part assigned.
|
||||
* @return AssemblyBOMEntry[]|null Returns null if no entries found
|
||||
*/
|
||||
public function getAssemblyBomEntriesWithoutPart(ProjectBOMEntry $projectBOMEntry): ?array
|
||||
{
|
||||
$this->ensureBOMEntryValid($projectBOMEntry);
|
||||
|
||||
if (!$projectBOMEntry->getAssembly() instanceof Assembly) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$assemblyBuildRequest = new AssemblyBuildRequest($projectBOMEntry->getAssembly(), $this->number_of_builds);
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($assemblyBuildRequest->getBomEntries() as $assemblyBOMEntry) {
|
||||
if ($assemblyBOMEntry->getPart() === null) {
|
||||
$result[] = $assemblyBOMEntry;
|
||||
}
|
||||
}
|
||||
|
||||
return count($result) > 0 ? $result : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all available assembly BOM-entries with no part assigned.
|
||||
* @return AssemblyBOMEntry[]|null Returns null if no entries found
|
||||
*/
|
||||
public function getAssemblyBomEntriesWithPartNoStock(ProjectBOMEntry $projectBOMEntry): ?array
|
||||
{
|
||||
$this->ensureBOMEntryValid($projectBOMEntry);
|
||||
|
||||
if (!$projectBOMEntry->getAssembly() instanceof Assembly) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$assemblyBuildRequest = new AssemblyBuildRequest($projectBOMEntry->getAssembly(), $this->number_of_builds);
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($assemblyBuildRequest->getBomEntries() as $assemblyBOMEntry) {
|
||||
if ($assemblyBOMEntry->getPart() instanceof Part && $assemblyBOMEntry->getPart()->getPartLots()->filter(fn (PartLot $lot) => !$lot->isInstockUnknown())->count() === 0) {
|
||||
$result[] = $assemblyBOMEntry;
|
||||
}
|
||||
}
|
||||
|
||||
return count($result) > 0 ? $result : null;
|
||||
return $projectBOMEntry->getPart()->getPartLots()->filter(fn (PartLot $lot) => !$lot->isInstockUnknown())->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -349,15 +266,6 @@ final class ProjectBuildRequest
|
|||
return $this->project->getBomEntries()->filter(fn(ProjectBOMEntry $entry) => $entry->isPartBomEntry())->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the all assembly bom entries that have to be built.
|
||||
* @return ProjectBOMEntry[]
|
||||
*/
|
||||
public function getAssemblyBomEntries(): array
|
||||
{
|
||||
return $this->project->getBomEntries()->filter(fn(ProjectBOMEntry $entry) => $entry->isAssemblyBomEntry())->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns which project should be build
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,69 +0,0 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\AssemblySystem\Assembly;
|
||||
|
||||
/**
|
||||
* @template TEntityClass of Assembly
|
||||
* @extends DBElementRepository<TEntityClass>
|
||||
*/
|
||||
class AssemblyRepository extends StructuralDBElementRepository
|
||||
{
|
||||
/**
|
||||
* @return Assembly[]
|
||||
*/
|
||||
public function autocompleteSearch(string $query, int $max_limits = 50): array
|
||||
{
|
||||
$qb = $this->createQueryBuilder('assembly');
|
||||
$qb->select('assembly')
|
||||
->where('ILIKE(assembly.name, :query) = TRUE')
|
||||
->orWhere('ILIKE(assembly.description, :query) = TRUE');
|
||||
|
||||
$qb->setParameter('query', '%'.$query.'%');
|
||||
|
||||
$qb->setMaxResults($max_limits);
|
||||
$qb->orderBy('NATSORT(assembly.name)', 'ASC');
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
}
|
||||
|
|
@ -51,4 +51,22 @@ class DeviceRepository extends StructuralDBElementRepository
|
|||
//Prevent user from deleting devices, to not accidentally remove filled devices from old versions
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Project[]
|
||||
*/
|
||||
public function autocompleteSearch(string $query, int $max_limits = 50): array
|
||||
{
|
||||
$qb = $this->createQueryBuilder('project');
|
||||
$qb->select('project')
|
||||
->where('ILIKE(project.name, :query) = TRUE')
|
||||
->orWhere('ILIKE(project.description, :query) = TRUE');
|
||||
|
||||
$qb->setParameter('query', '%'.$query.'%');
|
||||
|
||||
$qb->setMaxResults($max_limits);
|
||||
$qb->orderBy('NATSORT(project.name)', 'ASC');
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,14 +27,17 @@ use App\Entity\AssemblySystem\AssemblyBOMEntry;
|
|||
use App\Entity\Parts\Part;
|
||||
use App\Helpers\Assemblies\AssemblyBuildRequest;
|
||||
use App\Services\Parts\PartLotWithdrawAddHelper;
|
||||
use App\Services\ProjectSystem\ProjectBuildHelper;
|
||||
|
||||
/**
|
||||
* @see \App\Tests\Services\AssemblySystem\AssemblyBuildHelperTest
|
||||
*/
|
||||
class AssemblyBuildHelper
|
||||
{
|
||||
public function __construct(private readonly PartLotWithdrawAddHelper $withdraw_add_helper)
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PartLotWithdrawAddHelper $withdraw_add_helper,
|
||||
private readonly ProjectBuildHelper $projectBuildHelper
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -66,12 +69,16 @@ class AssemblyBuildHelper
|
|||
$maximum_buildable_count = PHP_INT_MAX;
|
||||
foreach ($assembly->getBomEntries() as $bom_entry) {
|
||||
//Skip BOM entries without a part (as we can not determine that)
|
||||
if (!$bom_entry->isPartBomEntry()) {
|
||||
if (!$bom_entry->isPartBomEntry() && $bom_entry->getProject() === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//The maximum buildable count for the whole assembly is the minimum of all BOM entries
|
||||
$maximum_buildable_count = min($maximum_buildable_count, $this->getMaximumBuildableCountForBOMEntry($bom_entry));
|
||||
//The maximum buildable count for the whole project is the minimum of all BOM entries
|
||||
if ($bom_entry->getPart() !== null) {
|
||||
$maximum_buildable_count = min($maximum_buildable_count, $this->getMaximumBuildableCountForBOMEntry($bom_entry));
|
||||
} elseif ($bom_entry->getProject() !== null) {
|
||||
$maximum_buildable_count = min($maximum_buildable_count, $this->projectBuildHelper->getMaximumBuildableCount($bom_entry->getProject()));
|
||||
}
|
||||
}
|
||||
|
||||
return $maximum_buildable_count;
|
||||
|
|
@ -97,7 +104,7 @@ class AssemblyBuildHelper
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the assembly BOM entries for which parts are missing in the stock for the given number of builds
|
||||
* Returns the project BOM entries for which parts are missing in the stock for the given number of builds
|
||||
* @param Assembly $assembly The assembly for which the BOM entries should be checked
|
||||
* @param int $number_of_builds How often should the assembly be build?
|
||||
* @return AssemblyBOMEntry[]
|
||||
|
|
@ -108,24 +115,29 @@ class AssemblyBuildHelper
|
|||
throw new \InvalidArgumentException('The number of builds must be greater than 0!');
|
||||
}
|
||||
|
||||
$non_buildable_entries = [];
|
||||
$nonBuildableEntries = [];
|
||||
|
||||
foreach ($assembly->getBomEntries() as $bomEntry) {
|
||||
$part = $bomEntry->getPart();
|
||||
|
||||
//Skip BOM entries without a part (as we can not determine that)
|
||||
if (!$part instanceof Part) {
|
||||
if (!$part instanceof Part && $bomEntry->getAssembly() === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$amount_sum = $part->getAmountSum();
|
||||
if ($bomEntry->getPart() !== null) {
|
||||
$amount_sum = $part->getAmountSum();
|
||||
|
||||
if ($amount_sum < $bomEntry->getQuantity() * $number_of_builds) {
|
||||
$non_buildable_entries[] = $bomEntry;
|
||||
if ($amount_sum < $bomEntry->getQuantity() * $number_of_builds) {
|
||||
$nonBuildableEntries[] = $bomEntry;
|
||||
}
|
||||
} elseif ($bomEntry->getAssembly() !== null) {
|
||||
$nonBuildableAssemblyEntries = $this->projectBuildHelper->getNonBuildableProjectBomEntries($bomEntry->getProject(), $number_of_builds);
|
||||
$nonBuildableEntries = array_merge($nonBuildableEntries, $nonBuildableAssemblyEntries);
|
||||
}
|
||||
}
|
||||
|
||||
return $non_buildable_entries;
|
||||
return $nonBuildableEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -22,38 +22,38 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Services\Attachments;
|
||||
|
||||
use App\Entity\AssemblySystem\Assembly;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
|
||||
class AssemblyPreviewGenerator
|
||||
class ProjectPreviewGenerator
|
||||
{
|
||||
public function __construct(protected AttachmentManager $attachmentHelper)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of attachments that can be used for previewing the assembly ordered by priority.
|
||||
* Returns a list of attachments that can be used for previewing the project ordered by priority.
|
||||
*
|
||||
* @param Assembly $assembly the assembly for which the attachments should be determined
|
||||
* @param Project $project the project for which the attachments should be determined
|
||||
*
|
||||
* @return (Attachment|null)[]
|
||||
*
|
||||
* @psalm-return list<Attachment|null>
|
||||
*/
|
||||
public function getPreviewAttachments(Assembly $assembly): array
|
||||
public function getPreviewAttachments(Project $project): array
|
||||
{
|
||||
$list = [];
|
||||
|
||||
//Master attachment has top priority
|
||||
$attachment = $assembly->getMasterPictureAttachment();
|
||||
$attachment = $project->getMasterPictureAttachment();
|
||||
if ($this->isAttachmentValidPicture($attachment)) {
|
||||
$list[] = $attachment;
|
||||
}
|
||||
|
||||
//Then comes the other images of the assembly
|
||||
foreach ($assembly->getAttachments() as $attachment) {
|
||||
//Then comes the other images of the project
|
||||
foreach ($project->getAttachments() as $attachment) {
|
||||
//Dont show the master attachment twice
|
||||
if ($this->isAttachmentValidPicture($attachment) && $attachment !== $assembly->getMasterPictureAttachment()) {
|
||||
if ($this->isAttachmentValidPicture($attachment) && $attachment !== $project->getMasterPictureAttachment()) {
|
||||
$list[] = $attachment;
|
||||
}
|
||||
}
|
||||
|
|
@ -62,14 +62,14 @@ class AssemblyPreviewGenerator
|
|||
}
|
||||
|
||||
/**
|
||||
* Determines what attachment should be used for previewing a assembly (especially in assembly table).
|
||||
* Determines what attachment should be used for previewing a project (especially in project table).
|
||||
* The returned attachment is guaranteed to be existing and be a picture.
|
||||
*
|
||||
* @param Assembly $assembly The assembly for which the attachment should be determined
|
||||
* @param Project $project The project for which the attachment should be determined
|
||||
*/
|
||||
public function getTablePreviewAttachment(Assembly $assembly): ?Attachment
|
||||
public function getTablePreviewAttachment(Project $project): ?Attachment
|
||||
{
|
||||
$attachment = $assembly->getMasterPictureAttachment();
|
||||
$attachment = $project->getMasterPictureAttachment();
|
||||
if ($this->isAttachmentValidPicture($attachment)) {
|
||||
return $attachment;
|
||||
}
|
||||
|
|
@ -22,13 +22,10 @@ declare(strict_types=1);
|
|||
*/
|
||||
namespace App\Services\ProjectSystem;
|
||||
|
||||
use App\Entity\AssemblySystem\AssemblyBOMEntry;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Entity\ProjectSystem\ProjectBOMEntry;
|
||||
use App\Helpers\Assemblies\AssemblyBuildRequest;
|
||||
use App\Helpers\Projects\ProjectBuildRequest;
|
||||
use App\Services\AssemblySystem\AssemblyBuildHelper;
|
||||
use App\Services\Parts\PartLotWithdrawAddHelper;
|
||||
|
||||
/**
|
||||
|
|
@ -36,10 +33,8 @@ use App\Services\Parts\PartLotWithdrawAddHelper;
|
|||
*/
|
||||
class ProjectBuildHelper
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PartLotWithdrawAddHelper $withdrawAddHelper,
|
||||
private readonly AssemblyBuildHelper $assemblyBuildHelper
|
||||
) {
|
||||
public function __construct(private readonly PartLotWithdrawAddHelper $withdraw_add_helper)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -71,16 +66,12 @@ class ProjectBuildHelper
|
|||
$maximum_buildable_count = PHP_INT_MAX;
|
||||
foreach ($project->getBomEntries() as $bom_entry) {
|
||||
//Skip BOM entries without a part (as we can not determine that)
|
||||
if (!$bom_entry->isPartBomEntry() && $bom_entry->getAssembly() === null) {
|
||||
if (!$bom_entry->isPartBomEntry()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//The maximum buildable count for the whole project is the minimum of all BOM entries
|
||||
if ($bom_entry->getPart() !== null) {
|
||||
$maximum_buildable_count = min($maximum_buildable_count, $this->getMaximumBuildableCountForBOMEntry($bom_entry));
|
||||
} elseif ($bom_entry->getAssembly() !== null) {
|
||||
$maximum_buildable_count = min($maximum_buildable_count, $this->assemblyBuildHelper->getMaximumBuildableCount($bom_entry->getAssembly()));
|
||||
}
|
||||
$maximum_buildable_count = min($maximum_buildable_count, $this->getMaximumBuildableCountForBOMEntry($bom_entry));
|
||||
}
|
||||
|
||||
return $maximum_buildable_count;
|
||||
|
|
@ -106,10 +97,10 @@ class ProjectBuildHelper
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the project or assembly BOM entries for which parts are missing in the stock for the given number of builds
|
||||
* Returns the project BOM entries for which parts are missing in the stock for the given number of builds
|
||||
* @param Project $project The project for which the BOM entries should be checked
|
||||
* @param int $number_of_builds How often should the project be build?
|
||||
* @return ProjectBOMEntry[]|AssemblyBOMEntry[]
|
||||
* @return ProjectBOMEntry[]
|
||||
*/
|
||||
public function getNonBuildableProjectBomEntries(Project $project, int $number_of_builds = 1): array
|
||||
{
|
||||
|
|
@ -117,29 +108,24 @@ class ProjectBuildHelper
|
|||
throw new \InvalidArgumentException('The number of builds must be greater than 0!');
|
||||
}
|
||||
|
||||
$nonBuildableEntries = [];
|
||||
$non_buildable_entries = [];
|
||||
|
||||
foreach ($project->getBomEntries() as $bomEntry) {
|
||||
$part = $bomEntry->getPart();
|
||||
|
||||
//Skip BOM entries without a part (as we can not determine that)
|
||||
if (!$part instanceof Part && $bomEntry->getAssembly() === null) {
|
||||
if (!$part instanceof Part) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($bomEntry->getPart() !== null) {
|
||||
$amount_sum = $part->getAmountSum();
|
||||
$amount_sum = $part->getAmountSum();
|
||||
|
||||
if ($amount_sum < $bomEntry->getQuantity() * $number_of_builds) {
|
||||
$nonBuildableEntries[] = $bomEntry;
|
||||
}
|
||||
} elseif ($bomEntry->getAssembly() !== null) {
|
||||
$nonBuildableAssemblyEntries = $this->assemblyBuildHelper->getNonBuildableAssemblyBomEntries($bomEntry->getAssembly(), $number_of_builds);
|
||||
$nonBuildableEntries = array_merge($nonBuildableEntries, $nonBuildableAssemblyEntries);
|
||||
if ($amount_sum < $bomEntry->getQuantity() * $number_of_builds) {
|
||||
$non_buildable_entries[] = $bomEntry;
|
||||
}
|
||||
}
|
||||
|
||||
return $nonBuildableEntries;
|
||||
return $non_buildable_entries;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -147,37 +133,22 @@ class ProjectBuildHelper
|
|||
* The ProjectBuildRequest has to be validated before!!
|
||||
* You have to flush changes to DB afterward
|
||||
*/
|
||||
public function doBuild(ProjectBuildRequest $projectBuildRequest): void
|
||||
public function doBuild(ProjectBuildRequest $buildRequest): void
|
||||
{
|
||||
$message = $projectBuildRequest->getComment();
|
||||
$message .= ' (Project build: '.$projectBuildRequest->getProject()->getName().')';
|
||||
$message = $buildRequest->getComment();
|
||||
$message .= ' (Project build: '.$buildRequest->getProject()->getName().')';
|
||||
|
||||
foreach ($projectBuildRequest->getPartBomEntries() as $bomEntry) {
|
||||
foreach ($projectBuildRequest->getPartLotsForBOMEntry($bomEntry) as $partLot) {
|
||||
$amount = $projectBuildRequest->getLotWithdrawAmount($partLot);
|
||||
foreach ($buildRequest->getPartBomEntries() as $bom_entry) {
|
||||
foreach ($buildRequest->getPartLotsForBOMEntry($bom_entry) as $part_lot) {
|
||||
$amount = $buildRequest->getLotWithdrawAmount($part_lot);
|
||||
if ($amount > 0) {
|
||||
$this->withdrawAddHelper->withdraw($partLot, $amount, $message);
|
||||
$this->withdraw_add_helper->withdraw($part_lot, $amount, $message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($projectBuildRequest->getAssemblyBomEntries() as $bomEntry) {
|
||||
$assemblyBuildRequest = new AssemblyBuildRequest($bomEntry->getAssembly(), $projectBuildRequest->getNumberOfBuilds());
|
||||
|
||||
//Add fields for assembly bom entries
|
||||
foreach ($assemblyBuildRequest->getPartBomEntries() as $partBomEntry) {
|
||||
foreach ($assemblyBuildRequest->getPartLotsForBOMEntry($partBomEntry) as $partLot) {
|
||||
//Read amount from build configuration of the projectBuildRequest
|
||||
$amount = $projectBuildRequest->getLotWithdrawAmount($partLot);
|
||||
if ($amount > 0) {
|
||||
$this->withdrawAddHelper->withdraw($partLot, $amount, $message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($projectBuildRequest->getAddBuildsToBuildsPart()) {
|
||||
$this->withdrawAddHelper->add($projectBuildRequest->getBuildsPartLot(), $projectBuildRequest->getNumberOfBuilds(), $message);
|
||||
if ($buildRequest->getAddBuildsToBuildsPart()) {
|
||||
$this->withdraw_add_helper->add($buildRequest->getBuildsPartLot(), $buildRequest->getNumberOfBuilds(), $message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ class AssemblyTwigExtension extends AbstractExtension
|
|||
public function getFunctions(): array
|
||||
{
|
||||
return [
|
||||
new TwigFunction('has_assembly', [$this, 'hasAssembly']),
|
||||
new TwigFunction('has_project', [$this, 'hasProject']),
|
||||
];
|
||||
}
|
||||
|
||||
public function hasAssembly(array $bomEntries): bool
|
||||
public function hasProject(array $bomEntries): bool
|
||||
{
|
||||
foreach ($bomEntries as $entry) {
|
||||
if ($entry->getAssembly() !== null) {
|
||||
if ($entry->getProject() !== null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue