Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions assets/controllers/datum-format-helper_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Controller } from '@hotwired/stimulus';
import Sortable from "sortablejs";
import Translator from "bazinga-translator";

export default class extends Controller {
static targets = ['help']

updateHelp(event) {
let value = event.target.value;
let help = '';

if (value === 'date' || value === 'checkbox' || value === 'price' || value === 'country') {
help = Translator.trans('label.expected_format') + ' ' + Translator.trans('label.' + value + '.format')
}

this.helpTarget.innerHTML = help;
}
}
44 changes: 44 additions & 0 deletions assets/controllers/import-mapping_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Controller } from '@hotwired/stimulus';
import Sortable from "sortablejs";

/* stimulusFetch: 'lazy' */
export default class extends Controller {
static targets = ['mapping', 'button', 'position']

index = null;

connect() {
this.index = this.mappingTargets.length;
this.computePositions();

let self = this;
new Sortable(this.element, {
draggable: '.mapping',
handle: '.handle',
onSort: function () {
self.computePositions();
}
});
}

add(event) {
event.preventDefault();
let prototype = this.element.dataset.prototype;
let newForm = prototype.replace(/__name__/g, this.index);
this.buttonTarget.insertAdjacentHTML('beforebegin', newForm);
this.index++;
this.computePositions();
}

remove(event) {
event.preventDefault();
event.target.closest('.mapping').remove();
this.computePositions();
}

computePositions() {
this.positionTargets.forEach((element, index) => {
element.value = index+1;
})
}
}
31 changes: 31 additions & 0 deletions migrations/Mysql/Version20251130000942.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace App\Migrations\Mysql;

use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

final class Version20251130000942 extends AbstractMigration
{
public function getDescription(): string
{
return '[Mysql] Add `koi_import` table.';
}

public function up(Schema $schema): void
{
$this->skipIf(!$this->connection->getDatabasePlatform() instanceof MySQLPlatform, 'Mysql or Mariadb migration only. Skipped.');

$this->addSql('CREATE TABLE koi_import (id CHAR(36) NOT NULL, file VARCHAR(255) DEFAULT NULL, filename VARCHAR(255) DEFAULT NULL, status VARCHAR(255) NOT NULL, name_index INT DEFAULT NULL, image_index INT DEFAULT NULL, mapping JSON DEFAULT NULL, total_number_of_items INT DEFAULT NULL, number_of_imported_items INT DEFAULT NULL, number_of_duplicated_items INT DEFAULT NULL, number_of_skipped_items INT DEFAULT NULL, created_at DATETIME NOT NULL, collection_id CHAR(36) DEFAULT NULL, owner_id CHAR(36) DEFAULT NULL, UNIQUE INDEX UNIQ_7FE294F48C9F3610 (file), INDEX IDX_7FE294F4514956FD (collection_id), INDEX IDX_7FE294F47E3C61F9 (owner_id), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci`');
$this->addSql('ALTER TABLE koi_import ADD CONSTRAINT FK_7FE294F4514956FD FOREIGN KEY (collection_id) REFERENCES koi_collection (id)');
$this->addSql('ALTER TABLE koi_import ADD CONSTRAINT FK_7FE294F47E3C61F9 FOREIGN KEY (owner_id) REFERENCES koi_user (id)');
}

public function down(Schema $schema): void
{
$this->skipIf(true, 'Always move forward.');
}
}
43 changes: 43 additions & 0 deletions migrations/Postgresql/Version20250820093147.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace App\Migrations\Postgresql;

use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

final class Version20250820093147 extends AbstractMigration
{
public function getDescription(): string
{
return '[Postgresql] Add `koi_import` table.';
}

public function up(Schema $schema): void
{
$this->skipIf(!$this->connection->getDatabasePlatform() instanceof PostgreSQLPlatform, 'Postgresql migration only. Skipped.');

$this->addSql('CREATE TABLE koi_import (id CHAR(36) NOT NULL, file VARCHAR(255) DEFAULT NULL, filename VARCHAR(255) DEFAULT NULL, status VARCHAR(255) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, collection_id CHAR(36) DEFAULT NULL, owner_id CHAR(36) DEFAULT NULL, PRIMARY KEY (id))');
$this->addSql('CREATE UNIQUE INDEX UNIQ_7FE294F48C9F3610 ON koi_import (file)');
$this->addSql('CREATE INDEX IDX_7FE294F4514956FD ON koi_import (collection_id)');
$this->addSql('CREATE INDEX IDX_7FE294F47E3C61F9 ON koi_import (owner_id)');

$this->addSql('ALTER TABLE koi_import ADD CONSTRAINT FK_7FE294F4514956FD FOREIGN KEY (collection_id) REFERENCES koi_collection (id)');
$this->addSql('ALTER TABLE koi_import ADD CONSTRAINT FK_7FE294F47E3C61F9 FOREIGN KEY (owner_id) REFERENCES koi_user (id)');
$this->addSql('ALTER TABLE koi_import ADD mapping JSON DEFAULT NULL');
$this->addSql('ALTER TABLE koi_import ADD total_number_of_items INT DEFAULT NULL');
$this->addSql('ALTER TABLE koi_import ADD number_of_imported_items INT DEFAULT NULL');
$this->addSql('ALTER TABLE koi_import ADD number_of_duplicated_items INT DEFAULT NULL');
$this->addSql('ALTER TABLE koi_import ADD number_of_skipped_items INT DEFAULT NULL');

$this->addSql('ALTER TABLE koi_import ADD name_index INT DEFAULT NULL');
$this->addSql('ALTER TABLE koi_import ADD image_index INT DEFAULT NULL');
}

public function down(Schema $schema): void
{
$this->skipIf(true, 'Always move forward.');
}
}
118 changes: 118 additions & 0 deletions src/Controller/ImportController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php

