From 7a2eb610e672bb18af830410c021c47b3472de63 Mon Sep 17 00:00:00 2001 From: Atala Date: Sat, 23 Jun 2018 11:43:01 +0200 Subject: [PATCH] feat: add working product and product options for stores --- .../Version20180623084629.php | 41 ++ package-lock.json | 530 ------------------ src/AppBundle/Controller/AdminController.php | 7 +- .../Controller/Utils/ProductTrait.php | 21 +- .../Controller/Utils/RestaurantTrait.php | 8 +- src/AppBundle/Controller/Utils/StoreTrait.php | 37 ++ .../Controller/Utils/WithRoutesTrait.php | 4 +- src/AppBundle/Entity/Base/LocalBusiness.php | 55 ++ src/AppBundle/Entity/Restaurant.php | 48 +- src/AppBundle/Entity/Store.php | 5 + src/AppBundle/Entity/Sylius/Product.php | 25 + src/AppBundle/Entity/Sylius/ProductOption.php | 6 + src/AppBundle/Form/MenuEditorType.php | 2 - src/AppBundle/Form/ProductType.php | 2 +- .../Resources/config/doctrine/Store.orm.yml | 35 ++ .../Resources/config/routing/admin.yml | 105 +++- .../Resources/config/routing/profile.yml | 76 ++- .../views/Restaurant/product.html.twig | 2 +- .../views/Restaurant/productOption.html.twig | 3 +- .../views/Restaurant/productOptions.html.twig | 2 +- .../views/Restaurant/products.html.twig | 2 +- .../Resources/views/Store/product.html.twig | 17 + .../views/Store/productOption.html.twig | 18 + .../views/Store/productOptions.html.twig | 16 + .../Resources/views/Store/products.html.twig | 16 + .../LocalBusiness/productOptions.html.twig | 11 +- .../LocalBusiness/products.html.twig | 4 +- .../Sylius/Product/ProductInterface.php | 11 + 28 files changed, 479 insertions(+), 630 deletions(-) create mode 100644 app/DoctrineMigrations/Version20180623084629.php create mode 100644 src/AppBundle/Resources/views/Store/product.html.twig create mode 100644 src/AppBundle/Resources/views/Store/productOption.html.twig create mode 100644 src/AppBundle/Resources/views/Store/productOptions.html.twig create mode 100644 src/AppBundle/Resources/views/Store/products.html.twig diff --git a/app/DoctrineMigrations/Version20180623084629.php b/app/DoctrineMigrations/Version20180623084629.php new file mode 100644 index 0000000000..83c0e10acb --- /dev/null +++ b/app/DoctrineMigrations/Version20180623084629.php @@ -0,0 +1,41 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('CREATE TABLE store_product (restaurant_id INT NOT NULL, product_id INT NOT NULL, PRIMARY KEY(restaurant_id, product_id))'); + $this->addSql('CREATE INDEX IDX_CA42254AB1E7706E ON store_product (restaurant_id)'); + $this->addSql('CREATE INDEX IDX_CA42254A4584665A ON store_product (product_id)'); + $this->addSql('CREATE TABLE store_product_option (store_id INT NOT NULL, option_id INT NOT NULL, PRIMARY KEY(store_id, option_id))'); + $this->addSql('CREATE INDEX IDX_2F6C5CCAB092A811 ON store_product_option (store_id)'); + $this->addSql('CREATE INDEX IDX_2F6C5CCAA7C41D6F ON store_product_option (option_id)'); + $this->addSql('ALTER TABLE store_product ADD CONSTRAINT FK_CA42254AB1E7706E FOREIGN KEY (restaurant_id) REFERENCES store (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE store_product ADD CONSTRAINT FK_CA42254A4584665A FOREIGN KEY (product_id) REFERENCES sylius_product (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE store_product_option ADD CONSTRAINT FK_2F6C5CCAB092A811 FOREIGN KEY (store_id) REFERENCES store (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE store_product_option ADD CONSTRAINT FK_2F6C5CCAA7C41D6F FOREIGN KEY (option_id) REFERENCES sylius_product_option (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema) + { + // this down() migration is auto-generated, please modify it to your needs + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('DROP TABLE store_product'); + $this->addSql('DROP TABLE store_product_option'); + $this->addSql('ALTER TABLE store ALTER telephone TYPE VARCHAR(255)'); + $this->addSql('ALTER TABLE store ALTER telephone DROP DEFAULT'); + + } +} diff --git a/package-lock.json b/package-lock.json index d9982a75ce..9ad7efc8e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2243,7 +2243,6 @@ "anymatch": "2.0.0", "async-each": "1.0.1", "braces": "2.3.2", - "fsevents": "1.2.4", "glob-parent": "3.1.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", @@ -4807,535 +4806,6 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "fsevents": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", - "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", - "dev": true, - "optional": true, - "requires": { - "nan": "2.10.0", - "node-pre-gyp": "0.10.0" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "dev": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "debug": { - "version": "2.6.9", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "2.2.4" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "1.2.0", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" - } - }, - "glob": { - "version": "7.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.21", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safer-buffer": "2.1.2" - } - }, - "ignore-walk": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimatch": "3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true - }, - "minipass": { - "version": "2.2.4", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "5.1.1", - "yallist": "3.0.2" - } - }, - "minizlib": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "2.2.4" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "needle": { - "version": "2.2.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "2.6.9", - "iconv-lite": "0.4.21", - "sax": "1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.10.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "1.0.3", - "mkdirp": "0.5.1", - "needle": "2.2.0", - "nopt": "4.0.1", - "npm-packlist": "1.1.10", - "npmlog": "4.1.2", - "rc": "1.2.7", - "rimraf": "2.6.2", - "semver": "5.5.0", - "tar": "4.4.1" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1.1.1", - "osenv": "0.1.5" - } - }, - "npm-bundled": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "npm-packlist": { - "version": "1.1.10", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ignore-walk": "3.0.1", - "npm-bundled": "1.0.3" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "0.5.1", - "ini": "1.3.5", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.1", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" - } - }, - "rimraf": { - "version": "2.6.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "glob": "7.1.2" - } - }, - "safe-buffer": { - "version": "5.1.1", - "bundled": true, - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "dev": true, - "optional": true - }, - "semver": { - "version": "5.5.0", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "4.4.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "chownr": "1.0.1", - "fs-minipass": "1.2.5", - "minipass": "2.2.4", - "minizlib": "1.1.0", - "mkdirp": "0.5.1", - "safe-buffer": "5.1.1", - "yallist": "3.0.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "wide-align": { - "version": "1.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "1.0.2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "yallist": { - "version": "3.0.2", - "bundled": true, - "dev": true - } - } - }, "fstream": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", diff --git a/src/AppBundle/Controller/AdminController.php b/src/AppBundle/Controller/AdminController.php index 38d5391021..1fb84e64e4 100644 --- a/src/AppBundle/Controller/AdminController.php +++ b/src/AppBundle/Controller/AdminController.php @@ -68,16 +68,17 @@ protected function getRoutes() return [ // restaurants 'restaurant' => 'admin_restaurant', + 'restaurants' => 'admin_restaurants', 'menu_taxons' => 'admin_restaurant_menu_taxons', 'menu_taxon' => 'admin_restaurant_menu_taxon', 'products' => 'admin_restaurant_products', - 'product_options' => 'admin_restaurant_product_options', + 'product_options' => 'admin_restaurant_products', 'dashboard' => 'admin_restaurant_dashboard', 'planning' => 'admin_restaurant_planning', // store - 'store_route' => 'admin_store', - 'stores_route' => 'admin_stores', + 'store' => 'admin_store', + 'stores' => 'admin_stores', 'store_delivery' => 'admin_store_delivery', 'calculate_price' => 'admin_deliveries_calculate_price' ]; diff --git a/src/AppBundle/Controller/Utils/ProductTrait.php b/src/AppBundle/Controller/Utils/ProductTrait.php index 42a3e0205d..eab9434993 100644 --- a/src/AppBundle/Controller/Utils/ProductTrait.php +++ b/src/AppBundle/Controller/Utils/ProductTrait.php @@ -6,13 +6,16 @@ use AppBundle\Entity\Sylius\Product; use AppBundle\Form\ProductType; use AppBundle\Form\ProductOptionType; +use Ramsey\Uuid\Uuid; use Symfony\Component\HttpFoundation\Request; trait ProductTrait { public function createProductForm(LocalBusiness $localBusiness, Product $product) { - call_user_func(array($product, 'set'. (new \ReflectionClass($localBusiness))->getShortName()), $localBusiness); + call_user_func( + array($product, 'set'. (new \ReflectionClass($localBusiness))->getShortName()), + $localBusiness); return $this->createForm(ProductType::class, $product); } @@ -28,7 +31,7 @@ public function productsAction($id, string $class, Request $request) return $this->render($request->attributes->get('template'), $this->withRoutes([ 'layout' => $request->attributes->get('layout'), 'products' => $localBusiness->getProducts(), - 'restaurant' => $localBusiness, + 'local_business' => $localBusiness, ], $routes)); } @@ -55,7 +58,7 @@ public function productAction($id, string $class, $productId, Request $request) return $this->render($request->attributes->get('template'), $this->withRoutes([ 'layout' => $request->attributes->get('layout'), - 'restaurant' => $localBusiness, + 'local_business' => $localBusiness, 'product' => $product, 'form' => $form->createView() ], $routes)); @@ -86,7 +89,7 @@ public function newProductAction($id, string $class, Request $request) { return $this->render($request->attributes->get('template'), $this->withRoutes([ 'layout' => $request->attributes->get('layout'), - 'restaurant' => $localBusiness, + 'local_business' => $localBusiness, 'product' => $product, 'form' => $form->createView() ], $routes)); @@ -103,7 +106,7 @@ public function productOptionsAction($id, string $class, Request $request) { return $this->render($request->attributes->get('template'), $this->withRoutes([ 'layout' => $request->attributes->get('layout'), 'options' => $localBusiness->getProductOptions(), - 'restaurant' => $localBusiness, + 'local_business' => $localBusiness, ], $routes)); } @@ -143,7 +146,7 @@ public function productOptionAction($id, string $class, $optionId, Request $requ return $this->render($request->attributes->get('template'), $this->withRoutes([ 'layout' => $request->attributes->get('layout'), - 'restaurant' => $localBusiness, + 'local_business' => $localBusiness, 'form' => $form->createView(), ], $routes)); } @@ -157,7 +160,9 @@ public function newProductOptionAction($id, string $class, Request $request) { $productOption = $this->get('sylius.factory.product_option') ->createNew(); - call_user_func(array($productOption, 'set'. (new \ReflectionClass($localBusiness))->getShortName()), $localBusiness); + call_user_func( + array($productOption, 'set'. (new \ReflectionClass($localBusiness))->getShortName()), + $localBusiness); $routes = $request->attributes->get('routes'); @@ -180,7 +185,7 @@ public function newProductOptionAction($id, string $class, Request $request) { return $this->render($request->attributes->get('template'), $this->withRoutes([ 'layout' => $request->attributes->get('layout'), - 'restaurant' => $localBusiness, + 'local_business' => $localBusiness, 'form' => $form->createView(), ], $routes)); } diff --git a/src/AppBundle/Controller/Utils/RestaurantTrait.php b/src/AppBundle/Controller/Utils/RestaurantTrait.php index 690a484fea..75c0b2c490 100644 --- a/src/AppBundle/Controller/Utils/RestaurantTrait.php +++ b/src/AppBundle/Controller/Utils/RestaurantTrait.php @@ -476,9 +476,9 @@ public function restaurantProductsAction($id, Request $request) return $this->productsAction($id, Restaurant::class, $request); } - public function restaurantProductAction($restaurantId, $productId, Request $request) + public function restaurantProductAction($id, $productId, Request $request) { - return $this->productAction($restaurantId, Restaurant::class, $productId, $request); + return $this->productAction($id, Restaurant::class, $productId, $request); } public function newRestaurantProductAction($id, Request $request) @@ -491,10 +491,10 @@ public function restaurantProductOptionsAction($id, Request $request) return $this->productOptionsAction($id, Restaurant::class, $request); } - public function restaurantProductOptionAction($restaurantId, $optionId, Request $request) + public function restaurantProductOptionAction($id, $optionId, Request $request) { return $this->productOptionAction( - $restaurantId, + $id, Restaurant::class, $optionId, $request); diff --git a/src/AppBundle/Controller/Utils/StoreTrait.php b/src/AppBundle/Controller/Utils/StoreTrait.php index 632fa86126..fc83d1216a 100644 --- a/src/AppBundle/Controller/Utils/StoreTrait.php +++ b/src/AppBundle/Controller/Utils/StoreTrait.php @@ -105,4 +105,41 @@ public function storeAction($id, Request $request) return $this->renderStoreForm($store, $request); } + + public function storeProductsAction($id, Request $request) + { + return $this->productsAction($id, Store::class, $request); + } + + public function storeProductAction($id, $productId, Request $request) + { + return $this->productAction($id, Store::class, $productId, $request); + } + + public function newStoreProductAction($id, Request $request) + { + return $this->newProductAction($id, Store::class, $request); + } + + public function storeProductOptionsAction($id, Request $request) + { + return $this->productOptionsAction($id, Store::class, $request); + } + + public function storeProductOptionAction($id, $optionId, Request $request) + { + return $this->productOptionAction( + $id, + Store::class, + $optionId, + $request); + } + + public function newStoreProductOptionAction($id, Request $request) + { + return $this->newProductOptionAction( + $id, + Store::class, + $request); + } } diff --git a/src/AppBundle/Controller/Utils/WithRoutesTrait.php b/src/AppBundle/Controller/Utils/WithRoutesTrait.php index c9148b96e1..6fce06186a 100644 --- a/src/AppBundle/Controller/Utils/WithRoutesTrait.php +++ b/src/AppBundle/Controller/Utils/WithRoutesTrait.php @@ -6,7 +6,9 @@ trait WithRoutesTrait { protected function withRoutes($params, $routes) { - $routes = array_merge($routes, $this->getRoutes()); + $routes = $routes ? $routes : []; + $routes = array_merge($this->getRoutes(), $routes); + $routeParams = []; foreach ($routes as $key => $value) { $routeParams[sprintf('%s_route', $key)] = $value; diff --git a/src/AppBundle/Entity/Base/LocalBusiness.php b/src/AppBundle/Entity/Base/LocalBusiness.php index e73c3e93b5..82ebbbfdf7 100644 --- a/src/AppBundle/Entity/Base/LocalBusiness.php +++ b/src/AppBundle/Entity/Base/LocalBusiness.php @@ -2,10 +2,14 @@ namespace AppBundle\Entity\Base; +use AppBundle\Sylius\Product\ProductInterface; +use AppBundle\Sylius\Product\ProductOptionInterface; use AppBundle\Utils\TimeRange; use AppBundle\Validator\Constraints\TimeRange as AssertTimeRange; use ApiPlatform\Core\Annotation\ApiProperty; use ApiPlatform\Core\Annotation\ApiResource; +use Doctrine\Common\Collections\ArrayCollection; +use Sylius\Component\Taxonomy\Model\TaxonInterface; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Serializer\Annotation\Groups; @@ -48,6 +52,18 @@ abstract class LocalBusiness protected $additionalProperties = []; + protected $products; + + protected $productOptions; + + protected $activeMenuTaxon; + + public function __construct() + { + $this->products = new ArrayCollection(); + $this->productOptions = new ArrayCollection(); + } + public function getLegalName() { return $this->legalName; @@ -110,6 +126,45 @@ public function isOpen(\DateTime $now = null) return false; } + public function getProducts() + { + return $this->products; + } + + public function hasProduct(ProductInterface $product) + { + return $this->products->contains($product); + } + + public function addProduct(ProductInterface $product) + { + if (!$this->products->contains($product)) { + $this->products->add($product); + } + } + + public function getProductOptions() + { + return $this->productOptions; + } + + public function addProductOption(ProductOptionInterface $productOption) + { + if (!$this->productOptions->contains($productOption)) { + $this->productOptions->add($productOption); + } + } + + public function getMenuTaxon() + { + return $this->activeMenuTaxon; + } + + public function setMenuTaxon(TaxonInterface $taxon) + { + $this->activeMenuTaxon = $taxon; + } + /** * Get the next date the LocalBusiness will be opened at. * diff --git a/src/AppBundle/Entity/Restaurant.php b/src/AppBundle/Entity/Restaurant.php index df968590e8..973039daa7 100644 --- a/src/AppBundle/Entity/Restaurant.php +++ b/src/AppBundle/Entity/Restaurant.php @@ -159,17 +159,11 @@ class Restaurant extends FoodEstablishment private $owners; - private $products; - - private $productOptions; - /** * @Groups({"restaurant"}) */ private $taxons; - private $activeMenuTaxon; - /** * @var Contract * @Groups({"order_create"}) @@ -178,11 +172,10 @@ class Restaurant extends FoodEstablishment public function __construct() { + parent::__construct(); $this->servesCuisine = new ArrayCollection(); $this->closingRules = new ArrayCollection(); $this->owners = new ArrayCollection(); - $this->products = new ArrayCollection(); - $this->productOptions = new ArrayCollection(); $this->taxons = new ArrayCollection(); } @@ -419,16 +412,6 @@ public function setStripeAccount(StripeAccount $stripeAccount) return $this; } - public function getMenuTaxon() - { - return $this->activeMenuTaxon; - } - - public function setMenuTaxon(TaxonInterface $taxon) - { - $this->activeMenuTaxon = $taxon; - } - /** * @return string */ @@ -495,35 +478,6 @@ public function getOwners() return $this->owners; } - public function getProducts() - { - return $this->products; - } - - public function hasProduct(ProductInterface $product) - { - return $this->products->contains($product); - } - - public function addProduct(ProductInterface $product) - { - if (!$this->products->contains($product)) { - $this->products->add($product); - } - } - - public function getProductOptions() - { - return $this->productOptions; - } - - public function addProductOption(ProductOptionInterface $productOption) - { - if (!$this->productOptions->contains($productOption)) { - $this->productOptions->add($productOption); - } - } - public function getTaxons() { return $this->taxons; diff --git a/src/AppBundle/Entity/Store.php b/src/AppBundle/Entity/Store.php index c36ee3dcb6..c160f6e110 100644 --- a/src/AppBundle/Entity/Store.php +++ b/src/AppBundle/Entity/Store.php @@ -5,6 +5,7 @@ use ApiPlatform\Core\Annotation\ApiProperty; use ApiPlatform\Core\Annotation\ApiResource; use AppBundle\Entity\Base\LocalBusiness; +use AppBundle\Entity\Sylius\Taxon; use Doctrine\Common\Collections\ArrayCollection; use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\Serializer\Annotation\Groups; @@ -90,7 +91,11 @@ class Store extends LocalBusiness private $deliveries; public function __construct() { + parent::__construct(); $this->deliveries = new ArrayCollection(); + $catalog = new Taxon(); + $catalog->setName('Catalog'); + $this->activeMenuTaxon = $catalog; } /** diff --git a/src/AppBundle/Entity/Sylius/Product.php b/src/AppBundle/Entity/Sylius/Product.php index 673a662c94..6f7815dbe5 100644 --- a/src/AppBundle/Entity/Sylius/Product.php +++ b/src/AppBundle/Entity/Sylius/Product.php @@ -3,6 +3,7 @@ namespace AppBundle\Entity\Sylius; use AppBundle\Entity\Restaurant; +use AppBundle\Entity\Store; use AppBundle\Sylius\Product\ProductInterface; use Sylius\Component\Product\Model\Product as BaseProduct; @@ -13,6 +14,12 @@ class Product extends BaseProduct implements ProductInterface protected $restaurant; + protected $store; + + public function getLocalBusiness() { + return $this->restaurant ? $this->restaurant : $this->store; + } + /** * {@inheritdoc} */ @@ -30,4 +37,22 @@ public function setRestaurant(?Restaurant $restaurant): void $this->restaurant = $restaurant; } + + /** + * {@inheritdoc} + */ + public function getStore(): ?Store + { + return $this->store; + } + + /** + * {@inheritdoc} + */ + public function setStore(?Store $store): void + { + $store->addProduct($this); + + $this->store = $store; + } } diff --git a/src/AppBundle/Entity/Sylius/ProductOption.php b/src/AppBundle/Entity/Sylius/ProductOption.php index eb4dc01b43..a1e0de6092 100644 --- a/src/AppBundle/Entity/Sylius/ProductOption.php +++ b/src/AppBundle/Entity/Sylius/ProductOption.php @@ -3,6 +3,7 @@ namespace AppBundle\Entity\Sylius; use AppBundle\Entity\Restaurant; +use AppBundle\Entity\Store; use AppBundle\Sylius\Product\ProductOptionInterface; use Sylius\Component\Product\Model\ProductOption as BaseProductOption; @@ -54,4 +55,9 @@ public function setRestaurant(Restaurant $restaurant) { $restaurant->addProductOption($this); } + + public function setStore(Store $store) + { + $store->addProductOption($this); + } } diff --git a/src/AppBundle/Form/MenuEditorType.php b/src/AppBundle/Form/MenuEditorType.php index aed17ac9af..b35b206b8f 100644 --- a/src/AppBundle/Form/MenuEditorType.php +++ b/src/AppBundle/Form/MenuEditorType.php @@ -4,10 +4,8 @@ use AppBundle\Form\MenuEditor\TaxonType; use AppBundle\Utils\MenuEditor; -use Sylius\Component\Taxonomy\Model\Taxon; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\Extension\Core\Type\CollectionType; use Symfony\Component\OptionsResolver\OptionsResolver; diff --git a/src/AppBundle/Form/ProductType.php b/src/AppBundle/Form/ProductType.php index 107b582121..650d219ab0 100644 --- a/src/AppBundle/Form/ProductType.php +++ b/src/AppBundle/Form/ProductType.php @@ -70,7 +70,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) $form->add('options', EntityType::class, [ 'class' => ProductOption::class, - 'choices' => $product->getRestaurant()->getProductOptions(), + 'choices' => $product->getLocalBusiness()->getProductOptions(), 'expanded' => true, 'multiple' => true, ]); diff --git a/src/AppBundle/Resources/config/doctrine/Store.orm.yml b/src/AppBundle/Resources/config/doctrine/Store.orm.yml index 197d2096b5..473f2ea179 100644 --- a/src/AppBundle/Resources/config/doctrine/Store.orm.yml +++ b/src/AppBundle/Resources/config/doctrine/Store.orm.yml @@ -63,6 +63,35 @@ AppBundle\Entity\Store: joinColumns: address_id: referencedColumnName: id + manyToMany: + products: + targetEntity: Sylius\Component\Product\Model\ProductInterface + cascade: + - persist + joinTable: + name: store_product + inverseJoinColumns: + - + name: product_id + referencedColumnName: id + joinColumns: + - + name: restaurant_id + referencedColumnName: id + productOptions: + targetEntity: Sylius\Component\Product\Model\ProductOptionInterface + cascade: + - persist + joinTable: + name: store_product_option + inverseJoinColumns: + - + name: option_id + referencedColumnName: id + joinColumns: + - + name: store_id + referencedColumnName: id manyToOne: stripeAccount: targetEntity: AppBundle\Entity\StripeAccount @@ -77,6 +106,12 @@ AppBundle\Entity\Store: joinColumns: pricing_rule_set_id: referencedColumnName: id +# activeMenuTaxon: +# targetEntity: Sylius\Component\Taxonomy\Model\TaxonInterface +# joinColumns: +# active_menu_taxon_id: +# referencedColumnName: id +# nullable: true oneToMany: deliveries: targetEntity: AppBundle\Entity\Delivery diff --git a/src/AppBundle/Resources/config/routing/admin.yml b/src/AppBundle/Resources/config/routing/admin.yml index df0ce6d06a..cb5e7a467c 100644 --- a/src/AppBundle/Resources/config/routing/admin.yml +++ b/src/AppBundle/Resources/config/routing/admin.yml @@ -78,6 +78,18 @@ admin_restaurant: planning: admin_restaurant_planning methods: [ GET, POST ] +admin_restaurant_planning: + path: /admin/restaurants/{id}/planning + defaults: + _controller: AppBundle:Admin:restaurantPlanning + layout: AppBundle::admin.html.twig + template: AppBundle:Restaurant:planning.html.twig + routes: + restaurants: admin_restaurants + restaurant: admin_restaurant + success: admin_restaurant_planning + methods: [ GET, POST ] + admin_restaurants: path: /admin/restaurants defaults: @@ -171,13 +183,10 @@ admin_restaurant_product_new: _controller: AppBundle:Admin:newRestaurantProduct layout: AppBundle::admin.html.twig template: AppBundle:Restaurant:product.html.twig - routes: - restaurants: admin_restaurants - restaurant: admin_restaurant - products: admin_restaurant_products + admin_restaurant_product: - path: /admin/restaurants/{restaurantId}/products/{productId} + path: /admin/restaurants/{id}/products/{productId} defaults: _controller: AppBundle:Admin:restaurantProduct layout: AppBundle::admin.html.twig @@ -187,18 +196,6 @@ admin_restaurant_product: restaurant: admin_restaurant products: admin_restaurant_products -admin_restaurant_planning: - path: /admin/restaurants/{id}/planning - defaults: - _controller: AppBundle:Admin:restaurantPlanning - layout: AppBundle::admin.html.twig - template: AppBundle:Restaurant:planning.html.twig - routes: - restaurants: admin_restaurants - restaurant: admin_restaurant - success: admin_restaurant_planning - methods: [ GET, POST ] - admin_restaurant_product_options: path: /admin/restaurants/{id}/product-options defaults: @@ -225,7 +222,7 @@ admin_restaurant_product_option_new: methods: [ GET, POST ] admin_restaurant_product_option: - path: /admin/restaurants/{restaurantId}/product-options/{optionId} + path: /admin/restaurants/{id}/product-options/{optionId} defaults: _controller: AppBundle:Admin:restaurantProductOption layout: AppBundle::admin.html.twig @@ -350,6 +347,78 @@ admin_store_delivery: calculate_price: admin_deliveries_calculate_price methods: [ GET, POST ] +admin_store_products: + path: /admin/stores/{id}/products + defaults: + _controller: AppBundle:Admin:storeProducts + layout: AppBundle::admin.html.twig + template: AppBundle:Store:products.html.twig + routes: + stores: admin_stores + store: admin_store + product: admin_store_product + new_product: admin_store_product_new + methods: [ GET ] + +admin_store_product_new: + path: /admin/stores/{id}/products/new + defaults: + _controller: AppBundle:Admin:newStoreProduct + layout: AppBundle::admin.html.twig + template: AppBundle:Store:product.html.twig + routes: + stores: admin_stores + store: admin_store + products: admin_store_products + +admin_store_product: + path: /admin/stores/{id}/products/{productId} + defaults: + _controller: AppBundle:Admin:storeProduct + layout: AppBundle::admin.html.twig + template: AppBundle:Store:product.html.twig + routes: + stores: admin_stores + store: admin_store + products: admin_store_products + +admin_store_product_options: + path: /admin/stores/{id}/product-options + defaults: + _controller: AppBundle:Admin:storeProductOptions + layout: AppBundle::admin.html.twig + template: AppBundle:Store:productOptions.html.twig + routes: + stores: admin_stores + store: admin_store + product_option: admin_store_product_option + new_product_option: admin_store_product_option_new + methods: [ GET ] + +admin_store_product_option_new: + path: /admin/stores/{id}/product-options/new + defaults: + _controller: AppBundle:Admin:newStoreProductOption + layout: AppBundle::admin.html.twig + template: AppBundle:Store:productOption.html.twig + routes: + stores: admin_stores + store: admin_store + product_options: admin_store_product_options + methods: [ GET, POST ] + +admin_store_product_option: + path: /admin/stores/{id}/product-options/{optionId} + defaults: + _controller: AppBundle:Admin:storeProductOption + layout: AppBundle::admin.html.twig + template: AppBundle:Store:productOption.html.twig + routes: + stores: admin_stores + store: admin_store + product_options: admin_store_product_options + methods: [ GET, POST ] + admin_tag_new: path: /admin/settings/tags/new defaults: diff --git a/src/AppBundle/Resources/config/routing/profile.yml b/src/AppBundle/Resources/config/routing/profile.yml index 5e14e01e97..7ab5588b85 100644 --- a/src/AppBundle/Resources/config/routing/profile.yml +++ b/src/AppBundle/Resources/config/routing/profile.yml @@ -218,7 +218,7 @@ profile_restaurant_product_new: products: profile_restaurant_products profile_restaurant_product: - path: /profile/restaurants/{restaurantId}/products/{productId} + path: /profile/restaurants/{id}/products/{productId} defaults: _controller: AppBundle:Profile:restaurantProduct layout: AppBundle::profile.html.twig @@ -265,7 +265,7 @@ profile_restaurant_product_option_new: methods: [ GET, POST ] profile_restaurant_product_option: - path: /profile/restaurants/{restaurantId}/product-options/{optionId} + path: /profile/restaurants/{id}/product-options/{optionId} defaults: _controller: AppBundle:Profile:restaurantProductOption layout: AppBundle::profile.html.twig @@ -312,3 +312,75 @@ profile_store_delivery: success: profile_orders calculate_price: profile_deliveries_calculate_price methods: [ GET, POST ] + +profile_store_products: + path: /profile/stores/{id}/products + defaults: + _controller: AppBundle:Profile:storeProducts + layout: AppBundle::profile.html.twig + template: AppBundle:Store:products.html.twig + routes: + stores: profile_stores + store: profile_store + product: profile_store_product + new_product: profile_store_product_new + methods: [ GET ] + +profile_store_product_new: + path: /profile/stores/{id}/products/new + defaults: + _controller: AppBundle:Profile:newStoreProduct + layout: AppBundle::profile.html.twig + template: AppBundle:Store:product.html.twig + routes: + stores: profile_stores + store: profile_store + products: profile_store_products + +profile_store_product: + path: /profile/stores/{id}/products/{productId} + defaults: + _controller: AppBundle:Profile:storeProduct + layout: AppBundle::profile.html.twig + template: AppBundle:Store:product.html.twig + routes: + stores: profile_stores + store: profile_store + products: profile_store_products + +profile_store_product_options: + path: /profile/stores/{id}/product-options + defaults: + _controller: AppBundle:Profile:storeProductOptions + layout: AppBundle::profile.html.twig + template: AppBundle:Store:productOptions.html.twig + routes: + stores: profile_stores + store: profile_store + product_option: profile_store_product_option + new_product_option: profile_store_product_option_new + methods: [ GET ] + +profile_store_product_option_new: + path: /profile/stores/{id}/product-options/new + defaults: + _controller: AppBundle:Profile:newStoreProductOption + layout: AppBundle::profile.html.twig + template: AppBundle:Store:productOption.html.twig + routes: + stores: profile_stores + store: profile_store + product_options: profile_store_product_options + methods: [ GET, POST ] + +profile_store_product_option: + path: /profile/stores/{id}/product-options/{optionId} + defaults: + _controller: AppBundle:Profile:storeProductOption + layout: AppBundle::profile.html.twig + template: AppBundle:Store:productOption.html.twig + routes: + stores: profile_stores + store: profile_store + product_options: profile_store_product_options + methods: [ GET, POST ] diff --git a/src/AppBundle/Resources/views/Restaurant/product.html.twig b/src/AppBundle/Resources/views/Restaurant/product.html.twig index d56ed7c741..304f85d35b 100644 --- a/src/AppBundle/Resources/views/Restaurant/product.html.twig +++ b/src/AppBundle/Resources/views/Restaurant/product.html.twig @@ -7,7 +7,7 @@ {% set title = 'adminDashboard.restaurants.title' %} {% set local_businesses_route = restaurants_route %} {% set local_business_route = restaurant_route %} -{% set local_business = restaurant %} +{% set restaurant = local_business %} {% block breadcrumb %} {{ parent() }} diff --git a/src/AppBundle/Resources/views/Restaurant/productOption.html.twig b/src/AppBundle/Resources/views/Restaurant/productOption.html.twig index c516496785..7fd55fc7d0 100644 --- a/src/AppBundle/Resources/views/Restaurant/productOption.html.twig +++ b/src/AppBundle/Resources/views/Restaurant/productOption.html.twig @@ -8,7 +8,8 @@ {% set productsOptionTitle = 'restaurant.list.product_options' %} {% set local_businesses_route = restaurants_route %} {% set local_business_route = restaurant_route %} -{% set local_business = restaurant %} +{% set product_options_route = restaurantF_product_options_route %} +{% set restaurant = local_business %} {% block breadcrumb %} {{ parent() }} diff --git a/src/AppBundle/Resources/views/Restaurant/productOptions.html.twig b/src/AppBundle/Resources/views/Restaurant/productOptions.html.twig index 5dcaf460e2..713307e6f7 100644 --- a/src/AppBundle/Resources/views/Restaurant/productOptions.html.twig +++ b/src/AppBundle/Resources/views/Restaurant/productOptions.html.twig @@ -6,7 +6,7 @@ {% set productsOptionTitle = 'restaurant.list.product_options' %} {% set local_businesses_route = restaurants_route %} {% set local_business_route = restaurant_route %} -{% set local_business = restaurant %} +{% set restaurant = local_business %} {% block breadcrumb %} {{ parent() }} diff --git a/src/AppBundle/Resources/views/Restaurant/products.html.twig b/src/AppBundle/Resources/views/Restaurant/products.html.twig index 364ca1c39b..49bf46120a 100644 --- a/src/AppBundle/Resources/views/Restaurant/products.html.twig +++ b/src/AppBundle/Resources/views/Restaurant/products.html.twig @@ -6,7 +6,7 @@ {% set productsTitle = 'restaurant.list.products' %} {% set local_businesses_route = restaurants_route %} {% set local_business_route = restaurant_route %} -{% set local_business = restaurant %} +{% set restaurant = local_business %} {% block breadcrumb %} {{ parent() }} diff --git a/src/AppBundle/Resources/views/Store/product.html.twig b/src/AppBundle/Resources/views/Store/product.html.twig new file mode 100644 index 0000000000..fe53997324 --- /dev/null +++ b/src/AppBundle/Resources/views/Store/product.html.twig @@ -0,0 +1,17 @@ +{% extends layout %} + +{% form_theme form 'AppBundle::Form/product.html.twig' %} + +{% use 'AppBundle::_partials/LocalBusiness/product.html.twig' %} + +{% set title = 'adminDashboard.stores.title' %} +{% set local_businesses_route = stores_route %} +{% set local_business_route = store_route %} + +{% block breadcrumb %} + {{ parent() }} +{% endblock %} + +{% block content %} + {{ parent() }} +{% endblock %} diff --git a/src/AppBundle/Resources/views/Store/productOption.html.twig b/src/AppBundle/Resources/views/Store/productOption.html.twig new file mode 100644 index 0000000000..4cacfb0ab7 --- /dev/null +++ b/src/AppBundle/Resources/views/Store/productOption.html.twig @@ -0,0 +1,18 @@ +{% extends layout %} + +{% form_theme form '@App/Form/productOption.html.twig' %} + +{% use 'AppBundle::_partials/LocalBusiness/productOption.html.twig' %} + +{% set title = 'adminDashboard.stores.title' %} +{% set productsOptionTitle = 'restaurant.list.product_options' %} +{% set local_businesses_route = stores_route %} +{% set local_business_route = store_route %} + +{% block breadcrumb %} + {{ parent() }} +{% endblock %} + +{% block content %} + {{ parent() }} +{% endblock %} \ No newline at end of file diff --git a/src/AppBundle/Resources/views/Store/productOptions.html.twig b/src/AppBundle/Resources/views/Store/productOptions.html.twig new file mode 100644 index 0000000000..c6f3fa1ad7 --- /dev/null +++ b/src/AppBundle/Resources/views/Store/productOptions.html.twig @@ -0,0 +1,16 @@ +{% extends layout %} + +{% use 'AppBundle::_partials/LocalBusiness/productOptions.html.twig' %} + +{% set title = 'adminDashboard.stores.title' %} +{% set productsOptionTitle = 'restaurant.list.product_options' %} +{% set local_businesses_route = stores_route %} +{% set local_business_route = store_route %} + +{% block breadcrumb %} + {{ parent() }} +{% endblock %} + +{% block content %} + {{ parent() }} +{% endblock %} \ No newline at end of file diff --git a/src/AppBundle/Resources/views/Store/products.html.twig b/src/AppBundle/Resources/views/Store/products.html.twig new file mode 100644 index 0000000000..aa62059a1b --- /dev/null +++ b/src/AppBundle/Resources/views/Store/products.html.twig @@ -0,0 +1,16 @@ +{% extends layout %} + +{% use 'AppBundle::_partials/LocalBusiness/products.html.twig' %} + +{% set title = 'adminDashboard.stores.title' %} +{% set productsTitle = 'restaurant.list.products' %} +{% set local_businesses_route = stores_route %} +{% set local_business_route = store_route %} + +{% block breadcrumb %} + {{ parent() }} +{% endblock %} + +{% block content %} + {{ parent() }} +{% endblock %} \ No newline at end of file diff --git a/src/AppBundle/Resources/views/_partials/LocalBusiness/productOptions.html.twig b/src/AppBundle/Resources/views/_partials/LocalBusiness/productOptions.html.twig index 894f0d517a..c8abc8151d 100644 --- a/src/AppBundle/Resources/views/_partials/LocalBusiness/productOptions.html.twig +++ b/src/AppBundle/Resources/views/_partials/LocalBusiness/productOptions.html.twig @@ -1,12 +1,7 @@ {% block breadcrumb %}
  • {{ title | trans }}
  • {{ local_business.name }}
  • -
  • - - {{ productsOptionTitle | trans }} - -
  • -
  • +
  • {{ productsOptionTitle | trans }}
  • {% endblock %} {% block content %} @@ -14,7 +9,7 @@  {% trans %}restaurant.product_options.help{% endtrans %}

    - +  {{ 'basics.add'|trans }}

    @@ -32,7 +27,7 @@ {{ ('product_option.strategy.' ~ option.strategy)|trans }} {{ option.values|length }} -  {{ 'basics.edit'|trans }} diff --git a/src/AppBundle/Resources/views/_partials/LocalBusiness/products.html.twig b/src/AppBundle/Resources/views/_partials/LocalBusiness/products.html.twig index a8984c9046..65232e90f5 100644 --- a/src/AppBundle/Resources/views/_partials/LocalBusiness/products.html.twig +++ b/src/AppBundle/Resources/views/_partials/LocalBusiness/products.html.twig @@ -6,7 +6,7 @@ {% block content %}

    -  {{ 'basics.add'|trans }}

    @@ -25,7 +25,7 @@ {% if product.enabled %}{% endif %} diff --git a/src/AppBundle/Sylius/Product/ProductInterface.php b/src/AppBundle/Sylius/Product/ProductInterface.php index 001cab0446..a60e3fba30 100644 --- a/src/AppBundle/Sylius/Product/ProductInterface.php +++ b/src/AppBundle/Sylius/Product/ProductInterface.php @@ -3,6 +3,7 @@ namespace AppBundle\Sylius\Product; use AppBundle\Entity\Restaurant; +use AppBundle\Entity\Store; use Sylius\Component\Product\Model\ProductInterface as BaseProductInterface; interface ProductInterface extends BaseProductInterface @@ -20,4 +21,14 @@ public function getRestaurant(): ?Restaurant; * @param Restaurant $restaurant */ public function setRestaurant(?Restaurant $restaurant): void; + + /** + * @return Store + */ + public function getStore(): ?Store; + + /** + * @param Store $store + */ + public function setStore(?Store $store): void; }
    -  {{ 'basics.edit'|trans }}