. */ namespace App\Serializer; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Orderdetail; use App\Entity\PriceInformations\Pricedetail; use Brick\Math\BigDecimal; use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; /** * @see \App\Tests\Serializer\PartNormalizerTest */ class PartNormalizer implements NormalizerInterface, DenormalizerInterface, NormalizerAwareInterface, DenormalizerAwareInterface { use NormalizerAwareTrait; use DenormalizerAwareTrait; private const ALREADY_CALLED = 'PART_NORMALIZER_ALREADY_CALLED'; private const DENORMALIZE_KEY_MAPPING = [ 'notes' => 'comment', 'quantity' => 'instock', 'amount' => 'instock', 'mpn' => 'manufacturer_product_number', 'spn' => 'supplier_part_number', 'supplier_product_number' => 'supplier_part_number', 'storage_location' => 'storelocation', ]; public function __construct( private readonly StructuralElementFromNameDenormalizer $locationDenormalizer, ) { } public function supportsNormalization($data, ?string $format = null, array $context = []): bool { //We only remove the type field for CSV export return !isset($context[self::ALREADY_CALLED]) && $format === 'csv' && $data instanceof Part ; } public function normalize($object, ?string $format = null, array $context = []): array { if (!$object instanceof Part) { throw new \InvalidArgumentException('This normalizer only supports Part objects!'); } $context[self::ALREADY_CALLED] = true; //Prevent exception in API Platform if ($object->getID() === null) { $context['iri'] = 'not-persisted'; } $data = $this->normalizer->normalize($object, $format, $context); //Remove type field for CSV export if ($format === 'csv') { unset($data['type']); } return $data; } public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool { //Only denormalize if we are doing a file import operation if (!($context['partdb_import'] ?? false)) { return false; } //Only make the denormalizer available on import operations return !isset($context[self::ALREADY_CALLED]) && is_array($data) && is_a($type, Part::class, true); } private function normalizeKeys(array &$data): array { //Rename keys based on the mapping, while leaving the data untouched foreach ($data as $key => $value) { if (isset(self::DENORMALIZE_KEY_MAPPING[$key])) { $data[self::DENORMALIZE_KEY_MAPPING[$key]] = $value; unset($data[$key]); } } return $data; } public function denormalize($data, string $type, ?string $format = null, array $context = []): ?Part { $this->normalizeKeys($data); //Empty IPN should be null, or we get a constraint error if (isset($data['ipn']) && $data['ipn'] === '') { $data['ipn'] = null; } //Fill empty needs_review and needs_review_comment fields with false if (empty($data['needs_review'])) { $data['needs_review'] = false; } if (empty($data['favorite'])) { $data['favorite'] = false; } if (empty($data['minamount'])) { $data['minamount'] = 0.0; } $context[self::ALREADY_CALLED] = true; $object = $this->denormalizer->denormalize($data, $type, $format, $context); if (!$object instanceof Part) { throw new \InvalidArgumentException('This normalizer only supports Part objects!'); } if ((isset($data['instock']) && trim((string) $data['instock']) !== "") || (isset($data['storelocation']) && trim((string) $data['storelocation']) !== "")) { $partLot = new PartLot(); if (isset($data['instock']) && $data['instock'] !== "") { //Replace comma with dot $instock = (float) str_replace(',', '.', (string) $data['instock']); $partLot->setAmount($instock); } else { $partLot->setInstockUnknown(true); } if (isset($data['storelocation']) && $data['storelocation'] !== "") { $location = $this->locationDenormalizer->denormalize($data['storelocation'], StorageLocation::class, $format, $context); $partLot->setStorageLocation($location); } $object->addPartLot($partLot); } if (isset($data['supplier']) && $data['supplier'] !== "") { $supplier = $this->locationDenormalizer->denormalize($data['supplier'], Supplier::class, $format, $context); if ($supplier !== null) { $orderdetail = new Orderdetail(); $orderdetail->setSupplier($supplier); if (isset($data['supplier_part_number']) && $data['supplier_part_number'] !== "") { $orderdetail->setSupplierpartnr($data['supplier_part_number']); } $object->addOrderdetail($orderdetail); if (isset($data['price']) && $data['price'] !== "") { $pricedetail = new Pricedetail(); $pricedetail->setMinDiscountQuantity(1); $pricedetail->setPriceRelatedQuantity(1); $price = BigDecimal::of(str_replace(',', '.', (string) $data['price'])); $pricedetail->setPrice($price); $orderdetail->addPricedetail($pricedetail); } } } return $object; } /** * @return bool[] */ public function getSupportedTypes(?string $format): array { //Must be false, because we rely on is_array($data) in supportsDenormalization() return [ Part::class => false, ]; } }