declare(strict_types=1);

namespace App\Controller;

use App\Entity\Collection;
use App\Entity\Import;
use App\Enum\ImportStatusEnum;
use App\Form\Type\Entity\ImportMappingType;
use App\Form\Type\Entity\ImportUploadType;
use App\Service\ImportHandler;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class ImportController extends AbstractController
{
#[Route(path: '/collections/{id}/import', name: 'app_collection_import', methods: ['GET', 'POST'])]
public function import(Request $request, Collection $collection, ManagerRegistry $managerRegistry): Response
{
//$this->denyAccessUnlessFeaturesEnabled(['imports']);

$import = new Import()
->setCollection($collection)
->setOwner($this->getUser())
->setStatus(ImportStatusEnum::NEW)
;

$form = $this->createForm(ImportUploadType::class, $import);

$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$managerRegistry->getManager()->persist($import);
$managerRegistry->getManager()->flush();

return $this->redirectToRoute('app_collection_import_mapping', [
'id' => $collection->getId(),
'importId' => $import->getId()
]);
}

return $this->render('App/Import/import.html.twig', [
'form' => $form,
'collection' => $collection,
]);
}

#[Route(path: '/collections/{id}/import/{importId}/mapping', name: 'app_collection_import_mapping', methods: ['GET', 'POST'])]
public function mapping(
Request $request,
Collection $collection,
#[MapEntity(expr: 'repository.find(importId)')] Import $import,
ManagerRegistry $managerRegistry
): Response
{
//$this->denyAccessUnlessFeaturesEnabled(['imports']);

$form = $this->createForm(ImportMappingType::class, $import);

$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$managerRegistry->getManager()->flush();

return $this->redirectToRoute('app_collection_import_preview', [
'id' => $collection->getId(),
'importId' => $import->getId()
]);
}

return $this->render('App/Import/import-mapping.html.twig', [
'form' => $form,
'collection' => $collection,
'import' => $import,
]);
}

#[Route(path: '/collections/{id}/import/{importId}/preview', name: 'app_collection_import_preview', methods: ['GET', 'POST'])]
public function preview(
Collection $collection,
#[MapEntity(expr: 'repository.find(importId)')] Import $import,
ImportHandler $importHandler
): Response
{
//$this->denyAccessUnlessFeaturesEnabled(['imports']);

$items = $importHandler->createItems($import, true);

return $this->render('App/Import/import-preview.html.twig', [
'collection' => $collection,
'import' => $import,
'items' => $items
]);
}

#[Route(path: '/collections/{id}/import/{importId}/import', name: 'app_collection_import_import', methods: ['POST'])]
public function executeImport(
Collection $collection,
#[MapEntity(expr: 'repository.find(importId)')] Import $import,
ImportHandler $importHandler,
ManagerRegistry $managerRegistry
): Response
{
//$this->denyAccessUnlessFeaturesEnabled(['imports']);

$items = $importHandler->createItems($import, false);
foreach ($items as $item) {
$item->setCollection($collection);
$managerRegistry->getManager()->persist($item);
}

$managerRegistry->getManager()->flush();

return $this->redirectToRoute('app_collection_show', ['id' => $collection->getId()]);
}
}
3 changes: 3 additions & 0 deletions src/Entity/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ class Collection implements LoggableInterface, BreadcrumbableInterface, Cacheabl
#[ORM\OneToMany(targetEntity: Item::class, mappedBy: 'collection', cascade: ['all'])]
private DoctrineCollection $items;

#[ORM\OneToMany(targetEntity: Import::class, mappedBy: 'collection', cascade: ['all'])]
private DoctrineCollection $imports;

#[Groups(['collection:read', 'collection:write'])]
#[ORM\OneToOne(targetEntity: DisplayConfiguration::class, cascade: ['all'])]
#[Assert\Valid]
Expand Down
Loading