diff --git a/.github/workflows/assets_artifact_build.yml b/.github/workflows/assets_artifact_build.yml
index 447f95bf..3c7b2522 100644
--- a/.github/workflows/assets_artifact_build.yml
+++ b/.github/workflows/assets_artifact_build.yml
@@ -60,7 +60,7 @@ jobs:
${{ runner.os }}-yarn-
- name: Setup node
- uses: actions/setup-node@v5
+ uses: actions/setup-node@v6
with:
node-version: '20'
@@ -80,13 +80,13 @@ jobs:
run: zip -r /tmp/partdb_assets.zip public/build/ vendor/
- name: Upload assets artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v5
with:
name: Only dependencies and built assets
path: /tmp/partdb_assets.zip
- name: Upload full artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v5
with:
name: Full Part-DB including dependencies and built assets
path: /tmp/partdb_with_assets.zip
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index c7c0965b..fee987ae 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -104,7 +104,7 @@ jobs:
run: composer install --prefer-dist --no-progress
- name: Setup node
- uses: actions/setup-node@v5
+ uses: actions/setup-node@v6
with:
node-version: '20'
diff --git a/assets/controllers/elements/ckeditor_controller.js b/assets/controllers/elements/ckeditor_controller.js
index 46e78fd5..b7c87dab 100644
--- a/assets/controllers/elements/ckeditor_controller.js
+++ b/assets/controllers/elements/ckeditor_controller.js
@@ -106,6 +106,15 @@ export default class extends Controller {
editor_div.classList.add(...new_classes.split(","));
}
+ // Automatic synchronization of source input
+ editor.model.document.on("change:data", () => {
+ editor.updateSourceElement();
+
+ // Dispatch the input event for further treatment
+ const event = new Event("input");
+ this.element.dispatchEvent(event);
+ });
+
//This return is important! Otherwise we get mysterious errors in the console
//See: https://github.com/ckeditor/ckeditor5/issues/5897#issuecomment-628471302
return editor;
diff --git a/assets/controllers/elements/ipn_suggestion_controller.js b/assets/controllers/elements/ipn_suggestion_controller.js
new file mode 100644
index 00000000..c8b543cb
--- /dev/null
+++ b/assets/controllers/elements/ipn_suggestion_controller.js
@@ -0,0 +1,250 @@
+import { Controller } from "@hotwired/stimulus";
+import "../../css/components/autocomplete_bootstrap_theme.css";
+
+export default class extends Controller {
+ static targets = ["input"];
+ static values = {
+ partId: Number,
+ partCategoryId: Number,
+ partDescription: String,
+ suggestions: Object,
+ commonSectionHeader: String, // Dynamic header for common Prefixes
+ partIncrementHeader: String, // Dynamic header for new possible part increment
+ suggestUrl: String,
+ };
+
+ connect() {
+ this.configureAutocomplete();
+ this.watchCategoryChanges();
+ this.watchDescriptionChanges();
+ }
+
+ templates = {
+ commonSectionHeader({ title, html }) {
+ return html`
+
+ `;
+ },
+ partIncrementHeader({ title, html }) {
+ return html`
+
+ `;
+ },
+ list({ html }) {
+ return html`
+
+ `;
+ },
+ item({ suggestion, description, html }) {
+ return html`
+
+
+
+
+
+
${suggestion}
+
${description}
+
+
+
+
+ `;
+ },
+ };
+
+ configureAutocomplete() {
+ const inputField = this.inputTarget;
+ const commonPrefixes = this.suggestionsValue.commonPrefixes || [];
+ const prefixesPartIncrement = this.suggestionsValue.prefixesPartIncrement || [];
+ const commonHeader = this.commonSectionHeaderValue;
+ const partIncrementHeader = this.partIncrementHeaderValue;
+
+ if (!inputField || (!commonPrefixes.length && !prefixesPartIncrement.length)) return;
+
+ // Check whether the panel should be created at the update
+ if (this.isPanelInitialized) {
+ const existingPanel = inputField.parentNode.querySelector(".aa-Panel");
+ if (existingPanel) {
+ // Only remove the panel in the update phase
+
+ existingPanel.remove();
+ }
+ }
+
+ // Create panel
+ const panel = document.createElement("div");
+ panel.classList.add("aa-Panel");
+ panel.style.display = "none";
+
+ // Create panel layout
+ const panelLayout = document.createElement("div");
+ panelLayout.classList.add("aa-PanelLayout", "aa-Panel--scrollable");
+
+ // Section for prefixes part increment
+ if (prefixesPartIncrement.length) {
+ const partIncrementSection = document.createElement("section");
+ partIncrementSection.classList.add("aa-Source");
+
+ const partIncrementHeaderHtml = this.templates.partIncrementHeader({
+ title: partIncrementHeader,
+ html: String.raw,
+ });
+ partIncrementSection.innerHTML += partIncrementHeaderHtml;
+
+ const partIncrementList = document.createElement("ul");
+ partIncrementList.classList.add("aa-List");
+ partIncrementList.setAttribute("role", "listbox");
+
+ prefixesPartIncrement.forEach((prefix) => {
+ const itemHTML = this.templates.item({
+ suggestion: prefix.title,
+ description: prefix.description,
+ html: String.raw,
+ });
+ partIncrementList.innerHTML += itemHTML;
+ });
+
+ partIncrementSection.appendChild(partIncrementList);
+ panelLayout.appendChild(partIncrementSection);
+ }
+
+ // Section for common prefixes
+ if (commonPrefixes.length) {
+ const commonSection = document.createElement("section");
+ commonSection.classList.add("aa-Source");
+
+ const commonSectionHeader = this.templates.commonSectionHeader({
+ title: commonHeader,
+ html: String.raw,
+ });
+ commonSection.innerHTML += commonSectionHeader;
+
+ const commonList = document.createElement("ul");
+ commonList.classList.add("aa-List");
+ commonList.setAttribute("role", "listbox");
+
+ commonPrefixes.forEach((prefix) => {
+ const itemHTML = this.templates.item({
+ suggestion: prefix.title,
+ description: prefix.description,
+ html: String.raw,
+ });
+ commonList.innerHTML += itemHTML;
+ });
+
+ commonSection.appendChild(commonList);
+ panelLayout.appendChild(commonSection);
+ }
+
+ panel.appendChild(panelLayout);
+ inputField.parentNode.appendChild(panel);
+
+ inputField.addEventListener("focus", () => {
+ panel.style.display = "block";
+ });
+
+ inputField.addEventListener("blur", () => {
+ setTimeout(() => {
+ panel.style.display = "none";
+ }, 100);
+ });
+
+ // Selection of an item
+ panelLayout.addEventListener("mousedown", (event) => {
+ const target = event.target.closest("li");
+
+ if (target) {
+ inputField.value = target.dataset.suggestion;
+ panel.style.display = "none";
+ }
+ });
+
+ this.isPanelInitialized = true;
+ };
+
+ watchCategoryChanges() {
+ const categoryField = document.querySelector('[data-ipn-suggestion="categoryField"]');
+ const descriptionField = document.querySelector('[data-ipn-suggestion="descriptionField"]');
+ this.previousCategoryId = Number(this.partCategoryIdValue);
+
+ if (categoryField) {
+ categoryField.addEventListener("change", () => {
+ const categoryId = Number(categoryField.value);
+ const description = String(descriptionField?.value ?? '');
+
+ // Check whether the category has changed compared to the previous ID
+ if (categoryId !== this.previousCategoryId) {
+ this.fetchNewSuggestions(categoryId, description);
+ this.previousCategoryId = categoryId;
+ }
+ });
+ }
+ }
+
+ watchDescriptionChanges() {
+ const categoryField = document.querySelector('[data-ipn-suggestion="categoryField"]');
+ const descriptionField = document.querySelector('[data-ipn-suggestion="descriptionField"]');
+ this.previousDescription = String(this.partDescriptionValue);
+
+ if (descriptionField) {
+ descriptionField.addEventListener("input", () => {
+ const categoryId = Number(categoryField.value);
+ const description = String(descriptionField?.value ?? '');
+
+ // Check whether the description has changed compared to the previous one
+ if (description !== this.previousDescription) {
+ this.fetchNewSuggestions(categoryId, description);
+ this.previousDescription = description;
+ }
+ });
+ }
+ }
+
+ fetchNewSuggestions(categoryId, description) {
+ const baseUrl = this.suggestUrlValue;
+ const partId = this.partIdValue;
+ const truncatedDescription = description.length > 150 ? description.substring(0, 150) : description;
+ const encodedDescription = this.base64EncodeUtf8(truncatedDescription);
+ const url = `${baseUrl}?partId=${partId}&categoryId=${categoryId}` + (description !== '' ? `&description=${encodedDescription}` : '');
+
+ fetch(url, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ "Accept": "application/json",
+ },
+ })
+ .then((response) => {
+ if (!response.ok) {
+ throw new Error(`Error when calling up the IPN-suggestions: ${response.status}`);
+ }
+ return response.json();
+ })
+ .then((data) => {
+ this.suggestionsValue = data;
+ this.configureAutocomplete();
+ })
+ .catch((error) => {
+ console.error("Errors when loading the new IPN-suggestions:", error);
+ });
+ };
+
+ base64EncodeUtf8(text) {
+ const utf8Bytes = new TextEncoder().encode(text);
+ return btoa(String.fromCharCode(...utf8Bytes));
+ };
+}
diff --git a/assets/controllers/pages/synonyms_collection_controller.js b/assets/controllers/pages/synonyms_collection_controller.js
new file mode 100644
index 00000000..6b2f4811
--- /dev/null
+++ b/assets/controllers/pages/synonyms_collection_controller.js
@@ -0,0 +1,68 @@
+/*
+ * 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 .
+ */
+
+import { Controller } from '@hotwired/stimulus';
+
+export default class extends Controller {
+ static targets = ['items'];
+ static values = {
+ prototype: String,
+ prototypeName: { type: String, default: '__name__' },
+ index: { type: Number, default: 0 },
+ };
+
+ connect() {
+ if (!this.hasIndexValue || Number.isNaN(this.indexValue)) {
+ this.indexValue = this.itemsTarget?.children.length || 0;
+ }
+ }
+
+ add(event) {
+ event.preventDefault();
+
+ const encodedProto = this.prototypeValue || '';
+ const placeholder = this.prototypeNameValue || '__name__';
+ if (!encodedProto || !this.itemsTarget) return;
+
+ const protoHtml = this._decodeHtmlAttribute(encodedProto);
+
+ const idx = this.indexValue;
+ const html = protoHtml.replace(new RegExp(placeholder, 'g'), String(idx));
+
+ const wrapper = document.createElement('div');
+ wrapper.innerHTML = html;
+ const newItem = wrapper.firstElementChild;
+ if (newItem) {
+ this.itemsTarget.appendChild(newItem);
+ this.indexValue = idx + 1;
+ }
+ }
+
+ remove(event) {
+ event.preventDefault();
+ const row = event.currentTarget.closest('.tc-item');
+ if (row) row.remove();
+ }
+
+ _decodeHtmlAttribute(str) {
+ const tmp = document.createElement('textarea');
+ tmp.innerHTML = str;
+ return tmp.value || tmp.textContent || '';
+ }
+}
diff --git a/composer.json b/composer.json
index f53130d4..1c6eafc7 100644
--- a/composer.json
+++ b/composer.json
@@ -118,6 +118,13 @@
"symfony/stopwatch": "7.3.*",
"symfony/web-profiler-bundle": "7.3.*"
},
+ "replace": {
+ "symfony/polyfill-mbstring": "*",
+ "symfony/polyfill-php74": "*",
+ "symfony/polyfill-php80": "*",
+ "symfony/polyfill-php81": "*",
+ "symfony/polyfill-php82": "*"
+ },
"suggest": {
"ext-bcmath": "Used to improve price calculation performance",
"ext-gmp": "Used to improve price calculation performanice"
diff --git a/composer.lock b/composer.lock
index 72e83e0f..d0142b5f 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "3b5a603cc4c289262a2e58b0f37ee42e",
+ "content-hash": "7e6a6a56cfdcc08fc186bb3894ae00e0",
"packages": [
{
"name": "amphp/amp",
@@ -968,7 +968,7 @@
},
{
"name": "api-platform/doctrine-common",
- "version": "v4.2.2",
+ "version": "v4.2.3",
"source": {
"type": "git",
"url": "https://github.com/api-platform/doctrine-common.git",
@@ -1052,22 +1052,22 @@
"rest"
],
"support": {
- "source": "https://github.com/api-platform/doctrine-common/tree/v4.2.2"
+ "source": "https://github.com/api-platform/doctrine-common/tree/v4.2.3"
},
"time": "2025-08-27T12:34:14+00:00"
},
{
"name": "api-platform/doctrine-orm",
- "version": "v4.2.2",
+ "version": "v4.2.3",
"source": {
"type": "git",
"url": "https://github.com/api-platform/doctrine-orm.git",
- "reference": "d35d97423f7b399117ee033ecc886b3ed9dc2e23"
+ "reference": "f30b580379ea16f6de3e27ecf8e474335af011f9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/api-platform/doctrine-orm/zipball/d35d97423f7b399117ee033ecc886b3ed9dc2e23",
- "reference": "d35d97423f7b399117ee033ecc886b3ed9dc2e23",
+ "url": "https://api.github.com/repos/api-platform/doctrine-orm/zipball/f30b580379ea16f6de3e27ecf8e474335af011f9",
+ "reference": "f30b580379ea16f6de3e27ecf8e474335af011f9",
"shasum": ""
},
"require": {
@@ -1139,13 +1139,13 @@
"rest"
],
"support": {
- "source": "https://github.com/api-platform/doctrine-orm/tree/v4.2.2"
+ "source": "https://github.com/api-platform/doctrine-orm/tree/v4.2.3"
},
- "time": "2025-10-07T13:54:25+00:00"
+ "time": "2025-10-31T11:51:24+00:00"
},
{
"name": "api-platform/documentation",
- "version": "v4.2.2",
+ "version": "v4.2.3",
"source": {
"type": "git",
"url": "https://github.com/api-platform/documentation.git",
@@ -1202,13 +1202,13 @@
],
"description": "API Platform documentation controller.",
"support": {
- "source": "https://github.com/api-platform/documentation/tree/v4.2.2"
+ "source": "https://github.com/api-platform/documentation/tree/v4.2.3"
},
"time": "2025-08-19T08:04:29+00:00"
},
{
"name": "api-platform/http-cache",
- "version": "v4.2.2",
+ "version": "v4.2.3",
"source": {
"type": "git",
"url": "https://github.com/api-platform/http-cache.git",
@@ -1282,22 +1282,22 @@
"rest"
],
"support": {
- "source": "https://github.com/api-platform/http-cache/tree/v4.2.2"
+ "source": "https://github.com/api-platform/http-cache/tree/v4.2.3"
},
"time": "2025-09-16T12:51:08+00:00"
},
{
"name": "api-platform/hydra",
- "version": "v4.2.2",
+ "version": "v4.2.3",
"source": {
"type": "git",
"url": "https://github.com/api-platform/hydra.git",
- "reference": "bfbe928e6a3999433ef11afc267e591152b17cc3"
+ "reference": "3ffe1232babfbba29ffbf52af1080aef5a015c65"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/api-platform/hydra/zipball/bfbe928e6a3999433ef11afc267e591152b17cc3",
- "reference": "bfbe928e6a3999433ef11afc267e591152b17cc3",
+ "url": "https://api.github.com/repos/api-platform/hydra/zipball/3ffe1232babfbba29ffbf52af1080aef5a015c65",
+ "reference": "3ffe1232babfbba29ffbf52af1080aef5a015c65",
"shasum": ""
},
"require": {
@@ -1369,13 +1369,13 @@
"rest"
],
"support": {
- "source": "https://github.com/api-platform/hydra/tree/v4.2.2"
+ "source": "https://github.com/api-platform/hydra/tree/v4.2.3"
},
- "time": "2025-10-07T13:39:38+00:00"
+ "time": "2025-10-24T09:59:50+00:00"
},
{
"name": "api-platform/json-api",
- "version": "v4.2.2",
+ "version": "v4.2.3",
"source": {
"type": "git",
"url": "https://github.com/api-platform/json-api.git",
@@ -1451,22 +1451,22 @@
"rest"
],
"support": {
- "source": "https://github.com/api-platform/json-api/tree/v4.2.2"
+ "source": "https://github.com/api-platform/json-api/tree/v4.2.3"
},
"time": "2025-09-16T12:49:22+00:00"
},
{
"name": "api-platform/json-schema",
- "version": "v4.2.2",
+ "version": "v4.2.3",
"source": {
"type": "git",
"url": "https://github.com/api-platform/json-schema.git",
- "reference": "ec81bdd09375067d7d2555c10ec3dfc697874327"
+ "reference": "aa8fe10d527e0ecb946ee4b873cfa97e02fb13c3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/api-platform/json-schema/zipball/ec81bdd09375067d7d2555c10ec3dfc697874327",
- "reference": "ec81bdd09375067d7d2555c10ec3dfc697874327",
+ "url": "https://api.github.com/repos/api-platform/json-schema/zipball/aa8fe10d527e0ecb946ee4b873cfa97e02fb13c3",
+ "reference": "aa8fe10d527e0ecb946ee4b873cfa97e02fb13c3",
"shasum": ""
},
"require": {
@@ -1532,13 +1532,13 @@
"swagger"
],
"support": {
- "source": "https://github.com/api-platform/json-schema/tree/v4.2.2"
+ "source": "https://github.com/api-platform/json-schema/tree/v4.2.3"
},
- "time": "2025-10-07T09:45:59+00:00"
+ "time": "2025-10-31T08:51:19+00:00"
},
{
"name": "api-platform/jsonld",
- "version": "v4.2.2",
+ "version": "v4.2.3",
"source": {
"type": "git",
"url": "https://github.com/api-platform/jsonld.git",
@@ -1612,22 +1612,22 @@
"rest"
],
"support": {
- "source": "https://github.com/api-platform/jsonld/tree/v4.2.2"
+ "source": "https://github.com/api-platform/jsonld/tree/v4.2.3"
},
"time": "2025-09-25T19:30:56+00:00"
},
{
"name": "api-platform/metadata",
- "version": "v4.2.2",
+ "version": "v4.2.3",
"source": {
"type": "git",
"url": "https://github.com/api-platform/metadata.git",
- "reference": "5aeba910cb6352068664ca11cd9ee0dc208894c0"
+ "reference": "4a7676a1787b71730e1bcce83fc8987df745cb2c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/api-platform/metadata/zipball/5aeba910cb6352068664ca11cd9ee0dc208894c0",
- "reference": "5aeba910cb6352068664ca11cd9ee0dc208894c0",
+ "url": "https://api.github.com/repos/api-platform/metadata/zipball/4a7676a1787b71730e1bcce83fc8987df745cb2c",
+ "reference": "4a7676a1787b71730e1bcce83fc8987df745cb2c",
"shasum": ""
},
"require": {
@@ -1710,13 +1710,13 @@
"swagger"
],
"support": {
- "source": "https://github.com/api-platform/metadata/tree/v4.2.2"
+ "source": "https://github.com/api-platform/metadata/tree/v4.2.3"
},
- "time": "2025-10-08T08:36:37+00:00"
+ "time": "2025-10-31T08:55:46+00:00"
},
{
"name": "api-platform/openapi",
- "version": "v4.2.2",
+ "version": "v4.2.3",
"source": {
"type": "git",
"url": "https://github.com/api-platform/openapi.git",
@@ -1800,22 +1800,22 @@
"swagger"
],
"support": {
- "source": "https://github.com/api-platform/openapi/tree/v4.2.2"
+ "source": "https://github.com/api-platform/openapi/tree/v4.2.3"
},
"time": "2025-09-30T12:06:50+00:00"
},
{
"name": "api-platform/serializer",
- "version": "v4.2.2",
+ "version": "v4.2.3",
"source": {
"type": "git",
"url": "https://github.com/api-platform/serializer.git",
- "reference": "58c1378af6429049ee62951b838f6b645706c3a3"
+ "reference": "50255df8751ffa81aea0eb0455bd248e9c8c2aa7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/api-platform/serializer/zipball/58c1378af6429049ee62951b838f6b645706c3a3",
- "reference": "58c1378af6429049ee62951b838f6b645706c3a3",
+ "url": "https://api.github.com/repos/api-platform/serializer/zipball/50255df8751ffa81aea0eb0455bd248e9c8c2aa7",
+ "reference": "50255df8751ffa81aea0eb0455bd248e9c8c2aa7",
"shasum": ""
},
"require": {
@@ -1893,22 +1893,22 @@
"serializer"
],
"support": {
- "source": "https://github.com/api-platform/serializer/tree/v4.2.2"
+ "source": "https://github.com/api-platform/serializer/tree/v4.2.3"
},
- "time": "2025-10-03T08:13:34+00:00"
+ "time": "2025-10-31T14:00:01+00:00"
},
{
"name": "api-platform/state",
- "version": "v4.2.2",
+ "version": "v4.2.3",
"source": {
"type": "git",
"url": "https://github.com/api-platform/state.git",
- "reference": "7dc3dfbafa4627cc789bd8bcd4da46e6c37f6b26"
+ "reference": "5a74ea2ca36d0651bf637b0da6c10db4383172bf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/api-platform/state/zipball/7dc3dfbafa4627cc789bd8bcd4da46e6c37f6b26",
- "reference": "7dc3dfbafa4627cc789bd8bcd4da46e6c37f6b26",
+ "url": "https://api.github.com/repos/api-platform/state/zipball/5a74ea2ca36d0651bf637b0da6c10db4383172bf",
+ "reference": "5a74ea2ca36d0651bf637b0da6c10db4383172bf",
"shasum": ""
},
"require": {
@@ -1989,22 +1989,22 @@
"swagger"
],
"support": {
- "source": "https://github.com/api-platform/state/tree/v4.2.2"
+ "source": "https://github.com/api-platform/state/tree/v4.2.3"
},
- "time": "2025-10-09T08:33:56+00:00"
+ "time": "2025-10-31T10:04:25+00:00"
},
{
"name": "api-platform/symfony",
- "version": "v4.2.2",
+ "version": "v4.2.3",
"source": {
"type": "git",
"url": "https://github.com/api-platform/symfony.git",
- "reference": "44bb117df1cd5695203ec6a97999cabc85257780"
+ "reference": "a07233f9a1cb20dcb141056ac767c28c62c74269"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/api-platform/symfony/zipball/44bb117df1cd5695203ec6a97999cabc85257780",
- "reference": "44bb117df1cd5695203ec6a97999cabc85257780",
+ "url": "https://api.github.com/repos/api-platform/symfony/zipball/a07233f9a1cb20dcb141056ac767c28c62c74269",
+ "reference": "a07233f9a1cb20dcb141056ac767c28c62c74269",
"shasum": ""
},
"require": {
@@ -2019,6 +2019,7 @@
"api-platform/state": "^4.2@beta",
"api-platform/validator": "^4.1.11",
"php": ">=8.2",
+ "symfony/asset": "^6.4 || ^7.0",
"symfony/finder": "^6.4 || ^7.0",
"symfony/property-access": "^6.4 || ^7.0",
"symfony/property-info": "^6.4 || ^7.1",
@@ -2118,22 +2119,22 @@
"symfony"
],
"support": {
- "source": "https://github.com/api-platform/symfony/tree/v4.2.2"
+ "source": "https://github.com/api-platform/symfony/tree/v4.2.3"
},
- "time": "2025-10-09T08:33:56+00:00"
+ "time": "2025-10-31T08:55:46+00:00"
},
{
"name": "api-platform/validator",
- "version": "v4.2.2",
+ "version": "v4.2.3",
"source": {
"type": "git",
"url": "https://github.com/api-platform/validator.git",
- "reference": "9986e84b27e3de7f87c7c23f238430a572f87f67"
+ "reference": "bb8697d3676f9034865dfbf96df9e55734aecad5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/api-platform/validator/zipball/9986e84b27e3de7f87c7c23f238430a572f87f67",
- "reference": "9986e84b27e3de7f87c7c23f238430a572f87f67",
+ "url": "https://api.github.com/repos/api-platform/validator/zipball/bb8697d3676f9034865dfbf96df9e55734aecad5",
+ "reference": "bb8697d3676f9034865dfbf96df9e55734aecad5",
"shasum": ""
},
"require": {
@@ -2194,9 +2195,9 @@
"validator"
],
"support": {
- "source": "https://github.com/api-platform/validator/tree/v4.2.2"
+ "source": "https://github.com/api-platform/validator/tree/v4.2.3"
},
- "time": "2025-09-29T15:36:04+00:00"
+ "time": "2025-10-31T11:51:24+00:00"
},
{
"name": "beberlei/assert",
@@ -2389,16 +2390,16 @@
},
{
"name": "composer/ca-bundle",
- "version": "1.5.8",
+ "version": "1.5.9",
"source": {
"type": "git",
"url": "https://github.com/composer/ca-bundle.git",
- "reference": "719026bb30813accb68271fee7e39552a58e9f65"
+ "reference": "1905981ee626e6f852448b7aaa978f8666c5bc54"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/ca-bundle/zipball/719026bb30813accb68271fee7e39552a58e9f65",
- "reference": "719026bb30813accb68271fee7e39552a58e9f65",
+ "url": "https://api.github.com/repos/composer/ca-bundle/zipball/1905981ee626e6f852448b7aaa978f8666c5bc54",
+ "reference": "1905981ee626e6f852448b7aaa978f8666c5bc54",
"shasum": ""
},
"require": {
@@ -2445,7 +2446,7 @@
"support": {
"irc": "irc://irc.freenode.org/composer",
"issues": "https://github.com/composer/ca-bundle/issues",
- "source": "https://github.com/composer/ca-bundle/tree/1.5.8"
+ "source": "https://github.com/composer/ca-bundle/tree/1.5.9"
},
"funding": [
{
@@ -2457,7 +2458,7 @@
"type": "github"
}
],
- "time": "2025-08-20T18:49:47+00:00"
+ "time": "2025-11-06T11:46:17+00:00"
},
{
"name": "composer/package-versions-deprecated",
@@ -2732,16 +2733,16 @@
},
{
"name": "doctrine/collections",
- "version": "2.3.0",
+ "version": "2.4.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/collections.git",
- "reference": "2eb07e5953eed811ce1b309a7478a3b236f2273d"
+ "reference": "9acfeea2e8666536edff3d77c531261c63680160"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/collections/zipball/2eb07e5953eed811ce1b309a7478a3b236f2273d",
- "reference": "2eb07e5953eed811ce1b309a7478a3b236f2273d",
+ "url": "https://api.github.com/repos/doctrine/collections/zipball/9acfeea2e8666536edff3d77c531261c63680160",
+ "reference": "9acfeea2e8666536edff3d77c531261c63680160",
"shasum": ""
},
"require": {
@@ -2750,11 +2751,11 @@
"symfony/polyfill-php84": "^1.30"
},
"require-dev": {
- "doctrine/coding-standard": "^12",
+ "doctrine/coding-standard": "^14",
"ext-json": "*",
- "phpstan/phpstan": "^1.8",
- "phpstan/phpstan-phpunit": "^1.0",
- "phpunit/phpunit": "^10.5"
+ "phpstan/phpstan": "^2.1.30",
+ "phpstan/phpstan-phpunit": "^2.0.7",
+ "phpunit/phpunit": "^10.5.58 || ^11.5.42 || ^12.4"
},
"type": "library",
"autoload": {
@@ -2798,7 +2799,7 @@
],
"support": {
"issues": "https://github.com/doctrine/collections/issues",
- "source": "https://github.com/doctrine/collections/tree/2.3.0"
+ "source": "https://github.com/doctrine/collections/tree/2.4.0"
},
"funding": [
{
@@ -2814,7 +2815,7 @@
"type": "tidelift"
}
],
- "time": "2025-03-22T10:17:19+00:00"
+ "time": "2025-10-25T09:18:13+00:00"
},
{
"name": "doctrine/common",
@@ -3146,16 +3147,16 @@
},
{
"name": "doctrine/doctrine-bundle",
- "version": "2.18.0",
+ "version": "2.18.1",
"source": {
"type": "git",
"url": "https://github.com/doctrine/DoctrineBundle.git",
- "reference": "cd5d4da6a5f7cf3d8708e17211234657b5eb4e95"
+ "reference": "b769877014de053da0e5cbbb63d0ea2f3b2fea76"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/cd5d4da6a5f7cf3d8708e17211234657b5eb4e95",
- "reference": "cd5d4da6a5f7cf3d8708e17211234657b5eb4e95",
+ "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/b769877014de053da0e5cbbb63d0ea2f3b2fea76",
+ "reference": "b769877014de053da0e5cbbb63d0ea2f3b2fea76",
"shasum": ""
},
"require": {
@@ -3247,7 +3248,7 @@
],
"support": {
"issues": "https://github.com/doctrine/DoctrineBundle/issues",
- "source": "https://github.com/doctrine/DoctrineBundle/tree/2.18.0"
+ "source": "https://github.com/doctrine/DoctrineBundle/tree/2.18.1"
},
"funding": [
{
@@ -3263,20 +3264,20 @@
"type": "tidelift"
}
],
- "time": "2025-10-11T04:43:27+00:00"
+ "time": "2025-11-05T14:42:10+00:00"
},
{
"name": "doctrine/doctrine-migrations-bundle",
- "version": "3.5.0",
+ "version": "3.6.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/DoctrineMigrationsBundle.git",
- "reference": "71c81279ca0e907c3edc718418b93fd63074856c"
+ "reference": "49ecc564568d7da101779112579e78b677fbc94a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/71c81279ca0e907c3edc718418b93fd63074856c",
- "reference": "71c81279ca0e907c3edc718418b93fd63074856c",
+ "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/49ecc564568d7da101779112579e78b677fbc94a",
+ "reference": "49ecc564568d7da101779112579e78b677fbc94a",
"shasum": ""
},
"require": {
@@ -3284,7 +3285,7 @@
"doctrine/migrations": "^3.2",
"php": "^7.2 || ^8.0",
"symfony/deprecation-contracts": "^2.1 || ^3",
- "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0"
+ "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0 || ^8.0"
},
"require-dev": {
"composer/semver": "^3.0",
@@ -3296,8 +3297,8 @@
"phpstan/phpstan-strict-rules": "^1.1 || ^2",
"phpstan/phpstan-symfony": "^1.3 || ^2",
"phpunit/phpunit": "^8.5 || ^9.5",
- "symfony/phpunit-bridge": "^6.3 || ^7",
- "symfony/var-exporter": "^5.4 || ^6 || ^7"
+ "symfony/phpunit-bridge": "^6.3 || ^7 || ^8",
+ "symfony/var-exporter": "^5.4 || ^6 || ^7 || ^8"
},
"type": "symfony-bundle",
"autoload": {
@@ -3332,7 +3333,7 @@
],
"support": {
"issues": "https://github.com/doctrine/DoctrineMigrationsBundle/issues",
- "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/3.5.0"
+ "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/3.6.0"
},
"funding": [
{
@@ -3348,7 +3349,7 @@
"type": "tidelift"
}
],
- "time": "2025-10-12T17:06:40+00:00"
+ "time": "2025-11-07T19:40:03+00:00"
},
{
"name": "doctrine/event-manager",
@@ -3783,16 +3784,16 @@
},
{
"name": "doctrine/orm",
- "version": "3.5.2",
+ "version": "3.5.7",
"source": {
"type": "git",
"url": "https://github.com/doctrine/orm.git",
- "reference": "5a541b8b3a327ab1ea5f93b1615b4ff67a34e109"
+ "reference": "f18de9d569f00ed6eb9dac4b33c7844d705d17da"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/orm/zipball/5a541b8b3a327ab1ea5f93b1615b4ff67a34e109",
- "reference": "5a541b8b3a327ab1ea5f93b1615b4ff67a34e109",
+ "url": "https://api.github.com/repos/doctrine/orm/zipball/f18de9d569f00ed6eb9dac4b33c7844d705d17da",
+ "reference": "f18de9d569f00ed6eb9dac4b33c7844d705d17da",
"shasum": ""
},
"require": {
@@ -3812,15 +3813,14 @@
"symfony/var-exporter": "^6.3.9 || ^7.0"
},
"require-dev": {
- "doctrine/coding-standard": "^13.0",
+ "doctrine/coding-standard": "^14.0",
"phpbench/phpbench": "^1.0",
"phpdocumentor/guides-cli": "^1.4",
"phpstan/extension-installer": "^1.4",
- "phpstan/phpstan": "2.0.3",
+ "phpstan/phpstan": "2.1.23",
"phpstan/phpstan-deprecation-rules": "^2",
- "phpunit/phpunit": "^10.4.0",
+ "phpunit/phpunit": "^10.5.0 || ^11.5",
"psr/log": "^1 || ^2 || ^3",
- "squizlabs/php_codesniffer": "3.12.0",
"symfony/cache": "^5.4 || ^6.2 || ^7.0"
},
"suggest": {
@@ -3867,9 +3867,9 @@
],
"support": {
"issues": "https://github.com/doctrine/orm/issues",
- "source": "https://github.com/doctrine/orm/tree/3.5.2"
+ "source": "https://github.com/doctrine/orm/tree/3.5.7"
},
- "time": "2025-08-08T17:00:40+00:00"
+ "time": "2025-11-11T18:27:40+00:00"
},
{
"name": "doctrine/persistence",
@@ -3966,26 +3966,26 @@
},
{
"name": "doctrine/sql-formatter",
- "version": "1.5.2",
+ "version": "1.5.3",
"source": {
"type": "git",
"url": "https://github.com/doctrine/sql-formatter.git",
- "reference": "d6d00aba6fd2957fe5216fe2b7673e9985db20c8"
+ "reference": "a8af23a8e9d622505baa2997465782cbe8bb7fc7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/sql-formatter/zipball/d6d00aba6fd2957fe5216fe2b7673e9985db20c8",
- "reference": "d6d00aba6fd2957fe5216fe2b7673e9985db20c8",
+ "url": "https://api.github.com/repos/doctrine/sql-formatter/zipball/a8af23a8e9d622505baa2997465782cbe8bb7fc7",
+ "reference": "a8af23a8e9d622505baa2997465782cbe8bb7fc7",
"shasum": ""
},
"require": {
"php": "^8.1"
},
"require-dev": {
- "doctrine/coding-standard": "^12",
- "ergebnis/phpunit-slow-test-detector": "^2.14",
- "phpstan/phpstan": "^1.10",
- "phpunit/phpunit": "^10.5"
+ "doctrine/coding-standard": "^14",
+ "ergebnis/phpunit-slow-test-detector": "^2.20",
+ "phpstan/phpstan": "^2.1.31",
+ "phpunit/phpunit": "^10.5.58"
},
"bin": [
"bin/sql-formatter"
@@ -4015,22 +4015,22 @@
],
"support": {
"issues": "https://github.com/doctrine/sql-formatter/issues",
- "source": "https://github.com/doctrine/sql-formatter/tree/1.5.2"
+ "source": "https://github.com/doctrine/sql-formatter/tree/1.5.3"
},
- "time": "2025-01-24T11:45:48+00:00"
+ "time": "2025-10-26T09:35:14+00:00"
},
{
"name": "dompdf/dompdf",
- "version": "v3.1.3",
+ "version": "v3.1.4",
"source": {
"type": "git",
"url": "https://github.com/dompdf/dompdf.git",
- "reference": "baed300e4fb8226359c04395518059a136e2a2e2"
+ "reference": "db712c90c5b9868df3600e64e68da62e78a34623"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/dompdf/dompdf/zipball/baed300e4fb8226359c04395518059a136e2a2e2",
- "reference": "baed300e4fb8226359c04395518059a136e2a2e2",
+ "url": "https://api.github.com/repos/dompdf/dompdf/zipball/db712c90c5b9868df3600e64e68da62e78a34623",
+ "reference": "db712c90c5b9868df3600e64e68da62e78a34623",
"shasum": ""
},
"require": {
@@ -4079,9 +4079,9 @@
"homepage": "https://github.com/dompdf/dompdf",
"support": {
"issues": "https://github.com/dompdf/dompdf/issues",
- "source": "https://github.com/dompdf/dompdf/tree/v3.1.3"
+ "source": "https://github.com/dompdf/dompdf/tree/v3.1.4"
},
- "time": "2025-10-14T13:10:17+00:00"
+ "time": "2025-10-29T12:43:30+00:00"
},
{
"name": "dompdf/php-font-lib",
@@ -5765,16 +5765,16 @@
},
{
"name": "league/csv",
- "version": "9.27.0",
+ "version": "9.27.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/csv.git",
- "reference": "cb491b1ba3c42ff2bcd0113814f4256b42bae845"
+ "reference": "26de738b8fccf785397d05ee2fc07b6cd8749797"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/csv/zipball/cb491b1ba3c42ff2bcd0113814f4256b42bae845",
- "reference": "cb491b1ba3c42ff2bcd0113814f4256b42bae845",
+ "url": "https://api.github.com/repos/thephpleague/csv/zipball/26de738b8fccf785397d05ee2fc07b6cd8749797",
+ "reference": "26de738b8fccf785397d05ee2fc07b6cd8749797",
"shasum": ""
},
"require": {
@@ -5852,7 +5852,7 @@
"type": "github"
}
],
- "time": "2025-10-16T08:22:09+00:00"
+ "time": "2025-10-25T08:35:20+00:00"
},
{
"name": "league/html-to-markdown",
@@ -6977,25 +6977,28 @@
},
{
"name": "nelmio/cors-bundle",
- "version": "2.5.0",
+ "version": "2.6.0",
"source": {
"type": "git",
"url": "https://github.com/nelmio/NelmioCorsBundle.git",
- "reference": "3a526fe025cd20e04a6a11370cf5ab28dbb5a544"
+ "reference": "530217472204881cacd3671909f634b960c7b948"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/3a526fe025cd20e04a6a11370cf5ab28dbb5a544",
- "reference": "3a526fe025cd20e04a6a11370cf5ab28dbb5a544",
+ "url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/530217472204881cacd3671909f634b960c7b948",
+ "reference": "530217472204881cacd3671909f634b960c7b948",
"shasum": ""
},
"require": {
"psr/log": "^1.0 || ^2.0 || ^3.0",
- "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0"
+ "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0 || ^8.0"
},
"require-dev": {
- "mockery/mockery": "^1.3.6",
- "symfony/phpunit-bridge": "^5.4 || ^6.0 || ^7.0"
+ "phpstan/phpstan": "^1.11.5",
+ "phpstan/phpstan-deprecation-rules": "^1.2.0",
+ "phpstan/phpstan-phpunit": "^1.4",
+ "phpstan/phpstan-symfony": "^1.4.4",
+ "phpunit/phpunit": "^8"
},
"type": "symfony-bundle",
"extra": {
@@ -7033,9 +7036,9 @@
],
"support": {
"issues": "https://github.com/nelmio/NelmioCorsBundle/issues",
- "source": "https://github.com/nelmio/NelmioCorsBundle/tree/2.5.0"
+ "source": "https://github.com/nelmio/NelmioCorsBundle/tree/2.6.0"
},
- "time": "2024-06-24T21:25:28+00:00"
+ "time": "2025-10-23T06:57:22+00:00"
},
{
"name": "nelmio/security-bundle",
@@ -7113,25 +7116,25 @@
},
{
"name": "nette/schema",
- "version": "v1.3.2",
+ "version": "v1.3.3",
"source": {
"type": "git",
"url": "https://github.com/nette/schema.git",
- "reference": "da801d52f0354f70a638673c4a0f04e16529431d"
+ "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nette/schema/zipball/da801d52f0354f70a638673c4a0f04e16529431d",
- "reference": "da801d52f0354f70a638673c4a0f04e16529431d",
+ "url": "https://api.github.com/repos/nette/schema/zipball/2befc2f42d7c715fd9d95efc31b1081e5d765004",
+ "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004",
"shasum": ""
},
"require": {
"nette/utils": "^4.0",
- "php": "8.1 - 8.4"
+ "php": "8.1 - 8.5"
},
"require-dev": {
"nette/tester": "^2.5.2",
- "phpstan/phpstan-nette": "^1.0",
+ "phpstan/phpstan-nette": "^2.0@stable",
"tracy/tracy": "^2.8"
},
"type": "library",
@@ -7141,6 +7144,9 @@
}
},
"autoload": {
+ "psr-4": {
+ "Nette\\": "src"
+ },
"classmap": [
"src/"
]
@@ -7169,9 +7175,9 @@
],
"support": {
"issues": "https://github.com/nette/schema/issues",
- "source": "https://github.com/nette/schema/tree/v1.3.2"
+ "source": "https://github.com/nette/schema/tree/v1.3.3"
},
- "time": "2024-10-06T23:10:23+00:00"
+ "time": "2025-10-30T22:57:59+00:00"
},
{
"name": "nette/utils",
@@ -8468,16 +8474,16 @@
},
{
"name": "phpoffice/phpspreadsheet",
- "version": "5.1.0",
+ "version": "5.2.0",
"source": {
"type": "git",
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
- "reference": "fd26e45a814e94ae2aad0df757d9d1739c4bf2e0"
+ "reference": "3b8994b3aac4b61018bc04fc8c441f4fd68c18eb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fd26e45a814e94ae2aad0df757d9d1739c4bf2e0",
- "reference": "fd26e45a814e94ae2aad0df757d9d1739c4bf2e0",
+ "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/3b8994b3aac4b61018bc04fc8c441f4fd68c18eb",
+ "reference": "3b8994b3aac4b61018bc04fc8c441f4fd68c18eb",
"shasum": ""
},
"require": {
@@ -8507,7 +8513,7 @@
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
"dompdf/dompdf": "^2.0 || ^3.0",
"friendsofphp/php-cs-fixer": "^3.2",
- "mitoteam/jpgraph": "^10.3",
+ "mitoteam/jpgraph": "^10.5",
"mpdf/mpdf": "^8.1.1",
"phpcompatibility/php-compatibility": "^9.3",
"phpstan/phpstan": "^1.1 || ^2.0",
@@ -8519,7 +8525,7 @@
},
"suggest": {
"dompdf/dompdf": "Option for rendering PDF with PDF Writer",
- "ext-intl": "PHP Internationalization Functions",
+ "ext-intl": "PHP Internationalization Functions, regquired for NumberFormat Wizard",
"mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer"
@@ -8568,9 +8574,9 @@
],
"support": {
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
- "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/5.1.0"
+ "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/5.2.0"
},
- "time": "2025-09-04T05:34:49+00:00"
+ "time": "2025-10-26T15:54:22+00:00"
},
{
"name": "phpstan/phpdoc-parser",
@@ -9481,16 +9487,16 @@
},
{
"name": "s9e/text-formatter",
- "version": "2.19.0",
+ "version": "2.19.1",
"source": {
"type": "git",
"url": "https://github.com/s9e/TextFormatter.git",
- "reference": "d65a4f61cbe494937afb3150dc73b6e757d400d3"
+ "reference": "47c8324f370cc23e72190f00a4ffb18f50516452"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/d65a4f61cbe494937afb3150dc73b6e757d400d3",
- "reference": "d65a4f61cbe494937afb3150dc73b6e757d400d3",
+ "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/47c8324f370cc23e72190f00a4ffb18f50516452",
+ "reference": "47c8324f370cc23e72190f00a4ffb18f50516452",
"shasum": ""
},
"require": {
@@ -9518,7 +9524,7 @@
},
"type": "library",
"extra": {
- "version": "2.19.0"
+ "version": "2.19.1"
},
"autoload": {
"psr-4": {
@@ -9550,9 +9556,9 @@
],
"support": {
"issues": "https://github.com/s9e/TextFormatter/issues",
- "source": "https://github.com/s9e/TextFormatter/tree/2.19.0"
+ "source": "https://github.com/s9e/TextFormatter/tree/2.19.1"
},
- "time": "2025-04-26T09:27:34+00:00"
+ "time": "2025-10-26T07:38:53+00:00"
},
{
"name": "sabberworm/php-css-parser",
@@ -9963,40 +9969,28 @@
},
{
"name": "spomky-labs/cbor-php",
- "version": "3.1.1",
+ "version": "3.2.0",
"source": {
"type": "git",
"url": "https://github.com/Spomky-Labs/cbor-php.git",
- "reference": "5404f3e21cbe72f5cf612aa23db2b922fd2f43bf"
+ "reference": "53b2adc63d126ddd062016969ce2bad5871fda6c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/5404f3e21cbe72f5cf612aa23db2b922fd2f43bf",
- "reference": "5404f3e21cbe72f5cf612aa23db2b922fd2f43bf",
+ "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/53b2adc63d126ddd062016969ce2bad5871fda6c",
+ "reference": "53b2adc63d126ddd062016969ce2bad5871fda6c",
"shasum": ""
},
"require": {
- "brick/math": "^0.9|^0.10|^0.11|^0.12|^0.13",
+ "brick/math": "^0.9|^0.10|^0.11|^0.12|^0.13|^0.14",
"ext-mbstring": "*",
"php": ">=8.0"
},
"require-dev": {
- "deptrac/deptrac": "^3.0",
- "ekino/phpstan-banned-code": "^1.0|^2.0|^3.0",
"ext-json": "*",
- "infection/infection": "^0.29",
- "php-parallel-lint/php-parallel-lint": "^1.3",
- "phpstan/extension-installer": "^1.1",
- "phpstan/phpstan": "^1.0|^2.0",
- "phpstan/phpstan-beberlei-assert": "^1.0|^2.0",
- "phpstan/phpstan-deprecation-rules": "^1.0|^2.0",
- "phpstan/phpstan-phpunit": "^1.0|^2.0",
- "phpstan/phpstan-strict-rules": "^1.0|^2.0",
- "phpunit/phpunit": "^10.1|^11.0|^12.0",
- "rector/rector": "^1.0|^2.0",
"roave/security-advisories": "dev-latest",
- "symfony/var-dumper": "^6.0|^7.0",
- "symplify/easy-coding-standard": "^12.0"
+ "symfony/error-handler": "^6.4|^7.1|^8.0",
+ "symfony/var-dumper": "^6.4|^7.1|^8.0"
},
"suggest": {
"ext-bcmath": "GMP or BCMath extensions will drastically improve the library performance. BCMath extension needed to handle the Big Float and Decimal Fraction Tags",
@@ -10030,7 +10024,7 @@
],
"support": {
"issues": "https://github.com/Spomky-Labs/cbor-php/issues",
- "source": "https://github.com/Spomky-Labs/cbor-php/tree/3.1.1"
+ "source": "https://github.com/Spomky-Labs/cbor-php/tree/3.2.0"
},
"funding": [
{
@@ -10042,7 +10036,7 @@
"type": "patreon"
}
],
- "time": "2025-06-13T11:57:55+00:00"
+ "time": "2025-11-07T09:45:05+00:00"
},
{
"name": "spomky-labs/otphp",
@@ -10128,20 +10122,20 @@
},
{
"name": "spomky-labs/pki-framework",
- "version": "1.3.0",
+ "version": "1.4.0",
"source": {
"type": "git",
"url": "https://github.com/Spomky-Labs/pki-framework.git",
- "reference": "eced5b5ce70518b983ff2be486e902bbd15135ae"
+ "reference": "bf6f55a9d9eb25b7781640221cb54f5c727850d7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/eced5b5ce70518b983ff2be486e902bbd15135ae",
- "reference": "eced5b5ce70518b983ff2be486e902bbd15135ae",
+ "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/bf6f55a9d9eb25b7781640221cb54f5c727850d7",
+ "reference": "bf6f55a9d9eb25b7781640221cb54f5c727850d7",
"shasum": ""
},
"require": {
- "brick/math": "^0.10|^0.11|^0.12|^0.13",
+ "brick/math": "^0.10|^0.11|^0.12|^0.13|^0.14",
"ext-mbstring": "*",
"php": ">=8.1"
},
@@ -10149,7 +10143,7 @@
"ekino/phpstan-banned-code": "^1.0|^2.0|^3.0",
"ext-gmp": "*",
"ext-openssl": "*",
- "infection/infection": "^0.28|^0.29",
+ "infection/infection": "^0.28|^0.29|^0.31",
"php-parallel-lint/php-parallel-lint": "^1.3",
"phpstan/extension-installer": "^1.3|^2.0",
"phpstan/phpstan": "^1.8|^2.0",
@@ -10159,8 +10153,8 @@
"phpunit/phpunit": "^10.1|^11.0|^12.0",
"rector/rector": "^1.0|^2.0",
"roave/security-advisories": "dev-latest",
- "symfony/string": "^6.4|^7.0",
- "symfony/var-dumper": "^6.4|^7.0",
+ "symfony/string": "^6.4|^7.0|^8.0",
+ "symfony/var-dumper": "^6.4|^7.0|^8.0",
"symplify/easy-coding-standard": "^12.0"
},
"suggest": {
@@ -10221,7 +10215,7 @@
],
"support": {
"issues": "https://github.com/Spomky-Labs/pki-framework/issues",
- "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.3.0"
+ "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.4.0"
},
"funding": [
{
@@ -10233,7 +10227,7 @@
"type": "patreon"
}
],
- "time": "2025-06-13T08:35:04+00:00"
+ "time": "2025-10-22T08:24:34+00:00"
},
{
"name": "symfony/apache-pack",
@@ -10332,16 +10326,16 @@
},
{
"name": "symfony/cache",
- "version": "v7.3.4",
+ "version": "v7.3.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/cache.git",
- "reference": "bf8afc8ffd4bfd3d9c373e417f041d9f1e5b863f"
+ "reference": "1277a1ec61c8d93ea61b2a59738f1deb9bfb6701"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/cache/zipball/bf8afc8ffd4bfd3d9c373e417f041d9f1e5b863f",
- "reference": "bf8afc8ffd4bfd3d9c373e417f041d9f1e5b863f",
+ "url": "https://api.github.com/repos/symfony/cache/zipball/1277a1ec61c8d93ea61b2a59738f1deb9bfb6701",
+ "reference": "1277a1ec61c8d93ea61b2a59738f1deb9bfb6701",
"shasum": ""
},
"require": {
@@ -10410,7 +10404,7 @@
"psr6"
],
"support": {
- "source": "https://github.com/symfony/cache/tree/v7.3.4"
+ "source": "https://github.com/symfony/cache/tree/v7.3.6"
},
"funding": [
{
@@ -10430,7 +10424,7 @@
"type": "tidelift"
}
],
- "time": "2025-09-11T10:12:26+00:00"
+ "time": "2025-10-30T13:22:58+00:00"
},
{
"name": "symfony/cache-contracts",
@@ -10584,16 +10578,16 @@
},
{
"name": "symfony/config",
- "version": "v7.3.4",
+ "version": "v7.3.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
- "reference": "8a09223170046d2cfda3d2e11af01df2c641e961"
+ "reference": "9d18eba95655a3152ae4c1d53c6cc34eb4d4a0b7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/config/zipball/8a09223170046d2cfda3d2e11af01df2c641e961",
- "reference": "8a09223170046d2cfda3d2e11af01df2c641e961",
+ "url": "https://api.github.com/repos/symfony/config/zipball/9d18eba95655a3152ae4c1d53c6cc34eb4d4a0b7",
+ "reference": "9d18eba95655a3152ae4c1d53c6cc34eb4d4a0b7",
"shasum": ""
},
"require": {
@@ -10639,7 +10633,7 @@
"description": "Helps you find, load, combine, autofill and validate configuration values of any kind",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/config/tree/v7.3.4"
+ "source": "https://github.com/symfony/config/tree/v7.3.6"
},
"funding": [
{
@@ -10659,20 +10653,20 @@
"type": "tidelift"
}
],
- "time": "2025-09-22T12:46:16+00:00"
+ "time": "2025-11-02T08:04:43+00:00"
},
{
"name": "symfony/console",
- "version": "v7.3.4",
+ "version": "v7.3.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db"
+ "reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/2b9c5fafbac0399a20a2e82429e2bd735dcfb7db",
- "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db",
+ "url": "https://api.github.com/repos/symfony/console/zipball/c28ad91448f86c5f6d9d2c70f0cf68bf135f252a",
+ "reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a",
"shasum": ""
},
"require": {
@@ -10737,7 +10731,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v7.3.4"
+ "source": "https://github.com/symfony/console/tree/v7.3.6"
},
"funding": [
{
@@ -10757,20 +10751,20 @@
"type": "tidelift"
}
],
- "time": "2025-09-22T15:31:00+00:00"
+ "time": "2025-11-04T01:21:42+00:00"
},
{
"name": "symfony/css-selector",
- "version": "v7.3.0",
+ "version": "v7.3.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
- "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2"
+ "reference": "84321188c4754e64273b46b406081ad9b18e8614"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/css-selector/zipball/601a5ce9aaad7bf10797e3663faefce9e26c24e2",
- "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/84321188c4754e64273b46b406081ad9b18e8614",
+ "reference": "84321188c4754e64273b46b406081ad9b18e8614",
"shasum": ""
},
"require": {
@@ -10806,7 +10800,7 @@
"description": "Converts CSS selectors to XPath expressions",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/css-selector/tree/v7.3.0"
+ "source": "https://github.com/symfony/css-selector/tree/v7.3.6"
},
"funding": [
{
@@ -10817,25 +10811,29 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2024-09-25T14:21:43+00:00"
+ "time": "2025-10-29T17:24:25+00:00"
},
{
"name": "symfony/dependency-injection",
- "version": "v7.3.4",
+ "version": "v7.3.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
- "reference": "82119812ab0bf3425c1234d413efd1b19bb92ae4"
+ "reference": "98af8bb46c56aedd9dd5a7f0414fc72bf2dcfe69"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/82119812ab0bf3425c1234d413efd1b19bb92ae4",
- "reference": "82119812ab0bf3425c1234d413efd1b19bb92ae4",
+ "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/98af8bb46c56aedd9dd5a7f0414fc72bf2dcfe69",
+ "reference": "98af8bb46c56aedd9dd5a7f0414fc72bf2dcfe69",
"shasum": ""
},
"require": {
@@ -10886,7 +10884,7 @@
"description": "Allows you to standardize and centralize the way objects are constructed in your application",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/dependency-injection/tree/v7.3.4"
+ "source": "https://github.com/symfony/dependency-injection/tree/v7.3.6"
},
"funding": [
{
@@ -10906,7 +10904,7 @@
"type": "tidelift"
}
],
- "time": "2025-09-11T10:12:26+00:00"
+ "time": "2025-10-31T10:11:11+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -10977,16 +10975,16 @@
},
{
"name": "symfony/doctrine-bridge",
- "version": "v7.3.4",
+ "version": "v7.3.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/doctrine-bridge.git",
- "reference": "21cd48c34a47a0d0e303a590a67c3450fde55888"
+ "reference": "e7d308bd44ff8673a259e2727d13af6a93a5d83e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/21cd48c34a47a0d0e303a590a67c3450fde55888",
- "reference": "21cd48c34a47a0d0e303a590a67c3450fde55888",
+ "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/e7d308bd44ff8673a259e2727d13af6a93a5d83e",
+ "reference": "e7d308bd44ff8673a259e2727d13af6a93a5d83e",
"shasum": ""
},
"require": {
@@ -11066,7 +11064,7 @@
"description": "Provides integration for Doctrine with various Symfony components",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/doctrine-bridge/tree/v7.3.4"
+ "source": "https://github.com/symfony/doctrine-bridge/tree/v7.3.5"
},
"funding": [
{
@@ -11086,7 +11084,7 @@
"type": "tidelift"
}
],
- "time": "2025-09-24T09:56:23+00:00"
+ "time": "2025-09-27T09:00:46+00:00"
},
{
"name": "symfony/dom-crawler",
@@ -11239,16 +11237,16 @@
},
{
"name": "symfony/error-handler",
- "version": "v7.3.4",
+ "version": "v7.3.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/error-handler.git",
- "reference": "99f81bc944ab8e5dae4f21b4ca9972698bbad0e4"
+ "reference": "bbe40bfab84323d99dab491b716ff142410a92a8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/error-handler/zipball/99f81bc944ab8e5dae4f21b4ca9972698bbad0e4",
- "reference": "99f81bc944ab8e5dae4f21b4ca9972698bbad0e4",
+ "url": "https://api.github.com/repos/symfony/error-handler/zipball/bbe40bfab84323d99dab491b716ff142410a92a8",
+ "reference": "bbe40bfab84323d99dab491b716ff142410a92a8",
"shasum": ""
},
"require": {
@@ -11296,7 +11294,7 @@
"description": "Provides tools to manage errors and ease debugging PHP code",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/error-handler/tree/v7.3.4"
+ "source": "https://github.com/symfony/error-handler/tree/v7.3.6"
},
"funding": [
{
@@ -11316,7 +11314,7 @@
"type": "tidelift"
}
],
- "time": "2025-09-11T10:12:26+00:00"
+ "time": "2025-10-31T19:12:50+00:00"
},
{
"name": "symfony/event-dispatcher",
@@ -11548,16 +11546,16 @@
},
{
"name": "symfony/filesystem",
- "version": "v7.3.2",
+ "version": "v7.3.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
- "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd"
+ "reference": "e9bcfd7837928ab656276fe00464092cc9e1826a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/filesystem/zipball/edcbb768a186b5c3f25d0643159a787d3e63b7fd",
- "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/e9bcfd7837928ab656276fe00464092cc9e1826a",
+ "reference": "e9bcfd7837928ab656276fe00464092cc9e1826a",
"shasum": ""
},
"require": {
@@ -11594,7 +11592,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/filesystem/tree/v7.3.2"
+ "source": "https://github.com/symfony/filesystem/tree/v7.3.6"
},
"funding": [
{
@@ -11614,20 +11612,20 @@
"type": "tidelift"
}
],
- "time": "2025-07-07T08:17:47+00:00"
+ "time": "2025-11-05T09:52:27+00:00"
},
{
"name": "symfony/finder",
- "version": "v7.3.2",
+ "version": "v7.3.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe"
+ "reference": "9f696d2f1e340484b4683f7853b273abff94421f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/2a6614966ba1074fa93dae0bc804227422df4dfe",
- "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/9f696d2f1e340484b4683f7853b273abff94421f",
+ "reference": "9f696d2f1e340484b4683f7853b273abff94421f",
"shasum": ""
},
"require": {
@@ -11662,7 +11660,7 @@
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/finder/tree/v7.3.2"
+ "source": "https://github.com/symfony/finder/tree/v7.3.5"
},
"funding": [
{
@@ -11682,35 +11680,35 @@
"type": "tidelift"
}
],
- "time": "2025-07-15T13:41:35+00:00"
+ "time": "2025-10-15T18:45:57+00:00"
},
{
"name": "symfony/flex",
- "version": "v2.8.2",
+ "version": "v2.9.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/flex.git",
- "reference": "f356aa35f3cf3d2f46c31d344c1098eb2d260426"
+ "reference": "94b37978c9982dc41c5b6a4147892d2d3d1b9ce6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/flex/zipball/f356aa35f3cf3d2f46c31d344c1098eb2d260426",
- "reference": "f356aa35f3cf3d2f46c31d344c1098eb2d260426",
+ "url": "https://api.github.com/repos/symfony/flex/zipball/94b37978c9982dc41c5b6a4147892d2d3d1b9ce6",
+ "reference": "94b37978c9982dc41c5b6a4147892d2d3d1b9ce6",
"shasum": ""
},
"require": {
"composer-plugin-api": "^2.1",
- "php": ">=8.0"
+ "php": ">=8.1"
},
"conflict": {
"composer/semver": "<1.7.2"
},
"require-dev": {
"composer/composer": "^2.1",
- "symfony/dotenv": "^5.4|^6.0",
- "symfony/filesystem": "^5.4|^6.0",
- "symfony/phpunit-bridge": "^5.4|^6.0",
- "symfony/process": "^5.4|^6.0"
+ "symfony/dotenv": "^6.4|^7.4|^8.0",
+ "symfony/filesystem": "^6.4|^7.4|^8.0",
+ "symfony/phpunit-bridge": "^6.4|^7.4|^8.0",
+ "symfony/process": "^6.4|^7.4|^8.0"
},
"type": "composer-plugin",
"extra": {
@@ -11734,7 +11732,7 @@
"description": "Composer plugin for Symfony",
"support": {
"issues": "https://github.com/symfony/flex/issues",
- "source": "https://github.com/symfony/flex/tree/v2.8.2"
+ "source": "https://github.com/symfony/flex/tree/v2.9.0"
},
"funding": [
{
@@ -11754,20 +11752,20 @@
"type": "tidelift"
}
],
- "time": "2025-08-22T07:17:23+00:00"
+ "time": "2025-10-31T15:22:50+00:00"
},
{
"name": "symfony/form",
- "version": "v7.3.4",
+ "version": "v7.3.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/form.git",
- "reference": "7b3eee0f4d4dfd1ff1be70a27474197330c61736"
+ "reference": "a8f32aa19b322bf46cbaaafa89c132eb662ecfe5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/form/zipball/7b3eee0f4d4dfd1ff1be70a27474197330c61736",
- "reference": "7b3eee0f4d4dfd1ff1be70a27474197330c61736",
+ "url": "https://api.github.com/repos/symfony/form/zipball/a8f32aa19b322bf46cbaaafa89c132eb662ecfe5",
+ "reference": "a8f32aa19b322bf46cbaaafa89c132eb662ecfe5",
"shasum": ""
},
"require": {
@@ -11835,7 +11833,7 @@
"description": "Allows to easily create, process and reuse HTML forms",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/form/tree/v7.3.4"
+ "source": "https://github.com/symfony/form/tree/v7.3.6"
},
"funding": [
{
@@ -11855,20 +11853,20 @@
"type": "tidelift"
}
],
- "time": "2025-09-22T15:31:00+00:00"
+ "time": "2025-10-31T09:25:04+00:00"
},
{
"name": "symfony/framework-bundle",
- "version": "v7.3.4",
+ "version": "v7.3.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/framework-bundle.git",
- "reference": "b13e7cec5a144c8dba6f4233a2c53c00bc29e140"
+ "reference": "cabfdfa82bc4f75d693a329fe263d96937636b77"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/b13e7cec5a144c8dba6f4233a2c53c00bc29e140",
- "reference": "b13e7cec5a144c8dba6f4233a2c53c00bc29e140",
+ "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/cabfdfa82bc4f75d693a329fe263d96937636b77",
+ "reference": "cabfdfa82bc4f75d693a329fe263d96937636b77",
"shasum": ""
},
"require": {
@@ -11993,7 +11991,7 @@
"description": "Provides a tight integration between Symfony components and the Symfony full-stack framework",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/framework-bundle/tree/v7.3.4"
+ "source": "https://github.com/symfony/framework-bundle/tree/v7.3.6"
},
"funding": [
{
@@ -12013,20 +12011,20 @@
"type": "tidelift"
}
],
- "time": "2025-09-17T05:51:54+00:00"
+ "time": "2025-10-30T09:42:24+00:00"
},
{
"name": "symfony/http-client",
- "version": "v7.3.4",
+ "version": "v7.3.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client.git",
- "reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62"
+ "reference": "3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-client/zipball/4b62871a01c49457cf2a8e560af7ee8a94b87a62",
- "reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62",
+ "url": "https://api.github.com/repos/symfony/http-client/zipball/3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de",
+ "reference": "3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de",
"shasum": ""
},
"require": {
@@ -12093,7 +12091,7 @@
"http"
],
"support": {
- "source": "https://github.com/symfony/http-client/tree/v7.3.4"
+ "source": "https://github.com/symfony/http-client/tree/v7.3.6"
},
"funding": [
{
@@ -12113,7 +12111,7 @@
"type": "tidelift"
}
],
- "time": "2025-09-11T10:12:26+00:00"
+ "time": "2025-11-05T17:41:46+00:00"
},
{
"name": "symfony/http-client-contracts",
@@ -12195,16 +12193,16 @@
},
{
"name": "symfony/http-foundation",
- "version": "v7.3.4",
+ "version": "v7.3.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "c061c7c18918b1b64268771aad04b40be41dd2e6"
+ "reference": "db488a62f98f7a81d5746f05eea63a74e55bb7c4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/c061c7c18918b1b64268771aad04b40be41dd2e6",
- "reference": "c061c7c18918b1b64268771aad04b40be41dd2e6",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/db488a62f98f7a81d5746f05eea63a74e55bb7c4",
+ "reference": "db488a62f98f7a81d5746f05eea63a74e55bb7c4",
"shasum": ""
},
"require": {
@@ -12254,7 +12252,7 @@
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-foundation/tree/v7.3.4"
+ "source": "https://github.com/symfony/http-foundation/tree/v7.3.7"
},
"funding": [
{
@@ -12274,20 +12272,20 @@
"type": "tidelift"
}
],
- "time": "2025-09-16T08:38:17+00:00"
+ "time": "2025-11-08T16:41:12+00:00"
},
{
"name": "symfony/http-kernel",
- "version": "v7.3.4",
+ "version": "v7.3.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
- "reference": "b796dffea7821f035047235e076b60ca2446e3cf"
+ "reference": "10b8e9b748ea95fa4539c208e2487c435d3c87ce"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b796dffea7821f035047235e076b60ca2446e3cf",
- "reference": "b796dffea7821f035047235e076b60ca2446e3cf",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/10b8e9b748ea95fa4539c208e2487c435d3c87ce",
+ "reference": "10b8e9b748ea95fa4539c208e2487c435d3c87ce",
"shasum": ""
},
"require": {
@@ -12372,7 +12370,7 @@
"description": "Provides a structured process for converting a Request into a Response",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-kernel/tree/v7.3.4"
+ "source": "https://github.com/symfony/http-kernel/tree/v7.3.7"
},
"funding": [
{
@@ -12392,20 +12390,20 @@
"type": "tidelift"
}
],
- "time": "2025-09-27T12:32:17+00:00"
+ "time": "2025-11-12T11:38:40+00:00"
},
{
"name": "symfony/intl",
- "version": "v7.3.4",
+ "version": "v7.3.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/intl.git",
- "reference": "e6db84864655885d9dac676a9d7dde0d904fda54"
+ "reference": "9eccaaa94ac6f9deb3620c9d47a057d965baeabf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/intl/zipball/e6db84864655885d9dac676a9d7dde0d904fda54",
- "reference": "e6db84864655885d9dac676a9d7dde0d904fda54",
+ "url": "https://api.github.com/repos/symfony/intl/zipball/9eccaaa94ac6f9deb3620c9d47a057d965baeabf",
+ "reference": "9eccaaa94ac6f9deb3620c9d47a057d965baeabf",
"shasum": ""
},
"require": {
@@ -12462,7 +12460,7 @@
"localization"
],
"support": {
- "source": "https://github.com/symfony/intl/tree/v7.3.4"
+ "source": "https://github.com/symfony/intl/tree/v7.3.5"
},
"funding": [
{
@@ -12482,20 +12480,20 @@
"type": "tidelift"
}
],
- "time": "2025-09-08T14:11:30+00:00"
+ "time": "2025-10-01T06:11:17+00:00"
},
{
"name": "symfony/mailer",
- "version": "v7.3.4",
+ "version": "v7.3.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/mailer.git",
- "reference": "ab97ef2f7acf0216955f5845484235113047a31d"
+ "reference": "fd497c45ba9c10c37864e19466b090dcb60a50ba"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/mailer/zipball/ab97ef2f7acf0216955f5845484235113047a31d",
- "reference": "ab97ef2f7acf0216955f5845484235113047a31d",
+ "url": "https://api.github.com/repos/symfony/mailer/zipball/fd497c45ba9c10c37864e19466b090dcb60a50ba",
+ "reference": "fd497c45ba9c10c37864e19466b090dcb60a50ba",
"shasum": ""
},
"require": {
@@ -12546,7 +12544,7 @@
"description": "Helps sending emails",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/mailer/tree/v7.3.4"
+ "source": "https://github.com/symfony/mailer/tree/v7.3.5"
},
"funding": [
{
@@ -12566,7 +12564,7 @@
"type": "tidelift"
}
],
- "time": "2025-09-17T05:51:54+00:00"
+ "time": "2025-10-24T14:27:20+00:00"
},
{
"name": "symfony/mime",
@@ -12658,16 +12656,16 @@
},
{
"name": "symfony/monolog-bridge",
- "version": "v7.3.4",
+ "version": "v7.3.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/monolog-bridge.git",
- "reference": "7acf2abe23e5019451399ba69fc8ed3d61d4d8f0"
+ "reference": "48e8542ba35afd2293a8c8fd4bcf8abe46357ddf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/7acf2abe23e5019451399ba69fc8ed3d61d4d8f0",
- "reference": "7acf2abe23e5019451399ba69fc8ed3d61d4d8f0",
+ "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/48e8542ba35afd2293a8c8fd4bcf8abe46357ddf",
+ "reference": "48e8542ba35afd2293a8c8fd4bcf8abe46357ddf",
"shasum": ""
},
"require": {
@@ -12716,7 +12714,7 @@
"description": "Provides integration for Monolog with various Symfony components",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/monolog-bridge/tree/v7.3.4"
+ "source": "https://github.com/symfony/monolog-bridge/tree/v7.3.6"
},
"funding": [
{
@@ -12736,7 +12734,7 @@
"type": "tidelift"
}
],
- "time": "2025-09-24T16:45:39+00:00"
+ "time": "2025-11-01T09:17:24+00:00"
},
{
"name": "symfony/monolog-bundle",
@@ -13387,255 +13385,6 @@
],
"time": "2024-09-09T11:45:10+00:00"
},
- {
- "name": "symfony/polyfill-mbstring",
- "version": "v1.33.0",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
- "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
- "shasum": ""
- },
- "require": {
- "ext-iconv": "*",
- "php": ">=7.2"
- },
- "provide": {
- "ext-mbstring": "*"
- },
- "suggest": {
- "ext-mbstring": "For best performance"
- },
- "type": "library",
- "extra": {
- "thanks": {
- "url": "https://github.com/symfony/polyfill",
- "name": "symfony/polyfill"
- }
- },
- "autoload": {
- "files": [
- "bootstrap.php"
- ],
- "psr-4": {
- "Symfony\\Polyfill\\Mbstring\\": ""
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony polyfill for the Mbstring extension",
- "homepage": "https://symfony.com",
- "keywords": [
- "compatibility",
- "mbstring",
- "polyfill",
- "portable",
- "shim"
- ],
- "support": {
- "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://github.com/nicolas-grekas",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2024-12-23T08:48:59+00:00"
- },
- {
- "name": "symfony/polyfill-php80",
- "version": "v1.33.0",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/polyfill-php80.git",
- "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
- "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
- "shasum": ""
- },
- "require": {
- "php": ">=7.2"
- },
- "type": "library",
- "extra": {
- "thanks": {
- "url": "https://github.com/symfony/polyfill",
- "name": "symfony/polyfill"
- }
- },
- "autoload": {
- "files": [
- "bootstrap.php"
- ],
- "psr-4": {
- "Symfony\\Polyfill\\Php80\\": ""
- },
- "classmap": [
- "Resources/stubs"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Ion Bazan",
- "email": "ion.bazan@gmail.com"
- },
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
- "homepage": "https://symfony.com",
- "keywords": [
- "compatibility",
- "polyfill",
- "portable",
- "shim"
- ],
- "support": {
- "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://github.com/nicolas-grekas",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2025-01-02T08:10:11+00:00"
- },
- {
- "name": "symfony/polyfill-php82",
- "version": "v1.33.0",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/polyfill-php82.git",
- "reference": "5d2ed36f7734637dacc025f179698031951b1692"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/5d2ed36f7734637dacc025f179698031951b1692",
- "reference": "5d2ed36f7734637dacc025f179698031951b1692",
- "shasum": ""
- },
- "require": {
- "php": ">=7.2"
- },
- "type": "library",
- "extra": {
- "thanks": {
- "url": "https://github.com/symfony/polyfill",
- "name": "symfony/polyfill"
- }
- },
- "autoload": {
- "files": [
- "bootstrap.php"
- ],
- "psr-4": {
- "Symfony\\Polyfill\\Php82\\": ""
- },
- "classmap": [
- "Resources/stubs"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions",
- "homepage": "https://symfony.com",
- "keywords": [
- "compatibility",
- "polyfill",
- "portable",
- "shim"
- ],
- "support": {
- "source": "https://github.com/symfony/polyfill-php82/tree/v1.33.0"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://github.com/nicolas-grekas",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2024-09-09T11:45:10+00:00"
- },
{
"name": "symfony/polyfill-php83",
"version": "v1.33.0",
@@ -14026,23 +13775,23 @@
},
{
"name": "symfony/property-info",
- "version": "v7.3.4",
+ "version": "v7.3.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/property-info.git",
- "reference": "7b6db23f23d13ada41e1cb484748a8ec028fbace"
+ "reference": "0b346ed259dc5da43535caf243005fe7d4b0f051"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/property-info/zipball/7b6db23f23d13ada41e1cb484748a8ec028fbace",
- "reference": "7b6db23f23d13ada41e1cb484748a8ec028fbace",
+ "url": "https://api.github.com/repos/symfony/property-info/zipball/0b346ed259dc5da43535caf243005fe7d4b0f051",
+ "reference": "0b346ed259dc5da43535caf243005fe7d4b0f051",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/string": "^6.4|^7.0",
- "symfony/type-info": "~7.2.8|^7.3.1"
+ "symfony/type-info": "^7.3.5"
},
"conflict": {
"phpdocumentor/reflection-docblock": "<5.2",
@@ -14092,7 +13841,7 @@
"validator"
],
"support": {
- "source": "https://github.com/symfony/property-info/tree/v7.3.4"
+ "source": "https://github.com/symfony/property-info/tree/v7.3.5"
},
"funding": [
{
@@ -14112,7 +13861,7 @@
"type": "tidelift"
}
],
- "time": "2025-09-15T13:55:54+00:00"
+ "time": "2025-10-05T22:12:41+00:00"
},
{
"name": "symfony/psr-http-message-bridge",
@@ -14273,16 +14022,16 @@
},
{
"name": "symfony/routing",
- "version": "v7.3.4",
+ "version": "v7.3.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
- "reference": "8dc648e159e9bac02b703b9fbd937f19ba13d07c"
+ "reference": "c97abe725f2a1a858deca629a6488c8fc20c3091"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/routing/zipball/8dc648e159e9bac02b703b9fbd937f19ba13d07c",
- "reference": "8dc648e159e9bac02b703b9fbd937f19ba13d07c",
+ "url": "https://api.github.com/repos/symfony/routing/zipball/c97abe725f2a1a858deca629a6488c8fc20c3091",
+ "reference": "c97abe725f2a1a858deca629a6488c8fc20c3091",
"shasum": ""
},
"require": {
@@ -14334,7 +14083,7 @@
"url"
],
"support": {
- "source": "https://github.com/symfony/routing/tree/v7.3.4"
+ "source": "https://github.com/symfony/routing/tree/v7.3.6"
},
"funding": [
{
@@ -14354,7 +14103,7 @@
"type": "tidelift"
}
],
- "time": "2025-09-11T10:12:26+00:00"
+ "time": "2025-11-05T07:57:47+00:00"
},
{
"name": "symfony/runtime",
@@ -14551,16 +14300,16 @@
},
{
"name": "symfony/security-core",
- "version": "v7.3.4",
+ "version": "v7.3.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/security-core.git",
- "reference": "68b9d3ca57615afde6152a1e1441fa035bea43f8"
+ "reference": "772a7c1eddd8bf8a977a67e6e8adc59650c604eb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/security-core/zipball/68b9d3ca57615afde6152a1e1441fa035bea43f8",
- "reference": "68b9d3ca57615afde6152a1e1441fa035bea43f8",
+ "url": "https://api.github.com/repos/symfony/security-core/zipball/772a7c1eddd8bf8a977a67e6e8adc59650c604eb",
+ "reference": "772a7c1eddd8bf8a977a67e6e8adc59650c604eb",
"shasum": ""
},
"require": {
@@ -14618,7 +14367,7 @@
"description": "Symfony Security Component - Core Library",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/security-core/tree/v7.3.4"
+ "source": "https://github.com/symfony/security-core/tree/v7.3.5"
},
"funding": [
{
@@ -14638,7 +14387,7 @@
"type": "tidelift"
}
],
- "time": "2025-09-24T14:32:13+00:00"
+ "time": "2025-10-24T14:27:20+00:00"
},
{
"name": "symfony/security-csrf",
@@ -14712,16 +14461,16 @@
},
{
"name": "symfony/security-http",
- "version": "v7.3.4",
+ "version": "v7.3.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/security-http.git",
- "reference": "1cf54d0648ebab23bf9b8972617b79f1995e13a9"
+ "reference": "e79a63fd5dec6e5b1ba31227e98d860a4f8ba95c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/security-http/zipball/1cf54d0648ebab23bf9b8972617b79f1995e13a9",
- "reference": "1cf54d0648ebab23bf9b8972617b79f1995e13a9",
+ "url": "https://api.github.com/repos/symfony/security-http/zipball/e79a63fd5dec6e5b1ba31227e98d860a4f8ba95c",
+ "reference": "e79a63fd5dec6e5b1ba31227e98d860a4f8ba95c",
"shasum": ""
},
"require": {
@@ -14780,7 +14529,7 @@
"description": "Symfony Security Component - HTTP Integration",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/security-http/tree/v7.3.4"
+ "source": "https://github.com/symfony/security-http/tree/v7.3.5"
},
"funding": [
{
@@ -14800,20 +14549,20 @@
"type": "tidelift"
}
],
- "time": "2025-09-09T17:06:44+00:00"
+ "time": "2025-10-13T09:30:10+00:00"
},
{
"name": "symfony/serializer",
- "version": "v7.3.4",
+ "version": "v7.3.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/serializer.git",
- "reference": "0df5af266c6fe9a855af7db4fea86e13b9ca3ab1"
+ "reference": "ba2e50a5f2870c93f0f47ca1a4e56e4bbe274035"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/serializer/zipball/0df5af266c6fe9a855af7db4fea86e13b9ca3ab1",
- "reference": "0df5af266c6fe9a855af7db4fea86e13b9ca3ab1",
+ "url": "https://api.github.com/repos/symfony/serializer/zipball/ba2e50a5f2870c93f0f47ca1a4e56e4bbe274035",
+ "reference": "ba2e50a5f2870c93f0f47ca1a4e56e4bbe274035",
"shasum": ""
},
"require": {
@@ -14883,7 +14632,7 @@
"description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/serializer/tree/v7.3.4"
+ "source": "https://github.com/symfony/serializer/tree/v7.3.5"
},
"funding": [
{
@@ -14903,20 +14652,20 @@
"type": "tidelift"
}
],
- "time": "2025-09-15T13:39:02+00:00"
+ "time": "2025-10-08T11:26:21+00:00"
},
{
"name": "symfony/service-contracts",
- "version": "v3.6.0",
+ "version": "v3.6.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
- "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4"
+ "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
- "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43",
+ "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43",
"shasum": ""
},
"require": {
@@ -14970,7 +14719,7 @@
"standards"
],
"support": {
- "source": "https://github.com/symfony/service-contracts/tree/v3.6.0"
+ "source": "https://github.com/symfony/service-contracts/tree/v3.6.1"
},
"funding": [
{
@@ -14981,25 +14730,29 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-04-25T09:37:31+00:00"
+ "time": "2025-07-15T11:30:57+00:00"
},
{
"name": "symfony/stimulus-bundle",
- "version": "v2.30.0",
+ "version": "v2.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/stimulus-bundle.git",
- "reference": "668b9efe9d0ab8b4e50091263171609e0459c0c8"
+ "reference": "c5ea8ee2ccd45447b7f4b6b82f704ee5e76127f0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/668b9efe9d0ab8b4e50091263171609e0459c0c8",
- "reference": "668b9efe9d0ab8b4e50091263171609e0459c0c8",
+ "url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/c5ea8ee2ccd45447b7f4b6b82f704ee5e76127f0",
+ "reference": "c5ea8ee2ccd45447b7f4b6b82f704ee5e76127f0",
"shasum": ""
},
"require": {
@@ -15039,7 +14792,7 @@
"symfony-ux"
],
"support": {
- "source": "https://github.com/symfony/stimulus-bundle/tree/v2.30.0"
+ "source": "https://github.com/symfony/stimulus-bundle/tree/v2.31.0"
},
"funding": [
{
@@ -15059,7 +14812,7 @@
"type": "tidelift"
}
],
- "time": "2025-08-27T15:25:48+00:00"
+ "time": "2025-09-24T13:27:42+00:00"
},
{
"name": "symfony/stopwatch",
@@ -15315,16 +15068,16 @@
},
{
"name": "symfony/translation-contracts",
- "version": "v3.6.0",
+ "version": "v3.6.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation-contracts.git",
- "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d"
+ "reference": "65a8bc82080447fae78373aa10f8d13b38338977"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d",
- "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d",
+ "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977",
+ "reference": "65a8bc82080447fae78373aa10f8d13b38338977",
"shasum": ""
},
"require": {
@@ -15373,7 +15126,7 @@
"standards"
],
"support": {
- "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0"
+ "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1"
},
"funding": [
{
@@ -15384,25 +15137,29 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2024-09-27T08:32:26+00:00"
+ "time": "2025-07-15T13:41:35+00:00"
},
{
"name": "symfony/twig-bridge",
- "version": "v7.3.3",
+ "version": "v7.3.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/twig-bridge.git",
- "reference": "33558f013b7f6ed72805527c8405cae0062e47c5"
+ "reference": "d1aaec8eee1f5591f56b9efe00194d73a8e38319"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/33558f013b7f6ed72805527c8405cae0062e47c5",
- "reference": "33558f013b7f6ed72805527c8405cae0062e47c5",
+ "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/d1aaec8eee1f5591f56b9efe00194d73a8e38319",
+ "reference": "d1aaec8eee1f5591f56b9efe00194d73a8e38319",
"shasum": ""
},
"require": {
@@ -15484,7 +15241,7 @@
"description": "Provides integration for Twig with various Symfony components",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/twig-bridge/tree/v7.3.3"
+ "source": "https://github.com/symfony/twig-bridge/tree/v7.3.6"
},
"funding": [
{
@@ -15504,7 +15261,7 @@
"type": "tidelift"
}
],
- "time": "2025-08-18T13:10:53+00:00"
+ "time": "2025-11-04T15:37:51+00:00"
},
{
"name": "symfony/twig-bundle",
@@ -15596,16 +15353,16 @@
},
{
"name": "symfony/type-info",
- "version": "v7.3.4",
+ "version": "v7.3.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/type-info.git",
- "reference": "d34eaeb57f39c8a9c97eb72a977c423207dfa35b"
+ "reference": "8b36f41421160db56914f897b57eaa6a830758b3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/type-info/zipball/d34eaeb57f39c8a9c97eb72a977c423207dfa35b",
- "reference": "d34eaeb57f39c8a9c97eb72a977c423207dfa35b",
+ "url": "https://api.github.com/repos/symfony/type-info/zipball/8b36f41421160db56914f897b57eaa6a830758b3",
+ "reference": "8b36f41421160db56914f897b57eaa6a830758b3",
"shasum": ""
},
"require": {
@@ -15655,7 +15412,7 @@
"type"
],
"support": {
- "source": "https://github.com/symfony/type-info/tree/v7.3.4"
+ "source": "https://github.com/symfony/type-info/tree/v7.3.5"
},
"funding": [
{
@@ -15675,7 +15432,7 @@
"type": "tidelift"
}
],
- "time": "2025-09-11T15:33:27+00:00"
+ "time": "2025-10-16T12:30:12+00:00"
},
{
"name": "symfony/uid",
@@ -15753,16 +15510,16 @@
},
{
"name": "symfony/ux-translator",
- "version": "v2.30.0",
+ "version": "v2.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/ux-translator.git",
- "reference": "9616091db206df4caa7d8dce2e48941512b1a94a"
+ "reference": "b4b323fdc846d2d67feb7f8ca5ef5a05238f6639"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/ux-translator/zipball/9616091db206df4caa7d8dce2e48941512b1a94a",
- "reference": "9616091db206df4caa7d8dce2e48941512b1a94a",
+ "url": "https://api.github.com/repos/symfony/ux-translator/zipball/b4b323fdc846d2d67feb7f8ca5ef5a05238f6639",
+ "reference": "b4b323fdc846d2d67feb7f8ca5ef5a05238f6639",
"shasum": ""
},
"require": {
@@ -15810,7 +15567,7 @@
"symfony-ux"
],
"support": {
- "source": "https://github.com/symfony/ux-translator/tree/v2.30.0"
+ "source": "https://github.com/symfony/ux-translator/tree/v2.31.0"
},
"funding": [
{
@@ -15830,20 +15587,20 @@
"type": "tidelift"
}
],
- "time": "2025-08-27T15:25:48+00:00"
+ "time": "2025-10-16T07:24:06+00:00"
},
{
"name": "symfony/ux-turbo",
- "version": "v2.30.0",
+ "version": "v2.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/ux-turbo.git",
- "reference": "c5e88c7e16713e84a2a35f36276ccdb05c2c78d8"
+ "reference": "06d5e4cf4573efe4faf648f3810a28c63684c706"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/c5e88c7e16713e84a2a35f36276ccdb05c2c78d8",
- "reference": "c5e88c7e16713e84a2a35f36276ccdb05c2c78d8",
+ "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/06d5e4cf4573efe4faf648f3810a28c63684c706",
+ "reference": "06d5e4cf4573efe4faf648f3810a28c63684c706",
"shasum": ""
},
"require": {
@@ -15856,7 +15613,7 @@
"require-dev": {
"dbrekelmans/bdi": "dev-main",
"doctrine/doctrine-bundle": "^2.4.3",
- "doctrine/orm": "^2.8 | 3.0",
+ "doctrine/orm": "^2.8|^3.0",
"php-webdriver/webdriver": "^1.15",
"phpstan/phpstan": "^2.1.17",
"symfony/asset-mapper": "^6.4|^7.0|^8.0",
@@ -15913,7 +15670,7 @@
"turbo-stream"
],
"support": {
- "source": "https://github.com/symfony/ux-turbo/tree/v2.30.0"
+ "source": "https://github.com/symfony/ux-turbo/tree/v2.31.0"
},
"funding": [
{
@@ -15933,20 +15690,20 @@
"type": "tidelift"
}
],
- "time": "2025-08-27T15:25:48+00:00"
+ "time": "2025-10-16T07:24:06+00:00"
},
{
"name": "symfony/validator",
- "version": "v7.3.4",
+ "version": "v7.3.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/validator.git",
- "reference": "5e29a348b5fac2227b6938a54db006d673bb813a"
+ "reference": "8290a095497c3fe5046db21888d1f75b54ddf39d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/validator/zipball/5e29a348b5fac2227b6938a54db006d673bb813a",
- "reference": "5e29a348b5fac2227b6938a54db006d673bb813a",
+ "url": "https://api.github.com/repos/symfony/validator/zipball/8290a095497c3fe5046db21888d1f75b54ddf39d",
+ "reference": "8290a095497c3fe5046db21888d1f75b54ddf39d",
"shasum": ""
},
"require": {
@@ -16015,7 +15772,7 @@
"description": "Provides tools to validate values",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/validator/tree/v7.3.4"
+ "source": "https://github.com/symfony/validator/tree/v7.3.7"
},
"funding": [
{
@@ -16035,20 +15792,20 @@
"type": "tidelift"
}
],
- "time": "2025-09-24T06:32:27+00:00"
+ "time": "2025-11-08T16:29:29+00:00"
},
{
"name": "symfony/var-dumper",
- "version": "v7.3.4",
+ "version": "v7.3.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb"
+ "reference": "476c4ae17f43a9a36650c69879dcf5b1e6ae724d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb",
- "reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/476c4ae17f43a9a36650c69879dcf5b1e6ae724d",
+ "reference": "476c4ae17f43a9a36650c69879dcf5b1e6ae724d",
"shasum": ""
},
"require": {
@@ -16102,7 +15859,7 @@
"dump"
],
"support": {
- "source": "https://github.com/symfony/var-dumper/tree/v7.3.4"
+ "source": "https://github.com/symfony/var-dumper/tree/v7.3.5"
},
"funding": [
{
@@ -16122,7 +15879,7 @@
"type": "tidelift"
}
],
- "time": "2025-09-11T10:12:26+00:00"
+ "time": "2025-09-27T09:00:46+00:00"
},
{
"name": "symfony/var-exporter",
@@ -16366,16 +16123,16 @@
},
{
"name": "symfony/yaml",
- "version": "v7.3.3",
+ "version": "v7.3.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "d4f4a66866fe2451f61296924767280ab5732d9d"
+ "reference": "90208e2fc6f68f613eae7ca25a2458a931b1bacc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/d4f4a66866fe2451f61296924767280ab5732d9d",
- "reference": "d4f4a66866fe2451f61296924767280ab5732d9d",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/90208e2fc6f68f613eae7ca25a2458a931b1bacc",
+ "reference": "90208e2fc6f68f613eae7ca25a2458a931b1bacc",
"shasum": ""
},
"require": {
@@ -16418,7 +16175,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/yaml/tree/v7.3.3"
+ "source": "https://github.com/symfony/yaml/tree/v7.3.5"
},
"funding": [
{
@@ -16438,20 +16195,20 @@
"type": "tidelift"
}
],
- "time": "2025-08-27T11:34:33+00:00"
+ "time": "2025-09-27T09:00:46+00:00"
},
{
"name": "symplify/easy-coding-standard",
- "version": "12.6.0",
+ "version": "12.6.2",
"source": {
"type": "git",
"url": "https://github.com/easy-coding-standard/easy-coding-standard.git",
- "reference": "781e6124dc7e14768ae999a8f5309566bbe62004"
+ "reference": "7a6798aa424f0ecafb1542b6f5207c5a99704d3d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/781e6124dc7e14768ae999a8f5309566bbe62004",
- "reference": "781e6124dc7e14768ae999a8f5309566bbe62004",
+ "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/7a6798aa424f0ecafb1542b6f5207c5a99704d3d",
+ "reference": "7a6798aa424f0ecafb1542b6f5207c5a99704d3d",
"shasum": ""
},
"require": {
@@ -16487,7 +16244,7 @@
],
"support": {
"issues": "https://github.com/easy-coding-standard/easy-coding-standard/issues",
- "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/12.6.0"
+ "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/12.6.2"
},
"funding": [
{
@@ -16499,7 +16256,7 @@
"type": "github"
}
],
- "time": "2025-09-10T14:21:58+00:00"
+ "time": "2025-10-29T08:51:50+00:00"
},
{
"name": "tecnickcom/tc-lib-barcode",
@@ -16727,16 +16484,16 @@
},
{
"name": "twig/cssinliner-extra",
- "version": "v3.21.0",
+ "version": "v3.22.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/cssinliner-extra.git",
- "reference": "378d29b61d6406c456e3a4afbd15bbeea0b72ea8"
+ "reference": "9bcbf04ca515e98fcde479fdceaa1d9d9e76173e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/twigphp/cssinliner-extra/zipball/378d29b61d6406c456e3a4afbd15bbeea0b72ea8",
- "reference": "378d29b61d6406c456e3a4afbd15bbeea0b72ea8",
+ "url": "https://api.github.com/repos/twigphp/cssinliner-extra/zipball/9bcbf04ca515e98fcde479fdceaa1d9d9e76173e",
+ "reference": "9bcbf04ca515e98fcde479fdceaa1d9d9e76173e",
"shasum": ""
},
"require": {
@@ -16780,7 +16537,7 @@
"twig"
],
"support": {
- "source": "https://github.com/twigphp/cssinliner-extra/tree/v3.21.0"
+ "source": "https://github.com/twigphp/cssinliner-extra/tree/v3.22.0"
},
"funding": [
{
@@ -16792,20 +16549,20 @@
"type": "tidelift"
}
],
- "time": "2025-01-31T20:45:36+00:00"
+ "time": "2025-07-29T08:07:07+00:00"
},
{
"name": "twig/extra-bundle",
- "version": "v3.21.0",
+ "version": "v3.22.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/twig-extra-bundle.git",
- "reference": "62d1cf47a1aa009cbd07b21045b97d3d5cb79896"
+ "reference": "6d253f0fe28a83a045497c8fb3ea9bfe84e82cf4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/62d1cf47a1aa009cbd07b21045b97d3d5cb79896",
- "reference": "62d1cf47a1aa009cbd07b21045b97d3d5cb79896",
+ "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/6d253f0fe28a83a045497c8fb3ea9bfe84e82cf4",
+ "reference": "6d253f0fe28a83a045497c8fb3ea9bfe84e82cf4",
"shasum": ""
},
"require": {
@@ -16815,7 +16572,7 @@
"twig/twig": "^3.2|^4.0"
},
"require-dev": {
- "league/commonmark": "^1.0|^2.0",
+ "league/commonmark": "^2.7",
"symfony/phpunit-bridge": "^6.4|^7.0",
"twig/cache-extra": "^3.0",
"twig/cssinliner-extra": "^3.0",
@@ -16854,7 +16611,7 @@
"twig"
],
"support": {
- "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.21.0"
+ "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.22.0"
},
"funding": [
{
@@ -16866,11 +16623,11 @@
"type": "tidelift"
}
],
- "time": "2025-02-19T14:29:33+00:00"
+ "time": "2025-09-15T05:57:37+00:00"
},
{
"name": "twig/html-extra",
- "version": "v3.21.0",
+ "version": "v3.22.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/html-extra.git",
@@ -16922,7 +16679,7 @@
"twig"
],
"support": {
- "source": "https://github.com/twigphp/html-extra/tree/v3.21.0"
+ "source": "https://github.com/twigphp/html-extra/tree/v3.22.0"
},
"funding": [
{
@@ -16938,16 +16695,16 @@
},
{
"name": "twig/inky-extra",
- "version": "v3.21.0",
+ "version": "v3.22.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/inky-extra.git",
- "reference": "aacd79d94534b4a7fd6533cb5c33c4ee97239a0d"
+ "reference": "631f42c7123240d9c2497903679ec54bb25f2f52"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/twigphp/inky-extra/zipball/aacd79d94534b4a7fd6533cb5c33c4ee97239a0d",
- "reference": "aacd79d94534b4a7fd6533cb5c33c4ee97239a0d",
+ "url": "https://api.github.com/repos/twigphp/inky-extra/zipball/631f42c7123240d9c2497903679ec54bb25f2f52",
+ "reference": "631f42c7123240d9c2497903679ec54bb25f2f52",
"shasum": ""
},
"require": {
@@ -16992,7 +16749,7 @@
"twig"
],
"support": {
- "source": "https://github.com/twigphp/inky-extra/tree/v3.21.0"
+ "source": "https://github.com/twigphp/inky-extra/tree/v3.22.0"
},
"funding": [
{
@@ -17004,20 +16761,20 @@
"type": "tidelift"
}
],
- "time": "2025-01-31T20:45:36+00:00"
+ "time": "2025-07-29T08:07:07+00:00"
},
{
"name": "twig/intl-extra",
- "version": "v3.21.0",
+ "version": "v3.22.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/intl-extra.git",
- "reference": "05bc5d46b9df9e62399eae53e7c0b0633298b146"
+ "reference": "7393fc911c7315db18a805d3a541ac7bb9e4fdc0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/twigphp/intl-extra/zipball/05bc5d46b9df9e62399eae53e7c0b0633298b146",
- "reference": "05bc5d46b9df9e62399eae53e7c0b0633298b146",
+ "url": "https://api.github.com/repos/twigphp/intl-extra/zipball/7393fc911c7315db18a805d3a541ac7bb9e4fdc0",
+ "reference": "7393fc911c7315db18a805d3a541ac7bb9e4fdc0",
"shasum": ""
},
"require": {
@@ -17056,7 +16813,7 @@
"twig"
],
"support": {
- "source": "https://github.com/twigphp/intl-extra/tree/v3.21.0"
+ "source": "https://github.com/twigphp/intl-extra/tree/v3.22.0"
},
"funding": [
{
@@ -17068,20 +16825,20 @@
"type": "tidelift"
}
],
- "time": "2025-01-31T20:45:36+00:00"
+ "time": "2025-09-15T06:05:04+00:00"
},
{
"name": "twig/markdown-extra",
- "version": "v3.21.0",
+ "version": "v3.22.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/markdown-extra.git",
- "reference": "f4616e1dd375209dacf6026f846e6b537d036ce4"
+ "reference": "fb6f952082e3a7d62a75c8be2c8c47242d3925fb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/f4616e1dd375209dacf6026f846e6b537d036ce4",
- "reference": "f4616e1dd375209dacf6026f846e6b537d036ce4",
+ "url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/fb6f952082e3a7d62a75c8be2c8c47242d3925fb",
+ "reference": "fb6f952082e3a7d62a75c8be2c8c47242d3925fb",
"shasum": ""
},
"require": {
@@ -17091,7 +16848,7 @@
},
"require-dev": {
"erusev/parsedown": "dev-master as 1.x-dev",
- "league/commonmark": "^1.0|^2.0",
+ "league/commonmark": "^2.7",
"league/html-to-markdown": "^4.8|^5.0",
"michelf/php-markdown": "^1.8|^2.0",
"symfony/phpunit-bridge": "^6.4|^7.0"
@@ -17128,7 +16885,7 @@
"twig"
],
"support": {
- "source": "https://github.com/twigphp/markdown-extra/tree/v3.21.0"
+ "source": "https://github.com/twigphp/markdown-extra/tree/v3.22.0"
},
"funding": [
{
@@ -17140,11 +16897,11 @@
"type": "tidelift"
}
],
- "time": "2025-01-31T20:45:36+00:00"
+ "time": "2025-09-15T05:57:37+00:00"
},
{
"name": "twig/string-extra",
- "version": "v3.21.0",
+ "version": "v3.22.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/string-extra.git",
@@ -17195,7 +16952,7 @@
"unicode"
],
"support": {
- "source": "https://github.com/twigphp/string-extra/tree/v3.21.0"
+ "source": "https://github.com/twigphp/string-extra/tree/v3.22.0"
},
"funding": [
{
@@ -17211,16 +16968,16 @@
},
{
"name": "twig/twig",
- "version": "v3.21.1",
+ "version": "v3.22.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
- "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d"
+ "reference": "4509984193026de413baf4ba80f68590a7f2c51d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/twigphp/Twig/zipball/285123877d4dd97dd7c11842ac5fb7e86e60d81d",
- "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d",
+ "url": "https://api.github.com/repos/twigphp/Twig/zipball/4509984193026de413baf4ba80f68590a7f2c51d",
+ "reference": "4509984193026de413baf4ba80f68590a7f2c51d",
"shasum": ""
},
"require": {
@@ -17274,7 +17031,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
- "source": "https://github.com/twigphp/Twig/tree/v3.21.1"
+ "source": "https://github.com/twigphp/Twig/tree/v3.22.0"
},
"funding": [
{
@@ -17286,7 +17043,7 @@
"type": "tidelift"
}
],
- "time": "2025-05-03T07:21:55+00:00"
+ "time": "2025-10-29T15:56:47+00:00"
},
{
"name": "ua-parser/uap-php",
@@ -17603,28 +17360,28 @@
},
{
"name": "webmozart/assert",
- "version": "1.11.0",
+ "version": "1.12.1",
"source": {
"type": "git",
"url": "https://github.com/webmozarts/assert.git",
- "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991"
+ "reference": "9be6926d8b485f55b9229203f962b51ed377ba68"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991",
- "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991",
+ "url": "https://api.github.com/repos/webmozarts/assert/zipball/9be6926d8b485f55b9229203f962b51ed377ba68",
+ "reference": "9be6926d8b485f55b9229203f962b51ed377ba68",
"shasum": ""
},
"require": {
"ext-ctype": "*",
+ "ext-date": "*",
+ "ext-filter": "*",
"php": "^7.2 || ^8.0"
},
- "conflict": {
- "phpstan/phpstan": "<0.12.20",
- "vimeo/psalm": "<4.6.1 || 4.6.2"
- },
- "require-dev": {
- "phpunit/phpunit": "^8.5.13"
+ "suggest": {
+ "ext-intl": "",
+ "ext-simplexml": "",
+ "ext-spl": ""
},
"type": "library",
"extra": {
@@ -17655,9 +17412,9 @@
],
"support": {
"issues": "https://github.com/webmozarts/assert/issues",
- "source": "https://github.com/webmozarts/assert/tree/1.11.0"
+ "source": "https://github.com/webmozarts/assert/tree/1.12.1"
},
- "time": "2022-06-03T18:03:27+00:00"
+ "time": "2025-10-29T15:56:20+00:00"
},
{
"name": "willdurand/negotiation",
@@ -17788,20 +17545,20 @@
},
{
"name": "doctrine/doctrine-fixtures-bundle",
- "version": "4.2.0",
+ "version": "4.3.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/DoctrineFixturesBundle.git",
- "reference": "cd58d7738fe1fea1dbfd3e3f3bb421ee92d45e10"
+ "reference": "11941deb6f2899b91e8b8680b07ffe63899d864b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/DoctrineFixturesBundle/zipball/cd58d7738fe1fea1dbfd3e3f3bb421ee92d45e10",
- "reference": "cd58d7738fe1fea1dbfd3e3f3bb421ee92d45e10",
+ "url": "https://api.github.com/repos/doctrine/DoctrineFixturesBundle/zipball/11941deb6f2899b91e8b8680b07ffe63899d864b",
+ "reference": "11941deb6f2899b91e8b8680b07ffe63899d864b",
"shasum": ""
},
"require": {
- "doctrine/data-fixtures": "^2.0",
+ "doctrine/data-fixtures": "^2.2",
"doctrine/doctrine-bundle": "^2.2 || ^3.0",
"doctrine/orm": "^2.14.0 || ^3.0",
"doctrine/persistence": "^2.4 || ^3.0 || ^4.0",
@@ -17854,7 +17611,7 @@
],
"support": {
"issues": "https://github.com/doctrine/DoctrineFixturesBundle/issues",
- "source": "https://github.com/doctrine/DoctrineFixturesBundle/tree/4.2.0"
+ "source": "https://github.com/doctrine/DoctrineFixturesBundle/tree/4.3.0"
},
"funding": [
{
@@ -17870,7 +17627,7 @@
"type": "tidelift"
}
],
- "time": "2025-10-12T16:50:54+00:00"
+ "time": "2025-10-20T06:18:40+00:00"
},
{
"name": "ekino/phpstan-banned-code",
@@ -18070,16 +17827,16 @@
},
{
"name": "nikic/php-parser",
- "version": "v5.6.1",
+ "version": "v5.6.2",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2"
+ "reference": "3a454ca033b9e06b63282ce19562e892747449bb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2",
- "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb",
+ "reference": "3a454ca033b9e06b63282ce19562e892747449bb",
"shasum": ""
},
"require": {
@@ -18122,9 +17879,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2"
},
- "time": "2025-08-13T20:13:15+00:00"
+ "time": "2025-10-21T19:32:17+00:00"
},
{
"name": "phar-io/manifest",
@@ -18294,11 +18051,11 @@
},
{
"name": "phpstan/phpstan",
- "version": "2.1.31",
+ "version": "2.1.32",
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ead89849d879fe203ce9292c6ef5e7e76f867b96",
- "reference": "ead89849d879fe203ce9292c6ef5e7e76f867b96",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e126cad1e30a99b137b8ed75a85a676450ebb227",
+ "reference": "e126cad1e30a99b137b8ed75a85a676450ebb227",
"shasum": ""
},
"require": {
@@ -18343,20 +18100,20 @@
"type": "github"
}
],
- "time": "2025-10-10T14:14:11+00:00"
+ "time": "2025-11-11T15:18:17+00:00"
},
{
"name": "phpstan/phpstan-doctrine",
- "version": "2.0.10",
+ "version": "2.0.11",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-doctrine.git",
- "reference": "5eaf37b87288474051469aee9f937fc9d862f330"
+ "reference": "368ad1c713a6d95763890bc2292694a603ece7c8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/5eaf37b87288474051469aee9f937fc9d862f330",
- "reference": "5eaf37b87288474051469aee9f937fc9d862f330",
+ "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/368ad1c713a6d95763890bc2292694a603ece7c8",
+ "reference": "368ad1c713a6d95763890bc2292694a603ece7c8",
"shasum": ""
},
"require": {
@@ -18386,7 +18143,7 @@
"nesbot/carbon": "^2.49",
"php-parallel-lint/php-parallel-lint": "^1.2",
"phpstan/phpstan-deprecation-rules": "^2.0.2",
- "phpstan/phpstan-phpunit": "^2.0",
+ "phpstan/phpstan-phpunit": "^2.0.8",
"phpstan/phpstan-strict-rules": "^2.0",
"phpunit/phpunit": "^9.6.20",
"ramsey/uuid": "^4.2",
@@ -18414,9 +18171,9 @@
"description": "Doctrine extensions for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-doctrine/issues",
- "source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.10"
+ "source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.11"
},
- "time": "2025-10-06T10:01:02+00:00"
+ "time": "2025-11-04T09:55:35+00:00"
},
{
"name": "phpstan/phpstan-strict-rules",
@@ -18874,16 +18631,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "11.5.42",
+ "version": "11.5.43",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "1c6cb5dfe412af3d0dfd414cfd110e3b9cfdbc3c"
+ "reference": "c6b89b6cf4324a8b4cb86e1f5dfdd6c9e0371924"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1c6cb5dfe412af3d0dfd414cfd110e3b9cfdbc3c",
- "reference": "1c6cb5dfe412af3d0dfd414cfd110e3b9cfdbc3c",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c6b89b6cf4324a8b4cb86e1f5dfdd6c9e0371924",
+ "reference": "c6b89b6cf4324a8b4cb86e1f5dfdd6c9e0371924",
"shasum": ""
},
"require": {
@@ -18955,7 +18712,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.42"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.43"
},
"funding": [
{
@@ -18979,25 +18736,25 @@
"type": "tidelift"
}
],
- "time": "2025-09-28T12:09:13+00:00"
+ "time": "2025-10-30T08:39:39+00:00"
},
{
"name": "rector/rector",
- "version": "2.2.3",
+ "version": "2.2.8",
"source": {
"type": "git",
"url": "https://github.com/rectorphp/rector.git",
- "reference": "d27f976a332a87b5d03553c2e6f04adbe5da034f"
+ "reference": "303aa811649ccd1d32e51e62d5c85949d01b5f1b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/rectorphp/rector/zipball/d27f976a332a87b5d03553c2e6f04adbe5da034f",
- "reference": "d27f976a332a87b5d03553c2e6f04adbe5da034f",
+ "url": "https://api.github.com/repos/rectorphp/rector/zipball/303aa811649ccd1d32e51e62d5c85949d01b5f1b",
+ "reference": "303aa811649ccd1d32e51e62d5c85949d01b5f1b",
"shasum": ""
},
"require": {
"php": "^7.4|^8.0",
- "phpstan/phpstan": "^2.1.26"
+ "phpstan/phpstan": "^2.1.32"
},
"conflict": {
"rector/rector-doctrine": "*",
@@ -19031,7 +18788,7 @@
],
"support": {
"issues": "https://github.com/rectorphp/rector/issues",
- "source": "https://github.com/rectorphp/rector/tree/2.2.3"
+ "source": "https://github.com/rectorphp/rector/tree/2.2.8"
},
"funding": [
{
@@ -19039,7 +18796,7 @@
"type": "github"
}
],
- "time": "2025-10-11T21:50:23+00:00"
+ "time": "2025-11-12T18:38:00+00:00"
},
{
"name": "roave/security-advisories",
@@ -19047,18 +18804,18 @@
"source": {
"type": "git",
"url": "https://github.com/Roave/SecurityAdvisories.git",
- "reference": "7a8f128281289412092c450a5eb3df5cabbc89e1"
+ "reference": "ccc4996aff4ff810b514472932f677753ee5d8a4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/7a8f128281289412092c450a5eb3df5cabbc89e1",
- "reference": "7a8f128281289412092c450a5eb3df5cabbc89e1",
+ "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/ccc4996aff4ff810b514472932f677753ee5d8a4",
+ "reference": "ccc4996aff4ff810b514472932f677753ee5d8a4",
"shasum": ""
},
"conflict": {
"3f/pygmentize": "<1.2",
"adaptcms/adaptcms": "<=1.3",
- "admidio/admidio": "<4.3.12",
+ "admidio/admidio": "<=4.3.16",
"adodb/adodb-php": "<=5.22.9",
"aheinze/cockpit": "<2.2",
"aimeos/ai-admin-graphql": ">=2022.04.1,<2022.10.10|>=2023.04.1,<2023.10.6|>=2024.04.1,<2024.07.2",
@@ -19161,6 +18918,7 @@
"clickstorm/cs-seo": ">=6,<6.8|>=7,<7.5|>=8,<8.4|>=9,<9.3",
"co-stack/fal_sftp": "<0.2.6",
"cockpit-hq/cockpit": "<2.11.4",
+ "code16/sharp": "<9.11.1",
"codeception/codeception": "<3.1.3|>=4,<4.1.22",
"codeigniter/framework": "<3.1.10",
"codeigniter4/framework": "<4.6.2",
@@ -19219,30 +18977,39 @@
"dompdf/dompdf": "<2.0.4",
"doublethreedigital/guest-entries": "<3.1.2",
"drupal-pattern-lab/unified-twig-extensions": "<=0.1",
+ "drupal/access_code": "<2.0.5",
+ "drupal/acquia_dam": "<1.1.5",
"drupal/admin_audit_trail": "<1.0.5",
"drupal/ai": "<1.0.5",
"drupal/alogin": "<2.0.6",
"drupal/cache_utility": "<1.2.1",
+ "drupal/civictheme": "<1.12",
"drupal/commerce_alphabank_redirect": "<1.0.3",
"drupal/commerce_eurobank_redirect": "<2.1.1",
"drupal/config_split": "<1.10|>=2,<2.0.2",
"drupal/core": ">=6,<6.38|>=7,<7.102|>=8,<10.3.14|>=10.4,<10.4.5|>=11,<11.0.13|>=11.1,<11.1.5",
"drupal/core-recommended": ">=7,<7.102|>=8,<10.2.11|>=10.3,<10.3.9|>=11,<11.0.8",
+ "drupal/currency": "<3.5",
"drupal/drupal": ">=5,<5.11|>=6,<6.38|>=7,<7.102|>=8,<10.2.11|>=10.3,<10.3.9|>=11,<11.0.8",
"drupal/formatter_suite": "<2.1",
"drupal/gdpr": "<3.0.1|>=3.1,<3.1.2",
"drupal/google_tag": "<1.8|>=2,<2.0.8",
"drupal/ignition": "<1.0.4",
+ "drupal/json_field": "<1.5",
"drupal/lightgallery": "<1.6",
"drupal/link_field_display_mode_formatter": "<1.6",
"drupal/matomo": "<1.24",
"drupal/oauth2_client": "<4.1.3",
"drupal/oauth2_server": "<2.1",
"drupal/obfuscate": "<2.0.1",
+ "drupal/plausible_tracking": "<1.0.2",
"drupal/quick_node_block": "<2",
"drupal/rapidoc_elements_field_formatter": "<1.0.1",
+ "drupal/reverse_proxy_header": "<1.1.2",
+ "drupal/simple_oauth": ">=6,<6.0.7",
"drupal/spamspan": "<3.2.1",
"drupal/tfa": "<1.10",
+ "drupal/umami_analytics": "<1.0.1",
"duncanmcclean/guest-entries": "<3.1.2",
"dweeves/magmi": "<=0.7.24",
"ec-cube/ec-cube": "<2.4.4|>=2.11,<=2.17.1|>=3,<=3.0.18.0-patch4|>=4,<=4.1.2",
@@ -19466,7 +19233,7 @@
"luyadev/yii-helpers": "<1.2.1",
"macropay-solutions/laravel-crud-wizard-free": "<3.4.17",
"maestroerror/php-heic-to-jpg": "<1.0.5",
- "magento/community-edition": "<=2.4.5.0-patch14|==2.4.6|>=2.4.6.0-patch1,<=2.4.6.0-patch12|>=2.4.7.0-beta1,<=2.4.7.0-patch7|>=2.4.8.0-beta1,<=2.4.8.0-patch2|>=2.4.9.0-alpha1,<=2.4.9.0-alpha2|==2.4.9",
+ "magento/community-edition": "<2.4.6.0-patch13|>=2.4.7.0-beta1,<2.4.7.0-patch8|>=2.4.8.0-beta1,<2.4.8.0-patch3|>=2.4.9.0-alpha1,<2.4.9.0-alpha3|==2.4.9",
"magento/core": "<=1.9.4.5",
"magento/magento1ce": "<1.9.4.3-dev",
"magento/magento1ee": ">=1,<1.14.4.3-dev",
@@ -19477,7 +19244,7 @@
"maikuolan/phpmussel": ">=1,<1.6",
"mainwp/mainwp": "<=4.4.3.3",
"manogi/nova-tiptap": "<=3.2.6",
- "mantisbt/mantisbt": "<=2.26.3",
+ "mantisbt/mantisbt": "<2.27.2",
"marcwillmann/turn": "<0.3.3",
"marshmallow/nova-tiptap": "<5.7",
"matomo/matomo": "<1.11",
@@ -19487,7 +19254,7 @@
"maximebf/debugbar": "<1.19",
"mdanter/ecc": "<2",
"mediawiki/abuse-filter": "<1.39.9|>=1.40,<1.41.3|>=1.42,<1.42.2",
- "mediawiki/cargo": "<3.6.1",
+ "mediawiki/cargo": "<3.8.3",
"mediawiki/core": "<1.39.5|==1.40",
"mediawiki/data-transfer": ">=1.39,<1.39.11|>=1.41,<1.41.3|>=1.42,<1.42.2",
"mediawiki/matomo": "<2.4.3",
@@ -19512,7 +19279,7 @@
"mojo42/jirafeau": "<4.4",
"mongodb/mongodb": ">=1,<1.9.2",
"monolog/monolog": ">=1.8,<1.12",
- "moodle/moodle": "<4.3.12|>=4.4,<4.4.8|>=4.5.0.0-beta,<4.5.4",
+ "moodle/moodle": "<4.4.11|>=4.5.0.0-beta,<4.5.7|>=5.0.0.0-beta,<5.0.3",
"moonshine/moonshine": "<=3.12.5",
"mos/cimage": "<0.7.19",
"movim/moxl": ">=0.8,<=0.10",
@@ -19565,7 +19332,7 @@
"open-web-analytics/open-web-analytics": "<1.8.1",
"opencart/opencart": ">=0",
"openid/php-openid": "<2.3",
- "openmage/magento-lts": "<20.12.3",
+ "openmage/magento-lts": "<20.16",
"opensolutions/vimbadmin": "<=3.0.15",
"opensource-workshop/connect-cms": "<1.8.7|>=2,<2.4.7",
"orchid/platform": ">=8,<14.43",
@@ -19646,8 +19413,8 @@
"prestashop/ps_emailsubscription": "<2.6.1",
"prestashop/ps_facetedsearch": "<3.4.1",
"prestashop/ps_linklist": "<3.1",
- "privatebin/privatebin": "<1.4|>=1.5,<1.7.4",
- "processwire/processwire": "<=3.0.229",
+ "privatebin/privatebin": "<1.4|>=1.5,<1.7.4|>=1.7.7,<2.0.2",
+ "processwire/processwire": "<=3.0.246",
"propel/propel": ">=2.0.0.0-alpha1,<=2.0.0.0-alpha7",
"propel/propel1": ">=1,<=1.7.1",
"pterodactyl/panel": "<=1.11.10",
@@ -19690,8 +19457,8 @@
"setasign/fpdi": "<2.6.4",
"sfroemken/url_redirect": "<=1.2.1",
"sheng/yiicms": "<1.2.1",
- "shopware/core": "<6.5.8.18-dev|>=6.6,<6.6.10.3-dev|>=6.7,<6.7.2.1-dev",
- "shopware/platform": "<=6.6.10.4|>=6.7.0.0-RC1-dev,<6.7.0.0-RC2-dev",
+ "shopware/core": "<6.6.10.7-dev|>=6.7,<6.7.3.1-dev",
+ "shopware/platform": "<6.6.10.7-dev|>=6.7,<6.7.3.1-dev",
"shopware/production": "<=6.3.5.2",
"shopware/shopware": "<=5.7.17|>=6.7,<6.7.2.1-dev",
"shopware/storefront": "<=6.4.8.1|>=6.5.8,<6.5.8.7-dev",
@@ -19744,15 +19511,16 @@
"spatie/image-optimizer": "<1.7.3",
"spencer14420/sp-php-email-handler": "<1",
"spipu/html2pdf": "<5.2.8",
+ "spiral/roadrunner": "<2025.1",
"spoon/library": "<1.4.1",
"spoonity/tcpdf": "<6.2.22",
"squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1",
"ssddanbrown/bookstack": "<24.05.1",
- "starcitizentools/citizen-skin": ">=1.9.4,<3.4",
+ "starcitizentools/citizen-skin": ">=1.9.4,<3.9",
"starcitizentools/short-description": ">=4,<4.0.1",
"starcitizentools/tabber-neue": ">=1.9.1,<2.7.2|>=3,<3.1.1",
"starcitizenwiki/embedvideo": "<=4",
- "statamic/cms": "<=5.16",
+ "statamic/cms": "<=5.22",
"stormpath/sdk": "<9.9.99",
"studio-42/elfinder": "<=2.1.64",
"studiomitte/friendlycaptcha": "<0.1.4",
@@ -19783,7 +19551,7 @@
"symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1",
"symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=5.3.14,<5.3.15|>=5.4.3,<5.4.4|>=6.0.3,<6.0.4",
"symfony/http-client": ">=4.3,<5.4.47|>=6,<6.4.15|>=7,<7.1.8",
- "symfony/http-foundation": "<5.4.46|>=6,<6.4.14|>=7,<7.1.7",
+ "symfony/http-foundation": "<5.4.50|>=6,<6.4.29|>=7,<7.3.7",
"symfony/http-kernel": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6",
"symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13",
"symfony/maker-bundle": ">=1.27,<1.29.2|>=1.30,<1.31.1",
@@ -19802,7 +19570,7 @@
"symfony/security-guard": ">=2.8,<3.4.48|>=4,<4.4.23|>=5,<5.2.8",
"symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.4.47|>=6,<6.4.15|>=7,<7.1.8",
"symfony/serializer": ">=2,<2.0.11|>=4.1,<4.4.35|>=5,<5.3.12",
- "symfony/symfony": "<5.4.47|>=6,<6.4.15|>=7,<7.1.8",
+ "symfony/symfony": "<5.4.50|>=6,<6.4.29|>=7,<7.3.7",
"symfony/translation": ">=2,<2.0.17",
"symfony/twig-bridge": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8",
"symfony/ux-autocomplete": "<2.11.2",
@@ -19837,7 +19605,7 @@
"topthink/framework": "<6.0.17|>=6.1,<=8.0.4",
"topthink/think": "<=6.1.1",
"topthink/thinkphp": "<=3.2.3|>=6.1.3,<=8.0.4",
- "torrentpier/torrentpier": "<=2.4.3",
+ "torrentpier/torrentpier": "<=2.8.8",
"tpwd/ke_search": "<4.0.3|>=4.1,<4.6.6|>=5,<5.0.2",
"tribalsystems/zenario": "<=9.7.61188",
"truckersmp/phpwhois": "<=4.3.1",
@@ -20019,7 +19787,7 @@
"type": "tidelift"
}
],
- "time": "2025-10-17T18:06:27+00:00"
+ "time": "2025-11-12T14:06:11+00:00"
},
{
"name": "sebastian/cli-parser",
@@ -21061,16 +20829,16 @@
},
{
"name": "symfony/browser-kit",
- "version": "v7.3.2",
+ "version": "v7.3.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/browser-kit.git",
- "reference": "f0b889b73a845cddef1d25fe207b37fd04cb5419"
+ "reference": "e9a9fd604296b17bf90939c3647069f1f16ef04e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/browser-kit/zipball/f0b889b73a845cddef1d25fe207b37fd04cb5419",
- "reference": "f0b889b73a845cddef1d25fe207b37fd04cb5419",
+ "url": "https://api.github.com/repos/symfony/browser-kit/zipball/e9a9fd604296b17bf90939c3647069f1f16ef04e",
+ "reference": "e9a9fd604296b17bf90939c3647069f1f16ef04e",
"shasum": ""
},
"require": {
@@ -21109,7 +20877,7 @@
"description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/browser-kit/tree/v7.3.2"
+ "source": "https://github.com/symfony/browser-kit/tree/v7.3.6"
},
"funding": [
{
@@ -21129,20 +20897,20 @@
"type": "tidelift"
}
],
- "time": "2025-07-10T08:47:49+00:00"
+ "time": "2025-11-05T07:57:47+00:00"
},
{
"name": "symfony/debug-bundle",
- "version": "v7.3.4",
+ "version": "v7.3.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug-bundle.git",
- "reference": "30f922edd53dd85238f1f26dbb68a044109f8f0e"
+ "reference": "0aee008fb501677fa5b62ea5f65cabcf041629ef"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/30f922edd53dd85238f1f26dbb68a044109f8f0e",
- "reference": "30f922edd53dd85238f1f26dbb68a044109f8f0e",
+ "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/0aee008fb501677fa5b62ea5f65cabcf041629ef",
+ "reference": "0aee008fb501677fa5b62ea5f65cabcf041629ef",
"shasum": ""
},
"require": {
@@ -21184,7 +20952,7 @@
"description": "Provides a tight integration of the Symfony VarDumper component and the ServerLogCommand from MonologBridge into the Symfony full-stack framework",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/debug-bundle/tree/v7.3.4"
+ "source": "https://github.com/symfony/debug-bundle/tree/v7.3.5"
},
"funding": [
{
@@ -21204,7 +20972,7 @@
"type": "tidelift"
}
],
- "time": "2025-09-10T12:00:31+00:00"
+ "time": "2025-10-13T11:49:56+00:00"
},
{
"name": "symfony/maker-bundle",
@@ -21390,16 +21158,16 @@
},
{
"name": "symfony/web-profiler-bundle",
- "version": "v7.3.4",
+ "version": "v7.3.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/web-profiler-bundle.git",
- "reference": "f305fa4add690bb7d6b14ab61f37c3bd061a3dd7"
+ "reference": "c2ed11cc0e9093fe0425ad52498d26a458842e0c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/f305fa4add690bb7d6b14ab61f37c3bd061a3dd7",
- "reference": "f305fa4add690bb7d6b14ab61f37c3bd061a3dd7",
+ "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/c2ed11cc0e9093fe0425ad52498d26a458842e0c",
+ "reference": "c2ed11cc0e9093fe0425ad52498d26a458842e0c",
"shasum": ""
},
"require": {
@@ -21455,7 +21223,7 @@
"dev"
],
"support": {
- "source": "https://github.com/symfony/web-profiler-bundle/tree/v7.3.4"
+ "source": "https://github.com/symfony/web-profiler-bundle/tree/v7.3.5"
},
"funding": [
{
@@ -21475,7 +21243,7 @@
"type": "tidelift"
}
],
- "time": "2025-09-25T08:03:55+00:00"
+ "time": "2025-10-06T13:36:11+00:00"
},
{
"name": "theseer/tokenizer",
diff --git a/config/packages/translation.yaml b/config/packages/translation.yaml
index cbc1cd7e..a3f529e3 100644
--- a/config/packages/translation.yaml
+++ b/config/packages/translation.yaml
@@ -1,7 +1,7 @@
framework:
default_locale: 'en'
# Just enable the locales we need for performance reasons.
- enabled_locale: '%partdb.locale_menu%'
+ enabled_locale: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh', 'pl']
translator:
default_path: '%kernel.project_dir%/translations'
fallbacks:
diff --git a/config/packages/twig.yaml b/config/packages/twig.yaml
index 674aa317..95ae4f3b 100644
--- a/config/packages/twig.yaml
+++ b/config/packages/twig.yaml
@@ -1,6 +1,6 @@
twig:
default_path: '%kernel.project_dir%/templates'
- form_themes: ['bootstrap_5_horizontal_layout.html.twig', 'form/extended_bootstrap_layout.html.twig', 'form/permission_layout.html.twig', 'form/filter_types_layout.html.twig']
+ form_themes: ['bootstrap_5_horizontal_layout.html.twig', 'form/extended_bootstrap_layout.html.twig', 'form/permission_layout.html.twig', 'form/filter_types_layout.html.twig', 'form/synonyms_collection.html.twig']
paths:
'%kernel.project_dir%/assets/css': css
@@ -20,4 +20,4 @@ twig:
when@test:
twig:
- strict_variables: true
\ No newline at end of file
+ strict_variables: true
diff --git a/config/permissions.yaml b/config/permissions.yaml
index 8cbd60c3..5adfb79d 100644
--- a/config/permissions.yaml
+++ b/config/permissions.yaml
@@ -18,13 +18,13 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
parts: # e.g. this maps to perms_parts in User/Group database
group: "data"
- label: "perm.parts"
+ label: "{{part}}"
operations: # Here are all possible operations are listed => the op name is mapped to bit value
read:
label: "perm.read"
# If a part can be read by a user, he can also see all the datastructures (except devices)
alsoSet: ['storelocations.read', 'footprints.read', 'categories.read', 'suppliers.read', 'manufacturers.read',
- 'currencies.read', 'attachment_types.read', 'measurement_units.read']
+ 'currencies.read', 'attachment_types.read', 'measurement_units.read', 'part_custom_states.read']
apiTokenRole: ROLE_API_READ_ONLY
edit:
label: "perm.edit"
@@ -71,7 +71,7 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
storelocations: &PART_CONTAINING
- label: "perm.storelocations"
+ label: "{{storage_location}}"
group: "data"
operations:
read:
@@ -103,35 +103,39 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
footprints:
<<: *PART_CONTAINING
- label: "perm.part.footprints"
+ label: "{{footprint}}"
categories:
<<: *PART_CONTAINING
- label: "perm.part.categories"
+ label: "{{category}}"
suppliers:
<<: *PART_CONTAINING
- label: "perm.part.supplier"
+ label: "{{supplier}}"
manufacturers:
<<: *PART_CONTAINING
- label: "perm.part.manufacturers"
+ label: "{{manufacturer}}"
projects:
<<: *PART_CONTAINING
- label: "perm.projects"
+ label: "{{project}}"
attachment_types:
<<: *PART_CONTAINING
- label: "perm.part.attachment_types"
+ label: "{{attachment_type}}"
currencies:
<<: *PART_CONTAINING
- label: "perm.currencies"
+ label: "{{currency}}"
measurement_units:
<<: *PART_CONTAINING
- label: "perm.measurement_units"
+ label: "{{measurement_unit}}"
+
+ part_custom_states:
+ <<: *PART_CONTAINING
+ label: "{{part_custom_state}}"
tools:
label: "perm.part.tools"
@@ -373,4 +377,4 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
manage_tokens:
label: "perm.api.manage_tokens"
alsoSet: ['access_api']
- apiTokenRole: ROLE_API_FULL
\ No newline at end of file
+ apiTokenRole: ROLE_API_FULL
diff --git a/config/services.yaml b/config/services.yaml
index 17611cea..b48b3eff 100644
--- a/config/services.yaml
+++ b/config/services.yaml
@@ -231,6 +231,16 @@ services:
tags:
- { name: 'doctrine.fixtures.purger_factory', alias: 'reset_autoincrement_purger' }
+ App\Repository\PartRepository:
+ arguments:
+ $translator: '@translator'
+ tags: ['doctrine.repository_service']
+
+ App\EventSubscriber\UserSystem\PartUniqueIpnSubscriber:
+ tags:
+ - { name: doctrine.event_listener, event: onFlush, connection: default }
+
+
# We are needing this service inside a migration, where only the container is injected. So we need to define it as public, to access it from the container.
App\Services\UserSystem\PermissionPresetsHelper:
public: true
diff --git a/docs/configuration.md b/docs/configuration.md
index d4b21781..4bb46d08 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -116,6 +116,16 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept
value should be handled as confidential data and not shared publicly.
* `SHOW_PART_IMAGE_OVERLAY`: Set to 0 to disable the part image overlay, which appears if you hover over an image in the
part image gallery
+* `IPN_SUGGEST_REGEX`: A global regular expression, that part IPNs have to fullfill. Enforce your own format for your users.
+* `IPN_SUGGEST_REGEX_HELP`: Define your own user help text for the Regex format specification.
+* `IPN_AUTO_APPEND_SUFFIX`: When enabled, an incremental suffix will be added to the user input when entering an existing
+* IPN again upon saving.
+* `IPN_SUGGEST_PART_DIGITS`: Defines the fixed number of digits used as the increment at the end of an IPN (Internal Part Number).
+ IPN prefixes, maintained within part categories and their hierarchy, form the foundation for suggesting complete IPNs.
+ These suggestions become accessible during IPN input of a part. The constant specifies the digits used to calculate and assign
+ unique increments for parts within a category hierarchy, ensuring consistency and uniqueness in IPN generation.
+* `IPN_USE_DUPLICATE_DESCRIPTION`: When enabled, the part’s description is used to find existing parts with the same
+ description and to determine the next available IPN by incrementing their numeric suffix for the suggestion list.
### E-Mail settings (all env only)
@@ -136,7 +146,7 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept
* `TABLE_PARTS_DEFAULT_COLUMNS`: The columns in parts tables, which are visible by default (when loading table for first
time).
Also specify the default order of the columns. This is a comma separated list of column names. Available columns
- are: `name`, `id`, `ipn`, `description`, `category`, `footprint`, `manufacturer`, `storage_location`, `amount`, `minamount`, `partUnit`, `addedDate`, `lastModified`, `needs_review`, `favorite`, `manufacturing_status`, `manufacturer_product_number`, `mass`, `tags`, `attachments`, `edit`.
+ are: `name`, `id`, `ipn`, `description`, `category`, `footprint`, `manufacturer`, `storage_location`, `amount`, `minamount`, `partUnit`, `partCustomState`, `addedDate`, `lastModified`, `needs_review`, `favorite`, `manufacturing_status`, `manufacturer_product_number`, `mass`, `tags`, `attachments`, `edit`.
### History/Eventlog-related settings
diff --git a/migrations/Version20250321075747.php b/migrations/Version20250321075747.php
new file mode 100644
index 00000000..14bcb8a9
--- /dev/null
+++ b/migrations/Version20250321075747.php
@@ -0,0 +1,605 @@
+addSql(<<<'SQL'
+ CREATE TABLE part_custom_states (
+ id INT AUTO_INCREMENT NOT NULL,
+ parent_id INT DEFAULT NULL,
+ id_preview_attachment INT DEFAULT NULL,
+ name VARCHAR(255) NOT NULL,
+ comment LONGTEXT NOT NULL,
+ not_selectable TINYINT(1) NOT NULL,
+ alternative_names LONGTEXT DEFAULT NULL,
+ last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ INDEX IDX_F552745D727ACA70 (parent_id),
+ INDEX IDX_F552745DEA7100A1 (id_preview_attachment),
+ INDEX part_custom_state_name (name),
+ PRIMARY KEY(id)
+ )
+ DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci`
+ SQL);
+ $this->addSql(<<<'SQL'
+ ALTER TABLE part_custom_states ADD CONSTRAINT FK_F552745D727ACA70 FOREIGN KEY (parent_id) REFERENCES part_custom_states (id)
+ SQL);
+ $this->addSql(<<<'SQL'
+ ALTER TABLE part_custom_states ADD CONSTRAINT FK_F552745DEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON DELETE SET NULL
+ SQL);
+
+ $this->addSql(<<<'SQL'
+ ALTER TABLE parts ADD id_part_custom_state INT DEFAULT NULL AFTER id_part_unit
+ SQL);
+ $this->addSql(<<<'SQL'
+ ALTER TABLE parts ADD CONSTRAINT FK_6940A7FEA3ED1215 FOREIGN KEY (id_part_custom_state) REFERENCES part_custom_states (id)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX IDX_6940A7FEA3ED1215 ON parts (id_part_custom_state)
+ SQL);
+ }
+
+ public function mySQLDown(Schema $schema): void
+ {
+ $this->addSql(<<<'SQL'
+ ALTER TABLE parts DROP FOREIGN KEY FK_6940A7FEA3ED1215
+ SQL);
+ $this->addSql(<<<'SQL'
+ DROP INDEX IDX_6940A7FEA3ED1215 ON parts
+ SQL);
+ $this->addSql(<<<'SQL'
+ ALTER TABLE parts DROP id_part_custom_state
+ SQL);
+
+ $this->addSql(<<<'SQL'
+ ALTER TABLE part_custom_states DROP FOREIGN KEY FK_F552745D727ACA70
+ SQL);
+ $this->addSql(<<<'SQL'
+ ALTER TABLE part_custom_states DROP FOREIGN KEY FK_F552745DEA7100A1
+ SQL);
+ $this->addSql(<<<'SQL'
+ DROP TABLE part_custom_states
+ SQL);
+ }
+
+ public function sqLiteUp(Schema $schema): void
+ {
+ $this->addSql(<<<'SQL'
+ CREATE TABLE "part_custom_states" (
+ id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ parent_id INTEGER DEFAULT NULL,
+ id_preview_attachment INTEGER DEFAULT NULL,
+ name VARCHAR(255) NOT NULL,
+ comment CLOB NOT NULL,
+ not_selectable BOOLEAN NOT NULL,
+ alternative_names CLOB DEFAULT NULL,
+ last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ CONSTRAINT FK_F552745D727ACA70 FOREIGN KEY (parent_id) REFERENCES "part_custom_states" (id) NOT DEFERRABLE INITIALLY IMMEDIATE,
+ CONSTRAINT FK_F5AF83CFEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE
+ )
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX IDX_F552745D727ACA70 ON "part_custom_states" (parent_id)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX part_custom_state_name ON "part_custom_states" (name)
+ SQL);
+
+ $this->addSql(<<<'SQL'
+ CREATE TEMPORARY TABLE __temp__parts AS
+ SELECT
+ id,
+ id_preview_attachment,
+ id_category,
+ id_footprint,
+ id_part_unit,
+ id_manufacturer,
+ order_orderdetails_id,
+ built_project_id,
+ datetime_added,
+ name,
+ last_modified,
+ needs_review,
+ tags,
+ mass,
+ description,
+ comment,
+ visible,
+ favorite,
+ minamount,
+ manufacturer_product_url,
+ manufacturer_product_number,
+ manufacturing_status,
+ order_quantity,
+ manual_order,
+ ipn,
+ provider_reference_provider_key,
+ provider_reference_provider_id,
+ provider_reference_provider_url,
+ provider_reference_last_updated,
+ eda_info_reference_prefix,
+ eda_info_value,
+ eda_info_invisible,
+ eda_info_exclude_from_bom,
+ eda_info_exclude_from_board,
+ eda_info_exclude_from_sim,
+ eda_info_kicad_symbol,
+ eda_info_kicad_footprint
+ FROM parts
+ SQL);
+
+ $this->addSql(<<<'SQL'
+ DROP TABLE parts
+ SQL);
+
+ $this->addSql(<<<'SQL'
+ CREATE TABLE parts (
+ id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ id_preview_attachment INTEGER DEFAULT NULL,
+ id_category INTEGER NOT NULL,
+ id_footprint INTEGER DEFAULT NULL,
+ id_part_unit INTEGER DEFAULT NULL,
+ id_manufacturer INTEGER DEFAULT NULL,
+ id_part_custom_state INTEGER DEFAULT NULL,
+ order_orderdetails_id INTEGER DEFAULT NULL,
+ built_project_id INTEGER DEFAULT NULL,
+ datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ name VARCHAR(255) NOT NULL,
+ last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ needs_review BOOLEAN NOT NULL,
+ tags CLOB NOT NULL,
+ mass DOUBLE PRECISION DEFAULT NULL,
+ description CLOB NOT NULL,
+ comment CLOB NOT NULL,
+ visible BOOLEAN NOT NULL,
+ favorite BOOLEAN NOT NULL,
+ minamount DOUBLE PRECISION NOT NULL,
+ manufacturer_product_url CLOB NOT NULL,
+ manufacturer_product_number VARCHAR(255) NOT NULL,
+ manufacturing_status VARCHAR(255) DEFAULT NULL,
+ order_quantity INTEGER NOT NULL,
+ manual_order BOOLEAN NOT NULL,
+ ipn VARCHAR(100) DEFAULT NULL,
+ provider_reference_provider_key VARCHAR(255) DEFAULT NULL,
+ provider_reference_provider_id VARCHAR(255) DEFAULT NULL,
+ provider_reference_provider_url VARCHAR(255) DEFAULT NULL,
+ provider_reference_last_updated DATETIME DEFAULT NULL,
+ eda_info_reference_prefix VARCHAR(255) DEFAULT NULL,
+ eda_info_value VARCHAR(255) DEFAULT NULL,
+ eda_info_invisible BOOLEAN DEFAULT NULL,
+ eda_info_exclude_from_bom BOOLEAN DEFAULT NULL,
+ eda_info_exclude_from_board BOOLEAN DEFAULT NULL,
+ eda_info_exclude_from_sim BOOLEAN DEFAULT NULL,
+ eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL,
+ eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL,
+ CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE,
+ CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES categories (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE,
+ CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES footprints (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE,
+ CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES measurement_units (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE,
+ CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES manufacturers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE,
+ CONSTRAINT FK_6940A7FEA3ED1215 FOREIGN KEY (id_part_custom_state) REFERENCES "part_custom_states" (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE,
+ CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES orderdetails (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE,
+ CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE
+ )
+ SQL);
+
+ $this->addSql(<<<'SQL'
+ INSERT INTO parts (
+ id,
+ id_preview_attachment,
+ id_category,
+ id_footprint,
+ id_part_unit,
+ id_manufacturer,
+ order_orderdetails_id,
+ built_project_id,
+ datetime_added,
+ name,
+ last_modified,
+ needs_review,
+ tags,
+ mass,
+ description,
+ comment,
+ visible,
+ favorite,
+ minamount,
+ manufacturer_product_url,
+ manufacturer_product_number,
+ manufacturing_status,
+ order_quantity,
+ manual_order,
+ ipn,
+ provider_reference_provider_key,
+ provider_reference_provider_id,
+ provider_reference_provider_url,
+ provider_reference_last_updated,
+ eda_info_reference_prefix,
+ eda_info_value,
+ eda_info_invisible,
+ eda_info_exclude_from_bom,
+ eda_info_exclude_from_board,
+ eda_info_exclude_from_sim,
+ eda_info_kicad_symbol,
+ eda_info_kicad_footprint)
+ SELECT
+ id,
+ id_preview_attachment,
+ id_category,
+ id_footprint,
+ id_part_unit,
+ id_manufacturer,
+ order_orderdetails_id,
+ built_project_id,
+ datetime_added,
+ name,
+ last_modified,
+ needs_review,
+ tags,
+ mass,
+ description,
+ comment,
+ visible,
+ favorite,
+ minamount,
+ manufacturer_product_url,
+ manufacturer_product_number,
+ manufacturing_status,
+ order_quantity,
+ manual_order,
+ ipn,
+ provider_reference_provider_key,
+ provider_reference_provider_id,
+ provider_reference_provider_url,
+ provider_reference_last_updated,
+ eda_info_reference_prefix,
+ eda_info_value,
+ eda_info_invisible,
+ eda_info_exclude_from_bom,
+ eda_info_exclude_from_board,
+ eda_info_exclude_from_sim,
+ eda_info_kicad_symbol,
+ eda_info_kicad_footprint
+ FROM __temp__parts
+ SQL);
+
+ $this->addSql(<<<'SQL'
+ DROP TABLE __temp__parts
+ SQL);
+
+ $this->addSql(<<<'SQL'
+ CREATE INDEX parts_idx_name ON parts (name)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX parts_idx_ipn ON parts (ipn)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX parts_idx_datet_name_last_id_needs ON parts (datetime_added, name, last_modified, id, needs_review)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON parts (built_project_id)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON parts (order_orderdetails_id)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON parts (ipn)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX IDX_6940A7FEEA7100A1 ON parts (id_preview_attachment)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX IDX_6940A7FE7E371A10 ON parts (id_footprint)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX IDX_6940A7FE5697F554 ON parts (id_category)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX IDX_6940A7FE2626CEF9 ON parts (id_part_unit)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX IDX_6940A7FE1ECB93AE ON parts (id_manufacturer)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX IDX_6940A7FEA3ED1215 ON parts (id_part_custom_state)
+ SQL);
+ }
+
+ public function sqLiteDown(Schema $schema): void
+ {
+ $this->addSql(<<<'SQL'
+ CREATE TEMPORARY TABLE __temp__parts AS
+ SELECT
+ id,
+ id_preview_attachment,
+ id_category,
+ id_footprint,
+ id_part_unit,
+ id_manufacturer,
+ order_orderdetails_id,
+ built_project_id,
+ datetime_added,
+ name,
+ last_modified,
+ needs_review,
+ tags,
+ mass,
+ description,
+ comment,
+ visible,
+ favorite,
+ minamount,
+ manufacturer_product_url,
+ manufacturer_product_number,
+ manufacturing_status,
+ order_quantity,
+ manual_order,
+ ipn,
+ provider_reference_provider_key,
+ provider_reference_provider_id,
+ provider_reference_provider_url,
+ provider_reference_last_updated,
+ eda_info_reference_prefix,
+ eda_info_value,
+ eda_info_invisible,
+ eda_info_exclude_from_bom,
+ eda_info_exclude_from_board,
+ eda_info_exclude_from_sim,
+ eda_info_kicad_symbol,
+ eda_info_kicad_footprint
+ FROM "parts"
+ SQL);
+ $this->addSql(<<<'SQL'
+ DROP TABLE "parts"
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE TABLE "parts" (
+ id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ id_preview_attachment INTEGER DEFAULT NULL,
+ id_category INTEGER NOT NULL,
+ id_footprint INTEGER DEFAULT NULL,
+ id_part_unit INTEGER DEFAULT NULL,
+ id_manufacturer INTEGER DEFAULT NULL,
+ order_orderdetails_id INTEGER DEFAULT NULL,
+ built_project_id INTEGER DEFAULT NULL,
+ datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ name VARCHAR(255) NOT NULL,
+ last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ needs_review BOOLEAN NOT NULL,
+ tags CLOB NOT NULL,
+ mass DOUBLE PRECISION DEFAULT NULL,
+ description CLOB NOT NULL,
+ comment CLOB NOT NULL,
+ visible BOOLEAN NOT NULL,
+ favorite BOOLEAN NOT NULL,
+ minamount DOUBLE PRECISION NOT NULL,
+ manufacturer_product_url CLOB NOT NULL,
+ manufacturer_product_number VARCHAR(255) NOT NULL,
+ manufacturing_status VARCHAR(255) DEFAULT NULL,
+ order_quantity INTEGER NOT NULL,
+ manual_order BOOLEAN NOT NULL,
+ ipn VARCHAR(100) DEFAULT NULL,
+ provider_reference_provider_key VARCHAR(255) DEFAULT NULL,
+ provider_reference_provider_id VARCHAR(255) DEFAULT NULL,
+ provider_reference_provider_url VARCHAR(255) DEFAULT NULL,
+ provider_reference_last_updated DATETIME DEFAULT NULL,
+ eda_info_reference_prefix VARCHAR(255) DEFAULT NULL,
+ eda_info_value VARCHAR(255) DEFAULT NULL,
+ eda_info_invisible BOOLEAN DEFAULT NULL,
+ eda_info_exclude_from_bom BOOLEAN DEFAULT NULL,
+ eda_info_exclude_from_board BOOLEAN DEFAULT NULL,
+ eda_info_exclude_from_sim BOOLEAN DEFAULT NULL,
+ eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL,
+ eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL,
+ CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE,
+ CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES "categories" (id) NOT DEFERRABLE INITIALLY IMMEDIATE,
+ CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES "footprints" (id) NOT DEFERRABLE INITIALLY IMMEDIATE,
+ CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE,
+ CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES "manufacturers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE,
+ CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES "orderdetails" (id) NOT DEFERRABLE INITIALLY IMMEDIATE,
+ CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE
+ )
+ SQL);
+ $this->addSql(<<<'SQL'
+ INSERT INTO "parts" (
+ id,
+ id_preview_attachment,
+ id_category,
+ id_footprint,
+ id_part_unit,
+ id_manufacturer,
+ order_orderdetails_id,
+ built_project_id,
+ datetime_added,
+ name,
+ last_modified,
+ needs_review,
+ tags,
+ mass,
+ description,
+ comment,
+ visible,
+ favorite,
+ minamount,
+ manufacturer_product_url,
+ manufacturer_product_number,
+ manufacturing_status,
+ order_quantity,
+ manual_order,
+ ipn,
+ provider_reference_provider_key,
+ provider_reference_provider_id,
+ provider_reference_provider_url,
+ provider_reference_last_updated,
+ eda_info_reference_prefix,
+ eda_info_value,
+ eda_info_invisible,
+ eda_info_exclude_from_bom,
+ eda_info_exclude_from_board,
+ eda_info_exclude_from_sim,
+ eda_info_kicad_symbol,
+ eda_info_kicad_footprint
+ ) SELECT
+ id,
+ id_preview_attachment,
+ id_category,
+ id_footprint,
+ id_part_unit,
+ id_manufacturer,
+ order_orderdetails_id,
+ built_project_id,
+ datetime_added,
+ name,
+ last_modified,
+ needs_review,
+ tags,
+ mass,
+ description,
+ comment,
+ visible,
+ favorite,
+ minamount,
+ manufacturer_product_url,
+ manufacturer_product_number,
+ manufacturing_status,
+ order_quantity,
+ manual_order,
+ ipn,
+ provider_reference_provider_key,
+ provider_reference_provider_id,
+ provider_reference_provider_url,
+ provider_reference_last_updated,
+ eda_info_reference_prefix,
+ eda_info_value,
+ eda_info_invisible,
+ eda_info_exclude_from_bom,
+ eda_info_exclude_from_board,
+ eda_info_exclude_from_sim,
+ eda_info_kicad_symbol,
+ eda_info_kicad_footprint
+ FROM __temp__parts
+ SQL);
+
+ $this->addSql(<<<'SQL'
+ DROP TABLE __temp__parts
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON "parts" (ipn)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX IDX_6940A7FEEA7100A1 ON "parts" (id_preview_attachment)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX IDX_6940A7FE5697F554 ON "parts" (id_category)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX IDX_6940A7FE7E371A10 ON "parts" (id_footprint)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX IDX_6940A7FE2626CEF9 ON "parts" (id_part_unit)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX IDX_6940A7FE1ECB93AE ON "parts" (id_manufacturer)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON "parts" (order_orderdetails_id)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON "parts" (built_project_id)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX parts_idx_datet_name_last_id_needs ON "parts" (datetime_added, name, last_modified, id, needs_review)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX parts_idx_name ON "parts" (name)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX parts_idx_ipn ON "parts" (ipn)
+ SQL);
+
+ $this->addSql(<<<'SQL'
+ DROP TABLE "part_custom_states"
+ SQL);
+ }
+
+ public function postgreSQLUp(Schema $schema): void
+ {
+ $this->addSql(<<<'SQL'
+ CREATE TABLE "part_custom_states" (
+ id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
+ parent_id INT DEFAULT NULL,
+ id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id),
+ name VARCHAR(255) NOT NULL,
+ comment TEXT NOT NULL,
+ not_selectable BOOLEAN NOT NULL,
+ alternative_names TEXT DEFAULT NULL,
+ last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL
+ )
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX IDX_F552745D727ACA70 ON "part_custom_states" (parent_id)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX IDX_F552745DEA7100A1 ON "part_custom_states" (id_preview_attachment)
+ SQL);
+ $this->addSql(<<<'SQL'
+ ALTER TABLE "part_custom_states"
+ ADD CONSTRAINT FK_F552745D727ACA70
+ FOREIGN KEY (parent_id) REFERENCES "part_custom_states" (id) NOT DEFERRABLE INITIALLY IMMEDIATE
+ SQL);
+ $this->addSql(<<<'SQL'
+ ALTER TABLE "part_custom_states"
+ ADD CONSTRAINT FK_F552745DEA7100A1
+ FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE
+ SQL);
+
+
+ $this->addSql(<<<'SQL'
+ ALTER TABLE parts ADD id_part_custom_state INT DEFAULT NULL
+ SQL);
+ $this->addSql(<<<'SQL'
+ ALTER TABLE parts ADD CONSTRAINT FK_6940A7FEA3ED1215 FOREIGN KEY (id_part_custom_state) REFERENCES "part_custom_states" (id) NOT DEFERRABLE INITIALLY IMMEDIATE
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX IDX_6940A7FEA3ED1215 ON parts (id_part_custom_state)
+ SQL);
+ }
+
+ public function postgreSQLDown(Schema $schema): void
+ {
+ $this->addSql(<<<'SQL'
+ ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FEA3ED1215
+ SQL);
+ $this->addSql(<<<'SQL'
+ DROP INDEX IDX_6940A7FEA3ED1215
+ SQL);
+ $this->addSql(<<<'SQL'
+ ALTER TABLE "parts" DROP id_part_custom_state
+ SQL);
+
+ $this->addSql(<<<'SQL'
+ ALTER TABLE "part_custom_states" DROP CONSTRAINT FK_F552745D727ACA70
+ SQL);
+ $this->addSql(<<<'SQL'
+ ALTER TABLE "part_custom_states" DROP CONSTRAINT FK_F552745DEA7100A1
+ SQL);
+ $this->addSql(<<<'SQL'
+ DROP TABLE "part_custom_states"
+ SQL);
+ }
+}
diff --git a/migrations/Version20250325073036.php b/migrations/Version20250325073036.php
new file mode 100644
index 00000000..3bae80ab
--- /dev/null
+++ b/migrations/Version20250325073036.php
@@ -0,0 +1,307 @@
+addSql(<<<'SQL'
+ ALTER TABLE categories ADD COLUMN part_ipn_prefix VARCHAR(255) NOT NULL DEFAULT ''
+ SQL);
+ }
+
+ public function mySQLDown(Schema $schema): void
+ {
+ $this->addSql(<<<'SQL'
+ ALTER TABLE categories DROP part_ipn_prefix
+ SQL);
+ }
+
+ public function sqLiteUp(Schema $schema): void
+ {
+ $this->addSql(<<<'SQL'
+ CREATE TEMPORARY TABLE __temp__categories AS
+ SELECT
+ id,
+ parent_id,
+ id_preview_attachment,
+ partname_hint,
+ partname_regex,
+ disable_footprints,
+ disable_manufacturers,
+ disable_autodatasheets,
+ disable_properties,
+ default_description,
+ default_comment,
+ comment,
+ not_selectable,
+ name,
+ last_modified,
+ datetime_added,
+ alternative_names,
+ eda_info_reference_prefix,
+ eda_info_invisible,
+ eda_info_exclude_from_bom,
+ eda_info_exclude_from_board,
+ eda_info_exclude_from_sim,
+ eda_info_kicad_symbol
+ FROM categories
+ SQL);
+
+ $this->addSql('DROP TABLE categories');
+
+ $this->addSql(<<<'SQL'
+ CREATE TABLE categories (
+ id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ parent_id INTEGER DEFAULT NULL,
+ id_preview_attachment INTEGER DEFAULT NULL,
+ partname_hint CLOB NOT NULL,
+ partname_regex CLOB NOT NULL,
+ part_ipn_prefix VARCHAR(255) DEFAULT '' NOT NULL,
+ disable_footprints BOOLEAN NOT NULL,
+ disable_manufacturers BOOLEAN NOT NULL,
+ disable_autodatasheets BOOLEAN NOT NULL,
+ disable_properties BOOLEAN NOT NULL,
+ default_description CLOB NOT NULL,
+ default_comment CLOB NOT NULL,
+ comment CLOB NOT NULL,
+ not_selectable BOOLEAN NOT NULL,
+ name VARCHAR(255) NOT NULL,
+ last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ alternative_names CLOB DEFAULT NULL,
+ eda_info_reference_prefix VARCHAR(255) DEFAULT NULL,
+ eda_info_invisible BOOLEAN DEFAULT NULL,
+ eda_info_exclude_from_bom BOOLEAN DEFAULT NULL,
+ eda_info_exclude_from_board BOOLEAN DEFAULT NULL,
+ eda_info_exclude_from_sim BOOLEAN DEFAULT NULL,
+ eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL,
+ CONSTRAINT FK_3AF34668727ACA70 FOREIGN KEY (parent_id) REFERENCES categories (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE,
+ CONSTRAINT FK_3AF34668EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE
+ )
+ SQL);
+
+ $this->addSql(<<<'SQL'
+ INSERT INTO categories (
+ id,
+ parent_id,
+ id_preview_attachment,
+ partname_hint,
+ partname_regex,
+ disable_footprints,
+ disable_manufacturers,
+ disable_autodatasheets,
+ disable_properties,
+ default_description,
+ default_comment,
+ comment,
+ not_selectable,
+ name,
+ last_modified,
+ datetime_added,
+ alternative_names,
+ eda_info_reference_prefix,
+ eda_info_invisible,
+ eda_info_exclude_from_bom,
+ eda_info_exclude_from_board,
+ eda_info_exclude_from_sim,
+ eda_info_kicad_symbol
+ ) SELECT
+ id,
+ parent_id,
+ id_preview_attachment,
+ partname_hint,
+ partname_regex,
+ disable_footprints,
+ disable_manufacturers,
+ disable_autodatasheets,
+ disable_properties,
+ default_description,
+ default_comment,
+ comment,
+ not_selectable,
+ name,
+ last_modified,
+ datetime_added,
+ alternative_names,
+ eda_info_reference_prefix,
+ eda_info_invisible,
+ eda_info_exclude_from_bom,
+ eda_info_exclude_from_board,
+ eda_info_exclude_from_sim,
+ eda_info_kicad_symbol
+ FROM __temp__categories
+ SQL);
+
+ $this->addSql('DROP TABLE __temp__categories');
+
+ $this->addSql(<<<'SQL'
+ CREATE INDEX IDX_3AF34668727ACA70 ON categories (parent_id)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX IDX_3AF34668EA7100A1 ON categories (id_preview_attachment)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX category_idx_name ON categories (name)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX category_idx_parent_name ON categories (parent_id, name)
+ SQL);
+ }
+
+ public function sqLiteDown(Schema $schema): void
+ {
+ $this->addSql(<<<'SQL'
+ CREATE TEMPORARY TABLE __temp__categories AS
+ SELECT
+ id,
+ parent_id,
+ id_preview_attachment,
+ partname_hint,
+ partname_regex,
+ disable_footprints,
+ disable_manufacturers,
+ disable_autodatasheets,
+ disable_properties,
+ default_description,
+ default_comment,
+ comment,
+ not_selectable,
+ name,
+ last_modified,
+ datetime_added,
+ alternative_names,
+ eda_info_reference_prefix,
+ eda_info_invisible,
+ eda_info_exclude_from_bom,
+ eda_info_exclude_from_board,
+ eda_info_exclude_from_sim,
+ eda_info_kicad_symbol
+ FROM categories
+ SQL);
+
+ $this->addSql('DROP TABLE categories');
+
+ $this->addSql(<<<'SQL'
+ CREATE TABLE categories (
+ id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ parent_id INTEGER DEFAULT NULL,
+ id_preview_attachment INTEGER DEFAULT NULL,
+ partname_hint CLOB NOT NULL,
+ partname_regex CLOB NOT NULL,
+ disable_footprints BOOLEAN NOT NULL,
+ disable_manufacturers BOOLEAN NOT NULL,
+ disable_autodatasheets BOOLEAN NOT NULL,
+ disable_properties BOOLEAN NOT NULL,
+ default_description CLOB NOT NULL,
+ default_comment CLOB NOT NULL,
+ comment CLOB NOT NULL,
+ not_selectable BOOLEAN NOT NULL,
+ name VARCHAR(255) NOT NULL,
+ last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ alternative_names CLOB DEFAULT NULL,
+ eda_info_reference_prefix VARCHAR(255) DEFAULT NULL,
+ eda_info_invisible BOOLEAN DEFAULT NULL,
+ eda_info_exclude_from_bom BOOLEAN DEFAULT NULL,
+ eda_info_exclude_from_board BOOLEAN DEFAULT NULL,
+ eda_info_exclude_from_sim BOOLEAN DEFAULT NULL,
+ eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL,
+ CONSTRAINT FK_3AF34668727ACA70 FOREIGN KEY (parent_id) REFERENCES categories (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE,
+ CONSTRAINT FK_3AF34668EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE
+ )
+ SQL);
+
+ $this->addSql(<<<'SQL'
+ INSERT INTO categories (
+ id,
+ parent_id,
+ id_preview_attachment,
+ partname_hint,
+ partname_regex,
+ disable_footprints,
+ disable_manufacturers,
+ disable_autodatasheets,
+ disable_properties,
+ default_description,
+ default_comment,
+ comment,
+ not_selectable,
+ name,
+ last_modified,
+ datetime_added,
+ alternative_names,
+ eda_info_reference_prefix,
+ eda_info_invisible,
+ eda_info_exclude_from_bom,
+ eda_info_exclude_from_board,
+ eda_info_exclude_from_sim,
+ eda_info_kicad_symbol
+ ) SELECT
+ id,
+ parent_id,
+ id_preview_attachment,
+ partname_hint,
+ partname_regex,
+ disable_footprints,
+ disable_manufacturers,
+ disable_autodatasheets,
+ disable_properties,
+ default_description,
+ default_comment,
+ comment,
+ not_selectable,
+ name,
+ last_modified,
+ datetime_added,
+ alternative_names,
+ eda_info_reference_prefix,
+ eda_info_invisible,
+ eda_info_exclude_from_bom,
+ eda_info_exclude_from_board,
+ eda_info_exclude_from_sim,
+ eda_info_kicad_symbol
+ FROM __temp__categories
+ SQL);
+
+ $this->addSql('DROP TABLE __temp__categories');
+
+ $this->addSql(<<<'SQL'
+ CREATE INDEX IDX_3AF34668727ACA70 ON categories (parent_id)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX IDX_3AF34668EA7100A1 ON categories (id_preview_attachment)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX category_idx_name ON categories (name)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX category_idx_parent_name ON categories (parent_id, name)
+ SQL);
+ }
+
+ public function postgreSQLUp(Schema $schema): void
+ {
+ $this->addSql(<<<'SQL'
+ ALTER TABLE categories ADD part_ipn_prefix VARCHAR(255) DEFAULT '' NOT NULL
+ SQL);
+ }
+
+ public function postgreSQLDown(Schema $schema): void
+ {
+ $this->addSql(<<<'SQL'
+ ALTER TABLE "categories" DROP part_ipn_prefix
+ SQL);
+ }
+}
diff --git a/src/Command/Migrations/ImportPartKeeprCommand.php b/src/Command/Migrations/ImportPartKeeprCommand.php
index aee71afe..429f018d 100644
--- a/src/Command/Migrations/ImportPartKeeprCommand.php
+++ b/src/Command/Migrations/ImportPartKeeprCommand.php
@@ -121,6 +121,11 @@ class ImportPartKeeprCommand extends Command
$count = $this->datastructureImporter->importPartUnits($data);
$io->success('Imported '.$count.' measurement units.');
+ //Import the custom states
+ $io->info('Importing custom states...');
+ $count = $this->datastructureImporter->importPartCustomStates($data);
+ $io->success('Imported '.$count.' custom states.');
+
//Import manufacturers
$io->info('Importing manufacturers...');
$count = $this->datastructureImporter->importManufacturers($data);
diff --git a/src/Controller/AdminPages/BaseAdminController.php b/src/Controller/AdminPages/BaseAdminController.php
index edc5917a..e7dd7421 100644
--- a/src/Controller/AdminPages/BaseAdminController.php
+++ b/src/Controller/AdminPages/BaseAdminController.php
@@ -232,6 +232,7 @@ abstract class BaseAdminController extends AbstractController
'timeTravel' => $timeTravel_timestamp,
'repo' => $repo,
'partsContainingElement' => $repo instanceof PartsContainingRepositoryInterface,
+ 'showParameters' => !($this instanceof PartCustomStateController),
]);
}
@@ -382,6 +383,7 @@ abstract class BaseAdminController extends AbstractController
'import_form' => $import_form,
'mass_creation_form' => $mass_creation_form,
'route_base' => $this->route_base,
+ 'showParameters' => !($this instanceof PartCustomStateController),
]);
}
diff --git a/src/Controller/AdminPages/PartCustomStateController.php b/src/Controller/AdminPages/PartCustomStateController.php
new file mode 100644
index 00000000..60f63abf
--- /dev/null
+++ b/src/Controller/AdminPages/PartCustomStateController.php
@@ -0,0 +1,83 @@
+.
+ */
+
+declare(strict_types=1);
+
+namespace App\Controller\AdminPages;
+
+use App\Entity\Attachments\PartCustomStateAttachment;
+use App\Entity\Parameters\PartCustomStateParameter;
+use App\Entity\Parts\PartCustomState;
+use App\Form\AdminPages\PartCustomStateAdminForm;
+use App\Services\ImportExportSystem\EntityExporter;
+use App\Services\ImportExportSystem\EntityImporter;
+use App\Services\Trees\StructuralElementRecursionHelper;
+use Doctrine\ORM\EntityManagerInterface;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing\Attribute\Route;
+
+/**
+ * @see \App\Tests\Controller\AdminPages\PartCustomStateControllerTest
+ */
+#[Route(path: '/part_custom_state')]
+class PartCustomStateController extends BaseAdminController
+{
+ protected string $entity_class = PartCustomState::class;
+ protected string $twig_template = 'admin/part_custom_state_admin.html.twig';
+ protected string $form_class = PartCustomStateAdminForm::class;
+ protected string $route_base = 'part_custom_state';
+ protected string $attachment_class = PartCustomStateAttachment::class;
+ protected ?string $parameter_class = PartCustomStateParameter::class;
+
+ #[Route(path: '/{id}', name: 'part_custom_state_delete', methods: ['DELETE'])]
+ public function delete(Request $request, PartCustomState $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
+ {
+ return $this->_delete($request, $entity, $recursionHelper);
+ }
+
+ #[Route(path: '/{id}/edit/{timestamp}', name: 'part_custom_state_edit', requirements: ['id' => '\d+'])]
+ #[Route(path: '/{id}', requirements: ['id' => '\d+'])]
+ public function edit(PartCustomState $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
+ {
+ return $this->_edit($entity, $request, $em, $timestamp);
+ }
+
+ #[Route(path: '/new', name: 'part_custom_state_new')]
+ #[Route(path: '/{id}/clone', name: 'part_custom_state_clone')]
+ #[Route(path: '/')]
+ public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?PartCustomState $entity = null): Response
+ {
+ return $this->_new($request, $em, $importer, $entity);
+ }
+
+ #[Route(path: '/export', name: 'part_custom_state_export_all')]
+ public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response
+ {
+ return $this->_exportAll($em, $exporter, $request);
+ }
+
+ #[Route(path: '/{id}/export', name: 'part_custom_state_export')]
+ public function exportEntity(PartCustomState $entity, EntityExporter $exporter, Request $request): Response
+ {
+ return $this->_exportEntity($entity, $exporter, $request);
+ }
+}
diff --git a/src/Controller/PartController.php b/src/Controller/PartController.php
index aeb2664e..3a121ad2 100644
--- a/src/Controller/PartController.php
+++ b/src/Controller/PartController.php
@@ -47,6 +47,7 @@ use App\Services\Parts\PartLotWithdrawAddHelper;
use App\Services\Parts\PricedetailHelper;
use App\Services\ProjectSystem\ProjectBuildPartHelper;
use App\Settings\BehaviorSettings\PartInfoSettings;
+use App\Settings\MiscSettings\IpnSuggestSettings;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
@@ -74,6 +75,7 @@ final class PartController extends AbstractController
private readonly EntityManagerInterface $em,
private readonly EventCommentHelper $commentHelper,
private readonly PartInfoSettings $partInfoSettings,
+ private readonly IpnSuggestSettings $ipnSuggestSettings,
) {
}
@@ -444,10 +446,13 @@ final class PartController extends AbstractController
$template = 'parts/edit/update_from_ip.html.twig';
}
+ $partRepository = $this->em->getRepository(Part::class);
+
return $this->render(
$template,
[
'part' => $new_part,
+ 'ipnSuggestions' => $partRepository->autoCompleteIpn($data, $data->getDescription(), $this->ipnSuggestSettings->suggestPartDigits),
'form' => $form,
'merge_old_name' => $merge_infos['tname_before'] ?? null,
'merge_other' => $merge_infos['other_part'] ?? null,
@@ -457,7 +462,6 @@ final class PartController extends AbstractController
);
}
-
#[Route(path: '/{id}/add_withdraw', name: 'part_add_withdraw', methods: ['POST'])]
public function withdrawAddHandler(Part $part, Request $request, EntityManagerInterface $em, PartLotWithdrawAddHelper $withdrawAddHelper): Response
{
diff --git a/src/Controller/SettingsController.php b/src/Controller/SettingsController.php
index 3479cf84..15c945f6 100644
--- a/src/Controller/SettingsController.php
+++ b/src/Controller/SettingsController.php
@@ -64,7 +64,7 @@ class SettingsController extends AbstractController
$this->settingsManager->save($settings);
//It might be possible, that the tree settings have changed, so clear the cache
- $cache->invalidateTags(['tree_treeview', 'sidebar_tree_update']);
+ $cache->invalidateTags(['tree_tools', 'tree_treeview', 'sidebar_tree_update', 'synonyms']);
$this->addFlash('success', t('settings.flash.saved'));
}
diff --git a/src/Controller/TypeaheadController.php b/src/Controller/TypeaheadController.php
index 89eac7ff..39821f59 100644
--- a/src/Controller/TypeaheadController.php
+++ b/src/Controller/TypeaheadController.php
@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace App\Controller;
use App\Entity\Parameters\AbstractParameter;
+use App\Settings\MiscSettings\IpnSuggestSettings;
use Symfony\Component\HttpFoundation\Response;
use App\Entity\Attachments\Attachment;
use App\Entity\Parts\Category;
@@ -60,8 +61,11 @@ use Symfony\Component\Serializer\Serializer;
#[Route(path: '/typeahead')]
class TypeaheadController extends AbstractController
{
- public function __construct(protected AttachmentURLGenerator $urlGenerator, protected Packages $assets)
- {
+ public function __construct(
+ protected AttachmentURLGenerator $urlGenerator,
+ protected Packages $assets,
+ protected IpnSuggestSettings $ipnSuggestSettings,
+ ) {
}
#[Route(path: '/builtInResources/search', name: 'typeahead_builtInRessources')]
@@ -183,4 +187,30 @@ class TypeaheadController extends AbstractController
return new JsonResponse($data, Response::HTTP_OK, [], true);
}
+
+ #[Route(path: '/parts/ipn-suggestions', name: 'ipn_suggestions', methods: ['GET'])]
+ public function ipnSuggestions(
+ Request $request,
+ EntityManagerInterface $entityManager
+ ): JsonResponse {
+ $partId = $request->query->get('partId');
+ if ($partId === '0' || $partId === 'undefined' || $partId === 'null') {
+ $partId = null;
+ }
+ $categoryId = $request->query->getInt('categoryId');
+ $description = base64_decode($request->query->getString('description'), true);
+
+ /** @var Part $part */
+ $part = $partId !== null ? $entityManager->getRepository(Part::class)->find($partId) : new Part();
+ /** @var Category|null $category */
+ $category = $entityManager->getRepository(Category::class)->find($categoryId);
+
+ $clonedPart = clone $part;
+ $clonedPart->setCategory($category);
+
+ $partRepository = $entityManager->getRepository(Part::class);
+ $ipnSuggestions = $partRepository->autoCompleteIpn($clonedPart, $description, $this->ipnSuggestSettings->suggestPartDigits);
+
+ return new JsonResponse($ipnSuggestions);
+ }
}
diff --git a/src/DataFixtures/DataStructureFixtures.php b/src/DataFixtures/DataStructureFixtures.php
index fc713d4d..9c685338 100644
--- a/src/DataFixtures/DataStructureFixtures.php
+++ b/src/DataFixtures/DataStructureFixtures.php
@@ -24,6 +24,7 @@ namespace App\DataFixtures;
use App\Entity\Attachments\AttachmentType;
use App\Entity\Base\AbstractStructuralDBElement;
+use App\Entity\Parts\PartCustomState;
use App\Entity\ProjectSystem\Project;
use App\Entity\Parts\Category;
use App\Entity\Parts\Footprint;
@@ -50,7 +51,7 @@ class DataStructureFixtures extends Fixture implements DependentFixtureInterface
{
//Reset autoincrement
$types = [AttachmentType::class, Project::class, Category::class, Footprint::class, Manufacturer::class,
- MeasurementUnit::class, StorageLocation::class, Supplier::class,];
+ MeasurementUnit::class, StorageLocation::class, Supplier::class, PartCustomState::class];
foreach ($types as $type) {
$this->createNodesForClass($type, $manager);
diff --git a/src/DataTables/Filters/PartFilter.php b/src/DataTables/Filters/PartFilter.php
index e44cf69d..cf185dfd 100644
--- a/src/DataTables/Filters/PartFilter.php
+++ b/src/DataTables/Filters/PartFilter.php
@@ -41,6 +41,7 @@ use App\Entity\Parts\Category;
use App\Entity\Parts\Footprint;
use App\Entity\Parts\Manufacturer;
use App\Entity\Parts\MeasurementUnit;
+use App\Entity\Parts\PartCustomState;
use App\Entity\Parts\PartLot;
use App\Entity\Parts\StorageLocation;
use App\Entity\Parts\Supplier;
@@ -86,6 +87,7 @@ class PartFilter implements FilterInterface
public readonly EntityConstraint $lotOwner;
public readonly EntityConstraint $measurementUnit;
+ public readonly EntityConstraint $partCustomState;
public readonly TextConstraint $manufacturer_product_url;
public readonly TextConstraint $manufacturer_product_number;
public readonly IntConstraint $attachmentsCount;
@@ -128,6 +130,7 @@ class PartFilter implements FilterInterface
$this->favorite = new BooleanConstraint('part.favorite');
$this->needsReview = new BooleanConstraint('part.needs_review');
$this->measurementUnit = new EntityConstraint($nodesListBuilder, MeasurementUnit::class, 'part.partUnit');
+ $this->partCustomState = new EntityConstraint($nodesListBuilder, PartCustomState::class, 'part.partCustomState');
$this->mass = new NumberConstraint('part.mass');
$this->dbId = new IntConstraint('part.id');
$this->ipn = new TextConstraint('part.ipn');
diff --git a/src/DataTables/PartsDataTable.php b/src/DataTables/PartsDataTable.php
index a97762b1..0baee630 100644
--- a/src/DataTables/PartsDataTable.php
+++ b/src/DataTables/PartsDataTable.php
@@ -174,6 +174,19 @@ final class PartsDataTable implements DataTableTypeInterface
return $tmp;
}
])
+ ->add('partCustomState', TextColumn::class, [
+ 'label' => $this->translator->trans('part.table.partCustomState'),
+ 'orderField' => 'NATSORT(_partCustomState.name)',
+ 'render' => function($value, Part $context): string {
+ $partCustomState = $context->getPartCustomState();
+
+ if ($partCustomState === null) {
+ return '';
+ }
+
+ return htmlspecialchars($partCustomState->getName());
+ }
+ ])
->add('addedDate', LocaleDateTimeColumn::class, [
'label' => $this->translator->trans('part.table.addedDate'),
])
@@ -309,6 +322,7 @@ final class PartsDataTable implements DataTableTypeInterface
->addSelect('footprint')
->addSelect('manufacturer')
->addSelect('partUnit')
+ ->addSelect('partCustomState')
->addSelect('master_picture_attachment')
->addSelect('footprint_attachment')
->addSelect('partLots')
@@ -327,6 +341,7 @@ final class PartsDataTable implements DataTableTypeInterface
->leftJoin('orderdetails.supplier', 'suppliers')
->leftJoin('part.attachments', 'attachments')
->leftJoin('part.partUnit', 'partUnit')
+ ->leftJoin('part.partCustomState', 'partCustomState')
->leftJoin('part.parameters', 'parameters')
->where('part.id IN (:ids)')
->setParameter('ids', $ids)
@@ -344,6 +359,7 @@ final class PartsDataTable implements DataTableTypeInterface
->addGroupBy('suppliers')
->addGroupBy('attachments')
->addGroupBy('partUnit')
+ ->addGroupBy('partCustomState')
->addGroupBy('parameters');
//Get the results in the same order as the IDs were passed
@@ -415,6 +431,10 @@ final class PartsDataTable implements DataTableTypeInterface
$builder->leftJoin('part.partUnit', '_partUnit');
$builder->addGroupBy('_partUnit');
}
+ if (str_contains($dql, '_partCustomState')) {
+ $builder->leftJoin('part.partCustomState', '_partCustomState');
+ $builder->addGroupBy('_partCustomState');
+ }
if (str_contains($dql, '_parameters')) {
$builder->leftJoin('part.parameters', '_parameters');
//Do not group by many-to-* relations, as it would restrict the COUNT having clauses to be maximum 1
diff --git a/src/Entity/Attachments/Attachment.php b/src/Entity/Attachments/Attachment.php
index 00cf581a..35a6a529 100644
--- a/src/Entity/Attachments/Attachment.php
+++ b/src/Entity/Attachments/Attachment.php
@@ -97,7 +97,7 @@ use function in_array;
#[DiscriminatorMap(typeProperty: '_type', mapping: self::API_DISCRIMINATOR_MAP)]
abstract class Attachment extends AbstractNamedDBElement
{
- private const ORM_DISCRIMINATOR_MAP = ['Part' => PartAttachment::class, 'Device' => ProjectAttachment::class,
+ private const ORM_DISCRIMINATOR_MAP = ['Part' => PartAttachment::class, 'PartCustomState' => PartCustomStateAttachment::class, 'Device' => ProjectAttachment::class,
'AttachmentType' => AttachmentTypeAttachment::class,
'Category' => CategoryAttachment::class, 'Footprint' => FootprintAttachment::class, 'Manufacturer' => ManufacturerAttachment::class,
'Currency' => CurrencyAttachment::class, 'Group' => GroupAttachment::class, 'MeasurementUnit' => MeasurementUnitAttachment::class,
@@ -107,7 +107,8 @@ abstract class Attachment extends AbstractNamedDBElement
/*
* The discriminator map used for API platform. The key should be the same as the api platform short type (the @type JSONLD field).
*/
- private const API_DISCRIMINATOR_MAP = ["Part" => PartAttachment::class, "Project" => ProjectAttachment::class, "AttachmentType" => AttachmentTypeAttachment::class,
+ private const API_DISCRIMINATOR_MAP = ["Part" => PartAttachment::class, "PartCustomState" => PartCustomStateAttachment::class, "Project" => ProjectAttachment::class,
+ "AttachmentType" => AttachmentTypeAttachment::class,
"Category" => CategoryAttachment::class, "Footprint" => FootprintAttachment::class, "Manufacturer" => ManufacturerAttachment::class,
"Currency" => CurrencyAttachment::class, "Group" => GroupAttachment::class, "MeasurementUnit" => MeasurementUnitAttachment::class,
"StorageLocation" => StorageLocationAttachment::class, "Supplier" => SupplierAttachment::class, "User" => UserAttachment::class, "LabelProfile" => LabelAttachment::class];
diff --git a/src/Entity/Attachments/PartCustomStateAttachment.php b/src/Entity/Attachments/PartCustomStateAttachment.php
new file mode 100644
index 00000000..3a561b13
--- /dev/null
+++ b/src/Entity/Attachments/PartCustomStateAttachment.php
@@ -0,0 +1,45 @@
+.
+ */
+
+declare(strict_types=1);
+
+namespace App\Entity\Attachments;
+
+use App\Entity\Parts\PartCustomState;
+use App\Serializer\APIPlatform\OverrideClassDenormalizer;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
+use Symfony\Component\Serializer\Attribute\Context;
+
+/**
+ * An attachment attached to a part custom state element.
+ * @extends Attachment
+ */
+#[UniqueEntity(['name', 'attachment_type', 'element'])]
+#[ORM\Entity]
+class PartCustomStateAttachment extends Attachment
+{
+ final public const ALLOWED_ELEMENT_CLASS = PartCustomState::class;
+
+ #[ORM\ManyToOne(targetEntity: PartCustomState::class, inversedBy: 'attachments')]
+ #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')]
+ #[Context(denormalizationContext: [OverrideClassDenormalizer::CONTEXT_KEY => self::ALLOWED_ELEMENT_CLASS])]
+ protected ?AttachmentContainingDBElement $element = null;
+}
diff --git a/src/Entity/Base/AbstractDBElement.php b/src/Entity/Base/AbstractDBElement.php
index 9fb5d648..a088b3df 100644
--- a/src/Entity/Base/AbstractDBElement.php
+++ b/src/Entity/Base/AbstractDBElement.php
@@ -33,6 +33,7 @@ use App\Entity\Attachments\LabelAttachment;
use App\Entity\Attachments\ManufacturerAttachment;
use App\Entity\Attachments\MeasurementUnitAttachment;
use App\Entity\Attachments\PartAttachment;
+use App\Entity\Attachments\PartCustomStateAttachment;
use App\Entity\Attachments\ProjectAttachment;
use App\Entity\Attachments\StorageLocationAttachment;
use App\Entity\Attachments\SupplierAttachment;
@@ -40,6 +41,7 @@ use App\Entity\Attachments\UserAttachment;
use App\Entity\Parameters\AbstractParameter;
use App\Entity\Parts\Category;
use App\Entity\PriceInformations\Pricedetail;
+use App\Entity\Parts\PartCustomState;
use App\Entity\ProjectSystem\Project;
use App\Entity\ProjectSystem\ProjectBOMEntry;
use App\Entity\Parts\Footprint;
@@ -68,7 +70,41 @@ use Symfony\Component\Serializer\Annotation\Groups;
* Every database table which are managed with this class (or a subclass of it)
* must have the table row "id"!! The ID is the unique key to identify the elements.
*/
-#[DiscriminatorMap(typeProperty: 'type', mapping: ['attachment_type' => AttachmentType::class, 'attachment' => Attachment::class, 'attachment_type_attachment' => AttachmentTypeAttachment::class, 'category_attachment' => CategoryAttachment::class, 'currency_attachment' => CurrencyAttachment::class, 'footprint_attachment' => FootprintAttachment::class, 'group_attachment' => GroupAttachment::class, 'label_attachment' => LabelAttachment::class, 'manufacturer_attachment' => ManufacturerAttachment::class, 'measurement_unit_attachment' => MeasurementUnitAttachment::class, 'part_attachment' => PartAttachment::class, 'project_attachment' => ProjectAttachment::class, 'storelocation_attachment' => StorageLocationAttachment::class, 'supplier_attachment' => SupplierAttachment::class, 'user_attachment' => UserAttachment::class, 'category' => Category::class, 'project' => Project::class, 'project_bom_entry' => ProjectBOMEntry::class, 'footprint' => Footprint::class, 'group' => Group::class, 'manufacturer' => Manufacturer::class, 'orderdetail' => Orderdetail::class, 'part' => Part::class, 'pricedetail' => Pricedetail::class, 'storelocation' => StorageLocation::class, 'part_lot' => PartLot::class, 'currency' => Currency::class, 'measurement_unit' => MeasurementUnit::class, 'parameter' => AbstractParameter::class, 'supplier' => Supplier::class, 'user' => User::class])]
+#[DiscriminatorMap(typeProperty: 'type', mapping: [
+ 'attachment_type' => AttachmentType::class,
+ 'attachment' => Attachment::class,
+ 'attachment_type_attachment' => AttachmentTypeAttachment::class,
+ 'category_attachment' => CategoryAttachment::class,
+ 'currency_attachment' => CurrencyAttachment::class,
+ 'footprint_attachment' => FootprintAttachment::class,
+ 'group_attachment' => GroupAttachment::class,
+ 'label_attachment' => LabelAttachment::class,
+ 'manufacturer_attachment' => ManufacturerAttachment::class,
+ 'measurement_unit_attachment' => MeasurementUnitAttachment::class,
+ 'part_attachment' => PartAttachment::class,
+ 'part_custom_state_attachment' => PartCustomStateAttachment::class,
+ 'project_attachment' => ProjectAttachment::class,
+ 'storelocation_attachment' => StorageLocationAttachment::class,
+ 'supplier_attachment' => SupplierAttachment::class,
+ 'user_attachment' => UserAttachment::class,
+ 'category' => Category::class,
+ 'project' => Project::class,
+ 'project_bom_entry' => ProjectBOMEntry::class,
+ 'footprint' => Footprint::class,
+ 'group' => Group::class,
+ 'manufacturer' => Manufacturer::class,
+ 'orderdetail' => Orderdetail::class,
+ 'part' => Part::class,
+ 'part_custom_state' => PartCustomState::class,
+ 'pricedetail' => Pricedetail::class,
+ 'storelocation' => StorageLocation::class,
+ 'part_lot' => PartLot::class,
+ 'currency' => Currency::class,
+ 'measurement_unit' => MeasurementUnit::class,
+ 'parameter' => AbstractParameter::class,
+ 'supplier' => Supplier::class,
+ 'user' => User::class]
+)]
#[ORM\MappedSuperclass(repositoryClass: DBElementRepository::class)]
abstract class AbstractDBElement implements JsonSerializable
{
diff --git a/src/Entity/LogSystem/CollectionElementDeleted.php b/src/Entity/LogSystem/CollectionElementDeleted.php
index 16bf33f5..34ab8fba 100644
--- a/src/Entity/LogSystem/CollectionElementDeleted.php
+++ b/src/Entity/LogSystem/CollectionElementDeleted.php
@@ -46,6 +46,7 @@ use App\Entity\Attachments\AttachmentType;
use App\Entity\Attachments\AttachmentTypeAttachment;
use App\Entity\Attachments\CategoryAttachment;
use App\Entity\Attachments\CurrencyAttachment;
+use App\Entity\Attachments\PartCustomStateAttachment;
use App\Entity\Attachments\ProjectAttachment;
use App\Entity\Attachments\FootprintAttachment;
use App\Entity\Attachments\GroupAttachment;
@@ -58,6 +59,8 @@ use App\Entity\Attachments\UserAttachment;
use App\Entity\Base\AbstractDBElement;
use App\Entity\Contracts\LogWithEventUndoInterface;
use App\Entity\Contracts\NamedElementInterface;
+use App\Entity\Parameters\PartCustomStateParameter;
+use App\Entity\Parts\PartCustomState;
use App\Entity\ProjectSystem\Project;
use App\Entity\Parameters\AbstractParameter;
use App\Entity\Parameters\AttachmentTypeParameter;
@@ -158,6 +161,7 @@ class CollectionElementDeleted extends AbstractLogEntry implements LogWithEventU
Part::class => PartParameter::class,
StorageLocation::class => StorageLocationParameter::class,
Supplier::class => SupplierParameter::class,
+ PartCustomState::class => PartCustomStateParameter::class,
default => throw new \RuntimeException('Unknown target class for parameter: '.$this->getTargetClass()),
};
}
@@ -173,6 +177,7 @@ class CollectionElementDeleted extends AbstractLogEntry implements LogWithEventU
Manufacturer::class => ManufacturerAttachment::class,
MeasurementUnit::class => MeasurementUnitAttachment::class,
Part::class => PartAttachment::class,
+ PartCustomState::class => PartCustomStateAttachment::class,
StorageLocation::class => StorageLocationAttachment::class,
Supplier::class => SupplierAttachment::class,
User::class => UserAttachment::class,
diff --git a/src/Entity/LogSystem/LogTargetType.php b/src/Entity/LogSystem/LogTargetType.php
index 61a2b081..3b2d8682 100644
--- a/src/Entity/LogSystem/LogTargetType.php
+++ b/src/Entity/LogSystem/LogTargetType.php
@@ -34,6 +34,7 @@ use App\Entity\Parts\Manufacturer;
use App\Entity\Parts\MeasurementUnit;
use App\Entity\Parts\Part;
use App\Entity\Parts\PartAssociation;
+use App\Entity\Parts\PartCustomState;
use App\Entity\Parts\PartLot;
use App\Entity\Parts\StorageLocation;
use App\Entity\Parts\Supplier;
@@ -71,6 +72,7 @@ enum LogTargetType: int
case PART_ASSOCIATION = 20;
case BULK_INFO_PROVIDER_IMPORT_JOB = 21;
case BULK_INFO_PROVIDER_IMPORT_JOB_PART = 22;
+ case PART_CUSTOM_STATE = 23;
/**
* Returns the class name of the target type or null if the target type is NONE.
@@ -102,6 +104,7 @@ enum LogTargetType: int
self::PART_ASSOCIATION => PartAssociation::class,
self::BULK_INFO_PROVIDER_IMPORT_JOB => BulkInfoProviderImportJob::class,
self::BULK_INFO_PROVIDER_IMPORT_JOB_PART => BulkInfoProviderImportJobPart::class,
+ self::PART_CUSTOM_STATE => PartCustomState::class
};
}
diff --git a/src/Entity/Parameters/AbstractParameter.php b/src/Entity/Parameters/AbstractParameter.php
index 39f333da..388745d4 100644
--- a/src/Entity/Parameters/AbstractParameter.php
+++ b/src/Entity/Parameters/AbstractParameter.php
@@ -73,7 +73,8 @@ use function sprintf;
#[ORM\DiscriminatorMap([0 => CategoryParameter::class, 1 => CurrencyParameter::class, 2 => ProjectParameter::class,
3 => FootprintParameter::class, 4 => GroupParameter::class, 5 => ManufacturerParameter::class,
6 => MeasurementUnitParameter::class, 7 => PartParameter::class, 8 => StorageLocationParameter::class,
- 9 => SupplierParameter::class, 10 => AttachmentTypeParameter::class])]
+ 9 => SupplierParameter::class, 10 => AttachmentTypeParameter::class,
+ 12 => PartCustomStateParameter::class])]
#[ORM\Table('parameters')]
#[ORM\Index(columns: ['name'], name: 'parameter_name_idx')]
#[ORM\Index(columns: ['param_group'], name: 'parameter_group_idx')]
@@ -105,7 +106,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement implements Uniqu
"AttachmentType" => AttachmentTypeParameter::class, "Category" => CategoryParameter::class, "Currency" => CurrencyParameter::class,
"Project" => ProjectParameter::class, "Footprint" => FootprintParameter::class, "Group" => GroupParameter::class,
"Manufacturer" => ManufacturerParameter::class, "MeasurementUnit" => MeasurementUnitParameter::class,
- "StorageLocation" => StorageLocationParameter::class, "Supplier" => SupplierParameter::class];
+ "StorageLocation" => StorageLocationParameter::class, "Supplier" => SupplierParameter::class, "PartCustomState" => PartCustomStateParameter::class];
/**
* @var string The class of the element that can be passed to this attachment. Must be overridden in subclasses.
diff --git a/src/Entity/Parameters/PartCustomStateParameter.php b/src/Entity/Parameters/PartCustomStateParameter.php
new file mode 100644
index 00000000..ceedf7b4
--- /dev/null
+++ b/src/Entity/Parameters/PartCustomStateParameter.php
@@ -0,0 +1,65 @@
+.
+ */
+
+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 .
+ */
+
+namespace App\Entity\Parameters;
+
+use App\Entity\Base\AbstractDBElement;
+use App\Entity\Parts\PartCustomState;
+use App\Repository\ParameterRepository;
+use App\Serializer\APIPlatform\OverrideClassDenormalizer;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
+use Symfony\Component\Serializer\Attribute\Context;
+
+#[UniqueEntity(fields: ['name', 'group', 'element'])]
+#[ORM\Entity(repositoryClass: ParameterRepository::class)]
+class PartCustomStateParameter extends AbstractParameter
+{
+ final public const ALLOWED_ELEMENT_CLASS = PartCustomState::class;
+
+ /**
+ * @var PartCustomState the element this para is associated with
+ */
+ #[ORM\ManyToOne(targetEntity: PartCustomState::class, inversedBy: 'parameters')]
+ #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')]
+ #[Context(denormalizationContext: [OverrideClassDenormalizer::CONTEXT_KEY => self::ALLOWED_ELEMENT_CLASS])]
+ protected ?AbstractDBElement $element = null;
+}
diff --git a/src/Entity/Parts/Category.php b/src/Entity/Parts/Category.php
index 99ed3c6d..7fca81bc 100644
--- a/src/Entity/Parts/Category.php
+++ b/src/Entity/Parts/Category.php
@@ -118,6 +118,13 @@ class Category extends AbstractPartsContainingDBElement
#[ORM\Column(type: Types::TEXT)]
protected string $partname_regex = '';
+ /**
+ * @var string The prefix for ipn generation for created parts in this category.
+ */
+ #[Groups(['full', 'import', 'category:read', 'category:write'])]
+ #[ORM\Column(type: Types::STRING, length: 255, nullable: false, options: ['default' => ''])]
+ protected string $part_ipn_prefix = '';
+
/**
* @var bool Set to true, if the footprints should be disabled for parts this category (not implemented yet).
*/
@@ -225,6 +232,16 @@ class Category extends AbstractPartsContainingDBElement
return $this;
}
+ public function getPartIpnPrefix(): string
+ {
+ return $this->part_ipn_prefix;
+ }
+
+ public function setPartIpnPrefix(string $part_ipn_prefix): void
+ {
+ $this->part_ipn_prefix = $part_ipn_prefix;
+ }
+
public function isDisableFootprints(): bool
{
return $this->disable_footprints;
diff --git a/src/Entity/Parts/Part.php b/src/Entity/Parts/Part.php
index 2f274a8a..d0a279e3 100644
--- a/src/Entity/Parts/Part.php
+++ b/src/Entity/Parts/Part.php
@@ -61,7 +61,6 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Mapping as ORM;
-use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
@@ -75,7 +74,6 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
* @extends AttachmentContainingDBElement
* @template-use ParametersTrait
*/
-#[UniqueEntity(fields: ['ipn'], message: 'part.ipn.must_be_unique')]
#[ORM\Entity(repositoryClass: PartRepository::class)]
#[ORM\EntityListeners([TreeCacheInvalidationListener::class])]
#[ORM\Table('`parts`')]
@@ -107,7 +105,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
denormalizationContext: ['groups' => ['part:write', 'api:basic:write', 'eda_info:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'],
)]
#[ApiFilter(PropertyFilter::class)]
-#[ApiFilter(EntityFilter::class, properties: ["category", "footprint", "manufacturer", "partUnit"])]
+#[ApiFilter(EntityFilter::class, properties: ["category", "footprint", "manufacturer", "partUnit", "partCustomState"])]
#[ApiFilter(PartStoragelocationFilter::class, properties: ["storage_location"])]
#[ApiFilter(LikeFilter::class, properties: ["name", "comment", "description", "ipn", "manufacturer_product_number"])]
#[ApiFilter(TagFilter::class, properties: ["tags"])]
diff --git a/src/Entity/Parts/PartCustomState.php b/src/Entity/Parts/PartCustomState.php
new file mode 100644
index 00000000..136ff984
--- /dev/null
+++ b/src/Entity/Parts/PartCustomState.php
@@ -0,0 +1,127 @@
+.
+ */
+
+declare(strict_types=1);
+
+namespace App\Entity\Parts;
+
+use ApiPlatform\Metadata\ApiProperty;
+use App\Entity\Attachments\Attachment;
+use App\Entity\Attachments\PartCustomStateAttachment;
+use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface;
+use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
+use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
+use ApiPlatform\Metadata\ApiFilter;
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Delete;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Metadata\Patch;
+use ApiPlatform\Metadata\Post;
+use ApiPlatform\Serializer\Filter\PropertyFilter;
+use App\ApiPlatform\Filter\LikeFilter;
+use App\Entity\Base\AbstractPartsContainingDBElement;
+use App\Entity\Base\AbstractStructuralDBElement;
+use App\Entity\Parameters\PartCustomStateParameter;
+use App\Repository\Parts\PartCustomStateRepository;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\Common\Collections\Criteria;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
+use Symfony\Component\Validator\Constraints as Assert;
+
+/**
+ * This entity represents a custom part state.
+ * If an organisation uses Part-DB and has its custom part states, this is useful.
+ *
+ * @extends AbstractPartsContainingDBElement
+ */
+#[ORM\Entity(repositoryClass: PartCustomStateRepository::class)]
+#[ORM\Table('`part_custom_states`')]
+#[ORM\Index(columns: ['name'], name: 'part_custom_state_name')]
+#[ApiResource(
+ operations: [
+ new Get(security: 'is_granted("read", object)'),
+ new GetCollection(security: 'is_granted("@part_custom_states.read")'),
+ new Post(securityPostDenormalize: 'is_granted("create", object)'),
+ new Patch(security: 'is_granted("edit", object)'),
+ new Delete(security: 'is_granted("delete", object)'),
+ ],
+ normalizationContext: ['groups' => ['part_custom_state:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
+ denormalizationContext: ['groups' => ['part_custom_state:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'],
+)]
+#[ApiFilter(PropertyFilter::class)]
+#[ApiFilter(LikeFilter::class, properties: ["name"])]
+#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)]
+#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])]
+class PartCustomState extends AbstractPartsContainingDBElement
+{
+ /**
+ * @var string The comment info for this element as markdown
+ */
+ #[Groups(['part_custom_state:read', 'part_custom_state:write', 'full', 'import'])]
+ protected string $comment = '';
+
+ #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class, cascade: ['persist'])]
+ #[ORM\OrderBy(['name' => Criteria::ASC])]
+ protected Collection $children;
+
+ #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')]
+ #[ORM\JoinColumn(name: 'parent_id')]
+ #[Groups(['part_custom_state:read', 'part_custom_state:write'])]
+ #[ApiProperty(readableLink: false, writableLink: false)]
+ protected ?AbstractStructuralDBElement $parent = null;
+
+ /**
+ * @var Collection
+ */
+ #[Assert\Valid]
+ #[ORM\OneToMany(targetEntity: PartCustomStateAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)]
+ #[ORM\OrderBy(['name' => Criteria::ASC])]
+ #[Groups(['part_custom_state:read', 'part_custom_state:write'])]
+ protected Collection $attachments;
+
+ #[ORM\ManyToOne(targetEntity: PartCustomStateAttachment::class)]
+ #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')]
+ #[Groups(['part_custom_state:read', 'part_custom_state:write'])]
+ protected ?Attachment $master_picture_attachment = null;
+
+ /** @var Collection
+ */
+ #[Assert\Valid]
+ #[ORM\OneToMany(mappedBy: 'element', targetEntity: PartCustomStateParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
+ #[ORM\OrderBy(['name' => 'ASC'])]
+ #[Groups(['part_custom_state:read', 'part_custom_state:write'])]
+ protected Collection $parameters;
+
+ #[Groups(['part_custom_state:read'])]
+ protected ?\DateTimeImmutable $addedDate = null;
+ #[Groups(['part_custom_state:read'])]
+ protected ?\DateTimeImmutable $lastModified = null;
+
+ public function __construct()
+ {
+ parent::__construct();
+ $this->children = new ArrayCollection();
+ $this->attachments = new ArrayCollection();
+ $this->parameters = new ArrayCollection();
+ }
+}
diff --git a/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php b/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php
index 230ba7b7..2cee7f1a 100644
--- a/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php
+++ b/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php
@@ -23,12 +23,14 @@ declare(strict_types=1);
namespace App\Entity\Parts\PartTraits;
use App\Entity\Parts\InfoProviderReference;
+use App\Entity\Parts\PartCustomState;
use Doctrine\DBAL\Types\Types;
use App\Entity\Parts\Part;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Constraints\Length;
+use App\Validator\Constraints\UniquePartIpnConstraint;
/**
* Advanced properties of a part, not related to a more specific group.
@@ -64,6 +66,7 @@ trait AdvancedPropertyTrait
#[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])]
#[ORM\Column(type: Types::STRING, length: 100, unique: true, nullable: true)]
#[Length(max: 100)]
+ #[UniquePartIpnConstraint]
protected ?string $ipn = null;
/**
@@ -73,6 +76,14 @@ trait AdvancedPropertyTrait
#[Groups(['full', 'part:read'])]
protected InfoProviderReference $providerReference;
+ /**
+ * @var ?PartCustomState the custom state for the part
+ */
+ #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])]
+ #[ORM\ManyToOne(targetEntity: PartCustomState::class)]
+ #[ORM\JoinColumn(name: 'id_part_custom_state')]
+ protected ?PartCustomState $partCustomState = null;
+
/**
* Checks if this part is marked, for that it needs further review.
*/
@@ -180,7 +191,24 @@ trait AdvancedPropertyTrait
return $this;
}
+ /**
+ * Gets the custom part state for the part
+ * Returns null if no specific part state is set.
+ */
+ public function getPartCustomState(): ?PartCustomState
+ {
+ return $this->partCustomState;
+ }
+ /**
+ * Sets the custom part state.
+ *
+ * @return $this
+ */
+ public function setPartCustomState(?PartCustomState $partCustomState): self
+ {
+ $this->partCustomState = $partCustomState;
-
+ return $this;
+ }
}
diff --git a/src/EventListener/RegisterSynonymsAsTranslationParametersListener.php b/src/EventListener/RegisterSynonymsAsTranslationParametersListener.php
new file mode 100644
index 00000000..b216aad4
--- /dev/null
+++ b/src/EventListener/RegisterSynonymsAsTranslationParametersListener.php
@@ -0,0 +1,93 @@
+.
+ */
+
+declare(strict_types=1);
+
+
+namespace App\EventListener;
+
+use App\Services\ElementTypeNameGenerator;
+use App\Services\ElementTypes;
+use Symfony\Component\DependencyInjection\Attribute\Autowire;
+use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
+use Symfony\Component\HttpKernel\Event\RequestEvent;
+use Symfony\Component\Translation\Translator;
+use Symfony\Contracts\Cache\CacheInterface;
+use Symfony\Contracts\Cache\ItemInterface;
+use Symfony\Contracts\Cache\TagAwareCacheInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+#[AsEventListener]
+readonly class RegisterSynonymsAsTranslationParametersListener
+{
+ private Translator $translator;
+
+ public function __construct(
+ #[Autowire(service: 'translator.default')] TranslatorInterface $translator,
+ private TagAwareCacheInterface $cache,
+ private ElementTypeNameGenerator $typeNameGenerator)
+ {
+ if (!$translator instanceof Translator) {
+ throw new \RuntimeException('Translator must be an instance of Symfony\Component\Translation\Translator or this listener cannot be used.');
+ }
+ $this->translator = $translator;
+ }
+
+ public function getSynonymPlaceholders(): array
+ {
+ return $this->cache->get('partdb_synonym_placeholders', function (ItemInterface $item) {
+ $item->tag('synonyms');
+
+
+ $placeholders = [];
+
+ //Generate a placeholder for each element type
+ foreach (ElementTypes::cases() as $elementType) {
+ //We have a placeholder for singular
+ $placeholders['{' . $elementType->value . '}'] = $this->typeNameGenerator->typeLabel($elementType);
+ //We have a placeholder for plural
+ $placeholders['{{' . $elementType->value . '}}'] = $this->typeNameGenerator->typeLabelPlural($elementType);
+
+ //And we have lowercase versions for both
+ $placeholders['[' . $elementType->value . ']'] = mb_strtolower($this->typeNameGenerator->typeLabel($elementType));
+ $placeholders['[[' . $elementType->value . ']]'] = mb_strtolower($this->typeNameGenerator->typeLabelPlural($elementType));
+ }
+
+ return $placeholders;
+ });
+ }
+
+ public function __invoke(RequestEvent $event): void
+ {
+ //If we already added the parameters, skip adding them again
+ if (isset($this->translator->getGlobalParameters()['@@partdb_synonyms_registered@@'])) {
+ return;
+ }
+
+ //Register all placeholders for synonyms
+ $placeholders = $this->getSynonymPlaceholders();
+ foreach ($placeholders as $key => $value) {
+ $this->translator->addGlobalParameter($key, $value);
+ }
+
+ //Register the marker parameter to avoid double registration
+ $this->translator->addGlobalParameter('@@partdb_synonyms_registered@@', 'registered');
+ }
+}
diff --git a/src/EventSubscriber/UserSystem/PartUniqueIpnSubscriber.php b/src/EventSubscriber/UserSystem/PartUniqueIpnSubscriber.php
new file mode 100644
index 00000000..ecc25b4f
--- /dev/null
+++ b/src/EventSubscriber/UserSystem/PartUniqueIpnSubscriber.php
@@ -0,0 +1,97 @@
+ipnSuggestSettings->autoAppendSuffix) {
+ return;
+ }
+
+ $em = $args->getObjectManager();
+ $uow = $em->getUnitOfWork();
+ $meta = $em->getClassMetadata(Part::class);
+
+ // Collect all IPNs already reserved in the current flush (so new entities do not collide with each other)
+ $reservedIpns = [];
+
+ // Helper to assign a collision-free IPN for a Part entity
+ $ensureUnique = function (Part $part) use ($em, $uow, $meta, &$reservedIpns) {
+ $ipn = $part->getIpn();
+ if ($ipn === null || $ipn === '') {
+ return;
+ }
+
+ // Check against IPNs already reserved in the current flush (except itself)
+ $originalIpn = $ipn;
+ $candidate = $originalIpn;
+ $increment = 1;
+
+ $conflicts = function (string $candidate) use ($em, $part, $reservedIpns) {
+ // Collision within the current flush session?
+ if (isset($reservedIpns[$candidate]) && $reservedIpns[$candidate] !== $part) {
+ return true;
+ }
+ // Collision with an existing DB row?
+ $existing = $em->getRepository(Part::class)->findOneBy(['ipn' => $candidate]);
+ return $existing !== null && $existing->getId() !== $part->getId();
+ };
+
+ while ($conflicts($candidate)) {
+ $candidate = $originalIpn . '_' . $increment;
+ $increment++;
+ }
+
+ if ($candidate !== $ipn) {
+ $before = $part->getIpn();
+ $part->setIpn($candidate);
+
+ // Recompute the change set so Doctrine writes the change
+ $uow->recomputeSingleEntityChangeSet($meta, $part);
+ $reservedIpns[$candidate] = $part;
+
+ // If the old IPN was reserved already, clean it up
+ if ($before !== null && isset($reservedIpns[$before]) && $reservedIpns[$before] === $part) {
+ unset($reservedIpns[$before]);
+ }
+ } else {
+ // Candidate unchanged, but reserve it so subsequent entities see it
+ $reservedIpns[$candidate] = $part;
+ }
+ };
+
+ // 1) Iterate over new entities
+ foreach ($uow->getScheduledEntityInsertions() as $entity) {
+ if ($entity instanceof Part) {
+ $ensureUnique($entity);
+ }
+ }
+
+ // 2) Iterate over updates (if IPN changed, ensure uniqueness again)
+ foreach ($uow->getScheduledEntityUpdates() as $entity) {
+ if ($entity instanceof Part) {
+ $ensureUnique($entity);
+ }
+ }
+ }
+}
diff --git a/src/Form/AdminPages/CategoryAdminForm.php b/src/Form/AdminPages/CategoryAdminForm.php
index 44c1dede..489649ed 100644
--- a/src/Form/AdminPages/CategoryAdminForm.php
+++ b/src/Form/AdminPages/CategoryAdminForm.php
@@ -84,6 +84,17 @@ class CategoryAdminForm extends BaseEntityAdminForm
'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity),
]);
+ $builder->add('part_ipn_prefix', TextType::class, [
+ 'required' => false,
+ 'empty_data' => '',
+ 'label' => 'category.edit.part_ipn_prefix',
+ 'help' => 'category.edit.part_ipn_prefix.help',
+ 'attr' => [
+ 'placeholder' => 'category.edit.part_ipn_prefix.placeholder',
+ ],
+ 'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity),
+ ]);
+
$builder->add('default_description', RichTextEditorType::class, [
'required' => false,
'empty_data' => '',
diff --git a/src/Form/AdminPages/PartCustomStateAdminForm.php b/src/Form/AdminPages/PartCustomStateAdminForm.php
new file mode 100644
index 00000000..b8bb2815
--- /dev/null
+++ b/src/Form/AdminPages/PartCustomStateAdminForm.php
@@ -0,0 +1,27 @@
+.
+ */
+
+declare(strict_types=1);
+
+namespace App\Form\AdminPages;
+
+class PartCustomStateAdminForm extends BaseEntityAdminForm
+{
+}
diff --git a/src/Form/Filters/LogFilterType.php b/src/Form/Filters/LogFilterType.php
index c973ad0f..30abf723 100644
--- a/src/Form/Filters/LogFilterType.php
+++ b/src/Form/Filters/LogFilterType.php
@@ -130,6 +130,7 @@ class LogFilterType extends AbstractType
LogTargetType::PART_ASSOCIATION => 'part_association.label',
LogTargetType::BULK_INFO_PROVIDER_IMPORT_JOB => 'bulk_info_provider_import_job.label',
LogTargetType::BULK_INFO_PROVIDER_IMPORT_JOB_PART => 'bulk_info_provider_import_job_part.label',
+ LogTargetType::PART_CUSTOM_STATE => 'part_custom_state.label',
},
]);
diff --git a/src/Form/Filters/PartFilterType.php b/src/Form/Filters/PartFilterType.php
index 871f9b07..e101c635 100644
--- a/src/Form/Filters/PartFilterType.php
+++ b/src/Form/Filters/PartFilterType.php
@@ -32,6 +32,7 @@ use App\Entity\Parts\Category;
use App\Entity\Parts\Footprint;
use App\Entity\Parts\Manufacturer;
use App\Entity\Parts\MeasurementUnit;
+use App\Entity\Parts\PartCustomState;
use App\Entity\Parts\StorageLocation;
use App\Entity\Parts\Supplier;
use App\Entity\ProjectSystem\Project;
@@ -139,6 +140,11 @@ class PartFilterType extends AbstractType
'entity_class' => MeasurementUnit::class
]);
+ $builder->add('partCustomState', StructuralEntityConstraintType::class, [
+ 'label' => 'part.edit.partCustomState',
+ 'entity_class' => PartCustomState::class
+ ]);
+
$builder->add('lastModified', DateTimeConstraintType::class, [
'label' => 'lastModified'
]);
diff --git a/src/Form/Part/PartBaseType.php b/src/Form/Part/PartBaseType.php
index 0bd3d0e3..b8276589 100644
--- a/src/Form/Part/PartBaseType.php
+++ b/src/Form/Part/PartBaseType.php
@@ -30,6 +30,7 @@ use App\Entity\Parts\Manufacturer;
use App\Entity\Parts\ManufacturingStatus;
use App\Entity\Parts\MeasurementUnit;
use App\Entity\Parts\Part;
+use App\Entity\Parts\PartCustomState;
use App\Entity\PriceInformations\Orderdetail;
use App\Form\AttachmentFormType;
use App\Form\ParameterType;
@@ -41,6 +42,7 @@ use App\Form\Type\StructuralEntityType;
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
use App\Services\LogSystem\EventCommentNeededHelper;
use App\Services\LogSystem\EventCommentType;
+use App\Settings\MiscSettings\IpnSuggestSettings;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
@@ -56,8 +58,12 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class PartBaseType extends AbstractType
{
- public function __construct(protected Security $security, protected UrlGeneratorInterface $urlGenerator, protected EventCommentNeededHelper $event_comment_needed_helper)
- {
+ public function __construct(
+ protected Security $security,
+ protected UrlGeneratorInterface $urlGenerator,
+ protected EventCommentNeededHelper $event_comment_needed_helper,
+ protected IpnSuggestSettings $ipnSuggestSettings,
+ ) {
}
public function buildForm(FormBuilderInterface $builder, array $options): void
@@ -69,6 +75,39 @@ class PartBaseType extends AbstractType
/** @var PartDetailDTO|null $dto */
$dto = $options['info_provider_dto'];
+ $descriptionAttr = [
+ 'placeholder' => 'part.edit.description.placeholder',
+ 'rows' => 2,
+ ];
+
+ if ($this->ipnSuggestSettings->useDuplicateDescription) {
+ // Only add attribute when duplicate description feature is enabled
+ $descriptionAttr['data-ipn-suggestion'] = 'descriptionField';
+ }
+
+ $ipnAttr = [
+ 'class' => 'ipn-suggestion-field',
+ 'data-elements--ipn-suggestion-target' => 'input',
+ 'autocomplete' => 'off',
+ ];
+
+ if ($this->ipnSuggestSettings->regex !== null && $this->ipnSuggestSettings->regex !== '') {
+ $ipnAttr['pattern'] = $this->ipnSuggestSettings->regex;
+ $ipnAttr['placeholder'] = $this->ipnSuggestSettings->regex;
+ $ipnAttr['title'] = $this->ipnSuggestSettings->regexHelp;
+ }
+
+ $ipnOptions = [
+ 'required' => false,
+ 'empty_data' => null,
+ 'label' => 'part.edit.ipn',
+ 'attr' => $ipnAttr,
+ ];
+
+ if (isset($ipnAttr['pattern']) && $this->ipnSuggestSettings->regexHelp !== null && $this->ipnSuggestSettings->regexHelp !== '') {
+ $ipnOptions['help'] = $this->ipnSuggestSettings->regexHelp;
+ }
+
//Common section
$builder
->add('name', TextType::class, [
@@ -83,10 +122,7 @@ class PartBaseType extends AbstractType
'empty_data' => '',
'label' => 'part.edit.description',
'mode' => 'markdown-single_line',
- 'attr' => [
- 'placeholder' => 'part.edit.description.placeholder',
- 'rows' => 2,
- ],
+ 'attr' => $descriptionAttr,
])
->add('minAmount', SIUnitType::class, [
'attr' => [
@@ -104,6 +140,9 @@ class PartBaseType extends AbstractType
'disable_not_selectable' => true,
//Do not require category for new parts, so that the user must select the category by hand and cannot forget it (the requirement is handled by the constraint in the entity)
'required' => !$new_part,
+ 'attr' => [
+ 'data-ipn-suggestion' => 'categoryField',
+ ]
])
->add('footprint', StructuralEntityType::class, [
'class' => Footprint::class,
@@ -171,11 +210,13 @@ class PartBaseType extends AbstractType
'disable_not_selectable' => true,
'label' => 'part.edit.partUnit',
])
- ->add('ipn', TextType::class, [
+ ->add('partCustomState', StructuralEntityType::class, [
+ 'class' => PartCustomState::class,
'required' => false,
- 'empty_data' => null,
- 'label' => 'part.edit.ipn',
- ]);
+ 'disable_not_selectable' => true,
+ 'label' => 'part.edit.partCustomState',
+ ])
+ ->add('ipn', TextType::class, $ipnOptions);
//Comment section
$builder->add('comment', RichTextEditorType::class, [
diff --git a/src/Form/Type/LanguageMenuEntriesType.php b/src/Form/Settings/LanguageMenuEntriesType.php
similarity index 95%
rename from src/Form/Type/LanguageMenuEntriesType.php
rename to src/Form/Settings/LanguageMenuEntriesType.php
index a3bba77f..9bc2e850 100644
--- a/src/Form/Type/LanguageMenuEntriesType.php
+++ b/src/Form/Settings/LanguageMenuEntriesType.php
@@ -21,12 +21,11 @@
declare(strict_types=1);
-namespace App\Form\Type;
+namespace App\Form\Settings;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\LanguageType;
-use Symfony\Component\Form\Extension\Core\Type\LocaleType;
use Symfony\Component\Intl\Languages;
use Symfony\Component\OptionsResolver\OptionsResolver;
diff --git a/src/Form/Settings/TypeSynonymRowType.php b/src/Form/Settings/TypeSynonymRowType.php
new file mode 100644
index 00000000..332db907
--- /dev/null
+++ b/src/Form/Settings/TypeSynonymRowType.php
@@ -0,0 +1,142 @@
+.
+ */
+
+declare(strict_types=1);
+
+namespace App\Form\Settings;
+
+use App\Services\ElementTypes;
+use App\Settings\SystemSettings\LocalizationSettings;
+use Symfony\Component\DependencyInjection\Attribute\Autowire;
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\Extension\Core\Type\EnumType;
+use Symfony\Component\Form\Extension\Core\Type\LocaleType;
+use Symfony\Component\Form\Extension\Core\Type\TextType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\Intl\Locales;
+use Symfony\Component\Validator\Constraints as Assert;
+
+/**
+ * A single translation row: data source + language + translations (singular/plural).
+ */
+class TypeSynonymRowType extends AbstractType
+{
+
+ private const PREFERRED_TYPES = [
+ ElementTypes::CATEGORY,
+ ElementTypes::STORAGE_LOCATION,
+ ElementTypes::FOOTPRINT,
+ ElementTypes::MANUFACTURER,
+ ElementTypes::SUPPLIER,
+ ElementTypes::PROJECT,
+ ];
+
+ public function __construct(
+ private readonly LocalizationSettings $localizationSettings,
+ #[Autowire(param: 'partdb.locale_menu')] private readonly array $preferredLanguagesParam,
+ ) {
+ }
+
+ public function buildForm(FormBuilderInterface $builder, array $options): void
+ {
+ $builder
+ ->add('dataSource', EnumType::class, [
+ 'class' => ElementTypes::class,
+ 'label' => false,
+ 'required' => true,
+ 'constraints' => [
+ new Assert\NotBlank(),
+ ],
+ 'row_attr' => ['class' => 'mb-0'],
+ 'attr' => ['class' => 'form-select-sm'],
+ 'preferred_choices' => self::PREFERRED_TYPES
+ ])
+ ->add('locale', LocaleType::class, [
+ 'label' => false,
+ 'required' => true,
+ // Restrict to languages configured in the language menu: disable ChoiceLoader and provide explicit choices
+ 'choice_loader' => null,
+ 'choices' => $this->buildLocaleChoices(true),
+ 'preferred_choices' => $this->getPreferredLocales(),
+ 'constraints' => [
+ new Assert\NotBlank(),
+ ],
+ 'row_attr' => ['class' => 'mb-0'],
+ 'attr' => ['class' => 'form-select-sm']
+ ])
+ ->add('translation_singular', TextType::class, [
+ 'label' => false,
+ 'required' => true,
+ 'empty_data' => '',
+ 'constraints' => [
+ new Assert\NotBlank(),
+ ],
+ 'row_attr' => ['class' => 'mb-0'],
+ 'attr' => ['class' => 'form-select-sm']
+ ])
+ ->add('translation_plural', TextType::class, [
+ 'label' => false,
+ 'required' => true,
+ 'empty_data' => '',
+ 'constraints' => [
+ new Assert\NotBlank(),
+ ],
+ 'row_attr' => ['class' => 'mb-0'],
+ 'attr' => ['class' => 'form-select-sm']
+ ]);
+ }
+
+
+ /**
+ * Returns only locales configured in the language menu (settings) or falls back to the parameter.
+ * Format: ['German (DE)' => 'de', ...]
+ */
+ private function buildLocaleChoices(bool $returnPossible = false): array
+ {
+ $locales = $this->getPreferredLocales();
+
+ if ($returnPossible) {
+ $locales = $this->getPossibleLocales();
+ }
+
+ $choices = [];
+ foreach ($locales as $code) {
+ $label = Locales::getName($code);
+ $choices[$label . ' (' . strtoupper($code) . ')'] = $code;
+ }
+ return $choices;
+ }
+
+ /**
+ * Source of allowed locales:
+ * 1) LocalizationSettings->languageMenuEntries (if set)
+ * 2) Fallback: parameter partdb.locale_menu
+ */
+ private function getPreferredLocales(): array
+ {
+ $fromSettings = $this->localizationSettings->languageMenuEntries ?? [];
+ return !empty($fromSettings) ? array_values($fromSettings) : array_values($this->preferredLanguagesParam);
+ }
+
+ private function getPossibleLocales(): array
+ {
+ return array_values($this->preferredLanguagesParam);
+ }
+}
diff --git a/src/Form/Settings/TypeSynonymsCollectionType.php b/src/Form/Settings/TypeSynonymsCollectionType.php
new file mode 100644
index 00000000..4756930a
--- /dev/null
+++ b/src/Form/Settings/TypeSynonymsCollectionType.php
@@ -0,0 +1,223 @@
+.
+ */
+
+declare(strict_types=1);
+
+namespace App\Form\Settings;
+
+use App\Services\ElementTypes;
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\CallbackTransformer;
+use Symfony\Component\Form\Extension\Core\Type\CollectionType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\Form\FormError;
+use Symfony\Component\Form\FormEvent;
+use Symfony\Component\Form\FormEvents;
+use Symfony\Component\Intl\Locales;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+/**
+ * Flat collection of translation rows.
+ * View data: list [{dataSource, locale, translation_singular, translation_plural}, ...]
+ * Model data: same structure (list). Optionally expands a nested map to a list.
+ */
+class TypeSynonymsCollectionType extends AbstractType
+{
+ public function __construct(private readonly TranslatorInterface $translator)
+ {
+ }
+
+ private function flattenStructure(array $modelValue): array
+ {
+ //If the model is already flattened, return as is
+ if (array_is_list($modelValue)) {
+ return $modelValue;
+ }
+
+ $out = [];
+ foreach ($modelValue as $dataSource => $locales) {
+ if (!is_array($locales)) {
+ continue;
+ }
+ foreach ($locales as $locale => $translations) {
+ if (!is_array($translations)) {
+ continue;
+ }
+ $out[] = [
+ //Convert string to enum value
+ 'dataSource' => ElementTypes::from($dataSource),
+ 'locale' => $locale,
+ 'translation_singular' => $translations['singular'] ?? '',
+ 'translation_plural' => $translations['plural'] ?? '',
+ ];
+ }
+ }
+ return $out;
+ }
+
+ public function buildForm(FormBuilderInterface $builder, array $options): void
+ {
+ $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event): void {
+ //Flatten the structure
+ $data = $event->getData();
+ $event->setData($this->flattenStructure($data));
+ });
+
+ $builder->addModelTransformer(new CallbackTransformer(
+ // Model -> View
+ $this->flattenStructure(...),
+ // View -> Model (keep list; let existing behavior unchanged)
+ function (array $viewValue) {
+ //Turn our flat list back into the structured array
+
+ $out = [];
+
+ foreach ($viewValue as $row) {
+ if (!is_array($row)) {
+ continue;
+ }
+ $dataSource = $row['dataSource'] ?? null;
+ $locale = $row['locale'] ?? null;
+ $translation_singular = $row['translation_singular'] ?? null;
+ $translation_plural = $row['translation_plural'] ?? null;
+
+ if ($dataSource === null ||
+ !is_string($locale) || $locale === ''
+ ) {
+ continue;
+ }
+
+ $out[$dataSource->value][$locale] = [
+ 'singular' => is_string($translation_singular) ? $translation_singular : '',
+ 'plural' => is_string($translation_plural) ? $translation_plural : '',
+ ];
+ }
+
+ return $out;
+ }
+ ));
+
+ // Validation and normalization (duplicates + sorting) during SUBMIT
+ $builder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event): void {
+ $form = $event->getForm();
+ $rows = $event->getData();
+
+ if (!is_array($rows)) {
+ return;
+ }
+
+ // Duplicate check: (dataSource, locale) must be unique
+ $seen = [];
+ $hasDuplicate = false;
+
+ foreach ($rows as $idx => $row) {
+ if (!is_array($row)) {
+ continue;
+ }
+ $ds = $row['dataSource'] ?? null;
+ $loc = $row['locale'] ?? null;
+
+ if ($ds !== null && is_string($loc) && $loc !== '') {
+ $key = $ds->value . '|' . $loc;
+ if (isset($seen[$key])) {
+ $hasDuplicate = true;
+
+ if ($form->has((string)$idx)) {
+ $child = $form->get((string)$idx);
+
+ if ($child->has('dataSource')) {
+ $child->get('dataSource')->addError(
+ new FormError($this->translator->trans(
+ 'settings.synonyms.type_synonyms.collection_type.duplicate',
+ [], 'validators'
+ ))
+ );
+ }
+ if ($child->has('locale')) {
+ $child->get('locale')->addError(
+ new FormError($this->translator->trans(
+ 'settings.synonyms.type_synonyms.collection_type.duplicate',
+ [], 'validators'
+ ))
+ );
+ }
+ }
+ } else {
+ $seen[$key] = true;
+ }
+ }
+ }
+
+ if ($hasDuplicate) {
+ return;
+ }
+
+ // Overall sort: first by dataSource key, then by localized language name
+ $sortable = $rows;
+
+ usort($sortable, static function ($a, $b) {
+ $aDs = $a['dataSource']->value ?? '';
+ $bDs = $b['dataSource']->value ?? '';
+
+ $cmpDs = strcasecmp($aDs, $bDs);
+ if ($cmpDs !== 0) {
+ return $cmpDs;
+ }
+
+ $aLoc = (string)($a['locale'] ?? '');
+ $bLoc = (string)($b['locale'] ?? '');
+
+ $aName = Locales::getName($aLoc);
+ $bName = Locales::getName($bLoc);
+
+ return strcasecmp($aName, $bName);
+ });
+
+ $event->setData($sortable);
+ });
+ }
+
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+
+ // Defaults for the collection and entry type
+ $resolver->setDefaults([
+ 'entry_type' => TypeSynonymRowType::class,
+ 'allow_add' => true,
+ 'allow_delete' => true,
+ 'by_reference' => false,
+ 'required' => false,
+ 'prototype' => true,
+ 'empty_data' => [],
+ 'entry_options' => ['label' => false],
+ ]);
+ }
+
+ public function getParent(): ?string
+ {
+ return CollectionType::class;
+ }
+
+ public function getBlockPrefix(): string
+ {
+ return 'type_synonyms_collection';
+ }
+}
diff --git a/src/Form/Type/LocaleSelectType.php b/src/Form/Type/LocaleSelectType.php
index d47fb57f..6dc6f9fc 100644
--- a/src/Form/Type/LocaleSelectType.php
+++ b/src/Form/Type/LocaleSelectType.php
@@ -30,7 +30,6 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* A locale select field that uses the preferred languages from the configuration.
-
*/
class LocaleSelectType extends AbstractType
{
diff --git a/src/Repository/PartRepository.php b/src/Repository/PartRepository.php
index edccd74b..3c83001a 100644
--- a/src/Repository/PartRepository.php
+++ b/src/Repository/PartRepository.php
@@ -22,17 +22,35 @@ declare(strict_types=1);
namespace App\Repository;
+use App\Entity\Parts\Category;
use App\Entity\Parts\Part;
use App\Entity\Parts\PartLot;
+use App\Settings\MiscSettings\IpnSuggestSettings;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\NoResultException;
use Doctrine\ORM\QueryBuilder;
+use Symfony\Contracts\Translation\TranslatorInterface;
+use Doctrine\ORM\EntityManagerInterface;
/**
* @extends NamedDBElementRepository
*/
class PartRepository extends NamedDBElementRepository
{
+ private TranslatorInterface $translator;
+ private IpnSuggestSettings $ipnSuggestSettings;
+
+ public function __construct(
+ EntityManagerInterface $em,
+ TranslatorInterface $translator,
+ IpnSuggestSettings $ipnSuggestSettings,
+ ) {
+ parent::__construct($em, $em->getClassMetadata(Part::class));
+
+ $this->translator = $translator;
+ $this->ipnSuggestSettings = $ipnSuggestSettings;
+ }
+
/**
* Gets the summed up instock of all parts (only parts without a measurement unit).
*
@@ -84,8 +102,7 @@ class PartRepository extends NamedDBElementRepository
->where('ILIKE(part.name, :query) = TRUE')
->orWhere('ILIKE(part.description, :query) = TRUE')
->orWhere('ILIKE(category.name, :query) = TRUE')
- ->orWhere('ILIKE(footprint.name, :query) = TRUE')
- ;
+ ->orWhere('ILIKE(footprint.name, :query) = TRUE');
$qb->setParameter('query', '%'.$query.'%');
@@ -94,4 +111,240 @@ class PartRepository extends NamedDBElementRepository
return $qb->getQuery()->getResult();
}
+
+ /**
+ * Provides IPN (Internal Part Number) suggestions for a given part based on its category, description,
+ * and configured autocomplete digit length.
+ *
+ * This function generates suggestions for common prefixes and incremented prefixes based on
+ * the part's current category and its hierarchy. If the part is unsaved, a default "n.a." prefix is returned.
+ *
+ * @param Part $part The part for which autocomplete suggestions are generated.
+ * @param string $description description to assist in generating suggestions.
+ * @param int $suggestPartDigits The number of digits used in autocomplete increments.
+ *
+ * @return array An associative array containing the following keys:
+ * - 'commonPrefixes': List of common prefixes found for the part.
+ * - 'prefixesPartIncrement': Increments for the generated prefixes, including hierarchical prefixes.
+ */
+ public function autoCompleteIpn(Part $part, string $description, int $suggestPartDigits): array
+ {
+ $category = $part->getCategory();
+ $ipnSuggestions = ['commonPrefixes' => [], 'prefixesPartIncrement' => []];
+
+ if (strlen($description) > 150) {
+ $description = substr($description, 0, 150);
+ }
+
+ if ($description !== '' && $this->ipnSuggestSettings->useDuplicateDescription) {
+ // Check if the description is already used in another part,
+
+ $suggestionByDescription = $this->getIpnSuggestByDescription($description);
+
+ if ($suggestionByDescription !== null && $suggestionByDescription !== $part->getIpn() && $part->getIpn() !== null && $part->getIpn() !== '') {
+ $ipnSuggestions['prefixesPartIncrement'][] = [
+ 'title' => $part->getIpn(),
+ 'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.description.current-increment')
+ ];
+ }
+
+ if ($suggestionByDescription !== null) {
+ $ipnSuggestions['prefixesPartIncrement'][] = [
+ 'title' => $suggestionByDescription,
+ 'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.description.increment')
+ ];
+ }
+ }
+
+ // Validate the category and ensure it's an instance of Category
+ if ($category instanceof Category) {
+ $currentPath = $category->getPartIpnPrefix();
+ $directIpnPrefixEmpty = $category->getPartIpnPrefix() === '';
+ $currentPath = $currentPath === '' ? 'n.a.' : $currentPath;
+
+ $increment = $this->generateNextPossiblePartIncrement($currentPath, $part, $suggestPartDigits);
+
+ $ipnSuggestions['commonPrefixes'][] = [
+ 'title' => $currentPath . '-',
+ 'description' => $directIpnPrefixEmpty ? $this->translator->trans('part.edit.tab.advanced.ipn.prefix_empty.direct_category', ['%name%' => $category->getName()]) : $this->translator->trans('part.edit.tab.advanced.ipn.prefix.direct_category')
+ ];
+
+ $ipnSuggestions['prefixesPartIncrement'][] = [
+ 'title' => $currentPath . '-' . $increment,
+ 'description' => $directIpnPrefixEmpty ? $this->translator->trans('part.edit.tab.advanced.ipn.prefix_empty.direct_category', ['%name%' => $category->getName()]) : $this->translator->trans('part.edit.tab.advanced.ipn.prefix.direct_category.increment')
+ ];
+
+ // Process parent categories
+ $parentCategory = $category->getParent();
+
+ while ($parentCategory instanceof Category) {
+ // Prepend the parent category's prefix to the current path
+ $currentPath = $parentCategory->getPartIpnPrefix() . '-' . $currentPath;
+ $currentPath = $parentCategory->getPartIpnPrefix() === '' ? 'n.a.-' . $currentPath : $currentPath;
+
+ $ipnSuggestions['commonPrefixes'][] = [
+ 'title' => $currentPath . '-',
+ 'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.hierarchical.no_increment')
+ ];
+
+ $increment = $this->generateNextPossiblePartIncrement($currentPath, $part, $suggestPartDigits);
+
+ $ipnSuggestions['prefixesPartIncrement'][] = [
+ 'title' => $currentPath . '-' . $increment,
+ 'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.hierarchical.increment')
+ ];
+
+ // Move to the next parent category
+ $parentCategory = $parentCategory->getParent();
+ }
+ } elseif ($part->getID() === null) {
+ $ipnSuggestions['commonPrefixes'][] = [
+ 'title' => 'n.a.',
+ 'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.not_saved')
+ ];
+ }
+
+ return $ipnSuggestions;
+ }
+
+ /**
+ * Suggests the next IPN (Internal Part Number) based on the provided part description.
+ *
+ * Searches for parts with similar descriptions and retrieves their existing IPNs to calculate the next suggestion.
+ * Returns null if the description is empty or no suggestion can be generated.
+ *
+ * @param string $description The part description to search for.
+ *
+ * @return string|null The suggested IPN, or null if no suggestion is possible.
+ *
+ * @throws NonUniqueResultException
+ */
+ public function getIpnSuggestByDescription(string $description): ?string
+ {
+ if ($description === '') {
+ return null;
+ }
+
+ $qb = $this->createQueryBuilder('part');
+
+ $qb->select('part')
+ ->where('part.description LIKE :descriptionPattern')
+ ->setParameter('descriptionPattern', $description.'%')
+ ->orderBy('part.id', 'ASC');
+
+ $partsBySameDescription = $qb->getQuery()->getResult();
+ $givenIpnsWithSameDescription = [];
+
+ foreach ($partsBySameDescription as $part) {
+ if ($part->getIpn() === null || $part->getIpn() === '') {
+ continue;
+ }
+
+ $givenIpnsWithSameDescription[] = $part->getIpn();
+ }
+
+ return $this->getNextIpnSuggestion($givenIpnsWithSameDescription);
+ }
+
+ /**
+ * Generates the next possible increment for a part within a given category, while ensuring uniqueness.
+ *
+ * This method calculates the next available increment for a part's identifier (`ipn`) based on the current path
+ * and the number of digits specified for the autocomplete feature. It ensures that the generated identifier
+ * aligns with the expected length and does not conflict with already existing identifiers in the same category.
+ *
+ * @param string $currentPath The base path or prefix for the part's identifier.
+ * @param Part $currentPart The part entity for which the increment is being generated.
+ * @param int $suggestPartDigits The number of digits reserved for the increment.
+ *
+ * @return string The next possible increment as a zero-padded string.
+ *
+ * @throws NonUniqueResultException If the query returns non-unique results.
+ * @throws NoResultException If the query fails to return a result.
+ */
+ private function generateNextPossiblePartIncrement(string $currentPath, Part $currentPart, int $suggestPartDigits): string
+ {
+ $qb = $this->createQueryBuilder('part');
+
+ $expectedLength = strlen($currentPath) + 1 + $suggestPartDigits; // Path + '-' + $suggestPartDigits digits
+
+ // Fetch all parts in the given category, sorted by their ID in ascending order
+ $qb->select('part')
+ ->where('part.ipn LIKE :ipnPattern')
+ ->andWhere('LENGTH(part.ipn) = :expectedLength')
+ ->setParameter('ipnPattern', $currentPath . '%')
+ ->setParameter('expectedLength', $expectedLength)
+ ->orderBy('part.id', 'ASC');
+
+ $parts = $qb->getQuery()->getResult();
+
+ // Collect all used increments in the category
+ $usedIncrements = [];
+ foreach ($parts as $part) {
+ if ($part->getIpn() === null || $part->getIpn() === '') {
+ continue;
+ }
+
+ if ($part->getId() === $currentPart->getId() && $currentPart->getID() !== null) {
+ // Extract and return the current part's increment directly
+ $incrementPart = substr($part->getIpn(), -$suggestPartDigits);
+ if (is_numeric($incrementPart)) {
+ return str_pad((string) $incrementPart, $suggestPartDigits, '0', STR_PAD_LEFT);
+ }
+ }
+
+ // Extract last $autocompletePartDigits digits for possible available part increment
+ $incrementPart = substr($part->getIpn(), -$suggestPartDigits);
+ if (is_numeric($incrementPart)) {
+ $usedIncrements[] = (int) $incrementPart;
+ }
+
+ }
+
+ // Generate the next free $autocompletePartDigits-digit increment
+ $nextIncrement = 1; // Start at the beginning
+
+ while (in_array($nextIncrement, $usedIncrements, true)) {
+ $nextIncrement++;
+ }
+
+ return str_pad((string) $nextIncrement, $suggestPartDigits, '0', STR_PAD_LEFT);
+ }
+
+ /**
+ * Generates the next IPN suggestion based on the maximum numeric suffix found in the given IPNs.
+ *
+ * The new IPN is constructed using the base format of the first provided IPN,
+ * incremented by the next free numeric suffix. If no base IPNs are found,
+ * returns null.
+ *
+ * @param array $givenIpns List of IPNs to analyze.
+ *
+ * @return string|null The next suggested IPN, or null if no base IPNs can be derived.
+ */
+ private function getNextIpnSuggestion(array $givenIpns): ?string {
+ $maxSuffix = 0;
+
+ foreach ($givenIpns as $ipn) {
+ // Check whether the IPN contains a suffix "_ "
+ if (preg_match('/_(\d+)$/', $ipn, $matches)) {
+ $suffix = (int)$matches[1];
+ if ($suffix > $maxSuffix) {
+ $maxSuffix = $suffix; // Höchste Nummer speichern
+ }
+ }
+ }
+
+ // Find the basic format (the IPN without suffix) from the first IPN
+ $baseIpn = $givenIpns[0] ?? '';
+ $baseIpn = preg_replace('/_\d+$/', '', $baseIpn); // Remove existing "_ "
+
+ if ($baseIpn === '') {
+ return null;
+ }
+
+ // Generate next free possible IPN
+ return $baseIpn . '_' . ($maxSuffix + 1);
+ }
+
}
diff --git a/src/Repository/Parts/PartCustomStateRepository.php b/src/Repository/Parts/PartCustomStateRepository.php
new file mode 100644
index 00000000..d66221a2
--- /dev/null
+++ b/src/Repository/Parts/PartCustomStateRepository.php
@@ -0,0 +1,48 @@
+.
+ */
+namespace App\Repository\Parts;
+
+use App\Entity\Parts\PartCustomState;
+use App\Repository\AbstractPartsContainingRepository;
+use InvalidArgumentException;
+
+class PartCustomStateRepository extends AbstractPartsContainingRepository
+{
+ public function getParts(object $element, string $nameOrderDirection = "ASC"): array
+ {
+ if (!$element instanceof PartCustomState) {
+ throw new InvalidArgumentException('$element must be an PartCustomState!');
+ }
+
+ return $this->getPartsByField($element, $nameOrderDirection, 'partUnit');
+ }
+
+ public function getPartsCount(object $element): int
+ {
+ if (!$element instanceof PartCustomState) {
+ throw new InvalidArgumentException('$element must be an PartCustomState!');
+ }
+
+ return $this->getPartsCountByField($element, 'partUnit');
+ }
+}
diff --git a/src/Security/Voter/AttachmentVoter.php b/src/Security/Voter/AttachmentVoter.php
index bd7ae4df..df3d73a7 100644
--- a/src/Security/Voter/AttachmentVoter.php
+++ b/src/Security/Voter/AttachmentVoter.php
@@ -22,6 +22,7 @@ declare(strict_types=1);
namespace App\Security\Voter;
+use App\Entity\Attachments\PartCustomStateAttachment;
use App\Services\UserSystem\VoterHelper;
use Symfony\Bundle\SecurityBundle\Security;
use App\Entity\Attachments\AttachmentContainingDBElement;
@@ -99,6 +100,8 @@ final class AttachmentVoter extends Voter
$param = 'measurement_units';
} elseif (is_a($subject, PartAttachment::class, true)) {
$param = 'parts';
+ } elseif (is_a($subject, PartCustomStateAttachment::class, true)) {
+ $param = 'part_custom_states';
} elseif (is_a($subject, StorageLocationAttachment::class, true)) {
$param = 'storelocations';
} elseif (is_a($subject, SupplierAttachment::class, true)) {
diff --git a/src/Security/Voter/ParameterVoter.php b/src/Security/Voter/ParameterVoter.php
index f59bdeaf..5dc30ea2 100644
--- a/src/Security/Voter/ParameterVoter.php
+++ b/src/Security/Voter/ParameterVoter.php
@@ -22,6 +22,7 @@ declare(strict_types=1);
*/
namespace App\Security\Voter;
+use App\Entity\Parameters\PartCustomStateParameter;
use App\Services\UserSystem\VoterHelper;
use Symfony\Bundle\SecurityBundle\Security;
use App\Entity\Base\AbstractDBElement;
@@ -97,6 +98,8 @@ final class ParameterVoter extends Voter
$param = 'measurement_units';
} elseif (is_a($subject, PartParameter::class, true)) {
$param = 'parts';
+ } elseif (is_a($subject, PartCustomStateParameter::class, true)) {
+ $param = 'part_custom_states';
} elseif (is_a($subject, StorageLocationParameter::class, true)) {
$param = 'storelocations';
} elseif (is_a($subject, SupplierParameter::class, true)) {
diff --git a/src/Security/Voter/StructureVoter.php b/src/Security/Voter/StructureVoter.php
index ad0299a7..16d38e05 100644
--- a/src/Security/Voter/StructureVoter.php
+++ b/src/Security/Voter/StructureVoter.php
@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace App\Security\Voter;
use App\Entity\Attachments\AttachmentType;
+use App\Entity\Parts\PartCustomState;
use App\Entity\ProjectSystem\Project;
use App\Entity\Parts\Category;
use App\Entity\Parts\Footprint;
@@ -53,6 +54,7 @@ final class StructureVoter extends Voter
Supplier::class => 'suppliers',
Currency::class => 'currencies',
MeasurementUnit::class => 'measurement_units',
+ PartCustomState::class => 'part_custom_states',
];
public function __construct(private readonly VoterHelper $helper)
diff --git a/src/Services/Attachments/AttachmentSubmitHandler.php b/src/Services/Attachments/AttachmentSubmitHandler.php
index 9fbc3fe3..c7e69257 100644
--- a/src/Services/Attachments/AttachmentSubmitHandler.php
+++ b/src/Services/Attachments/AttachmentSubmitHandler.php
@@ -30,6 +30,7 @@ use App\Entity\Attachments\AttachmentUpload;
use App\Entity\Attachments\CategoryAttachment;
use App\Entity\Attachments\CurrencyAttachment;
use App\Entity\Attachments\LabelAttachment;
+use App\Entity\Attachments\PartCustomStateAttachment;
use App\Entity\Attachments\ProjectAttachment;
use App\Entity\Attachments\FootprintAttachment;
use App\Entity\Attachments\GroupAttachment;
@@ -80,6 +81,7 @@ class AttachmentSubmitHandler
//The mapping used to determine which folder will be used for an attachment type
$this->folder_mapping = [
PartAttachment::class => 'part',
+ PartCustomStateAttachment::class => 'part_custom_state',
AttachmentTypeAttachment::class => 'attachment_type',
CategoryAttachment::class => 'category',
CurrencyAttachment::class => 'currency',
diff --git a/src/Services/Attachments/PartPreviewGenerator.php b/src/Services/Attachments/PartPreviewGenerator.php
index ba6e5db0..9aedba74 100644
--- a/src/Services/Attachments/PartPreviewGenerator.php
+++ b/src/Services/Attachments/PartPreviewGenerator.php
@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace App\Services\Attachments;
use App\Entity\Parts\Footprint;
+use App\Entity\Parts\PartCustomState;
use App\Entity\ProjectSystem\Project;
use App\Entity\Parts\Category;
use App\Entity\Parts\StorageLocation;
diff --git a/src/Services/EDA/KiCadHelper.php b/src/Services/EDA/KiCadHelper.php
index 75c2cc34..4b7c5e5a 100644
--- a/src/Services/EDA/KiCadHelper.php
+++ b/src/Services/EDA/KiCadHelper.php
@@ -233,6 +233,10 @@ class KiCadHelper
}
$result["fields"]["Part-DB Unit"] = $this->createField($unit);
}
+ if ($part->getPartCustomState() !== null) {
+ $customState = $part->getPartCustomState()->getName();
+ $result["fields"]["Part-DB Custom state"] = $this->createField($customState);
+ }
if ($part->getMass()) {
$result["fields"]["Mass"] = $this->createField($part->getMass() . ' g');
}
diff --git a/src/Services/ElementTypeNameGenerator.php b/src/Services/ElementTypeNameGenerator.php
index 326707b7..19bb19f5 100644
--- a/src/Services/ElementTypeNameGenerator.php
+++ b/src/Services/ElementTypeNameGenerator.php
@@ -24,66 +24,31 @@ namespace App\Services;
use App\Entity\Attachments\Attachment;
use App\Entity\Attachments\AttachmentContainingDBElement;
-use App\Entity\Attachments\AttachmentType;
use App\Entity\Base\AbstractDBElement;
use App\Entity\Contracts\NamedElementInterface;
-use App\Entity\InfoProviderSystem\BulkInfoProviderImportJob;
-use App\Entity\InfoProviderSystem\BulkInfoProviderImportJobPart;
-use App\Entity\LabelSystem\LabelProfile;
use App\Entity\Parameters\AbstractParameter;
-use App\Entity\Parts\Category;
-use App\Entity\Parts\Footprint;
-use App\Entity\Parts\Manufacturer;
-use App\Entity\Parts\MeasurementUnit;
use App\Entity\Parts\Part;
-use App\Entity\Parts\PartAssociation;
use App\Entity\Parts\PartLot;
-use App\Entity\Parts\StorageLocation;
-use App\Entity\Parts\Supplier;
-use App\Entity\PriceInformations\Currency;
use App\Entity\PriceInformations\Orderdetail;
use App\Entity\PriceInformations\Pricedetail;
use App\Entity\ProjectSystem\Project;
use App\Entity\ProjectSystem\ProjectBOMEntry;
-use App\Entity\UserSystem\Group;
-use App\Entity\UserSystem\User;
use App\Exceptions\EntityNotSupportedException;
+use App\Settings\SynonymSettings;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @see \App\Tests\Services\ElementTypeNameGeneratorTest
*/
-class ElementTypeNameGenerator
+final readonly class ElementTypeNameGenerator
{
- protected array $mapping;
- public function __construct(protected TranslatorInterface $translator, private readonly EntityURLGenerator $entityURLGenerator)
+ public function __construct(
+ private TranslatorInterface $translator,
+ private EntityURLGenerator $entityURLGenerator,
+ private SynonymSettings $synonymsSettings,
+ )
{
- //Child classes has to become before parent classes
- $this->mapping = [
- Attachment::class => $this->translator->trans('attachment.label'),
- Category::class => $this->translator->trans('category.label'),
- AttachmentType::class => $this->translator->trans('attachment_type.label'),
- Project::class => $this->translator->trans('project.label'),
- ProjectBOMEntry::class => $this->translator->trans('project_bom_entry.label'),
- Footprint::class => $this->translator->trans('footprint.label'),
- Manufacturer::class => $this->translator->trans('manufacturer.label'),
- MeasurementUnit::class => $this->translator->trans('measurement_unit.label'),
- Part::class => $this->translator->trans('part.label'),
- PartLot::class => $this->translator->trans('part_lot.label'),
- StorageLocation::class => $this->translator->trans('storelocation.label'),
- Supplier::class => $this->translator->trans('supplier.label'),
- Currency::class => $this->translator->trans('currency.label'),
- Orderdetail::class => $this->translator->trans('orderdetail.label'),
- Pricedetail::class => $this->translator->trans('pricedetail.label'),
- Group::class => $this->translator->trans('group.label'),
- User::class => $this->translator->trans('user.label'),
- AbstractParameter::class => $this->translator->trans('parameter.label'),
- LabelProfile::class => $this->translator->trans('label_profile.label'),
- PartAssociation::class => $this->translator->trans('part_association.label'),
- BulkInfoProviderImportJob::class => $this->translator->trans('bulk_info_provider_import_job.label'),
- BulkInfoProviderImportJobPart::class => $this->translator->trans('bulk_info_provider_import_job_part.label'),
- ];
}
/**
@@ -97,27 +62,69 @@ class ElementTypeNameGenerator
* @return string the localized label for the entity type
*
* @throws EntityNotSupportedException when the passed entity is not supported
+ * @deprecated Use label() instead
*/
public function getLocalizedTypeLabel(object|string $entity): string
{
- $class = is_string($entity) ? $entity : $entity::class;
-
- //Check if we have a direct array entry for our entity class, then we can use it
- if (isset($this->mapping[$class])) {
- return $this->mapping[$class];
- }
-
- //Otherwise iterate over array and check for inheritance (needed when the proxy element from doctrine are passed)
- foreach ($this->mapping as $class_to_check => $translation) {
- if (is_a($entity, $class_to_check, true)) {
- return $translation;
- }
- }
-
- //When nothing was found throw an exception
- throw new EntityNotSupportedException(sprintf('No localized label for the element with type %s was found!', is_object($entity) ? $entity::class : (string) $entity));
+ return $this->typeLabel($entity);
}
+ private function resolveSynonymLabel(ElementTypes $type, ?string $locale, bool $plural): ?string
+ {
+ $locale ??= $this->translator->getLocale();
+
+ if ($this->synonymsSettings->isSynonymDefinedForType($type)) {
+ if ($plural) {
+ $syn = $this->synonymsSettings->getPluralSynonymForType($type, $locale);
+ } else {
+ $syn = $this->synonymsSettings->getSingularSynonymForType($type, $locale);
+ }
+
+ if ($syn === null) {
+ //Try to fall back to english
+ if ($plural) {
+ $syn = $this->synonymsSettings->getPluralSynonymForType($type, 'en');
+ } else {
+ $syn = $this->synonymsSettings->getSingularSynonymForType($type, 'en');
+ }
+ }
+
+ return $syn;
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets a localized label for the type of the entity. If user defined synonyms are defined,
+ * these are used instead of the default labels.
+ * @param object|string $entity
+ * @param string|null $locale
+ * @return string
+ */
+ public function typeLabel(object|string $entity, ?string $locale = null): string
+ {
+ $type = ElementTypes::fromValue($entity);
+
+ return $this->resolveSynonymLabel($type, $locale, false)
+ ?? $this->translator->trans($type->getDefaultLabelKey(), locale: $locale);
+ }
+
+ /**
+ * Similar to label(), but returns the plural version of the label.
+ * @param object|string $entity
+ * @param string|null $locale
+ * @return string
+ */
+ public function typeLabelPlural(object|string $entity, ?string $locale = null): string
+ {
+ $type = ElementTypes::fromValue($entity);
+
+ return $this->resolveSynonymLabel($type, $locale, true)
+ ?? $this->translator->trans($type->getDefaultPluralLabelKey(), locale: $locale);
+ }
+
+
/**
* Returns a string like in the format ElementType: ElementName.
* For example this could be something like: "Part: BC547".
@@ -132,7 +139,7 @@ class ElementTypeNameGenerator
*/
public function getTypeNameCombination(NamedElementInterface $entity, bool $use_html = false): string
{
- $type = $this->getLocalizedTypeLabel($entity);
+ $type = $this->typeLabel($entity);
if ($use_html) {
return '' . $type . ': ' . htmlspecialchars($entity->getName());
}
@@ -142,7 +149,7 @@ class ElementTypeNameGenerator
/**
- * Returns a HTML formatted label for the given enitity in the format "Type: Name" (on elements with a name) and
+ * Returns a HTML formatted label for the given entity in the format "Type: Name" (on elements with a name) and
* "Type: ID" (on elements without a name). If possible the value is given as a link to the element.
* @param AbstractDBElement $entity The entity for which the label should be generated
* @param bool $include_associated If set to true, the associated entity (like the part belonging to a part lot) is included in the label to give further information
@@ -163,7 +170,7 @@ class ElementTypeNameGenerator
} else { //Target does not have a name
$tmp = sprintf(
'%s : %s',
- $this->getLocalizedTypeLabel($entity),
+ $this->typeLabel($entity),
$entity->getID()
);
}
@@ -207,7 +214,7 @@ class ElementTypeNameGenerator
{
return sprintf(
'%s : %s [%s]',
- $this->getLocalizedTypeLabel($class),
+ $this->typeLabel($class),
$id,
$this->translator->trans('log.target_deleted')
);
diff --git a/src/Services/ElementTypes.php b/src/Services/ElementTypes.php
new file mode 100644
index 00000000..6ce8f851
--- /dev/null
+++ b/src/Services/ElementTypes.php
@@ -0,0 +1,229 @@
+.
+ */
+
+declare(strict_types=1);
+
+
+namespace App\Services;
+
+use App\Entity\Attachments\Attachment;
+use App\Entity\Attachments\AttachmentType;
+use App\Entity\InfoProviderSystem\BulkInfoProviderImportJob;
+use App\Entity\InfoProviderSystem\BulkInfoProviderImportJobPart;
+use App\Entity\LabelSystem\LabelProfile;
+use App\Entity\Parameters\AbstractParameter;
+use App\Entity\Parts\Category;
+use App\Entity\Parts\Footprint;
+use App\Entity\Parts\Manufacturer;
+use App\Entity\Parts\MeasurementUnit;
+use App\Entity\Parts\Part;
+use App\Entity\Parts\PartAssociation;
+use App\Entity\Parts\PartCustomState;
+use App\Entity\Parts\PartLot;
+use App\Entity\Parts\StorageLocation;
+use App\Entity\Parts\Supplier;
+use App\Entity\PriceInformations\Currency;
+use App\Entity\PriceInformations\Orderdetail;
+use App\Entity\PriceInformations\Pricedetail;
+use App\Entity\ProjectSystem\Project;
+use App\Entity\ProjectSystem\ProjectBOMEntry;
+use App\Entity\UserSystem\Group;
+use App\Entity\UserSystem\User;
+use App\Exceptions\EntityNotSupportedException;
+use Symfony\Contracts\Translation\TranslatableInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+enum ElementTypes: string implements TranslatableInterface
+{
+ case ATTACHMENT = "attachment";
+ case CATEGORY = "category";
+ case ATTACHMENT_TYPE = "attachment_type";
+ case PROJECT = "project";
+ case PROJECT_BOM_ENTRY = "project_bom_entry";
+ case FOOTPRINT = "footprint";
+ case MANUFACTURER = "manufacturer";
+ case MEASUREMENT_UNIT = "measurement_unit";
+ case PART = "part";
+ case PART_LOT = "part_lot";
+ case STORAGE_LOCATION = "storage_location";
+ case SUPPLIER = "supplier";
+ case CURRENCY = "currency";
+ case ORDERDETAIL = "orderdetail";
+ case PRICEDETAIL = "pricedetail";
+ case GROUP = "group";
+ case USER = "user";
+ case PARAMETER = "parameter";
+ case LABEL_PROFILE = "label_profile";
+ case PART_ASSOCIATION = "part_association";
+ case BULK_INFO_PROVIDER_IMPORT_JOB = "bulk_info_provider_import_job";
+ case BULK_INFO_PROVIDER_IMPORT_JOB_PART = "bulk_info_provider_import_job_part";
+ case PART_CUSTOM_STATE = "part_custom_state";
+
+ //Child classes has to become before parent classes
+ private const CLASS_MAPPING = [
+ Attachment::class => self::ATTACHMENT,
+ Category::class => self::CATEGORY,
+ AttachmentType::class => self::ATTACHMENT_TYPE,
+ Project::class => self::PROJECT,
+ ProjectBOMEntry::class => self::PROJECT_BOM_ENTRY,
+ Footprint::class => self::FOOTPRINT,
+ Manufacturer::class => self::MANUFACTURER,
+ MeasurementUnit::class => self::MEASUREMENT_UNIT,
+ Part::class => self::PART,
+ PartLot::class => self::PART_LOT,
+ StorageLocation::class => self::STORAGE_LOCATION,
+ Supplier::class => self::SUPPLIER,
+ Currency::class => self::CURRENCY,
+ Orderdetail::class => self::ORDERDETAIL,
+ Pricedetail::class => self::PRICEDETAIL,
+ Group::class => self::GROUP,
+ User::class => self::USER,
+ AbstractParameter::class => self::PARAMETER,
+ LabelProfile::class => self::LABEL_PROFILE,
+ PartAssociation::class => self::PART_ASSOCIATION,
+ BulkInfoProviderImportJob::class => self::BULK_INFO_PROVIDER_IMPORT_JOB,
+ BulkInfoProviderImportJobPart::class => self::BULK_INFO_PROVIDER_IMPORT_JOB_PART,
+ PartCustomState::class => self::PART_CUSTOM_STATE,
+ ];
+
+ /**
+ * Gets the default translation key for the label of the element type (singular form).
+ */
+ public function getDefaultLabelKey(): string
+ {
+ return match ($this) {
+ self::ATTACHMENT => 'attachment.label',
+ self::CATEGORY => 'category.label',
+ self::ATTACHMENT_TYPE => 'attachment_type.label',
+ self::PROJECT => 'project.label',
+ self::PROJECT_BOM_ENTRY => 'project_bom_entry.label',
+ self::FOOTPRINT => 'footprint.label',
+ self::MANUFACTURER => 'manufacturer.label',
+ self::MEASUREMENT_UNIT => 'measurement_unit.label',
+ self::PART => 'part.label',
+ self::PART_LOT => 'part_lot.label',
+ self::STORAGE_LOCATION => 'storelocation.label',
+ self::SUPPLIER => 'supplier.label',
+ self::CURRENCY => 'currency.label',
+ self::ORDERDETAIL => 'orderdetail.label',
+ self::PRICEDETAIL => 'pricedetail.label',
+ self::GROUP => 'group.label',
+ self::USER => 'user.label',
+ self::PARAMETER => 'parameter.label',
+ self::LABEL_PROFILE => 'label_profile.label',
+ self::PART_ASSOCIATION => 'part_association.label',
+ self::BULK_INFO_PROVIDER_IMPORT_JOB => 'bulk_info_provider_import_job.label',
+ self::BULK_INFO_PROVIDER_IMPORT_JOB_PART => 'bulk_info_provider_import_job_part.label',
+ self::PART_CUSTOM_STATE => 'part_custom_state.label',
+ };
+ }
+
+ public function getDefaultPluralLabelKey(): string
+ {
+ return match ($this) {
+ self::ATTACHMENT => 'attachment.labelp',
+ self::CATEGORY => 'category.labelp',
+ self::ATTACHMENT_TYPE => 'attachment_type.labelp',
+ self::PROJECT => 'project.labelp',
+ self::PROJECT_BOM_ENTRY => 'project_bom_entry.labelp',
+ self::FOOTPRINT => 'footprint.labelp',
+ self::MANUFACTURER => 'manufacturer.labelp',
+ self::MEASUREMENT_UNIT => 'measurement_unit.labelp',
+ self::PART => 'part.labelp',
+ self::PART_LOT => 'part_lot.labelp',
+ self::STORAGE_LOCATION => 'storelocation.labelp',
+ self::SUPPLIER => 'supplier.labelp',
+ self::CURRENCY => 'currency.labelp',
+ self::ORDERDETAIL => 'orderdetail.labelp',
+ self::PRICEDETAIL => 'pricedetail.labelp',
+ self::GROUP => 'group.labelp',
+ self::USER => 'user.labelp',
+ self::PARAMETER => 'parameter.labelp',
+ self::LABEL_PROFILE => 'label_profile.labelp',
+ self::PART_ASSOCIATION => 'part_association.labelp',
+ self::BULK_INFO_PROVIDER_IMPORT_JOB => 'bulk_info_provider_import_job.labelp',
+ self::BULK_INFO_PROVIDER_IMPORT_JOB_PART => 'bulk_info_provider_import_job_part.labelp',
+ self::PART_CUSTOM_STATE => 'part_custom_state.labelp',
+ };
+ }
+
+ /**
+ * Used to get a user-friendly representation of the object that can be translated.
+ * For this the singular default label key is used.
+ * @param TranslatorInterface $translator
+ * @param string|null $locale
+ * @return string
+ */
+ public function trans(TranslatorInterface $translator, ?string $locale = null): string
+ {
+ return $translator->trans($this->getDefaultLabelKey(), locale: $locale);
+ }
+
+ /**
+ * Determines the ElementType from a value, which can either be an enum value, an ElementTypes instance, a class name or an object instance.
+ * @param string|object $value
+ * @return self
+ */
+ public static function fromValue(string|object $value): self
+ {
+ if ($value instanceof self) {
+ return $value;
+ }
+ if (is_object($value)) {
+ return self::fromClass($value);
+ }
+
+
+ //Otherwise try to parse it as enum value first
+ $enumValue = self::tryFrom($value);
+
+ //Otherwise try to get it from class name
+ return $enumValue ?? self::fromClass($value);
+ }
+
+ /**
+ * Determines the ElementType from a class name or object instance.
+ * @param string|object $class
+ * @throws EntityNotSupportedException if the class is not supported
+ * @return self
+ */
+ public static function fromClass(string|object $class): self
+ {
+ if (is_object($class)) {
+ $className = get_class($class);
+ } else {
+ $className = $class;
+ }
+
+ if (array_key_exists($className, self::CLASS_MAPPING)) {
+ return self::CLASS_MAPPING[$className];
+ }
+
+ //Otherwise we need to check for inheritance
+ foreach (self::CLASS_MAPPING as $entityClass => $elementType) {
+ if (is_a($className, $entityClass, true)) {
+ return $elementType;
+ }
+ }
+
+ throw new EntityNotSupportedException(sprintf('No localized label for the element with type %s was found!', $className));
+ }
+
+}
diff --git a/src/Services/EntityMergers/Mergers/PartMerger.php b/src/Services/EntityMergers/Mergers/PartMerger.php
index 01b53e25..d1f5c137 100644
--- a/src/Services/EntityMergers/Mergers/PartMerger.php
+++ b/src/Services/EntityMergers/Mergers/PartMerger.php
@@ -65,6 +65,7 @@ class PartMerger implements EntityMergerInterface
$this->useOtherValueIfNotNull($target, $other, 'footprint');
$this->useOtherValueIfNotNull($target, $other, 'category');
$this->useOtherValueIfNotNull($target, $other, 'partUnit');
+ $this->useOtherValueIfNotNull($target, $other, 'partCustomState');
//We assume that the higher value is the correct one for minimum instock
$this->useLargerValue($target, $other, 'minamount');
diff --git a/src/Services/EntityURLGenerator.php b/src/Services/EntityURLGenerator.php
index 78db06f0..91e271cc 100644
--- a/src/Services/EntityURLGenerator.php
+++ b/src/Services/EntityURLGenerator.php
@@ -27,6 +27,7 @@ use App\Entity\Attachments\AttachmentType;
use App\Entity\Attachments\PartAttachment;
use App\Entity\Base\AbstractDBElement;
use App\Entity\Parameters\PartParameter;
+use App\Entity\Parts\PartCustomState;
use App\Entity\ProjectSystem\Project;
use App\Entity\LabelSystem\LabelProfile;
use App\Entity\Parts\Category;
@@ -107,6 +108,7 @@ class EntityURLGenerator
MeasurementUnit::class => 'measurement_unit_edit',
Group::class => 'group_edit',
LabelProfile::class => 'label_profile_edit',
+ PartCustomState::class => 'part_custom_state_edit',
];
try {
@@ -213,6 +215,7 @@ class EntityURLGenerator
MeasurementUnit::class => 'measurement_unit_edit',
Group::class => 'group_edit',
LabelProfile::class => 'label_profile_edit',
+ PartCustomState::class => 'part_custom_state_edit',
];
return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]);
@@ -243,6 +246,7 @@ class EntityURLGenerator
MeasurementUnit::class => 'measurement_unit_edit',
Group::class => 'group_edit',
LabelProfile::class => 'label_profile_edit',
+ PartCustomState::class => 'part_custom_state_edit',
];
return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]);
@@ -274,6 +278,7 @@ class EntityURLGenerator
MeasurementUnit::class => 'measurement_unit_new',
Group::class => 'group_new',
LabelProfile::class => 'label_profile_new',
+ PartCustomState::class => 'part_custom_state_new',
];
return $this->urlGenerator->generate($this->mapToController($map, $entity));
@@ -305,6 +310,7 @@ class EntityURLGenerator
MeasurementUnit::class => 'measurement_unit_clone',
Group::class => 'group_clone',
LabelProfile::class => 'label_profile_clone',
+ PartCustomState::class => 'part_custom_state_clone',
];
return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]);
@@ -350,6 +356,7 @@ class EntityURLGenerator
MeasurementUnit::class => 'measurement_unit_delete',
Group::class => 'group_delete',
LabelProfile::class => 'label_profile_delete',
+ PartCustomState::class => 'part_custom_state_delete',
];
return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]);
diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKDatastructureImporter.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKDatastructureImporter.php
index 1f842c23..9e674f05 100644
--- a/src/Services/ImportExportSystem/PartKeeprImporter/PKDatastructureImporter.php
+++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKDatastructureImporter.php
@@ -29,6 +29,7 @@ use App\Entity\Parts\Category;
use App\Entity\Parts\Footprint;
use App\Entity\Parts\Manufacturer;
use App\Entity\Parts\MeasurementUnit;
+use App\Entity\Parts\PartCustomState;
use App\Entity\Parts\StorageLocation;
use App\Entity\Parts\Supplier;
use Doctrine\ORM\EntityManagerInterface;
@@ -148,6 +149,26 @@ class PKDatastructureImporter
return is_countable($partunit_data) ? count($partunit_data) : 0;
}
+ public function importPartCustomStates(array $data): int
+ {
+ if (!isset($data['partcustomstate'])) {
+ throw new \RuntimeException('$data must contain a "partcustomstate" key!');
+ }
+
+ $partCustomStateData = $data['partcustomstate'];
+ foreach ($partCustomStateData as $partCustomState) {
+ $customState = new PartCustomState();
+ $customState->setName($partCustomState['name']);
+
+ $this->setIDOfEntity($customState, $partCustomState['id']);
+ $this->em->persist($customState);
+ }
+
+ $this->em->flush();
+
+ return is_countable($partCustomStateData) ? count($partCustomStateData) : 0;
+ }
+
public function importCategories(array $data): int
{
if (!isset($data['partcategory'])) {
diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php
index 80c2dbf7..ab06a134 100644
--- a/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php
+++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php
@@ -91,6 +91,8 @@ class PKPartImporter
$this->setAssociationField($entity, 'partUnit', MeasurementUnit::class, $part['partUnit_id']);
}
+ $this->setAssociationField($entity, 'partCustomState', MeasurementUnit::class, $part['partCustomState_id']);
+
//Create a part lot to store the stock level and location
$lot = new PartLot();
$lot->setAmount((float) ($part['stockLevel'] ?? 0));
diff --git a/src/Services/LabelSystem/SandboxedTwigFactory.php b/src/Services/LabelSystem/SandboxedTwigFactory.php
index d6ea6968..d5e09fa5 100644
--- a/src/Services/LabelSystem/SandboxedTwigFactory.php
+++ b/src/Services/LabelSystem/SandboxedTwigFactory.php
@@ -133,7 +133,7 @@ final class SandboxedTwigFactory
Supplier::class => ['getShippingCosts', 'getDefaultCurrency'],
Part::class => ['isNeedsReview', 'getTags', 'getMass', 'getIpn', 'getProviderReference',
'getDescription', 'getComment', 'isFavorite', 'getCategory', 'getFootprint',
- 'getPartLots', 'getPartUnit', 'useFloatAmount', 'getMinAmount', 'getAmountSum', 'isNotEnoughInstock', 'isAmountUnknown', 'getExpiredAmountSum',
+ 'getPartLots', 'getPartUnit', 'getPartCustomState', 'useFloatAmount', 'getMinAmount', 'getAmountSum', 'isNotEnoughInstock', 'isAmountUnknown', 'getExpiredAmountSum',
'getManufacturerProductUrl', 'getCustomProductURL', 'getManufacturingStatus', 'getManufacturer',
'getManufacturerProductNumber', 'getOrderdetails', 'isObsolete',
'getParameters', 'getGroupedParameters',
diff --git a/src/Services/Trees/ToolsTreeBuilder.php b/src/Services/Trees/ToolsTreeBuilder.php
index 036797f6..37a09b09 100644
--- a/src/Services/Trees/ToolsTreeBuilder.php
+++ b/src/Services/Trees/ToolsTreeBuilder.php
@@ -29,6 +29,7 @@ use App\Entity\Parts\Footprint;
use App\Entity\Parts\Manufacturer;
use App\Entity\Parts\MeasurementUnit;
use App\Entity\Parts\Part;
+use App\Entity\Parts\PartCustomState;
use App\Entity\Parts\StorageLocation;
use App\Entity\Parts\Supplier;
use App\Entity\PriceInformations\Currency;
@@ -37,6 +38,7 @@ use App\Entity\UserSystem\Group;
use App\Entity\UserSystem\User;
use App\Helpers\Trees\TreeViewNode;
use App\Services\Cache\UserCacheKeyGenerator;
+use App\Services\ElementTypeNameGenerator;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Contracts\Cache\ItemInterface;
@@ -49,8 +51,14 @@ use Symfony\Contracts\Translation\TranslatorInterface;
*/
class ToolsTreeBuilder
{
- public function __construct(protected TranslatorInterface $translator, protected UrlGeneratorInterface $urlGenerator, protected TagAwareCacheInterface $cache, protected UserCacheKeyGenerator $keyGenerator, protected Security $security)
- {
+ public function __construct(
+ protected TranslatorInterface $translator,
+ protected UrlGeneratorInterface $urlGenerator,
+ protected TagAwareCacheInterface $cache,
+ protected UserCacheKeyGenerator $keyGenerator,
+ protected Security $security,
+ private readonly ElementTypeNameGenerator $elementTypeNameGenerator,
+ ) {
}
/**
@@ -138,7 +146,7 @@ class ToolsTreeBuilder
$this->translator->trans('info_providers.search.title'),
$this->urlGenerator->generate('info_providers_search')
))->setIcon('fa-treeview fa-fw fa-solid fa-cloud-arrow-down');
-
+
$nodes[] = (new TreeViewNode(
$this->translator->trans('info_providers.bulk_import.manage_jobs'),
$this->urlGenerator->generate('bulk_info_provider_manage')
@@ -159,64 +167,70 @@ class ToolsTreeBuilder
if ($this->security->isGranted('read', new AttachmentType())) {
$nodes[] = (new TreeViewNode(
- $this->translator->trans('tree.tools.edit.attachment_types'),
+ $this->elementTypeNameGenerator->typeLabelPlural(AttachmentType::class),
$this->urlGenerator->generate('attachment_type_new')
))->setIcon('fa-fw fa-treeview fa-solid fa-file-alt');
}
if ($this->security->isGranted('read', new Category())) {
$nodes[] = (new TreeViewNode(
- $this->translator->trans('tree.tools.edit.categories'),
+ $this->elementTypeNameGenerator->typeLabelPlural(Category::class),
$this->urlGenerator->generate('category_new')
))->setIcon('fa-fw fa-treeview fa-solid fa-tags');
}
if ($this->security->isGranted('read', new Project())) {
$nodes[] = (new TreeViewNode(
- $this->translator->trans('tree.tools.edit.projects'),
+ $this->elementTypeNameGenerator->typeLabelPlural(Project::class),
$this->urlGenerator->generate('project_new')
))->setIcon('fa-fw fa-treeview fa-solid fa-archive');
}
if ($this->security->isGranted('read', new Supplier())) {
$nodes[] = (new TreeViewNode(
- $this->translator->trans('tree.tools.edit.suppliers'),
+ $this->elementTypeNameGenerator->typeLabelPlural(Supplier::class),
$this->urlGenerator->generate('supplier_new')
))->setIcon('fa-fw fa-treeview fa-solid fa-truck');
}
if ($this->security->isGranted('read', new Manufacturer())) {
$nodes[] = (new TreeViewNode(
- $this->translator->trans('tree.tools.edit.manufacturer'),
+ $this->elementTypeNameGenerator->typeLabelPlural(Manufacturer::class),
$this->urlGenerator->generate('manufacturer_new')
))->setIcon('fa-fw fa-treeview fa-solid fa-industry');
}
if ($this->security->isGranted('read', new StorageLocation())) {
$nodes[] = (new TreeViewNode(
- $this->translator->trans('tree.tools.edit.storelocation'),
+ $this->elementTypeNameGenerator->typeLabelPlural(StorageLocation::class),
$this->urlGenerator->generate('store_location_new')
))->setIcon('fa-fw fa-treeview fa-solid fa-cube');
}
if ($this->security->isGranted('read', new Footprint())) {
$nodes[] = (new TreeViewNode(
- $this->translator->trans('tree.tools.edit.footprint'),
+ $this->elementTypeNameGenerator->typeLabelPlural(Footprint::class),
$this->urlGenerator->generate('footprint_new')
))->setIcon('fa-fw fa-treeview fa-solid fa-microchip');
}
if ($this->security->isGranted('read', new Currency())) {
$nodes[] = (new TreeViewNode(
- $this->translator->trans('tree.tools.edit.currency'),
+ $this->elementTypeNameGenerator->typeLabelPlural(Currency::class),
$this->urlGenerator->generate('currency_new')
))->setIcon('fa-fw fa-treeview fa-solid fa-coins');
}
if ($this->security->isGranted('read', new MeasurementUnit())) {
$nodes[] = (new TreeViewNode(
- $this->translator->trans('tree.tools.edit.measurement_unit'),
+ $this->elementTypeNameGenerator->typeLabelPlural(MeasurementUnit::class),
$this->urlGenerator->generate('measurement_unit_new')
))->setIcon('fa-fw fa-treeview fa-solid fa-balance-scale');
}
if ($this->security->isGranted('read', new LabelProfile())) {
$nodes[] = (new TreeViewNode(
- $this->translator->trans('tree.tools.edit.label_profile'),
+ $this->elementTypeNameGenerator->typeLabelPlural(LabelProfile::class),
$this->urlGenerator->generate('label_profile_new')
))->setIcon('fa-fw fa-treeview fa-solid fa-qrcode');
}
+ if ($this->security->isGranted('read', new PartCustomState())) {
+ $nodes[] = (new TreeViewNode(
+ $this->elementTypeNameGenerator->typeLabelPlural(PartCustomState::class),
+ $this->urlGenerator->generate('part_custom_state_new')
+ ))->setIcon('fa-fw fa-treeview fa-solid fa-tools');
+ }
if ($this->security->isGranted('create', new Part())) {
$nodes[] = (new TreeViewNode(
$this->translator->trans('tree.tools.edit.part'),
diff --git a/src/Services/Trees/TreeViewGenerator.php b/src/Services/Trees/TreeViewGenerator.php
index 73ffa5ba..d55c87b7 100644
--- a/src/Services/Trees/TreeViewGenerator.php
+++ b/src/Services/Trees/TreeViewGenerator.php
@@ -34,9 +34,9 @@ use App\Entity\ProjectSystem\Project;
use App\Helpers\Trees\TreeViewNode;
use App\Helpers\Trees\TreeViewNodeIterator;
use App\Repository\NamedDBElementRepository;
-use App\Repository\StructuralDBElementRepository;
use App\Services\Cache\ElementCacheTagGenerator;
use App\Services\Cache\UserCacheKeyGenerator;
+use App\Services\ElementTypeNameGenerator;
use App\Services\EntityURLGenerator;
use App\Settings\BehaviorSettings\SidebarSettings;
use Doctrine\ORM\EntityManagerInterface;
@@ -67,6 +67,7 @@ class TreeViewGenerator
protected TranslatorInterface $translator,
private readonly UrlGeneratorInterface $router,
private readonly SidebarSettings $sidebarSettings,
+ private readonly ElementTypeNameGenerator $elementTypeNameGenerator
) {
$this->rootNodeEnabled = $this->sidebarSettings->rootNodeEnabled;
$this->rootNodeExpandedByDefault = $this->sidebarSettings->rootNodeExpanded;
@@ -212,15 +213,7 @@ class TreeViewGenerator
protected function entityClassToRootNodeString(string $class): string
{
- return match ($class) {
- Category::class => $this->translator->trans('category.labelp'),
- StorageLocation::class => $this->translator->trans('storelocation.labelp'),
- Footprint::class => $this->translator->trans('footprint.labelp'),
- Manufacturer::class => $this->translator->trans('manufacturer.labelp'),
- Supplier::class => $this->translator->trans('supplier.labelp'),
- Project::class => $this->translator->trans('project.labelp'),
- default => $this->translator->trans('tree.root_node.text'),
- };
+ return $this->elementTypeNameGenerator->typeLabelPlural($class);
}
protected function entityClassToRootNodeIcon(string $class): ?string
diff --git a/src/Services/UserSystem/PermissionPresetsHelper.php b/src/Services/UserSystem/PermissionPresetsHelper.php
index 554da8b3..a3ed01b8 100644
--- a/src/Services/UserSystem/PermissionPresetsHelper.php
+++ b/src/Services/UserSystem/PermissionPresetsHelper.php
@@ -102,6 +102,7 @@ class PermissionPresetsHelper
$this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'attachment_types', PermissionData::ALLOW);
$this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'currencies', PermissionData::ALLOW);
$this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'measurement_units', PermissionData::ALLOW);
+ $this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'part_custom_states', PermissionData::ALLOW);
$this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'suppliers', PermissionData::ALLOW);
$this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'projects', PermissionData::ALLOW);
@@ -131,6 +132,7 @@ class PermissionPresetsHelper
$this->permissionResolver->setAllOperationsOfPermissionExcept($permHolder, 'attachment_types', PermissionData::ALLOW, ['import']);
$this->permissionResolver->setAllOperationsOfPermissionExcept($permHolder, 'currencies', PermissionData::ALLOW, ['import']);
$this->permissionResolver->setAllOperationsOfPermissionExcept($permHolder, 'measurement_units', PermissionData::ALLOW, ['import']);
+ $this->permissionResolver->setAllOperationsOfPermissionExcept($permHolder, 'part_custom_states', PermissionData::ALLOW, ['import']);
$this->permissionResolver->setAllOperationsOfPermissionExcept($permHolder, 'suppliers', PermissionData::ALLOW, ['import']);
$this->permissionResolver->setAllOperationsOfPermissionExcept($permHolder, 'projects', PermissionData::ALLOW, ['import']);
diff --git a/src/Settings/AppSettings.php b/src/Settings/AppSettings.php
index 42831d08..14d9395e 100644
--- a/src/Settings/AppSettings.php
+++ b/src/Settings/AppSettings.php
@@ -47,6 +47,12 @@ class AppSettings
#[EmbeddedSettings()]
public ?InfoProviderSettings $infoProviders = null;
+ #[EmbeddedSettings]
+ public ?SynonymSettings $synonyms = null;
+
#[EmbeddedSettings()]
public ?MiscSettings $miscSettings = null;
+
+
+
}
diff --git a/src/Settings/BehaviorSettings/PartTableColumns.php b/src/Settings/BehaviorSettings/PartTableColumns.php
index eea6ad86..c025c952 100644
--- a/src/Settings/BehaviorSettings/PartTableColumns.php
+++ b/src/Settings/BehaviorSettings/PartTableColumns.php
@@ -46,6 +46,7 @@ enum PartTableColumns : string implements TranslatableInterface
case FAVORITE = "favorite";
case MANUFACTURING_STATUS = "manufacturing_status";
case MPN = "manufacturer_product_number";
+ case CUSTOM_PART_STATE = 'partCustomState';
case MASS = "mass";
case TAGS = "tags";
case ATTACHMENTS = "attachments";
@@ -63,4 +64,4 @@ enum PartTableColumns : string implements TranslatableInterface
return $translator->trans($key, locale: $locale);
}
-}
\ No newline at end of file
+}
diff --git a/src/Settings/BehaviorSettings/TableSettings.php b/src/Settings/BehaviorSettings/TableSettings.php
index b6964876..b3421e41 100644
--- a/src/Settings/BehaviorSettings/TableSettings.php
+++ b/src/Settings/BehaviorSettings/TableSettings.php
@@ -68,7 +68,7 @@ class TableSettings
#[Assert\All([new Assert\Type(PartTableColumns::class)])]
public array $partsDefaultColumns = [PartTableColumns::NAME, PartTableColumns::DESCRIPTION,
PartTableColumns::CATEGORY, PartTableColumns::FOOTPRINT, PartTableColumns::MANUFACTURER,
- PartTableColumns::LOCATION, PartTableColumns::AMOUNT];
+ PartTableColumns::LOCATION, PartTableColumns::AMOUNT, PartTableColumns::CUSTOM_PART_STATE];
#[SettingsParameter(label: new TM("settings.behavior.table.preview_image_min_width"),
formOptions: ['attr' => ['min' => 1, 'max' => 100]],
diff --git a/src/Settings/MiscSettings/IpnSuggestSettings.php b/src/Settings/MiscSettings/IpnSuggestSettings.php
new file mode 100644
index 00000000..891b885c
--- /dev/null
+++ b/src/Settings/MiscSettings/IpnSuggestSettings.php
@@ -0,0 +1,80 @@
+.
+ */
+
+declare(strict_types=1);
+
+
+namespace App\Settings\MiscSettings;
+
+use App\Settings\SettingsIcon;
+use Jbtronics\SettingsBundle\Metadata\EnvVarMode;
+use Jbtronics\SettingsBundle\ParameterTypes\StringType;
+use Jbtronics\SettingsBundle\Settings\Settings;
+use Jbtronics\SettingsBundle\Settings\SettingsParameter;
+use Jbtronics\SettingsBundle\Settings\SettingsTrait;
+use Symfony\Component\Translation\TranslatableMessage as TM;
+use Symfony\Component\Validator\Constraints as Assert;
+
+#[Settings(label: new TM("settings.misc.ipn_suggest"))]
+#[SettingsIcon("fa-list")]
+class IpnSuggestSettings
+{
+ use SettingsTrait;
+
+ #[SettingsParameter(
+ label: new TM("settings.misc.ipn_suggest.regex"),
+ description: new TM("settings.misc.ipn_suggest.regex.help"),
+ options: ['type' => StringType::class],
+ formOptions: ['attr' => ['placeholder' => '^[A-Za-z0-9]{3,4}(?:-[A-Za-z0-9]{3,4})*-\d{4}$']],
+ envVar: "IPN_SUGGEST_REGEX", envVarMode: EnvVarMode::OVERWRITE,
+ )]
+ public ?string $regex = null;
+
+ #[SettingsParameter(
+ label: new TM("settings.misc.ipn_suggest.regex_help"),
+ description: new TM("settings.misc.ipn_suggest.regex_help_description"),
+ options: ['type' => StringType::class],
+ formOptions: ['attr' => ['placeholder' => 'Format: 3–4 alphanumeric segments (any number) separated by "-", followed by "-" and 4 digits, e.g., PCOM-RES-0001']],
+ envVar: "IPN_SUGGEST_REGEX_HELP", envVarMode: EnvVarMode::OVERWRITE,
+ )]
+ public ?string $regexHelp = null;
+
+ #[SettingsParameter(
+ label: new TM("settings.misc.ipn_suggest.autoAppendSuffix"),
+ envVar: "bool:IPN_AUTO_APPEND_SUFFIX", envVarMode: EnvVarMode::OVERWRITE,
+ )]
+ public bool $autoAppendSuffix = false;
+
+ #[SettingsParameter(label: new TM("settings.misc.ipn_suggest.suggestPartDigits"),
+ description: new TM("settings.misc.ipn_suggest.suggestPartDigits.help"),
+ formOptions: ['attr' => ['min' => 1, 'max' => 8]],
+ envVar: "int:IPN_SUGGEST_PART_DIGITS", envVarMode: EnvVarMode::OVERWRITE
+ )]
+ #[Assert\Range(min: 1, max: 8)]
+ public int $suggestPartDigits = 4;
+
+ #[SettingsParameter(
+ label: new TM("settings.misc.ipn_suggest.useDuplicateDescription"),
+ description: new TM("settings.misc.ipn_suggest.useDuplicateDescription.help"),
+ envVar: "bool:IPN_USE_DUPLICATE_DESCRIPTION", envVarMode: EnvVarMode::OVERWRITE,
+ )]
+ public bool $useDuplicateDescription = false;
+}
diff --git a/src/Settings/MiscSettings/MiscSettings.php b/src/Settings/MiscSettings/MiscSettings.php
index 1c304d4a..050dbcbc 100644
--- a/src/Settings/MiscSettings/MiscSettings.php
+++ b/src/Settings/MiscSettings/MiscSettings.php
@@ -35,4 +35,7 @@ class MiscSettings
#[EmbeddedSettings]
public ?ExchangeRateSettings $exchangeRate = null;
+
+ #[EmbeddedSettings]
+ public ?IpnSuggestSettings $ipnSuggestSettings = null;
}
diff --git a/src/Settings/SynonymSettings.php b/src/Settings/SynonymSettings.php
new file mode 100644
index 00000000..25fc87e9
--- /dev/null
+++ b/src/Settings/SynonymSettings.php
@@ -0,0 +1,116 @@
+.
+ */
+
+declare(strict_types=1);
+
+namespace App\Settings;
+
+use App\Form\Settings\TypeSynonymsCollectionType;
+use App\Services\ElementTypes;
+use Jbtronics\SettingsBundle\ParameterTypes\ArrayType;
+use Jbtronics\SettingsBundle\ParameterTypes\SerializeType;
+use Jbtronics\SettingsBundle\Settings\Settings;
+use Jbtronics\SettingsBundle\Settings\SettingsParameter;
+use Jbtronics\SettingsBundle\Settings\SettingsTrait;
+use Symfony\Component\Translation\TranslatableMessage as TM;
+use Symfony\Component\Validator\Constraints as Assert;
+
+#[Settings(label: new TM("settings.synonyms"), description: "settings.synonyms.help")]
+#[SettingsIcon("fa-language")]
+class SynonymSettings
+{
+ use SettingsTrait;
+
+ #[SettingsParameter(
+ ArrayType::class,
+ label: new TM("settings.synonyms.type_synonyms"),
+ description: new TM("settings.synonyms.type_synonyms.help"),
+ options: ['type' => SerializeType::class],
+ formType: TypeSynonymsCollectionType::class,
+ formOptions: [
+ 'required' => false,
+ ],
+ )]
+ #[Assert\Type('array')]
+ #[Assert\All([new Assert\Type('array')])]
+ /**
+ * @var array> $typeSynonyms
+ * An array of the form: [
+ * 'category' => [
+ * 'en' => ['singular' => 'Category', 'plural' => 'Categories'],
+ * 'de' => ['singular' => 'Kategorie', 'plural' => 'Kategorien'],
+ * ],
+ * 'manufacturer' => [
+ * 'en' => ['singular' => 'Manufacturer', 'plural' =>'Manufacturers'],
+ * ],
+ * ]
+ */
+ public array $typeSynonyms = [];
+
+ /**
+ * Checks if there is any synonym defined for the given type (no matter which language).
+ * @param ElementTypes $type
+ * @return bool
+ */
+ public function isSynonymDefinedForType(ElementTypes $type): bool
+ {
+ return isset($this->typeSynonyms[$type->value]) && count($this->typeSynonyms[$type->value]) > 0;
+ }
+
+ /**
+ * Returns the singular synonym for the given type and locale, or null if none is defined.
+ * @param ElementTypes $type
+ * @param string $locale
+ * @return string|null
+ */
+ public function getSingularSynonymForType(ElementTypes $type, string $locale): ?string
+ {
+ return $this->typeSynonyms[$type->value][$locale]['singular'] ?? null;
+ }
+
+ /**
+ * Returns the plural synonym for the given type and locale, or null if none is defined.
+ * @param ElementTypes $type
+ * @param string|null $locale
+ * @return string|null
+ */
+ public function getPluralSynonymForType(ElementTypes $type, ?string $locale): ?string
+ {
+ return $this->typeSynonyms[$type->value][$locale]['plural']
+ ?? $this->typeSynonyms[$type->value][$locale]['singular']
+ ?? null;
+ }
+
+ /**
+ * Sets a synonym for the given type and locale.
+ * @param ElementTypes $type
+ * @param string $locale
+ * @param string $singular
+ * @param string $plural
+ * @return void
+ */
+ public function setSynonymForType(ElementTypes $type, string $locale, string $singular, string $plural): void
+ {
+ $this->typeSynonyms[$type->value][$locale] = [
+ 'singular' => $singular,
+ 'plural' => $plural,
+ ];
+ }
+}
diff --git a/src/Settings/SystemSettings/LocalizationSettings.php b/src/Settings/SystemSettings/LocalizationSettings.php
index 7c83d1ef..c6780c6c 100644
--- a/src/Settings/SystemSettings/LocalizationSettings.php
+++ b/src/Settings/SystemSettings/LocalizationSettings.php
@@ -23,7 +23,7 @@ declare(strict_types=1);
namespace App\Settings\SystemSettings;
-use App\Form\Type\LanguageMenuEntriesType;
+use App\Form\Settings\LanguageMenuEntriesType;
use App\Form\Type\LocaleSelectType;
use App\Settings\SettingsIcon;
use Jbtronics\SettingsBundle\Metadata\EnvVarMode;
diff --git a/src/Settings/SystemSettings/SystemSettings.php b/src/Settings/SystemSettings/SystemSettings.php
index 71dd963d..8cbeb560 100644
--- a/src/Settings/SystemSettings/SystemSettings.php
+++ b/src/Settings/SystemSettings/SystemSettings.php
@@ -33,6 +33,8 @@ class SystemSettings
#[EmbeddedSettings()]
public ?LocalizationSettings $localization = null;
+
+
#[EmbeddedSettings()]
public ?CustomizationSettings $customization = null;
diff --git a/src/Twig/EntityExtension.php b/src/Twig/EntityExtension.php
index 762ebb09..427a39b5 100644
--- a/src/Twig/EntityExtension.php
+++ b/src/Twig/EntityExtension.php
@@ -24,6 +24,7 @@ namespace App\Twig;
use App\Entity\Attachments\Attachment;
use App\Entity\Base\AbstractDBElement;
+use App\Entity\Parts\PartCustomState;
use App\Entity\ProjectSystem\Project;
use App\Entity\LabelSystem\LabelProfile;
use App\Entity\Parts\Category;
@@ -75,6 +76,8 @@ final class EntityExtension extends AbstractExtension
/* Gets a human readable label for the type of the given entity */
new TwigFunction('entity_type_label', fn(object|string $entity): string => $this->nameGenerator->getLocalizedTypeLabel($entity)),
+ new TwigFunction('type_label', fn(object|string $entity): string => $this->nameGenerator->typeLabel($entity)),
+ new TwigFunction('type_label_p', fn(object|string $entity): string => $this->nameGenerator->typeLabelPlural($entity)),
];
}
@@ -115,6 +118,7 @@ final class EntityExtension extends AbstractExtension
Currency::class => 'currency',
MeasurementUnit::class => 'measurement_unit',
LabelProfile::class => 'label_profile',
+ PartCustomState::class => 'part_custom_state',
];
foreach ($map as $class => $type) {
diff --git a/src/Validator/Constraints/UniquePartIpnConstraint.php b/src/Validator/Constraints/UniquePartIpnConstraint.php
new file mode 100644
index 00000000..ca32f9ef
--- /dev/null
+++ b/src/Validator/Constraints/UniquePartIpnConstraint.php
@@ -0,0 +1,22 @@
+entityManager = $entityManager;
+ $this->ipnSuggestSettings = $ipnSuggestSettings;
+ }
+
+ public function validate($value, Constraint $constraint): void
+ {
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ //If the autoAppendSuffix option is enabled, the IPN becomes unique automatically later
+ if ($this->ipnSuggestSettings->autoAppendSuffix) {
+ return;
+ }
+
+ if (!$constraint instanceof UniquePartIpnConstraint) {
+ return;
+ }
+
+ /** @var Part $currentPart */
+ $currentPart = $this->context->getObject();
+
+ if (!$currentPart instanceof Part) {
+ return;
+ }
+
+ $repository = $this->entityManager->getRepository(Part::class);
+ $existingParts = $repository->findBy(['ipn' => $value]);
+
+ foreach ($existingParts as $existingPart) {
+ if ($currentPart->getId() !== $existingPart->getId()) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $value)
+ ->addViolation();
+ }
+ }
+ }
+}
diff --git a/symfony.lock b/symfony.lock
index 7c136b4b..2cff2c00 100644
--- a/symfony.lock
+++ b/symfony.lock
@@ -592,12 +592,6 @@
"symfony/polyfill-intl-normalizer": {
"version": "v1.17.0"
},
- "symfony/polyfill-mbstring": {
- "version": "v1.10.0"
- },
- "symfony/polyfill-php80": {
- "version": "v1.17.0"
- },
"symfony/process": {
"version": "v4.2.3"
},
diff --git a/templates/admin/attachment_type_admin.html.twig b/templates/admin/attachment_type_admin.html.twig
index 06a8c09d..87a053af 100644
--- a/templates/admin/attachment_type_admin.html.twig
+++ b/templates/admin/attachment_type_admin.html.twig
@@ -15,4 +15,4 @@
{% block new_title %}
{% trans %}attachment_type.new{% endtrans %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/templates/admin/base_admin.html.twig b/templates/admin/base_admin.html.twig
index 51790c3c..e9fc0fb9 100644
--- a/templates/admin/base_admin.html.twig
+++ b/templates/admin/base_admin.html.twig
@@ -86,7 +86,7 @@
{% trans %}admin.attachments{% endtrans %}
- {% if entity.parameters is defined %}
+ {% if entity.parameters is defined and showParameters == true %}
{% trans %}admin.parameters{% endtrans %}
diff --git a/templates/admin/category_admin.html.twig b/templates/admin/category_admin.html.twig
index 5811640b..3ddc1472 100644
--- a/templates/admin/category_admin.html.twig
+++ b/templates/admin/category_admin.html.twig
@@ -1,7 +1,7 @@
{% extends "admin/base_admin.html.twig" %}
{% block card_title %}
- {% trans %}category.labelp{% endtrans %}
+ {{ type_label_p(entity) }}
{% endblock %}
{% block additional_pills %}
@@ -31,6 +31,7 @@
{{ form_row(form.partname_regex) }}
{{ form_row(form.partname_hint) }}
+ {{ form_row(form.part_ipn_prefix) }}
{{ form_row(form.default_description) }}
{{ form_row(form.default_comment) }}
@@ -60,4 +61,4 @@
{{ form_row(form.eda_info.kicad_symbol) }}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/templates/admin/currency_admin.html.twig b/templates/admin/currency_admin.html.twig
index fbd3822c..a5d59970 100644
--- a/templates/admin/currency_admin.html.twig
+++ b/templates/admin/currency_admin.html.twig
@@ -3,7 +3,7 @@
{% import "vars.macro.twig" as vars %}
{% block card_title %}
- {% trans %}currency.caption{% endtrans %}
+ {{ type_label_p(entity) }}
{% endblock %}
{% block additional_controls %}
@@ -41,4 +41,4 @@
{% block new_title %}
{% trans %}currency.new{% endtrans %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/templates/admin/footprint_admin.html.twig b/templates/admin/footprint_admin.html.twig
index a2c3e4af..1ed39e9f 100644
--- a/templates/admin/footprint_admin.html.twig
+++ b/templates/admin/footprint_admin.html.twig
@@ -1,7 +1,7 @@
{% extends "admin/base_admin.html.twig" %}
{% block card_title %}
- {% trans %}footprint.labelp{% endtrans %}
+ {{ type_label_p(entity) }}
{% endblock %}
{% block master_picture_block %}
@@ -34,4 +34,4 @@
{{ form_row(form.eda_info.kicad_footprint) }}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/templates/admin/group_admin.html.twig b/templates/admin/group_admin.html.twig
index 91975524..831c08d5 100644
--- a/templates/admin/group_admin.html.twig
+++ b/templates/admin/group_admin.html.twig
@@ -1,7 +1,7 @@
{% extends "admin/base_admin.html.twig" %}
{% block card_title %}
- {% trans %}group.edit.caption{% endtrans %}
+ {{ type_label_p(entity) }}
{% endblock %}
@@ -27,4 +27,4 @@
{% block new_title %}
{% trans %}group.new{% endtrans %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/templates/admin/label_profile_admin.html.twig b/templates/admin/label_profile_admin.html.twig
index 10c2320f..8702b18a 100644
--- a/templates/admin/label_profile_admin.html.twig
+++ b/templates/admin/label_profile_admin.html.twig
@@ -1,7 +1,7 @@
{% extends "admin/base_admin.html.twig" %}
{% block card_title %}
- {% trans %}label_profile.caption{% endtrans %}
+ {{ type_label_p(entity) }}
{% endblock %}
{% block additional_pills %}
@@ -58,4 +58,4 @@
{% block new_title %}
{% trans %}label_profile.new{% endtrans %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/templates/admin/manufacturer_admin.html.twig b/templates/admin/manufacturer_admin.html.twig
index 5db892c0..4f8f1c2b 100644
--- a/templates/admin/manufacturer_admin.html.twig
+++ b/templates/admin/manufacturer_admin.html.twig
@@ -1,7 +1,7 @@
{% extends "admin/base_company_admin.html.twig" %}
{% block card_title %}
- {% trans %}manufacturer.caption{% endtrans %}
+ {{ type_label_p(entity) }}
{% endblock %}
{% block edit_title %}
@@ -10,4 +10,4 @@
{% block new_title %}
{% trans %}manufacturer.new{% endtrans %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/templates/admin/measurement_unit_admin.html.twig b/templates/admin/measurement_unit_admin.html.twig
index 31748509..14df7364 100644
--- a/templates/admin/measurement_unit_admin.html.twig
+++ b/templates/admin/measurement_unit_admin.html.twig
@@ -1,7 +1,7 @@
{% extends "admin/base_admin.html.twig" %}
{% block card_title %}
- {% trans %}measurement_unit.caption{% endtrans %}
+ {{ type_label_p(entity) }}
{% endblock %}
{% block edit_title %}
diff --git a/templates/admin/part_custom_state_admin.html.twig b/templates/admin/part_custom_state_admin.html.twig
new file mode 100644
index 00000000..9d857646
--- /dev/null
+++ b/templates/admin/part_custom_state_admin.html.twig
@@ -0,0 +1,14 @@
+{% extends "admin/base_admin.html.twig" %}
+
+{% block card_title %}
+ {{ type_label_p(entity) }}
+{% endblock %}
+
+{% block edit_title %}
+ {% trans %}part_custom_state.edit{% endtrans %}: {{ entity.name }}
+{% endblock %}
+
+{% block new_title %}
+ {% trans %}part_custom_state.new{% endtrans %}
+{% endblock %}
+
diff --git a/templates/admin/project_admin.html.twig b/templates/admin/project_admin.html.twig
index 1a995069..d199b63c 100644
--- a/templates/admin/project_admin.html.twig
+++ b/templates/admin/project_admin.html.twig
@@ -3,7 +3,7 @@
{# @var entity App\Entity\ProjectSystem\Project #}
{% block card_title %}
- {% trans %}project.caption{% endtrans %}
+ {{ type_label_p(entity) }}
{% endblock %}
{% block edit_title %}
@@ -59,4 +59,4 @@
{% endif %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/templates/admin/storelocation_admin.html.twig b/templates/admin/storelocation_admin.html.twig
index c93339dc..b01ecc73 100644
--- a/templates/admin/storelocation_admin.html.twig
+++ b/templates/admin/storelocation_admin.html.twig
@@ -2,7 +2,7 @@
{% import "label_system/dropdown_macro.html.twig" as dropdown %}
{% block card_title %}
- {% trans %}storelocation.labelp{% endtrans %}
+ {{ type_label_p(entity) }}
{% endblock %}
{% block additional_controls %}
@@ -38,4 +38,4 @@
{% block new_title %}
{% trans %}storelocation.new{% endtrans %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/templates/admin/supplier_admin.html.twig b/templates/admin/supplier_admin.html.twig
index ce38a5ca..d0ca85aa 100644
--- a/templates/admin/supplier_admin.html.twig
+++ b/templates/admin/supplier_admin.html.twig
@@ -1,7 +1,7 @@
{% extends "admin/base_company_admin.html.twig" %}
{% block card_title %}
- {% trans %}supplier.caption{% endtrans %}
+ {{ type_label_p(entity) }}
{% endblock %}
{% block additional_panes %}
@@ -19,4 +19,4 @@
{% block new_title %}
{% trans %}supplier.new{% endtrans %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/templates/admin/user_admin.html.twig b/templates/admin/user_admin.html.twig
index 772b42d9..9b241e56 100644
--- a/templates/admin/user_admin.html.twig
+++ b/templates/admin/user_admin.html.twig
@@ -5,7 +5,7 @@
{# @var entity \App\Entity\UserSystem\User #}
{% block card_title %}
- {% trans %}user.edit.caption{% endtrans %}
+ {{ type_label_p(entity) }}
{% endblock %}
{% block comment %}{% endblock %}
@@ -111,4 +111,4 @@
{% block preview_picture %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/templates/components/tree_macros.html.twig b/templates/components/tree_macros.html.twig
index 366d42fe..aaa871ea 100644
--- a/templates/components/tree_macros.html.twig
+++ b/templates/components/tree_macros.html.twig
@@ -1,13 +1,15 @@
{% macro sidebar_dropdown() %}
+ {% set currentLocale = app.request.locale %}
+
{# Format is [mode, route, label, show_condition] #}
{% set data_sources = [
- ['categories', path('tree_category_root'), 'category.labelp', is_granted('@categories.read') and is_granted('@parts.read')],
- ['locations', path('tree_location_root'), 'storelocation.labelp', is_granted('@storelocations.read') and is_granted('@parts.read')],
- ['footprints', path('tree_footprint_root'), 'footprint.labelp', is_granted('@footprints.read') and is_granted('@parts.read')],
- ['manufacturers', path('tree_manufacturer_root'), 'manufacturer.labelp', is_granted('@manufacturers.read') and is_granted('@parts.read')],
- ['suppliers', path('tree_supplier_root'), 'supplier.labelp', is_granted('@suppliers.read') and is_granted('@parts.read')],
- ['projects', path('tree_device_root'), 'project.labelp', is_granted('@projects.read')],
- ['tools', path('tree_tools'), 'tools.label', true],
+ ['categories', path('tree_category_root'), '@category@@', is_granted('@categories.read') and is_granted('@parts.read')],
+ ['locations', path('tree_location_root'), '@storage_location@@', is_granted('@storelocations.read') and is_granted('@parts.read'), ],
+ ['footprints', path('tree_footprint_root'), '@footprint@@', is_granted('@footprints.read') and is_granted('@parts.read')],
+ ['manufacturers', path('tree_manufacturer_root'), '@manufacturer@@', is_granted('@manufacturers.read') and is_granted('@parts.read'), 'manufacturer'],
+ ['suppliers', path('tree_supplier_root'), '@supplier@@', is_granted('@suppliers.read') and is_granted('@parts.read'), 'supplier'],
+ ['projects', path('tree_device_root'), '@project@@', is_granted('@projects.read'), 'project'],
+ ['tools', path('tree_tools'), 'tools.label', true, 'tool'],
] %}
@@ -18,9 +20,20 @@
{% for source in data_sources %}
{% if source[3] %} {# show_condition #}
- {{ source[2] | trans }}
+
+ {% if source[2] starts with '@' %}
+ {% set label = type_label_p(source[2]|replace({'@': ''})) %}
+ {% else %}
+ {% set label = source[2]|trans %}
+ {% endif %}
+
+ {{ label }}
+
{% endif %}
{% endfor %}
{% endmacro %}
@@ -61,4 +74,4 @@
-{% endmacro %}
\ No newline at end of file
+{% endmacro %}
diff --git a/templates/form/synonyms_collection.html.twig b/templates/form/synonyms_collection.html.twig
new file mode 100644
index 00000000..ee69dffc
--- /dev/null
+++ b/templates/form/synonyms_collection.html.twig
@@ -0,0 +1,59 @@
+{% macro renderForm(child) %}
+
+ {% form_theme child "form/vertical_bootstrap_layout.html.twig" %}
+
+
{{ form_row(child.dataSource) }}
+
{{ form_row(child.locale) }}
+
{{ form_row(child.translation_singular) }}
+
{{ form_row(child.translation_plural) }}
+
+
+ {{ 'settings.synonyms.type_synonym.remove_entry'|trans }}
+
+
+
+
+{% endmacro %}
+
+{% block type_synonyms_collection_widget %}
+ {% set _attrs = attr|default({}) %}
+ {% set _attrs = _attrs|merge({
+ class: (_attrs.class|default('') ~ ' type_synonyms_collection-widget')|trim
+ }) %}
+
+ {% set has_proto = prototype is defined %}
+ {% if has_proto %}
+ {% set __proto %}
+ {{- _self.renderForm(prototype) -}}
+ {% endset %}
+ {% set _proto_html = __proto|e('html_attr') %}
+ {% set _proto_name = form.vars.prototype_name|default('__name__') %}
+ {% set _index = form|length %}
+ {% endif %}
+
+
+
+
{% trans%}settings.synonyms.type_synonym.type{% endtrans%}
+
{% trans%}settings.synonyms.type_synonym.language{% endtrans%}
+
{% trans%}settings.synonyms.type_synonym.translation_singular{% endtrans%}
+
{% trans%}settings.synonyms.type_synonym.translation_plural{% endtrans%}
+
+
+
+
+ {% for child in form %}
+ {{ _self.renderForm(child) }}
+ {% endfor %}
+
+
+ {{ 'settings.synonyms.type_synonym.add_entry'|trans }}
+
+
+{% endblock %}
diff --git a/templates/form/vertical_bootstrap_layout.html.twig b/templates/form/vertical_bootstrap_layout.html.twig
new file mode 100644
index 00000000..5f41d82e
--- /dev/null
+++ b/templates/form/vertical_bootstrap_layout.html.twig
@@ -0,0 +1,26 @@
+{% extends 'bootstrap_5_layout.html.twig' %}
+
+
+{%- block choice_widget_collapsed -%}
+ {# Only add the BS5 form-select class if we dont use bootstrap-selectpicker #}
+ {# {% if attr["data-controller"] is defined and attr["data-controller"] not in ["elements--selectpicker"] %}
+ {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-select')|trim}) -%}
+ {% else %}
+ {# If it is an selectpicker add form-control class to fill whole width
+ {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) -%}
+ {% endif %}
+ #}
+
+ {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-select')|trim}) -%}
+
+ {# If no data-controller was explictly defined add data-controller=elements--select #}
+ {% if attr["data-controller"] is not defined %}
+ {%- set attr = attr|merge({"data-controller": "elements--select"}) -%}
+
+ {% if attr["data-empty-message"] is not defined %}
+ {%- set attr = attr|merge({"data-empty-message": ("selectpicker.nothing_selected"|trans)}) -%}
+ {% endif %}
+ {% endif %}
+
+ {{- block("choice_widget_collapsed", "bootstrap_base_layout.html.twig") -}}
+{%- endblock choice_widget_collapsed -%}
diff --git a/templates/parts/edit/_advanced.html.twig b/templates/parts/edit/_advanced.html.twig
index 12b546ab..b0f1ff86 100644
--- a/templates/parts/edit/_advanced.html.twig
+++ b/templates/parts/edit/_advanced.html.twig
@@ -1,5 +1,16 @@
{{ form_row(form.needsReview) }}
{{ form_row(form.favorite) }}
{{ form_row(form.mass) }}
-{{ form_row(form.ipn) }}
-{{ form_row(form.partUnit) }}
\ No newline at end of file
+
+ {{ form_row(form.ipn) }}
+
+{{ form_row(form.partUnit) }}
+{{ form_row(form.partCustomState) }}
\ No newline at end of file
diff --git a/templates/parts/info/_sidebar.html.twig b/templates/parts/info/_sidebar.html.twig
index 28eada04..0c353d8f 100644
--- a/templates/parts/info/_sidebar.html.twig
+++ b/templates/parts/info/_sidebar.html.twig
@@ -36,6 +36,19 @@
{% endif %}
+{% if part.partCustomState is not null %}
+
+
+ {{ part.partCustomState.name }}
+
+ {% if part.partCustomState is not null and part.partCustomState.masterPictureAttachment and attachment_manager.fileExisting(part.partCustomState.masterPictureAttachment) %}
+
+
+ {% endif %}
+
+
+{% endif %}
+
{# Favorite Status tag #}
{% if part.favorite %}
@@ -79,4 +92,4 @@
-{% endif %}
\ No newline at end of file
+{% endif %}
diff --git a/templates/parts/lists/_filter.html.twig b/templates/parts/lists/_filter.html.twig
index ba9168d1..2fb5bff2 100644
--- a/templates/parts/lists/_filter.html.twig
+++ b/templates/parts/lists/_filter.html.twig
@@ -61,6 +61,7 @@
{{ form_row(filterForm.favorite) }}
{{ form_row(filterForm.needsReview) }}
{{ form_row(filterForm.measurementUnit) }}
+ {{ form_row(filterForm.partCustomState) }}
{{ form_row(filterForm.mass) }}
{{ form_row(filterForm.dbId) }}
{{ form_row(filterForm.ipn) }}
diff --git a/templates/settings/settings.html.twig b/templates/settings/settings.html.twig
index 5ddbd900..96e0f209 100644
--- a/templates/settings/settings.html.twig
+++ b/templates/settings/settings.html.twig
@@ -36,7 +36,7 @@
{% for section_widget in tab_widget %}
{% set settings_object = section_widget.vars.value %}
- {% if section_widget.vars.compound ?? false %}
+ {% if section_widget.vars.embedded_settings_metadata is defined %} {# Check if we have nested embedded settings or not #}
diff --git a/tests/API/Endpoints/PartCustomStateEndpointTest.php b/tests/API/Endpoints/PartCustomStateEndpointTest.php
new file mode 100644
index 00000000..ac353d9c
--- /dev/null
+++ b/tests/API/Endpoints/PartCustomStateEndpointTest.php
@@ -0,0 +1,69 @@
+.
+ */
+
+declare(strict_types=1);
+
+
+namespace App\Tests\API\Endpoints;
+
+class PartCustomStateEndpointTest extends CrudEndpointTestCase
+{
+
+ protected function getBasePath(): string
+ {
+ return '/api/part_custom_states';
+ }
+
+ public function testGetCollection(): void
+ {
+ $this->_testGetCollection();
+ self::assertJsonContains([
+ 'hydra:totalItems' => 7,
+ ]);
+ }
+
+ public function testGetItem(): void
+ {
+ $this->_testGetItem(1);
+ $this->_testGetItem(2);
+ $this->_testGetItem(3);
+ }
+
+ public function testCreateItem(): void
+ {
+ $this->_testPostItem([
+ 'name' => 'Test API',
+ 'parent' => '/api/part_custom_states/1',
+ ]);
+ }
+
+ public function testUpdateItem(): void
+ {
+ $this->_testPatchItem(5, [
+ 'name' => 'Updated',
+ 'parent' => '/api/part_custom_states/2',
+ ]);
+ }
+
+ public function testDeleteItem(): void
+ {
+ $this->_testDeleteItem(4);
+ }
+}
diff --git a/tests/Controller/AdminPages/PartCustomStateControllerTest.php b/tests/Controller/AdminPages/PartCustomStateControllerTest.php
new file mode 100644
index 00000000..3e87dfe2
--- /dev/null
+++ b/tests/Controller/AdminPages/PartCustomStateControllerTest.php
@@ -0,0 +1,34 @@
+.
+ */
+
+declare(strict_types=1);
+
+namespace App\Tests\Controller\AdminPages;
+
+use App\Entity\Parts\PartCustomState;
+use PHPUnit\Framework\Attributes\Group;
+
+#[Group('slow')]
+#[Group('DB')]
+class PartCustomStateControllerTest extends AbstractAdminController
+{
+ protected static string $base_path = '/en/part_custom_state';
+ protected static string $entity_class = PartCustomState::class;
+}
diff --git a/tests/Entity/Attachments/AttachmentTest.php b/tests/Entity/Attachments/AttachmentTest.php
index 00a68d7d..35222d63 100644
--- a/tests/Entity/Attachments/AttachmentTest.php
+++ b/tests/Entity/Attachments/AttachmentTest.php
@@ -29,6 +29,7 @@ use App\Entity\Attachments\AttachmentType;
use App\Entity\Attachments\AttachmentTypeAttachment;
use App\Entity\Attachments\CategoryAttachment;
use App\Entity\Attachments\CurrencyAttachment;
+use App\Entity\Attachments\PartCustomStateAttachment;
use App\Entity\Attachments\ProjectAttachment;
use App\Entity\Attachments\FootprintAttachment;
use App\Entity\Attachments\GroupAttachment;
@@ -38,6 +39,7 @@ use App\Entity\Attachments\PartAttachment;
use App\Entity\Attachments\StorageLocationAttachment;
use App\Entity\Attachments\SupplierAttachment;
use App\Entity\Attachments\UserAttachment;
+use App\Entity\Parts\PartCustomState;
use App\Entity\ProjectSystem\Project;
use App\Entity\Parts\Category;
use App\Entity\Parts\Footprint;
@@ -86,6 +88,7 @@ class AttachmentTest extends TestCase
yield [ManufacturerAttachment::class, Manufacturer::class];
yield [MeasurementUnitAttachment::class, MeasurementUnit::class];
yield [PartAttachment::class, Part::class];
+ yield [PartCustomStateAttachment::class, PartCustomState::class];
yield [StorageLocationAttachment::class, StorageLocation::class];
yield [SupplierAttachment::class, Supplier::class];
yield [UserAttachment::class, User::class];
diff --git a/tests/EventListener/RegisterSynonymsAsTranslationParametersTest.php b/tests/EventListener/RegisterSynonymsAsTranslationParametersTest.php
new file mode 100644
index 00000000..d08edecb
--- /dev/null
+++ b/tests/EventListener/RegisterSynonymsAsTranslationParametersTest.php
@@ -0,0 +1,49 @@
+.
+ */
+
+namespace App\Tests\EventListener;
+
+use App\EventListener\RegisterSynonymsAsTranslationParametersListener;
+use PHPUnit\Framework\TestCase;
+use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
+
+class RegisterSynonymsAsTranslationParametersTest extends KernelTestCase
+{
+
+ private RegisterSynonymsAsTranslationParametersListener $listener;
+
+ public function setUp(): void
+ {
+ self::bootKernel();
+ $this->listener = self::getContainer()->get(RegisterSynonymsAsTranslationParametersListener::class);
+ }
+
+ public function testGetSynonymPlaceholders(): void
+ {
+ $placeholders = $this->listener->getSynonymPlaceholders();
+
+ $this->assertIsArray($placeholders);
+ $this->assertSame('Part', $placeholders['{part}']);
+ $this->assertSame('Parts', $placeholders['{{part}}']);
+ //Lowercase versions:
+ $this->assertSame('part', $placeholders['[part]']);
+ $this->assertSame('parts', $placeholders['[[part]]']);
+ }
+}
diff --git a/tests/Repository/PartRepositoryTest.php b/tests/Repository/PartRepositoryTest.php
new file mode 100644
index 00000000..68b75abb
--- /dev/null
+++ b/tests/Repository/PartRepositoryTest.php
@@ -0,0 +1,297 @@
+.
+ */
+
+declare(strict_types=1);
+
+namespace App\Tests\Repository;
+
+use App\Entity\Parts\Category;
+use App\Entity\Parts\Part;
+use App\Settings\MiscSettings\IpnSuggestSettings;
+use Doctrine\ORM\EntityManagerInterface;
+use Doctrine\ORM\Mapping\ClassMetadata;
+use PHPUnit\Framework\TestCase;
+use Doctrine\ORM\Query;
+use Doctrine\ORM\QueryBuilder;
+use Symfony\Contracts\Translation\TranslatorInterface;
+use App\Repository\PartRepository;
+
+final class PartRepositoryTest extends TestCase
+{
+ public function test_autocompleteSearch_builds_expected_query_without_db(): void
+ {
+ $qb = $this->getMockBuilder(QueryBuilder::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods([
+ 'select', 'leftJoin', 'where', 'orWhere',
+ 'setParameter', 'setMaxResults', 'orderBy', 'getQuery'
+ ])->getMock();
+
+ $qb->expects(self::once())->method('select')->with('part')->willReturnSelf();
+
+ $qb->expects(self::exactly(2))->method('leftJoin')->with($this->anything(), $this->anything())->willReturnSelf();
+
+ $qb->expects(self::atLeastOnce())->method('where')->with($this->anything())->willReturnSelf();
+ $qb->method('orWhere')->with($this->anything())->willReturnSelf();
+
+ $searchQuery = 'res';
+ $qb->expects(self::once())->method('setParameter')->with('query', '%'.$searchQuery.'%')->willReturnSelf();
+ $qb->expects(self::once())->method('setMaxResults')->with(10)->willReturnSelf();
+ $qb->expects(self::once())->method('orderBy')->with('NATSORT(part.name)', 'ASC')->willReturnSelf();
+
+ $emMock = $this->createMock(EntityManagerInterface::class);
+ $classMetadata = new ClassMetadata(Part::class);
+ $emMock->method('getClassMetadata')->with(Part::class)->willReturn($classMetadata);
+
+ $translatorMock = $this->createMock(TranslatorInterface::class);
+ $ipnSuggestSettings = $this->createMock(IpnSuggestSettings::class);
+
+ $repo = $this->getMockBuilder(PartRepository::class)
+ ->setConstructorArgs([$emMock, $translatorMock, $ipnSuggestSettings])
+ ->onlyMethods(['createQueryBuilder'])
+ ->getMock();
+
+ $repo->expects(self::once())
+ ->method('createQueryBuilder')
+ ->with('part')
+ ->willReturn($qb);
+
+ $part = new Part(); // create found part, because it is not saved in DB
+ $part->setName('Resistor');
+
+ $queryMock = $this->getMockBuilder(Query::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods(['getResult'])
+ ->getMock();
+ $queryMock->expects(self::once())->method('getResult')->willReturn([$part]);
+
+ $qb->method('getQuery')->willReturn($queryMock);
+
+ $result = $repo->autocompleteSearch($searchQuery, 10);
+
+ // Check one part found and returned
+ self::assertIsArray($result);
+ self::assertCount(1, $result);
+ self::assertSame($part, $result[0]);
+ }
+
+ public function test_autoCompleteIpn_with_unsaved_part_and_category_without_part_description(): void
+ {
+ $qb = $this->getMockBuilder(QueryBuilder::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods([
+ 'select', 'leftJoin', 'where', 'andWhere', 'orWhere',
+ 'setParameter', 'setMaxResults', 'orderBy', 'getQuery'
+ ])->getMock();
+
+ $qb->method('select')->willReturnSelf();
+ $qb->method('leftJoin')->willReturnSelf();
+ $qb->method('where')->willReturnSelf();
+ $qb->method('andWhere')->willReturnSelf();
+ $qb->method('orWhere')->willReturnSelf();
+ $qb->method('setParameter')->willReturnSelf();
+ $qb->method('setMaxResults')->willReturnSelf();
+ $qb->method('orderBy')->willReturnSelf();
+
+ $emMock = $this->createMock(EntityManagerInterface::class);
+ $classMetadata = new ClassMetadata(Part::class);
+ $emMock->method('getClassMetadata')->with(Part::class)->willReturn($classMetadata);
+
+ $translatorMock = $this->createMock(TranslatorInterface::class);
+ $translatorMock->method('trans')
+ ->willReturnCallback(static function (string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string {
+ return $id;
+ });
+
+ $ipnSuggestSettings = $this->createMock(IpnSuggestSettings::class);
+
+ $ipnSuggestSettings->suggestPartDigits = 4;
+ $ipnSuggestSettings->useDuplicateDescription = false;
+
+ $repo = $this->getMockBuilder(PartRepository::class)
+ ->setConstructorArgs([$emMock, $translatorMock, $ipnSuggestSettings])
+ ->onlyMethods(['createQueryBuilder'])
+ ->getMock();
+
+ $repo->expects(self::atLeastOnce())
+ ->method('createQueryBuilder')
+ ->with('part')
+ ->willReturn($qb);
+
+ $queryMock = $this->getMockBuilder(Query::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods(['getResult'])
+ ->getMock();
+
+ $categoryParent = new Category();
+ $categoryParent->setName('Passive components');
+ $categoryParent->setPartIpnPrefix('PCOM');
+
+ $categoryChild = new Category();
+ $categoryChild->setName('Resistors');
+ $categoryChild->setPartIpnPrefix('RES');
+ $categoryChild->setParent($categoryParent);
+
+ $partForSuggestGeneration = new Part(); // create found part, because it is not saved in DB
+ $partForSuggestGeneration->setIpn('RES-0001');
+ $partForSuggestGeneration->setCategory($categoryChild);
+
+ $queryMock->method('getResult')->willReturn([$partForSuggestGeneration]);
+ $qb->method('getQuery')->willReturn($queryMock);
+ $suggestions = $repo->autoCompleteIpn($partForSuggestGeneration, '', 4);
+
+ // Check structure available
+ self::assertIsArray($suggestions);
+ self::assertArrayHasKey('commonPrefixes', $suggestions);
+ self::assertArrayHasKey('prefixesPartIncrement', $suggestions);
+ self::assertNotEmpty($suggestions['commonPrefixes']);
+ self::assertNotEmpty($suggestions['prefixesPartIncrement']);
+
+ // Check expected values
+ self::assertSame('RES-', $suggestions['commonPrefixes'][0]['title']);
+ self::assertSame('part.edit.tab.advanced.ipn.prefix.direct_category', $suggestions['commonPrefixes'][0]['description']);
+ self::assertSame('PCOM-RES-', $suggestions['commonPrefixes'][1]['title']);
+ self::assertSame('part.edit.tab.advanced.ipn.prefix.hierarchical.no_increment', $suggestions['commonPrefixes'][1]['description']);
+
+ self::assertSame('RES-0002', $suggestions['prefixesPartIncrement'][0]['title']); // next possible free increment for given part category
+ self::assertSame('part.edit.tab.advanced.ipn.prefix.direct_category.increment', $suggestions['prefixesPartIncrement'][0]['description']);
+ self::assertSame('PCOM-RES-0002', $suggestions['prefixesPartIncrement'][1]['title']); // next possible free increment for given part category
+ self::assertSame('part.edit.tab.advanced.ipn.prefix.hierarchical.increment', $suggestions['prefixesPartIncrement'][1]['description']);
+ }
+
+ public function test_autoCompleteIpn_with_unsaved_part_and_category_with_part_description(): void
+ {
+ $qb = $this->getMockBuilder(QueryBuilder::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods([
+ 'select', 'leftJoin', 'where', 'andWhere', 'orWhere',
+ 'setParameter', 'setMaxResults', 'orderBy', 'getQuery'
+ ])->getMock();
+
+ $qb->method('select')->willReturnSelf();
+ $qb->method('leftJoin')->willReturnSelf();
+ $qb->method('where')->willReturnSelf();
+ $qb->method('andWhere')->willReturnSelf();
+ $qb->method('orWhere')->willReturnSelf();
+ $qb->method('setParameter')->willReturnSelf();
+ $qb->method('setMaxResults')->willReturnSelf();
+ $qb->method('orderBy')->willReturnSelf();
+
+ $emMock = $this->createMock(EntityManagerInterface::class);
+ $classMetadata = new ClassMetadata(Part::class);
+ $emMock->method('getClassMetadata')->with(Part::class)->willReturn($classMetadata);
+
+ $translatorMock = $this->createMock(TranslatorInterface::class);
+ $translatorMock->method('trans')
+ ->willReturnCallback(static function (string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string {
+ return $id;
+ });
+
+ $ipnSuggestSettings = $this->createMock(IpnSuggestSettings::class);
+
+ $ipnSuggestSettings->suggestPartDigits = 4;
+ $ipnSuggestSettings->useDuplicateDescription = false;
+
+ $repo = $this->getMockBuilder(PartRepository::class)
+ ->setConstructorArgs([$emMock, $translatorMock, $ipnSuggestSettings])
+ ->onlyMethods(['createQueryBuilder'])
+ ->getMock();
+
+ $repo->expects(self::atLeastOnce())
+ ->method('createQueryBuilder')
+ ->with('part')
+ ->willReturn($qb);
+
+ $queryMock = $this->getMockBuilder(Query::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods(['getResult'])
+ ->getMock();
+
+ $categoryParent = new Category();
+ $categoryParent->setName('Passive components');
+ $categoryParent->setPartIpnPrefix('PCOM');
+
+ $categoryChild = new Category();
+ $categoryChild->setName('Resistors');
+ $categoryChild->setPartIpnPrefix('RES');
+ $categoryChild->setParent($categoryParent);
+
+ $partForSuggestGeneration = new Part(); // create found part, because it is not saved in DB
+ $partForSuggestGeneration->setCategory($categoryChild);
+ $partForSuggestGeneration->setIpn('1810-1679_1');
+ $partForSuggestGeneration->setDescription('NETWORK-RESISTOR 4 0 OHM +5PCT 0.063W TKF SMT');
+
+ $queryMock->method('getResult')->willReturn([$partForSuggestGeneration]);
+ $qb->method('getQuery')->willReturn($queryMock);
+ $suggestions = $repo->autoCompleteIpn($partForSuggestGeneration, 'NETWORK-RESISTOR 4 0 OHM +5PCT 0.063W TKF SMT', 4);
+
+ // Check structure available
+ self::assertIsArray($suggestions);
+ self::assertArrayHasKey('commonPrefixes', $suggestions);
+ self::assertArrayHasKey('prefixesPartIncrement', $suggestions);
+ self::assertNotEmpty($suggestions['commonPrefixes']);
+ self::assertCount(2, $suggestions['commonPrefixes']);
+ self::assertNotEmpty($suggestions['prefixesPartIncrement']);
+ self::assertCount(2, $suggestions['prefixesPartIncrement']);
+
+ // Check expected values without any increment, for user to decide
+ self::assertSame('RES-', $suggestions['commonPrefixes'][0]['title']);
+ self::assertSame('part.edit.tab.advanced.ipn.prefix.direct_category', $suggestions['commonPrefixes'][0]['description']);
+ self::assertSame('PCOM-RES-', $suggestions['commonPrefixes'][1]['title']);
+ self::assertSame('part.edit.tab.advanced.ipn.prefix.hierarchical.no_increment', $suggestions['commonPrefixes'][1]['description']);
+
+ // Check expected values with next possible increment at category level
+ self::assertSame('RES-0001', $suggestions['prefixesPartIncrement'][0]['title']); // next possible free increment for given part category
+ self::assertSame('part.edit.tab.advanced.ipn.prefix.direct_category.increment', $suggestions['prefixesPartIncrement'][0]['description']);
+ self::assertSame('PCOM-RES-0001', $suggestions['prefixesPartIncrement'][1]['title']); // next possible free increment for given part category
+ self::assertSame('part.edit.tab.advanced.ipn.prefix.hierarchical.increment', $suggestions['prefixesPartIncrement'][1]['description']);
+
+ $ipnSuggestSettings->useDuplicateDescription = true;
+
+ $suggestionsWithSameDescription = $repo->autoCompleteIpn($partForSuggestGeneration, 'NETWORK-RESISTOR 4 0 OHM +5PCT 0.063W TKF SMT', 4);
+
+ // Check structure available
+ self::assertIsArray($suggestionsWithSameDescription);
+ self::assertArrayHasKey('commonPrefixes', $suggestionsWithSameDescription);
+ self::assertArrayHasKey('prefixesPartIncrement', $suggestionsWithSameDescription);
+ self::assertNotEmpty($suggestionsWithSameDescription['commonPrefixes']);
+ self::assertCount(2, $suggestionsWithSameDescription['commonPrefixes']);
+ self::assertNotEmpty($suggestionsWithSameDescription['prefixesPartIncrement']);
+ self::assertCount(4, $suggestionsWithSameDescription['prefixesPartIncrement']);
+
+ // Check expected values without any increment, for user to decide
+ self::assertSame('RES-', $suggestionsWithSameDescription['commonPrefixes'][0]['title']);
+ self::assertSame('part.edit.tab.advanced.ipn.prefix.direct_category', $suggestionsWithSameDescription['commonPrefixes'][0]['description']);
+ self::assertSame('PCOM-RES-', $suggestionsWithSameDescription['commonPrefixes'][1]['title']);
+ self::assertSame('part.edit.tab.advanced.ipn.prefix.hierarchical.no_increment', $suggestionsWithSameDescription['commonPrefixes'][1]['description']);
+
+ // Check expected values with next possible increment at part description level
+ self::assertSame('1810-1679_1', $suggestionsWithSameDescription['prefixesPartIncrement'][0]['title']); // current given value
+ self::assertSame('part.edit.tab.advanced.ipn.prefix.description.current-increment', $suggestionsWithSameDescription['prefixesPartIncrement'][0]['description']);
+ self::assertSame('1810-1679_2', $suggestionsWithSameDescription['prefixesPartIncrement'][1]['title']); // next possible value
+ self::assertSame('part.edit.tab.advanced.ipn.prefix.description.increment', $suggestionsWithSameDescription['prefixesPartIncrement'][1]['description']);
+
+ // Check expected values with next possible increment at category level
+ self::assertSame('RES-0001', $suggestionsWithSameDescription['prefixesPartIncrement'][2]['title']); // next possible free increment for given part category
+ self::assertSame('part.edit.tab.advanced.ipn.prefix.direct_category.increment', $suggestionsWithSameDescription['prefixesPartIncrement'][2]['description']);
+ self::assertSame('PCOM-RES-0001', $suggestionsWithSameDescription['prefixesPartIncrement'][3]['title']); // next possible free increment for given part category
+ self::assertSame('part.edit.tab.advanced.ipn.prefix.hierarchical.increment', $suggestionsWithSameDescription['prefixesPartIncrement'][3]['description']);
+ }
+}
diff --git a/tests/Services/ElementTypeNameGeneratorTest.php b/tests/Services/ElementTypeNameGeneratorTest.php
index f99b0676..8739dd06 100644
--- a/tests/Services/ElementTypeNameGeneratorTest.php
+++ b/tests/Services/ElementTypeNameGeneratorTest.php
@@ -30,20 +30,27 @@ use App\Entity\Parts\Category;
use App\Entity\Parts\Part;
use App\Exceptions\EntityNotSupportedException;
use App\Services\ElementTypeNameGenerator;
+use App\Services\ElementTypes;
use App\Services\Formatters\AmountFormatter;
+use App\Settings\SynonymSettings;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class ElementTypeNameGeneratorTest extends WebTestCase
{
- /**
- * @var AmountFormatter
- */
- protected $service;
+ protected ElementTypeNameGenerator $service;
+ private SynonymSettings $synonymSettings;
protected function setUp(): void
{
//Get an service instance.
$this->service = self::getContainer()->get(ElementTypeNameGenerator::class);
+ $this->synonymSettings = self::getContainer()->get(SynonymSettings::class);
+ }
+
+ protected function tearDown(): void
+ {
+ //Clean up synonym settings
+ $this->synonymSettings->typeSynonyms = [];
}
public function testGetLocalizedTypeNameCombination(): void
@@ -84,4 +91,30 @@ class ElementTypeNameGeneratorTest extends WebTestCase
}
});
}
+
+ public function testTypeLabel(): void
+ {
+ //If no synonym is defined, the default label should be used
+ $this->assertSame('Part', $this->service->typeLabel(Part::class));
+ $this->assertSame('Part', $this->service->typeLabel(new Part()));
+ $this->assertSame('Part', $this->service->typeLabel(ElementTypes::PART));
+ $this->assertSame('Part', $this->service->typeLabel('part'));
+
+ //Define a synonym for parts in english
+ $this->synonymSettings->setSynonymForType(ElementTypes::PART, 'en', 'Singular', 'Plurals');
+ $this->assertSame('Singular', $this->service->typeLabel(Part::class));
+ }
+
+ public function testTypeLabelPlural(): void
+ {
+ //If no synonym is defined, the default label should be used
+ $this->assertSame('Parts', $this->service->typeLabelPlural(Part::class));
+ $this->assertSame('Parts', $this->service->typeLabelPlural(new Part()));
+ $this->assertSame('Parts', $this->service->typeLabelPlural(ElementTypes::PART));
+ $this->assertSame('Parts', $this->service->typeLabelPlural('part'));
+
+ //Define a synonym for parts in english
+ $this->synonymSettings->setSynonymForType(ElementTypes::PART, 'en', 'Singular', 'Plurals');
+ $this->assertSame('Plurals', $this->service->typeLabelPlural(Part::class));
+ }
}
diff --git a/tests/Services/ElementTypesTest.php b/tests/Services/ElementTypesTest.php
new file mode 100644
index 00000000..d4fa77ff
--- /dev/null
+++ b/tests/Services/ElementTypesTest.php
@@ -0,0 +1,79 @@
+.
+ */
+
+namespace App\Tests\Services;
+
+use App\Entity\Parameters\CategoryParameter;
+use App\Entity\Parts\Category;
+use App\Exceptions\EntityNotSupportedException;
+use App\Services\ElementTypes;
+use PHPUnit\Framework\TestCase;
+
+class ElementTypesTest extends TestCase
+{
+
+ public function testFromClass(): void
+ {
+ $this->assertSame(ElementTypes::CATEGORY, ElementTypes::fromClass(Category::class));
+ $this->assertSame(ElementTypes::CATEGORY, ElementTypes::fromClass(new Category()));
+
+ //Should also work with subclasses
+ $this->assertSame(ElementTypes::PARAMETER, ElementTypes::fromClass(CategoryParameter::class));
+ $this->assertSame(ElementTypes::PARAMETER, ElementTypes::fromClass(new CategoryParameter()));
+ }
+
+ public function testFromClassNotExisting(): void
+ {
+ $this->expectException(EntityNotSupportedException::class);
+ ElementTypes::fromClass(\LogicException::class);
+ }
+
+ public function testFromValue(): void
+ {
+ //By enum value
+ $this->assertSame(ElementTypes::CATEGORY, ElementTypes::fromValue('category'));
+ $this->assertSame(ElementTypes::ATTACHMENT, ElementTypes::fromValue('attachment'));
+
+ //From enum instance
+ $this->assertSame(ElementTypes::CATEGORY, ElementTypes::fromValue(ElementTypes::CATEGORY));
+
+ //From class string
+ $this->assertSame(ElementTypes::CATEGORY, ElementTypes::fromValue(Category::class));
+ $this->assertSame(ElementTypes::PARAMETER, ElementTypes::fromValue(CategoryParameter::class));
+
+ //From class instance
+ $this->assertSame(ElementTypes::CATEGORY, ElementTypes::fromValue(new Category()));
+ $this->assertSame(ElementTypes::PARAMETER, ElementTypes::fromValue(new CategoryParameter()));
+ }
+
+ public function testGetDefaultLabelKey(): void
+ {
+ $this->assertSame('category.label', ElementTypes::CATEGORY->getDefaultLabelKey());
+ $this->assertSame('attachment.label', ElementTypes::ATTACHMENT->getDefaultLabelKey());
+ }
+
+ public function testGetDefaultPluralLabelKey(): void
+ {
+ $this->assertSame('category.labelp', ElementTypes::CATEGORY->getDefaultPluralLabelKey());
+ $this->assertSame('attachment.labelp', ElementTypes::ATTACHMENT->getDefaultPluralLabelKey());
+ }
+
+
+}
diff --git a/tests/Services/EntityMergers/Mergers/PartMergerTest.php b/tests/Services/EntityMergers/Mergers/PartMergerTest.php
index 56c7712e..7db4ddd6 100644
--- a/tests/Services/EntityMergers/Mergers/PartMergerTest.php
+++ b/tests/Services/EntityMergers/Mergers/PartMergerTest.php
@@ -29,6 +29,7 @@ use App\Entity\Parts\Manufacturer;
use App\Entity\Parts\MeasurementUnit;
use App\Entity\Parts\Part;
use App\Entity\Parts\PartAssociation;
+use App\Entity\Parts\PartCustomState;
use App\Entity\Parts\PartLot;
use App\Entity\PriceInformations\Orderdetail;
use App\Services\EntityMergers\Mergers\PartMerger;
@@ -54,6 +55,7 @@ class PartMergerTest extends KernelTestCase
$manufacturer1 = new Manufacturer();
$manufacturer2 = new Manufacturer();
$unit = new MeasurementUnit();
+ $customState = new PartCustomState();
$part1 = (new Part())
->setCategory($category)
@@ -62,7 +64,8 @@ class PartMergerTest extends KernelTestCase
$part2 = (new Part())
->setFootprint($footprint)
->setManufacturer($manufacturer2)
- ->setPartUnit($unit);
+ ->setPartUnit($unit)
+ ->setPartCustomState($customState);
$merged = $this->merger->merge($part1, $part2);
$this->assertSame($merged, $part1);
@@ -70,6 +73,7 @@ class PartMergerTest extends KernelTestCase
$this->assertSame($footprint, $merged->getFootprint());
$this->assertSame($manufacturer1, $merged->getManufacturer());
$this->assertSame($unit, $merged->getPartUnit());
+ $this->assertSame($customState, $merged->getPartCustomState());
}
public function testMergeOfTags(): void
diff --git a/tests/Settings/SynonymSettingsTest.php b/tests/Settings/SynonymSettingsTest.php
new file mode 100644
index 00000000..2d1407ac
--- /dev/null
+++ b/tests/Settings/SynonymSettingsTest.php
@@ -0,0 +1,76 @@
+.
+ */
+
+namespace App\Tests\Settings;
+
+use App\Services\ElementTypes;
+use App\Settings\SynonymSettings;
+use App\Tests\SettingsTestHelper;
+use PHPUnit\Framework\TestCase;
+
+class SynonymSettingsTest extends TestCase
+{
+
+ public function testGetSingularSynonymForType(): void
+ {
+ $settings = SettingsTestHelper::createSettingsDummy(SynonymSettings::class);
+ $settings->typeSynonyms['category'] = [
+ 'en' => ['singular' => 'Category', 'plural' => 'Categories'],
+ 'de' => ['singular' => 'Kategorie', 'plural' => 'Kategorien'],
+ ];
+
+ $this->assertEquals('Category', $settings->getSingularSynonymForType(ElementTypes::CATEGORY, 'en'));
+ $this->assertEquals('Kategorie', $settings->getSingularSynonymForType(ElementTypes::CATEGORY, 'de'));
+
+ //If no synonym is defined, it should return null
+ $this->assertNull($settings->getSingularSynonymForType(ElementTypes::MANUFACTURER, 'en'));
+ }
+
+ public function testIsSynonymDefinedForType(): void
+ {
+ $settings = SettingsTestHelper::createSettingsDummy(SynonymSettings::class);
+ $settings->typeSynonyms['category'] = [
+ 'en' => ['singular' => 'Category', 'plural' => 'Categories'],
+ 'de' => ['singular' => 'Kategorie', 'plural' => 'Kategorien'],
+ ];
+
+ $settings->typeSynonyms['supplier'] = [];
+
+ $this->assertTrue($settings->isSynonymDefinedForType(ElementTypes::CATEGORY));
+ $this->assertFalse($settings->isSynonymDefinedForType(ElementTypes::FOOTPRINT));
+ $this->assertFalse($settings->isSynonymDefinedForType(ElementTypes::SUPPLIER));
+ }
+
+ public function testGetPluralSynonymForType(): void
+ {
+ $settings = SettingsTestHelper::createSettingsDummy(SynonymSettings::class);
+ $settings->typeSynonyms['category'] = [
+ 'en' => ['singular' => 'Category', 'plural' => 'Categories'],
+ 'de' => ['singular' => 'Kategorie',],
+ ];
+
+ $this->assertEquals('Categories', $settings->getPluralSynonymForType(ElementTypes::CATEGORY, 'en'));
+ //Fallback to singular if no plural is defined
+ $this->assertEquals('Kategorie', $settings->getPluralSynonymForType(ElementTypes::CATEGORY, 'de'));
+
+ //If no synonym is defined, it should return null
+ $this->assertNull($settings->getPluralSynonymForType(ElementTypes::MANUFACTURER, 'en'));
+ }
+}
diff --git a/tests/Twig/EntityExtensionTest.php b/tests/Twig/EntityExtensionTest.php
index 3adb9ad2..18fe970b 100644
--- a/tests/Twig/EntityExtensionTest.php
+++ b/tests/Twig/EntityExtensionTest.php
@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace App\Tests\Twig;
use App\Entity\Attachments\PartAttachment;
+use App\Entity\Parts\PartCustomState;
use App\Entity\ProjectSystem\Project;
use App\Entity\LabelSystem\LabelProfile;
use App\Entity\Parts\Category;
@@ -67,6 +68,7 @@ class EntityExtensionTest extends WebTestCase
$this->assertSame('currency', $this->service->getEntityType(new Currency()));
$this->assertSame('measurement_unit', $this->service->getEntityType(new MeasurementUnit()));
$this->assertSame('label_profile', $this->service->getEntityType(new LabelProfile()));
+ $this->assertSame('part_custom_state', $this->service->getEntityType(new PartCustomState()));
}
}
diff --git a/tests/assets/partkeepr_import_test.xml b/tests/assets/partkeepr_import_test.xml
index 4fa497e2..28e6f099 100644
--- a/tests/assets/partkeepr_import_test.xml
+++ b/tests/assets/partkeepr_import_test.xml
@@ -7437,11 +7437,13 @@
+
+
diff --git a/translations/messages.cs.xlf b/translations/messages.cs.xlf
index 1f234450..cd572dae 100644
--- a/translations/messages.cs.xlf
+++ b/translations/messages.cs.xlf
@@ -548,6 +548,12 @@
Měrné jednotky
+
+
+ part_custom_state.caption
+ Vlastní stav komponenty
+
+
Part-DB1\templates\AdminPages\StorelocationAdmin.html.twig:5
@@ -1842,6 +1848,66 @@ Související prvky budou přesunuty nahoru.
Pokročilé
+
+
+ part.edit.tab.advanced.ipn.commonSectionHeader
+ Návrhy bez přírůstku části
+
+
+
+
+ part.edit.tab.advanced.ipn.partIncrementHeader
+ Návrhy s číselnými přírůstky částí
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.description.current-increment
+ Aktuální specifikace IPN pro součást
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.description.increment
+ Další možná specifikace IPN na základě identického popisu součásti
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix_empty.direct_category
+ IPN předpona přímé kategorie je prázdná, zadejte ji v kategorii „%name%“
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.direct_category
+ IPN prefix přímé kategorie
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.direct_category.increment
+ IPN prefix přímé kategorie a specifického přírůstku pro část
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.hierarchical.no_increment
+ IPN prefixy s hierarchickým pořadím kategorií rodičovských prefixů
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.hierarchical.increment
+ IPN prefixy s hierarchickým pořadím kategorií rodičovských prefixů a specifickým přírůstkem pro část
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.not_saved
+ Nejprve vytvořte součást a přiřaďte ji do kategorie: s dostupnými kategoriemi a jejich vlastními IPN prefixy lze automaticky navrhnout IPN označení pro danou součást
+
+
Part-DB1\templates\Parts\edit\edit_part_info.html.twig:40
@@ -4831,6 +4897,12 @@ Pokud jste to provedli nesprávně nebo pokud počítač již není důvěryhodn
Měrné jednotky
+
+
+ part.table.partCustomState
+ Vlastní stav součásti
+
+
Part-DB1\src\DataTables\PartsDataTable.php:236
@@ -5695,6 +5767,12 @@ Pokud jste to provedli nesprávně nebo pokud počítač již není důvěryhodn
Měrná jednotka
+
+
+ part.edit.partCustomState
+ Vlastní stav součásti
+
+
Part-DB1\src\Form\Part\PartBaseType.php:212
@@ -5982,6 +6060,12 @@ Pokud jste to provedli nesprávně nebo pokud počítač již není důvěryhodn
Měrná jednotka
+
+
+ part_custom_state.label
+ Vlastní stav součásti
+
+
Part-DB1\src\Services\ElementTypeNameGenerator.php:90
@@ -6225,6 +6309,12 @@ Pokud jste to provedli nesprávně nebo pokud počítač již není důvěryhodn
Měrné jednotky
+
+
+ tree.tools.edit.part_custom_state
+ Vlastní stav součásti
+
+
Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:203
@@ -6959,6 +7049,12 @@ Pokud jste to provedli nesprávně nebo pokud počítač již není důvěryhodn
Filtr názvů
+
+
+ category.edit.part_ipn_prefix
+ Předpona součásti IPN
+
+
obsolete
@@ -8495,6 +8591,12 @@ Element 3
Měrná jednotka
+
+
+ perm.part_custom_states
+ Vlastní stav součásti
+
+
obsolete
@@ -10254,12 +10356,24 @@ Element 3
např. "/Kondenzátor \d+ nF/i"
+
+
+ category.edit.part_ipn_prefix.placeholder
+ např. "B12A"
+
+
category.edit.partname_regex.help
Regulární výraz kompatibilní s PCRE, kterému musí název dílu odpovídat.
+
+
+ category.edit.part_ipn_prefix.help
+ Předpona navrhovaná při zadávání IPN části.
+
+
entity.select.add_hint
@@ -10806,6 +10920,12 @@ Element 3
Měrná jednotka
+
+
+ log.element_edited.changed_fields.partCustomState
+ Vlastní stav součásti
+
+
log.element_edited.changed_fields.expiration_date
@@ -11064,6 +11184,18 @@ Element 3
Upravit měrnou jednotku
+
+
+ part_custom_state.new
+ Nový vlastní stav komponenty
+
+
+
+
+ part_custom_state.edit
+ Upravit vlastní stav komponenty
+
+
user.aboutMe.label
@@ -12975,6 +13107,54 @@ Vezměte prosím na vědomí, že se nemůžete vydávat za uživatele se zakáz
Pokud potřebujete směnné kurzy mezi měnami mimo eurozónu, můžete zde zadat API klíč z fixer.io.
+
+
+ settings.misc.ipn_suggest
+ Seznam návrhů IPN součástek
+
+
+
+
+ settings.misc.ipn_suggest.regex
+ Regex
+
+
+
+
+ settings.misc.ipn_suggest.regex_help
+ Nápověda text
+
+
+
+
+ settings.misc.ipn_suggest.regex_help_description
+ Definujte svůj vlastní text nápovědy pro specifikaci formátu Regex.
+
+
+
+
+ settings.misc.ipn_suggest.autoAppendSuffix
+ Pokud je tato možnost povolena, bude při opětovném zadání existujícího IPN při ukládání k vstupu přidána přírůstková přípona.
+
+
+
+
+ settings.misc.ipn_suggest.suggestPartDigits
+ Počet čísel pro inkrement
+
+
+
+
+ settings.misc.ipn_suggest.useDuplicateDescription
+ Je-li povoleno, použije se popis součástky k nalezení existujících součástek se stejným popisem a k určení další volné IPN navýšením její číselné přípony pro seznam návrhů.
+
+
+
+
+ settings.misc.ipn_suggest.suggestPartDigits.help
+ Počet číslic použitých pro inkrementální číslování součástí v návrhovém systému IPN (Interní číslo součástky).
+
+
settings.behavior.part_info
diff --git a/translations/messages.da.xlf b/translations/messages.da.xlf
index d7258986..530d91aa 100644
--- a/translations/messages.da.xlf
+++ b/translations/messages.da.xlf
@@ -548,6 +548,12 @@
Måleenhed
+
+
+ part_custom_state.caption
+ Brugerdefineret komponentstatus
+
+
Part-DB1\templates\AdminPages\StorelocationAdmin.html.twig:5
@@ -1850,6 +1856,66 @@ Underelementer vil blive flyttet opad.
Advanceret
+
+
+ part.edit.tab.advanced.ipn.commonSectionHeader
+ Forslag uden del-inkrement
+
+
+
+
+ part.edit.tab.advanced.ipn.partIncrementHeader
+ Forslag med numeriske deleforøgelser
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.description.current-increment
+ Aktuel IPN-specifikation for delen
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.description.increment
+ Næste mulige IPN-specifikation baseret på en identisk delebeskrivelse
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix_empty.direct_category
+ IPN-præfikset for den direkte kategori er tomt, angiv det i kategorien "%name%"
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.direct_category
+ IPN-præfiks for direkte kategori
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.direct_category.increment
+ IPN-præfiks for den direkte kategori og en delspecifik inkrement
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.hierarchical.no_increment
+ IPN-præfikser med hierarkisk rækkefølge af overordnede præfikser
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.hierarchical.increment
+ IPN-præfikser med hierarkisk rækkefølge af overordnede præfikser og en del-specifik inkrement
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.not_saved
+ Opret først en komponent, og tildel den en kategori: med eksisterende kategorier og deres egne IPN-præfikser kan IPN-betegnelsen for komponenten foreslås automatisk
+
+
Part-DB1\templates\Parts\edit\edit_part_info.html.twig:40
@@ -4838,6 +4904,12 @@ Bemærk også, at uden to-faktor-godkendelse er din konto ikke længere så godt
Måleenhed
+
+
+ part.table.partCustomState
+ Brugerdefineret komponentstatus
+
+
Part-DB1\src\DataTables\PartsDataTable.php:236
@@ -5702,6 +5774,12 @@ Bemærk også, at uden to-faktor-godkendelse er din konto ikke længere så godt
Måleenhed
+
+
+ part.edit.partCustomState
+ Brugerdefineret deltilstand
+
+
Part-DB1\src\Form\Part\PartBaseType.php:212
@@ -5989,6 +6067,12 @@ Bemærk også, at uden to-faktor-godkendelse er din konto ikke længere så godt
Måleenhed
+
+
+ part_custom_state.label
+ Brugerdefineret deltilstand
+
+
Part-DB1\src\Services\ElementTypeNameGenerator.php:90
@@ -6232,6 +6316,12 @@ Bemærk også, at uden to-faktor-godkendelse er din konto ikke længere så godt
Måleenhed
+
+
+ tree.tools.edit.part_custom_state
+ Brugerdefineret komponenttilstand
+
+
Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:203
@@ -6966,6 +7056,12 @@ Bemærk også, at uden to-faktor-godkendelse er din konto ikke længere så godt
Navnefilter
+
+
+ category.edit.part_ipn_prefix
+ IPN-komponentförstavelse
+
+
obsolete
@@ -8502,6 +8598,12 @@ Element 3
Måleenhed
+
+
+ perm.part_custom_states
+ Brugerdefineret komponentstatus
+
+
obsolete
@@ -10280,12 +10382,24 @@ Element 3
f.eks. "/Kondensator \d+ nF/i"
+
+
+ category.edit.part_ipn_prefix.placeholder
+ f.eks. "B12A"
+
+
category.edit.partname_regex.help
Et PCRE-kompatibelt regulært udtryk, som delnavnet skal opfylde.
+
+
+ category.edit.part_ipn_prefix.help
+ Et prefix foreslået, når IPN for en del indtastes.
+
+
entity.select.add_hint
@@ -10832,6 +10946,12 @@ Element 3
Måleenhed
+
+
+ log.element_edited.changed_fields.partCustomState
+ Brugerdefineret komponentstatus
+
+
log.element_edited.changed_fields.expiration_date
@@ -11096,6 +11216,18 @@ Oversættelsen
Ret måleenhed
+
+
+ part_custom_state.new
+ Ny brugerdefineret komponentstatus
+
+
+
+
+ part_custom_state.edit
+ Rediger brugerdefineret komponentstatus
+
+
user.aboutMe.label
diff --git a/translations/messages.de.xlf b/translations/messages.de.xlf
index 1d1c49af..806c2e52 100644
--- a/translations/messages.de.xlf
+++ b/translations/messages.de.xlf
@@ -1,6 +1,6 @@
-
+
Part-DB1\templates\AdminPages\AttachmentTypeAdmin.html.twig:4
@@ -242,7 +242,7 @@
part.info.timetravel_hint
- So sah das Bauteil vor %timestamp% aus. <i>Beachten Sie, dass dieses Feature experimentell ist und die angezeigten Infos daher nicht unbedingt korrekt sind.</i>
+ Beachten Sie, dass dieses Feature experimentell ist und die angezeigten Infos daher nicht unbedingt korrekt sind.]]>
@@ -548,6 +548,12 @@
Maßeinheit
+
+
+ part_custom_state.caption
+ Benutzerdefinierter Bauteilstatus
+
+
Part-DB1\templates\AdminPages\StorelocationAdmin.html.twig:5
@@ -731,9 +737,9 @@
user.edit.tfa.disable_tfa_message
- Dies wird <b>alle aktiven Zwei-Faktor-Authentifizierungsmethoden des Nutzers deaktivieren</b> und die <b>Backupcodes löschen</b>! <br>
-Der Benutzer wird alle Zwei-Faktor-Authentifizierungmethoden neu einrichten müssen und neue Backupcodes ausdrucken müssen! <br><br>
-<b>Führen sie dies nur durch, wenn Sie über die Identität des (um Hilfe suchenden) Benutzers absolut sicher sind, da ansonsten eine Kompromittierung des Accounts durch einen Angreifer erfolgen könnte!</b>
+ alle aktiven Zwei-Faktor-Authentifizierungsmethoden des Nutzers deaktivieren und die Backupcodes löschen !
+Der Benutzer wird alle Zwei-Faktor-Authentifizierungmethoden neu einrichten müssen und neue Backupcodes ausdrucken müssen!
+Führen sie dies nur durch, wenn Sie über die Identität des (um Hilfe suchenden) Benutzers absolut sicher sind, da ansonsten eine Kompromittierung des Accounts durch einen Angreifer erfolgen könnte! ]]>
@@ -1440,7 +1446,7 @@ Subelemente werden beim Löschen nach oben verschoben.
homepage.github.text
- Quellcode, Downloads, Bugreports, ToDo-Liste usw. gibts auf der <a class="link-external" target="_blank" href="%href%">GitHub Projektseite</a>
+ GitHub Projektseite]]>
@@ -1462,7 +1468,7 @@ Subelemente werden beim Löschen nach oben verschoben.
homepage.help.text
- Hilfe und Tipps finden sie im <a class="link-external" rel="noopener" target="_blank" href="%href%">Wiki</a> der GitHub Seite.
+ Wiki der GitHub Seite.]]>
@@ -1704,7 +1710,7 @@ Subelemente werden beim Löschen nach oben verschoben.
email.pw_reset.fallback
- Wenn dies nicht funktioniert, rufen Sie <a href="%url%">%url%</a> auf und geben Sie die folgenden Daten ein
+ %url% auf und geben Sie die folgenden Daten ein]]>
@@ -1734,7 +1740,7 @@ Subelemente werden beim Löschen nach oben verschoben.
email.pw_reset.valid_unit %date%
- Das Reset-Token ist gültig bis <i>%date%</i>
+ %date%]]>
@@ -1841,6 +1847,66 @@ Subelemente werden beim Löschen nach oben verschoben.
Erweiterte Optionen
+
+
+ part.edit.tab.advanced.ipn.commonSectionHeader
+ Vorschläge ohne Teil-Inkrement
+
+
+
+
+ part.edit.tab.advanced.ipn.partIncrementHeader
+ Vorschläge mit numerischen Teil-Inkrement
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.description.current-increment
+ Aktuelle IPN-Angabe des Bauteils
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.description.increment
+ Nächstmögliche IPN-Angabe auf Basis der identischen Bauteil-Beschreibung
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix_empty.direct_category
+ IPN-Präfix der direkten Kategorie leer, geben Sie einen Präfix in Kategorie "%name%" an
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.direct_category
+ IPN-Präfix der direkten Kategorie
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.direct_category.increment
+ IPN-Präfix der direkten Kategorie und eines teilspezifischen Inkrements
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.hierarchical.no_increment
+ IPN-Präfixe mit hierarchischer Kategorienreihenfolge der Elternpräfixe
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.hierarchical.increment
+ IPN-Präfixe mit hierarchischer Kategorienreihenfolge der Elternpräfixe und ein teilsspezifisches Inkrement
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.not_saved
+ Bitte erstellen Sie zuerst ein Bauteil und weisen Sie dieses einer Kategorie zu: mit vorhandenen Kategorien und derene eigenen IPN-Präfix kann die IPN-Angabe für das jeweilige Teil automatisch vorgeschlagen werden
+
+
Part-DB1\templates\Parts\edit\edit_part_info.html.twig:40
@@ -3577,8 +3643,8 @@ Subelemente werden beim Löschen nach oben verschoben.
tfa_google.disable.confirm_message
- Wenn Sie die Authenticator App deaktivieren, werden alle Backupcodes gelöscht, daher sie müssen sie evtl. neu ausdrucken.<br>
-Beachten Sie außerdem, dass ihr Account ohne Zwei-Faktor-Authentifizierung nicht mehr so gut gegen Angreifer geschützt ist!
+
+Beachten Sie außerdem, dass ihr Account ohne Zwei-Faktor-Authentifizierung nicht mehr so gut gegen Angreifer geschützt ist!]]>
@@ -3598,7 +3664,7 @@ Beachten Sie außerdem, dass ihr Account ohne Zwei-Faktor-Authentifizierung nich
tfa_google.step.download
- Laden Sie eine Authenticator App herunter (z.B. <a class="link-external" target="_blank" href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2">Google Authenticator</a> oder <a class="link-external" target="_blank" href="https://play.google.com/store/apps/details?id=org.fedorahosted.freeotp">FreeOTP Authenticator</a>)
+ Google Authenticator oder FreeOTP Authenticator )]]>
@@ -3840,8 +3906,8 @@ Beachten Sie außerdem, dass ihr Account ohne Zwei-Faktor-Authentifizierung nich
tfa_trustedDevices.explanation
- Bei der Überprüfung des zweiten Faktors, kann der aktuelle Computer als vertrauenswürdig gekennzeichnet werden, daher werden keine Zwei-Faktor-Überprüfungen mehr an diesem Computer benötigt.
-Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertrauenswürdig ist, können Sie hier den Status <i>aller </i>Computer zurücksetzen.
+ aller Computer zurücksetzen.]]>
@@ -4830,6 +4896,12 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr
Maßeinheit
+
+
+ part.table.partCustomState
+ Benutzerdefinierter Bauteilstatus
+
+
Part-DB1\src\DataTables\PartsDataTable.php:236
@@ -5312,7 +5384,7 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr
label_options.lines_mode.help
- Wenn Sie hier Twig auswählen, wird das Contentfeld als Twig-Template interpretiert. Weitere Hilfe gibt es in der <a href="https://twig.symfony.com/doc/3.x/templates.html">Twig Dokumentation</a> und dem <a href="https://docs.part-db.de/usage/labels.html#twig-mode">Wiki</a>.
+ Twig Dokumentation und dem Wiki .]]>
@@ -5694,6 +5766,12 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr
Maßeinheit
+
+
+ part.edit.partCustomState
+ Benutzerdefinierter Bauteilstatus
+
+
Part-DB1\src\Form\Part\PartBaseType.php:212
@@ -5981,6 +6059,12 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr
Maßeinheit
+
+
+ part_custom_state.label
+ Benutzerdefinierter Bauteilstatus
+
+
Part-DB1\src\Services\ElementTypeNameGenerator.php:90
@@ -6224,6 +6308,12 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr
Maßeinheiten
+
+
+ tree.tools.edit.part_custom_state
+ Benutzerdefinierter Bauteilstatus
+
+
Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:203
@@ -6958,6 +7048,12 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr
Namensfilter
+
+
+ category.edit.part_ipn_prefix
+ Bauteil IPN-Präfix
+
+
obsolete
@@ -7156,15 +7252,15 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr
mass_creation.lines.placeholder
- Element 1
+
+Element 1 -> Element 1.1
+Element 1 -> Element 1.2]]>
@@ -8497,6 +8593,12 @@ Element 1 -> Element 1.2
Maßeinheiten
+
+
+ perm.part_custom_states
+ Benutzerdefinierter Bauteilstatus
+
+
obsolete
@@ -9443,25 +9545,25 @@ Element 1 -> Element 1.2
filter.parameter_value_constraint.operator.<
- Typ. Wert <
+
filter.parameter_value_constraint.operator.>
- Typ. Wert >
+ ]]>
filter.parameter_value_constraint.operator.<=
- Typ. Wert <=
+
filter.parameter_value_constraint.operator.>=
- Typ. Wert >=
+ =]]>
@@ -9569,7 +9671,7 @@ Element 1 -> Element 1.2
parts_list.search.searching_for
- Suche Teile mit dem Suchbegriff <b>%keyword%</b>
+ %keyword%]]>
@@ -10229,13 +10331,13 @@ Element 1 -> Element 1.2
project.builds.number_of_builds_possible
- Sie haben genug Bauteile auf Lager, um <b>%max_builds%</b> Exemplare dieses Projektes zu bauen.
+ %max_builds% Exemplare dieses Projektes zu bauen.]]>
project.builds.check_project_status
- Der aktuelle Projektstatus ist <b>"%project_status%"</b>. Sie sollten überprüfen, ob sie das Projekt mit diesem Status wirklich bauen wollen!
+ "%project_status%". Sie sollten überprüfen, ob sie das Projekt mit diesem Status wirklich bauen wollen!]]>
@@ -10328,16 +10430,28 @@ Element 1 -> Element 1.2
z.B. "/Kondensator \d+ nF/i"
+
+
+ category.edit.part_ipn_prefix.placeholder
+ z.B. "B12A"
+
+
category.edit.partname_regex.help
Ein PCRE-kompatibler regulärer Ausdruck, den der Bauteilename erfüllen muss.
+
+
+ category.edit.part_ipn_prefix.help
+ Ein Präfix, der bei der IPN-Eingabe eines Bauteils vorgeschlagen wird.
+
+
entity.select.add_hint
- Nutzen Sie -> um verschachtelte Strukturen anzulegen, z.B. "Element 1->Element 1.1"
+ um verschachtelte Strukturen anzulegen, z.B. "Element 1->Element 1.1"]]>
@@ -10361,13 +10475,13 @@ Element 1 -> Element 1.2
homepage.first_steps.introduction
- Die Datenbank ist momentan noch leer. Sie möchten möglicherweise die <a href="%url%">Dokumentation</a> lesen oder anfangen, die folgenden Datenstrukturen anzulegen.
+ Dokumentation lesen oder anfangen, die folgenden Datenstrukturen anzulegen.]]>
homepage.first_steps.create_part
- Oder Sie können direkt ein <a href="%url%">neues Bauteil erstellen</a>.
+ neues Bauteil erstellen.]]>
@@ -10379,7 +10493,7 @@ Element 1 -> Element 1.2
homepage.forum.text
- Für Fragen rund um Part-DB, nutze das <a class="link-external" rel="noopener" target="_blank" href="%href%">Diskussionsforum</a>
+ Diskussionsforum]]>
@@ -10880,6 +10994,12 @@ Element 1 -> Element 1.2
Maßeinheit
+
+
+ log.element_edited.changed_fields.partCustomState
+ Benutzerdefinierter Bauteilstatus
+
+
log.element_edited.changed_fields.expiration_date
@@ -11039,7 +11159,7 @@ Element 1 -> Element 1.2
parts.import.help_documentation
- Konsultieren Sie die <a href="%link%">Dokumentation</a> für weiter Informationen über das Dateiformat.
+ Dokumentation für weiter Informationen über das Dateiformat.]]>
@@ -11144,6 +11264,18 @@ Element 1 -> Element 1.2
Bearbeite Maßeinheit
+
+
+ part_custom_state.new
+ Neuer benutzerdefinierter Bauteilstatus
+
+
+
+
+ part_custom_state.edit
+ Bearbeite benutzerdefinierten Bauteilstatus
+
+
user.aboutMe.label
@@ -11219,7 +11351,7 @@ Element 1 -> Element 1.2
part.filter.lessThanDesired
- Weniger vorhanden als gewünscht (Gesamtmenge < Mindestmenge)
+
@@ -12031,13 +12163,13 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön
part.merge.confirm.title
- Möchten Sie wirklich <b>%other%</b> in <b>%target%</b> zusammenführen?
+ %other% in %target% zusammenführen?]]>
part.merge.confirm.message
- <b>%other%</b> wird gelöscht, und das aktuelle Bauteil wird mit den angezeigten Daten gespeichert.
+ %other% wird gelöscht, und das aktuelle Bauteil wird mit den angezeigten Daten gespeichert.]]>
@@ -12391,7 +12523,7 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön
settings.ips.element14.apiKey.help
- Sie können sich unter <a href="https://partner.element14.com/">https://partner.element14.com/</a> für einen API-Schlüssel registrieren.
+ https://partner.element14.com/ für einen API-Schlüssel registrieren.]]>
@@ -12403,7 +12535,7 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön
settings.ips.element14.storeId.help
- Die Domain des Shops, aus dem die Daten abgerufen werden sollen. Diese bestimmt die Sprache und Währung der Ergebnisse. Eine Liste der gültigen Domains finden Sie <a href="https://partner.element14.com/docs/Product_Search_API_REST__Description">hier</a>.
+ hier.]]>
@@ -12421,7 +12553,7 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön
settings.ips.tme.token.help
- Sie können einen API-Token und einen geheimen Schlüssel unter <a href="https://developers.tme.eu/en/">https://developers.tme.eu/en/</a> erhalten.
+ https://developers.tme.eu/en/ erhalten.]]>
@@ -12469,7 +12601,7 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön
settings.ips.mouser.apiKey.help
- Sie können sich unter <a href="https://eu.mouser.com/api-hub/">https://eu.mouser.com/api-hub/</a> für einen API-Schlüssel registrieren.
+ https://eu.mouser.com/api-hub/ für einen API-Schlüssel registrieren.]]>
@@ -12517,7 +12649,7 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön
settings.ips.mouser.searchOptions.rohsAndInStock
- Sofort verfügbar & RoHS konform
+
@@ -12547,7 +12679,7 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön
settings.system.attachments
- Anhänge & Dateien
+
@@ -12571,7 +12703,7 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön
settings.system.attachments.allowDownloads.help
- Mit dieser Option können Benutzer externe Dateien in die Part-DB herunterladen, indem sie eine URL angeben. <b>Achtung: Dies kann ein Sicherheitsrisiko darstellen, da Benutzer dadurch möglicherweise über die Part-DB auf Intranet-Ressourcen zugreifen können!</b>
+ Achtung: Dies kann ein Sicherheitsrisiko darstellen, da Benutzer dadurch möglicherweise über die Part-DB auf Intranet-Ressourcen zugreifen können!]]>
@@ -12745,8 +12877,8 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön
settings.system.localization.base_currency_description
- Die Währung, in der Preisinformationen und Wechselkurse gespeichert werden. Diese Währung wird angenommen, wenn für eine Preisinformation keine Währung festgelegt ist.
-<b>Bitte beachten Sie, dass die Währungen bei einer Änderung dieses Wertes nicht umgerechnet werden. Wenn Sie also die Basiswährung ändern, nachdem Sie bereits Preisinformationen hinzugefügt haben, führt dies zu falschen Preisen!</b>
+ Bitte beachten Sie, dass die Währungen bei einer Änderung dieses Wertes nicht umgerechnet werden. Wenn Sie also die Basiswährung ändern, nachdem Sie bereits Preisinformationen hinzugefügt haben, führt dies zu falschen Preisen!]]>
@@ -12776,7 +12908,7 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön
settings.misc.kicad_eda.category_depth.help
- Dieser Wert bestimmt die Tiefe des Kategoriebaums, der in KiCad sichtbar ist. 0 bedeutet, dass nur die Kategorien der obersten Ebene sichtbar sind. Setzen Sie den Wert auf > 0, um weitere Ebenen anzuzeigen. Setzen Sie den Wert auf -1, um alle Teile der Part-DB innerhalb einer einzigen Kategorie in KiCad anzuzeigen.
+ 0, um weitere Ebenen anzuzeigen. Setzen Sie den Wert auf -1, um alle Teile der Part-DB innerhalb einer einzigen Kategorie in KiCad anzuzeigen.]]>
@@ -12794,7 +12926,7 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön
settings.behavior.sidebar.items.help
- Die Menüs, die standardmäßig in der Seitenleiste angezeigt werden. Die Reihenfolge der Elemente kann per Drag & Drop geändert werden.
+
@@ -12842,7 +12974,7 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön
settings.behavior.table.parts_default_columns.help
- Die Spalten, die standardmäßig in Bauteiltabellen angezeigt werden sollen. Die Reihenfolge der Elemente kann per Drag & Drop geändert werden.
+
@@ -12896,7 +13028,7 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön
settings.ips.oemsecrets.sortMode.M
- Vollständigkeit & Herstellername
+
@@ -13055,6 +13187,54 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön
Wenn Sie Wechselkurse zwischen Nicht-Euro-Währungen benötigen, können Sie hier einen API-Schlüssel von fixer.io eingeben.
+
+
+ settings.misc.ipn_suggest
+ Bauteil IPN-Vorschlagsliste
+
+
+
+
+ settings.misc.ipn_suggest.regex
+ Regex
+
+
+
+
+ settings.misc.ipn_suggest.regex_help
+ Hilfetext
+
+
+
+
+ settings.misc.ipn_suggest.regex_help_description
+ Definieren Sie Ihren eigenen Nutzer-Hilfetext zur Regex Formatvorgabe.
+
+
+
+
+ settings.misc.ipn_suggest.autoAppendSuffix
+ Hänge ein inkrementelles Suffix an, wenn eine IPN bereits durch ein anderes Bauteil verwendet wird.
+
+
+
+
+ settings.misc.ipn_suggest.suggestPartDigits
+ Stellen für numerisches Inkrement
+
+
+
+
+ settings.misc.ipn_suggest.useDuplicateDescription
+ Verwende Bauteilebeschreibung zur Ermittlung der nächsten IPN
+
+
+
+
+ settings.misc.ipn_suggest.suggestPartDigits.help
+ Die Anzahl der Ziffern, die für die inkrementale Nummerierung von Teilen im IPN-Vorschlagssystem verwendet werden.
+
+
settings.behavior.part_info
@@ -13508,7 +13688,7 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön
settings.behavior.homepage.items.help
- Die Elemente, die auf der Startseite angezeigt werden sollen. Die Reihenfolge kann per Drag & Drop geändert werden.
+
@@ -14250,5 +14430,11 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön
Dies ist auf der Informationsquellen Übersichtsseite möglich.
+
+
+ settings.misc.ipn_suggest.useDuplicateDescription.help
+ Wenn aktiviert, wird die Bauteil-Beschreibung verwendet, um vorhandene Teile mit derselben Beschreibung zu finden und die nächste verfügbare IPN für die Vorschlagsliste zu ermitteln, indem der numerische Suffix entsprechend erhöht wird.
+
+
diff --git a/translations/messages.el.xlf b/translations/messages.el.xlf
index cc17d9be..3618fa3d 100644
--- a/translations/messages.el.xlf
+++ b/translations/messages.el.xlf
@@ -318,6 +318,12 @@
Μονάδα μέτρησης
+
+
+ part_custom_state.caption
+ Προσαρμοσμένη κατάσταση εξαρτήματος
+
+
Part-DB1\templates\AdminPages\StorelocationAdmin.html.twig:5
@@ -1535,5 +1541,131 @@
Επεξεργασία
+
+
+ perm.part_custom_states
+ Προσαρμοσμένη κατάσταση εξαρτήματος
+
+
+
+
+ tree.tools.edit.part_custom_state
+ Προσαρμοσμένη κατάσταση εξαρτήματος
+
+
+
+
+ part_custom_state.new
+ Νέα προσαρμοσμένη κατάσταση εξαρτήματος
+
+
+
+
+ part_custom_state.edit
+ Επεξεργασία προσαρμοσμένης κατάστασης εξαρτήματος
+
+
+
+
+ part_custom_state.label
+ Προσαρμοσμένη κατάσταση μέρους
+
+
+
+
+ log.element_edited.changed_fields.partCustomState
+ Προσαρμοσμένη κατάσταση εξαρτήματος
+
+
+
+
+ part.edit.partCustomState
+ Προσαρμοσμένη κατάσταση εξαρτήματος
+
+
+
+
+ part.table.partCustomState
+ Προσαρμοσμένη κατάσταση μέρους
+
+
+
+
+ category.edit.part_ipn_prefix
+ Πρόθεμα εξαρτήματος IPN
+
+
+
+
+ category.edit.part_ipn_prefix.placeholder
+ π.χ. "B12A"
+
+
+
+
+ category.edit.part_ipn_prefix.help
+ Μια προτεινόμενη πρόθεμα κατά την εισαγωγή του IPN ενός τμήματος.
+
+
+
+
+ part.edit.tab.advanced.ipn.commonSectionHeader
+ Προτάσεις χωρίς αύξηση μέρους
+
+
+
+
+ part.edit.tab.advanced.ipn.partIncrementHeader
+ Προτάσεις με αριθμητικές αυξήσεις μερών
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.description.current-increment
+ Τρέχουσα προδιαγραφή IPN του εξαρτήματος
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.description.increment
+ Επόμενη δυνατή προδιαγραφή IPN βάσει της ίδιας περιγραφής εξαρτήματος
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix_empty.direct_category
+ Το IPN πρόθεμα της άμεσης κατηγορίας είναι κενό, καθορίστε το στην κατηγορία "%name%"
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.direct_category
+ Πρόθεμα IPN για την άμεση κατηγορία
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.direct_category.increment
+ Πρόθεμα IPN της άμεσης κατηγορίας και μιας ειδικής για μέρος αύξησης
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.hierarchical.no_increment
+ Προθέματα IPN με ιεραρχική σειρά κατηγοριών των προθέτων γονέων
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.hierarchical.increment
+ Προθέματα IPN με ιεραρχική σειρά κατηγοριών των προθέτων γονέων και συγκεκριμένη αύξηση για το μέρος
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.not_saved
+ Δημιουργήστε πρώτα ένα εξάρτημα και αντιστοιχίστε το σε μια κατηγορία: με τις υπάρχουσες κατηγορίες και τα δικά τους προθέματα IPN, η ονομασία IPN για το εξάρτημα μπορεί να προταθεί αυτόματα
+
+
diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf
index 62f145e0..ca05bee9 100644
--- a/translations/messages.en.xlf
+++ b/translations/messages.en.xlf
@@ -97,16 +97,6 @@
New category
-
-
- Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:4
- Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:4
-
-
- currency.caption
- Currency
-
-
Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:12
@@ -418,16 +408,6 @@
New footprint
-
-
- Part-DB1\templates\AdminPages\GroupAdmin.html.twig:4
- Part-DB1\templates\AdminPages\GroupAdmin.html.twig:4
-
-
- group.edit.caption
- Groups
-
-
Part-DB1\templates\AdminPages\GroupAdmin.html.twig:9
@@ -460,15 +440,6 @@
New group
-
-
- Part-DB1\templates\AdminPages\LabelProfileAdmin.html.twig:4
-
-
- label_profile.caption
- Label profiles
-
-
Part-DB1\templates\AdminPages\LabelProfileAdmin.html.twig:8
@@ -507,17 +478,6 @@
New label profile
-
-
- Part-DB1\templates\AdminPages\ManufacturerAdmin.html.twig:4
- Part-DB1\templates\AdminPages\ManufacturerAdmin.html.twig:4
- templates\AdminPages\ManufacturerAdmin.html.twig:4
-
-
- manufacturer.caption
- Manufacturers
-
-
Part-DB1\templates\AdminPages\ManufacturerAdmin.html.twig:8
@@ -538,16 +498,6 @@
New manufacturer
-
-
- Part-DB1\templates\AdminPages\MeasurementUnitAdmin.html.twig:4
- Part-DB1\templates\AdminPages\MeasurementUnitAdmin.html.twig:4
-
-
- measurement_unit.caption
- Measurement Unit
-
-
Part-DB1\templates\AdminPages\StorelocationAdmin.html.twig:5
@@ -614,16 +564,6 @@
New supplier
-
-
- Part-DB1\templates\AdminPages\UserAdmin.html.twig:8
- Part-DB1\templates\AdminPages\UserAdmin.html.twig:8
-
-
- user.edit.caption
- Users
-
-
Part-DB1\templates\AdminPages\UserAdmin.html.twig:14
@@ -1842,6 +1782,66 @@ Sub elements will be moved upwards.
Advanced
+
+
+ part.edit.tab.advanced.ipn.commonSectionHeader
+ Suggestions without part increment
+
+
+
+
+ part.edit.tab.advanced.ipn.partIncrementHeader
+ Suggestions with numeric part increment
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.description.current-increment
+ Current IPN specification of the part
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.description.increment
+ Next possible IPN specification based on an identical part description
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix_empty.direct_category
+ IPN prefix of direct category empty, specify one in category "%name%"
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.direct_category
+ IPN prefix of direct category
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.direct_category.increment
+ IPN prefix of direct category and part-specific increment
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.hierarchical.no_increment
+ IPN prefixes with hierarchical category order of parent-prefix(es)
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.hierarchical.increment
+ IPN prefixes with hierarchical category order of parent-prefix(es) and part-specific increment
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.not_saved
+ Please create part at first and assign it to a category: with existing categories and their own IPN prefix, the IPN for the part can be suggested automatically
+
+
Part-DB1\templates\Parts\edit\edit_part_info.html.twig:40
@@ -4831,6 +4831,12 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
Measurement Unit
+
+
+ part.table.partCustomState
+ Custom part state
+
+
Part-DB1\src\DataTables\PartsDataTable.php:236
@@ -5695,6 +5701,12 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
Measuring unit
+
+
+ part.edit.partCustomState
+ Custom part state
+
+
Part-DB1\src\Form\Part\PartBaseType.php:212
@@ -5982,6 +5994,12 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
Measurement unit
+
+
+ part_custom_state.label
+ Custom part state
+
+
Part-DB1\src\Services\ElementTypeNameGenerator.php:90
@@ -6225,6 +6243,12 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
Measurement Unit
+
+
+ tree.tools.edit.part_custom_state
+ Custom part states
+
+
Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:203
@@ -6959,6 +6983,12 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
Name filter
+
+
+ category.edit.part_ipn_prefix
+ Part IPN Prefix
+
+
obsolete
@@ -7628,16 +7658,6 @@ Element 1 -> Element 1.2
System
-
-
- obsolete
- obsolete
-
-
- perm.parts
- Parts
-
-
obsolete
@@ -7898,16 +7918,6 @@ Element 1 -> Element 1.2
Orders
-
-
- obsolete
- obsolete
-
-
- perm.storelocations
- Storage locations
-
-
obsolete
@@ -7928,66 +7938,6 @@ Element 1 -> Element 1.2
List parts
-
-
- obsolete
- obsolete
-
-
- perm.part.footprints
- Footprints
-
-
-
-
- obsolete
- obsolete
-
-
- perm.part.categories
- Categories
-
-
-
-
- obsolete
- obsolete
-
-
- perm.part.supplier
- Suppliers
-
-
-
-
- obsolete
- obsolete
-
-
- perm.part.manufacturers
- Manufacturers
-
-
-
-
- obsolete
- obsolete
-
-
- perm.projects
- Projects
-
-
-
-
- obsolete
- obsolete
-
-
- perm.part.attachment_types
- Attachment types
-
-
obsolete
@@ -10329,12 +10279,24 @@ Element 1 -> Element 1.2
e.g "/Capacitor \d+ nF/i"
+
+
+ category.edit.part_ipn_prefix.placeholder
+ e.g "B12A"
+
+
category.edit.partname_regex.help
A PCRE-compatible regular expression, which a part name have to match.
+
+
+ category.edit.part_ipn_prefix.help
+ A prefix suggested when entering the IPN of a part.
+
+
entity.select.add_hint
@@ -10881,6 +10843,12 @@ Element 1 -> Element 1.2
Measuring Unit
+
+
+ log.element_edited.changed_fields.partCustomState
+ Custom part state
+
+
log.element_edited.changed_fields.expiration_date
@@ -11145,6 +11113,18 @@ Element 1 -> Element 1.2
Edit Measurement Unit
+
+
+ part_custom_state.new
+ New custom part state
+
+
+
+
+ part_custom_state.edit
+ Edit custom part state
+
+
user.aboutMe.label
@@ -13056,6 +13036,54 @@ Please note, that you can not impersonate a disabled user. If you try you will g
If you need exchange rates between non-euro currencies, you can input an API key from fixer.io here.
+
+
+ settings.misc.ipn_suggest
+ Part IPN Suggest
+
+
+
+
+ settings.misc.ipn_suggest.regex
+ Regex
+
+
+
+
+ settings.misc.ipn_suggest.regex_help
+ Help text
+
+
+
+
+ settings.misc.ipn_suggest.regex_help_description
+ Define your own user help text for the Regex format specification.
+
+
+
+
+ settings.misc.ipn_suggest.autoAppendSuffix
+ Add incremental suffix to IPN, if the value is already used by another part
+
+
+
+
+ settings.misc.ipn_suggest.suggestPartDigits
+ Increment Digits
+
+
+
+
+ settings.misc.ipn_suggest.useDuplicateDescription
+ Use part description to find next available IPN
+
+
+
+
+ settings.misc.ipn_suggest.suggestPartDigits.help
+ The number of digits used for the incremental numbering of parts in the IPN (Internal Part Number) suggestion system.
+
+
settings.behavior.part_info
@@ -14223,32 +14251,7 @@ Please note, that you can not impersonate a disabled user. If you try you will g
settings.system.localization.language_menu_entries.description
-
-
-
-
-
- project.builds.no_bom_entries
- Project has no BOM entries
-
-
-
-
- settings.behavior.sidebar.data_structure_nodes_table_include_children
- Tables should include children nodes by default
-
-
-
-
- settings.behavior.sidebar.data_structure_nodes_table_include_children.help
- If checked, the part tables for categories, footprints, etc. should include all parts of child categories. If not checked, only parts that strictly belong to the clicked node are shown.
-
-
-
-
- info_providers.search.error.oauth_reconnect
- You need to reconnect OAuth for following providers: %provider%
-You can do this in the provider info list.
+ The languages to show in the language drop-down menu. Order can be changed via drag & drop. Leave empty to show all available languages.
@@ -14276,5 +14279,138 @@ You can do this in the provider info list.
You can do this in the provider info list.
+
+
+ settings.misc.ipn_suggest.useDuplicateDescription.help
+ When enabled, the part’s description is used to find existing parts with the same description and to determine the next available IPN by incrementing their numeric suffix for the suggestion list.
+
+
+
+
+ settings.misc.ipn_suggest.regex.help
+ A PCRE-compatible regular expression every IPN has to fulfill. Leave empty to allow all everything as IPN.
+
+
+
+
+ user.labelp
+ Users
+
+
+
+
+ currency.labelp
+ Currencies
+
+
+
+
+ measurement_unit.labelp
+ Measurement units
+
+
+
+
+ attachment_type.labelp
+ Attachment types
+
+
+
+
+ label_profile.labelp
+ Label profiles
+
+
+
+
+ part_custom_state.labelp
+ Custom part states
+
+
+
+
+ group.labelp
+ Groups
+
+
+
+
+ settings.synonyms.type_synonym.type
+ Type
+
+
+
+
+ settings.synonyms.type_synonym.language
+ Language
+
+
+
+
+ settings.synonyms.type_synonym.translation_singular
+ Translation Singular
+
+
+
+
+ settings.synonyms.type_synonym.translation_plural
+ Translation Plural
+
+
+
+
+ settings.synonyms.type_synonym.add_entry
+ Add entry
+
+
+
+
+ settings.synonyms.type_synonym.remove_entry
+ Remove entry
+
+
+
+
+ settings.synonyms
+ Synonyms
+
+
+
+
+ settings.synonyms.help
+ The synonyms systems allow overriding how Part-DB call certain things. This can be useful, especially if Part-DB is used in a different context than electronics.
+Please note that this system is currently experimental, and the synonyms defined here might not show up at all places.
+
+
+
+
+ settings.synonyms.type_synonyms
+ Type synonyms
+
+
+
+
+ settings.synonyms.type_synonyms.help
+ Type synonyms allow you to replace the labels of built-in data types. For example, you can rename "Footprint" to something else.
+
+
+
+
+ {{part}}
+ Parts
+
+
+
+
+ log.element_edited.changed_fields.part_ipn_prefix
+ IPN prefix
+
+
+
+
+ part.labelp
+ Parts
+
+
diff --git a/translations/messages.es.xlf b/translations/messages.es.xlf
index fce38e52..57ac5c85 100644
--- a/translations/messages.es.xlf
+++ b/translations/messages.es.xlf
@@ -548,6 +548,12 @@
Unidad de medida
+
+
+ part_custom_state.caption
+ Estado personalizado del componente
+
+
Part-DB1\templates\AdminPages\StorelocationAdmin.html.twig:5
@@ -1842,6 +1848,66 @@ Subelementos serán desplazados hacia arriba.
Avanzado
+
+
+ part.edit.tab.advanced.ipn.commonSectionHeader
+ Sugerencias sin incremento de parte
+
+
+
+
+ part.edit.tab.advanced.ipn.partIncrementHeader
+ Sugerencias con incrementos numéricos de partes
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.description.current-increment
+ Especificación actual de IPN de la pieza
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.description.increment
+ Siguiente especificación de IPN posible basada en una descripción idéntica de la pieza
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix_empty.direct_category
+ El prefijo IPN de la categoría directa está vacío, especifíquelo en la categoría "%name%"
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.direct_category
+ Prefijo IPN de la categoría directa
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.direct_category.increment
+ Prefijo IPN de la categoría directa y un incremento específico de la pieza
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.hierarchical.no_increment
+ Prefijos IPN con orden jerárquico de categorías de prefijos principales
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.hierarchical.increment
+ Prefijos IPN con orden jerárquico de categorías de prefijos principales y un incremento específico para la parte
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.not_saved
+ Primero cree un componente y asígnele una categoría: con las categorías existentes y sus propios prefijos IPN, el identificador IPN para el componente puede ser sugerido automáticamente
+
+
Part-DB1\templates\Parts\edit\edit_part_info.html.twig:40
@@ -4830,6 +4896,12 @@ Subelementos serán desplazados hacia arriba.
Unidad de Medida
+
+
+ part.table.partCustomState
+ Estado personalizado del componente
+
+
Part-DB1\src\DataTables\PartsDataTable.php:236
@@ -5694,6 +5766,12 @@ Subelementos serán desplazados hacia arriba.
Unidad de medida
+
+
+ part.edit.partCustomState
+ Estado personalizado de la pieza
+
+
Part-DB1\src\Form\Part\PartBaseType.php:212
@@ -5981,6 +6059,12 @@ Subelementos serán desplazados hacia arriba.
Unidad de medida
+
+
+ part_custom_state.label
+ Estado personalizado de la pieza
+
+
Part-DB1\src\Services\ElementTypeNameGenerator.php:90
@@ -6224,6 +6308,12 @@ Subelementos serán desplazados hacia arriba.
Unidad de medida
+
+
+ tree.tools.edit.part_custom_state
+ Estado personalizado del componente
+
+
Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:203
@@ -6958,6 +7048,12 @@ Subelementos serán desplazados hacia arriba.
Filtro de nombre
+
+
+ category.edit.part_ipn_prefix
+ Prefijo de IPN de la pieza
+
+
obsolete
@@ -8494,6 +8590,12 @@ Elemento 3
Unidad de medida
+
+
+ perm.part_custom_states
+ Estado personalizado del componente
+
+
obsolete
@@ -10272,12 +10374,24 @@ Elemento 3
p.ej. "/Condensador \d+ nF/i"
+
+
+ category.edit.part_ipn_prefix.placeholder
+ p.ej. "B12A"
+
+
category.edit.partname_regex.help
Una expresión regular compatible con PCRE, la cual debe coincidir con el nombre de un componente.
+
+
+ category.edit.part_ipn_prefix.help
+ Un prefijo sugerido al ingresar el IPN de una parte.
+
+
entity.select.add_hint
@@ -10824,6 +10938,12 @@ Elemento 3
Unidad de medida
+
+
+ log.element_edited.changed_fields.partCustomState
+ Estado personalizado del componente
+
+
log.element_edited.changed_fields.expiration_date
@@ -11082,6 +11202,18 @@ Elemento 3
Editar Unidad de Medida
+
+
+ part_custom_state.new
+ Nuevo estado personalizado del componente
+
+
+
+
+ part_custom_state.edit
+ Editar estado personalizado del componente
+
+
user.aboutMe.label
diff --git a/translations/messages.fr.xlf b/translations/messages.fr.xlf
index 11f7da3d..8ed971b8 100644
--- a/translations/messages.fr.xlf
+++ b/translations/messages.fr.xlf
@@ -1,7 +1,7 @@
-
+
Part-DB1\templates\AdminPages\AttachmentTypeAdmin.html.twig:4
Part-DB1\templates\AdminPages\AttachmentTypeAdmin.html.twig:4
@@ -9,10 +9,10 @@
attachment_type.caption
- Type de fichiers pour la pièce jointe
+ Types pour fichiers joints
-
+
Part-DB1\templates\AdminPages\AttachmentTypeAdmin.html.twig:12
new
@@ -22,7 +22,7 @@
Modifier le type de pièce jointe
-
+
Part-DB1\templates\AdminPages\AttachmentTypeAdmin.html.twig:16
new
@@ -32,7 +32,7 @@
Nouveau type de pièce jointe
-
+
Part-DB1\templates\AdminPages\CategoryAdmin.html.twig:4
Part-DB1\templates\_sidebar.html.twig:22
@@ -51,7 +51,7 @@
Catégories
-
+
Part-DB1\templates\AdminPages\CategoryAdmin.html.twig:8
Part-DB1\templates\AdminPages\StorelocationAdmin.html.twig:19
@@ -64,7 +64,7 @@
Options
-
+
Part-DB1\templates\AdminPages\CategoryAdmin.html.twig:9
Part-DB1\templates\AdminPages\CompanyAdminBase.html.twig:15
@@ -77,7 +77,7 @@
Avancé
-
+
Part-DB1\templates\AdminPages\CategoryAdmin.html.twig:13
new
@@ -87,7 +87,7 @@
Éditer la catégorie
-
+
Part-DB1\templates\AdminPages\CategoryAdmin.html.twig:17
new
@@ -97,7 +97,7 @@
Nouvelle catégorie
-
+
Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:4
Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:4
@@ -107,7 +107,7 @@
Devise
-
+
Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:12
Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:12
@@ -117,7 +117,7 @@
Code ISO
-
+
Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:15
Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:15
@@ -127,17 +127,17 @@
Symbole de la devise
-
+
Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:29
new
currency.edit
- Éditer la devise
+ Editer la devise
-
+
Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:33
new
@@ -147,38 +147,7 @@
Nouvelle devise
-
-
- Part-DB1\templates\AdminPages\DeviceAdmin.html.twig:4
- Part-DB1\templates\AdminPages\DeviceAdmin.html.twig:4
- templates\AdminPages\DeviceAdmin.html.twig:4
-
-
- project.caption
- Projet
-
-
-
-
- Part-DB1\templates\AdminPages\DeviceAdmin.html.twig:8
- new
-
-
- project.edit
- Éditer le projet
-
-
-
-
- Part-DB1\templates\AdminPages\DeviceAdmin.html.twig:12
- new
-
-
- project.new
- Nouveau projet
-
-
-
+
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:19
Part-DB1\templates\_navbar_search.html.twig:67
@@ -201,7 +170,7 @@
Recherche
-
+
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:23
Part-DB1\templates\_sidebar.html.twig:3
@@ -217,7 +186,7 @@
Agrandir tout
-
+
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:27
Part-DB1\templates\_sidebar.html.twig:4
@@ -233,7 +202,7 @@
Réduire tout
-
+
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:54
Part-DB1\templates\Parts\info\_sidebar.html.twig:4
@@ -242,10 +211,10 @@
part.info.timetravel_hint
- C'est ainsi que le composant apparaissait avant le %timestamp%. <i>Veuillez noter que cette fonctionnalité est expérimentale, les informations ne sont peut-être pas correctes. </i>
+ C'est ainsi que le composant apparaissait avant le %timestamp%. <i>Veuillez noter que cette fonctionnalité est expérimentale, donc les infos ne sont peut-être pas correctes. </i>
-
+
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:60
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:60
@@ -256,7 +225,7 @@
Propriétés
-
+
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:61
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:61
@@ -267,7 +236,7 @@
Informations
-
+
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:63
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:63
@@ -278,7 +247,7 @@
Historique
-
+
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:66
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:66
@@ -289,7 +258,7 @@
Exporter
-
+
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:68
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:68
@@ -297,20 +266,20 @@
import_export.label
- Importer / exporter
+ Importer exporter
-
+
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:69
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:69
mass_creation.label
- Création en masse
+ Création multiple
-
+
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:82
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:82
@@ -321,7 +290,7 @@
Commun
-
+
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:86
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:86
@@ -331,7 +300,7 @@
Fichiers joints
-
+
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:90
@@ -340,7 +309,7 @@
Paramètres
-
+
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:179
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:167
@@ -351,17 +320,17 @@
Exporter tous les éléments
-
+
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:185
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:173
mass_creation.help
- Chaque ligne sera interprétée comme le nom d'un élément qui sera créé. Vous pouvez créer des structures imbriquées par indentations.
+ Chaque ligne sera interprétée comme le nom d'un élément qui sera créé.
-
+
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:45
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:45
@@ -372,7 +341,7 @@
Éditer l'élément "%name"
-
+
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:50
Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:50
@@ -383,7 +352,7 @@
Nouvel élément
-
+
Part-DB1\templates\AdminPages\FootprintAdmin.html.twig:4
Part-DB1\templates\_sidebar.html.twig:9
@@ -398,17 +367,17 @@
Empreintes
-
+
Part-DB1\templates\AdminPages\FootprintAdmin.html.twig:13
new
footprint.edit
- Éditer l'empreinte
+ Editer l'empreinte
-
+
Part-DB1\templates\AdminPages\FootprintAdmin.html.twig:17
new
@@ -418,7 +387,7 @@
Nouvelle empreinte
-
+
Part-DB1\templates\AdminPages\GroupAdmin.html.twig:4
Part-DB1\templates\AdminPages\GroupAdmin.html.twig:4
@@ -428,7 +397,7 @@
Groupes
-
+
Part-DB1\templates\AdminPages\GroupAdmin.html.twig:9
Part-DB1\templates\AdminPages\UserAdmin.html.twig:16
@@ -440,17 +409,17 @@
Permissions
-
+
Part-DB1\templates\AdminPages\GroupAdmin.html.twig:24
new
group.edit
- Éditer le groupe
+ Editer le groupe
-
+
Part-DB1\templates\AdminPages\GroupAdmin.html.twig:28
new
@@ -460,7 +429,7 @@
Nouveau groupe
-
+
Part-DB1\templates\AdminPages\LabelProfileAdmin.html.twig:4
@@ -469,7 +438,7 @@
Profil des étiquettes
-
+
Part-DB1\templates\AdminPages\LabelProfileAdmin.html.twig:8
@@ -478,7 +447,7 @@
Avancé
-
+
Part-DB1\templates\AdminPages\LabelProfileAdmin.html.twig:9
@@ -487,17 +456,17 @@
Commentaire
-
+
Part-DB1\templates\AdminPages\LabelProfileAdmin.html.twig:55
new
label_profile.edit
- Éditer profil d'étiquette
+ Editer profil d'étiquette
-
+
Part-DB1\templates\AdminPages\LabelProfileAdmin.html.twig:59
new
@@ -507,7 +476,7 @@
Nouveau profil d'étiquette
-
+
Part-DB1\templates\AdminPages\ManufacturerAdmin.html.twig:4
Part-DB1\templates\AdminPages\ManufacturerAdmin.html.twig:4
@@ -518,7 +487,7 @@
Fabricants
-
+
Part-DB1\templates\AdminPages\ManufacturerAdmin.html.twig:8
new
@@ -528,7 +497,7 @@
Modifiez le fabricant
-
+
Part-DB1\templates\AdminPages\ManufacturerAdmin.html.twig:12
new
@@ -538,7 +507,7 @@
Nouveau fabricant
-
+
Part-DB1\templates\AdminPages\MeasurementUnitAdmin.html.twig:4
Part-DB1\templates\AdminPages\MeasurementUnitAdmin.html.twig:4
@@ -548,7 +517,13 @@
Unité de mesure
-
+
+
+ part_custom_state.caption
+ État personnalisé du composant
+
+
+
Part-DB1\templates\AdminPages\StorelocationAdmin.html.twig:5
Part-DB1\templates\_sidebar.html.twig:8
@@ -563,7 +538,7 @@
Emplacement de stockage
-
+
Part-DB1\templates\AdminPages\StorelocationAdmin.html.twig:32
new
@@ -573,7 +548,7 @@
Modifier l'emplacement de stockage
-
+
Part-DB1\templates\AdminPages\StorelocationAdmin.html.twig:36
new
@@ -583,7 +558,7 @@
Nouvel emplacement de stockage
-
+
Part-DB1\templates\AdminPages\SupplierAdmin.html.twig:4
Part-DB1\templates\AdminPages\SupplierAdmin.html.twig:4
@@ -594,7 +569,7 @@
Fournisseurs
-
+
Part-DB1\templates\AdminPages\SupplierAdmin.html.twig:16
new
@@ -604,7 +579,7 @@
Modifier le fournisseur
-
+
Part-DB1\templates\AdminPages\SupplierAdmin.html.twig:20
new
@@ -614,7 +589,7 @@
Nouveau fournisseur
-
+
Part-DB1\templates\AdminPages\UserAdmin.html.twig:8
Part-DB1\templates\AdminPages\UserAdmin.html.twig:8
@@ -624,7 +599,7 @@
Utilisateurs
-
+
Part-DB1\templates\AdminPages\UserAdmin.html.twig:14
Part-DB1\templates\AdminPages\UserAdmin.html.twig:14
@@ -634,7 +609,7 @@
Configuration
-
+
Part-DB1\templates\AdminPages\UserAdmin.html.twig:15
Part-DB1\templates\AdminPages\UserAdmin.html.twig:15
@@ -644,7 +619,7 @@
Mot de passe
-
+
Part-DB1\templates\AdminPages\UserAdmin.html.twig:45
Part-DB1\templates\AdminPages\UserAdmin.html.twig:45
@@ -654,7 +629,7 @@
Authentification à deux facteurs
-
+
Part-DB1\templates\AdminPages\UserAdmin.html.twig:47
Part-DB1\templates\AdminPages\UserAdmin.html.twig:47
@@ -664,7 +639,7 @@
Application d'authentification active
-
+
Part-DB1\templates\AdminPages\UserAdmin.html.twig:48
Part-DB1\templates\Users\backup_codes.html.twig:15
@@ -678,7 +653,7 @@
Nombre de codes de secours restant
-
+
Part-DB1\templates\AdminPages\UserAdmin.html.twig:49
Part-DB1\templates\Users\backup_codes.html.twig:17
@@ -692,7 +667,7 @@
Date de génération des codes de secours
-
+
Part-DB1\templates\AdminPages\UserAdmin.html.twig:53
Part-DB1\templates\AdminPages\UserAdmin.html.twig:60
@@ -704,7 +679,7 @@
Méthode désactivée
-
+
Part-DB1\templates\AdminPages\UserAdmin.html.twig:56
Part-DB1\templates\AdminPages\UserAdmin.html.twig:56
@@ -714,17 +689,17 @@
Clés de sécurité actives
-
+
Part-DB1\templates\AdminPages\UserAdmin.html.twig:72
Part-DB1\templates\AdminPages\UserAdmin.html.twig:72
user.edit.tfa.disable_tfa_title
- Voulez-vous vraiment poursuivre ?
+ Voulez vous vraiment poursuivre ?
-
+
Part-DB1\templates\AdminPages\UserAdmin.html.twig:72
Part-DB1\templates\AdminPages\UserAdmin.html.twig:72
@@ -737,7 +712,7 @@ L'utilisateur devra configurer à nouveau toutes les méthodes d'authentificatio
<b>Ne faites ceci qu'en étant sûr de l'identité de l'utilisateur (ayant besoin d'aide),autrement le compte pourrai être compromis!</b>
-
+
Part-DB1\templates\AdminPages\UserAdmin.html.twig:73
Part-DB1\templates\AdminPages\UserAdmin.html.twig:73
@@ -747,7 +722,7 @@ L'utilisateur devra configurer à nouveau toutes les méthodes d'authentificatio
Désactiver toutes les méthodes d'authentification à deux facteurs
-
+
Part-DB1\templates\AdminPages\UserAdmin.html.twig:85
new
@@ -757,7 +732,7 @@ L'utilisateur devra configurer à nouveau toutes les méthodes d'authentificatio
Modifier l'utilisateur
-
+
Part-DB1\templates\AdminPages\UserAdmin.html.twig:89
new
@@ -767,7 +742,7 @@ L'utilisateur devra configurer à nouveau toutes les méthodes d'authentificatio
Nouvel utilisateur
-
+
Part-DB1\templates\AdminPages\_attachments.html.twig:4
Part-DB1\templates\Parts\edit\_attachments.html.twig:4
@@ -780,13 +755,21 @@ L'utilisateur devra configurer à nouveau toutes les méthodes d'authentificatio
Supprimer
-
+
+
+ Part-DB1\templates\AdminPages\_attachments.html.twig:41
+ Part-DB1\templates\Parts\edit\_attachments.html.twig:38
+ Part-DB1\templates\Parts\info\_attachments_info.html.twig:35
+ Part-DB1\src\DataTables\AttachmentDataTable.php:159
+ Part-DB1\templates\Parts\edit\_attachments.html.twig:38
+ Part-DB1\src\DataTables\AttachmentDataTable.php:159
+
- attachment.external_only
- Pièce jointe externe uniquement
+ attachment.external
+ Externe
-
+
Part-DB1\templates\AdminPages\_attachments.html.twig:49
Part-DB1\templates\Parts\edit\_attachments.html.twig:47
@@ -798,7 +781,7 @@ L'utilisateur devra configurer à nouveau toutes les méthodes d'authentificatio
Miniature du fichier joint
-
+
Part-DB1\templates\AdminPages\_attachments.html.twig:52
Part-DB1\templates\Parts\edit\_attachments.html.twig:50
@@ -808,11 +791,11 @@ L'utilisateur devra configurer à nouveau toutes les méthodes d'authentificatio
Part-DB1\templates\Parts\info\_attachments_info.html.twig:45
- attachment.view_local
- Vue locale de la pièce jointe
+ attachment.view
+ Afficher
-
+
Part-DB1\templates\AdminPages\_attachments.html.twig:58
Part-DB1\templates\Parts\edit\_attachments.html.twig:56
@@ -828,7 +811,7 @@ L'utilisateur devra configurer à nouveau toutes les méthodes d'authentificatio
Fichier introuvable
-
+
Part-DB1\templates\AdminPages\_attachments.html.twig:66
Part-DB1\templates\Parts\edit\_attachments.html.twig:64
@@ -840,7 +823,7 @@ L'utilisateur devra configurer à nouveau toutes les méthodes d'authentificatio
Fichier joint privé
-
+
Part-DB1\templates\AdminPages\_attachments.html.twig:79
Part-DB1\templates\Parts\edit\_attachments.html.twig:77
@@ -852,7 +835,7 @@ L'utilisateur devra configurer à nouveau toutes les méthodes d'authentificatio
Ajouter un fichier joint
-
+
Part-DB1\templates\AdminPages\_attachments.html.twig:84
Part-DB1\templates\Parts\edit\_attachments.html.twig:82
@@ -863,10 +846,10 @@ L'utilisateur devra configurer à nouveau toutes les méthodes d'authentificatio
part_lot.edit.delete.confirm
- Voulez-vous vraiment supprimer ce stock ? Cette action ne pourra pas être annulée !
+ Voulez vous vraiment supprimer ce stock ? Cette action ne pourra pas être annulée!
-
+
Part-DB1\templates\AdminPages\_delete_form.html.twig:2
Part-DB1\templates\AdminPages\_delete_form.html.twig:2
@@ -874,10 +857,10 @@ L'utilisateur devra configurer à nouveau toutes les méthodes d'authentificatio
entity.delete.confirm_title
- Voulez-vous vraiment supprimer %name% ?
+ Voulez vous vraiment supprimer %name%?
-
+
Part-DB1\templates\AdminPages\_delete_form.html.twig:3
Part-DB1\templates\AdminPages\_delete_form.html.twig:3
@@ -885,12 +868,12 @@ L'utilisateur devra configurer à nouveau toutes les méthodes d'authentificatio
entity.delete.message
- Cette action ne pourra pas être annulée !
+ Cette action ne pourra pas être annulée!
<br>
Les sous éléments seront déplacés vers le haut.
-
+
Part-DB1\templates\AdminPages\_delete_form.html.twig:11
Part-DB1\templates\AdminPages\_delete_form.html.twig:11
@@ -901,7 +884,7 @@ Les sous éléments seront déplacés vers le haut.
Supprimer l'élément
-
+
Part-DB1\templates\AdminPages\_delete_form.html.twig:16
Part-DB1\templates\Parts\info\_tools.html.twig:45
@@ -916,7 +899,7 @@ Les sous éléments seront déplacés vers le haut.
Éditer le commentaire
-
+
Part-DB1\templates\AdminPages\_delete_form.html.twig:24
Part-DB1\templates\AdminPages\_delete_form.html.twig:24
@@ -927,7 +910,7 @@ Les sous éléments seront déplacés vers le haut.
Suppression récursive (tous les sous éléments)
-
+
Part-DB1\templates\AdminPages\_duplicate.html.twig:3
@@ -936,7 +919,7 @@ Les sous éléments seront déplacés vers le haut.
Dupliquer l’élément
-
+
Part-DB1\templates\AdminPages\_export_form.html.twig:4
Part-DB1\src\Form\AdminPages\ImportType.php:76
@@ -950,7 +933,7 @@ Les sous éléments seront déplacés vers le haut.
Format de fichier
-
+
Part-DB1\templates\AdminPages\_export_form.html.twig:16
Part-DB1\templates\AdminPages\_export_form.html.twig:16
@@ -961,7 +944,7 @@ Les sous éléments seront déplacés vers le haut.
Niveau de verbosité
-
+
Part-DB1\templates\AdminPages\_export_form.html.twig:19
Part-DB1\templates\AdminPages\_export_form.html.twig:19
@@ -972,7 +955,7 @@ Les sous éléments seront déplacés vers le haut.
Simple
-
+
Part-DB1\templates\AdminPages\_export_form.html.twig:20
Part-DB1\templates\AdminPages\_export_form.html.twig:20
@@ -983,7 +966,7 @@ Les sous éléments seront déplacés vers le haut.
Étendu
-
+
Part-DB1\templates\AdminPages\_export_form.html.twig:21
Part-DB1\templates\AdminPages\_export_form.html.twig:21
@@ -994,7 +977,7 @@ Les sous éléments seront déplacés vers le haut.
Complet
-
+
Part-DB1\templates\AdminPages\_export_form.html.twig:31
Part-DB1\templates\AdminPages\_export_form.html.twig:31
@@ -1005,7 +988,7 @@ Les sous éléments seront déplacés vers le haut.
Exporter également les sous éléments
-
+
Part-DB1\templates\AdminPages\_export_form.html.twig:39
Part-DB1\templates\AdminPages\_export_form.html.twig:39
@@ -1016,7 +999,7 @@ Les sous éléments seront déplacés vers le haut.
Exporter
-
+
Part-DB1\templates\AdminPages\_info.html.twig:4
Part-DB1\templates\Parts\edit\edit_part_info.html.twig:12
@@ -1035,7 +1018,7 @@ Les sous éléments seront déplacés vers le haut.
ID
-
+
Part-DB1\templates\AdminPages\_info.html.twig:11
Part-DB1\templates\Parts\info\_attachments_info.html.twig:76
@@ -1059,7 +1042,7 @@ Les sous éléments seront déplacés vers le haut.
Créé le
-
+
Part-DB1\templates\AdminPages\_info.html.twig:25
Part-DB1\templates\Parts\info\_extended_infos.html.twig:21
@@ -1077,7 +1060,7 @@ Les sous éléments seront déplacés vers le haut.
Dernière modification
-
+
Part-DB1\templates\AdminPages\_info.html.twig:38
Part-DB1\templates\AdminPages\_info.html.twig:38
@@ -1087,7 +1070,7 @@ Les sous éléments seront déplacés vers le haut.
Nombre de composants avec cet élément
-
+
Part-DB1\templates\AdminPages\_parameters.html.twig:6
Part-DB1\templates\helper.twig:125
@@ -1098,7 +1081,7 @@ Les sous éléments seront déplacés vers le haut.
Paramètre
-
+
Part-DB1\templates\AdminPages\_parameters.html.twig:7
Part-DB1\templates\Parts\edit\_specifications.html.twig:7
@@ -1108,7 +1091,7 @@ Les sous éléments seront déplacés vers le haut.
Symbole
-
+
Part-DB1\templates\AdminPages\_parameters.html.twig:8
Part-DB1\templates\Parts\edit\_specifications.html.twig:8
@@ -1118,17 +1101,17 @@ Les sous éléments seront déplacés vers le haut.
Min.
-
+
Part-DB1\templates\AdminPages\_parameters.html.twig:9
Part-DB1\templates\Parts\edit\_specifications.html.twig:9
specifications.value_typ
- Type.
+ Typ.
-
+
Part-DB1\templates\AdminPages\_parameters.html.twig:10
Part-DB1\templates\Parts\edit\_specifications.html.twig:10
@@ -1138,7 +1121,7 @@ Les sous éléments seront déplacés vers le haut.
Max.
-
+
Part-DB1\templates\AdminPages\_parameters.html.twig:11
Part-DB1\templates\Parts\edit\_specifications.html.twig:11
@@ -1148,7 +1131,7 @@ Les sous éléments seront déplacés vers le haut.
Unité
-
+
Part-DB1\templates\AdminPages\_parameters.html.twig:12
Part-DB1\templates\Parts\edit\_specifications.html.twig:12
@@ -1158,7 +1141,7 @@ Les sous éléments seront déplacés vers le haut.
Texte
-
+
Part-DB1\templates\AdminPages\_parameters.html.twig:13
Part-DB1\templates\Parts\edit\_specifications.html.twig:13
@@ -1168,7 +1151,7 @@ Les sous éléments seront déplacés vers le haut.
Groupe
-
+
Part-DB1\templates\AdminPages\_parameters.html.twig:26
Part-DB1\templates\Parts\edit\_specifications.html.twig:26
@@ -1178,7 +1161,7 @@ Les sous éléments seront déplacés vers le haut.
Nouveau paramètre
-
+
Part-DB1\templates\AdminPages\_parameters.html.twig:31
Part-DB1\templates\Parts\edit\_specifications.html.twig:31
@@ -1188,7 +1171,7 @@ Les sous éléments seront déplacés vers le haut.
Souhaitez-vous vraiment supprimer ce paramètre ?
-
+
Part-DB1\templates\attachment_list.html.twig:3
Part-DB1\templates\attachment_list.html.twig:3
@@ -1198,7 +1181,7 @@ Les sous éléments seront déplacés vers le haut.
Liste des fichiers joints
-
+
Part-DB1\templates\attachment_list.html.twig:10
Part-DB1\templates\LogSystem\_log_table.html.twig:8
@@ -1212,7 +1195,7 @@ Les sous éléments seront déplacés vers le haut.
Chargement
-
+
Part-DB1\templates\attachment_list.html.twig:11
Part-DB1\templates\LogSystem\_log_table.html.twig:9
@@ -1226,7 +1209,7 @@ Les sous éléments seront déplacés vers le haut.
Cela peut prendre un moment.Si ce message ne disparaît pas, essayez de recharger la page.
-
+
Part-DB1\templates\base.html.twig:68
Part-DB1\templates\base.html.twig:68
@@ -1234,20 +1217,21 @@ Les sous éléments seront déplacés vers le haut.
vendor.base.javascript_hint
- Activez JavaScript pour profiter de toutes les fonctionnalités !
+ Activez Javascipt pour profiter de toutes les fonctionnalités!
-
+
Part-DB1\templates\base.html.twig:73
Part-DB1\templates\base.html.twig:73
sidebar.big.toggle
- Afficher / Cacher le panneau latéral
+ Afficher/Cacher le panneau latéral
+Show/Hide sidebar
-
+
Part-DB1\templates\base.html.twig:95
Part-DB1\templates\base.html.twig:95
@@ -1255,10 +1239,10 @@ Les sous éléments seront déplacés vers le haut.
loading.caption
- Chargement :
+ Chargement:
-
+
Part-DB1\templates\base.html.twig:96
Part-DB1\templates\base.html.twig:96
@@ -1269,7 +1253,7 @@ Les sous éléments seront déplacés vers le haut.
Cela peut prendre un moment.Si ce message ne disparaît pas, essayez de recharger la page.
-
+
Part-DB1\templates\base.html.twig:101
Part-DB1\templates\base.html.twig:101
@@ -1280,7 +1264,7 @@ Les sous éléments seront déplacés vers le haut.
Chargement...
-
+
Part-DB1\templates\base.html.twig:112
Part-DB1\templates\base.html.twig:112
@@ -1291,7 +1275,7 @@ Les sous éléments seront déplacés vers le haut.
Retour en haut de page
-
+
Part-DB1\templates\Form\permissionLayout.html.twig:35
Part-DB1\templates\Form\permissionLayout.html.twig:35
@@ -1301,7 +1285,7 @@ Les sous éléments seront déplacés vers le haut.
Permissions
-
+
Part-DB1\templates\Form\permissionLayout.html.twig:36
Part-DB1\templates\Form\permissionLayout.html.twig:36
@@ -1311,17 +1295,17 @@ Les sous éléments seront déplacés vers le haut.
Valeur
-
+
Part-DB1\templates\Form\permissionLayout.html.twig:53
Part-DB1\templates\Form\permissionLayout.html.twig:53
permission.legend.title
- Explication des états :
+ Explication des états:
-
+
Part-DB1\templates\Form\permissionLayout.html.twig:57
Part-DB1\templates\Form\permissionLayout.html.twig:57
@@ -1331,7 +1315,7 @@ Les sous éléments seront déplacés vers le haut.
Interdire
-
+
Part-DB1\templates\Form\permissionLayout.html.twig:61
Part-DB1\templates\Form\permissionLayout.html.twig:61
@@ -1341,7 +1325,7 @@ Les sous éléments seront déplacés vers le haut.
Autoriser
-
+
Part-DB1\templates\Form\permissionLayout.html.twig:65
Part-DB1\templates\Form\permissionLayout.html.twig:65
@@ -1351,7 +1335,7 @@ Les sous éléments seront déplacés vers le haut.
Hériter du groupe (parent)
-
+
Part-DB1\templates\helper.twig:3
Part-DB1\templates\helper.twig:3
@@ -1361,7 +1345,7 @@ Les sous éléments seront déplacés vers le haut.
Vrai
-
+
Part-DB1\templates\helper.twig:5
Part-DB1\templates\helper.twig:5
@@ -1371,7 +1355,7 @@ Les sous éléments seront déplacés vers le haut.
Faux
-
+
Part-DB1\templates\helper.twig:92
Part-DB1\templates\helper.twig:87
@@ -1381,7 +1365,7 @@ Les sous éléments seront déplacés vers le haut.
Oui
-
+
Part-DB1\templates\helper.twig:94
Part-DB1\templates\helper.twig:89
@@ -1391,7 +1375,7 @@ Les sous éléments seront déplacés vers le haut.
Non
-
+
Part-DB1\templates\helper.twig:126
@@ -1400,7 +1384,7 @@ Les sous éléments seront déplacés vers le haut.
Valeur
-
+
Part-DB1\templates\homepage.html.twig:7
Part-DB1\templates\homepage.html.twig:7
@@ -1411,7 +1395,7 @@ Les sous éléments seront déplacés vers le haut.
Version
-
+
Part-DB1\templates\homepage.html.twig:22
Part-DB1\templates\homepage.html.twig:22
@@ -1419,10 +1403,10 @@ Les sous éléments seront déplacés vers le haut.
homepage.license
- Information de licence
+ Information de license
-
+
Part-DB1\templates\homepage.html.twig:31
Part-DB1\templates\homepage.html.twig:31
@@ -1433,7 +1417,7 @@ Les sous éléments seront déplacés vers le haut.
Page du projet
-
+
Part-DB1\templates\homepage.html.twig:31
Part-DB1\templates\homepage.html.twig:31
@@ -1441,10 +1425,10 @@ Les sous éléments seront déplacés vers le haut.
homepage.github.text
- Retrouvez les téléchargements, report de bugs, to-do-list, etc. sur <a href="%href%" class="link-external" target="_blank">la page du projet GitHub</a>
+ Retrouvez les téléchargements, report de bugs, to-do-list etc. sur <a href="%href%" class="link-external" target="_blank">la page du projet GitHub</a>
-
+
Part-DB1\templates\homepage.html.twig:32
Part-DB1\templates\homepage.html.twig:32
@@ -1455,7 +1439,7 @@ Les sous éléments seront déplacés vers le haut.
Aide
-
+
Part-DB1\templates\homepage.html.twig:32
Part-DB1\templates\homepage.html.twig:32
@@ -1466,7 +1450,7 @@ Les sous éléments seront déplacés vers le haut.
De l'aide et des conseils sont disponibles sur le Wiki de la <a href="%href%" class="link-external" target="_blank">page GitHub</a>
-
+
Part-DB1\templates\homepage.html.twig:33
Part-DB1\templates\homepage.html.twig:33
@@ -1477,7 +1461,7 @@ Les sous éléments seront déplacés vers le haut.
Forum
-
+
Part-DB1\templates\homepage.html.twig:45
Part-DB1\templates\homepage.html.twig:45
@@ -1488,7 +1472,7 @@ Les sous éléments seront déplacés vers le haut.
Activité récente
-
+
Part-DB1\templates\LabelSystem\dialog.html.twig:3
Part-DB1\templates\LabelSystem\dialog.html.twig:6
@@ -1498,7 +1482,7 @@ Les sous éléments seront déplacés vers le haut.
Générateur d'étiquettes
-
+
Part-DB1\templates\LabelSystem\dialog.html.twig:16
@@ -1507,7 +1491,7 @@ Les sous éléments seront déplacés vers le haut.
Commun
-
+
Part-DB1\templates\LabelSystem\dialog.html.twig:20
@@ -1516,7 +1500,7 @@ Les sous éléments seront déplacés vers le haut.
Avancé
-
+
Part-DB1\templates\LabelSystem\dialog.html.twig:24
@@ -1525,7 +1509,7 @@ Les sous éléments seront déplacés vers le haut.
Profils
-
+
Part-DB1\templates\LabelSystem\dialog.html.twig:58
@@ -1534,7 +1518,7 @@ Les sous éléments seront déplacés vers le haut.
Profil actuellement sélectionné
-
+
Part-DB1\templates\LabelSystem\dialog.html.twig:62
@@ -1543,7 +1527,7 @@ Les sous éléments seront déplacés vers le haut.
Modifier le profil
-
+
Part-DB1\templates\LabelSystem\dialog.html.twig:75
@@ -1552,7 +1536,7 @@ Les sous éléments seront déplacés vers le haut.
Charger le profil
-
+
Part-DB1\templates\LabelSystem\dialog.html.twig:102
@@ -1561,7 +1545,7 @@ Les sous éléments seront déplacés vers le haut.
Télécharger
-
+
Part-DB1\templates\LabelSystem\dropdown_macro.html.twig:3
Part-DB1\templates\LabelSystem\dropdown_macro.html.twig:5
@@ -1571,7 +1555,7 @@ Les sous éléments seront déplacés vers le haut.
Générer une étiquette
-
+
Part-DB1\templates\LabelSystem\dropdown_macro.html.twig:20
@@ -1580,7 +1564,7 @@ Les sous éléments seront déplacés vers le haut.
Nouvelle étiquette vide
-
+
Part-DB1\templates\LabelSystem\Scanner\dialog.html.twig:3
@@ -1589,7 +1573,7 @@ Les sous éléments seront déplacés vers le haut.
Lecteur d'étiquettes
-
+
Part-DB1\templates\LabelSystem\Scanner\dialog.html.twig:7
@@ -1598,7 +1582,7 @@ Les sous éléments seront déplacés vers le haut.
Aucune webcam trouvée
-
+
Part-DB1\templates\LabelSystem\Scanner\dialog.html.twig:7
@@ -1607,7 +1591,7 @@ Les sous éléments seront déplacés vers le haut.
Vous devez disposer d'une webcam et donner l'autorisation d'utiliser la fonction de scanner. Vous pouvez entrer le code à barres manuellement ci-dessous.
-
+
Part-DB1\templates\LabelSystem\Scanner\dialog.html.twig:27
@@ -1616,7 +1600,7 @@ Les sous éléments seront déplacés vers le haut.
Sélectionnez une source
-
+
Part-DB1\templates\LogSystem\log_list.html.twig:3
Part-DB1\templates\LogSystem\log_list.html.twig:3
@@ -1626,7 +1610,7 @@ Les sous éléments seront déplacés vers le haut.
Journal système
-
+
Part-DB1\templates\LogSystem\_log_table.html.twig:1
Part-DB1\templates\LogSystem\_log_table.html.twig:1
@@ -1637,7 +1621,7 @@ Les sous éléments seront déplacés vers le haut.
Annuler le changement / revenir à une date antérieure ?
-
+
Part-DB1\templates\LogSystem\_log_table.html.twig:2
Part-DB1\templates\LogSystem\_log_table.html.twig:2
@@ -1648,7 +1632,7 @@ Les sous éléments seront déplacés vers le haut.
Voulez-vous annuler la modification donnée / réinitialiser l'élément à une date donnée ?
-
+
Part-DB1\templates\mail\base.html.twig:24
Part-DB1\templates\mail\base.html.twig:24
@@ -1658,7 +1642,7 @@ Les sous éléments seront déplacés vers le haut.
Cet email a été envoyé automatiquement par
-
+
Part-DB1\templates\mail\base.html.twig:24
Part-DB1\templates\mail\base.html.twig:24
@@ -1668,7 +1652,7 @@ Les sous éléments seront déplacés vers le haut.
Ne répondez pas à cet email.
-
+
Part-DB1\templates\mail\pw_reset.html.twig:6
Part-DB1\templates\mail\pw_reset.html.twig:6
@@ -1678,7 +1662,7 @@ Les sous éléments seront déplacés vers le haut.
Bonjour %name%
-
+
Part-DB1\templates\mail\pw_reset.html.twig:7
Part-DB1\templates\mail\pw_reset.html.twig:7
@@ -1688,7 +1672,7 @@ Les sous éléments seront déplacés vers le haut.
Quelqu’un (surement vous) a demandé une réinitialisation de votre mot de passe.Si ce n'est pas le cas, ignorez simplement cet email.
-
+
Part-DB1\templates\mail\pw_reset.html.twig:9
Part-DB1\templates\mail\pw_reset.html.twig:9
@@ -1698,7 +1682,7 @@ Les sous éléments seront déplacés vers le haut.
Cliquez ici pour réinitialiser votre mot de passe
-
+
Part-DB1\templates\mail\pw_reset.html.twig:11
Part-DB1\templates\mail\pw_reset.html.twig:11
@@ -1708,7 +1692,7 @@ Les sous éléments seront déplacés vers le haut.
Si cela ne fonctionne pas pour vous, allez à <a href="%url%">%url%</a> et entrez les informations suivantes
-
+
Part-DB1\templates\mail\pw_reset.html.twig:16
Part-DB1\templates\mail\pw_reset.html.twig:16
@@ -1718,7 +1702,7 @@ Les sous éléments seront déplacés vers le haut.
Nom d'utilisateur
-
+
Part-DB1\templates\mail\pw_reset.html.twig:19
Part-DB1\templates\mail\pw_reset.html.twig:19
@@ -1728,7 +1712,7 @@ Les sous éléments seront déplacés vers le haut.
Jeton
-
+
Part-DB1\templates\mail\pw_reset.html.twig:24
Part-DB1\templates\mail\pw_reset.html.twig:24
@@ -1738,7 +1722,7 @@ Les sous éléments seront déplacés vers le haut.
Le jeton de réinitialisation sera valable jusqu'au <i>%date%</i>.
-
+
Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:18
Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:58
@@ -1750,7 +1734,7 @@ Les sous éléments seront déplacés vers le haut.
Supprimer
-
+
Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:39
Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:39
@@ -1760,7 +1744,7 @@ Les sous éléments seront déplacés vers le haut.
Quantité minimale de commande
-
+
Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:40
Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:40
@@ -1770,7 +1754,7 @@ Les sous éléments seront déplacés vers le haut.
Prix
-
+
Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:41
Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:41
@@ -1780,7 +1764,7 @@ Les sous éléments seront déplacés vers le haut.
Pour la quantité
-
+
Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:54
Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:54
@@ -1790,7 +1774,7 @@ Les sous éléments seront déplacés vers le haut.
Ajouter prix
-
+
Part-DB1\templates\Parts\edit\edit_part_info.html.twig:4
Part-DB1\templates\Parts\edit\edit_part_info.html.twig:4
@@ -1801,7 +1785,7 @@ Les sous éléments seront déplacés vers le haut.
Éditer le composant
-
+
Part-DB1\templates\Parts\edit\edit_part_info.html.twig:9
Part-DB1\templates\Parts\edit\edit_part_info.html.twig:9
@@ -1812,7 +1796,7 @@ Les sous éléments seront déplacés vers le haut.
Éditer le composant
-
+
Part-DB1\templates\Parts\edit\edit_part_info.html.twig:22
Part-DB1\templates\Parts\edit\edit_part_info.html.twig:22
@@ -1822,7 +1806,7 @@ Les sous éléments seront déplacés vers le haut.
Général
-
+
Part-DB1\templates\Parts\edit\edit_part_info.html.twig:28
Part-DB1\templates\Parts\edit\edit_part_info.html.twig:28
@@ -1832,7 +1816,7 @@ Les sous éléments seront déplacés vers le haut.
Fabricant
-
+
Part-DB1\templates\Parts\edit\edit_part_info.html.twig:34
Part-DB1\templates\Parts\edit\edit_part_info.html.twig:34
@@ -1842,7 +1826,67 @@ Les sous éléments seront déplacés vers le haut.
Avancé
-
+
+
+ part.edit.tab.advanced.ipn.commonSectionHeader
+ Suggestions sans incrément de partie
+
+
+
+
+ part.edit.tab.advanced.ipn.partIncrementHeader
+ Propositions avec incréments numériques de parties
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.description.current-increment
+ Spécification IPN actuelle pour la pièce
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.description.increment
+ Prochaine spécification IPN possible basée sur une description identique de la pièce
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix_empty.direct_category
+ Le préfixe IPN de la catégorie directe est vide, veuillez le spécifier dans la catégorie "%name%"
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.direct_category
+ Préfixe IPN de la catégorie directe
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.direct_category.increment
+ Préfixe IPN de la catégorie directe et d'un incrément spécifique à la partie
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.hierarchical.no_increment
+ Préfixes IPN avec un ordre hiérarchique des catégories des préfixes parents
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.hierarchical.increment
+ Préfixes IPN avec un ordre hiérarchique des catégories des préfixes parents et un incrément spécifique à la pièce
+
+
+
+
+ part.edit.tab.advanced.ipn.prefix.not_saved
+ Créez d'abord une pièce et assignez-la à une catégorie : avec les catégories existantes et leurs propres préfixes IPN, l'identifiant IPN pour la pièce peut être proposé automatiquement
+
+
+
Part-DB1\templates\Parts\edit\edit_part_info.html.twig:40
Part-DB1\templates\Parts\edit\edit_part_info.html.twig:40
@@ -1852,7 +1896,7 @@ Les sous éléments seront déplacés vers le haut.
Stocks
-
+
Part-DB1\templates\Parts\edit\edit_part_info.html.twig:46
Part-DB1\templates\Parts\edit\edit_part_info.html.twig:46
@@ -1862,7 +1906,7 @@ Les sous éléments seront déplacés vers le haut.
Fichiers joints
-
+
Part-DB1\templates\Parts\edit\edit_part_info.html.twig:52
Part-DB1\templates\Parts\edit\edit_part_info.html.twig:52
@@ -1872,7 +1916,7 @@ Les sous éléments seront déplacés vers le haut.
Informations pour la commande
-
+
Part-DB1\templates\Parts\edit\edit_part_info.html.twig:58
@@ -1881,7 +1925,7 @@ Les sous éléments seront déplacés vers le haut.
Caractéristiques
-
+
Part-DB1\templates\Parts\edit\edit_part_info.html.twig:64
Part-DB1\templates\Parts\edit\edit_part_info.html.twig:58
@@ -1891,7 +1935,7 @@ Les sous éléments seront déplacés vers le haut.
Commentaire
-
+
Part-DB1\templates\Parts\edit\new_part.html.twig:8
Part-DB1\templates\Parts\edit\new_part.html.twig:8
@@ -1902,7 +1946,7 @@ Les sous éléments seront déplacés vers le haut.
Créer un nouveau composant
-
+
Part-DB1\templates\Parts\edit\_lots.html.twig:5
Part-DB1\templates\Parts\edit\_lots.html.twig:5
@@ -1912,7 +1956,7 @@ Les sous éléments seront déplacés vers le haut.
Supprimer
-
+
Part-DB1\templates\Parts\edit\_lots.html.twig:28
Part-DB1\templates\Parts\edit\_lots.html.twig:28
@@ -1922,7 +1966,7 @@ Les sous éléments seront déplacés vers le haut.
Créer un inventaire
-
+
Part-DB1\templates\Parts\edit\_orderdetails.html.twig:13
Part-DB1\templates\Parts\edit\_orderdetails.html.twig:13
@@ -1932,7 +1976,7 @@ Les sous éléments seront déplacés vers le haut.
Ajouter un fournisseur
-
+
Part-DB1\templates\Parts\edit\_orderdetails.html.twig:18
Part-DB1\templates\Parts\edit\_orderdetails.html.twig:18
@@ -1942,7 +1986,7 @@ Les sous éléments seront déplacés vers le haut.
Voulez-vous vraiment supprimer ce prix ? Cela ne peut pas être défait !
-
+
Part-DB1\templates\Parts\edit\_orderdetails.html.twig:62
Part-DB1\templates\Parts\edit\_orderdetails.html.twig:61
@@ -1952,7 +1996,7 @@ Les sous éléments seront déplacés vers le haut.
Voulez-vous vraiment supprimer ce fournisseur ? Cela ne peut pas être défait !
-
+
Part-DB1\templates\Parts\info\show_part_info.html.twig:4
Part-DB1\templates\Parts\info\show_part_info.html.twig:19
@@ -1966,7 +2010,7 @@ Les sous éléments seront déplacés vers le haut.
Informations détaillées pour
-
+
Part-DB1\templates\Parts\info\show_part_info.html.twig:47
Part-DB1\templates\Parts\info\show_part_info.html.twig:47
@@ -1976,7 +2020,7 @@ Les sous éléments seront déplacés vers le haut.
Stocks
-
+
Part-DB1\templates\Parts\info\show_part_info.html.twig:56
Part-DB1\templates\Parts\lists\_info_card.html.twig:43
@@ -1991,7 +2035,7 @@ Les sous éléments seront déplacés vers le haut.
Commentaire
-
+
Part-DB1\templates\Parts\info\show_part_info.html.twig:64
@@ -2000,7 +2044,7 @@ Les sous éléments seront déplacés vers le haut.
Caractéristiques
-
+
Part-DB1\templates\Parts\info\show_part_info.html.twig:74
Part-DB1\templates\Parts\info\show_part_info.html.twig:64
@@ -2011,7 +2055,7 @@ Les sous éléments seront déplacés vers le haut.
Fichiers joints
-
+
Part-DB1\templates\Parts\info\show_part_info.html.twig:83
Part-DB1\templates\Parts\info\show_part_info.html.twig:71
@@ -2022,7 +2066,7 @@ Les sous éléments seront déplacés vers le haut.
Informations de commande
-
+
Part-DB1\templates\Parts\info\show_part_info.html.twig:91
Part-DB1\templates\Parts\info\show_part_info.html.twig:78
@@ -2033,7 +2077,7 @@ Les sous éléments seront déplacés vers le haut.
Historique
-
+
Part-DB1\templates\Parts\info\show_part_info.html.twig:97
Part-DB1\templates\_sidebar.html.twig:54
@@ -2052,7 +2096,7 @@ Les sous éléments seront déplacés vers le haut.
Outils
-
+
Part-DB1\templates\Parts\info\show_part_info.html.twig:103
Part-DB1\templates\Parts\info\show_part_info.html.twig:90
@@ -2062,7 +2106,7 @@ Les sous éléments seront déplacés vers le haut.
Informations complémentaires
-
+
Part-DB1\templates\Parts\info\_attachments_info.html.twig:7
Part-DB1\templates\Parts\info\_attachments_info.html.twig:7
@@ -2072,7 +2116,7 @@ Les sous éléments seront déplacés vers le haut.
Nom
-
+
Part-DB1\templates\Parts\info\_attachments_info.html.twig:8
Part-DB1\templates\Parts\info\_attachments_info.html.twig:8
@@ -2082,7 +2126,7 @@ Les sous éléments seront déplacés vers le haut.
Type de fichier joint
-
+
Part-DB1\templates\Parts\info\_attachments_info.html.twig:9
Part-DB1\templates\Parts\info\_attachments_info.html.twig:9
@@ -2092,7 +2136,7 @@ Les sous éléments seront déplacés vers le haut.
Nom du fichier
-
+
Part-DB1\templates\Parts\info\_attachments_info.html.twig:10
Part-DB1\templates\Parts\info\_attachments_info.html.twig:10
@@ -2102,7 +2146,7 @@ Les sous éléments seront déplacés vers le haut.
Taille du fichier
-
+
Part-DB1\templates\Parts\info\_attachments_info.html.twig:54
@@ -2111,17 +2155,17 @@ Les sous éléments seront déplacés vers le haut.
Aperçu de l'image
-
+
Part-DB1\templates\Parts\info\_attachments_info.html.twig:67
Part-DB1\templates\Parts\info\_attachments_info.html.twig:50
- attachment.download_local
- Télécharger la pièce jointe locale
+ attachment.download
+ Téléchargement
-
+
Part-DB1\templates\Parts\info\_extended_infos.html.twig:11
Part-DB1\templates\Parts\info\_extended_infos.html.twig:11
@@ -2132,7 +2176,7 @@ Les sous éléments seront déplacés vers le haut.
Utilisateur qui a créé ce composant
-
+
Part-DB1\templates\Parts\info\_extended_infos.html.twig:13
Part-DB1\templates\Parts\info\_extended_infos.html.twig:28
@@ -2146,7 +2190,7 @@ Les sous éléments seront déplacés vers le haut.
Inconnu
-
+
Part-DB1\templates\Parts\info\_extended_infos.html.twig:15
Part-DB1\templates\Parts\info\_extended_infos.html.twig:30
@@ -2159,7 +2203,7 @@ Les sous éléments seront déplacés vers le haut.
Accès refusé
-
+
Part-DB1\templates\Parts\info\_extended_infos.html.twig:26
Part-DB1\templates\Parts\info\_extended_infos.html.twig:26
@@ -2170,7 +2214,7 @@ Les sous éléments seront déplacés vers le haut.
Utilisateur qui a édité ce composant en dernier
-
+
Part-DB1\templates\Parts\info\_extended_infos.html.twig:41
Part-DB1\templates\Parts\info\_extended_infos.html.twig:41
@@ -2180,7 +2224,7 @@ Les sous éléments seront déplacés vers le haut.
Favoris
-
+
Part-DB1\templates\Parts\info\_extended_infos.html.twig:46
Part-DB1\templates\Parts\info\_extended_infos.html.twig:46
@@ -2190,7 +2234,7 @@ Les sous éléments seront déplacés vers le haut.
Quantité minimale de commande
-
+
Part-DB1\templates\Parts\info\_main_infos.html.twig:8
Part-DB1\templates\_navbar_search.html.twig:46
@@ -2207,7 +2251,7 @@ Les sous éléments seront déplacés vers le haut.
Fabricant
-
+
Part-DB1\templates\Parts\info\_main_infos.html.twig:24
Part-DB1\templates\_navbar_search.html.twig:11
@@ -2219,7 +2263,7 @@ Les sous éléments seront déplacés vers le haut.
Nom
-
+
Part-DB1\templates\Parts\info\_main_infos.html.twig:27
Part-DB1\templates\Parts\info\_main_infos.html.twig:27
@@ -2230,7 +2274,7 @@ Les sous éléments seront déplacés vers le haut.
Retour à la version actuelle
-
+
Part-DB1\templates\Parts\info\_main_infos.html.twig:32
Part-DB1\templates\_navbar_search.html.twig:19
@@ -2245,7 +2289,7 @@ Les sous éléments seront déplacés vers le haut.
Description
-
+
Part-DB1\templates\Parts\info\_main_infos.html.twig:34
Part-DB1\templates\_navbar_search.html.twig:15
@@ -2262,7 +2306,7 @@ Les sous éléments seront déplacés vers le haut.
Catégorie
-
+
Part-DB1\templates\Parts\info\_main_infos.html.twig:39
Part-DB1\templates\Parts\info\_main_infos.html.twig:39
@@ -2274,7 +2318,7 @@ Les sous éléments seront déplacés vers le haut.
En stock
-
+
Part-DB1\templates\Parts\info\_main_infos.html.twig:41
Part-DB1\templates\Parts\info\_main_infos.html.twig:41
@@ -2286,7 +2330,7 @@ Les sous éléments seront déplacés vers le haut.
Stock minimum
-
+
Part-DB1\templates\Parts\info\_main_infos.html.twig:45
Part-DB1\templates\_navbar_search.html.twig:52
@@ -2302,7 +2346,7 @@ Les sous éléments seront déplacés vers le haut.
Empreinte
-
+
Part-DB1\templates\Parts\info\_main_infos.html.twig:56
Part-DB1\templates\Parts\info\_main_infos.html.twig:59
@@ -2315,7 +2359,7 @@ Les sous éléments seront déplacés vers le haut.
Prix moyen
-
+
Part-DB1\templates\Parts\info\_order_infos.html.twig:5
Part-DB1\templates\Parts\info\_order_infos.html.twig:5
@@ -2325,17 +2369,17 @@ Les sous éléments seront déplacés vers le haut.
Nom
-
+
Part-DB1\templates\Parts\info\_order_infos.html.twig:6
Part-DB1\templates\Parts\info\_order_infos.html.twig:6
part.supplier.partnr
- Lien/Code Fournisseur
+ Lien/Code cmd.
-
+
Part-DB1\templates\Parts\info\_order_infos.html.twig:28
Part-DB1\templates\Parts\info\_order_infos.html.twig:28
@@ -2345,7 +2389,7 @@ Les sous éléments seront déplacés vers le haut.
Nombre minimum
-
+
Part-DB1\templates\Parts\info\_order_infos.html.twig:29
Part-DB1\templates\Parts\info\_order_infos.html.twig:29
@@ -2355,7 +2399,7 @@ Les sous éléments seront déplacés vers le haut.
Prix
-
+
Part-DB1\templates\Parts\info\_order_infos.html.twig:31
Part-DB1\templates\Parts\info\_order_infos.html.twig:31
@@ -2365,7 +2409,7 @@ Les sous éléments seront déplacés vers le haut.
Prix unitaire
-
+
Part-DB1\templates\Parts\info\_order_infos.html.twig:71
Part-DB1\templates\Parts\info\_order_infos.html.twig:71
@@ -2375,7 +2419,7 @@ Les sous éléments seront déplacés vers le haut.
Éditer
-
+
Part-DB1\templates\Parts\info\_order_infos.html.twig:72
Part-DB1\templates\Parts\info\_order_infos.html.twig:72
@@ -2385,7 +2429,7 @@ Les sous éléments seront déplacés vers le haut.
Supprimer
-
+
Part-DB1\templates\Parts\info\_part_lots.html.twig:7
Part-DB1\templates\Parts\info\_part_lots.html.twig:6
@@ -2395,7 +2439,7 @@ Les sous éléments seront déplacés vers le haut.
Description
-
+
Part-DB1\templates\Parts\info\_part_lots.html.twig:8
Part-DB1\templates\Parts\info\_part_lots.html.twig:7
@@ -2405,7 +2449,7 @@ Les sous éléments seront déplacés vers le haut.
Emplacement de stockage
-
+
Part-DB1\templates\Parts\info\_part_lots.html.twig:9
Part-DB1\templates\Parts\info\_part_lots.html.twig:8
@@ -2415,7 +2459,7 @@ Les sous éléments seront déplacés vers le haut.
Quantité
-
+
Part-DB1\templates\Parts\info\_part_lots.html.twig:24
Part-DB1\templates\Parts\info\_part_lots.html.twig:22
@@ -2425,7 +2469,7 @@ Les sous éléments seront déplacés vers le haut.
Emplacement de stockage inconnu
-
+
Part-DB1\templates\Parts\info\_part_lots.html.twig:31
Part-DB1\templates\Parts\info\_part_lots.html.twig:29
@@ -2435,7 +2479,7 @@ Les sous éléments seront déplacés vers le haut.
Quantité inconnue
-
+
Part-DB1\templates\Parts\info\_part_lots.html.twig:40
Part-DB1\templates\Parts\info\_part_lots.html.twig:38
@@ -2445,7 +2489,7 @@ Les sous éléments seront déplacés vers le haut.
Date d'expiration
-
+
Part-DB1\templates\Parts\info\_part_lots.html.twig:48
Part-DB1\templates\Parts\info\_part_lots.html.twig:46
@@ -2455,7 +2499,7 @@ Les sous éléments seront déplacés vers le haut.
Expiré
-
+
Part-DB1\templates\Parts\info\_part_lots.html.twig:55
Part-DB1\templates\Parts\info\_part_lots.html.twig:53
@@ -2465,7 +2509,7 @@ Les sous éléments seront déplacés vers le haut.
Doit être rempli à nouveau
-
+
Part-DB1\templates\Parts\info\_picture.html.twig:15
Part-DB1\templates\Parts\info\_picture.html.twig:15
@@ -2475,7 +2519,7 @@ Les sous éléments seront déplacés vers le haut.
Image précédente
-
+
Part-DB1\templates\Parts\info\_picture.html.twig:19
Part-DB1\templates\Parts\info\_picture.html.twig:19
@@ -2485,7 +2529,7 @@ Les sous éléments seront déplacés vers le haut.
Image suivante
-
+
Part-DB1\templates\Parts\info\_sidebar.html.twig:21
Part-DB1\templates\Parts\info\_sidebar.html.twig:21
@@ -2495,7 +2539,7 @@ Les sous éléments seront déplacés vers le haut.
Poids
-
+
Part-DB1\templates\Parts\info\_sidebar.html.twig:30
Part-DB1\templates\Parts\info\_sidebar.html.twig:30
@@ -2505,7 +2549,7 @@ Les sous éléments seront déplacés vers le haut.
Révision nécessaire
-
+
Part-DB1\templates\Parts\info\_sidebar.html.twig:39
Part-DB1\templates\Parts\info\_sidebar.html.twig:39
@@ -2515,7 +2559,7 @@ Les sous éléments seront déplacés vers le haut.
Favoris
-
+
Part-DB1\templates\Parts\info\_sidebar.html.twig:47
Part-DB1\templates\Parts\info\_sidebar.html.twig:47
@@ -2525,7 +2569,7 @@ Les sous éléments seront déplacés vers le haut.
N'est plus disponible
-
+
Part-DB1\templates\Parts\info\_specifications.html.twig:10
@@ -2534,7 +2578,7 @@ Les sous éléments seront déplacés vers le haut.
Automatiquement extrait de la description
-
+
Part-DB1\templates\Parts\info\_specifications.html.twig:15
@@ -2543,7 +2587,7 @@ Les sous éléments seront déplacés vers le haut.
Automatiquement extrait du commentaire
-
+
Part-DB1\templates\Parts\info\_tools.html.twig:6
Part-DB1\templates\Parts\info\_tools.html.twig:4
@@ -2554,7 +2598,7 @@ Les sous éléments seront déplacés vers le haut.
Éditer
-
+
Part-DB1\templates\Parts\info\_tools.html.twig:16
Part-DB1\templates\Parts\info\_tools.html.twig:14
@@ -2565,7 +2609,7 @@ Les sous éléments seront déplacés vers le haut.
Duplication
-
+
Part-DB1\templates\Parts\info\_tools.html.twig:24
Part-DB1\templates\Parts\lists\_action_bar.html.twig:4
@@ -2576,7 +2620,7 @@ Les sous éléments seront déplacés vers le haut.
Créer un nouveau composant
-
+
Part-DB1\templates\Parts\info\_tools.html.twig:31
Part-DB1\templates\Parts\info\_tools.html.twig:29
@@ -2586,7 +2630,7 @@ Les sous éléments seront déplacés vers le haut.
Voulez-vous vraiment supprimer ce composant ?
-
+
Part-DB1\templates\Parts\info\_tools.html.twig:32
Part-DB1\templates\Parts\info\_tools.html.twig:30
@@ -2596,7 +2640,7 @@ Les sous éléments seront déplacés vers le haut.
Le composant et toutes les informations associées (stocks, fichiers joints, etc.) sont supprimés. Cela ne pourra pas être annulé.
-
+
Part-DB1\templates\Parts\info\_tools.html.twig:39
Part-DB1\templates\Parts\info\_tools.html.twig:37
@@ -2606,7 +2650,7 @@ Les sous éléments seront déplacés vers le haut.
Supprimer le composant
-
+
Part-DB1\templates\Parts\lists\all_list.html.twig:4
Part-DB1\templates\Parts\lists\all_list.html.twig:4
@@ -2616,7 +2660,7 @@ Les sous éléments seront déplacés vers le haut.
Tous les composants
-
+
Part-DB1\templates\Parts\lists\category_list.html.twig:4
Part-DB1\templates\Parts\lists\category_list.html.twig:4
@@ -2626,7 +2670,7 @@ Les sous éléments seront déplacés vers le haut.
Composants avec catégorie
-
+