mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-07-22 11:54:41 +02:00
Merge branch 'part_info_provider_integration'
This commit is contained in:
commit
59b78e850f
84 changed files with 5436 additions and 158 deletions
|
@ -36,6 +36,10 @@
|
|||
PassEnv SAML_ENABLED SAML_ROLE_MAPPING SAML_UPDATE_GROUP_ON_LOGIN SAML_IDP_ENTITY_ID SAML_IDP_SINGLE_SIGN_ON_SERVICE SAML_IDP_SINGLE_LOGOUT_SERVICE SAML_IDP_X509_CERT SAML_SP_ENTITY_ID SAML_SP_X509_CERT SAMLP_SP_PRIVATE_KEY
|
||||
PassEnv TABLE_DEFAULT_PAGE_SIZE
|
||||
|
||||
PassEnv PROVIDER_DIGIKEY_CLIENT_ID PROVIDER_DIGIKEY_SECRET PROVIDER_DIGIKEY_CURRENCY PROVIDER_DIGIKEY_LANGUAGE PROVIDER_DIGIKEY_COUNTRY
|
||||
PassEnv PROVIDER_ELEMENT14_KEY PROVIDER_ELEMENT14_STORE_ID
|
||||
PassEnv PROVIDER_TME_KEY PROVIDER_TME_SECRET PROVIDER_TME_CURRENCY PROVIDER_TME_LANGUAGE PROVIDER_TME_COUNTRY PROVIDER_TME_GET_GROSS_PRICES
|
||||
|
||||
# For most configuration files from conf-available/, which are
|
||||
# enabled or disabled at a global level, it is possible to
|
||||
# include a line for only one particular virtual host. For example the
|
||||
|
|
35
.env
35
.env
|
@ -91,6 +91,41 @@ ERROR_PAGE_SHOW_HELP=1
|
|||
# The default page size for the part table (set to -1 to show all parts on one page)
|
||||
TABLE_DEFAULT_PAGE_SIZE=50
|
||||
|
||||
##################################################################################
|
||||
# Info provider settings
|
||||
##################################################################################
|
||||
|
||||
# Digikey Provider:
|
||||
# You can get your client id and secret from https://developer.digikey.com/
|
||||
PROVIDER_DIGIKEY_CLIENT_ID=
|
||||
PROVIDER_DIGIKEY_SECRET=
|
||||
# The currency to get prices in
|
||||
PROVIDER_DIGIKEY_CURRENCY=EUR
|
||||
# The language to get results in (en, de, fr, it, es, zh, ja, ko)
|
||||
PROVIDER_DIGIKEY_LANGUAGE=en
|
||||
# The country to get results for
|
||||
PROVIDER_DIGIKEY_COUNTRY=DE
|
||||
|
||||
# Farnell Provider:
|
||||
# You can get your API key from https://partner.element14.com/
|
||||
PROVIDER_ELEMENT14_KEY=
|
||||
# Configure the store domain you want to use. This decides the language and currency of results. You can get a list of available stores from https://partner.element14.com/docs/Product_Search_API_REST__Description
|
||||
PROVIDER_ELEMENT14_STORE_ID=de.farnell.com
|
||||
|
||||
# TME Provider:
|
||||
# You can get your API key from https://developers.tme.eu/en/
|
||||
PROVIDER_TME_KEY=
|
||||
PROVIDER_TME_SECRET=
|
||||
# The currency to get prices in
|
||||
PROVIDER_TME_CURRENCY=EUR
|
||||
# The language to get results in (en, de, pl)
|
||||
PROVIDER_TME_LANGUAGE=en
|
||||
# The country to get results for
|
||||
PROVIDER_TME_COUNTRY=DE
|
||||
# Set this to 1 to get gross prices (including VAT) instead of net prices
|
||||
PROVIDER_TME_GET_GROSS_PRICES=1
|
||||
|
||||
|
||||
###################################################################################
|
||||
# SAML Single sign on-settings
|
||||
###################################################################################
|
||||
|
|
|
@ -22,6 +22,8 @@ import '../../css/components/tom-select_extensions.css';
|
|||
import TomSelect from "tom-select";
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
import {trans, ENTITY_SELECT_GROUP_NEW_NOT_ADDED_TO_DB} from '../../translator.js'
|
||||
|
||||
|
||||
export default class extends Controller {
|
||||
_tomSelect;
|
||||
|
@ -40,7 +42,7 @@ export default class extends Controller {
|
|||
allowEmptyOption: true,
|
||||
selectOnTab: true,
|
||||
maxOptions: null,
|
||||
create: allowAdd,
|
||||
create: allowAdd ? this.createItem.bind(this) : false,
|
||||
createFilter: /\D/, //Must contain a non-digit character, otherwise they would be recognized as DB ID
|
||||
|
||||
searchField: [
|
||||
|
@ -68,6 +70,14 @@ export default class extends Controller {
|
|||
this._tomSelect.sync();
|
||||
}
|
||||
|
||||
createItem(input, callback) {
|
||||
callback({
|
||||
value: input,
|
||||
text: input,
|
||||
not_in_db_yet: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
updateValidity() {
|
||||
//Mark this input as invalid, if the selected option is disabled
|
||||
|
@ -97,14 +107,27 @@ export default class extends Controller {
|
|||
}
|
||||
|
||||
if (data.short) {
|
||||
return '<div><b>' + escape(data.short) + '</b></div>';
|
||||
let short = escape(data.short)
|
||||
|
||||
//Make text italic, if the item is not yet in the DB
|
||||
if (data.not_in_db_yet) {
|
||||
short = '<i>' + short + '</i>';
|
||||
}
|
||||
|
||||
return '<div><b>' + short + '</b></div>';
|
||||
}
|
||||
|
||||
let name = "";
|
||||
if (data.parent) {
|
||||
name += escape(data.parent) + " → ";
|
||||
}
|
||||
name += "<b>" + escape(data.text) + "</b>";
|
||||
|
||||
if (data.not_in_db_yet) {
|
||||
//Not yet added items are shown italic and with a badge
|
||||
name += "<i><b>" + escape(data.text) + "</b></i>" + "<span class='ms-3 badge bg-info badge-info'>" + trans(ENTITY_SELECT_GROUP_NEW_NOT_ADDED_TO_DB) + "</span>";
|
||||
} else {
|
||||
name += "<b>" + escape(data.text) + "</b>";
|
||||
}
|
||||
|
||||
return '<div>' + (data.image ? "<img class='structural-entity-select-image' style='margin-right: 5px;' ' src='" + data.image + "'/>" : "") + name + '</div>';
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ class RegisterEventHelper {
|
|||
const handler = () => {
|
||||
$(".tooltip").remove();
|
||||
//Exclude dropdown buttons from tooltips, otherwise we run into endless errors from bootstrap (bootstrap.esm.js:614 Bootstrap doesn't allow more than one instance per element. Bound instance: bs.dropdown.)
|
||||
$('a[title], label[title], button[title]:not([data-bs-toggle="dropdown"]), p[title], span[title], h6[title], h3[title], i[title]')
|
||||
$('a[title], label[title], button[title]:not([data-bs-toggle="dropdown"]), p[title], span[title], h6[title], h3[title], i[title], small[title]')
|
||||
//@ts-ignore
|
||||
.tooltip("hide").tooltip({container: "body", placement: "auto", boundary: 'window'});
|
||||
};
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"doctrine/dbal": "^3.4.6",
|
||||
"doctrine/doctrine-bundle": "^2.0",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.0",
|
||||
"doctrine/orm": "^2.9",
|
||||
"doctrine/orm": "dev-entity-level-commit-order#44d2a83 as 2.15.3",
|
||||
"dompdf/dompdf": "dev-master#87bea32efe0b0db309e1d31537201f64d5508280 as v2.0.3",
|
||||
"erusev/parsedown": "^1.7",
|
||||
"florianv/swap": "^4.0",
|
||||
|
@ -27,6 +27,7 @@
|
|||
"jbtronics/2fa-webauthn": "^v2.0.0",
|
||||
"jbtronics/dompdf-font-loader-bundle": "^1.0.0",
|
||||
"jfcherng/php-diff": "^6.14",
|
||||
"knpuniversity/oauth2-client-bundle": "^2.15",
|
||||
"league/csv": "^9.8.0",
|
||||
"league/html-to-markdown": "^5.0.1",
|
||||
"liip/imagine-bundle": "^2.2",
|
||||
|
|
650
composer.lock
generated
650
composer.lock
generated
|
@ -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": "e851e50a8353b7633581464ad286c6f7",
|
||||
"content-hash": "1c3a6a5bba2865b104630aaf4336e483",
|
||||
"packages": [
|
||||
{
|
||||
"name": "beberlei/assert",
|
||||
|
@ -1556,16 +1556,16 @@
|
|||
},
|
||||
{
|
||||
"name": "doctrine/orm",
|
||||
"version": "2.15.3",
|
||||
"version": "dev-entity-level-commit-order",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/orm.git",
|
||||
"reference": "4c3bd208018c26498e5f682aaad45fa00ea307d5"
|
||||
"reference": "44d2a83"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/orm/zipball/4c3bd208018c26498e5f682aaad45fa00ea307d5",
|
||||
"reference": "4c3bd208018c26498e5f682aaad45fa00ea307d5",
|
||||
"url": "https://api.github.com/repos/doctrine/orm/zipball/44d2a83",
|
||||
"reference": "44d2a83",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1651,9 +1651,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/orm/issues",
|
||||
"source": "https://github.com/doctrine/orm/tree/2.15.3"
|
||||
"source": "https://github.com/doctrine/orm/tree/entity-level-commit-order"
|
||||
},
|
||||
"time": "2023-06-22T12:36:06+00:00"
|
||||
"time": "2023-06-28T09:45:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/persistence",
|
||||
|
@ -2393,6 +2393,331 @@
|
|||
},
|
||||
"time": "2022-01-11T08:28:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
"version": "7.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/guzzle.git",
|
||||
"reference": "fb7566caccf22d74d1ab270de3551f72a58399f5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/fb7566caccf22d74d1ab270de3551f72a58399f5",
|
||||
"reference": "fb7566caccf22d74d1ab270de3551f72a58399f5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"guzzlehttp/promises": "^1.5.3 || ^2.0",
|
||||
"guzzlehttp/psr7": "^1.9.1 || ^2.4.5",
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"symfony/deprecation-contracts": "^2.2 || ^3.0"
|
||||
},
|
||||
"provide": {
|
||||
"psr/http-client-implementation": "1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.1",
|
||||
"ext-curl": "*",
|
||||
"php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999",
|
||||
"php-http/message-factory": "^1.1",
|
||||
"phpunit/phpunit": "^8.5.29 || ^9.5.23",
|
||||
"psr/log": "^1.1 || ^2.0 || ^3.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-curl": "Required for CURL handler support",
|
||||
"ext-intl": "Required for Internationalized Domain Name (IDN) support",
|
||||
"psr/log": "Required for using the Log middleware"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
},
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
},
|
||||
{
|
||||
"name": "Jeremy Lindblom",
|
||||
"email": "jeremeamia@gmail.com",
|
||||
"homepage": "https://github.com/jeremeamia"
|
||||
},
|
||||
{
|
||||
"name": "George Mponos",
|
||||
"email": "gmponos@gmail.com",
|
||||
"homepage": "https://github.com/gmponos"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Nyholm",
|
||||
"email": "tobias.nyholm@gmail.com",
|
||||
"homepage": "https://github.com/Nyholm"
|
||||
},
|
||||
{
|
||||
"name": "Márk Sági-Kazár",
|
||||
"email": "mark.sagikazar@gmail.com",
|
||||
"homepage": "https://github.com/sagikazarmark"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Schultze",
|
||||
"email": "webmaster@tubo-world.de",
|
||||
"homepage": "https://github.com/Tobion"
|
||||
}
|
||||
],
|
||||
"description": "Guzzle is a PHP HTTP client library",
|
||||
"keywords": [
|
||||
"client",
|
||||
"curl",
|
||||
"framework",
|
||||
"http",
|
||||
"http client",
|
||||
"psr-18",
|
||||
"psr-7",
|
||||
"rest",
|
||||
"web service"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/guzzle/guzzle/issues",
|
||||
"source": "https://github.com/guzzle/guzzle/tree/7.7.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Nyholm",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-05-21T14:04:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/promises",
|
||||
"version": "2.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/promises.git",
|
||||
"reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/promises/zipball/3a494dc7dc1d7d12e511890177ae2d0e6c107da6",
|
||||
"reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.1",
|
||||
"phpunit/phpunit": "^8.5.29 || ^9.5.23"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\Promise\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
},
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Nyholm",
|
||||
"email": "tobias.nyholm@gmail.com",
|
||||
"homepage": "https://github.com/Nyholm"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Schultze",
|
||||
"email": "webmaster@tubo-world.de",
|
||||
"homepage": "https://github.com/Tobion"
|
||||
}
|
||||
],
|
||||
"description": "Guzzle promises library",
|
||||
"keywords": [
|
||||
"promise"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/guzzle/promises/issues",
|
||||
"source": "https://github.com/guzzle/promises/tree/2.0.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Nyholm",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-05-21T13:50:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/psr7",
|
||||
"version": "2.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/psr7.git",
|
||||
"reference": "b635f279edd83fc275f822a1188157ffea568ff6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/psr7/zipball/b635f279edd83fc275f822a1188157ffea568ff6",
|
||||
"reference": "b635f279edd83fc275f822a1188157ffea568ff6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"psr/http-factory": "^1.0",
|
||||
"psr/http-message": "^1.1 || ^2.0",
|
||||
"ralouphie/getallheaders": "^3.0"
|
||||
},
|
||||
"provide": {
|
||||
"psr/http-factory-implementation": "1.0",
|
||||
"psr/http-message-implementation": "1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.1",
|
||||
"http-interop/http-factory-tests": "^0.9",
|
||||
"phpunit/phpunit": "^8.5.29 || ^9.5.23"
|
||||
},
|
||||
"suggest": {
|
||||
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\Psr7\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
},
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
},
|
||||
{
|
||||
"name": "George Mponos",
|
||||
"email": "gmponos@gmail.com",
|
||||
"homepage": "https://github.com/gmponos"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Nyholm",
|
||||
"email": "tobias.nyholm@gmail.com",
|
||||
"homepage": "https://github.com/Nyholm"
|
||||
},
|
||||
{
|
||||
"name": "Márk Sági-Kazár",
|
||||
"email": "mark.sagikazar@gmail.com",
|
||||
"homepage": "https://github.com/sagikazarmark"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Schultze",
|
||||
"email": "webmaster@tubo-world.de",
|
||||
"homepage": "https://github.com/Tobion"
|
||||
},
|
||||
{
|
||||
"name": "Márk Sági-Kazár",
|
||||
"email": "mark.sagikazar@gmail.com",
|
||||
"homepage": "https://sagikazarmark.hu"
|
||||
}
|
||||
],
|
||||
"description": "PSR-7 message implementation that also provides common utility methods",
|
||||
"keywords": [
|
||||
"http",
|
||||
"message",
|
||||
"psr-7",
|
||||
"request",
|
||||
"response",
|
||||
"stream",
|
||||
"uri",
|
||||
"url"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/guzzle/psr7/issues",
|
||||
"source": "https://github.com/guzzle/psr7/tree/2.5.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Nyholm",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-04-17T16:11:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "imagine/imagine",
|
||||
"version": "1.3.5",
|
||||
|
@ -2806,6 +3131,66 @@
|
|||
],
|
||||
"time": "2023-05-21T07:57:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "knpuniversity/oauth2-client-bundle",
|
||||
"version": "v2.15.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/knpuniversity/oauth2-client-bundle.git",
|
||||
"reference": "9df0736d02eb20b953ec8e9986743611747d9ed9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/knpuniversity/oauth2-client-bundle/zipball/9df0736d02eb20b953ec8e9986743611747d9ed9",
|
||||
"reference": "9df0736d02eb20b953ec8e9986743611747d9ed9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"league/oauth2-client": "^2.0",
|
||||
"php": ">=7.4",
|
||||
"symfony/dependency-injection": "^4.4|^5.0|^6.0",
|
||||
"symfony/framework-bundle": "^4.4|^5.0|^6.0",
|
||||
"symfony/http-foundation": "^4.4|^5.0|^6.0",
|
||||
"symfony/routing": "^4.4|^5.0|^6.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"league/oauth2-facebook": "^1.1|^2.0",
|
||||
"phpstan/phpstan": "^0.12",
|
||||
"symfony/phpunit-bridge": "^5.3.1|^6.0",
|
||||
"symfony/security-guard": "^4.4|^5.0|^6.0",
|
||||
"symfony/yaml": "^4.4|^5.0|^6.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/security-guard": "For integration with Symfony's Guard Security layer"
|
||||
},
|
||||
"type": "symfony-bundle",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"KnpU\\OAuth2ClientBundle\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ryan Weaver",
|
||||
"email": "ryan@symfonycasts.com"
|
||||
}
|
||||
],
|
||||
"description": "Integration with league/oauth2-client to provide services",
|
||||
"homepage": "https://symfonycasts.com",
|
||||
"keywords": [
|
||||
"oauth",
|
||||
"oauth2"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/knpuniversity/oauth2-client-bundle/issues",
|
||||
"source": "https://github.com/knpuniversity/oauth2-client-bundle/tree/v2.15.0"
|
||||
},
|
||||
"time": "2023-05-03T16:44:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laminas/laminas-code",
|
||||
"version": "4.11.0",
|
||||
|
@ -3094,16 +3479,16 @@
|
|||
},
|
||||
{
|
||||
"name": "league/html-to-markdown",
|
||||
"version": "5.1.0",
|
||||
"version": "5.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/html-to-markdown.git",
|
||||
"reference": "e0fc8cf07bdabbcd3765341ecb50c34c271d64e1"
|
||||
"reference": "0b4066eede55c48f38bcee4fb8f0aa85654390fd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/e0fc8cf07bdabbcd3765341ecb50c34c271d64e1",
|
||||
"reference": "e0fc8cf07bdabbcd3765341ecb50c34c271d64e1",
|
||||
"url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/0b4066eede55c48f38bcee4fb8f0aa85654390fd",
|
||||
"reference": "0b4066eede55c48f38bcee4fb8f0aa85654390fd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -3113,11 +3498,11 @@
|
|||
},
|
||||
"require-dev": {
|
||||
"mikehaertl/php-shellcommand": "^1.1.0",
|
||||
"phpstan/phpstan": "^0.12.99",
|
||||
"phpstan/phpstan": "^1.8.8",
|
||||
"phpunit/phpunit": "^8.5 || ^9.2",
|
||||
"scrutinizer/ocular": "^1.6",
|
||||
"unleashedtech/php-coding-standard": "^2.7",
|
||||
"vimeo/psalm": "^4.22"
|
||||
"unleashedtech/php-coding-standard": "^2.7 || ^3.0",
|
||||
"vimeo/psalm": "^4.22 || ^5.0"
|
||||
},
|
||||
"bin": [
|
||||
"bin/html-to-markdown"
|
||||
|
@ -3159,7 +3544,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/thephpleague/html-to-markdown/issues",
|
||||
"source": "https://github.com/thephpleague/html-to-markdown/tree/5.1.0"
|
||||
"source": "https://github.com/thephpleague/html-to-markdown/tree/5.1.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -3179,7 +3564,77 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-03-02T17:24:08+00:00"
|
||||
"time": "2023-07-12T21:21:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/oauth2-client",
|
||||
"version": "2.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/oauth2-client.git",
|
||||
"reference": "160d6274b03562ebeb55ed18399281d8118b76c8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/160d6274b03562ebeb55ed18399281d8118b76c8",
|
||||
"reference": "160d6274b03562ebeb55ed18399281d8118b76c8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"guzzlehttp/guzzle": "^6.0 || ^7.0",
|
||||
"paragonie/random_compat": "^1 || ^2 || ^9.99",
|
||||
"php": "^5.6 || ^7.0 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.3.5",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.3.1",
|
||||
"phpunit/phpunit": "^5.7 || ^6.0 || ^9.5",
|
||||
"squizlabs/php_codesniffer": "^2.3 || ^3.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-2.x": "2.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"League\\OAuth2\\Client\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Alex Bilbie",
|
||||
"email": "hello@alexbilbie.com",
|
||||
"homepage": "http://www.alexbilbie.com",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Woody Gilk",
|
||||
"homepage": "https://github.com/shadowhand",
|
||||
"role": "Contributor"
|
||||
}
|
||||
],
|
||||
"description": "OAuth 2.0 Client Library",
|
||||
"keywords": [
|
||||
"Authentication",
|
||||
"SSO",
|
||||
"authorization",
|
||||
"identity",
|
||||
"idp",
|
||||
"oauth",
|
||||
"oauth2",
|
||||
"single sign on"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/thephpleague/oauth2-client/issues",
|
||||
"source": "https://github.com/thephpleague/oauth2-client/tree/2.7.0"
|
||||
},
|
||||
"time": "2023-04-16T18:19:15+00:00"
|
||||
},
|
||||
{
|
||||
"name": "liip/imagine-bundle",
|
||||
|
@ -4197,6 +4652,56 @@
|
|||
},
|
||||
"time": "2022-06-14T06:56:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "paragonie/random_compat",
|
||||
"version": "v9.99.100",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paragonie/random_compat.git",
|
||||
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
|
||||
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">= 7"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "4.*|5.*",
|
||||
"vimeo/psalm": "^1"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
|
||||
},
|
||||
"type": "library",
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Paragon Initiative Enterprises",
|
||||
"email": "security@paragonie.com",
|
||||
"homepage": "https://paragonie.com"
|
||||
}
|
||||
],
|
||||
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
|
||||
"keywords": [
|
||||
"csprng",
|
||||
"polyfill",
|
||||
"pseudorandom",
|
||||
"random"
|
||||
],
|
||||
"support": {
|
||||
"email": "info@paragonie.com",
|
||||
"issues": "https://github.com/paragonie/random_compat/issues",
|
||||
"source": "https://github.com/paragonie/random_compat"
|
||||
},
|
||||
"time": "2020-10-15T08:29:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "part-db/label-fonts",
|
||||
"version": "v1.0.0",
|
||||
|
@ -5533,6 +6038,50 @@
|
|||
},
|
||||
"time": "2021-10-29T13:26:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ralouphie/getallheaders",
|
||||
"version": "3.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ralouphie/getallheaders.git",
|
||||
"reference": "120b605dfeb996808c31b6477290a714d356e822"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
|
||||
"reference": "120b605dfeb996808c31b6477290a714d356e822",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.6"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-coveralls/php-coveralls": "^2.1",
|
||||
"phpunit/phpunit": "^5 || ^6.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/getallheaders.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ralph Khattar",
|
||||
"email": "ralph.khattar@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "A polyfill for getallheaders.",
|
||||
"support": {
|
||||
"issues": "https://github.com/ralouphie/getallheaders/issues",
|
||||
"source": "https://github.com/ralouphie/getallheaders/tree/develop"
|
||||
},
|
||||
"time": "2019-03-08T08:55:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "robrichards/xmlseclibs",
|
||||
"version": "3.1.1",
|
||||
|
@ -13110,7 +13659,7 @@
|
|||
},
|
||||
{
|
||||
"name": "web-auth/metadata-service",
|
||||
"version": "4.6.3",
|
||||
"version": "4.6.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/web-auth/webauthn-metadata-service.git",
|
||||
|
@ -13175,7 +13724,7 @@
|
|||
"webauthn"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/web-auth/webauthn-metadata-service/tree/4.6.3"
|
||||
"source": "https://github.com/web-auth/webauthn-metadata-service/tree/4.6.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -13191,16 +13740,16 @@
|
|||
},
|
||||
{
|
||||
"name": "web-auth/webauthn-lib",
|
||||
"version": "4.6.3",
|
||||
"version": "4.6.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/web-auth/webauthn-lib.git",
|
||||
"reference": "e0f85f09b4e1a48169352290e7ccfd29ade93e34"
|
||||
"reference": "8cb4949d81ef8414c82f334fb3514141aa013340"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/e0f85f09b4e1a48169352290e7ccfd29ade93e34",
|
||||
"reference": "e0f85f09b4e1a48169352290e7ccfd29ade93e34",
|
||||
"url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/8cb4949d81ef8414c82f334fb3514141aa013340",
|
||||
"reference": "8cb4949d81ef8414c82f334fb3514141aa013340",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -13263,7 +13812,7 @@
|
|||
"webauthn"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/web-auth/webauthn-lib/tree/4.6.3"
|
||||
"source": "https://github.com/web-auth/webauthn-lib/tree/4.6.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -13275,11 +13824,11 @@
|
|||
"type": "patreon"
|
||||
}
|
||||
],
|
||||
"time": "2023-06-12T14:32:32+00:00"
|
||||
"time": "2023-07-15T14:53:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "web-auth/webauthn-symfony-bundle",
|
||||
"version": "4.6.3",
|
||||
"version": "4.6.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/web-auth/webauthn-symfony-bundle.git",
|
||||
|
@ -13343,7 +13892,7 @@
|
|||
"webauthn"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/web-auth/webauthn-symfony-bundle/tree/4.6.3"
|
||||
"source": "https://github.com/web-auth/webauthn-symfony-bundle/tree/4.6.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -14782,16 +15331,16 @@
|
|||
},
|
||||
{
|
||||
"name": "rector/rector",
|
||||
"version": "0.17.4",
|
||||
"version": "0.17.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/rectorphp/rector.git",
|
||||
"reference": "14829888274eebddc67a0d7248c3dd2965704fbc"
|
||||
"reference": "ec40080b9bdaf39eb0c0a9276cd7b4a778c03f21"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/rectorphp/rector/zipball/14829888274eebddc67a0d7248c3dd2965704fbc",
|
||||
"reference": "14829888274eebddc67a0d7248c3dd2965704fbc",
|
||||
"url": "https://api.github.com/repos/rectorphp/rector/zipball/ec40080b9bdaf39eb0c0a9276cd7b4a778c03f21",
|
||||
"reference": "ec40080b9bdaf39eb0c0a9276cd7b4a778c03f21",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -14831,7 +15380,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/rectorphp/rector/issues",
|
||||
"source": "https://github.com/rectorphp/rector/tree/0.17.4"
|
||||
"source": "https://github.com/rectorphp/rector/tree/0.17.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -14839,7 +15388,7 @@
|
|||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2023-07-11T16:00:46+00:00"
|
||||
"time": "2023-07-14T09:54:15+00:00"
|
||||
},
|
||||
{
|
||||
"name": "roave/security-advisories",
|
||||
|
@ -14847,12 +15396,12 @@
|
|||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Roave/SecurityAdvisories.git",
|
||||
"reference": "bcc78ca7e0e2bf8f2f8afd4eb9aabb988d593c21"
|
||||
"reference": "63f15424de3fd93ab776497787df3bb2eded004b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/bcc78ca7e0e2bf8f2f8afd4eb9aabb988d593c21",
|
||||
"reference": "bcc78ca7e0e2bf8f2f8afd4eb9aabb988d593c21",
|
||||
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/63f15424de3fd93ab776497787df3bb2eded004b",
|
||||
"reference": "63f15424de3fd93ab776497787df3bb2eded004b",
|
||||
"shasum": ""
|
||||
},
|
||||
"conflict": {
|
||||
|
@ -14912,6 +15461,7 @@
|
|||
"cakephp/cakephp": "<3.10.3|>=4,<4.0.10|>=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10|= 1.3.7|>=4.1,<4.1.4",
|
||||
"cakephp/database": ">=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10",
|
||||
"cardgate/magento2": "<2.0.33",
|
||||
"cardgate/woocommerce": "<=3.1.15",
|
||||
"cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4",
|
||||
"cartalyst/sentry": "<=2.1.6",
|
||||
"catfan/medoo": "<1.7.5",
|
||||
|
@ -15033,6 +15583,7 @@
|
|||
"grumpydictator/firefly-iii": "<6",
|
||||
"guzzlehttp/guzzle": "<6.5.8|>=7,<7.4.5",
|
||||
"guzzlehttp/psr7": "<1.9.1|>=2,<2.4.5",
|
||||
"haffner/jh_captcha": "<=2.1.3|>=3,<=3.0.2",
|
||||
"harvesthq/chosen": "<1.8.7",
|
||||
"helloxz/imgurl": "= 2.31|<=2.31",
|
||||
"hhxsv5/laravel-s": "<3.7.36",
|
||||
|
@ -15054,7 +15605,7 @@
|
|||
"illuminate/database": "<6.20.26|>=7,<7.30.5|>=8,<8.40",
|
||||
"illuminate/encryption": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.40|>=5.6,<5.6.15",
|
||||
"illuminate/view": "<6.20.42|>=7,<7.30.6|>=8,<8.75",
|
||||
"impresscms/impresscms": "<=1.4.3",
|
||||
"impresscms/impresscms": "<=1.4.5",
|
||||
"in2code/femanager": "<5.5.3|>=6,<6.3.4|>=7,<7.1",
|
||||
"in2code/ipandlanguageredirect": "<5.1.2",
|
||||
"in2code/lux": "<17.6.1|>=18,<24.0.2",
|
||||
|
@ -15105,7 +15656,7 @@
|
|||
"lms/routes": "<2.1.1",
|
||||
"localizationteam/l10nmgr": "<7.4|>=8,<8.7|>=9,<9.2",
|
||||
"luyadev/yii-helpers": "<1.2.1",
|
||||
"magento/community-edition": ">=2,<2.2.10|>=2.3,<2.3.3",
|
||||
"magento/community-edition": "<=2.4",
|
||||
"magento/magento1ce": "<1.9.4.3",
|
||||
"magento/magento1ee": ">=1,<1.14.4.3",
|
||||
"magento/product-community-edition": ">=2,<2.2.10|>=2.3,<2.3.2-p.2",
|
||||
|
@ -15121,14 +15672,14 @@
|
|||
"melisplatform/melis-front": "<5.0.1",
|
||||
"mezzio/mezzio-swoole": "<3.7|>=4,<4.3",
|
||||
"mgallegos/laravel-jqgrid": "<=1.3",
|
||||
"microweber/microweber": "<=1.3.4",
|
||||
"microweber/microweber": "= 1.1.18|<=1.3.4",
|
||||
"miniorange/miniorange-saml": "<1.4.3",
|
||||
"mittwald/typo3_forum": "<1.2.1",
|
||||
"mobiledetect/mobiledetectlib": "<2.8.32",
|
||||
"modx/revolution": "<= 2.8.3-pl|<2.8",
|
||||
"mojo42/jirafeau": "<4.4",
|
||||
"monolog/monolog": ">=1.8,<1.12",
|
||||
"moodle/moodle": "<4.2-rc.2|= 4.2.0|= 3.11",
|
||||
"moodle/moodle": "<4.2-rc.2|= 3.7|= 3.9|= 3.8|= 4.2.0|= 3.11",
|
||||
"movim/moxl": ">=0.8,<=0.10",
|
||||
"mustache/mustache": ">=2,<2.14.1",
|
||||
"namshi/jose": "<2.2",
|
||||
|
@ -15160,7 +15711,7 @@
|
|||
"openid/php-openid": "<2.3",
|
||||
"openmage/magento-lts": "<19.4.22|>=20,<20.0.19",
|
||||
"opensource-workshop/connect-cms": "<1.7.2|>=2,<2.3.2",
|
||||
"orchid/platform": ">=9,<9.4.4",
|
||||
"orchid/platform": ">=9,<9.4.4|>=14-alpha.4,<14.5",
|
||||
"oro/commerce": ">=4.1,<5.0.6",
|
||||
"oro/crm": ">=1.7,<1.7.4|>=3.1,<4.1.17|>=4.2,<4.2.7",
|
||||
"oro/platform": ">=1.7,<1.7.4|>=3.1,<3.1.29|>=4.1,<4.1.17|>=4.2,<4.2.8",
|
||||
|
@ -15178,7 +15729,7 @@
|
|||
"personnummer/personnummer": "<3.0.2",
|
||||
"phanan/koel": "<5.1.4",
|
||||
"php-mod/curl": "<2.3.2",
|
||||
"phpbb/phpbb": ">=3.2,<3.2.10|>=3.3,<3.3.1",
|
||||
"phpbb/phpbb": "<3.2.10|>=3.3,<3.3.1",
|
||||
"phpfastcache/phpfastcache": "<6.1.5|>=7,<7.1.2|>=8,<8.0.7",
|
||||
"phpmailer/phpmailer": "<6.5",
|
||||
"phpmussel/phpmussel": ">=1,<1.6",
|
||||
|
@ -15194,13 +15745,14 @@
|
|||
"phpxmlrpc/extras": "<0.6.1",
|
||||
"phpxmlrpc/phpxmlrpc": "<4.9.2",
|
||||
"pi/pi": "<=2.5",
|
||||
"pimcore/admin-ui-classic-bundle": "<1.0.3",
|
||||
"pimcore/customer-management-framework-bundle": "<3.4.1",
|
||||
"pimcore/data-hub": "<1.2.4",
|
||||
"pimcore/perspective-editor": "<1.5.1",
|
||||
"pimcore/pimcore": "<10.5.23",
|
||||
"pimcore/pimcore": "<10.5.24",
|
||||
"pixelfed/pixelfed": "<=0.11.4",
|
||||
"pocketmine/bedrock-protocol": "<8.0.2",
|
||||
"pocketmine/pocketmine-mp": "<4.20.5|>=4.21,<4.21.1|< 4.18.0-ALPHA2|>= 4.0.0-BETA5, < 4.4.2",
|
||||
"pocketmine/pocketmine-mp": "<4.22.3|>=5,<5.2.1|< 4.18.0-ALPHA2|>= 4.0.0-BETA5, < 4.4.2",
|
||||
"pressbooks/pressbooks": "<5.18",
|
||||
"prestashop/autoupgrade": ">=4,<4.10.1",
|
||||
"prestashop/blockwishlist": ">=2,<2.1.1",
|
||||
|
@ -15351,7 +15903,7 @@
|
|||
"truckersmp/phpwhois": "<=4.3.1",
|
||||
"ttskch/pagination-service-provider": "<1",
|
||||
"twig/twig": "<1.44.7|>=2,<2.15.3|>=3,<3.4.3",
|
||||
"typo3/cms": "<2.0.5|>=3,<3.0.3|>=6.2,<6.2.30|>=7,<7.6.32|>=8,<8.7.38|>=9,<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2",
|
||||
"typo3/cms": "<2.0.5|>=3,<3.0.3|>=6.2,<=6.2.38|>=7,<7.6.32|>=8,<8.7.38|>=9,<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2",
|
||||
"typo3/cms-backend": ">=7,<=7.6.50|>=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1",
|
||||
"typo3/cms-core": "<8.7.51|>=9,<9.5.40|>=10,<10.4.36|>=11,<11.5.23|>=12,<12.2",
|
||||
"typo3/cms-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1",
|
||||
|
@ -15429,6 +15981,7 @@
|
|||
"zendframework/zendframework1": "<1.12.20",
|
||||
"zendframework/zendopenid": ">=2,<2.0.2",
|
||||
"zendframework/zendxml": ">=1,<1.0.1",
|
||||
"zenstruck/collection": "<0.2.1",
|
||||
"zetacomponents/mail": "<1.8.2",
|
||||
"zf-commons/zfc-user": "<1.2.2",
|
||||
"zfcampus/zf-apigility-doctrine": ">=1,<1.0.3",
|
||||
|
@ -15471,7 +16024,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-07-11T01:32:50+00:00"
|
||||
"time": "2023-07-14T22:04:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/diff",
|
||||
|
@ -16234,6 +16787,12 @@
|
|||
}
|
||||
],
|
||||
"aliases": [
|
||||
{
|
||||
"package": "doctrine/orm",
|
||||
"version": "dev-entity-level-commit-order",
|
||||
"alias": "2.15.3",
|
||||
"alias_normalized": "2.15.3.0"
|
||||
},
|
||||
{
|
||||
"package": "dompdf/dompdf",
|
||||
"version": "9999999-dev",
|
||||
|
@ -16243,6 +16802,7 @@
|
|||
],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {
|
||||
"doctrine/orm": 20,
|
||||
"dompdf/dompdf": 20,
|
||||
"florianv/swap-bundle": 20,
|
||||
"roave/security-advisories": 20
|
||||
|
|
|
@ -30,4 +30,5 @@ return [
|
|||
Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true],
|
||||
Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true],
|
||||
Jbtronics\DompdfFontLoaderBundle\DompdfFontLoaderBundle::class => ['all' => true],
|
||||
KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle::class => ['all' => true],
|
||||
];
|
||||
|
|
|
@ -20,3 +20,6 @@ framework:
|
|||
tree.cache:
|
||||
adapter: cache.app
|
||||
tags: true
|
||||
|
||||
info_provider.cache:
|
||||
adapter: cache.app
|
||||
|
|
5
config/packages/http_client.yaml
Normal file
5
config/packages/http_client.yaml
Normal file
|
@ -0,0 +1,5 @@
|
|||
framework:
|
||||
http_client:
|
||||
default_options:
|
||||
headers:
|
||||
'User-Agent': 'Part-DB'
|
23
config/packages/knpu_oauth2_client.yaml
Normal file
23
config/packages/knpu_oauth2_client.yaml
Normal file
|
@ -0,0 +1,23 @@
|
|||
knpu_oauth2_client:
|
||||
clients:
|
||||
# configure your clients as described here: https://github.com/knpuniversity/oauth2-client-bundle#configuration
|
||||
|
||||
ip_digikey_oauth:
|
||||
type: generic
|
||||
provider_class: '\League\OAuth2\Client\Provider\GenericProvider'
|
||||
|
||||
client_id: '%env(PROVIDER_DIGIKEY_CLIENT_ID)%'
|
||||
client_secret: '%env(PROVIDER_DIGIKEY_SECRET)%'
|
||||
|
||||
redirect_route: 'oauth_client_check'
|
||||
redirect_params: {name: 'ip_digikey_oauth'}
|
||||
|
||||
provider_options:
|
||||
urlAuthorize: 'https://api.digikey.com/v1/oauth2/authorize'
|
||||
urlAccessToken: 'https://api.digikey.com/v1/oauth2/token'
|
||||
urlResourceOwnerDetails: ''
|
||||
|
||||
# Sandbox
|
||||
#urlAuthorize: 'https://sandbox-api.digikey.com/v1/oauth2/authorize'
|
||||
#urlAccessToken: 'https://sandbox-api.digikey.com/v1/oauth2/token'
|
||||
#urlResourceOwnerDetails: ''
|
|
@ -16,6 +16,9 @@ nelmio_security:
|
|||
# Whitelist the domain of the SAML IDP, so we can redirect to it during the SAML login process
|
||||
- '%env(string:key:host:url:SAML_IDP_SINGLE_SIGN_ON_SERVICE)%'
|
||||
|
||||
# Whitelist the info provider APIs
|
||||
- 'digikey.com'
|
||||
|
||||
# forces Microsoft's XSS-Protection with
|
||||
# its block mode
|
||||
xss_protection:
|
||||
|
|
|
@ -139,6 +139,13 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
|||
ic_logos:
|
||||
label: "perm.tools.ic_logos"
|
||||
|
||||
info_providers:
|
||||
label: "perm.part.info_providers"
|
||||
operations:
|
||||
create_parts:
|
||||
label: "perm.part.info_providers.create_parts"
|
||||
alsoSet: ['parts.create']
|
||||
|
||||
groups:
|
||||
label: "perm.groups"
|
||||
group: "system"
|
||||
|
@ -242,6 +249,8 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
|||
alsoSet: 'show_logs'
|
||||
server_infos:
|
||||
label: "perm.server_infos"
|
||||
manage_oauth_tokens:
|
||||
label: "Manage OAuth tokens"
|
||||
|
||||
attachments:
|
||||
label: "perm.part.attachments"
|
||||
|
|
|
@ -24,6 +24,9 @@ services:
|
|||
App\Services\LabelSystem\PlaceholderProviders\PlaceholderProviderInterface:
|
||||
tags: ['app.label_placeholder_provider']
|
||||
|
||||
App\Services\InfoProviderSystem\Providers\InfoProviderInterface:
|
||||
tags: ['app.info_provider']
|
||||
|
||||
# makes classes in src/ available to be used as services
|
||||
# this creates a service per class whose id is the fully-qualified class name
|
||||
App\:
|
||||
|
@ -234,6 +237,37 @@ services:
|
|||
$rootNodeExpandedByDefault: '%partdb.sidebar.root_expanded%'
|
||||
$rootNodeEnabled: '%partdb.sidebar.root_node_enable%'
|
||||
|
||||
####################################################################################################################
|
||||
# Part info provider system
|
||||
####################################################################################################################
|
||||
App\Services\InfoProviderSystem\ProviderRegistry:
|
||||
arguments:
|
||||
$providers: !tagged_iterator 'app.info_provider'
|
||||
|
||||
App\Services\InfoProviderSystem\Providers\Element14Provider:
|
||||
arguments:
|
||||
$api_key: '%env(string:PROVIDER_ELEMENT14_KEY)%'
|
||||
$store_id: '%env(string:PROVIDER_ELEMENT14_STORE_ID)%'
|
||||
|
||||
App\Services\InfoProviderSystem\Providers\DigikeyProvider:
|
||||
arguments:
|
||||
$clientId: '%env(string:PROVIDER_DIGIKEY_CLIENT_ID)%'
|
||||
$currency: '%env(string:PROVIDER_DIGIKEY_CURRENCY)%'
|
||||
$language: '%env(string:PROVIDER_DIGIKEY_LANGUAGE)%'
|
||||
$country: '%env(string:PROVIDER_DIGIKEY_COUNTRY)%'
|
||||
|
||||
App\Services\InfoProviderSystem\Providers\TMEClient:
|
||||
arguments:
|
||||
$secret: '%env(string:PROVIDER_TME_SECRET)%'
|
||||
$token: '%env(string:PROVIDER_TME_KEY)%'
|
||||
|
||||
App\Services\InfoProviderSystem\Providers\TMEProvider:
|
||||
arguments:
|
||||
$currency: '%env(string:PROVIDER_TME_CURRENCY)%'
|
||||
$country: '%env(string:PROVIDER_TME_COUNTRY)%'
|
||||
$language: '%env(string:PROVIDER_TME_LANGUAGE)%'
|
||||
$get_gross_prices: '%env(bool:PROVIDER_TME_GET_GROSS_PRICES)%'
|
||||
|
||||
####################################################################################################################
|
||||
# Symfony overrides
|
||||
####################################################################################################################
|
||||
|
|
351
migrations/Version20230716184033.php
Normal file
351
migrations/Version20230716184033.php
Normal file
|
@ -0,0 +1,351 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Migration\AbstractMultiPlatformMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
final class Version20230716184033 extends AbstractMultiPlatformMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Create the structure needed for the information provider system';
|
||||
}
|
||||
|
||||
public function mySQLUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE oauth_tokens (id INT AUTO_INCREMENT NOT NULL, token VARCHAR(255) DEFAULT NULL, expires_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', refresh_token VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, UNIQUE INDEX oauth_tokens_unique_name (name), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('ALTER TABLE attachment_types ADD alternative_names LONGTEXT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE categories ADD alternative_names LONGTEXT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE currencies ADD alternative_names LONGTEXT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE footprints ADD alternative_names LONGTEXT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE groups ADD alternative_names LONGTEXT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE manufacturers ADD alternative_names LONGTEXT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE measurement_units ADD alternative_names LONGTEXT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE parts ADD provider_reference_provider_key VARCHAR(255) DEFAULT NULL, ADD provider_reference_provider_id VARCHAR(255) DEFAULT NULL, ADD provider_reference_provider_url VARCHAR(255) DEFAULT NULL, ADD provider_reference_last_updated DATETIME DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE projects ADD alternative_names LONGTEXT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE storelocations ADD alternative_names LONGTEXT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE suppliers ADD alternative_names LONGTEXT DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function mySQLDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP TABLE oauth_tokens');
|
||||
$this->addSql('ALTER TABLE `attachment_types` DROP alternative_names');
|
||||
$this->addSql('ALTER TABLE `categories` DROP alternative_names');
|
||||
$this->addSql('ALTER TABLE currencies DROP alternative_names');
|
||||
$this->addSql('ALTER TABLE `footprints` DROP alternative_names');
|
||||
$this->addSql('ALTER TABLE `groups` DROP alternative_names');
|
||||
$this->addSql('ALTER TABLE `manufacturers` DROP alternative_names');
|
||||
$this->addSql('ALTER TABLE `measurement_units` DROP alternative_names');
|
||||
$this->addSql('ALTER TABLE `parts` DROP provider_reference_provider_key, DROP provider_reference_provider_id, DROP provider_reference_provider_url, DROP provider_reference_last_updated');
|
||||
$this->addSql('ALTER TABLE projects DROP alternative_names');
|
||||
$this->addSql('ALTER TABLE `storelocations` DROP alternative_names');
|
||||
$this->addSql('ALTER TABLE `suppliers` DROP alternative_names');
|
||||
}
|
||||
|
||||
public function sqLiteUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE oauth_tokens (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, token VARCHAR(255) DEFAULT NULL, expires_at DATETIME DEFAULT NULL --(DC2Type:datetime_immutable)
|
||||
, refresh_token VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL)');
|
||||
$this->addSql('CREATE UNIQUE INDEX oauth_tokens_unique_name ON oauth_tokens (name)');
|
||||
$this->addSql('ALTER TABLE attachment_types ADD COLUMN alternative_names CLOB DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE categories ADD COLUMN alternative_names CLOB DEFAULT NULL');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__currencies AS SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added FROM currencies');
|
||||
$this->addSql('DROP TABLE currencies');
|
||||
$this->addSql('CREATE TABLE currencies (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, exchange_rate NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal)
|
||||
, iso_code VARCHAR(255) 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, CONSTRAINT FK_37C44693727ACA70 FOREIGN KEY (parent_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_37C44693EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO currencies (id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added FROM __temp__currencies');
|
||||
$this->addSql('DROP TABLE __temp__currencies');
|
||||
$this->addSql('CREATE INDEX IDX_37C44693727ACA70 ON currencies (parent_id)');
|
||||
$this->addSql('CREATE INDEX currency_idx_name ON currencies (name)');
|
||||
$this->addSql('CREATE INDEX currency_idx_parent_name ON currencies (parent_id, name)');
|
||||
$this->addSql('CREATE INDEX IDX_37C44693EA7100A1 ON currencies (id_preview_attachment)');
|
||||
$this->addSql('ALTER TABLE footprints ADD COLUMN alternative_names CLOB DEFAULT NULL');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__groups AS SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data FROM groups');
|
||||
$this->addSql('DROP TABLE groups');
|
||||
$this->addSql('CREATE TABLE groups (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, enforce_2fa BOOLEAN 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, permissions_data CLOB NOT NULL --(DC2Type:json)
|
||||
, alternative_names CLOB DEFAULT NULL, CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES groups (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_F06D3970EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO groups (id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data) SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data FROM __temp__groups');
|
||||
$this->addSql('DROP TABLE __temp__groups');
|
||||
$this->addSql('CREATE INDEX group_idx_parent_name ON groups (parent_id, name)');
|
||||
$this->addSql('CREATE INDEX group_idx_name ON groups (name)');
|
||||
$this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON groups (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_F06D3970EA7100A1 ON groups (id_preview_attachment)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__log AS SELECT id, id_user, datetime, level, target_id, target_type, extra, type, username FROM log');
|
||||
$this->addSql('DROP TABLE log');
|
||||
$this->addSql('CREATE TABLE log (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_user INTEGER DEFAULT NULL, datetime DATETIME NOT NULL, level TINYINT NOT NULL --(DC2Type:tinyint)
|
||||
, target_id INTEGER NOT NULL, target_type SMALLINT NOT NULL, extra CLOB NOT NULL --(DC2Type:json)
|
||||
, type SMALLINT NOT NULL, username VARCHAR(255) NOT NULL, CONSTRAINT FK_8F3F68C56B3CA4B FOREIGN KEY (id_user) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO log (id, id_user, datetime, level, target_id, target_type, extra, type, username) SELECT id, id_user, datetime, level, target_id, target_type, extra, type, username FROM __temp__log');
|
||||
$this->addSql('DROP TABLE __temp__log');
|
||||
$this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)');
|
||||
$this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)');
|
||||
$this->addSql('CREATE INDEX log_idx_type ON log (type)');
|
||||
$this->addSql('CREATE INDEX IDX_8F3F68C56B3CA4B ON log (id_user)');
|
||||
$this->addSql('ALTER TABLE manufacturers ADD COLUMN alternative_names CLOB DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE measurement_units ADD COLUMN alternative_names CLOB DEFAULT NULL');
|
||||
$this->addSql('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 FROM parts');
|
||||
$this->addSql('DROP TABLE parts');
|
||||
$this->addSql('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 VARCHAR(255) 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, 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_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, CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('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) 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 FROM __temp__parts');
|
||||
$this->addSql('DROP TABLE __temp__parts');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FEEA7100A1 ON parts (id_preview_attachment)');
|
||||
$this->addSql('CREATE INDEX parts_idx_ipn ON parts (ipn)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON parts (ipn)');
|
||||
$this->addSql('CREATE INDEX parts_idx_name ON parts (name)');
|
||||
$this->addSql('CREATE INDEX parts_idx_datet_name_last_id_needs ON parts (datetime_added, name, last_modified, id, needs_review)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE5697F554 ON parts (id_category)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE7E371A10 ON parts (id_footprint)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE2626CEF9 ON parts (id_part_unit)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE1ECB93AE ON parts (id_manufacturer)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON parts (order_orderdetails_id)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON parts (built_project_id)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__pricedetails AS SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM pricedetails');
|
||||
$this->addSql('DROP TABLE pricedetails');
|
||||
$this->addSql('CREATE TABLE pricedetails (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_currency INTEGER DEFAULT NULL, orderdetails_id INTEGER NOT NULL, price NUMERIC(11, 5) NOT NULL --(DC2Type:big_decimal)
|
||||
, price_related_quantity DOUBLE PRECISION NOT NULL, min_discount_quantity DOUBLE PRECISION NOT NULL, manual_input BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_C68C4459398D64AA FOREIGN KEY (id_currency) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_C68C44594A01DDC7 FOREIGN KEY (orderdetails_id) REFERENCES orderdetails (id) ON UPDATE NO ACTION ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO pricedetails (id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added) SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM __temp__pricedetails');
|
||||
$this->addSql('DROP TABLE __temp__pricedetails');
|
||||
$this->addSql('CREATE INDEX IDX_C68C44594A01DDC7 ON pricedetails (orderdetails_id)');
|
||||
$this->addSql('CREATE INDEX IDX_C68C4459398D64AA ON pricedetails (id_currency)');
|
||||
$this->addSql('CREATE INDEX pricedetails_idx_min_discount ON pricedetails (min_discount_quantity)');
|
||||
$this->addSql('CREATE INDEX pricedetails_idx_min_discount_price_qty ON pricedetails (min_discount_quantity, price_related_quantity)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__project_bom_entries AS SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM project_bom_entries');
|
||||
$this->addSql('DROP TABLE project_bom_entries');
|
||||
$this->addSql('CREATE TABLE project_bom_entries (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_device INTEGER DEFAULT NULL, id_part INTEGER DEFAULT NULL, price_currency_id INTEGER DEFAULT NULL, quantity DOUBLE PRECISION NOT NULL, mountnames CLOB NOT NULL, name VARCHAR(255) DEFAULT NULL, comment CLOB NOT NULL, price NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal)
|
||||
, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_AFC547992F180363 FOREIGN KEY (id_device) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AFC54799C22F6CC4 FOREIGN KEY (id_part) REFERENCES parts (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD313FFDCD60 FOREIGN KEY (price_currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO project_bom_entries (id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added) SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM __temp__project_bom_entries');
|
||||
$this->addSql('DROP TABLE __temp__project_bom_entries');
|
||||
$this->addSql('CREATE INDEX IDX_1AA2DD31C22F6CC4 ON project_bom_entries (id_part)');
|
||||
$this->addSql('CREATE INDEX IDX_1AA2DD312F180363 ON project_bom_entries (id_device)');
|
||||
$this->addSql('CREATE INDEX IDX_1AA2DD313FFDCD60 ON project_bom_entries (price_currency_id)');
|
||||
$this->addSql('ALTER TABLE projects ADD COLUMN alternative_names CLOB DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE storelocations ADD COLUMN alternative_names CLOB DEFAULT NULL');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__suppliers AS SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM suppliers');
|
||||
$this->addSql('DROP TABLE suppliers');
|
||||
$this->addSql('CREATE TABLE suppliers (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, default_currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal)
|
||||
, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) 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, CONSTRAINT FK_AC28B95C727ACA70 FOREIGN KEY (parent_id) REFERENCES suppliers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO suppliers (id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM __temp__suppliers');
|
||||
$this->addSql('DROP TABLE __temp__suppliers');
|
||||
$this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON suppliers (default_currency_id)');
|
||||
$this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON suppliers (parent_id)');
|
||||
$this->addSql('CREATE INDEX supplier_idx_name ON suppliers (name)');
|
||||
$this->addSql('CREATE INDEX supplier_idx_parent_name ON suppliers (parent_id, name)');
|
||||
$this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON suppliers (id_preview_attachment)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__users AS SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data, saml_user, about_me, show_email_on_profile FROM users');
|
||||
$this->addSql('DROP TABLE users');
|
||||
$this->addSql('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, group_id INTEGER DEFAULT NULL, currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a CLOB NOT NULL, config_instock_comment_w CLOB NOT NULL, trusted_device_cookie_version INTEGER NOT NULL, backup_codes CLOB NOT NULL --(DC2Type:json)
|
||||
, google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, name VARCHAR(180) NOT NULL, settings CLOB NOT NULL --(DC2Type:json)
|
||||
, backup_codes_generation_date DATETIME DEFAULT NULL, pw_reset_expires DATETIME DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB NOT NULL --(DC2Type:json)
|
||||
, saml_user BOOLEAN NOT NULL, about_me CLOB NOT NULL, show_email_on_profile BOOLEAN DEFAULT 0 NOT NULL, CONSTRAINT FK_1483A5E9FE54D947 FOREIGN KEY (group_id) REFERENCES groups (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E9EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO users (id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data, saml_user, about_me, show_email_on_profile) SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data, saml_user, about_me, show_email_on_profile FROM __temp__users');
|
||||
$this->addSql('DROP TABLE __temp__users');
|
||||
$this->addSql('CREATE INDEX user_idx_username ON users (name)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON users (name)');
|
||||
$this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON users (group_id)');
|
||||
$this->addSql('CREATE INDEX IDX_1483A5E938248176 ON users (currency_id)');
|
||||
$this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON users (id_preview_attachment)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added, other_ui FROM webauthn_keys');
|
||||
$this->addSql('DROP TABLE webauthn_keys');
|
||||
$this->addSql('CREATE TABLE webauthn_keys (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, public_key_credential_id CLOB NOT NULL --(DC2Type:base64)
|
||||
, type VARCHAR(255) NOT NULL, transports CLOB NOT NULL --(DC2Type:array)
|
||||
, attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL --(DC2Type:trust_path)
|
||||
, aaguid CLOB NOT NULL --(DC2Type:aaguid)
|
||||
, credential_public_key CLOB NOT NULL --(DC2Type:base64)
|
||||
, user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, other_ui CLOB DEFAULT NULL --(DC2Type:array)
|
||||
, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO webauthn_keys (id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added, other_ui) SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added, other_ui FROM __temp__webauthn_keys');
|
||||
$this->addSql('DROP TABLE __temp__webauthn_keys');
|
||||
$this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)');
|
||||
}
|
||||
|
||||
public function sqLiteDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP TABLE oauth_tokens');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__attachment_types AS SELECT id, parent_id, id_preview_attachment, filetype_filter, comment, not_selectable, name, last_modified, datetime_added FROM "attachment_types"');
|
||||
$this->addSql('DROP TABLE "attachment_types"');
|
||||
$this->addSql('CREATE TABLE "attachment_types" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, filetype_filter 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, CONSTRAINT FK_EFAED719727ACA70 FOREIGN KEY (parent_id) REFERENCES "attachment_types" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_EFAED719EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "attachment_types" (id, parent_id, id_preview_attachment, filetype_filter, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, filetype_filter, comment, not_selectable, name, last_modified, datetime_added FROM __temp__attachment_types');
|
||||
$this->addSql('DROP TABLE __temp__attachment_types');
|
||||
$this->addSql('CREATE INDEX IDX_EFAED719727ACA70 ON "attachment_types" (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_EFAED719EA7100A1 ON "attachment_types" (id_preview_attachment)');
|
||||
$this->addSql('CREATE INDEX attachment_types_idx_name ON "attachment_types" (name)');
|
||||
$this->addSql('CREATE INDEX attachment_types_idx_parent_name ON "attachment_types" (parent_id, name)');
|
||||
$this->addSql('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 FROM "categories"');
|
||||
$this->addSql('DROP TABLE "categories"');
|
||||
$this->addSql('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, CONSTRAINT FK_3AF34668727ACA70 FOREIGN KEY (parent_id) REFERENCES "categories" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_3AF34668EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('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) 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 FROM __temp__categories');
|
||||
$this->addSql('DROP TABLE __temp__categories');
|
||||
$this->addSql('CREATE INDEX IDX_3AF34668727ACA70 ON "categories" (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_3AF34668EA7100A1 ON "categories" (id_preview_attachment)');
|
||||
$this->addSql('CREATE INDEX category_idx_name ON "categories" (name)');
|
||||
$this->addSql('CREATE INDEX category_idx_parent_name ON "categories" (parent_id, name)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__currencies AS SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added FROM currencies');
|
||||
$this->addSql('DROP TABLE currencies');
|
||||
$this->addSql('CREATE TABLE currencies (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, exchange_rate NUMERIC(11, 5) DEFAULT NULL --
|
||||
(DC2Type:big_decimal)
|
||||
, iso_code VARCHAR(255) 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, CONSTRAINT FK_37C44693727ACA70 FOREIGN KEY (parent_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_37C44693EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO currencies (id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added FROM __temp__currencies');
|
||||
$this->addSql('DROP TABLE __temp__currencies');
|
||||
$this->addSql('CREATE INDEX IDX_37C44693727ACA70 ON currencies (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_37C44693EA7100A1 ON currencies (id_preview_attachment)');
|
||||
$this->addSql('CREATE INDEX currency_idx_name ON currencies (name)');
|
||||
$this->addSql('CREATE INDEX currency_idx_parent_name ON currencies (parent_id, name)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__footprints AS SELECT id, parent_id, id_footprint_3d, id_preview_attachment, comment, not_selectable, name, last_modified, datetime_added FROM "footprints"');
|
||||
$this->addSql('DROP TABLE "footprints"');
|
||||
$this->addSql('CREATE TABLE "footprints" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_footprint_3d INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT 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, CONSTRAINT FK_A34D68A2727ACA70 FOREIGN KEY (parent_id) REFERENCES "footprints" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_A34D68A232A38C34 FOREIGN KEY (id_footprint_3d) REFERENCES "attachments" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_A34D68A2EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "footprints" (id, parent_id, id_footprint_3d, id_preview_attachment, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_footprint_3d, id_preview_attachment, comment, not_selectable, name, last_modified, datetime_added FROM __temp__footprints');
|
||||
$this->addSql('DROP TABLE __temp__footprints');
|
||||
$this->addSql('CREATE INDEX IDX_A34D68A2727ACA70 ON "footprints" (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_A34D68A232A38C34 ON "footprints" (id_footprint_3d)');
|
||||
$this->addSql('CREATE INDEX IDX_A34D68A2EA7100A1 ON "footprints" (id_preview_attachment)');
|
||||
$this->addSql('CREATE INDEX footprint_idx_name ON "footprints" (name)');
|
||||
$this->addSql('CREATE INDEX footprint_idx_parent_name ON "footprints" (parent_id, name)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__groups AS SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data FROM "groups"');
|
||||
$this->addSql('DROP TABLE "groups"');
|
||||
$this->addSql('CREATE TABLE "groups" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, enforce_2fa BOOLEAN 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, permissions_data CLOB NOT NULL --
|
||||
(DC2Type:json)
|
||||
, CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_F06D3970EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "groups" (id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data) SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data FROM __temp__groups');
|
||||
$this->addSql('DROP TABLE __temp__groups');
|
||||
$this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON "groups" (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_F06D3970EA7100A1 ON "groups" (id_preview_attachment)');
|
||||
$this->addSql('CREATE INDEX group_idx_name ON "groups" (name)');
|
||||
$this->addSql('CREATE INDEX group_idx_parent_name ON "groups" (parent_id, name)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__log AS SELECT id, id_user, username, datetime, level, target_id, target_type, extra, type FROM log');
|
||||
$this->addSql('DROP TABLE log');
|
||||
$this->addSql('CREATE TABLE log (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_user INTEGER DEFAULT NULL, username VARCHAR(255) NOT NULL, datetime DATETIME NOT NULL, level TINYINT NOT NULL --
|
||||
(DC2Type:tinyint)
|
||||
, target_id INTEGER NOT NULL, target_type SMALLINT NOT NULL, extra CLOB NOT NULL --
|
||||
(DC2Type:json)
|
||||
, type SMALLINT NOT NULL, CONSTRAINT FK_8F3F68C56B3CA4B FOREIGN KEY (id_user) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO log (id, id_user, username, datetime, level, target_id, target_type, extra, type) SELECT id, id_user, username, datetime, level, target_id, target_type, extra, type FROM __temp__log');
|
||||
$this->addSql('DROP TABLE __temp__log');
|
||||
$this->addSql('CREATE INDEX IDX_8F3F68C56B3CA4B ON log (id_user)');
|
||||
$this->addSql('CREATE INDEX log_idx_type ON log (type)');
|
||||
$this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)');
|
||||
$this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__manufacturers AS SELECT id, parent_id, id_preview_attachment, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM "manufacturers"');
|
||||
$this->addSql('DROP TABLE "manufacturers"');
|
||||
$this->addSql('CREATE TABLE "manufacturers" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) 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, CONSTRAINT FK_94565B12727ACA70 FOREIGN KEY (parent_id) REFERENCES "manufacturers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_94565B12EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "manufacturers" (id, parent_id, id_preview_attachment, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM __temp__manufacturers');
|
||||
$this->addSql('DROP TABLE __temp__manufacturers');
|
||||
$this->addSql('CREATE INDEX IDX_94565B12727ACA70 ON "manufacturers" (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_94565B12EA7100A1 ON "manufacturers" (id_preview_attachment)');
|
||||
$this->addSql('CREATE INDEX manufacturer_name ON "manufacturers" (name)');
|
||||
$this->addSql('CREATE INDEX manufacturer_idx_parent_name ON "manufacturers" (parent_id, name)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__measurement_units AS SELECT id, parent_id, id_preview_attachment, unit, is_integer, use_si_prefix, comment, not_selectable, name, last_modified, datetime_added FROM "measurement_units"');
|
||||
$this->addSql('DROP TABLE "measurement_units"');
|
||||
$this->addSql('CREATE TABLE "measurement_units" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, unit VARCHAR(255) DEFAULT NULL, is_integer BOOLEAN NOT NULL, use_si_prefix BOOLEAN 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, CONSTRAINT FK_F5AF83CF727ACA70 FOREIGN KEY (parent_id) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_F5AF83CFEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "measurement_units" (id, parent_id, id_preview_attachment, unit, is_integer, use_si_prefix, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, unit, is_integer, use_si_prefix, comment, not_selectable, name, last_modified, datetime_added FROM __temp__measurement_units');
|
||||
$this->addSql('DROP TABLE __temp__measurement_units');
|
||||
$this->addSql('CREATE INDEX IDX_F5AF83CF727ACA70 ON "measurement_units" (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_F5AF83CFEA7100A1 ON "measurement_units" (id_preview_attachment)');
|
||||
$this->addSql('CREATE INDEX unit_idx_name ON "measurement_units" (name)');
|
||||
$this->addSql('CREATE INDEX unit_idx_parent_name ON "measurement_units" (parent_id, name)');
|
||||
$this->addSql('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, name, last_modified, datetime_added, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order FROM "parts"');
|
||||
$this->addSql('DROP TABLE "parts"');
|
||||
$this->addSql('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, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, needs_review BOOLEAN NOT NULL, tags CLOB NOT NULL, mass DOUBLE PRECISION DEFAULT NULL, ipn VARCHAR(100) 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 VARCHAR(255) 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, 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)');
|
||||
$this->addSql('INSERT INTO "parts" (id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id, name, last_modified, datetime_added, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order) SELECT id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id, name, last_modified, datetime_added, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order FROM __temp__parts');
|
||||
$this->addSql('DROP TABLE __temp__parts');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON "parts" (ipn)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FEEA7100A1 ON "parts" (id_preview_attachment)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE5697F554 ON "parts" (id_category)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE7E371A10 ON "parts" (id_footprint)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE2626CEF9 ON "parts" (id_part_unit)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE1ECB93AE ON "parts" (id_manufacturer)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON "parts" (order_orderdetails_id)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON "parts" (built_project_id)');
|
||||
$this->addSql('CREATE INDEX parts_idx_datet_name_last_id_needs ON "parts" (datetime_added, name, last_modified, id, needs_review)');
|
||||
$this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)');
|
||||
$this->addSql('CREATE INDEX parts_idx_ipn ON "parts" (ipn)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__pricedetails AS SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM "pricedetails"');
|
||||
$this->addSql('DROP TABLE "pricedetails"');
|
||||
$this->addSql('CREATE TABLE "pricedetails" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_currency INTEGER DEFAULT NULL, orderdetails_id INTEGER NOT NULL, price NUMERIC(11, 5) NOT NULL --
|
||||
(DC2Type:big_decimal)
|
||||
, price_related_quantity DOUBLE PRECISION NOT NULL, min_discount_quantity DOUBLE PRECISION NOT NULL, manual_input BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_C68C4459398D64AA FOREIGN KEY (id_currency) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_C68C44594A01DDC7 FOREIGN KEY (orderdetails_id) REFERENCES "orderdetails" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "pricedetails" (id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added) SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM __temp__pricedetails');
|
||||
$this->addSql('DROP TABLE __temp__pricedetails');
|
||||
$this->addSql('CREATE INDEX IDX_C68C4459398D64AA ON "pricedetails" (id_currency)');
|
||||
$this->addSql('CREATE INDEX IDX_C68C44594A01DDC7 ON "pricedetails" (orderdetails_id)');
|
||||
$this->addSql('CREATE INDEX pricedetails_idx_min_discount ON "pricedetails" (min_discount_quantity)');
|
||||
$this->addSql('CREATE INDEX pricedetails_idx_min_discount_price_qty ON "pricedetails" (min_discount_quantity, price_related_quantity)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__project_bom_entries AS SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM project_bom_entries');
|
||||
$this->addSql('DROP TABLE project_bom_entries');
|
||||
$this->addSql('CREATE TABLE project_bom_entries (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_device INTEGER DEFAULT NULL, id_part INTEGER DEFAULT NULL, price_currency_id INTEGER DEFAULT NULL, quantity DOUBLE PRECISION NOT NULL, mountnames CLOB NOT NULL, name VARCHAR(255) DEFAULT NULL, comment CLOB NOT NULL, price NUMERIC(11, 5) DEFAULT NULL --
|
||||
(DC2Type:big_decimal)
|
||||
, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_1AA2DD312F180363 FOREIGN KEY (id_device) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD31C22F6CC4 FOREIGN KEY (id_part) REFERENCES "parts" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD313FFDCD60 FOREIGN KEY (price_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO project_bom_entries (id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added) SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM __temp__project_bom_entries');
|
||||
$this->addSql('DROP TABLE __temp__project_bom_entries');
|
||||
$this->addSql('CREATE INDEX IDX_1AA2DD312F180363 ON project_bom_entries (id_device)');
|
||||
$this->addSql('CREATE INDEX IDX_1AA2DD31C22F6CC4 ON project_bom_entries (id_part)');
|
||||
$this->addSql('CREATE INDEX IDX_1AA2DD313FFDCD60 ON project_bom_entries (price_currency_id)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__projects AS SELECT id, parent_id, id_preview_attachment, order_quantity, status, order_only_missing_parts, description, comment, not_selectable, name, last_modified, datetime_added FROM projects');
|
||||
$this->addSql('DROP TABLE projects');
|
||||
$this->addSql('CREATE TABLE projects (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, order_quantity INTEGER NOT NULL, status VARCHAR(64) DEFAULT NULL, order_only_missing_parts BOOLEAN NOT NULL, description 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, CONSTRAINT FK_5C93B3A4727ACA70 FOREIGN KEY (parent_id) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_5C93B3A4EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO projects (id, parent_id, id_preview_attachment, order_quantity, status, order_only_missing_parts, description, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, order_quantity, status, order_only_missing_parts, description, comment, not_selectable, name, last_modified, datetime_added FROM __temp__projects');
|
||||
$this->addSql('DROP TABLE __temp__projects');
|
||||
$this->addSql('CREATE INDEX IDX_5C93B3A4727ACA70 ON projects (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_5C93B3A4EA7100A1 ON projects (id_preview_attachment)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__storelocations AS SELECT id, parent_id, storage_type_id, id_owner, id_preview_attachment, is_full, only_single_part, limit_to_existing_parts, part_owner_must_match, comment, not_selectable, name, last_modified, datetime_added FROM "storelocations"');
|
||||
$this->addSql('DROP TABLE "storelocations"');
|
||||
$this->addSql('CREATE TABLE "storelocations" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, storage_type_id INTEGER DEFAULT NULL, id_owner INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, is_full BOOLEAN NOT NULL, only_single_part BOOLEAN NOT NULL, limit_to_existing_parts BOOLEAN NOT NULL, part_owner_must_match BOOLEAN DEFAULT 0 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, CONSTRAINT FK_7517020727ACA70 FOREIGN KEY (parent_id) REFERENCES "storelocations" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_7517020B270BFF1 FOREIGN KEY (storage_type_id) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_751702021E5A74C FOREIGN KEY (id_owner) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_7517020EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "storelocations" (id, parent_id, storage_type_id, id_owner, id_preview_attachment, is_full, only_single_part, limit_to_existing_parts, part_owner_must_match, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, storage_type_id, id_owner, id_preview_attachment, is_full, only_single_part, limit_to_existing_parts, part_owner_must_match, comment, not_selectable, name, last_modified, datetime_added FROM __temp__storelocations');
|
||||
$this->addSql('DROP TABLE __temp__storelocations');
|
||||
$this->addSql('CREATE INDEX IDX_7517020727ACA70 ON "storelocations" (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_7517020B270BFF1 ON "storelocations" (storage_type_id)');
|
||||
$this->addSql('CREATE INDEX IDX_751702021E5A74C ON "storelocations" (id_owner)');
|
||||
$this->addSql('CREATE INDEX IDX_7517020EA7100A1 ON "storelocations" (id_preview_attachment)');
|
||||
$this->addSql('CREATE INDEX location_idx_name ON "storelocations" (name)');
|
||||
$this->addSql('CREATE INDEX location_idx_parent_name ON "storelocations" (parent_id, name)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__suppliers AS SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM "suppliers"');
|
||||
$this->addSql('DROP TABLE "suppliers"');
|
||||
$this->addSql('CREATE TABLE "suppliers" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, default_currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL --
|
||||
(DC2Type:big_decimal)
|
||||
, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) 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, CONSTRAINT FK_AC28B95C727ACA70 FOREIGN KEY (parent_id) REFERENCES "suppliers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "suppliers" (id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM __temp__suppliers');
|
||||
$this->addSql('DROP TABLE __temp__suppliers');
|
||||
$this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON "suppliers" (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON "suppliers" (default_currency_id)');
|
||||
$this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON "suppliers" (id_preview_attachment)');
|
||||
$this->addSql('CREATE INDEX supplier_idx_name ON "suppliers" (name)');
|
||||
$this->addSql('CREATE INDEX supplier_idx_parent_name ON "suppliers" (parent_id, name)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__users AS SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, show_email_on_profile, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, saml_user, last_modified, datetime_added, permissions_data FROM "users"');
|
||||
$this->addSql('DROP TABLE "users"');
|
||||
$this->addSql('CREATE TABLE "users" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, group_id INTEGER DEFAULT NULL, currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a CLOB NOT NULL, config_instock_comment_w CLOB NOT NULL, about_me CLOB NOT NULL, trusted_device_cookie_version INTEGER NOT NULL, backup_codes CLOB NOT NULL --
|
||||
(DC2Type:json)
|
||||
, google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, show_email_on_profile BOOLEAN DEFAULT 0 NOT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, name VARCHAR(180) NOT NULL, settings CLOB NOT NULL --
|
||||
(DC2Type:json)
|
||||
, backup_codes_generation_date DATETIME DEFAULT NULL, pw_reset_expires DATETIME DEFAULT NULL, saml_user BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB NOT NULL --
|
||||
(DC2Type:json)
|
||||
, CONSTRAINT FK_1483A5E9FE54D947 FOREIGN KEY (group_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E9EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "users" (id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, show_email_on_profile, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, saml_user, last_modified, datetime_added, permissions_data) SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, show_email_on_profile, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, saml_user, last_modified, datetime_added, permissions_data FROM __temp__users');
|
||||
$this->addSql('DROP TABLE __temp__users');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON "users" (name)');
|
||||
$this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON "users" (group_id)');
|
||||
$this->addSql('CREATE INDEX IDX_1483A5E938248176 ON "users" (currency_id)');
|
||||
$this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON "users" (id_preview_attachment)');
|
||||
$this->addSql('CREATE INDEX user_idx_username ON "users" (name)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, other_ui, name, last_modified, datetime_added FROM webauthn_keys');
|
||||
$this->addSql('DROP TABLE webauthn_keys');
|
||||
$this->addSql('CREATE TABLE webauthn_keys (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, public_key_credential_id CLOB NOT NULL --
|
||||
(DC2Type:base64)
|
||||
, type VARCHAR(255) NOT NULL, transports CLOB NOT NULL --
|
||||
(DC2Type:array)
|
||||
, attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL --
|
||||
(DC2Type:trust_path)
|
||||
, aaguid CLOB NOT NULL --
|
||||
(DC2Type:aaguid)
|
||||
, credential_public_key CLOB NOT NULL --
|
||||
(DC2Type:base64)
|
||||
, user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, other_ui CLOB DEFAULT NULL --
|
||||
(DC2Type:array)
|
||||
, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO webauthn_keys (id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, other_ui, name, last_modified, datetime_added) SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, other_ui, name, last_modified, datetime_added FROM __temp__webauthn_keys');
|
||||
$this->addSql('DROP TABLE __temp__webauthn_keys');
|
||||
$this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)');
|
||||
}
|
||||
}
|
85
src/Controller/InfoProviderController.php
Normal file
85
src/Controller/InfoProviderController.php
Normal file
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Exceptions\AttachmentDownloadException;
|
||||
use App\Form\InfoProviderSystem\PartSearchType;
|
||||
use App\Form\Part\PartBaseType;
|
||||
use App\Services\Attachments\AttachmentSubmitHandler;
|
||||
use App\Services\InfoProviderSystem\PartInfoRetriever;
|
||||
use App\Services\InfoProviderSystem\ProviderRegistry;
|
||||
use App\Services\LogSystem\EventCommentHelper;
|
||||
use App\Services\Parts\PartFormHelper;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
#[Route('/tools/info_providers')]
|
||||
class InfoProviderController extends AbstractController
|
||||
{
|
||||
|
||||
public function __construct(private readonly ProviderRegistry $providerRegistry,
|
||||
private readonly PartInfoRetriever $infoRetriever)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#[Route('/providers', name: 'info_providers_list')]
|
||||
public function listProviders(): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@info_providers.create_parts');
|
||||
|
||||
return $this->render('info_providers/providers_list/providers_list.html.twig', [
|
||||
'active_providers' => $this->providerRegistry->getActiveProviders(),
|
||||
'disabled_providers' => $this->providerRegistry->getDisabledProviders(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/search', name: 'info_providers_search')]
|
||||
public function search(Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@info_providers.create_parts');
|
||||
|
||||
$form = $this->createForm(PartSearchType::class);
|
||||
$form->handleRequest($request);
|
||||
|
||||
$results = null;
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$keyword = $form->get('keyword')->getData();
|
||||
$providers = $form->get('providers')->getData();
|
||||
|
||||
$results = $this->infoRetriever->searchByKeyword(keyword: $keyword, providers: $providers);
|
||||
}
|
||||
|
||||
return $this->render('info_providers/search/part_search.html.twig', [
|
||||
'form' => $form,
|
||||
'results' => $results,
|
||||
]);
|
||||
}
|
||||
}
|
67
src/Controller/OAuthClientController.php
Normal file
67
src/Controller/OAuthClientController.php
Normal file
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Services\OAuth\OAuthTokenManager;
|
||||
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
use function Symfony\Component\Translation\t;
|
||||
|
||||
#[Route('/oauth/client')]
|
||||
class OAuthClientController extends AbstractController
|
||||
{
|
||||
public function __construct(private readonly ClientRegistry $clientRegistry, private readonly OAuthTokenManager $tokenManager)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#[Route('/{name}/connect', name: 'oauth_client_connect')]
|
||||
public function connect(string $name): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@system.manage_oauth_tokens');
|
||||
|
||||
return $this->clientRegistry
|
||||
->getClient($name) // key used in config/packages/knpu_oauth2_client.yaml
|
||||
->redirect([], []);
|
||||
}
|
||||
|
||||
#[Route('/{name}/check', name: 'oauth_client_check')]
|
||||
public function check(string $name, Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@system.manage_oauth_tokens');
|
||||
|
||||
$client = $this->clientRegistry->getClient($name);
|
||||
|
||||
$access_token = $client->getAccessToken();
|
||||
$this->tokenManager->saveToken($name, $access_token);
|
||||
|
||||
$this->addFlash('success', t('oauth_client.flash.connection_successful'));
|
||||
|
||||
return $this->redirectToRoute('homepage');
|
||||
}
|
||||
}
|
|
@ -36,6 +36,7 @@ use App\Exceptions\AttachmentDownloadException;
|
|||
use App\Form\Part\PartBaseType;
|
||||
use App\Services\Attachments\AttachmentSubmitHandler;
|
||||
use App\Services\Attachments\PartPreviewGenerator;
|
||||
use App\Services\InfoProviderSystem\PartInfoRetriever;
|
||||
use App\Services\LogSystem\EventCommentHelper;
|
||||
use App\Services\LogSystem\HistoryHelper;
|
||||
use App\Services\LogSystem\TimeTravel;
|
||||
|
@ -63,7 +64,11 @@ use function Symfony\Component\Translation\t;
|
|||
#[Route(path: '/part')]
|
||||
class PartController extends AbstractController
|
||||
{
|
||||
public function __construct(protected PricedetailHelper $pricedetailHelper, protected PartPreviewGenerator $partPreviewGenerator, protected EventCommentHelper $commentHelper)
|
||||
public function __construct(protected PricedetailHelper $pricedetailHelper,
|
||||
protected PartPreviewGenerator $partPreviewGenerator,
|
||||
private readonly TranslatorInterface $translator,
|
||||
private readonly AttachmentSubmitHandler $attachmentSubmitHandler, private readonly EntityManagerInterface $em,
|
||||
protected EventCommentHelper $commentHelper)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -121,65 +126,15 @@ class PartController extends AbstractController
|
|||
}
|
||||
|
||||
#[Route(path: '/{id}/edit', name: 'part_edit')]
|
||||
public function edit(Part $part, Request $request, EntityManagerInterface $em, TranslatorInterface $translator,
|
||||
AttachmentSubmitHandler $attachmentSubmitHandler): Response
|
||||
public function edit(Part $part, Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('edit', $part);
|
||||
|
||||
$form = $this->createForm(PartBaseType::class, $part);
|
||||
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
//Upload passed files
|
||||
$attachments = $form['attachments'];
|
||||
foreach ($attachments as $attachment) {
|
||||
/** @var FormInterface $attachment */
|
||||
$options = [
|
||||
'secure_attachment' => $attachment['secureFile']->getData(),
|
||||
'download_url' => $attachment['downloadURL']->getData(),
|
||||
];
|
||||
|
||||
try {
|
||||
$attachmentSubmitHandler->handleFormSubmit($attachment->getData(), $attachment['file']->getData(), $options);
|
||||
} catch (AttachmentDownloadException $attachmentDownloadException) {
|
||||
$this->addFlash(
|
||||
'error',
|
||||
$translator->trans('attachment.download_failed').' '.$attachmentDownloadException->getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->commentHelper->setMessage($form['log_comment']->getData());
|
||||
|
||||
$em->persist($part);
|
||||
$em->flush();
|
||||
$this->addFlash('success', 'part.edited_flash');
|
||||
|
||||
//Redirect to clone page if user wished that...
|
||||
//@phpstan-ignore-next-line
|
||||
if ('save_and_clone' === $form->getClickedButton()->getName()) {
|
||||
return $this->redirectToRoute('part_clone', ['id' => $part->getID()]);
|
||||
}
|
||||
//@phpstan-ignore-next-line
|
||||
if ('save_and_new' === $form->getClickedButton()->getName()) {
|
||||
return $this->redirectToRoute('part_new');
|
||||
}
|
||||
|
||||
//Reload form, so the SIUnitType entries use the new part unit
|
||||
$form = $this->createForm(PartBaseType::class, $part);
|
||||
} elseif ($form->isSubmitted() && !$form->isValid()) {
|
||||
$this->addFlash('error', 'part.edited_flash.invalid');
|
||||
}
|
||||
|
||||
return $this->render('parts/edit/edit_part_info.html.twig',
|
||||
[
|
||||
'part' => $part,
|
||||
'form' => $form,
|
||||
]);
|
||||
return $this->renderPartForm('edit', $request, $part);
|
||||
}
|
||||
|
||||
#[Route(path: '/{id}/delete', name: 'part_delete', methods: ['DELETE'])]
|
||||
public function delete(Request $request, Part $part, EntityManagerInterface $entityManager): RedirectResponse
|
||||
public function delete(Request $request, Part $part): RedirectResponse
|
||||
{
|
||||
$this->denyAccessUnlessGranted('delete', $part);
|
||||
|
||||
|
@ -188,10 +143,10 @@ class PartController extends AbstractController
|
|||
$this->commentHelper->setMessage($request->request->get('log_comment', null));
|
||||
|
||||
//Remove part
|
||||
$entityManager->remove($part);
|
||||
$this->em->remove($part);
|
||||
|
||||
//Flush changes
|
||||
$entityManager->flush();
|
||||
$this->em->flush();
|
||||
|
||||
$this->addFlash('success', 'part.deleted');
|
||||
}
|
||||
|
@ -262,7 +217,39 @@ class PartController extends AbstractController
|
|||
$new_part->addOrderdetail($orderdetail);
|
||||
}
|
||||
|
||||
$form = $this->createForm(PartBaseType::class, $new_part);
|
||||
return $this->renderPartForm('new', $request, $new_part);
|
||||
}
|
||||
|
||||
#[Route('/from_info_provider/{providerKey}/{providerId}/create', name: 'info_providers_create_part')]
|
||||
public function createFromInfoProvider(Request $request, string $providerKey, string $providerId, PartInfoRetriever $infoRetriever): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@info_providers.create_parts');
|
||||
|
||||
$dto = $infoRetriever->getDetails($providerKey, $providerId);
|
||||
$new_part = $infoRetriever->dtoToPart($dto);
|
||||
|
||||
return $this->renderPartForm('new', $request, $new_part, [
|
||||
'info_provider_dto' => $dto,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function provides a common implementation for methods, which use the part form.
|
||||
* @param Request $request
|
||||
* @param Part $data
|
||||
* @param array $form_options
|
||||
* @return Response
|
||||
*/
|
||||
private function renderPartForm(string $mode, Request $request, Part $data, array $form_options = []): Response
|
||||
{
|
||||
//Ensure that mode is either 'new' or 'edit
|
||||
if (!in_array($mode, ['new', 'edit'], true)) {
|
||||
throw new \InvalidArgumentException('Invalid mode given');
|
||||
}
|
||||
|
||||
$new_part = $data;
|
||||
|
||||
$form = $this->createForm(PartBaseType::class, $new_part, $form_options);
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
|
@ -277,20 +264,24 @@ class PartController extends AbstractController
|
|||
];
|
||||
|
||||
try {
|
||||
$attachmentSubmitHandler->handleFormSubmit($attachment->getData(), $attachment['file']->getData(), $options);
|
||||
$this->attachmentSubmitHandler->handleFormSubmit($attachment->getData(), $attachment['file']->getData(), $options);
|
||||
} catch (AttachmentDownloadException $attachmentDownloadException) {
|
||||
$this->addFlash(
|
||||
'error',
|
||||
$translator->trans('attachment.download_failed').' '.$attachmentDownloadException->getMessage()
|
||||
$this->translator->trans('attachment.download_failed').' '.$attachmentDownloadException->getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->commentHelper->setMessage($form['log_comment']->getData());
|
||||
|
||||
$em->persist($new_part);
|
||||
$em->flush();
|
||||
$this->addFlash('success', 'part.created_flash');
|
||||
$this->em->persist($new_part);
|
||||
$this->em->flush();
|
||||
if ($mode === 'new') {
|
||||
$this->addFlash('success', 'part.created_flash');
|
||||
} else if ($mode === 'edit') {
|
||||
$this->addFlash('success', 'part.edited_flash');
|
||||
}
|
||||
|
||||
//If a redirect URL was given, redirect there
|
||||
if ($request->query->get('_redirect')) {
|
||||
|
@ -314,13 +305,21 @@ class PartController extends AbstractController
|
|||
$this->addFlash('error', 'part.created_flash.invalid');
|
||||
}
|
||||
|
||||
return $this->render('parts/edit/new_part.html.twig',
|
||||
$template = '';
|
||||
if ($mode === 'new') {
|
||||
$template = 'parts/edit/new_part.html.twig';
|
||||
} else if ($mode === 'edit') {
|
||||
$template = 'parts/edit/edit_part_info.html.twig';
|
||||
}
|
||||
|
||||
return $this->render($template,
|
||||
[
|
||||
'part' => $new_part,
|
||||
'form' => $form,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
#[Route(path: '/{id}/add_withdraw', name: 'part_add_withdraw', methods: ['POST'])]
|
||||
public function withdrawAddHandler(Part $part, Request $request, EntityManagerInterface $em, PartLotWithdrawAddHelper $withdrawAddHelper): Response
|
||||
{
|
||||
|
|
|
@ -46,6 +46,7 @@ use App\Entity\Attachments\PartAttachment;
|
|||
use App\Entity\Parts\Category;
|
||||
use App\Entity\Parts\Footprint;
|
||||
use App\Entity\Parts\Manufacturer;
|
||||
use App\Entity\Parts\ManufacturingStatus;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\Parts\PartLot;
|
||||
use App\Entity\Parts\Storelocation;
|
||||
|
@ -83,7 +84,7 @@ class PartFixtures extends Fixture implements DependentFixtureInterface
|
|||
$part->setTags('test, Test, Part2');
|
||||
$part->setMass(100.2);
|
||||
$part->setNeedsReview(true);
|
||||
$part->setManufacturingStatus('active');
|
||||
$part->setManufacturingStatus(ManufacturingStatus::ACTIVE);
|
||||
$manager->persist($part);
|
||||
|
||||
/** Part with orderdetails, storelocations and Attachments */
|
||||
|
|
|
@ -31,10 +31,14 @@ class EnumColumn extends AbstractColumn
|
|||
{
|
||||
|
||||
/**
|
||||
* @phpstan-return T
|
||||
* @phpstan-return T|null
|
||||
*/
|
||||
public function normalize($value): UnitEnum
|
||||
public function normalize($value): ?UnitEnum
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_a($value, $this->getEnumClass())) {
|
||||
return $value;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\DataTables;
|
||||
|
||||
use App\DataTables\Column\EnumColumn;
|
||||
use App\Entity\Parts\ManufacturingStatus;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use App\Entity\Parts\Storelocation;
|
||||
use App\DataTables\Adapters\CustomFetchJoinORMAdapter;
|
||||
|
@ -227,18 +229,17 @@ final class PartsDataTable implements DataTableTypeInterface
|
|||
'label' => $this->translator->trans('part.table.favorite'),
|
||||
'visible' => false,
|
||||
])
|
||||
->add('manufacturing_status', MapColumn::class, [
|
||||
->add('manufacturing_status', EnumColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.manufacturingStatus'),
|
||||
'visible' => false,
|
||||
'default' => $this->translator->trans('m_status.unknown'),
|
||||
'map' => [
|
||||
'' => $this->translator->trans('m_status.unknown'),
|
||||
'announced' => $this->translator->trans('m_status.announced'),
|
||||
'active' => $this->translator->trans('m_status.active'),
|
||||
'nrfnd' => $this->translator->trans('m_status.nrfnd'),
|
||||
'eol' => $this->translator->trans('m_status.eol'),
|
||||
'discontinued' => $this->translator->trans('m_status.discontinued'),
|
||||
],
|
||||
'class' => ManufacturingStatus::class,
|
||||
'render' => function(?ManufacturingStatus $status, Part $context): string {
|
||||
if (!$status) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->translator->trans($status->toTranslationKey());
|
||||
} ,
|
||||
])
|
||||
->add('manufacturer_product_number', TextColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.mpn'),
|
||||
|
|
|
@ -124,6 +124,12 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement
|
|||
*/
|
||||
private array $full_path_strings = [];
|
||||
|
||||
/**
|
||||
* Alternative names (semicolon-separated) for this element, which can be used for searching (especially for info provider system)
|
||||
*/
|
||||
#[ORM\Column(type: Types::TEXT, nullable: true, options: ['default' => null])]
|
||||
private ?string $alternative_names = "";
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
@ -413,4 +419,34 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a comma separated list of alternative names.
|
||||
* @return string|null
|
||||
*/
|
||||
public function getAlternativeNames(): ?string
|
||||
{
|
||||
if ($this->alternative_names === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//Remove trailing comma
|
||||
return rtrim($this->alternative_names, ',');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a comma separated list of alternative names.
|
||||
* @return $this
|
||||
*/
|
||||
public function setAlternativeNames(?string $new_value): self
|
||||
{
|
||||
//Add a trailing comma, if not already there (makes it easier to find in the database)
|
||||
if (is_string($new_value) && substr($new_value, -1) !== ',') {
|
||||
$new_value .= ',';
|
||||
}
|
||||
|
||||
$this->alternative_names = $new_value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
134
src/Entity/OAuthToken.php
Normal file
134
src/Entity/OAuthToken.php
Normal file
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Entity\Base\AbstractDBElement;
|
||||
use App\Entity\Base\AbstractNamedDBElement;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use League\OAuth2\Client\Token\AccessTokenInterface;
|
||||
|
||||
/**
|
||||
* This entity represents a OAuth token pair (access and refresh token), for an application
|
||||
*/
|
||||
#[ORM\Entity()]
|
||||
#[ORM\Table(name: 'oauth_tokens')]
|
||||
#[ORM\UniqueConstraint(name: 'oauth_tokens_unique_name', columns: ['name'])]
|
||||
#[ORM\Index(columns: ['name'], name: 'oauth_tokens_name_idx')]
|
||||
class OAuthToken extends AbstractNamedDBElement implements AccessTokenInterface
|
||||
{
|
||||
/** @var string|null The short-term usable OAuth2 token */
|
||||
#[ORM\Column(type: 'string', nullable: true)]
|
||||
private ?string $token = null;
|
||||
|
||||
/** @var \DateTimeInterface The date when the token expires */
|
||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)]
|
||||
private ?\DateTimeInterface $expires_at = null;
|
||||
|
||||
/** @var string The refresh token for the OAuth2 auth */
|
||||
#[ORM\Column(type: 'string')]
|
||||
private string $refresh_token = '';
|
||||
|
||||
private const DEFAULT_EXPIRATION_TIME = 3600;
|
||||
|
||||
public function __construct(string $name, string $refresh_token, string $token = null, \DateTimeInterface $expires_at = null)
|
||||
{
|
||||
//If token is given, you also have to give the expires_at date
|
||||
if ($token !== null && $expires_at === null) {
|
||||
throw new \InvalidArgumentException('If you give a token, you also have to give the expires_at date');
|
||||
}
|
||||
|
||||
$this->name = $name;
|
||||
$this->refresh_token = $refresh_token;
|
||||
$this->expires_at = $expires_at;
|
||||
$this->token = $token;
|
||||
}
|
||||
|
||||
public static function fromAccessToken(AccessTokenInterface $accessToken, string $name): self
|
||||
{
|
||||
return new self(
|
||||
$name,
|
||||
$accessToken->getRefreshToken(),
|
||||
$accessToken->getToken(),
|
||||
self::unixTimestampToDatetime($accessToken->getExpires() ?? time() + self::DEFAULT_EXPIRATION_TIME)
|
||||
);
|
||||
}
|
||||
|
||||
private static function unixTimestampToDatetime(int $timestamp): \DateTimeInterface
|
||||
{
|
||||
return \DateTimeImmutable::createFromFormat('U', (string)$timestamp);
|
||||
}
|
||||
|
||||
public function getToken(): ?string
|
||||
{
|
||||
return $this->token;
|
||||
}
|
||||
|
||||
public function getExpirationDate(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->expires_at;
|
||||
}
|
||||
|
||||
public function getRefreshToken(): string
|
||||
{
|
||||
return $this->refresh_token;
|
||||
}
|
||||
|
||||
public function isExpired(): bool
|
||||
{
|
||||
//null token is always expired
|
||||
if ($this->token === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->expires_at === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->expires_at->getTimestamp() < time();
|
||||
}
|
||||
|
||||
public function replaceWithNewToken(AccessTokenInterface $accessToken): void
|
||||
{
|
||||
$this->token = $accessToken->getToken();
|
||||
$this->refresh_token = $accessToken->getRefreshToken();
|
||||
//If no expiration date is given, we set it to the default expiration time
|
||||
$this->expires_at = self::unixTimestampToDatetime($accessToken->getExpires() ?? time() + self::DEFAULT_EXPIRATION_TIME);
|
||||
}
|
||||
|
||||
public function getExpires()
|
||||
{
|
||||
return $this->expires_at->getTimestamp();
|
||||
}
|
||||
|
||||
public function hasExpired()
|
||||
{
|
||||
return $this->isExpired();
|
||||
}
|
||||
|
||||
public function getValues()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
155
src/Entity/Parts/InfoProviderReference.php
Normal file
155
src/Entity/Parts/InfoProviderReference.php
Normal file
|
@ -0,0 +1,155 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Entity\Parts;
|
||||
|
||||
use App\Services\InfoProviderSystem\DTOs\SearchResultDTO;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Embeddable;
|
||||
|
||||
/**
|
||||
* This class represents a reference to a info provider inside a part.
|
||||
*/
|
||||
#[Embeddable]
|
||||
class InfoProviderReference
|
||||
{
|
||||
|
||||
/** @var string|null The key referencing the provider used to get this part, or null if it was not provided by a data provider */
|
||||
#[Column(type: 'string', nullable: true)]
|
||||
private ?string $provider_key = null;
|
||||
|
||||
/** @var string|null The id of this part inside the provider system or null if the part was not provided by a data provider */
|
||||
#[Column(type: 'string', nullable: true)]
|
||||
private ?string $provider_id = null;
|
||||
|
||||
/**
|
||||
* @var string|null The url of this part inside the provider system or null if this info is not existing
|
||||
*/
|
||||
#[Column(type: 'string', nullable: true)]
|
||||
private ?string $provider_url = null;
|
||||
|
||||
#[Column(type: Types::DATETIME_MUTABLE, nullable: true, options: ['default' => null])]
|
||||
private ?\DateTimeInterface $last_updated = null;
|
||||
|
||||
/**
|
||||
* Constructing is forbidden from outside.
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key usable to identify the provider, which provided this part. Returns null, if the part was not created by a provider.
|
||||
* @return string|null
|
||||
*/
|
||||
public function getProviderKey(): ?string
|
||||
{
|
||||
return $this->provider_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id of this part inside the provider system or null if the part was not provided by a data provider.
|
||||
* @return string|null
|
||||
*/
|
||||
public function getProviderId(): ?string
|
||||
{
|
||||
return $this->provider_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the url of this part inside the provider system or null if this info is not existing.
|
||||
* @return string|null
|
||||
*/
|
||||
public function getProviderUrl(): ?string
|
||||
{
|
||||
return $this->provider_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the time, when the part was last time updated by the provider.
|
||||
* @return \DateTimeInterface|null
|
||||
*/
|
||||
public function getLastUpdated(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->last_updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true, if this part was created based on infos from a provider.
|
||||
* Or false, if this part was created by a user manually.
|
||||
* @return bool
|
||||
*/
|
||||
public function isProviderCreated(): bool
|
||||
{
|
||||
return $this->provider_key !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance, without any provider information.
|
||||
* Use this for parts, which are created by a user manually.
|
||||
* @return InfoProviderReference
|
||||
*/
|
||||
public static function noProvider(): self
|
||||
{
|
||||
$ref = new InfoProviderReference();
|
||||
$ref->provider_key = null;
|
||||
$ref->provider_id = null;
|
||||
$ref->provider_url = null;
|
||||
$ref->last_updated = null;
|
||||
return $ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a reference to an info provider based on the given parameters.
|
||||
* @param string $provider_key
|
||||
* @param string $provider_id
|
||||
* @param string|null $provider_url
|
||||
* @return self
|
||||
*/
|
||||
public static function providerReference(string $provider_key, string $provider_id, ?string $provider_url = null): self
|
||||
{
|
||||
$ref = new InfoProviderReference();
|
||||
$ref->provider_key = $provider_key;
|
||||
$ref->provider_id = $provider_id;
|
||||
$ref->provider_url = $provider_url;
|
||||
$ref->last_updated = new \DateTimeImmutable();
|
||||
return $ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a reference to an info provider based on the given Part DTO
|
||||
* @param SearchResultDTO $dto
|
||||
* @return self
|
||||
*/
|
||||
public static function fromPartDTO(SearchResultDTO $dto): self
|
||||
{
|
||||
$ref = new InfoProviderReference();
|
||||
$ref->provider_key = $dto->provider_key;
|
||||
$ref->provider_id = $dto->provider_id;
|
||||
$ref->provider_url = $dto->provider_url;
|
||||
$ref->last_updated = new \DateTimeImmutable();
|
||||
return $ref;
|
||||
}
|
||||
}
|
53
src/Entity/Parts/ManufacturingStatus.php
Normal file
53
src/Entity/Parts/ManufacturingStatus.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Entity\Parts;
|
||||
|
||||
enum ManufacturingStatus: string
|
||||
{
|
||||
/** Part has been announced, but is not in production yet */
|
||||
case ANNOUNCED = 'announced';
|
||||
/** Part is in production and will be for the foreseeable future */
|
||||
case ACTIVE = 'active';
|
||||
/** Not recommended for new designs. */
|
||||
case NRFND = 'nrfnd';
|
||||
/** End of life: Part will become discontinued soon */
|
||||
case EOL = 'eol';
|
||||
/** Part is obsolete/discontinued by the manufacturer. */
|
||||
case DISCONTINUED = 'discontinued';
|
||||
|
||||
/** Status not set */
|
||||
case NOT_SET = '';
|
||||
|
||||
public function toTranslationKey(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::ANNOUNCED => 'm_status.announced',
|
||||
self::ACTIVE => 'm_status.active',
|
||||
self::NRFND => 'm_status.nrfnd',
|
||||
self::EOL => 'm_status.eol',
|
||||
self::DISCONTINUED => 'm_status.discontinued',
|
||||
self::NOT_SET => '',
|
||||
};
|
||||
}
|
||||
}
|
|
@ -114,6 +114,9 @@ class Part extends AttachmentContainingDBElement
|
|||
$this->orderdetails = new ArrayCollection();
|
||||
$this->parameters = new ArrayCollection();
|
||||
$this->project_bom_entries = new ArrayCollection();
|
||||
|
||||
//By default, the part has no provider
|
||||
$this->providerReference = InfoProviderReference::noProvider();
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
|
@ -139,6 +142,9 @@ class Part extends AttachmentContainingDBElement
|
|||
foreach ($parameters as $parameter) {
|
||||
$this->addParameter(clone $parameter);
|
||||
}
|
||||
|
||||
//Deep clone info provider
|
||||
$this->providerReference = clone $this->providerReference;
|
||||
}
|
||||
parent::__clone();
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Entity\Parts\PartTraits;
|
||||
|
||||
use App\Entity\Parts\InfoProviderReference;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use App\Entity\Parts\Part;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
@ -63,6 +64,12 @@ trait AdvancedPropertyTrait
|
|||
#[ORM\Column(type: Types::STRING, length: 100, nullable: true, unique: true)]
|
||||
protected ?string $ipn = null;
|
||||
|
||||
/**
|
||||
* @var InfoProviderReference The reference to the info provider, that provided the information about this part
|
||||
*/
|
||||
#[ORM\Embedded(class: InfoProviderReference::class, columnPrefix: 'provider_reference_')]
|
||||
protected InfoProviderReference $providerReference;
|
||||
|
||||
/**
|
||||
* Checks if this part is marked, for that it needs further review.
|
||||
*/
|
||||
|
@ -150,5 +157,27 @@ trait AdvancedPropertyTrait
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reference to the info provider, that provided the information about this part.
|
||||
* @return InfoProviderReference
|
||||
*/
|
||||
public function getProviderReference(): InfoProviderReference
|
||||
{
|
||||
return $this->providerReference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the reference to the info provider, that provided the information about this part.
|
||||
* @param InfoProviderReference $providerReference
|
||||
* @return Part
|
||||
*/
|
||||
public function setProviderReference(InfoProviderReference $providerReference): Part
|
||||
{
|
||||
$this->providerReference = $providerReference;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Entity\Parts\PartTraits;
|
||||
|
||||
use App\Entity\Parts\ManufacturingStatus;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use App\Entity\Parts\Manufacturer;
|
||||
use App\Entity\Parts\Part;
|
||||
|
@ -60,12 +61,11 @@ trait ManufacturerTrait
|
|||
protected string $manufacturer_product_number = '';
|
||||
|
||||
/**
|
||||
* @var string|null The production status of this part. Can be one of the specified ones.
|
||||
* @var ManufacturingStatus|null The production status of this part. Can be one of the specified ones.
|
||||
*/
|
||||
#[Assert\Choice(['announced', 'active', 'nrfnd', 'eol', 'discontinued', ''])]
|
||||
#[Groups(['extended', 'full', 'import'])]
|
||||
#[ORM\Column(type: Types::STRING, length: 255, nullable: true)]
|
||||
protected ?string $manufacturing_status = '';
|
||||
#[ORM\Column(type: Types::STRING, length: 255, nullable: true, enumType: ManufacturingStatus::class)]
|
||||
protected ?ManufacturingStatus $manufacturing_status = ManufacturingStatus::NOT_SET;
|
||||
|
||||
/**
|
||||
* Get the link to the website of the article on the manufacturers website
|
||||
|
@ -113,9 +113,9 @@ trait ManufacturerTrait
|
|||
* * "eol": Part will become discontinued soon
|
||||
* * "discontinued": Part is obsolete/discontinued by the manufacturer.
|
||||
*
|
||||
* @return string
|
||||
* @return ManufacturingStatus|null
|
||||
*/
|
||||
public function getManufacturingStatus(): ?string
|
||||
public function getManufacturingStatus(): ?ManufacturingStatus
|
||||
{
|
||||
return $this->manufacturing_status;
|
||||
}
|
||||
|
@ -124,9 +124,9 @@ trait ManufacturerTrait
|
|||
* Sets the manufacturing status for this part
|
||||
* See getManufacturingStatus() for valid values.
|
||||
*
|
||||
* @return Part
|
||||
* @return $this
|
||||
*/
|
||||
public function setManufacturingStatus(string $manufacturing_status): self
|
||||
public function setManufacturingStatus(ManufacturingStatus $manufacturing_status): self
|
||||
{
|
||||
$this->manufacturing_status = $manufacturing_status;
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Entity\PriceInformations;
|
||||
|
||||
use App\Repository\CurrencyRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use App\Entity\Attachments\CurrencyAttachment;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
|
@ -42,7 +43,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
* @extends AbstractStructuralDBElement<CurrencyAttachment, CurrencyParameter>
|
||||
*/
|
||||
#[UniqueEntity('iso_code')]
|
||||
#[ORM\Entity]
|
||||
#[ORM\Entity(repositoryClass: CurrencyRepository::class)]
|
||||
#[ORM\Table(name: 'currencies')]
|
||||
#[ORM\Index(name: 'currency_idx_name', columns: ['name'])]
|
||||
#[ORM\Index(name: 'currency_idx_parent_name', columns: ['parent_id', 'name'])]
|
||||
|
|
|
@ -29,6 +29,7 @@ use App\Entity\LogSystem\CollectionElementDeleted;
|
|||
use App\Entity\LogSystem\ElementCreatedLogEntry;
|
||||
use App\Entity\LogSystem\ElementDeletedLogEntry;
|
||||
use App\Entity\LogSystem\ElementEditedLogEntry;
|
||||
use App\Entity\OAuthToken;
|
||||
use App\Entity\Parameters\AbstractParameter;
|
||||
use App\Entity\Parts\PartLot;
|
||||
use App\Entity\PriceInformations\Orderdetail;
|
||||
|
@ -344,6 +345,11 @@ class EventLoggerSubscriber implements EventSubscriber
|
|||
*/
|
||||
protected function validEntity(object $entity): bool
|
||||
{
|
||||
//Dont log OAuthTokens
|
||||
if ($entity instanceof OAuthToken) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//Dont log logentries itself!
|
||||
return $entity instanceof AbstractDBElement && !$entity instanceof AbstractLogEntry;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,9 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Form\AdminPages;
|
||||
|
||||
use App\Entity\PriceInformations\Currency;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Entity\UserSystem\Group;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use App\Entity\Base\AbstractNamedDBElement;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
|
@ -111,6 +114,19 @@ class BaseEntityAdminForm extends AbstractType
|
|||
);
|
||||
}
|
||||
|
||||
if ($entity instanceof AbstractStructuralDBElement && !($entity instanceof Group || $entity instanceof Project || $entity instanceof Currency)) {
|
||||
$builder->add('alternative_names', TextType::class, [
|
||||
'required' => false,
|
||||
'label' => 'entity.edit.alternative_names.label',
|
||||
'help' => 'entity.edit.alternative_names.help',
|
||||
'empty_data' => null,
|
||||
'attr' => [
|
||||
'class' => 'tagsinput',
|
||||
'data-controller' => 'elements--tagsinput',
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
$this->additionalFormElements($builder, $options, $entity);
|
||||
|
||||
//Attachment section
|
||||
|
|
47
src/Form/InfoProviderSystem/PartSearchType.php
Normal file
47
src/Form/InfoProviderSystem/PartSearchType.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Form\InfoProviderSystem;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SearchType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
class PartSearchType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->add('keyword', SearchType::class, [
|
||||
'label' => 'info_providers.search.keyword',
|
||||
]);
|
||||
$builder->add('providers', ProviderSelectType::class, [
|
||||
'label' => 'info_providers.search.providers',
|
||||
'help' => 'info_providers.search.providers.help',
|
||||
]);
|
||||
|
||||
$builder->add('submit', SubmitType::class, [
|
||||
'label' => 'info_providers.search.submit'
|
||||
]);
|
||||
}
|
||||
}
|
57
src/Form/InfoProviderSystem/ProviderSelectType.php
Normal file
57
src/Form/InfoProviderSystem/ProviderSelectType.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Form\InfoProviderSystem;
|
||||
|
||||
use App\Services\InfoProviderSystem\ProviderRegistry;
|
||||
use App\Services\InfoProviderSystem\Providers\InfoProviderInterface;
|
||||
use Hoa\Compiler\Llk\Rule\Choice;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceList;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class ProviderSelectType extends AbstractType
|
||||
{
|
||||
public function __construct(private readonly ProviderRegistry $providerRegistry)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function getParent(): string
|
||||
{
|
||||
return ChoiceType::class;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'choices' => $this->providerRegistry->getActiveProviders(),
|
||||
'choice_label' => ChoiceList::label($this, fn (?InfoProviderInterface $choice) => $choice?->getProviderInfo()['name']),
|
||||
'choice_value' => ChoiceList::value($this, fn(?InfoProviderInterface $choice) => $choice?->getProviderKey()),
|
||||
|
||||
'multiple' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
|
@ -22,6 +22,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Form\Part;
|
||||
|
||||
use App\Entity\Parts\ManufacturingStatus;
|
||||
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use App\Entity\Attachments\PartAttachment;
|
||||
use App\Entity\Parameters\PartParameter;
|
||||
|
@ -42,6 +44,7 @@ use Symfony\Component\Form\AbstractType;
|
|||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EnumType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ResetType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
|
@ -49,6 +52,7 @@ use Symfony\Component\Form\Extension\Core\Type\UrlType;
|
|||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class PartBaseType extends AbstractType
|
||||
{
|
||||
|
@ -62,14 +66,8 @@ class PartBaseType extends AbstractType
|
|||
$part = $builder->getData();
|
||||
$new_part = null === $part->getID();
|
||||
|
||||
$status_choices = [
|
||||
'm_status.unknown' => '',
|
||||
'm_status.announced' => 'announced',
|
||||
'm_status.active' => 'active',
|
||||
'm_status.nrfnd' => 'nrfnd',
|
||||
'm_status.eol' => 'eol',
|
||||
'm_status.discontinued' => 'discontinued',
|
||||
];
|
||||
/** @var PartDetailDTO|null $dto */
|
||||
$dto = $options['info_provider_dto'];
|
||||
|
||||
//Common section
|
||||
$builder
|
||||
|
@ -101,6 +99,7 @@ class PartBaseType extends AbstractType
|
|||
->add('category', StructuralEntityType::class, [
|
||||
'class' => Category::class,
|
||||
'allow_add' => $this->security->isGranted('@categories.create'),
|
||||
'dto_value' => $dto?->category,
|
||||
'label' => 'part.edit.category',
|
||||
'disable_not_selectable' => true,
|
||||
])
|
||||
|
@ -108,6 +107,7 @@ class PartBaseType extends AbstractType
|
|||
'class' => Footprint::class,
|
||||
'required' => false,
|
||||
'label' => 'part.edit.footprint',
|
||||
'dto_value' => $dto?->footprint,
|
||||
'allow_add' => $this->security->isGranted('@footprints.create'),
|
||||
'disable_not_selectable' => true,
|
||||
])
|
||||
|
@ -128,6 +128,7 @@ class PartBaseType extends AbstractType
|
|||
'required' => false,
|
||||
'label' => 'part.edit.manufacturer.label',
|
||||
'allow_add' => $this->security->isGranted('@manufacturers.create'),
|
||||
'dto_value' => $dto?->manufacturer,
|
||||
'disable_not_selectable' => true,
|
||||
])
|
||||
->add('manufacturer_product_url', UrlType::class, [
|
||||
|
@ -140,9 +141,10 @@ class PartBaseType extends AbstractType
|
|||
'empty_data' => '',
|
||||
'label' => 'part.edit.mpn',
|
||||
])
|
||||
->add('manufacturing_status', ChoiceType::class, [
|
||||
->add('manufacturing_status', EnumType::class, [
|
||||
'label' => 'part.edit.manufacturing_status',
|
||||
'choices' => $status_choices,
|
||||
'class' => ManufacturingStatus::class,
|
||||
'choice_label' => fn (ManufacturingStatus $status) => $status->toTranslationKey(),
|
||||
'required' => false,
|
||||
]);
|
||||
|
||||
|
@ -273,10 +275,15 @@ class PartBaseType extends AbstractType
|
|||
->add('reset', ResetType::class, ['label' => 'part.edit.reset']);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Part::class,
|
||||
'info_provider_dto' => null,
|
||||
]);
|
||||
|
||||
$resolver->setAllowedTypes('info_provider_dto', [PartDetailDTO::class, 'null']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,6 +86,9 @@ class StructuralEntityChoiceHelper
|
|||
$tmp += ['data-filetype_filter' => $choice->getFiletypeFilter()];
|
||||
}
|
||||
|
||||
//Show entities that are not added to DB yet separately from other entities
|
||||
$tmp['data-not_in_db_yet'] = $choice->getID() === null;
|
||||
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
|
@ -99,6 +102,9 @@ class StructuralEntityChoiceHelper
|
|||
$symbol = empty($choice->getIsoCode()) ? null : Currencies::getSymbol($choice->getIsoCode());
|
||||
$tmp['data-short'] = $options['short'] ? $symbol : $choice->getName();
|
||||
|
||||
//Show entities that are not added to DB yet separately from other entities
|
||||
$tmp['data-not_in_db_yet'] = $choice->getID() === null;
|
||||
|
||||
return $tmp + [
|
||||
'data-symbol' => $symbol,
|
||||
];
|
||||
|
|
|
@ -22,6 +22,8 @@ declare(strict_types=1);
|
|||
*/
|
||||
namespace App\Form\Type\Helper;
|
||||
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use App\Entity\PriceInformations\Currency;
|
||||
use App\Repository\StructuralDBElementRepository;
|
||||
use App\Services\Trees\NodesListBuilder;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
@ -32,13 +34,21 @@ class StructuralEntityChoiceLoader extends AbstractChoiceLoader
|
|||
{
|
||||
private ?string $additional_element = null;
|
||||
|
||||
private ?AbstractStructuralDBElement $starting_element = null;
|
||||
|
||||
public function __construct(private readonly Options $options, private readonly NodesListBuilder $builder, private readonly EntityManagerInterface $entityManager)
|
||||
{
|
||||
}
|
||||
|
||||
protected function loadChoices(): iterable
|
||||
{
|
||||
$tmp = [];
|
||||
//If the starting_element is set and not persisted yet, add it to the list
|
||||
if ($this->starting_element !== null && $this->starting_element->getID() === null) {
|
||||
$tmp = [$this->starting_element];
|
||||
} else {
|
||||
$tmp = [];
|
||||
}
|
||||
|
||||
if ($this->additional_element) {
|
||||
$tmp = $this->createNewEntitiesFromValue($this->additional_element);
|
||||
$this->additional_element = null;
|
||||
|
@ -49,14 +59,24 @@ class StructuralEntityChoiceLoader extends AbstractChoiceLoader
|
|||
|
||||
public function createNewEntitiesFromValue(string $value): array
|
||||
{
|
||||
if (!$this->options['allow_add']) {
|
||||
throw new \RuntimeException('Cannot create new entity, because allow_add is not enabled!');
|
||||
}
|
||||
|
||||
if (trim($value) === '') {
|
||||
throw new \InvalidArgumentException('Cannot create new entity, because the name is empty!');
|
||||
}
|
||||
|
||||
//Check if the value is matching the starting value element, we use the choice_value option to get the name of the starting element
|
||||
if ($this->starting_element !== null
|
||||
&& $this->starting_element->getID() === null //Element must not be persisted yet
|
||||
&& $this->options['choice_value']($this->starting_element) === $value) {
|
||||
|
||||
//Then reuse the starting element
|
||||
$this->entityManager->persist($this->starting_element);
|
||||
return [$this->starting_element];
|
||||
}
|
||||
|
||||
if (!$this->options['allow_add']) {
|
||||
throw new \RuntimeException('Cannot create new entity, because allow_add is not enabled!');
|
||||
}
|
||||
|
||||
$class = $this->options['class'];
|
||||
/** @var StructuralDBElementRepository $repo */
|
||||
$repo = $this->entityManager->getRepository($class);
|
||||
|
@ -86,4 +106,25 @@ class StructuralEntityChoiceLoader extends AbstractChoiceLoader
|
|||
return $this->additional_element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the initial value used to populate the field.
|
||||
* @return AbstractStructuralDBElement|null
|
||||
*/
|
||||
public function getStartingElement(): ?AbstractStructuralDBElement
|
||||
{
|
||||
return $this->starting_element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the initial value used to populate the field. This will always be an allowed value.
|
||||
* @param AbstractStructuralDBElement|null $starting_element
|
||||
* @return StructuralEntityChoiceLoader
|
||||
*/
|
||||
public function setStartingElement(?AbstractStructuralDBElement $starting_element): StructuralEntityChoiceLoader
|
||||
{
|
||||
$this->starting_element = $starting_element;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -100,6 +100,17 @@ class StructuralEntityType extends AbstractType
|
|||
|
||||
$resolver->setDefault('controller', 'elements--structural-entity-select');
|
||||
|
||||
//Options for DTO values
|
||||
$resolver->setDefault('dto_value', null);
|
||||
$resolver->setAllowedTypes('dto_value', ['null', 'string']);
|
||||
//If no help text is explicitly set, we use the dto value as help text and show it as html
|
||||
$resolver->setDefault('help', function (Options $options) {
|
||||
return $this->dtoText($options['dto_value']);
|
||||
});
|
||||
$resolver->setDefault('help_html', function (Options $options) {
|
||||
return $options['dto_value'] !== null;
|
||||
});
|
||||
|
||||
$resolver->setDefault('attr', function (Options $options) {
|
||||
$tmp = [
|
||||
'data-controller' => $options['controller'],
|
||||
|
@ -114,6 +125,16 @@ class StructuralEntityType extends AbstractType
|
|||
});
|
||||
}
|
||||
|
||||
private function dtoText(?string $text): ?string
|
||||
{
|
||||
if ($text === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$result = '<b>' . $this->translator->trans('info_providers.form.help_prefix') . ':</b> ';
|
||||
|
||||
return $result . htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ;
|
||||
}
|
||||
|
||||
public function getParent(): string
|
||||
{
|
||||
|
@ -122,6 +143,11 @@ class StructuralEntityType extends AbstractType
|
|||
|
||||
public function modelTransform($value, array $options)
|
||||
{
|
||||
$choice_loader = $options['choice_loader'];
|
||||
if ($choice_loader instanceof StructuralEntityChoiceLoader) {
|
||||
$choice_loader->setStartingElement($value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
|
59
src/Repository/CurrencyRepository.php
Normal file
59
src/Repository/CurrencyRepository.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use App\Entity\PriceInformations\Currency;
|
||||
use Symfony\Component\Intl\Currencies;
|
||||
|
||||
/**
|
||||
* @extends StructuralDBElementRepository<Currency>
|
||||
*/
|
||||
class CurrencyRepository extends StructuralDBElementRepository
|
||||
{
|
||||
/**
|
||||
* Finds or create a currency with the given ISO code.
|
||||
* @param string $iso_code
|
||||
* @return Currency
|
||||
*/
|
||||
public function findOrCreateByISOCode(string $iso_code): Currency
|
||||
{
|
||||
//Normalize ISO code
|
||||
$iso_code = strtoupper($iso_code);
|
||||
|
||||
//Try to find currency
|
||||
$currency = $this->findOneBy(['iso_code' => $iso_code]);
|
||||
if ($currency !== null) {
|
||||
return $currency;
|
||||
}
|
||||
|
||||
//Create currency if it does not exist
|
||||
$name = Currencies::getName($iso_code);
|
||||
|
||||
$currency = $this->findOrCreateForInfoProvider($name);
|
||||
$currency->setIsoCode($iso_code);
|
||||
|
||||
return $currency;
|
||||
}
|
||||
}
|
|
@ -185,4 +185,74 @@ class StructuralDBElementRepository extends NamedDBElementRepository
|
|||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the element with the given name for the use with the InfoProvider System
|
||||
* The name search is a bit more fuzzy than the normal findByName, because it is case-insensitive and ignores special characters.
|
||||
* Also, it will try to find the element using the additional names field, of the elements.
|
||||
* @param string $name
|
||||
* @return AbstractStructuralDBElement|null
|
||||
* @phpstan-return TEntityClass|null
|
||||
*/
|
||||
public function findForInfoProvider(string $name): ?AbstractStructuralDBElement
|
||||
{
|
||||
//First try to find the element by name
|
||||
$qb = $this->createQueryBuilder('e');
|
||||
//Use lowercase conversion to be case-insensitive
|
||||
$qb->where($qb->expr()->like('LOWER(e.name)', 'LOWER(:name)'));
|
||||
|
||||
$qb->setParameter('name', $name);
|
||||
|
||||
$result = $qb->getQuery()->getResult();
|
||||
|
||||
if (count($result) === 1) {
|
||||
return $result[0];
|
||||
}
|
||||
|
||||
//If we have no result, try to find the element by alternative names
|
||||
$qb = $this->createQueryBuilder('e');
|
||||
//Use lowercase conversion to be case-insensitive
|
||||
$qb->where($qb->expr()->like('LOWER(e.alternative_names)', 'LOWER(:name)'));
|
||||
$qb->setParameter('name', '%'.$name.',%');
|
||||
|
||||
$result = $qb->getQuery()->getResult();
|
||||
|
||||
if (count($result) >= 1) {
|
||||
return $result[0];
|
||||
}
|
||||
|
||||
//If we find nothing, return null
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to findForInfoProvider, but will create a new element with the given name if none was found.
|
||||
* @param string $name
|
||||
* @return AbstractStructuralDBElement
|
||||
* @phpstan-return TEntityClass
|
||||
*/
|
||||
public function findOrCreateForInfoProvider(string $name): AbstractStructuralDBElement
|
||||
{
|
||||
$entity = $this->findForInfoProvider($name);
|
||||
if (null === $entity) {
|
||||
|
||||
//Try to find if we already have an element cached for this name
|
||||
$entity = $this->getNewEntityFromCache($name, null);
|
||||
if ($entity) {
|
||||
return $entity;
|
||||
}
|
||||
|
||||
$class = $this->getClassName();
|
||||
/** @var AbstractStructuralDBElement $entity */
|
||||
$entity = new $class;
|
||||
$entity->setName($name);
|
||||
|
||||
//Set the found name to the alternative names, so the entity can be easily renamed later
|
||||
$entity->setAlternativeNames($name);
|
||||
|
||||
$this->setNewEntityToCache($entity);
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
}
|
||||
|
|
42
src/Services/InfoProviderSystem/DTOs/FileDTO.php
Normal file
42
src/Services/InfoProviderSystem/DTOs/FileDTO.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Services\InfoProviderSystem\DTOs;
|
||||
|
||||
/**
|
||||
* This DTO represents a file that can be downloaded from a URL.
|
||||
* This could be a datasheet, a 3D model, a picture or similar.
|
||||
*/
|
||||
class FileDTO
|
||||
{
|
||||
/**
|
||||
* @param string $url The URL where to get this file
|
||||
* @param string|null $name Optionally the name of this file
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string $url,
|
||||
public readonly ?string $name = null,
|
||||
) {}
|
||||
|
||||
|
||||
}
|
100
src/Services/InfoProviderSystem/DTOs/ParameterDTO.php
Normal file
100
src/Services/InfoProviderSystem/DTOs/ParameterDTO.php
Normal file
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Services\InfoProviderSystem\DTOs;
|
||||
|
||||
/**
|
||||
* This DTO represents a parameter of a part (similar to the AbstractParameter entity).
|
||||
* This could be a voltage, a current, a temperature or similar.
|
||||
*/
|
||||
class ParameterDTO
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $name,
|
||||
public readonly ?string $value_text = null,
|
||||
public readonly ?float $value_typ = null,
|
||||
public readonly ?float $value_min = null,
|
||||
public readonly ?float $value_max = null,
|
||||
public readonly ?string $unit = null,
|
||||
public readonly ?string $symbol = null,
|
||||
public readonly ?string $group = null,
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This function tries to decide on the value, if it is a numerical value (which is then stored in one of the value_*) fields) or a text value (which is stored in value_text).
|
||||
* It is possible to give ranges like 1...2 here, which will be parsed as value_min: 1.0, value_max: 2.0.
|
||||
* @param string $name
|
||||
* @param string|float $value
|
||||
* @param string|null $unit
|
||||
* @param string|null $symbol
|
||||
* @param string|null $group
|
||||
* @return self
|
||||
*/
|
||||
public static function parseValueField(string $name, string|float $value, ?string $unit = null, ?string $symbol = null, ?string $group = null): self
|
||||
{
|
||||
if (is_float($value) || is_numeric($value)) {
|
||||
return new self($name, value_typ: (float) $value, unit: $unit, symbol: $symbol, group: $group);
|
||||
}
|
||||
|
||||
//Try to parse as range
|
||||
if (str_contains($value, '...')) {
|
||||
$parts = explode('...', $value);
|
||||
if (count($parts) === 2) {
|
||||
|
||||
//Ensure that both parts are numerical
|
||||
if (is_numeric($parts[0]) && is_numeric($parts[1])) {
|
||||
return new self($name, value_min: (float) $parts[0], value_max: (float) $parts[1], unit: $unit, symbol: $symbol, group: $group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new self($name, value_text: $value, unit: $unit, symbol: $symbol, group: $group);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function tries to decide on the value, if it is a numerical value (which is then stored in one of the value_*) fields) or a text value (which is stored in value_text).
|
||||
* It also tries to extract the unit from the value field (so 3kg will be parsed as value_typ: 3.0, unit: kg).
|
||||
* Ranges like 1...2 will be parsed as value_min: 1.0, value_max: 2.0.
|
||||
* @param string $name
|
||||
* @param string|float $value
|
||||
* @param string|null $symbol
|
||||
* @param string|null $group
|
||||
* @return self
|
||||
*/
|
||||
public static function parseValueIncludingUnit(string $name, string|float $value, ?string $symbol = null, ?string $group = null): self
|
||||
{
|
||||
//Try to extract unit from value
|
||||
$unit = null;
|
||||
if (is_string($value) && preg_match('/^(?<value>[0-9.]+)\s*(?<unit>[°a-zA-Z_]+\s?\w{0,4})$/u', $value, $matches)) {
|
||||
$value = $matches['value'];
|
||||
$unit = $matches['unit'];
|
||||
|
||||
return self::parseValueField(name: $name, value: $value, unit: $unit, symbol: $symbol, group: $group);
|
||||
}
|
||||
|
||||
//Otherwise we assume that no unit is given
|
||||
return self::parseValueField(name: $name, value: $value, unit: null, symbol: $symbol, group: $group);
|
||||
}
|
||||
}
|
71
src/Services/InfoProviderSystem/DTOs/PartDetailDTO.php
Normal file
71
src/Services/InfoProviderSystem/DTOs/PartDetailDTO.php
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Services\InfoProviderSystem\DTOs;
|
||||
|
||||
use App\Entity\Parts\ManufacturingStatus;
|
||||
|
||||
/**
|
||||
* This DTO represents a part with all its details.
|
||||
*/
|
||||
class PartDetailDTO extends SearchResultDTO
|
||||
{
|
||||
public function __construct(
|
||||
string $provider_key,
|
||||
string $provider_id,
|
||||
string $name,
|
||||
string $description,
|
||||
?string $category = null,
|
||||
?string $manufacturer = null,
|
||||
?string $mpn = null,
|
||||
?string $preview_image_url = null,
|
||||
?ManufacturingStatus $manufacturing_status = null,
|
||||
?string $provider_url = null,
|
||||
?string $footprint = null,
|
||||
public readonly ?string $notes = null,
|
||||
/** @var FileDTO[]|null */
|
||||
public readonly ?array $datasheets = null,
|
||||
/** @var FileDTO[]|null */
|
||||
public readonly ?array $images = null,
|
||||
/** @var ParameterDTO[]|null */
|
||||
public readonly ?array $parameters = null,
|
||||
/** @var PurchaseInfoDTO[]|null */
|
||||
public readonly ?array $vendor_infos = null,
|
||||
/** The mass of the product in grams */
|
||||
public readonly ?float $mass = null,
|
||||
) {
|
||||
parent::__construct(
|
||||
provider_key: $provider_key,
|
||||
provider_id: $provider_id,
|
||||
name: $name,
|
||||
description: $description,
|
||||
category: $category,
|
||||
manufacturer: $manufacturer,
|
||||
mpn: $mpn,
|
||||
preview_image_url: $preview_image_url,
|
||||
manufacturing_status: $manufacturing_status,
|
||||
provider_url: $provider_url,
|
||||
footprint: $footprint,
|
||||
);
|
||||
}
|
||||
}
|
57
src/Services/InfoProviderSystem/DTOs/PriceDTO.php
Normal file
57
src/Services/InfoProviderSystem/DTOs/PriceDTO.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Services\InfoProviderSystem\DTOs;
|
||||
|
||||
use Brick\Math\BigDecimal;
|
||||
|
||||
/**
|
||||
* This DTO represents a price for a single unit in a certain discount range
|
||||
*/
|
||||
class PriceDTO
|
||||
{
|
||||
private readonly BigDecimal $price_as_big_decimal;
|
||||
|
||||
public function __construct(
|
||||
/** @var float The minimum amount that needs to get ordered for this price to be valid */
|
||||
public readonly float $minimum_discount_amount,
|
||||
/** @var string The price as string (with .) */
|
||||
public readonly string $price,
|
||||
/** @var string The currency of the used ISO code of this price detail */
|
||||
public readonly ?string $currency_iso_code,
|
||||
/** @var bool If the price includes tax */
|
||||
public readonly ?bool $includes_tax = true,
|
||||
)
|
||||
{
|
||||
$this->price_as_big_decimal = BigDecimal::of($this->price);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the price as BigDecimal
|
||||
* @return BigDecimal
|
||||
*/
|
||||
public function getPriceAsBigDecimal(): BigDecimal
|
||||
{
|
||||
return $this->price_as_big_decimal;
|
||||
}
|
||||
}
|
47
src/Services/InfoProviderSystem/DTOs/PurchaseInfoDTO.php
Normal file
47
src/Services/InfoProviderSystem/DTOs/PurchaseInfoDTO.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Services\InfoProviderSystem\DTOs;
|
||||
|
||||
/**
|
||||
* This DTO represents a purchase information for a part (supplier name, order number and prices).
|
||||
*/
|
||||
class PurchaseInfoDTO
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $distributor_name,
|
||||
public readonly string $order_number,
|
||||
/** @var PriceDTO[] */
|
||||
public readonly array $prices,
|
||||
/** @var string|null An url to the product page of the vendor */
|
||||
public readonly ?string $product_url = null,
|
||||
)
|
||||
{
|
||||
//Ensure that the prices are PriceDTO instances
|
||||
foreach ($this->prices as $price) {
|
||||
if (!$price instanceof PriceDTO) {
|
||||
throw new \InvalidArgumentException('The prices array must only contain PriceDTO instances');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
59
src/Services/InfoProviderSystem/DTOs/SearchResultDTO.php
Normal file
59
src/Services/InfoProviderSystem/DTOs/SearchResultDTO.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Services\InfoProviderSystem\DTOs;
|
||||
|
||||
use App\Entity\Parts\ManufacturingStatus;
|
||||
|
||||
/**
|
||||
* This DTO represents a search result for a part.
|
||||
*/
|
||||
class SearchResultDTO
|
||||
{
|
||||
public function __construct(
|
||||
/** @var string The provider key (e.g. "digikey") */
|
||||
public readonly string $provider_key,
|
||||
/** @var string The ID which identifies the part in the provider system */
|
||||
public readonly string $provider_id,
|
||||
/** @var string The name of the part */
|
||||
public readonly string $name,
|
||||
/** @var string A short description of the part */
|
||||
public readonly string $description,
|
||||
/** @var string|null The category the distributor assumes for the part */
|
||||
public readonly ?string $category = null,
|
||||
/** @var string|null The manufacturer of the part */
|
||||
public readonly ?string $manufacturer = null,
|
||||
/** @var string|null The manufacturer part number */
|
||||
public readonly ?string $mpn = null,
|
||||
/** @var string|null An URL to a preview image */
|
||||
public readonly ?string $preview_image_url = null,
|
||||
/** @var ManufacturingStatus|null The manufacturing status of the part */
|
||||
public readonly ?ManufacturingStatus $manufacturing_status = null,
|
||||
/** @var string|null A link to the part on the providers page */
|
||||
public readonly ?string $provider_url = null,
|
||||
/** @var string|null A footprint representation of the providers page */
|
||||
public readonly ?string $footprint = null,
|
||||
) {
|
||||
|
||||
}
|
||||
}
|
292
src/Services/InfoProviderSystem/DTOtoEntityConverter.php
Normal file
292
src/Services/InfoProviderSystem/DTOtoEntityConverter.php
Normal file
|
@ -0,0 +1,292 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Services\InfoProviderSystem;
|
||||
|
||||
use App\Entity\Attachments\AttachmentType;
|
||||
use App\Entity\Attachments\PartAttachment;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use App\Entity\Parameters\AbstractParameter;
|
||||
use App\Entity\Parameters\PartParameter;
|
||||
use App\Entity\Parts\Footprint;
|
||||
use App\Entity\Parts\InfoProviderReference;
|
||||
use App\Entity\Parts\Manufacturer;
|
||||
use App\Entity\Parts\ManufacturingStatus;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\Parts\Supplier;
|
||||
use App\Entity\PriceInformations\Currency;
|
||||
use App\Entity\PriceInformations\Orderdetail;
|
||||
use App\Entity\PriceInformations\Pricedetail;
|
||||
use App\Services\InfoProviderSystem\DTOs\FileDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\ParameterDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\PriceDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO;
|
||||
use Brick\Math\BigDecimal;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
/**
|
||||
* This class converts DTOs to entities which can be persisted in the DB
|
||||
*/
|
||||
final class DTOtoEntityConverter
|
||||
{
|
||||
private const TYPE_DATASHEETS_NAME = 'Datasheet';
|
||||
private const TYPE_IMAGE_NAME = 'Image';
|
||||
|
||||
public function __construct(private readonly EntityManagerInterface $em, private readonly string $base_currency)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given DTO to a PartParameter entity.
|
||||
* @param ParameterDTO $dto
|
||||
* @param PartParameter $entity The entity to apply the DTO on. If null a new entity will be created
|
||||
* @return PartParameter
|
||||
*/
|
||||
public function convertParameter(ParameterDTO $dto, PartParameter $entity = new PartParameter()): PartParameter
|
||||
{
|
||||
$entity->setName($dto->name);
|
||||
$entity->setValueText($dto->value_text ?? '');
|
||||
$entity->setValueTypical($dto->value_typ);
|
||||
$entity->setValueMin($dto->value_min);
|
||||
$entity->setValueMax($dto->value_max);
|
||||
$entity->setUnit($dto->unit ?? '');
|
||||
$entity->setSymbol($dto->symbol ?? '');
|
||||
$entity->setGroup($dto->group ?? '');
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given DTO to a Pricedetail entity.
|
||||
* @param PriceDTO $dto
|
||||
* @param Pricedetail $entity
|
||||
* @return Pricedetail
|
||||
*/
|
||||
public function convertPrice(PriceDTO $dto, Pricedetail $entity = new Pricedetail()): Pricedetail
|
||||
{
|
||||
$entity->setMinDiscountQuantity($dto->minimum_discount_amount);
|
||||
$entity->setPrice($dto->getPriceAsBigDecimal());
|
||||
|
||||
//Currency TODO
|
||||
if ($dto->currency_iso_code !== null) {
|
||||
$entity->setCurrency($this->getCurrency($dto->currency_iso_code));
|
||||
} else {
|
||||
$entity->setCurrency(null);
|
||||
}
|
||||
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given DTO to an orderdetail entity.
|
||||
*/
|
||||
public function convertPurchaseInfo(PurchaseInfoDTO $dto, Orderdetail $entity = new Orderdetail()): Orderdetail
|
||||
{
|
||||
$entity->setSupplierpartnr($dto->order_number);
|
||||
$entity->setSupplierProductUrl($dto->product_url ?? '');
|
||||
|
||||
$entity->setSupplier($this->getOrCreateEntityNonNull(Supplier::class, $dto->distributor_name));
|
||||
foreach ($dto->prices as $price) {
|
||||
$entity->addPricedetail($this->convertPrice($price));
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given DTO to an Attachment entity.
|
||||
* @param FileDTO $dto
|
||||
* @param AttachmentType $type The type which should be used for the attachment
|
||||
* @param PartAttachment $entity
|
||||
* @return PartAttachment
|
||||
*/
|
||||
public function convertFile(FileDTO $dto, AttachmentType $type, PartAttachment $entity = new PartAttachment()): PartAttachment
|
||||
{
|
||||
$entity->setURL($dto->url);
|
||||
|
||||
$entity->setAttachmentType($type);
|
||||
|
||||
//If no name is given, try to extract the name from the URL
|
||||
if (empty($dto->name)) {
|
||||
$entity->setName(basename($dto->url));
|
||||
} else {
|
||||
$entity->setName($dto->name);
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a PartDetailDTO to a Part entity
|
||||
* @param PartDetailDTO $dto
|
||||
* @param Part $entity The part entity to fill
|
||||
* @return Part
|
||||
*/
|
||||
public function convertPart(PartDetailDTO $dto, Part $entity = new Part()): Part
|
||||
{
|
||||
$entity->setName($dto->name);
|
||||
$entity->setDescription($dto->description ?? '');
|
||||
$entity->setComment($dto->notes ?? '');
|
||||
|
||||
$entity->setMass($dto->mass);
|
||||
|
||||
$entity->setManufacturer($this->getOrCreateEntity(Manufacturer::class, $dto->manufacturer));
|
||||
$entity->setFootprint($this->getOrCreateEntity(Footprint::class, $dto->footprint));
|
||||
|
||||
$entity->setManufacturerProductNumber($dto->mpn ?? '');
|
||||
$entity->setManufacturingStatus($dto->manufacturing_status ?? ManufacturingStatus::NOT_SET);
|
||||
|
||||
//Set the provider reference on the part
|
||||
$entity->setProviderReference(InfoProviderReference::fromPartDTO($dto));
|
||||
|
||||
//Add parameters
|
||||
foreach ($dto->parameters ?? [] as $parameter) {
|
||||
$entity->addParameter($this->convertParameter($parameter));
|
||||
}
|
||||
|
||||
//Add preview image
|
||||
$image_type = $this->getImageType();
|
||||
|
||||
if ($dto->preview_image_url) {
|
||||
$preview_image = new PartAttachment();
|
||||
$preview_image->setURL($dto->preview_image_url);
|
||||
$preview_image->setName('Main image');
|
||||
$preview_image->setAttachmentType($image_type);
|
||||
|
||||
$entity->addAttachment($preview_image);
|
||||
$entity->setMasterPictureAttachment($preview_image);
|
||||
}
|
||||
|
||||
//Add other images
|
||||
foreach ($dto->images ?? [] as $image) {
|
||||
//Ensure that the image is not the same as the preview image
|
||||
if ($image->url === $dto->preview_image_url) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$entity->addAttachment($this->convertFile($image, $image_type));
|
||||
}
|
||||
|
||||
|
||||
//Add datasheets
|
||||
$datasheet_type = $this->getDatasheetType();
|
||||
foreach ($dto->datasheets ?? [] as $datasheet) {
|
||||
$entity->addAttachment($this->convertFile($datasheet, $datasheet_type));
|
||||
}
|
||||
|
||||
//Add orderdetails and prices
|
||||
foreach ($dto->vendor_infos ?? [] as $vendor_info) {
|
||||
$entity->addOrderdetail($this->convertPurchaseInfo($vendor_info));
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the existing entity of the given class with the given name or create it if it does not exist.
|
||||
* If the name is null, null is returned.
|
||||
* @template T of AbstractStructuralDBElement
|
||||
* @param string $class
|
||||
* @phpstan-param class-string<T> $class
|
||||
* @param string|null $name
|
||||
* @return AbstractStructuralDBElement|null
|
||||
* @phpstan-return T|null
|
||||
*/
|
||||
private function getOrCreateEntity(string $class, ?string $name): ?AbstractStructuralDBElement
|
||||
{
|
||||
//Fall through to make converting easier
|
||||
if ($name === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->getOrCreateEntityNonNull($class, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the existing entity of the given class with the given name or create it if it does not exist.
|
||||
* @template T of AbstractStructuralDBElement
|
||||
* @param string $class The class of the entity to create
|
||||
* @phpstan-param class-string<T> $class
|
||||
* @param string $name The name of the entity to create
|
||||
* @return AbstractStructuralDBElement
|
||||
* @phpstan-return T
|
||||
*/
|
||||
private function getOrCreateEntityNonNull(string $class, string $name): AbstractStructuralDBElement
|
||||
{
|
||||
return $this->em->getRepository($class)->findOrCreateForInfoProvider($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currency entity for the given ISO code or create it if it does not exist
|
||||
* @param string $iso_code
|
||||
* @return Currency|null
|
||||
*/
|
||||
private function getCurrency(string $iso_code): ?Currency
|
||||
{
|
||||
//Check if the currency is the base currency (then we can just return null)
|
||||
if ($iso_code === $this->base_currency) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->em->getRepository(Currency::class)->findOrCreateByISOCode($iso_code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attachment type used for datasheets or creates it if it does not exist
|
||||
* @return AttachmentType
|
||||
*/
|
||||
private function getDatasheetType(): AttachmentType
|
||||
{
|
||||
/** @var AttachmentType $tmp */
|
||||
$tmp = $this->em->getRepository(AttachmentType::class)->findOrCreateForInfoProvider(self::TYPE_DATASHEETS_NAME);
|
||||
|
||||
//If the entity was newly created, set the file filter
|
||||
if ($tmp->getID() === null) {
|
||||
$tmp->setFiletypeFilter('application/pdf');
|
||||
$tmp->setAlternativeNames(self::TYPE_DATASHEETS_NAME);
|
||||
}
|
||||
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attachment type used for datasheets or creates it if it does not exist
|
||||
* @return AttachmentType
|
||||
*/
|
||||
private function getImageType(): AttachmentType
|
||||
{
|
||||
/** @var AttachmentType $tmp */
|
||||
$tmp = $this->em->getRepository(AttachmentType::class)->findOrCreateForInfoProvider(self::TYPE_IMAGE_NAME);
|
||||
|
||||
//If the entity was newly created, set the file filter
|
||||
if ($tmp->getID() === null) {
|
||||
$tmp->setFiletypeFilter('image/*');
|
||||
$tmp->setAlternativeNames(self::TYPE_DATASHEETS_NAME);
|
||||
}
|
||||
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
}
|
131
src/Services/InfoProviderSystem/PartInfoRetriever.php
Normal file
131
src/Services/InfoProviderSystem/PartInfoRetriever.php
Normal file
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Services\InfoProviderSystem;
|
||||
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\SearchResultDTO;
|
||||
use App\Services\InfoProviderSystem\Providers\InfoProviderInterface;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
use Symfony\Contracts\Cache\ItemInterface;
|
||||
|
||||
final class PartInfoRetriever
|
||||
{
|
||||
|
||||
private const CACHE_DETAIL_EXPIRATION = 60 * 60 * 24 * 4; // 4 days
|
||||
private const CACHE_RESULT_EXPIRATION = 60 * 60 * 24 * 7; // 7 days
|
||||
|
||||
public function __construct(private readonly ProviderRegistry $provider_registry,
|
||||
private readonly DTOtoEntityConverter $dto_to_entity_converter, private readonly CacheInterface $partInfoCache)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for a keyword in the given providers. The results can be cached
|
||||
* @param string[]|InfoProviderInterface[] $providers A list of providers to search in, either as provider keys or as provider instances
|
||||
* @param string $keyword The keyword to search for
|
||||
* @return SearchResultDTO[] The search results
|
||||
*/
|
||||
public function searchByKeyword(string $keyword, array $providers): array
|
||||
{
|
||||
$results = [];
|
||||
|
||||
foreach ($providers as $provider) {
|
||||
if (is_string($provider)) {
|
||||
$provider = $this->provider_registry->getProviderByKey($provider);
|
||||
}
|
||||
|
||||
if (!$provider instanceof InfoProviderInterface) {
|
||||
throw new \InvalidArgumentException("The provider must be either a provider key or a provider instance!");
|
||||
}
|
||||
|
||||
/** @noinspection SlowArrayOperationsInLoopInspection */
|
||||
$results = array_merge($results, $this->searchInProvider($provider, $keyword));
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for a keyword in the given provider. The result is cached for 7 days.
|
||||
* @return SearchResultDTO[]
|
||||
*/
|
||||
protected function searchInProvider(InfoProviderInterface $provider, string $keyword): array
|
||||
{
|
||||
return $this->partInfoCache->get("search_{$provider->getProviderKey()}_{$keyword}", function (ItemInterface $item) use ($provider, $keyword) {
|
||||
//Set the expiration time
|
||||
$item->expiresAfter(self::CACHE_RESULT_EXPIRATION);
|
||||
|
||||
return $provider->searchByKeyword($keyword);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the details for a part from the given provider with the given (provider) part id.
|
||||
* The result is cached for 4 days.
|
||||
* @param string $provider_key
|
||||
* @param string $part_id
|
||||
* @return PartDetailDTO
|
||||
*/
|
||||
public function getDetails(string $provider_key, string $part_id): PartDetailDTO
|
||||
{
|
||||
$provider = $this->provider_registry->getProviderByKey($provider_key);
|
||||
|
||||
return $this->partInfoCache->get("details_{$provider_key}_{$part_id}", function (ItemInterface $item) use ($provider, $part_id) {
|
||||
//Set the expiration time
|
||||
$item->expiresAfter(self::CACHE_DETAIL_EXPIRATION);
|
||||
|
||||
return $provider->getDetails($part_id);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the details for a part, based on the given search result.
|
||||
* @param SearchResultDTO $search_result
|
||||
* @return PartDetailDTO
|
||||
*/
|
||||
public function getDetailsForSearchResult(SearchResultDTO $search_result): PartDetailDTO
|
||||
{
|
||||
return $this->getDetails($search_result->provider_key, $search_result->provider_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given DTO to a part entity
|
||||
* @return Part
|
||||
*/
|
||||
public function dtoToPart(PartDetailDTO $search_result): Part
|
||||
{
|
||||
return $this->createPart($search_result->provider_key, $search_result->provider_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the given details to create a part entity
|
||||
*/
|
||||
public function createPart(string $provider_key, string $part_id): Part
|
||||
{
|
||||
$details = $this->getDetails($provider_key, $part_id);
|
||||
|
||||
return $this->dto_to_entity_converter->convertPart($details);
|
||||
}
|
||||
}
|
107
src/Services/InfoProviderSystem/ProviderRegistry.php
Normal file
107
src/Services/InfoProviderSystem/ProviderRegistry.php
Normal file
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Services\InfoProviderSystem;
|
||||
|
||||
use App\Services\InfoProviderSystem\Providers\InfoProviderInterface;
|
||||
|
||||
/**
|
||||
* This class keeps track of all registered info providers and allows to find them by their key
|
||||
*/
|
||||
final class ProviderRegistry
|
||||
{
|
||||
/**
|
||||
* @var InfoProviderInterface[] The info providers index by their keys
|
||||
* @phpstan-var array<string, InfoProviderInterface>
|
||||
*/
|
||||
private array $providers_by_name = [];
|
||||
|
||||
/**
|
||||
* @var InfoProviderInterface[] The enabled providers indexed by their keys
|
||||
*/
|
||||
private array $providers_active = [];
|
||||
|
||||
/**
|
||||
* @var InfoProviderInterface[] The disabled providers indexed by their keys
|
||||
*/
|
||||
private array $providers_disabled = [];
|
||||
|
||||
/**
|
||||
* @param iterable<InfoProviderInterface> $providers
|
||||
*/
|
||||
public function __construct(iterable $providers)
|
||||
{
|
||||
foreach ($providers as $provider) {
|
||||
$key = $provider->getProviderKey();
|
||||
|
||||
if (isset($this->providers_by_name[$key])) {
|
||||
throw new \LogicException("Provider with key $key already registered");
|
||||
}
|
||||
|
||||
$this->providers_by_name[$key] = $provider;
|
||||
if ($provider->isActive()) {
|
||||
$this->providers_active[$key] = $provider;
|
||||
} else {
|
||||
$this->providers_disabled[$key] = $provider;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all registered providers (enabled and disabled)
|
||||
* @return InfoProviderInterface[]
|
||||
*/
|
||||
public function getProviders(): array
|
||||
{
|
||||
return $this->providers_by_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the provider identified by the given key
|
||||
* @param string $key
|
||||
* @return InfoProviderInterface
|
||||
* @throws \InvalidArgumentException If the provider with the given key does not exist
|
||||
*/
|
||||
public function getProviderByKey(string $key): InfoProviderInterface
|
||||
{
|
||||
return $this->providers_by_name[$key] ?? throw new \InvalidArgumentException("Provider with key $key not found");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all active providers
|
||||
* @return InfoProviderInterface[]
|
||||
*/
|
||||
public function getActiveProviders(): array
|
||||
{
|
||||
return $this->providers_active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all disabled providers
|
||||
* @return InfoProviderInterface[]
|
||||
*/
|
||||
public function getDisabledProviders(): array
|
||||
{
|
||||
return $this->providers_disabled;
|
||||
}
|
||||
}
|
269
src/Services/InfoProviderSystem/Providers/DigikeyProvider.php
Normal file
269
src/Services/InfoProviderSystem/Providers/DigikeyProvider.php
Normal file
|
@ -0,0 +1,269 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Services\InfoProviderSystem\Providers;
|
||||
|
||||
use App\Entity\Parts\ManufacturingStatus;
|
||||
use App\Services\InfoProviderSystem\DTOs\FileDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\ParameterDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\PriceDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\SearchResultDTO;
|
||||
use App\Services\OAuth\OAuthTokenManager;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
class DigikeyProvider implements InfoProviderInterface
|
||||
{
|
||||
|
||||
private const OAUTH_APP_NAME = 'ip_digikey_oauth';
|
||||
|
||||
//Sandbox:'https://sandbox-api.digikey.com'; (you need to change it in knpu/oauth2-client-bundle config too)
|
||||
private const BASE_URI = 'https://api.digikey.com';
|
||||
|
||||
private const VENDOR_NAME = 'DigiKey';
|
||||
|
||||
private readonly HttpClientInterface $digikeyClient;
|
||||
|
||||
|
||||
public function __construct(HttpClientInterface $httpClient, private readonly OAuthTokenManager $authTokenManager,
|
||||
private readonly string $currency, private readonly string $clientId,
|
||||
private readonly string $language, private readonly string $country)
|
||||
{
|
||||
//Create the HTTP client with some default options
|
||||
$this->digikeyClient = $httpClient->withOptions([
|
||||
"base_uri" => self::BASE_URI,
|
||||
"headers" => [
|
||||
"X-DIGIKEY-Client-Id" => $clientId,
|
||||
"X-DIGIKEY-Locale-Site" => $this->country,
|
||||
"X-DIGIKEY-Locale-Language" => $this->language,
|
||||
"X-DIGIKEY-Locale-Currency" => $this->currency,
|
||||
"X-DIGIKEY-Customer-Id" => 0,
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function getProviderInfo(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'DigiKey',
|
||||
'description' => 'This provider uses the DigiKey API to search for parts.',
|
||||
'url' => 'https://www.digikey.com/',
|
||||
'oauth_app_name' => self::OAUTH_APP_NAME,
|
||||
'disabled_help' => 'Set the PROVIDER_DIGIKEY_CLIENT_ID and PROVIDER_DIGIKEY_SECRET env option and connect OAuth to enable.'
|
||||
];
|
||||
}
|
||||
|
||||
public function getCapabilities(): array
|
||||
{
|
||||
return [
|
||||
ProviderCapabilities::BASIC,
|
||||
ProviderCapabilities::FOOTPRINT,
|
||||
ProviderCapabilities::PICTURE,
|
||||
ProviderCapabilities::DATASHEET,
|
||||
ProviderCapabilities::PRICE,
|
||||
];
|
||||
}
|
||||
|
||||
public function getProviderKey(): string
|
||||
{
|
||||
return 'digikey';
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
//The client ID has to be set and a token has to be available (user clicked connect)
|
||||
return !empty($this->clientId) && $this->authTokenManager->hasToken(self::OAUTH_APP_NAME);
|
||||
}
|
||||
|
||||
public function searchByKeyword(string $keyword): array
|
||||
{
|
||||
$request = [
|
||||
'Keywords' => $keyword,
|
||||
'RecordCount' => 50,
|
||||
'RecordStartPosition' => 0,
|
||||
'ExcludeMarketPlaceProducts' => 'true',
|
||||
];
|
||||
|
||||
$response = $this->digikeyClient->request('POST', '/Search/v3/Products/Keyword', [
|
||||
'json' => $request,
|
||||
'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME)
|
||||
]);
|
||||
|
||||
$response_array = $response->toArray();
|
||||
|
||||
|
||||
$result = [];
|
||||
$products = $response_array['Products'];
|
||||
foreach ($products as $product) {
|
||||
$result[] = new SearchResultDTO(
|
||||
provider_key: $this->getProviderKey(),
|
||||
provider_id: $product['DigiKeyPartNumber'],
|
||||
name: $product['ManufacturerPartNumber'],
|
||||
description: $product['DetailedDescription'] ?? $product['ProductDescription'],
|
||||
category: $this->getCategoryString($product),
|
||||
manufacturer: $product['Manufacturer']['Value'] ?? null,
|
||||
mpn: $product['ManufacturerPartNumber'],
|
||||
preview_image_url: $product['PrimaryPhoto'] ?? null,
|
||||
manufacturing_status: $this->productStatusToManufacturingStatus($product['ProductStatus']),
|
||||
provider_url: $product['ProductUrl'],
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getDetails(string $id): PartDetailDTO
|
||||
{
|
||||
$response = $this->digikeyClient->request('GET', '/Search/v3/Products/' . $id, [
|
||||
'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME)
|
||||
]);
|
||||
|
||||
$product = $response->toArray();
|
||||
|
||||
$footprint = null;
|
||||
$parameters = $this->parametersToDTOs($product['Parameters'] ?? [], $footprint);
|
||||
$media = $this->mediaToDTOs($product['MediaLinks']);
|
||||
|
||||
return new PartDetailDTO(
|
||||
provider_key: $this->getProviderKey(),
|
||||
provider_id: $product['DigiKeyPartNumber'],
|
||||
name: $product['ManufacturerPartNumber'],
|
||||
description: $product['DetailedDescription'] ?? $product['ProductDescription'],
|
||||
category: $this->getCategoryString($product),
|
||||
manufacturer: $product['Manufacturer']['Value'] ?? null,
|
||||
mpn: $product['ManufacturerPartNumber'],
|
||||
preview_image_url: $product['PrimaryPhoto'] ?? null,
|
||||
manufacturing_status: $this->productStatusToManufacturingStatus($product['ProductStatus']),
|
||||
provider_url: $product['ProductUrl'],
|
||||
footprint: $footprint,
|
||||
datasheets: $media['datasheets'],
|
||||
images: $media['images'],
|
||||
parameters: $parameters,
|
||||
vendor_infos: $this->pricingToDTOs($product['StandardPricing'] ?? [], $product['DigiKeyPartNumber'], $product['ProductUrl']),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the product status from the Digikey API to the manufacturing status used in Part-DB
|
||||
* @param string|null $dk_status
|
||||
* @return ManufacturingStatus|null
|
||||
*/
|
||||
private function productStatusToManufacturingStatus(?string $dk_status): ?ManufacturingStatus
|
||||
{
|
||||
return match ($dk_status) {
|
||||
null => null,
|
||||
'Active' => ManufacturingStatus::ACTIVE,
|
||||
'Obsolete' => ManufacturingStatus::DISCONTINUED,
|
||||
'Discontinued at Digi-Key' => ManufacturingStatus::EOL,
|
||||
'Last Time Buy' => ManufacturingStatus::EOL,
|
||||
'Not For New Designs' => ManufacturingStatus::NRFND,
|
||||
'Preliminary' => ManufacturingStatus::ANNOUNCED,
|
||||
default => ManufacturingStatus::NOT_SET,
|
||||
};
|
||||
}
|
||||
|
||||
private function getCategoryString(array $product): string
|
||||
{
|
||||
$category = $product['Category']['Value'];
|
||||
$sub_category = $product['Family']['Value'];
|
||||
|
||||
//Replace the ' - ' category separator with ' -> '
|
||||
$sub_category = str_replace(' - ', ' -> ', $sub_category);
|
||||
|
||||
return $category . ' -> ' . $sub_category;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function converts the "Parameters" part of the Digikey API response to an array of ParameterDTOs
|
||||
* @param array $parameters
|
||||
* @param string|null $footprint_name You can pass a variable by reference, where the name of the footprint will be stored
|
||||
* @return ParameterDTO[]
|
||||
*/
|
||||
private function parametersToDTOs(array $parameters, string|null &$footprint_name = null): array
|
||||
{
|
||||
$results = [];
|
||||
|
||||
$footprint_name = null;
|
||||
|
||||
foreach ($parameters as $parameter) {
|
||||
if ($parameter['ParameterId'] === 1291) { //Meaning "Manufacturer given footprint"
|
||||
$footprint_name = $parameter['Value'];
|
||||
}
|
||||
|
||||
$results[] = ParameterDTO::parseValueIncludingUnit($parameter['Parameter'], $parameter['Value']);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the pricing (StandardPricing field) from the Digikey API to an array of PurchaseInfoDTOs
|
||||
* @param array $price_breaks
|
||||
* @param string $order_number
|
||||
* @param string $product_url
|
||||
* @return PurchaseInfoDTO[]
|
||||
*/
|
||||
private function pricingToDTOs(array $price_breaks, string $order_number, string $product_url): array
|
||||
{
|
||||
$prices = [];
|
||||
|
||||
foreach ($price_breaks as $price_break) {
|
||||
$prices[] = new PriceDTO(minimum_discount_amount: $price_break['BreakQuantity'], price: (string) $price_break['UnitPrice'], currency_iso_code: $this->currency);
|
||||
}
|
||||
|
||||
return [
|
||||
new PurchaseInfoDTO(distributor_name: self::VENDOR_NAME, order_number: $order_number, prices: $prices, product_url: $product_url)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $media_links
|
||||
* @return FileDTO[][]
|
||||
* @phpstan-return array<string, FileDTO[]>
|
||||
*/
|
||||
private function mediaToDTOs(array $media_links): array
|
||||
{
|
||||
$datasheets = [];
|
||||
$images = [];
|
||||
|
||||
foreach ($media_links as $media_link) {
|
||||
$file = new FileDTO(url: $media_link['Url'], name: $media_link['Title']);
|
||||
|
||||
switch ($media_link['MediaType']) {
|
||||
case 'Datasheets':
|
||||
$datasheets[] = $file;
|
||||
break;
|
||||
case 'Product Photos':
|
||||
$images[] = $file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'datasheets' => $datasheets,
|
||||
'images' => $images,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
325
src/Services/InfoProviderSystem/Providers/Element14Provider.php
Normal file
325
src/Services/InfoProviderSystem/Providers/Element14Provider.php
Normal file
|
@ -0,0 +1,325 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Services\InfoProviderSystem\Providers;
|
||||
|
||||
use App\Entity\Parts\ManufacturingStatus;
|
||||
use App\Form\InfoProviderSystem\ProviderSelectType;
|
||||
use App\Services\InfoProviderSystem\DTOs\FileDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\ParameterDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\PriceDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\SearchResultDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
class Element14Provider implements InfoProviderInterface
|
||||
{
|
||||
|
||||
private const ENDPOINT_URL = 'https://api.element14.com/catalog/products';
|
||||
private const API_VERSION_NUMBER = '1.2';
|
||||
private const NUMBER_OF_RESULTS = 20;
|
||||
|
||||
public const DISTRIBUTOR_NAME = 'Farnell';
|
||||
|
||||
private const COMPLIANCE_ATTRIBUTES = ['euEccn', 'hazardous', 'MSL', 'productTraceability', 'rohsCompliant',
|
||||
'rohsPhthalatesCompliant', 'SVHC', 'tariffCode', 'usEccn', 'hazardCode'];
|
||||
|
||||
public function __construct(private readonly HttpClientInterface $element14Client, private readonly string $api_key, private readonly string $store_id)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function getProviderInfo(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'Farnell element14',
|
||||
'description' => 'This provider uses the Farnell element14 API to search for parts.',
|
||||
'url' => 'https://www.element14.com/',
|
||||
'disabled_help' => 'Configure the API key in the PROVIDER_ELEMENT14_KEY environment variable to enable.'
|
||||
];
|
||||
}
|
||||
|
||||
public function getProviderKey(): string
|
||||
{
|
||||
return 'element14';
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return !empty($this->api_key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $term
|
||||
* @return PartDetailDTO[]
|
||||
*/
|
||||
private function queryByTerm(string $term): array
|
||||
{
|
||||
$response = $this->element14Client->request('GET', self::ENDPOINT_URL, [
|
||||
'query' => [
|
||||
'term' => $term,
|
||||
'storeInfo.id' => $this->store_id,
|
||||
'resultsSettings.offset' => 0,
|
||||
'resultsSettings.numberOfResults' => self::NUMBER_OF_RESULTS,
|
||||
'resultsSettings.responseGroup' => 'large',
|
||||
'callInfo.apiKey' => $this->api_key,
|
||||
'callInfo.responseDataFormat' => 'json',
|
||||
'callInfo.version' => self::API_VERSION_NUMBER,
|
||||
],
|
||||
]);
|
||||
|
||||
$arr = $response->toArray();
|
||||
if (isset($arr['keywordSearchReturn'])) {
|
||||
$products = $arr['keywordSearchReturn']['products'] ?? [];
|
||||
} elseif (isset($arr['premierFarnellPartNumberReturn'])) {
|
||||
$products = $arr['premierFarnellPartNumberReturn']['products'] ?? [];
|
||||
} else {
|
||||
throw new \RuntimeException('Unknown response format');
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($products as $product) {
|
||||
$result[] = new PartDetailDTO(
|
||||
provider_key: $this->getProviderKey(), provider_id: $product['sku'],
|
||||
name: $product['translatedManufacturerPartNumber'],
|
||||
description: $this->displayNameToDescription($product['displayName'], $product['translatedManufacturerPartNumber']),
|
||||
manufacturer: $product['vendorName'] ?? $product['brandName'] ?? null,
|
||||
mpn: $product['translatedManufacturerPartNumber'],
|
||||
preview_image_url: $this->toImageUrl($product['image'] ?? null),
|
||||
manufacturing_status: $this->releaseStatusCodeToManufacturingStatus($product['releaseStatusCode'] ?? null),
|
||||
provider_url: $this->generateProductURL($product['sku']),
|
||||
datasheets: $this->parseDataSheets($product['datasheets'] ?? null),
|
||||
parameters: $this->attributesToParameters($product['attributes'] ?? null),
|
||||
vendor_infos: $this->pricesToVendorInfo($product['sku'], $product['prices'] ?? [])
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function generateProductURL($sku): string
|
||||
{
|
||||
return 'https://' . $this->store_id . '/' . $sku;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[]|null $datasheets
|
||||
* @return FileDTO[]|null Array of FileDTOs
|
||||
*/
|
||||
private function parseDataSheets(?array $datasheets): ?array
|
||||
{
|
||||
if ($datasheets === null || count($datasheets) === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($datasheets as $datasheet) {
|
||||
$result[] = new FileDTO(url: $datasheet['url'], name: $datasheet['description']);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function toImageUrl(?array $image): ?string
|
||||
{
|
||||
if ($image === null || count($image) === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//See Constructing an Image URL: https://partner.element14.com/docs/Product_Search_API_REST__Description
|
||||
$locale = 'en_GB';
|
||||
if ($image['vrntPath'] === 'nio/') {
|
||||
$locale = 'en_US';
|
||||
}
|
||||
|
||||
return 'https://' . $this->store_id . '/productimages/standard/' . $locale . $image['baseName'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the price array to a VendorInfoDTO array to be used in the PartDetailDTO
|
||||
* @param string $sku
|
||||
* @param array $prices
|
||||
* @return array
|
||||
*/
|
||||
private function pricesToVendorInfo(string $sku, array $prices): array
|
||||
{
|
||||
$price_dtos = [];
|
||||
|
||||
foreach ($prices as $price) {
|
||||
$price_dtos[] = new PriceDTO(
|
||||
minimum_discount_amount: $price['from'],
|
||||
price: (string) $price['cost'],
|
||||
currency_iso_code: $this->getUsedCurrency(),
|
||||
includes_tax: false,
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
new PurchaseInfoDTO(
|
||||
distributor_name: self::DISTRIBUTOR_NAME,
|
||||
order_number: $sku,
|
||||
prices: $price_dtos,
|
||||
product_url: $this->generateProductURL($sku)
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
public function getUsedCurrency(): string
|
||||
{
|
||||
//Decide based on the shop ID
|
||||
return match ($this->store_id) {
|
||||
'bg.farnell.com' => 'EUR',
|
||||
'cz.farnell.com' => 'CZK',
|
||||
'dk.farnell.com' => 'DKK',
|
||||
'at.farnell.com' => 'EUR',
|
||||
'ch.farnell.com' => 'CHF',
|
||||
'de.farnell.com' => 'EUR',
|
||||
'cpc.farnell.com' => 'GBP',
|
||||
'cpcireland.farnell.com' => 'EUR',
|
||||
'export.farnell.com' => 'GBP',
|
||||
'onecall.farnell.com' => 'GBP',
|
||||
'ie.farnell.com' => 'EUR',
|
||||
'il.farnell.com' => 'USD',
|
||||
'uk.farnell.com' => 'GBP',
|
||||
'es.farnell.com' => 'EUR',
|
||||
'ee.farnell.com' => 'EUR',
|
||||
'fi.farnell.com' => 'EUR',
|
||||
'fr.farnell.com' => 'EUR',
|
||||
'hu.farnell.com' => 'HUF',
|
||||
'it.farnell.com' => 'EUR',
|
||||
'lt.farnell.com' => 'EUR',
|
||||
'lv.farnell.com' => 'EUR',
|
||||
'be.farnell.com' => 'EUR',
|
||||
'nl.farnell.com' => 'EUR',
|
||||
'no.farnell.com' => 'NOK',
|
||||
'pl.farnell.com' => 'PLN',
|
||||
'pt.farnell.com' => 'EUR',
|
||||
'ro.farnell.com' => 'EUR',
|
||||
'ru.farnell.com' => 'RUB',
|
||||
'sk.farnell.com' => 'EUR',
|
||||
'si.farnell.com' => 'EUR',
|
||||
'se.farnell.com' => 'SEK',
|
||||
'tr.farnell.com' => 'TRY',
|
||||
'canada.newark.com' => 'CAD',
|
||||
'mexico.newark.com' => 'MXN',
|
||||
'www.newark.com' => 'USD',
|
||||
'cn.element14.com' => 'CNY',
|
||||
'au.element14.com' => 'AUD',
|
||||
'nz.element14.com' => 'NZD',
|
||||
'hk.element14.com' => 'HKD',
|
||||
'sg.element14.com' => 'SGD',
|
||||
'my.element14.com' => 'MYR',
|
||||
'ph.element14.com' => 'PHP',
|
||||
'th.element14.com' => 'THB',
|
||||
'in.element14.com' => 'INR',
|
||||
'tw.element14.com' => 'TWD',
|
||||
'kr.element14.com' => 'KRW',
|
||||
'vn.element14.com' => 'VND',
|
||||
default => throw new \RuntimeException('Unknown store ID: ' . $this->store_id)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|null $attributes
|
||||
* @return ParameterDTO[]
|
||||
*/
|
||||
private function attributesToParameters(?array $attributes): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
$group = null;
|
||||
|
||||
//Check if the attribute is a compliance attribute, they get assigned to the compliance group
|
||||
if (in_array($attribute['attributeLabel'], self::COMPLIANCE_ATTRIBUTES, true)) {
|
||||
$group = 'Compliance';
|
||||
}
|
||||
|
||||
//tariffCode is a special case, we prepend a # to prevent conversion to float
|
||||
if (in_array($attribute['attributeLabel'], ['tariffCode', 'hazardCode'], true)) {
|
||||
$attribute['attributeValue'] = '#' . $attribute['attributeValue'];
|
||||
}
|
||||
|
||||
$result[] = ParameterDTO::parseValueField(name: $attribute['attributeLabel'], value: $attribute['attributeValue'], unit: $attribute['attributeUnit'] ?? null, group: $group);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function displayNameToDescription(string $display_name, string $mpn): string
|
||||
{
|
||||
//Try to find the position of the '-' after the MPN
|
||||
$pos = strpos($display_name, $mpn . ' - ');
|
||||
if ($pos === false) {
|
||||
return $display_name;
|
||||
}
|
||||
|
||||
//Remove the MPN and the '-' from the display name
|
||||
return substr($display_name, $pos + strlen($mpn) + 3);
|
||||
}
|
||||
|
||||
private function releaseStatusCodeToManufacturingStatus(?int $releaseStatusCode): ?ManufacturingStatus
|
||||
{
|
||||
if ($releaseStatusCode === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return match ($releaseStatusCode) {
|
||||
1 => ManufacturingStatus::ANNOUNCED,
|
||||
2,4 => ManufacturingStatus::ACTIVE,
|
||||
6 => ManufacturingStatus::EOL,
|
||||
7 => ManufacturingStatus::DISCONTINUED,
|
||||
default => ManufacturingStatus::NOT_SET
|
||||
};
|
||||
}
|
||||
|
||||
public function searchByKeyword(string $keyword): array
|
||||
{
|
||||
return $this->queryByTerm('any:' . $keyword);
|
||||
}
|
||||
|
||||
public function getDetails(string $id): PartDetailDTO
|
||||
{
|
||||
$tmp = $this->queryByTerm('id:' . $id);
|
||||
if (count($tmp) === 0) {
|
||||
throw new \RuntimeException('No part found with ID ' . $id);
|
||||
}
|
||||
|
||||
if (count($tmp) > 1) {
|
||||
throw new \RuntimeException('Multiple parts found with ID ' . $id);
|
||||
}
|
||||
|
||||
return $tmp[0];
|
||||
}
|
||||
|
||||
public function getCapabilities(): array
|
||||
{
|
||||
return [
|
||||
ProviderCapabilities::BASIC,
|
||||
ProviderCapabilities::PICTURE,
|
||||
ProviderCapabilities::DATASHEET,
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Services\InfoProviderSystem\Providers;
|
||||
|
||||
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\SearchResultDTO;
|
||||
|
||||
interface InfoProviderInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Get information about this provider
|
||||
*
|
||||
* @return array An associative array with the following keys (? means optional):
|
||||
* - name: The (user friendly) name of the provider (e.g. "Digikey"), will be translated
|
||||
* - description?: A short description of the provider (e.g. "Digikey is a ..."), will be translated
|
||||
* - logo?: The logo of the provider (e.g. "digikey.png")
|
||||
* - url?: The url of the provider (e.g. "https://www.digikey.com")
|
||||
* - disabled_help?: A help text which is shown when the provider is disabled, explaining how to enable it
|
||||
* - oauth_app_name?: The name of the OAuth app which is used for authentication (e.g. "ip_digikey_oauth"). If this is set a connect button will be shown
|
||||
*
|
||||
* @phpstan-return array{ name: string, description?: string, logo?: string, url?: string, disabled_help?: string, oauth_app_name?: string }
|
||||
*/
|
||||
public function getProviderInfo(): array;
|
||||
|
||||
/**
|
||||
* Returns a unique key for this provider, which will be saved into the database
|
||||
* and used to identify the provider
|
||||
* @return string A unique key for this provider (e.g. "digikey")
|
||||
*/
|
||||
public function getProviderKey(): string;
|
||||
|
||||
/**
|
||||
* Checks if this provider is enabled or not (meaning that it can be used for searching)
|
||||
* @return bool True if the provider is enabled, false otherwise
|
||||
*/
|
||||
public function isActive(): bool;
|
||||
|
||||
/**
|
||||
* Searches for a keyword and returns a list of search results
|
||||
* @param string $keyword The keyword to search for
|
||||
* @return SearchResultDTO[] A list of search results
|
||||
*/
|
||||
public function searchByKeyword(string $keyword): array;
|
||||
|
||||
/**
|
||||
* Returns detailed information about the part with the given id
|
||||
* @param string $id
|
||||
* @return PartDetailDTO
|
||||
*/
|
||||
public function getDetails(string $id): PartDetailDTO;
|
||||
|
||||
/**
|
||||
* A list of capabilities this provider supports (which kind of data it can provide).
|
||||
* Not every part have to contain all of these data, but the provider should be able to provide them in general.
|
||||
* Currently, this list is purely informational and not used in functional checks.
|
||||
* @return ProviderCapabilities[]
|
||||
*/
|
||||
public function getCapabilities(): array;
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Services\InfoProviderSystem\Providers;
|
||||
|
||||
/**
|
||||
* This enum contains all capabilities (which data it can provide) a provider can have.
|
||||
*/
|
||||
enum ProviderCapabilities
|
||||
{
|
||||
/** Basic information about a part, like the name, description, part number, manufacturer etc */
|
||||
case BASIC;
|
||||
|
||||
/** Information about the footprint of a part */
|
||||
case FOOTPRINT;
|
||||
|
||||
/** Provider can provide a picture for a part */
|
||||
case PICTURE;
|
||||
|
||||
/** Provider can provide datasheets for a part */
|
||||
case DATASHEET;
|
||||
|
||||
/** Provider can provide prices for a part */
|
||||
case PRICE;
|
||||
|
||||
public function getTranslationKey(): string
|
||||
{
|
||||
return 'info_providers.capabilities.' . match($this) {
|
||||
self::BASIC => 'basic',
|
||||
self::FOOTPRINT => 'footprint',
|
||||
self::PICTURE => 'picture',
|
||||
self::DATASHEET => 'datasheet',
|
||||
self::PRICE => 'price',
|
||||
};
|
||||
}
|
||||
|
||||
public function getFAIconClass(): string
|
||||
{
|
||||
return 'fa-solid ' . match($this) {
|
||||
self::BASIC => 'fa-info-circle',
|
||||
self::FOOTPRINT => 'fa-microchip',
|
||||
self::PICTURE => 'fa-image',
|
||||
self::DATASHEET => 'fa-file-alt',
|
||||
self::PRICE => 'fa-money-bill-wave',
|
||||
};
|
||||
}
|
||||
}
|
92
src/Services/InfoProviderSystem/Providers/TMEClient.php
Normal file
92
src/Services/InfoProviderSystem/Providers/TMEClient.php
Normal file
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Services\InfoProviderSystem\Providers;
|
||||
|
||||
use Symfony\Component\HttpClient\DecoratorTrait;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
|
||||
|
||||
class TMEClient
|
||||
{
|
||||
public const BASE_URI = 'https://api.tme.eu';
|
||||
|
||||
public function __construct(private readonly HttpClientInterface $tmeClient, private readonly string $token, private readonly string $secret)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function makeRequest(string $action, array $parameters): ResponseInterface
|
||||
{
|
||||
$parameters['Token'] = $this->token;
|
||||
$parameters['ApiSignature'] = $this->getSignature($action, $parameters, $this->secret);
|
||||
|
||||
return $this->tmeClient->request('POST', $this->getUrlForAction($action), [
|
||||
'body' => $parameters,
|
||||
]);
|
||||
}
|
||||
|
||||
public function isUsable(): bool
|
||||
{
|
||||
if ($this->token === '' || $this->secret === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates the signature for the given action and parameters.
|
||||
* Taken from https://github.com/tme-dev/TME-API/blob/master/PHP/basic/using_curl.php
|
||||
*/
|
||||
public function getSignature(string $action, array $parameters, string $appSecret): string
|
||||
{
|
||||
$parameters = $this->sortSignatureParams($parameters);
|
||||
|
||||
$queryString = http_build_query($parameters, '', '&', PHP_QUERY_RFC3986);
|
||||
$signatureBase = strtoupper('POST') .
|
||||
'&' . rawurlencode($this->getUrlForAction($action)) . '&' . rawurlencode($queryString);
|
||||
|
||||
return base64_encode(hash_hmac('sha1', $signatureBase, $appSecret, true));
|
||||
}
|
||||
|
||||
private function getUrlForAction(string $action): string
|
||||
{
|
||||
return self::BASE_URI . '/' . $action . '.json';
|
||||
}
|
||||
|
||||
private function sortSignatureParams(array $params): array
|
||||
{
|
||||
ksort($params);
|
||||
|
||||
foreach ($params as &$value) {
|
||||
if (is_array($value)) {
|
||||
$value = $this->sortSignatureParams($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
296
src/Services/InfoProviderSystem/Providers/TMEProvider.php
Normal file
296
src/Services/InfoProviderSystem/Providers/TMEProvider.php
Normal file
|
@ -0,0 +1,296 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Services\InfoProviderSystem\Providers;
|
||||
|
||||
use App\Entity\Parts\ManufacturingStatus;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Services\InfoProviderSystem\DTOs\FileDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\ParameterDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\PriceDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\SearchResultDTO;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
class TMEProvider implements InfoProviderInterface
|
||||
{
|
||||
|
||||
private const VENDOR_NAME = 'TME';
|
||||
|
||||
public function __construct(private readonly TMEClient $tmeClient, private readonly string $country,
|
||||
private readonly string $language, private readonly string $currency,
|
||||
/** @var bool If true, the prices are gross prices. If false, the prices are net prices. */
|
||||
private readonly bool $get_gross_prices)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function getProviderInfo(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'TME',
|
||||
'description' => 'This provider uses the API of TME (Transfer Multipart).',
|
||||
'url' => 'https://tme.eu/',
|
||||
'disabled_help' => 'Configure the PROVIDER_TME_KEY and PROVIDER_TME_SECRET environment variables to use this provider.'
|
||||
];
|
||||
}
|
||||
|
||||
public function getProviderKey(): string
|
||||
{
|
||||
return 'tme';
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->tmeClient->isUsable();
|
||||
}
|
||||
|
||||
public function searchByKeyword(string $keyword): array
|
||||
{
|
||||
$response = $this->tmeClient->makeRequest('Products/Search', [
|
||||
'Country' => $this->country,
|
||||
'Language' => $this->language,
|
||||
'SearchPlain' => $keyword,
|
||||
]);
|
||||
|
||||
$data = $response->toArray()['Data'];
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach($data['ProductList'] as $product) {
|
||||
$result[] = new SearchResultDTO(
|
||||
provider_key: $this->getProviderKey(),
|
||||
provider_id: $product['Symbol'],
|
||||
name: !empty($product['OriginalSymbol']) ? $product['OriginalSymbol'] : $product['Symbol'],
|
||||
description: $product['Description'],
|
||||
category: $product['Category'],
|
||||
manufacturer: $product['Producer'],
|
||||
mpn: $product['OriginalSymbol'] ?? null,
|
||||
preview_image_url: $this->normalizeURL($product['Photo']),
|
||||
manufacturing_status: $this->productStatusArrayToManufacturingStatus($product['ProductStatusList']),
|
||||
provider_url: $this->normalizeURL($product['ProductInformationPage']),
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getDetails(string $id): PartDetailDTO
|
||||
{
|
||||
$response = $this->tmeClient->makeRequest('Products/GetProducts', [
|
||||
'Country' => $this->country,
|
||||
'Language' => $this->language,
|
||||
'SymbolList' => [$id],
|
||||
]);
|
||||
|
||||
$product = $response->toArray()['Data']['ProductList'][0];
|
||||
|
||||
//Add a explicit https:// to the url if it is missing
|
||||
$productInfoPage = $this->normalizeURL($product['ProductInformationPage']);
|
||||
|
||||
$files = $this->getFiles($id);
|
||||
|
||||
$footprint = null;
|
||||
|
||||
$parameters = $this->getParameters($id, $footprint);
|
||||
|
||||
return new PartDetailDTO(
|
||||
provider_key: $this->getProviderKey(),
|
||||
provider_id: $product['Symbol'],
|
||||
name: !empty($product['OriginalSymbol']) ? $product['OriginalSymbol'] : $product['Symbol'],
|
||||
description: $product['Description'],
|
||||
category: $product['Category'],
|
||||
manufacturer: $product['Producer'],
|
||||
mpn: $product['OriginalSymbol'] ?? null,
|
||||
preview_image_url: $this->normalizeURL($product['Photo']),
|
||||
manufacturing_status: $this->productStatusArrayToManufacturingStatus($product['ProductStatusList']),
|
||||
provider_url: $productInfoPage,
|
||||
footprint: $footprint,
|
||||
datasheets: $files['datasheets'],
|
||||
images: $files['images'],
|
||||
parameters: $parameters,
|
||||
vendor_infos: [$this->getVendorInfo($id, $productInfoPage)],
|
||||
mass: $product['WeightUnit'] === 'g' ? $product['Weight'] : null,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all files for a given product id
|
||||
* @param string $id
|
||||
* @return array<string, list<FileDTO>> An array with the keys 'datasheet'
|
||||
* @phpstan-return array{datasheets: list<FileDTO>, images: list<FileDTO>}
|
||||
*/
|
||||
public function getFiles(string $id): array
|
||||
{
|
||||
$response = $this->tmeClient->makeRequest('Products/GetProductsFiles', [
|
||||
'Country' => $this->country,
|
||||
'Language' => $this->language,
|
||||
'SymbolList' => [$id],
|
||||
]);
|
||||
|
||||
$data = $response->toArray()['Data'];
|
||||
$files = $data['ProductList'][0]['Files'];
|
||||
|
||||
//Extract datasheets
|
||||
$documentList = $files['DocumentList'];
|
||||
$datasheets = [];
|
||||
foreach($documentList as $document) {
|
||||
$datasheets[] = new FileDTO(
|
||||
url: $this->normalizeURL($document['DocumentUrl']),
|
||||
);
|
||||
}
|
||||
|
||||
//Extract images
|
||||
$imageList = $files['AdditionalPhotoList'];
|
||||
$images = [];
|
||||
foreach($imageList as $image) {
|
||||
$images[] = new FileDTO(
|
||||
url: $this->normalizeURL($image['HighResolutionPhoto']),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return [
|
||||
'datasheets' => $datasheets,
|
||||
'images' => $images,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the vendor/purchase information for a given product id.
|
||||
* @param string $id
|
||||
* @param string|null $productURL
|
||||
* @return PurchaseInfoDTO
|
||||
*/
|
||||
public function getVendorInfo(string $id, ?string $productURL = null): PurchaseInfoDTO
|
||||
{
|
||||
$response = $this->tmeClient->makeRequest('Products/GetPricesAndStocks', [
|
||||
'Country' => $this->country,
|
||||
'Language' => $this->language,
|
||||
'Currency' => $this->currency,
|
||||
'GrossPrices' => $this->get_gross_prices,
|
||||
'SymbolList' => [$id],
|
||||
]);
|
||||
|
||||
$data = $response->toArray()['Data'];
|
||||
$currency = $data['Currency'];
|
||||
$include_tax = $data['PriceType'] === 'GROSS';
|
||||
|
||||
|
||||
$product = $response->toArray()['Data']['ProductList'][0];
|
||||
$vendor_order_number = $product['Symbol'];
|
||||
$priceList = $product['PriceList'];
|
||||
|
||||
$prices = [];
|
||||
foreach ($priceList as $price) {
|
||||
$prices[] = new PriceDTO(
|
||||
minimum_discount_amount: $price['Amount'],
|
||||
price: (string) $price['PriceValue'],
|
||||
currency_iso_code: $currency,
|
||||
includes_tax: $include_tax,
|
||||
);
|
||||
}
|
||||
|
||||
return new PurchaseInfoDTO(
|
||||
distributor_name: self::VENDOR_NAME,
|
||||
order_number: $vendor_order_number,
|
||||
prices: $prices,
|
||||
product_url: $productURL,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the parameters of a product
|
||||
* @param string $id
|
||||
* @param string|null $footprint_name You can pass a variable by reference, where the name of the footprint will be stored
|
||||
* @return ParameterDTO[]
|
||||
*/
|
||||
public function getParameters(string $id, string|null &$footprint_name = null): array
|
||||
{
|
||||
$response = $this->tmeClient->makeRequest('Products/GetParameters', [
|
||||
'Country' => $this->country,
|
||||
'Language' => $this->language,
|
||||
'SymbolList' => [$id],
|
||||
]);
|
||||
|
||||
$data = $response->toArray()['Data']['ProductList'][0];
|
||||
|
||||
$result = [];
|
||||
|
||||
$footprint_name = null;
|
||||
|
||||
foreach($data['ParameterList'] as $parameter) {
|
||||
$result[] = ParameterDTO::parseValueIncludingUnit($parameter['ParameterName'], $parameter['ParameterValue']);
|
||||
|
||||
//Check if the parameter is the case/footprint
|
||||
if ($parameter['ParameterId'] === 35) {
|
||||
$footprint_name = $parameter['ParameterValue'];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the array of product statuses to a single manufacturing status
|
||||
* @param array $statusArray
|
||||
* @return ManufacturingStatus
|
||||
*/
|
||||
private function productStatusArrayToManufacturingStatus(array $statusArray): ManufacturingStatus
|
||||
{
|
||||
if (in_array('AVAILABLE_WHILE_STOCKS_LAST', $statusArray, true)) {
|
||||
return ManufacturingStatus::EOL;
|
||||
}
|
||||
|
||||
if (in_array('INVALID', $statusArray, true)) {
|
||||
return ManufacturingStatus::DISCONTINUED;
|
||||
}
|
||||
|
||||
//By default we assume that the part is active
|
||||
return ManufacturingStatus::ACTIVE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function normalizeURL(string $url): string
|
||||
{
|
||||
//If a URL starts with // we assume that it is a relative URL and we add the protocol
|
||||
if (str_starts_with($url, '//')) {
|
||||
return 'https:' . $url;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
public function getCapabilities(): array
|
||||
{
|
||||
return [
|
||||
ProviderCapabilities::BASIC,
|
||||
ProviderCapabilities::FOOTPRINT,
|
||||
ProviderCapabilities::PICTURE,
|
||||
ProviderCapabilities::DATASHEET,
|
||||
ProviderCapabilities::PRICE,
|
||||
];
|
||||
}
|
||||
}
|
95
src/Services/InfoProviderSystem/Providers/TestProvider.php
Normal file
95
src/Services/InfoProviderSystem/Providers/TestProvider.php
Normal file
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Services\InfoProviderSystem\Providers;
|
||||
|
||||
use App\Services\InfoProviderSystem\DTOs\FileDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\SearchResultDTO;
|
||||
use Symfony\Component\DependencyInjection\Attribute\When;
|
||||
|
||||
/**
|
||||
* This is a provider, which is used during tests
|
||||
*/
|
||||
#[When(env: 'test')]
|
||||
class TestProvider implements InfoProviderInterface
|
||||
{
|
||||
|
||||
public function getProviderInfo(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'Test Provider',
|
||||
'description' => 'This is a test provider',
|
||||
//'url' => 'https://example.com',
|
||||
'disabled_help' => 'This provider is disabled for testing purposes'
|
||||
];
|
||||
}
|
||||
|
||||
public function getProviderKey(): string
|
||||
{
|
||||
return 'test';
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function searchByKeyword(string $keyword): array
|
||||
{
|
||||
return [
|
||||
new SearchResultDTO(provider_key: $this->getProviderKey(), provider_id: 'element1', name: 'Element 1', description: 'fd'),
|
||||
new SearchResultDTO(provider_key: $this->getProviderKey(), provider_id: 'element2', name: 'Element 2', description: 'fd'),
|
||||
new SearchResultDTO(provider_key: $this->getProviderKey(), provider_id: 'element3', name: 'Element 3', description: 'fd'),
|
||||
];
|
||||
}
|
||||
|
||||
public function getCapabilities(): array
|
||||
{
|
||||
return [
|
||||
ProviderCapabilities::BASIC,
|
||||
ProviderCapabilities::FOOTPRINT,
|
||||
];
|
||||
}
|
||||
|
||||
public function getDetails(string $id): PartDetailDTO
|
||||
{
|
||||
return new PartDetailDTO(
|
||||
provider_key: $this->getProviderKey(),
|
||||
provider_id: $id,
|
||||
name: 'Test Element',
|
||||
description: 'fd',
|
||||
manufacturer: 'Test Manufacturer',
|
||||
mpn: '1234',
|
||||
provider_url: 'https://invalid.invalid',
|
||||
footprint: 'Footprint',
|
||||
notes: 'Notes',
|
||||
datasheets: [
|
||||
new FileDTO('https://invalid.invalid/invalid.pdf', 'Datasheet')
|
||||
],
|
||||
images: [
|
||||
new FileDTO('https://invalid.invalid/invalid.png', 'Image')
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
|
@ -46,6 +46,7 @@ use App\Entity\LabelSystem\LabelSupportedElement;
|
|||
use App\Entity\Parts\Category;
|
||||
use App\Entity\Parts\Footprint;
|
||||
use App\Entity\Parts\Manufacturer;
|
||||
use App\Entity\Parts\ManufacturingStatus;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\Parts\PartLot;
|
||||
use App\Entity\Parts\Storelocation;
|
||||
|
@ -79,7 +80,7 @@ final class LabelExampleElementsGenerator
|
|||
$part->setMass(123.4);
|
||||
$part->setManufacturerProductNumber('CUSTOM MPN');
|
||||
$part->setTags('Tag1, Tag2, Tag3');
|
||||
$part->setManufacturingStatus('active');
|
||||
$part->setManufacturingStatus(ManufacturingStatus::ACTIVE);
|
||||
$part->updateTimestamps();
|
||||
|
||||
$part->setFavorite(true);
|
||||
|
|
|
@ -105,11 +105,11 @@ final class PartProvider implements PlaceholderProviderInterface
|
|||
}
|
||||
|
||||
if ('[[M_STATUS]]' === $placeholder) {
|
||||
if ('' === $part->getManufacturingStatus()) {
|
||||
if (null === $part->getManufacturingStatus()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->translator->trans('m_status.'.$part->getManufacturingStatus());
|
||||
return $this->translator->trans($part->getManufacturingStatus()->toTranslationKey());
|
||||
}
|
||||
|
||||
$parsedown = new Parsedown();
|
||||
|
|
142
src/Services/OAuth/OAuthTokenManager.php
Normal file
142
src/Services/OAuth/OAuthTokenManager.php
Normal file
|
@ -0,0 +1,142 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Services\OAuth;
|
||||
|
||||
use App\Entity\OAuthToken;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
|
||||
use League\OAuth2\Client\Token\AccessTokenInterface;
|
||||
|
||||
final class OAuthTokenManager
|
||||
{
|
||||
public function __construct(private readonly ClientRegistry $clientRegistry, private readonly EntityManagerInterface $entityManager)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the given token to the database, so it can be retrieved later
|
||||
* @param string $app_name
|
||||
* @param AccessTokenInterface $token
|
||||
* @return void
|
||||
*/
|
||||
public function saveToken(string $app_name, AccessTokenInterface $token): void
|
||||
{
|
||||
//Check if we already have a token for this app
|
||||
$tokenEntity = $this->entityManager->getRepository(OAuthToken::class)->findOneBy(['name' => $app_name]);
|
||||
|
||||
//If the token was already existing, we just replace it with the new one
|
||||
if ($tokenEntity) {
|
||||
$tokenEntity->replaceWithNewToken($token);
|
||||
|
||||
//@phpstan-ignore-next-line
|
||||
$this->entityManager->flush($tokenEntity);
|
||||
|
||||
//We are done
|
||||
return;
|
||||
}
|
||||
|
||||
//If the token was not existing, we create a new one
|
||||
$tokenEntity = OAuthToken::fromAccessToken($token, $app_name);
|
||||
$this->entityManager->persist($tokenEntity);
|
||||
//@phpstan-ignore-next-line
|
||||
$this->entityManager->flush($tokenEntity);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the token for the given app name
|
||||
* @param string $app_name
|
||||
* @return OAuthToken|null
|
||||
*/
|
||||
public function getToken(string $app_name): ?OAuthToken
|
||||
{
|
||||
return $this->entityManager->getRepository(OAuthToken::class)->findOneBy(['name' => $app_name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a token for the given app name is existing
|
||||
* @param string $app_name
|
||||
* @return bool
|
||||
*/
|
||||
public function hasToken(string $app_name): bool
|
||||
{
|
||||
return $this->getToken($app_name) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function refreshes the token for the given app name. The new token is saved to the database
|
||||
* The app_name must be registered in the knpu_oauth2_client.yaml
|
||||
* @param string $app_name
|
||||
* @return OAuthToken
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function refreshToken(string $app_name): OAuthToken
|
||||
{
|
||||
$token = $this->getToken($app_name);
|
||||
|
||||
if (!$token) {
|
||||
throw new \Exception('No token was saved yet for '.$app_name);
|
||||
}
|
||||
|
||||
$client = $this->clientRegistry->getClient($app_name);
|
||||
$new_token = $client->refreshAccessToken($token->getRefreshToken());
|
||||
|
||||
//Persist the token
|
||||
$token->replaceWithNewToken($new_token);
|
||||
|
||||
//@phpstan-ignore-next-line
|
||||
$this->entityManager->flush($token);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns the token of the given app name
|
||||
* @param string $app_name
|
||||
* @return string|null
|
||||
*/
|
||||
public function getAlwaysValidTokenString(string $app_name): ?string
|
||||
{
|
||||
//Get the token for the application
|
||||
$token = $this->getToken($app_name);
|
||||
|
||||
//If the token is not existing, we return null
|
||||
if (!$token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//If the token is still valid, we return it
|
||||
if (!$token->hasExpired()) {
|
||||
return $token->getToken();
|
||||
}
|
||||
|
||||
//If the token is expired, we refresh it
|
||||
$this->refreshToken($app_name);
|
||||
|
||||
//And return the new token
|
||||
return $token->getToken();
|
||||
}
|
||||
}
|
|
@ -133,6 +133,13 @@ class ToolsTreeBuilder
|
|||
))->setIcon('fa-treeview fa-fw fa-solid fa-file-import');
|
||||
}
|
||||
|
||||
if ($this->security->isGranted('@info_providers.create_parts')) {
|
||||
$nodes[] = (new TreeViewNode(
|
||||
$this->translator->trans('info_providers.search.title'),
|
||||
$this->urlGenerator->generate('info_providers_search')
|
||||
))->setIcon('fa-treeview fa-fw fa-solid fa-cloud-arrow-down');
|
||||
}
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
|
|
|
@ -105,6 +105,9 @@ class PermissionPresetsHelper
|
|||
$this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'suppliers', PermissionData::ALLOW);
|
||||
$this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'projects', PermissionData::ALLOW);
|
||||
|
||||
//Allow to manage Oauth tokens
|
||||
$this->permissionResolver->setPermission($perm_holder, 'system', 'manage_oauth_tokens', PermissionData::ALLOW);
|
||||
|
||||
}
|
||||
|
||||
private function editor(HasPermissionsInterface $permHolder): HasPermissionsInterface
|
||||
|
@ -139,6 +142,9 @@ class PermissionPresetsHelper
|
|||
//Various other permissions
|
||||
$this->permissionResolver->setPermission($permHolder, 'tools', 'lastActivity', PermissionData::ALLOW);
|
||||
|
||||
//Allow to create parts from information providers
|
||||
$this->permissionResolver->setPermission($permHolder, 'info_providers', 'create_parts', PermissionData::ALLOW);
|
||||
|
||||
|
||||
return $permHolder;
|
||||
}
|
||||
|
|
72
src/Twig/InfoProviderExtension.php
Normal file
72
src/Twig/InfoProviderExtension.php
Normal file
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Twig;
|
||||
|
||||
use App\Services\InfoProviderSystem\ProviderRegistry;
|
||||
use App\Services\InfoProviderSystem\Providers\InfoProviderInterface;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFunction;
|
||||
|
||||
class InfoProviderExtension extends AbstractExtension
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProviderRegistry $providerRegistry
|
||||
) {}
|
||||
|
||||
public function getFunctions(): array
|
||||
{
|
||||
return [
|
||||
new TwigFunction('info_provider', $this->getInfoProvider(...)),
|
||||
new TwigFunction('info_provider_label', $this->getInfoProviderName(...))
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the info provider with the given key. Returns null, if the provider does not exist.
|
||||
* @param string $key
|
||||
* @return InfoProviderInterface|null
|
||||
*/
|
||||
private function getInfoProvider(string $key): ?InfoProviderInterface
|
||||
{
|
||||
try {
|
||||
return $this->providerRegistry->getProviderByKey($key);
|
||||
} catch (\InvalidArgumentException $exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the label of the info provider with the given key. Returns null, if the provider does not exist.
|
||||
* @param string $key
|
||||
* @return string|null
|
||||
*/
|
||||
private function getInfoProviderName(string $key): ?string
|
||||
{
|
||||
try {
|
||||
return $this->providerRegistry->getProviderByKey($key)->getProviderInfo()['name'];
|
||||
} catch (\InvalidArgumentException $exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -55,6 +55,7 @@ final class TwigCoreExtension extends AbstractExtension
|
|||
new TwigTest('instanceof', static fn($var, $instance) => $var instanceof $instance),
|
||||
/* Checks if a given variable is an object. E.g. `x is object` */
|
||||
new TwigTest('object', static fn($var): bool => is_object($var)),
|
||||
new TwigTest('enum', fn($var) => $var instanceof \UnitEnum),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -97,7 +97,11 @@ final class UserExtension extends AbstractExtension
|
|||
{
|
||||
$token = $this->security->getToken();
|
||||
if ($token instanceof SwitchUserToken) {
|
||||
return $token->getOriginalToken()->getUser();
|
||||
$tmp = $token->getOriginalToken()->getUser();
|
||||
|
||||
if ($tmp instanceof User) {
|
||||
return $tmp;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
12
symfony.lock
12
symfony.lock
|
@ -180,6 +180,18 @@
|
|||
"jbtronics/dompdf-font-loader-bundle": {
|
||||
"version": "dev-main"
|
||||
},
|
||||
"knpuniversity/oauth2-client-bundle": {
|
||||
"version": "2.15",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes-contrib",
|
||||
"branch": "main",
|
||||
"version": "1.20",
|
||||
"ref": "1ff300d8c030f55c99219cc55050b97a695af3f6"
|
||||
},
|
||||
"files": [
|
||||
"./config/packages/knpu_oauth2_client.yaml"
|
||||
]
|
||||
},
|
||||
"laminas/laminas-code": {
|
||||
"version": "3.4.1"
|
||||
},
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
{% block additional_panes %}
|
||||
<div class="tab-pane" id="home_advanced">
|
||||
{{ form_row(form.alternative_names) }}
|
||||
{{ form_row(form.comment) }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -26,6 +26,8 @@
|
|||
</div>
|
||||
|
||||
<div class="tab-pane" id="home_advanced">
|
||||
{{ form_row(form.alternative_names) }}
|
||||
<hr>
|
||||
{{ form_row(form.partname_regex) }}
|
||||
{{ form_row(form.partname_hint) }}
|
||||
<hr>
|
||||
|
|
|
@ -16,3 +16,7 @@
|
|||
{% block new_title %}
|
||||
{% trans %}footprint.new{% endtrans %}
|
||||
{% endblock %}
|
||||
|
||||
{% block additional_controls %}
|
||||
{{ form_row(form.alternative_names) }}
|
||||
{% endblock %}
|
|
@ -16,5 +16,6 @@
|
|||
{{ form_row(form.unit) }}
|
||||
{{ form_row(form.is_integer) }}
|
||||
{{ form_row(form.use_si_prefix)}}
|
||||
{{ form_row(form.alternative_names) }}
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
|
||||
{% block additional_panes %}
|
||||
<div class="tab-pane" id="home_options">
|
||||
{{ form_row(form.alternative_names) }}
|
||||
|
||||
{{ form_row(form.storage_type) }}
|
||||
{{ form_row(form.is_full) }}
|
||||
{{ form_row(form.limit_to_existing_parts) }}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
{% block additional_panes %}
|
||||
<div class="tab-pane" id="home_advanced">
|
||||
{{ form_row(form.alternative_names) }}
|
||||
{{ form_row(form.default_currency) }}
|
||||
{{ form_row(form.shipping_costs) }}
|
||||
{{ form_row(form.comment) }}
|
||||
|
|
|
@ -37,6 +37,10 @@
|
|||
{% endmacro %}
|
||||
|
||||
{% macro m_status_to_badge(status, class="badge") %}
|
||||
{% if status is enum %}
|
||||
{% set status = status.value %}
|
||||
{% endif %}
|
||||
|
||||
{% if status is not empty %}
|
||||
{% set color = " bg-secondary" %}
|
||||
|
||||
|
|
55
templates/info_providers/providers.macro.html.twig
Normal file
55
templates/info_providers/providers.macro.html.twig
Normal file
|
@ -0,0 +1,55 @@
|
|||
{% macro provider_info_table(providers) %}
|
||||
<table class="table table-striped table-hover">
|
||||
<tbody>
|
||||
{% for provider in providers %}
|
||||
{# @var provider \App\Services\InfoProviderSystem\Providers\InfoProviderInterface #}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<h5>
|
||||
{% if provider.providerInfo.url is defined and provider.providerInfo.url is not empty %}
|
||||
<a href="{{ provider.providerInfo.url }}" target="_blank">{{ provider.providerInfo.name }}</a>
|
||||
{% else %}
|
||||
{{ provider.providerInfo.name | trans }}
|
||||
{% endif %}
|
||||
|
||||
</h5>
|
||||
<div>
|
||||
{% if provider.providerInfo.description is defined and provider.providerInfo.description is not null %}
|
||||
{{ provider.providerInfo.description | trans }}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-6">
|
||||
{% for capability in provider.capabilities %}
|
||||
{# @var capability \App\Services\InfoProviderSystem\Providers\ProviderCapabilities #}
|
||||
<span class="badge text-bg-secondary">
|
||||
<i class="{{ capability.fAIconClass }} fa-fw"></i>
|
||||
{{ capability.translationKey|trans }}
|
||||
</span>
|
||||
{% endfor %}
|
||||
{% if provider.providerInfo.oauth_app_name is defined and provider.providerInfo.oauth_app_name is not empty %}
|
||||
<br>
|
||||
<a href="{{ path('oauth_client_connect', {'name': provider.providerInfo.oauth_app_name}) }}" target="_blank" class="btn btn-outline-secondary btn-sm mt-2">{% trans %}oauth_client.connect.btn{% endtrans %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if provider.active == false %}
|
||||
<div class="row">
|
||||
<div class="col text-danger">
|
||||
<i class="fa-solid fa-circle-exclamation"></i> <b>{% trans %}info_providers.providers_list.disabled{% endtrans %}</b>
|
||||
{% if provider.providerInfo.disabled_help is defined and provider.providerInfo.disabled_help is not empty %}
|
||||
<br>
|
||||
<span class="text-muted">{{ provider.providerInfo.disabled_help|trans }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endmacro %}
|
|
@ -0,0 +1,33 @@
|
|||
{% extends "main_card.html.twig" %}
|
||||
|
||||
{% import "info_providers/providers.macro.html.twig" as providers_macro %}
|
||||
|
||||
{% block title %}{% trans %}info_providers.providers_list.title{% endtrans %}{% endblock %}
|
||||
|
||||
{% block card_title %}
|
||||
<i class="fas fa-cloud"></i> {% trans %}info_providers.providers_list.title{% endtrans %}
|
||||
{% endblock %}
|
||||
|
||||
{% block card_content %}
|
||||
<nav>
|
||||
<div class="nav nav-tabs" id="nav-tab" role="tablist">
|
||||
<button class="nav-link active" id="server_infos-partdb-tab" data-bs-toggle="tab" data-bs-target="#providers_list_active" type="button" role="tab" aria-controls="providers_list_active" aria-selected="true">
|
||||
{% trans %}info_providers.providers_list.active{% endtrans %}
|
||||
<span class="badge bg-secondary">{{ active_providers|length }}</span>
|
||||
</button>
|
||||
<button class="nav-link" id="server_infos-php-tab" data-bs-toggle="tab" data-bs-target="#providers_list_disabled" type="button" role="tab" aria-controls="providers_list_disabled" aria-selected="false">
|
||||
{% trans %}info_providers.providers_list.disabled{% endtrans %}
|
||||
<span class="badge bg-secondary">{{ disabled_providers|length }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="tab-content" id="tabContent">
|
||||
<div class="tab-pane fade show active" id="providers_list_active" role="tabpanel" aria-labelledby="server_infos-partdb-tab">
|
||||
{{ providers_macro.provider_info_table(active_providers) }}
|
||||
</div>
|
||||
<div class="tab-pane fade" id="providers_list_disabled" role="tabpanel" aria-labelledby="server_infos-php-tab">
|
||||
{{ providers_macro.provider_info_table(disabled_providers) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
101
templates/info_providers/search/part_search.html.twig
Normal file
101
templates/info_providers/search/part_search.html.twig
Normal file
|
@ -0,0 +1,101 @@
|
|||
{% extends "main_card.html.twig" %}
|
||||
|
||||
{% import "info_providers/providers.macro.html.twig" as providers_macro %}
|
||||
{% import "helper.twig" as helper %}
|
||||
|
||||
{% block title %}{% trans %}info_providers.search.title{% endtrans %}{% endblock %}
|
||||
|
||||
{% block card_title %}
|
||||
<i class="fas fa-cloud-arrow-down"></i> {% trans %}info_providers.search.title{% endtrans %}
|
||||
{% endblock %}
|
||||
|
||||
{% block card_content %}
|
||||
|
||||
|
||||
|
||||
{{ form_start(form) }}
|
||||
|
||||
{{ form_row(form.keyword) }}
|
||||
{{ form_row(form.providers) }}
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-9 offset-sm-3">
|
||||
<a href="{{ path('info_providers_list') }}">{% trans %}info_providers.search.info_providers_list{% endtrans %}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ form_row(form.submit) }}
|
||||
|
||||
{{ form_end(form) }}
|
||||
|
||||
{% if results is not null %}
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>{% trans %}name.label{% endtrans %} / {% trans %}part.table.mpn{% endtrans %}</th>
|
||||
<th>{% trans %}description.label{% endtrans %} / {% trans %}category.label{% endtrans %}</th>
|
||||
<th>{% trans %}manufacturer.label{% endtrans %} / {% trans %}footprint.label{% endtrans %}</th>
|
||||
<th>{% trans %}part.table.manufacturingStatus{% endtrans %}</th>
|
||||
<th>{% trans %}info_providers.table.provider.label{% endtrans %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for result in results %}
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ result.preview_image_url }}" data-thumbnail="{{ result.preview_image_url }}"
|
||||
class="hoverpic" style="max-width: 45px;" {{ stimulus_controller('elements/hoverpic') }}>
|
||||
</td>
|
||||
<td>
|
||||
{% if result.provider_url is not null %}
|
||||
<a href="{{ result.provider_url }}" target="_blank" rel="noopener">{{ result.name }}</a>
|
||||
{% else %}
|
||||
{{ result.name }}
|
||||
{% endif %}
|
||||
|
||||
{% if result.mpn is not null %}
|
||||
<br>
|
||||
<small class="text-muted" title="{% trans %}part.table.mpn{% endtrans %}">{{ result.mpn }}</small>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ result.description }}
|
||||
{% if result.category is not null %}
|
||||
<br>
|
||||
<small class="text-muted">{{ result.category }}</small>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ result.manufacturer ?? '' }}
|
||||
{% if result.footprint is not null %}
|
||||
<br>
|
||||
<small class="text-muted">{{ result.footprint }}</small>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ helper.m_status_to_badge(result.manufacturing_status) }}</td>
|
||||
<td>
|
||||
{% if result.provider_url %}
|
||||
<a href="{{ result.provider_url }}" target="_blank" rel="noopener">
|
||||
{{ info_provider_label(result.provider_key)|default(result.provider_key) }}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ info_provider_label(result.provider_key)|default(result.provider_key) }}
|
||||
{% endif %}
|
||||
<br>
|
||||
<small class="text-muted">{{ result.provider_id }}</small>
|
||||
<td>
|
||||
<a class="btn btn-primary" href="{{ path('info_providers_create_part', {'providerKey': result.provider_key, 'providerId': result.provider_id}) }}"
|
||||
target="_blank" title="{% trans %}part.create.btn{% endtrans %}">
|
||||
<i class="fa-solid fa-plus-square"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
|
@ -62,5 +62,28 @@
|
|||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>{% trans %}part.info_provider_reference{% endtrans %}</td>
|
||||
<td>
|
||||
{% if part.providerReference.providerCreated %}
|
||||
{% if part.providerReference.providerUrl %}
|
||||
<a href="{{ part.providerReference.providerUrl }}" rel="noopener">
|
||||
{% endif %}
|
||||
<span title="{{ part.providerReference.providerKey }}">{{ info_provider_label(part.providerReference.providerKey)|default(part.providerReference.providerKey) }}</span>: {{ part.providerReference.providerId }}
|
||||
<span> ({{ part.providerReference.lastUpdated | format_datetime() }})</span>
|
||||
{% if part.providerReference.providerUrl %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{# Show last updated date #}
|
||||
|
||||
{% else %}
|
||||
{{ helper.boolean_badge(part.providerReference.providerCreated) }}
|
||||
{% endif %}
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
|
@ -68,3 +68,15 @@
|
|||
</h6>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Info provider badge #}
|
||||
{% if part.providerReference.providerCreated %}
|
||||
<div class="mt-1">
|
||||
<h6>
|
||||
<a href="{{ part.providerReference.providerUrl ?? '#' }}" class="badge bg-info badge-info" title="{% trans %}part.info_provider_reference.badge{% endtrans %}">
|
||||
<i class="fa-solid fa-cloud"></i>
|
||||
{{ info_provider_label(part.providerReference.providerKey)|default(part.providerReference.providerKey) }}
|
||||
</a>
|
||||
</h6>
|
||||
</div>
|
||||
{% endif %}
|
|
@ -140,5 +140,10 @@ class ApplicationAvailabilityFunctionalTest extends WebTestCase
|
|||
yield ['/project/1/add_parts?parts=1,2'];
|
||||
yield ['/project/1/build?n=1'];
|
||||
yield ['/project/1/import_bom'];
|
||||
|
||||
//Test info provider system
|
||||
yield ['/tools/info_providers/providers']; //List all providers
|
||||
yield ['/tools/info_providers/search']; //Search page
|
||||
yield ['/part/from_info_provider/test/element1/create']; //Create part from info provider
|
||||
}
|
||||
}
|
||||
|
|
68
tests/Entity/Parts/InfoProviderReferenceTest.php
Normal file
68
tests/Entity/Parts/InfoProviderReferenceTest.php
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Entity\Parts;
|
||||
|
||||
use App\Entity\Parts\InfoProviderReference;
|
||||
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class InfoProviderReferenceTest extends TestCase
|
||||
{
|
||||
public function testNoProvider(): void
|
||||
{
|
||||
$provider = InfoProviderReference::noProvider();
|
||||
|
||||
//The no provider instance should return false for the providerCreated method
|
||||
$this->assertFalse($provider->isProviderCreated());
|
||||
//And null for all other methods
|
||||
$this->assertNull($provider->getProviderKey());
|
||||
$this->assertNull($provider->getProviderId());
|
||||
$this->assertNull($provider->getProviderUrl());
|
||||
$this->assertNull($provider->getLastUpdated());
|
||||
}
|
||||
|
||||
public function testProviderReference(): void
|
||||
{
|
||||
$provider = InfoProviderReference::providerReference('test', 'id', 'url');
|
||||
|
||||
//The provider reference instance should return true for the providerCreated method
|
||||
$this->assertTrue($provider->isProviderCreated());
|
||||
//And the correct values for all other methods
|
||||
$this->assertEquals('test', $provider->getProviderKey());
|
||||
$this->assertEquals('id', $provider->getProviderId());
|
||||
$this->assertEquals('url', $provider->getProviderUrl());
|
||||
$this->assertNotNull($provider->getLastUpdated());
|
||||
}
|
||||
|
||||
public function testFromPartDTO(): void
|
||||
{
|
||||
$dto = new PartDetailDTO(provider_key: 'test', provider_id: 'id', name: 'name', description: 'description', provider_url: 'url');
|
||||
$reference = InfoProviderReference::fromPartDTO($dto);
|
||||
|
||||
//The provider reference instance should return true for the providerCreated method
|
||||
$this->assertTrue($reference->isProviderCreated());
|
||||
//And the correct values for all other methods
|
||||
$this->assertEquals('test', $reference->getProviderKey());
|
||||
$this->assertEquals('id', $reference->getProviderId());
|
||||
$this->assertEquals('url', $reference->getProviderUrl());
|
||||
$this->assertNotNull($reference->getLastUpdated());
|
||||
}
|
||||
}
|
|
@ -215,7 +215,7 @@ EOT;
|
|||
$this->assertSame($category, $results[1]->getCategory());
|
||||
|
||||
$input = <<<EOT
|
||||
[{"name":"Test 1","description":"Test 1 description","notes":"Test 1 notes","manufacturer":"Test 1 manufacturer", "tags": "test,test2"},{"name":"Test 2","description":"Test 2 description","notes":"Test 2 notes","manufacturer":"Test 2 manufacturer", "manufacturing_status": "invalid"}]
|
||||
[{"name":"Test 1","description":"Test 1 description","notes":"Test 1 notes","manufacturer":"Test 1 manufacturer", "tags": "test,test2"},{"name":"","description":"Test 2 description","notes":"Test 2 notes","manufacturer":"Test 2 manufacturer", "manufacturing_status": "active"}]
|
||||
EOT;
|
||||
|
||||
$errors = [];
|
||||
|
@ -234,10 +234,10 @@ EOT;
|
|||
//Check the format of the error
|
||||
$error = reset($errors);
|
||||
$this->assertInstanceOf(Part::class, $error['entity']);
|
||||
$this->assertSame('Test 2', $error['entity']->getName());
|
||||
$this->assertSame('', $error['entity']->getName());
|
||||
$this->assertContainsOnlyInstancesOf(ConstraintViolation::class, $error['violations']);
|
||||
//Element name must be element name
|
||||
$this->assertArrayHasKey('Test 2', $errors);
|
||||
$this->assertArrayHasKey('', $errors);
|
||||
|
||||
//Check the valid element
|
||||
$this->assertSame('Test 1', $results[0]->getName());
|
||||
|
|
164
tests/Services/InfoProviderSystem/DTOs/ParameterDTOTest.php
Normal file
164
tests/Services/InfoProviderSystem/DTOs/ParameterDTOTest.php
Normal file
|
@ -0,0 +1,164 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Services\InfoProviderSystem\DTOs;
|
||||
|
||||
use App\Services\InfoProviderSystem\DTOs\ParameterDTO;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ParameterDTOTest extends TestCase
|
||||
{
|
||||
|
||||
public function parseValueFieldDataProvider(): \Generator
|
||||
{
|
||||
//Text value
|
||||
yield [
|
||||
new ParameterDTO('test', value_text: 'test', unit: 'm', symbol: 'm', group: 'test'),
|
||||
'test',
|
||||
'test',
|
||||
'm',
|
||||
'm',
|
||||
'test'
|
||||
];
|
||||
|
||||
//Numerical value
|
||||
yield [
|
||||
new ParameterDTO('test', value_typ: 1.0, unit: 'm', symbol: 'm', group: 'test'),
|
||||
'test',
|
||||
1.0,
|
||||
'm',
|
||||
'm',
|
||||
'test'
|
||||
];
|
||||
|
||||
//Numerical value with unit should be parsed as text value
|
||||
yield [
|
||||
new ParameterDTO('test', value_text: '1.0 m', unit: 'm', symbol: 'm', group: 'test'),
|
||||
'test',
|
||||
'1.0 m',
|
||||
'm',
|
||||
'm',
|
||||
'test'
|
||||
];
|
||||
|
||||
//Test ranges
|
||||
yield [
|
||||
new ParameterDTO('test', value_min: 1.0, value_max: 2.0, unit: 'kg', symbol: 'm', group: 'test'),
|
||||
'test',
|
||||
'1.0...2.0',
|
||||
'kg',
|
||||
'm',
|
||||
'test'
|
||||
];
|
||||
}
|
||||
|
||||
public function parseValueIncludingUnitDataProvider(): \Generator
|
||||
{
|
||||
//Text value
|
||||
yield [
|
||||
new ParameterDTO('test', value_text: 'test', unit: null, symbol: 'm', group: 'test'),
|
||||
'test',
|
||||
'test',
|
||||
'm',
|
||||
'test'
|
||||
];
|
||||
|
||||
//Numerical value
|
||||
yield [
|
||||
new ParameterDTO('test', value_typ: 1.0, unit: null, symbol: 'm', group: 'test'),
|
||||
'test',
|
||||
1.0,
|
||||
'm',
|
||||
'test'
|
||||
];
|
||||
|
||||
//Numerical value with unit should extract unit correctly
|
||||
yield [
|
||||
new ParameterDTO('test', value_typ: 1.0, unit: 'kg', symbol: 'm', group: 'test'),
|
||||
'test',
|
||||
'1.0 kg',
|
||||
'm',
|
||||
'test'
|
||||
];
|
||||
|
||||
//Should work without space between value and unit
|
||||
yield [
|
||||
new ParameterDTO('test', value_typ: 1.0, unit: 'kg', symbol: 'm', group: 'test'),
|
||||
'test',
|
||||
'1.0kg',
|
||||
'm',
|
||||
'test'
|
||||
];
|
||||
|
||||
//Allow ° as unit symbol
|
||||
yield [
|
||||
new ParameterDTO('test', value_typ: 1.0, unit: '°C', symbol: 'm', group: 'test'),
|
||||
'test',
|
||||
'1.0°C',
|
||||
'm',
|
||||
'test'
|
||||
];
|
||||
|
||||
//Allow _ in units
|
||||
yield [
|
||||
new ParameterDTO('test', value_typ: 1.0, unit: 'C_m', symbol: 'm', group: 'test'),
|
||||
'test',
|
||||
'1.0C_m',
|
||||
'm',
|
||||
'test'
|
||||
];
|
||||
|
||||
//Allow a single space in units
|
||||
yield [
|
||||
new ParameterDTO('test', value_typ: 1.0, unit: 'C m', symbol: 'm', group: 'test'),
|
||||
'test',
|
||||
'1.0C m',
|
||||
'm',
|
||||
'test'
|
||||
];
|
||||
|
||||
//Test ranges
|
||||
yield [
|
||||
new ParameterDTO('test', value_min: 1.0, value_max: 2.0, unit: 'kg', symbol: 'm', group: 'test'),
|
||||
'test',
|
||||
'1.0...2.0 kg',
|
||||
'm',
|
||||
'test'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider parseValueFieldDataProvider
|
||||
* @return void
|
||||
*/
|
||||
public function testParseValueField(ParameterDTO $expected, string $name, string|float $value, ?string $unit = null, ?string $symbol = null, ?string $group = null)
|
||||
{
|
||||
$this->assertEquals($expected, ParameterDTO::parseValueField($name, $value, $unit, $symbol, $group));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider parseValueIncludingUnitDataProvider
|
||||
* @return void
|
||||
*/
|
||||
public function testParseValueIncludingUnit(ParameterDTO $expected, string $name, string|float $value, ?string $symbol = null, ?string $group = null)
|
||||
{
|
||||
$this->assertEquals($expected, ParameterDTO::parseValueIncludingUnit($name, $value, $symbol, $group));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Services\InfoProviderSystem\DTOs;
|
||||
|
||||
use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class PurchaseInfoDTOTest extends TestCase
|
||||
{
|
||||
public function testThrowOnInvalidType(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('The prices array must only contain PriceDTO instances');
|
||||
new PurchaseInfoDTO('test', 'test', [new \stdClass()]);
|
||||
}
|
||||
}
|
181
tests/Services/InfoProviderSystem/DTOtoEntityConverterTest.php
Normal file
181
tests/Services/InfoProviderSystem/DTOtoEntityConverterTest.php
Normal file
|
@ -0,0 +1,181 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Services\InfoProviderSystem;
|
||||
|
||||
use App\Entity\Attachments\AttachmentType;
|
||||
use App\Entity\Parts\ManufacturingStatus;
|
||||
use App\Services\InfoProviderSystem\DTOs\FileDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\ParameterDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\PriceDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO;
|
||||
use App\Services\InfoProviderSystem\DTOtoEntityConverter;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
|
||||
class DTOtoEntityConverterTest extends WebTestCase
|
||||
{
|
||||
|
||||
private ?DTOtoEntityConverter $service = null;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
self::bootKernel();
|
||||
$this->service = self::getContainer()->get(DTOtoEntityConverter::class);
|
||||
}
|
||||
|
||||
public function testConvertParameter(): void
|
||||
{
|
||||
$dto = new ParameterDTO(
|
||||
name: 'TestParameter',
|
||||
value_text: 'Text',
|
||||
value_typ: 10.0, value_min: 0.0, value_max: 100.0,
|
||||
unit: 'kg', symbol: 'TP', group: 'TestGroup'
|
||||
);
|
||||
|
||||
$entity = $this->service->convertParameter($dto);
|
||||
|
||||
$this->assertEquals($dto->name, $entity->getName());
|
||||
$this->assertEquals($dto->value_text, $entity->getValueText());
|
||||
$this->assertEquals($dto->value_typ, $entity->getValueTypical());
|
||||
$this->assertEquals($dto->value_min, $entity->getValueMin());
|
||||
$this->assertEquals($dto->value_max, $entity->getValueMax());
|
||||
$this->assertEquals($dto->unit, $entity->getUnit());
|
||||
$this->assertEquals($dto->symbol, $entity->getSymbol());
|
||||
$this->assertEquals($dto->group, $entity->getGroup());
|
||||
}
|
||||
|
||||
public function testConvertPriceOtherCurrency(): void
|
||||
{
|
||||
$dto = new PriceDTO(
|
||||
minimum_discount_amount: 5,
|
||||
price: "10.0",
|
||||
currency_iso_code: 'CNY',
|
||||
includes_tax: true,
|
||||
);
|
||||
|
||||
$entity = $this->service->convertPrice($dto);
|
||||
$this->assertEquals($dto->minimum_discount_amount, $entity->getMinDiscountQuantity());
|
||||
$this->assertEquals((float) $dto->price, (float) (string) $entity->getPrice());
|
||||
|
||||
//For non-base currencies, a new currency entity is created
|
||||
$currency = $entity->getCurrency();
|
||||
$this->assertEquals($dto->currency_iso_code, $currency->getIsoCode());
|
||||
}
|
||||
|
||||
public function testConvertPriceBaseCurrency(): void
|
||||
{
|
||||
$dto = new PriceDTO(
|
||||
minimum_discount_amount: 5,
|
||||
price: "10.0",
|
||||
currency_iso_code: 'EUR',
|
||||
includes_tax: true,
|
||||
);
|
||||
|
||||
$entity = $this->service->convertPrice($dto);
|
||||
|
||||
//For base currencies, the currency field is null
|
||||
$this->assertNull($entity->getCurrency());
|
||||
}
|
||||
|
||||
public function testConvertPurchaseInfo(): void
|
||||
{
|
||||
$prices = [
|
||||
new PriceDTO(1, "10.0", 'EUR'),
|
||||
new PriceDTO(5, "9.0", 'EUR'),
|
||||
];
|
||||
|
||||
$dto = new PurchaseInfoDTO(
|
||||
distributor_name: 'TestDistributor',
|
||||
order_number: 'TestOrderNumber',
|
||||
prices: $prices,
|
||||
product_url: 'https://example.com',
|
||||
);
|
||||
|
||||
$entity = $this->service->convertPurchaseInfo($dto);
|
||||
|
||||
$this->assertEquals($dto->distributor_name, $entity->getSupplier()->getName());
|
||||
$this->assertEquals($dto->order_number, $entity->getSupplierPartNr());
|
||||
$this->assertEquals($dto->product_url, $entity->getSupplierProductUrl());
|
||||
}
|
||||
|
||||
public function testConvertFileWithName(): void
|
||||
{
|
||||
$dto = new FileDTO(url: 'https://invalid.com/file.pdf', name: 'TestFile');
|
||||
$type = new AttachmentType();
|
||||
|
||||
|
||||
$entity = $this->service->convertFile($dto, $type);
|
||||
|
||||
$this->assertEquals($dto->name, $entity->getName());
|
||||
$this->assertEquals($dto->url, $entity->getUrl());
|
||||
$this->assertEquals($type, $entity->getAttachmentType());
|
||||
}
|
||||
|
||||
public function testConvertFileWithoutName(): void
|
||||
{
|
||||
$dto = new FileDTO(url: 'https://invalid.invalid/file.pdf');
|
||||
$type = new AttachmentType();
|
||||
|
||||
|
||||
$entity = $this->service->convertFile($dto, $type);
|
||||
|
||||
//If no name is given, the name is derived from the url
|
||||
$this->assertEquals('file.pdf', $entity->getName());
|
||||
$this->assertEquals($dto->url, $entity->getUrl());
|
||||
$this->assertEquals($type, $entity->getAttachmentType());
|
||||
}
|
||||
|
||||
public function testConvertPart()
|
||||
{
|
||||
$parameters = [new ParameterDTO('Test', 'Test')];
|
||||
$datasheets = [new FileDTO('https://invalid.invalid/file.pdf')];
|
||||
$images = [new FileDTO('https://invalid.invalid/image.png')];
|
||||
$shopping_infos = [new PurchaseInfoDTO('TestDistributor', 'TestOrderNumber', [new PriceDTO(1, "10.0", 'EUR')])];
|
||||
|
||||
$dto = new PartDetailDTO(
|
||||
provider_key: 'test_provider', provider_id: 'test_id', provider_url: 'https://invalid.invalid/test_id',
|
||||
name: 'TestPart', description: 'TestDescription', category: 'TestCategory',
|
||||
manufacturer: 'TestManufacturer', mpn: 'TestMPN', manufacturing_status: ManufacturingStatus::EOL,
|
||||
preview_image_url: 'https://invalid.invalid/image.png',
|
||||
footprint: 'DIP8', notes: 'TestNotes', mass: 10.4,
|
||||
parameters: $parameters, datasheets: $datasheets, vendor_infos: $shopping_infos, images: $images
|
||||
);
|
||||
|
||||
$entity = $this->service->convertPart($dto);
|
||||
|
||||
$this->assertSame($dto->name, $entity->getName());
|
||||
$this->assertSame($dto->description, $entity->getDescription());
|
||||
$this->assertSame($dto->notes, $entity->getComment());
|
||||
|
||||
$this->assertSame($dto->manufacturer, $entity->getManufacturer()->getName());
|
||||
$this->assertSame($dto->mpn, $entity->getManufacturerProductNumber());
|
||||
$this->assertSame($dto->manufacturing_status, $entity->getManufacturingStatus());
|
||||
|
||||
$this->assertEquals($dto->mass, $entity->getMass());
|
||||
$this->assertEquals($dto->footprint, $entity->getFootprint());
|
||||
|
||||
//We just check that the lenghts of parameters, datasheets, images and shopping infos are the same
|
||||
//The actual content is tested in the corresponding tests
|
||||
$this->assertCount(count($parameters), $entity->getParameters());
|
||||
$this->assertCount(count($shopping_infos), $entity->getOrderdetails());
|
||||
}
|
||||
}
|
108
tests/Services/InfoProviderSystem/ProviderRegistryTest.php
Normal file
108
tests/Services/InfoProviderSystem/ProviderRegistryTest.php
Normal file
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Services\InfoProviderSystem;
|
||||
|
||||
use App\Services\InfoProviderSystem\ProviderRegistry;
|
||||
use App\Services\InfoProviderSystem\Providers\InfoProviderInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ProviderRegistryTest extends TestCase
|
||||
{
|
||||
|
||||
/** @var InfoProviderInterface[] */
|
||||
private array $providers = [];
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
//Create some mock providers
|
||||
$this->providers = [
|
||||
$this->getMockProvider('test1'),
|
||||
$this->getMockProvider('test2'),
|
||||
$this->getMockProvider('test3', false),
|
||||
];
|
||||
}
|
||||
|
||||
public function getMockProvider(string $key, bool $active = true): InfoProviderInterface
|
||||
{
|
||||
$mock = $this->createMock(InfoProviderInterface::class);
|
||||
$mock->method('getProviderKey')->willReturn($key);
|
||||
$mock->method('isActive')->willReturn($active);
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
public function testGetProviders(): void
|
||||
{
|
||||
$registry = new ProviderRegistry($this->providers);
|
||||
|
||||
$this->assertEquals(
|
||||
[
|
||||
'test1' => $this->providers[0],
|
||||
'test2' => $this->providers[1],
|
||||
'test3' => $this->providers[2],
|
||||
],
|
||||
$registry->getProviders());
|
||||
}
|
||||
|
||||
public function testGetDisabledProviders(): void
|
||||
{
|
||||
$registry = new ProviderRegistry($this->providers);
|
||||
|
||||
$this->assertEquals(
|
||||
[
|
||||
'test3' => $this->providers[2],
|
||||
],
|
||||
$registry->getDisabledProviders());
|
||||
}
|
||||
|
||||
public function testGetActiveProviders(): void
|
||||
{
|
||||
$registry = new ProviderRegistry($this->providers);
|
||||
|
||||
$this->assertEquals(
|
||||
[
|
||||
'test1' => $this->providers[0],
|
||||
'test2' => $this->providers[1],
|
||||
],
|
||||
$registry->getActiveProviders());
|
||||
}
|
||||
|
||||
public function testGetProviderByKey(): void
|
||||
{
|
||||
$registry = new ProviderRegistry($this->providers);
|
||||
|
||||
$this->assertEquals(
|
||||
$this->providers[0],
|
||||
$registry->getProviderByKey('test1')
|
||||
);
|
||||
}
|
||||
|
||||
public function testThrowOnDuplicateKeyOfProviders(): void
|
||||
{
|
||||
$this->expectException(\LogicException::class);
|
||||
|
||||
$registry = new ProviderRegistry([
|
||||
$this->getMockProvider('test1'),
|
||||
$this->getMockProvider('test2'),
|
||||
$this->getMockProvider('test1'),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -41,6 +41,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Tests\Services\LabelSystem\PlaceholderProviders;
|
||||
|
||||
use App\Entity\Parts\ManufacturingStatus;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use App\Entity\Parts\Category;
|
||||
use App\Entity\Parts\Footprint;
|
||||
|
@ -80,7 +81,7 @@ class PartProviderTest extends WebTestCase
|
|||
$this->target->setMass(1234.2);
|
||||
$this->target->setTags('SMD, Tag1, Tag2');
|
||||
$this->target->setManufacturerProductNumber('MPN123');
|
||||
$this->target->setManufacturingStatus('active');
|
||||
$this->target->setManufacturingStatus(ManufacturingStatus::ACTIVE);
|
||||
|
||||
$this->target->setDescription('<b>Bold</b> *Italic*');
|
||||
$this->target->setComment('<b>Bold</b> *Italic*');
|
||||
|
|
|
@ -11435,5 +11435,149 @@ Please note, that you can not impersonate a disabled user. If you try you will g
|
|||
<target>User impersonated</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="08SKwoy" name="info_providers.providers_list.title">
|
||||
<segment>
|
||||
<source>info_providers.providers_list.title</source>
|
||||
<target>Information providers</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="BnbVOSD" name="info_providers.providers_list.active">
|
||||
<segment>
|
||||
<source>info_providers.providers_list.active</source>
|
||||
<target>Active</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="adJnJGT" name="info_providers.providers_list.disabled">
|
||||
<segment>
|
||||
<source>info_providers.providers_list.disabled</source>
|
||||
<target>Disabled</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="1M7yaPw" name="info_providers.capabilities.basic">
|
||||
<segment>
|
||||
<source>info_providers.capabilities.basic</source>
|
||||
<target>Basic</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="PSu1VtZ" name="info_providers.capabilities.footprint">
|
||||
<segment>
|
||||
<source>info_providers.capabilities.footprint</source>
|
||||
<target>Footprint</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="NGbCaWh" name="info_providers.capabilities.picture">
|
||||
<segment>
|
||||
<source>info_providers.capabilities.picture</source>
|
||||
<target>Picture</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="a5JK5hi" name="info_providers.capabilities.datasheet">
|
||||
<segment>
|
||||
<source>info_providers.capabilities.datasheet</source>
|
||||
<target>Datasheets</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="60AvDOv" name="info_providers.capabilities.price">
|
||||
<segment>
|
||||
<source>info_providers.capabilities.price</source>
|
||||
<target>Prices</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="nNKOyNM" name="part.info_provider_reference.badge">
|
||||
<segment>
|
||||
<source>part.info_provider_reference.badge</source>
|
||||
<target>The information provider used to create this part.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="WTB_AKQ" name="part.info_provider_reference">
|
||||
<segment>
|
||||
<source>part.info_provider_reference</source>
|
||||
<target>Created by Information provider</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="Qhaj.j4" name="oauth_client.connect.btn">
|
||||
<segment>
|
||||
<source>oauth_client.connect.btn</source>
|
||||
<target>Connect OAuth</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="5_Z_m9s" name="info_providers.table.provider.label">
|
||||
<segment>
|
||||
<source>info_providers.table.provider.label</source>
|
||||
<target>Provider</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="Cj5csML" name="info_providers.search.keyword">
|
||||
<segment>
|
||||
<source>info_providers.search.keyword</source>
|
||||
<target>Keyword</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="rvTOWHb" name="info_providers.search.submit">
|
||||
<segment>
|
||||
<source>info_providers.search.submit</source>
|
||||
<target>Search</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="Mwq891Y" name="info_providers.search.providers.help">
|
||||
<segment>
|
||||
<source>info_providers.search.providers.help</source>
|
||||
<target>Select the providers in which should be searched.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="nvk25qA" name="info_providers.search.providers">
|
||||
<segment>
|
||||
<source>info_providers.search.providers</source>
|
||||
<target>Providers</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="5kSES8M" name="info_providers.search.info_providers_list">
|
||||
<segment>
|
||||
<source>info_providers.search.info_providers_list</source>
|
||||
<target>Show all available info providers</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="Y2gg4WI" name="info_providers.search.title">
|
||||
<segment>
|
||||
<source>info_providers.search.title</source>
|
||||
<target>Create parts from info provider</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="8w3P1gW" name="oauth_client.flash.connection_successful">
|
||||
<segment>
|
||||
<source>oauth_client.flash.connection_successful</source>
|
||||
<target>Connected to OAuth application successfully!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="9ULD.eH" name="perm.part.info_providers">
|
||||
<segment>
|
||||
<source>perm.part.info_providers</source>
|
||||
<target>Info providers</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="En713bs" name="perm.part.info_providers.create_parts">
|
||||
<segment>
|
||||
<source>perm.part.info_providers.create_parts</source>
|
||||
<target>Create parts from info provider</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="9BvJ32n" name="entity.edit.alternative_names.label">
|
||||
<segment>
|
||||
<source>entity.edit.alternative_names.label</source>
|
||||
<target>Alternative names</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="GgN2HwP" name="entity.edit.alternative_names.help">
|
||||
<segment>
|
||||
<source>entity.edit.alternative_names.help</source>
|
||||
<target>The alternative names given here, are used to find this element based on the results of the information providers.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="TRmIjDN" name="info_providers.form.help_prefix">
|
||||
<segment>
|
||||
<source>info_providers.form.help_prefix</source>
|
||||
<target>Provider</target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue