mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-01-21 01:29:33 +00:00
Merge branch 'Part-DB:master' into master
This commit is contained in:
commit
d623224652
215 changed files with 73571 additions and 5046 deletions
|
|
@ -25,16 +25,16 @@
|
|||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
|
||||
# Pass the configuration from the docker env to the PHP environment (here you should list all .env options)
|
||||
PassEnv APP_ENV APP_DEBUG APP_SECRET
|
||||
PassEnv APP_ENV APP_DEBUG APP_SECRET REDIRECT_TO_HTTPS
|
||||
PassEnv TRUSTED_PROXIES TRUSTED_HOSTS LOCK_DSN
|
||||
PassEnv DATABASE_URL ENFORCE_CHANGE_COMMENTS_FOR
|
||||
PassEnv DEFAULT_LANG DEFAULT_TIMEZONE BASE_CURRENCY INSTANCE_NAME ALLOW_ATTACHMENT_DOWNLOADS USE_GRAVATAR MAX_ATTACHMENT_FILE_SIZE DEFAULT_URI CHECK_FOR_UPDATES
|
||||
PassEnv DATABASE_URL ENFORCE_CHANGE_COMMENTS_FOR DATABASE_MYSQL_USE_SSL_CA DATABASE_MYSQL_SSL_VERIFY_CERT
|
||||
PassEnv DEFAULT_LANG DEFAULT_TIMEZONE BASE_CURRENCY INSTANCE_NAME ALLOW_ATTACHMENT_DOWNLOADS USE_GRAVATAR MAX_ATTACHMENT_FILE_SIZE DEFAULT_URI CHECK_FOR_UPDATES ATTACHMENT_DOWNLOAD_BY_DEFAULT
|
||||
PassEnv MAILER_DSN ALLOW_EMAIL_PW_RESET EMAIL_SENDER_EMAIL EMAIL_SENDER_NAME
|
||||
PassEnv HISTORY_SAVE_CHANGED_FIELDS HISTORY_SAVE_CHANGED_DATA HISTORY_SAVE_REMOVED_DATA HISTORY_SAVE_NEW_DATA
|
||||
PassEnv ERROR_PAGE_ADMIN_EMAIL ERROR_PAGE_SHOW_HELP
|
||||
PassEnv DEMO_MODE NO_URL_REWRITE_AVAILABLE FIXER_API_KEY BANNER
|
||||
# In old version the SAML sp private key env, was wrongly named SAMLP_SP_PRIVATE_KEY, keep it for backward compatibility
|
||||
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 SAML_SP_PRIVATE_KEY SAMLP_SP_PRIVATE_KEY
|
||||
PassEnv SAML_ENABLED SAML_BEHIND_PROXY 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 SAML_SP_PRIVATE_KEY SAMLP_SP_PRIVATE_KEY
|
||||
PassEnv TABLE_DEFAULT_PAGE_SIZE TABLE_PARTS_DEFAULT_COLUMNS
|
||||
|
||||
PassEnv PROVIDER_DIGIKEY_CLIENT_ID PROVIDER_DIGIKEY_SECRET PROVIDER_DIGIKEY_CURRENCY PROVIDER_DIGIKEY_LANGUAGE PROVIDER_DIGIKEY_COUNTRY
|
||||
|
|
@ -42,6 +42,7 @@
|
|||
PassEnv PROVIDER_TME_KEY PROVIDER_TME_SECRET PROVIDER_TME_CURRENCY PROVIDER_TME_LANGUAGE PROVIDER_TME_COUNTRY PROVIDER_TME_GET_GROSS_PRICES
|
||||
PassEnv PROVIDER_OCTOPART_CLIENT_ID PROVIDER_OCTOPART_SECRET PROVIDER_OCTOPART_CURRENCY PROVIDER_OCTOPART_COUNTRY PROVIDER_OCTOPART_SEARCH_LIMIT PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS
|
||||
PassEnv PROVIDER_MOUSER_KEY PROVIDER_MOUSER_SEARCH_OPTION PROVIDER_MOUSER_SEARCH_LIMIT PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE
|
||||
PassEnv EDA_KICAD_CATEGORY_DEPTH
|
||||
|
||||
# For most configuration files from conf-available/, which are
|
||||
# enabled or disabled at a global level, it is possible to
|
||||
|
|
|
|||
29
.env
29
.env
|
|
@ -14,6 +14,15 @@ DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db"
|
|||
# Uncomment this line (and comment the line above to use a MySQL database
|
||||
#DATABASE_URL=mysql://root:@127.0.0.1:3306/part-db?serverVersion=5.7
|
||||
|
||||
# Set this value to 1, if you want to use SSL to connect to the MySQL server. It will be tried to use the CA certificate
|
||||
# otherwise a CA bundle shipped with PHP will be used.
|
||||
# Leave it at 0, if you do not want to use SSL or if your server does not support it
|
||||
DATABASE_MYSQL_USE_SSL_CA=0
|
||||
|
||||
# Set this value to 0, if you don't want to verify the CA certificate of the MySQL server
|
||||
# Only do this, if you know what you are doing!
|
||||
DATABASE_MYSQL_SSL_VERIFY_CERT=1
|
||||
|
||||
###################################################################################
|
||||
# General settings
|
||||
###################################################################################
|
||||
|
|
@ -29,13 +38,15 @@ INSTANCE_NAME="Part-DB"
|
|||
# Allow users to download attachments to the server by providing an URL
|
||||
# This could be a potential security issue, as the user can retrieve any file the server has access to (via internet)
|
||||
ALLOW_ATTACHMENT_DOWNLOADS=0
|
||||
# Set this to 1, if the "download external files" checkbox should be checked by default for new attachments
|
||||
ATTACHMENT_DOWNLOAD_BY_DEFAULT=0
|
||||
# Use gravatars for user avatars, when user has no own avatar defined
|
||||
USE_GRAVATAR=0
|
||||
# The maximum allowed size for attachment files in bytes (you can use M for megabytes and G for gigabytes)
|
||||
# Please note that the php.ini setting upload_max_filesize also limits the maximum size of uploaded files
|
||||
MAX_ATTACHMENT_FILE_SIZE="100M"
|
||||
|
||||
# The public reachable URL of this Part-DB installation. This is used for generating links to the website in emails and so on
|
||||
# The public reachable URL of this Part-DB installation. This is used for generating links in SAML and email templates
|
||||
# This must end with a slash!
|
||||
DEFAULT_URI="https://partdb.changeme.invalid/"
|
||||
|
||||
|
|
@ -157,12 +168,25 @@ PROVIDER_MOUSER_SEARCH_LIMIT=50
|
|||
# Used when searching for keywords in the language specified when you signed up for Search API.
|
||||
PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE='true'
|
||||
|
||||
##################################################################################
|
||||
# EDA integration related settings
|
||||
##################################################################################
|
||||
|
||||
# This value determines the depth of the category tree, that is visible inside KiCad
|
||||
# 0 means that only the top level categories are visible. Set to a value > 0 to show more levels.
|
||||
# Set to -1, to show all parts of Part-DB inside a single category in KiCad
|
||||
EDA_KICAD_CATEGORY_DEPTH=0
|
||||
|
||||
###################################################################################
|
||||
# SAML Single sign on-settings
|
||||
###################################################################################
|
||||
# Set this to 1 to enable SAML single sign on
|
||||
# Be also sure to set the correct values for DEFAULT_URI
|
||||
SAML_ENABLED=0
|
||||
|
||||
# Set to 1, if your Part-DB installation is behind a reverse proxy and you want to use SAML
|
||||
SAML_BEHIND_PROXY=0
|
||||
|
||||
# A JSON encoded array of role mappings in the form { "saml_role": PARTDB_GROUP_ID, "*": PARTDB_GROUP_ID }
|
||||
# The first match is used, so the order is important! Put the group mapping with the most privileges first.
|
||||
# Please not to only use single quotes to enclose the JSON string
|
||||
|
|
@ -202,6 +226,9 @@ DEMO_MODE=0
|
|||
# In that case all URL contains the index.php front controller in URL
|
||||
NO_URL_REWRITE_AVAILABLE=0
|
||||
|
||||
# Set to 1, if Part-DB should redirect all HTTP requests to HTTPS. You dont need to configure this, if your webserver already does this.
|
||||
REDIRECT_TO_HTTPS=0
|
||||
|
||||
# If you want to use fixer.io for currency conversion, you have to set this to your API key
|
||||
FIXER_API_KEY=CHANGEME
|
||||
|
||||
|
|
|
|||
|
|
@ -5,5 +5,9 @@ SYMFONY_DEPRECATIONS_HELPER=999999
|
|||
PANTHER_APP_ENV=panther
|
||||
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
|
||||
|
||||
DATABASE_URL="sqlite:///%kernel.project_dir%/var/app_test.db"
|
||||
# Doctrine automatically adds an _test suffix to database name in test env
|
||||
DATABASE_URL=mysql://root:@127.0.0.1:3306/part-db
|
||||
#DATABASE_URL=mysql://root:@127.0.0.1:3306/part-db
|
||||
|
||||
# Disable update checks, as tests would fail, when github is not reachable
|
||||
CHECK_FOR_UPDATES=0
|
||||
10
.github/workflows/assets_artifact_build.yml
vendored
10
.github/workflows/assets_artifact_build.yml
vendored
|
|
@ -27,14 +27,14 @@ jobs:
|
|||
php-version: '8.2'
|
||||
coverage: none
|
||||
ini-values: xdebug.max_nesting_level=1000
|
||||
extensions: mbstring, intl, gd, xsl, gmp, bcmath
|
||||
extensions: mbstring, intl, gd, xsl, gmp, bcmath, :php-psr
|
||||
|
||||
- name: Get Composer Cache Directory
|
||||
id: composer-cache
|
||||
run: |
|
||||
echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
|
|
@ -48,7 +48,7 @@ jobs:
|
|||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
|
|
@ -77,13 +77,13 @@ jobs:
|
|||
run: zip -r /tmp/partdb_assets.zip public/build/ vendor/
|
||||
|
||||
- name: Upload assets artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Only dependencies and built assets
|
||||
path: /tmp/partdb_assets.zip
|
||||
|
||||
- name: Upload full artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Full Part-DB including dependencies and built assets
|
||||
path: /tmp/partdb_with_assets.zip
|
||||
|
|
|
|||
4
.github/workflows/static_analysis.yml
vendored
4
.github/workflows/static_analysis.yml
vendored
|
|
@ -24,14 +24,14 @@ jobs:
|
|||
php-version: '8.2'
|
||||
coverage: none
|
||||
ini-values: xdebug.max_nesting_level=1000
|
||||
extensions: mbstring, intl, gd, xsl, gmp, bcmath
|
||||
extensions: mbstring, intl, gd, xsl, gmp, bcmath, :php-psr
|
||||
|
||||
- name: Get Composer Cache Directory
|
||||
id: composer-cache
|
||||
run: |
|
||||
echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
|
|
|
|||
15
.github/workflows/tests.yml
vendored
15
.github/workflows/tests.yml
vendored
|
|
@ -13,12 +13,11 @@ on:
|
|||
jobs:
|
||||
phpunit:
|
||||
name: PHPUnit and coverage Test (PHP ${{ matrix.php-versions }}, ${{ matrix.db-type }})
|
||||
# Ubuntu 20.04 ships MySQL 8.0 which causes problems with login, so we just use ubuntu 18.04 for now...
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: [ '8.1', '8.2' ]
|
||||
php-versions: [ '8.1', '8.2', '8.3' ]
|
||||
db-type: [ 'mysql', 'sqlite' ]
|
||||
|
||||
env:
|
||||
|
|
@ -27,6 +26,7 @@ jobs:
|
|||
SYMFONY_DEPRECATIONS_HELPER: disabled
|
||||
PHP_VERSION: ${{ matrix.php-versions }}
|
||||
DB_TYPE: ${{ matrix.db-type }}
|
||||
CHECK_FOR_UPDATES: false # Disable update checks for tests
|
||||
|
||||
steps:
|
||||
- name: Set Database env for MySQL
|
||||
|
|
@ -46,7 +46,7 @@ jobs:
|
|||
php-version: ${{ matrix.php-versions }}
|
||||
coverage: pcov
|
||||
ini-values: xdebug.max_nesting_level=1000
|
||||
extensions: mbstring, intl, gd, xsl, gmp, bcmath
|
||||
extensions: mbstring, intl, gd, xsl, gmp, bcmath, :php-psr
|
||||
|
||||
- name: Start MySQL
|
||||
run: sudo systemctl start mysql.service
|
||||
|
|
@ -64,7 +64,7 @@ jobs:
|
|||
id: composer-cache
|
||||
run: |
|
||||
echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
|
|
@ -75,7 +75,7 @@ jobs:
|
|||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
|
|
@ -108,9 +108,10 @@ jobs:
|
|||
|
||||
- name: Do migrations
|
||||
run: php bin/console --env test doctrine:migrations:migrate -n
|
||||
|
||||
|
||||
# Use our own custom fixtures loading command to circumvent some problems with reset the autoincrement values
|
||||
- name: Load fixtures
|
||||
run: php bin/console --env test doctrine:fixtures:load -n
|
||||
run: php bin/console --env test partdb:fixtures:load -n
|
||||
|
||||
- name: Run PHPunit and generate coverage
|
||||
run: ./bin/phpunit --coverage-clover=coverage.xml
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ FROM debian:bullseye-slim
|
|||
# libpng-dev libjpeg-dev libfreetype6-dev gnupg zip libzip-dev libjpeg62-turbo-dev libonig-dev libxslt-dev libwebp-dev vim \
|
||||
# && apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN apt-get update && apt-get -y install apt-transport-https lsb-release ca-certificates curl zip \
|
||||
RUN apt-get update && apt-get -y install apt-transport-https lsb-release ca-certificates curl zip mariadb-client \
|
||||
&& curl -sSLo /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg \
|
||||
&& sh -c 'echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list' \
|
||||
&& apt-get update && apt-get upgrade -y \
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ for the first time.
|
|||
* Use cloud providers (like Octopart, Digikey, farnell or TME) to automatically get part information, datasheets and
|
||||
prices for parts
|
||||
* API to access Part-DB from other applications/scripts
|
||||
* [Integration with KiCad](https://docs.part-db.de/usage/eda_integration.html): Use Part-DB as central datasource for your
|
||||
KiCad and see available parts from Part-DB directly inside KiCad.
|
||||
|
||||
With these features Part-DB is useful to hobbyists, who want to keep track of their private electronic parts inventory,
|
||||
or makerspaces, where many users have should have (controlled) access to the shared inventory.
|
||||
|
|
|
|||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
1.9.0-dev
|
||||
1.10.6
|
||||
|
|
|
|||
|
|
@ -20,18 +20,26 @@
|
|||
'use strict';
|
||||
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import { marked } from "marked";
|
||||
import { Marked } from "marked";
|
||||
import { mangle } from "marked-mangle";
|
||||
import { gfmHeadingId } from "marked-gfm-heading-id";
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
import "../../css/app/markdown.css";
|
||||
|
||||
export default class extends Controller {
|
||||
export default class MarkdownController extends Controller {
|
||||
|
||||
static _marked = new Marked([
|
||||
{
|
||||
gfm: true,
|
||||
},
|
||||
gfmHeadingId(),
|
||||
mangle(),
|
||||
])
|
||||
;
|
||||
|
||||
connect()
|
||||
{
|
||||
this.configureMarked();
|
||||
this.render();
|
||||
|
||||
//Dispatch an event that we are now finished
|
||||
|
|
@ -45,7 +53,7 @@ export default class extends Controller {
|
|||
let raw = this.element.dataset['markdown'];
|
||||
|
||||
//Apply purified parsed markdown
|
||||
this.element.innerHTML = DOMPurify.sanitize(marked(this.unescapeHTML(raw)));
|
||||
this.element.innerHTML = DOMPurify.sanitize(MarkdownController._marked.parse(this.unescapeHTML(raw)));
|
||||
|
||||
for(let a of this.element.querySelectorAll('a')) {
|
||||
//Mark all links as external
|
||||
|
|
@ -81,8 +89,17 @@ export default class extends Controller {
|
|||
/**
|
||||
* Configure the marked parser
|
||||
*/
|
||||
configureMarked()
|
||||
/*static newMarked()
|
||||
{
|
||||
const marked = new Marked([
|
||||
{
|
||||
gfm: true,
|
||||
},
|
||||
gfmHeadingId(),
|
||||
mangle(),
|
||||
])
|
||||
;
|
||||
|
||||
marked.use(mangle());
|
||||
marked.use(gfmHeadingId({
|
||||
}));
|
||||
|
|
@ -90,5 +107,5 @@ export default class extends Controller {
|
|||
marked.setOptions({
|
||||
gfm: true,
|
||||
});
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
|
@ -70,7 +70,9 @@ export default class extends Controller {
|
|||
editor_div.classList.add(...new_classes.split(","));
|
||||
}
|
||||
|
||||
console.log(editor);
|
||||
//This return is important! Otherwise we get mysterious errors in the console
|
||||
//See: https://github.com/ckeditor/ckeditor5/issues/5897#issuecomment-628471302
|
||||
return editor;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ export default class extends Controller
|
|||
const message = this.element.dataset.deleteMessage;
|
||||
const title = this.element.dataset.deleteTitle;
|
||||
|
||||
const form = this.element;
|
||||
//Use event target, to find the form, where the submit button was clicked
|
||||
const form = event.target;
|
||||
const submitter = event.submitter;
|
||||
const that = this;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
export default class extends Controller
|
||||
{
|
||||
static values = {
|
||||
id: String
|
||||
}
|
||||
|
||||
connect() {
|
||||
this.loadState()
|
||||
this.element.addEventListener('change', () => {
|
||||
this.saveState()
|
||||
});
|
||||
}
|
||||
|
||||
loadState() {
|
||||
let storageKey = this.getStorageKey();
|
||||
let value = localStorage.getItem(storageKey);
|
||||
if (value === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value === 'true') {
|
||||
this.element.checked = true
|
||||
}
|
||||
if (value === 'false') {
|
||||
this.element.checked = false
|
||||
}
|
||||
}
|
||||
|
||||
saveState() {
|
||||
let storageKey = this.getStorageKey();
|
||||
|
||||
if (this.element.checked) {
|
||||
localStorage.setItem(storageKey, 'true');
|
||||
} else {
|
||||
localStorage.setItem(storageKey, 'false');
|
||||
}
|
||||
}
|
||||
|
||||
getStorageKey() {
|
||||
if (this.hasIdValue) {
|
||||
return 'persistent_checkbox_' + this.idValue
|
||||
}
|
||||
|
||||
return 'persistent_checkbox_' + this.element.id;
|
||||
}
|
||||
}
|
||||
|
|
@ -27,7 +27,7 @@ export default class extends Controller {
|
|||
}
|
||||
|
||||
let tmp = '<div class="row m-0">' +
|
||||
"<div class='col-2 p-0 d-flex align-items-center'>" +
|
||||
"<div class='col-2 p-0 d-flex align-items-center' style='max-width: 80px;'>" +
|
||||
(data.image ? "<img class='typeahead-image' src='" + data.image + "'/>" : "") +
|
||||
"</div>" +
|
||||
"<div class='col-10'>" +
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
import "tom-select/dist/css/tom-select.bootstrap5.css";
|
||||
import '../../css/components/tom-select_extensions.css';
|
||||
import TomSelect from "tom-select";
|
||||
|
||||
/**
|
||||
* This is the frontend controller for StaticFileAutocompleteType form element.
|
||||
* Basically it loads a text file from the given url (via data-url) and uses it as a source for the autocomplete.
|
||||
* The file is just a list of strings, one per line, which will be used as the autocomplete options.
|
||||
* Lines starting with # will be ignored.
|
||||
*/
|
||||
export default class extends Controller {
|
||||
_tomSelect;
|
||||
|
||||
connect() {
|
||||
|
||||
let settings = {
|
||||
persistent: false,
|
||||
create: true,
|
||||
maxItems: 1,
|
||||
maxOptions: 100,
|
||||
createOnBlur: true,
|
||||
selectOnTab: true,
|
||||
valueField: 'text',
|
||||
searchField: 'text',
|
||||
orderField: 'text',
|
||||
|
||||
//This a an ugly solution to disable the delimiter parsing of the TomSelect plugin
|
||||
delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING'
|
||||
};
|
||||
|
||||
if (this.element.dataset.url) {
|
||||
const url = this.element.dataset.url;
|
||||
settings.load = (query, callback) => {
|
||||
const self = this;
|
||||
if (self.loading > 1) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(url)
|
||||
.then(response => response.text())
|
||||
.then(text => {
|
||||
// Convert the text file to array
|
||||
let lines = text.split("\n");
|
||||
//Remove all lines beginning with #
|
||||
lines = lines.filter(x => !x.startsWith("#"));
|
||||
|
||||
//Convert the array to an object, where each line is in the text field
|
||||
lines = lines.map(x => {
|
||||
return {text: x};
|
||||
});
|
||||
|
||||
|
||||
//Unset the load function to prevent endless recursion
|
||||
self._tomSelect.settings.load = null;
|
||||
|
||||
callback(lines);
|
||||
}).catch(() => {
|
||||
callback();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
this._tomSelect = new TomSelect(this.element, settings);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
super.disconnect();
|
||||
//Destroy the TomSelect instance
|
||||
this._tomSelect.destroy();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -94,13 +94,15 @@ export default class extends Controller {
|
|||
showTags: this._showTags,
|
||||
data: data,
|
||||
showIcon: true,
|
||||
preventUnselect: true,
|
||||
allowReselect: true,
|
||||
onNodeSelected: (event) => {
|
||||
const node = event.detail.node;
|
||||
if (node.href) {
|
||||
window.Turbo.visit(node.href, {action: "advance"});
|
||||
this._registerURLWatcher(node);
|
||||
}
|
||||
},
|
||||
//onNodeContextmenu: contextmenu_handler,
|
||||
}, [BS5Theme, BS53Theme, FAIconTheme]);
|
||||
|
||||
this.treeTarget.addEventListener(EVENT_INITIALIZED, (event) => {
|
||||
|
|
@ -108,12 +110,42 @@ export default class extends Controller {
|
|||
const treeView = event.detail.treeView;
|
||||
treeView.revealNode(treeView.getSelected());
|
||||
|
||||
//Add the url watcher to all selected nodes
|
||||
for (const node of treeView.getSelected()) {
|
||||
this._registerURLWatcher(node);
|
||||
}
|
||||
|
||||
//Add contextmenu event listener to the tree, which allows us to open the links in a new tab with a right click
|
||||
treeView.getTreeElement().addEventListener("contextmenu", this._onContextMenu.bind(this));
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
_registerURLWatcher(node)
|
||||
{
|
||||
//Register a watcher for a location change, which will unselect the node, if the location changes
|
||||
const desired_url = node.href;
|
||||
|
||||
//Ensure that the node is unselected, if the location changes
|
||||
const unselectNode = () => {
|
||||
//Parse url so we can properly compare them
|
||||
const desired = new URL(node.href, window.location.origin);
|
||||
|
||||
//We only compare the pathname, because the hash and parameters should not matter
|
||||
if(window.location.pathname !== desired.pathname) {
|
||||
//The ignore parameter is important here, otherwise the node will not be unselected
|
||||
node.setSelected(false, {silent: true, ignorePreventUnselect: true});
|
||||
|
||||
//Unregister the watcher
|
||||
document.removeEventListener('turbo:load', unselectNode);
|
||||
}
|
||||
};
|
||||
|
||||
//Register the watcher via hotwire turbo
|
||||
//We must just load to have the new url in window.location
|
||||
document.addEventListener('turbo:load', unselectNode);
|
||||
}
|
||||
|
||||
_onContextMenu(event)
|
||||
{
|
||||
//Find the node that was clicked and open link in new tab
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
export default class extends Controller {
|
||||
|
||||
static targets = [ "display", "select" ]
|
||||
|
||||
connect()
|
||||
{
|
||||
this.update();
|
||||
this.selectTarget.addEventListener('change', this.update.bind(this));
|
||||
}
|
||||
|
||||
update()
|
||||
{
|
||||
//If the select value is 0, then we show the input field
|
||||
if( this.selectTarget.value === '0')
|
||||
{
|
||||
this.displayTarget.classList.remove('d-none');
|
||||
}
|
||||
else
|
||||
{
|
||||
this.displayTarget.classList.add('d-none');
|
||||
}
|
||||
}
|
||||
}
|
||||
68
assets/controllers/pages/part_merge_modal_controller.js
Normal file
68
assets/controllers/pages/part_merge_modal_controller.js
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
export default class extends Controller
|
||||
{
|
||||
static targets = ['link', 'mode', 'otherSelect'];
|
||||
static values = {
|
||||
targetId: Number,
|
||||
};
|
||||
|
||||
connect() {
|
||||
}
|
||||
|
||||
update() {
|
||||
const link = this.linkTarget;
|
||||
const other_select = this.otherSelectTarget;
|
||||
|
||||
//Extract the mode using the mode radio buttons (we filter the array to get the checked one)
|
||||
const mode = (this.modeTargets.filter((e)=>e.checked))[0].value;
|
||||
|
||||
if (other_select.value === '') {
|
||||
link.classList.add('disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
//Extract href template from data attribute on link target
|
||||
let href = link.getAttribute('data-href-template');
|
||||
|
||||
let target, other;
|
||||
if (mode === '1') {
|
||||
target = this.targetIdValue;
|
||||
other = other_select.value;
|
||||
} else if (mode === '2') {
|
||||
target = other_select.value;
|
||||
other = this.targetIdValue;
|
||||
} else {
|
||||
throw 'Invalid mode';
|
||||
}
|
||||
|
||||
//Replace placeholder with actual target id
|
||||
href = href.replace('__target__', target);
|
||||
//Replace placeholder with selected value of the select (the event sender)
|
||||
href = href.replace('__other__', other);
|
||||
|
||||
//Assign new href to link
|
||||
link.setAttribute('href', href);
|
||||
//Make link clickable
|
||||
link.classList.remove('disabled');
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
import {Tab, Dropdown} from "bootstrap";
|
||||
import {Tab, Dropdown, Collapse} from "bootstrap";
|
||||
import tab from "bootstrap/js/src/tab";
|
||||
|
||||
/**
|
||||
|
|
@ -54,6 +54,7 @@ class TabRememberHelper {
|
|||
const first_element = merged[0] ?? null;
|
||||
if(first_element) {
|
||||
this.revealElementOnTab(first_element);
|
||||
this.revealElementInCollapse(first_element);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -62,10 +63,20 @@ class TabRememberHelper {
|
|||
* @param event
|
||||
*/
|
||||
onInvalid(event) {
|
||||
this.revealElementInCollapse(event.target);
|
||||
this.revealElementOnTab(event.target);
|
||||
this.revealElementInDropdown(event.target);
|
||||
}
|
||||
|
||||
revealElementInCollapse(element) {
|
||||
let collapse = element.closest('.collapse');
|
||||
|
||||
if(collapse) {
|
||||
let bs_collapse = Collapse.getOrCreateInstance(collapse);
|
||||
bs_collapse.show();
|
||||
}
|
||||
}
|
||||
|
||||
revealElementInDropdown(element) {
|
||||
let dropdown = element.closest('.dropdown-menu');
|
||||
|
||||
|
|
|
|||
10
bin/phpunit
10
bin/phpunit
|
|
@ -6,9 +6,13 @@ if (!ini_get('date.timezone')) {
|
|||
}
|
||||
|
||||
if (is_file(dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit')) {
|
||||
define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php');
|
||||
require PHPUNIT_COMPOSER_INSTALL;
|
||||
PHPUnit\TextUI\Command::main();
|
||||
if (PHP_VERSION_ID >= 80000) {
|
||||
require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit';
|
||||
} else {
|
||||
define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php');
|
||||
require PHPUNIT_COMPOSER_INSTALL;
|
||||
PHPUnit\TextUI\Command::main();
|
||||
}
|
||||
} else {
|
||||
if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) {
|
||||
echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n";
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@
|
|||
"ext-mbstring": "*",
|
||||
"api-platform/core": "^3.1",
|
||||
"beberlei/doctrineextensions": "^1.2",
|
||||
"brick/math": "^0.11.0",
|
||||
"brick/math": "0.12.1 as 0.11.0",
|
||||
"composer/ca-bundle": "^1.3",
|
||||
"composer/package-versions-deprecated": "^1.11.99.5",
|
||||
"doctrine/annotations": "1.14.3",
|
||||
"doctrine/data-fixtures": "^1.6.6",
|
||||
|
|
@ -20,12 +21,12 @@
|
|||
"doctrine/doctrine-bundle": "^2.0",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.0",
|
||||
"doctrine/orm": "^2.16",
|
||||
"dompdf/dompdf": "dev-master#87bea32efe0b0db309e1d31537201f64d5508280 as v2.0.3",
|
||||
"dompdf/dompdf": "dev-master#c9cf4be933e2406a51990bd4eb9e70612e790cc0 as v2.0.4",
|
||||
"erusev/parsedown": "^1.7",
|
||||
"florianv/swap": "^4.0",
|
||||
"florianv/swap-bundle": "dev-master",
|
||||
"gregwar/captcha-bundle": "^2.1.0",
|
||||
"jbtronics/2fa-webauthn": "^v2.0.0",
|
||||
"jbtronics/2fa-webauthn": "^v2.2.0",
|
||||
"jbtronics/dompdf-font-loader-bundle": "^1.0.0",
|
||||
"jfcherng/php-diff": "^6.14",
|
||||
"knpuniversity/oauth2-client-bundle": "^2.15",
|
||||
|
|
@ -38,7 +39,7 @@
|
|||
"nelmio/security-bundle": "^3.0",
|
||||
"nyholm/psr7": "^1.1",
|
||||
"ocramius/proxy-manager": "2.2.*",
|
||||
"omines/datatables-bundle": "^0.7.2",
|
||||
"omines/datatables-bundle": "^0.8.0",
|
||||
"part-db/label-fonts": "^1.0",
|
||||
"php-translation/symfony-bundle": "^0.14.0",
|
||||
"phpdocumentor/reflection-docblock": "^5.2",
|
||||
|
|
@ -51,35 +52,36 @@
|
|||
"shivas/versioning-bundle": "^4.0",
|
||||
"spatie/db-dumper": "^3.3.1",
|
||||
"symfony/apache-pack": "^1.0",
|
||||
"symfony/asset": "6.3.*",
|
||||
"symfony/console": "6.3.*",
|
||||
"symfony/dotenv": "6.3.*",
|
||||
"symfony/expression-language": "6.3.*",
|
||||
"symfony/asset": "6.4.*",
|
||||
"symfony/console": "6.4.*",
|
||||
"symfony/dotenv": "6.4.*",
|
||||
"symfony/expression-language": "6.4.*",
|
||||
"symfony/flex": "^v2.3.1",
|
||||
"symfony/form": "6.3.*",
|
||||
"symfony/framework-bundle": "6.3.*",
|
||||
"symfony/http-client": "6.3.*",
|
||||
"symfony/http-kernel": "6.3.*",
|
||||
"symfony/mailer": "6.3.*",
|
||||
"symfony/form": "6.4.*",
|
||||
"symfony/framework-bundle": "6.4.*",
|
||||
"symfony/http-client": "6.4.*",
|
||||
"symfony/http-kernel": "6.4.*",
|
||||
"symfony/mailer": "6.4.*",
|
||||
"symfony/monolog-bundle": "^3.1",
|
||||
"symfony/process": "6.3.*",
|
||||
"symfony/property-access": "6.3.*",
|
||||
"symfony/property-info": "6.3.*",
|
||||
"symfony/proxy-manager-bridge": "6.3.*",
|
||||
"symfony/rate-limiter": "6.3.*",
|
||||
"symfony/runtime": "6.3.*",
|
||||
"symfony/security-bundle": "6.3.*",
|
||||
"symfony/serializer": "6.3.*",
|
||||
"symfony/string": "6.3.*",
|
||||
"symfony/translation": "6.3.*",
|
||||
"symfony/twig-bundle": "6.3.*",
|
||||
"symfony/polyfill-php82": "^1.28",
|
||||
"symfony/process": "6.4.*",
|
||||
"symfony/property-access": "6.4.*",
|
||||
"symfony/property-info": "6.4.*",
|
||||
"symfony/proxy-manager-bridge": "6.4.*",
|
||||
"symfony/rate-limiter": "6.4.*",
|
||||
"symfony/runtime": "6.4.*",
|
||||
"symfony/security-bundle": "6.4.*",
|
||||
"symfony/serializer": "6.4.*",
|
||||
"symfony/string": "6.4.*",
|
||||
"symfony/translation": "6.4.*",
|
||||
"symfony/twig-bundle": "6.4.*",
|
||||
"symfony/ux-translator": "^2.10",
|
||||
"symfony/ux-turbo": "^2.0",
|
||||
"symfony/validator": "6.3.*",
|
||||
"symfony/web-link": "6.3.*",
|
||||
"symfony/validator": "6.4.*",
|
||||
"symfony/web-link": "6.4.*",
|
||||
"symfony/webpack-encore-bundle": "^v2.0.1",
|
||||
"symfony/yaml": "6.3.*",
|
||||
"tecnickcom/tc-lib-barcode": "^1.15",
|
||||
"symfony/yaml": "6.4.*",
|
||||
"tecnickcom/tc-lib-barcode": "^2.1.4",
|
||||
"twig/cssinliner-extra": "^3.0",
|
||||
"twig/extra-bundle": "^3.0",
|
||||
"twig/html-extra": "^3.0",
|
||||
|
|
@ -90,7 +92,7 @@
|
|||
"webmozart/assert": "^1.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"dama/doctrine-test-bundle": "^7.0",
|
||||
"dama/doctrine-test-bundle": "^v8.0.0",
|
||||
"doctrine/doctrine-fixtures-bundle": "^3.2",
|
||||
"ekino/phpstan-banned-code": "^v1.0.0",
|
||||
"phpstan/extension-installer": "^1.0",
|
||||
|
|
@ -102,13 +104,13 @@
|
|||
"psalm/plugin-symfony": "^v5.0.1",
|
||||
"rector/rector": "^0.18.0",
|
||||
"roave/security-advisories": "dev-latest",
|
||||
"symfony/browser-kit": "6.3.*",
|
||||
"symfony/css-selector": "6.3.*",
|
||||
"symfony/debug-bundle": "6.3.*",
|
||||
"symfony/browser-kit": "6.4.*",
|
||||
"symfony/css-selector": "6.4.*",
|
||||
"symfony/debug-bundle": "6.4.*",
|
||||
"symfony/maker-bundle": "^1.13",
|
||||
"symfony/phpunit-bridge": "6.3.*",
|
||||
"symfony/stopwatch": "6.3.*",
|
||||
"symfony/web-profiler-bundle": "6.3.*",
|
||||
"symfony/phpunit-bridge": "6.4.*",
|
||||
"symfony/stopwatch": "6.4.*",
|
||||
"symfony/web-profiler-bundle": "6.4.*",
|
||||
"symplify/easy-coding-standard": "^12.0",
|
||||
"vimeo/psalm": "^5.6.0"
|
||||
},
|
||||
|
|
@ -161,7 +163,7 @@
|
|||
"extra": {
|
||||
"symfony": {
|
||||
"allow-contrib": false,
|
||||
"require": "6.3.*"
|
||||
"require": "6.4.*"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3238
composer.lock
generated
3238
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,14 +1,18 @@
|
|||
api_platform:
|
||||
|
||||
title: 'Part-DB API'
|
||||
description: 'API of Part-DB'
|
||||
|
||||
version: '0.1.0'
|
||||
|
||||
# eager_loading:
|
||||
# max_joins: 100
|
||||
formats:
|
||||
jsonld: ['application/ld+json']
|
||||
json: ['application/json']
|
||||
jsonapi: ['application/vnd.api+json']
|
||||
|
||||
keep_legacy_inflector: false
|
||||
docs_formats:
|
||||
jsonld: ['application/ld+json']
|
||||
jsonopenapi: ['application/vnd.openapi+json']
|
||||
html: ['text/html']
|
||||
json: ['application/vnd.openapi+json']
|
||||
|
||||
swagger:
|
||||
api_keys:
|
||||
|
|
@ -24,5 +28,9 @@ api_platform:
|
|||
vary: ['Content-Type', 'Authorization', 'Origin']
|
||||
extra_properties:
|
||||
standard_put: true
|
||||
rfc_7807_compliant_errors: true
|
||||
|
||||
pagination_client_items_per_page: true # Allow clients to override the default items per page
|
||||
pagination_client_items_per_page: true # Allow clients to override the default items per page
|
||||
|
||||
keep_legacy_inflector: false
|
||||
event_listeners_backward_compatibility_layer: false
|
||||
|
|
@ -2,6 +2,9 @@ doctrine:
|
|||
dbal:
|
||||
url: '%env(resolve:DATABASE_URL)%'
|
||||
|
||||
# Required for DAMA doctrine test bundle
|
||||
use_savepoints: true
|
||||
|
||||
# IMPORTANT: You MUST configure your server version,
|
||||
# either here or in the DATABASE_URL env var (see .env file)
|
||||
|
||||
|
|
@ -28,8 +31,8 @@ doctrine:
|
|||
auto_mapping: true
|
||||
mappings:
|
||||
App:
|
||||
is_bundle: false
|
||||
type: attribute
|
||||
is_bundle: false
|
||||
dir: '%kernel.project_dir%/src/Entity'
|
||||
prefix: 'App\Entity'
|
||||
alias: App
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@
|
|||
framework:
|
||||
secret: '%env(APP_SECRET)%'
|
||||
csrf_protection: true
|
||||
annotations: false
|
||||
handle_all_throwables: true
|
||||
|
||||
# We set this header by ourself, so we can disable it here
|
||||
# We set this header by ourselves, so we can disable it here
|
||||
disallow_search_engine_index: false
|
||||
|
||||
# Must be set to true, to enable the change of HTTP method via _method parameter, otherwise our delete routines does not work anymore
|
||||
|
|
@ -26,7 +27,6 @@ framework:
|
|||
handler_id: null
|
||||
cookie_secure: auto
|
||||
cookie_samesite: lax
|
||||
storage_factory_id: session.storage.factory.native
|
||||
|
||||
#esi: true
|
||||
#fragments: true
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ parameters:
|
|||
saml.sp.privateKey: '%env(string:SAML_SP_PRIVATE_KEY)%'
|
||||
|
||||
nbgrp_onelogin_saml:
|
||||
use_proxy_vars: '%env(bool:SAML_BEHIND_PROXY)%'
|
||||
onelogin_settings:
|
||||
default:
|
||||
# Basic settings
|
||||
|
|
@ -31,7 +32,7 @@ nbgrp_onelogin_saml:
|
|||
privateKey: '%env(string:default:saml.sp.privateKey:string:SAMLP_SP_PRIVATE_KEY)%'
|
||||
|
||||
# Optional settings
|
||||
#baseurl: 'http://myapp.com'
|
||||
baseurl: '%partdb.default_uri%saml/'
|
||||
strict: true
|
||||
debug: false
|
||||
security:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ scheb_two_factor:
|
|||
server_name: '$$DOMAIN$$' # This field is replaced by the domain name of the server in DecoratedGoogleAuthenticator
|
||||
issuer: '%partdb.title%' # Issuer name used in QR code
|
||||
digits: 6 # Number of digits in authentication code
|
||||
window: 1 # How many codes before/after the current one would be accepted as valid
|
||||
leeway: 5 # Acceptable time drift in seconds
|
||||
template: security/2fa_form.html.twig
|
||||
|
||||
backup_codes:
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ security:
|
|||
provider: app_user_provider
|
||||
lazy: true
|
||||
user_checker: App\Security\UserChecker
|
||||
entry_point: form_login
|
||||
entry_point: App\Security\AuthenticationEntryPoint
|
||||
|
||||
# Enable user impersonation
|
||||
switch_user: { role: CAN_SWITCH_USER }
|
||||
|
|
@ -71,3 +71,5 @@ security:
|
|||
- { path: "^/\\w{2}/tree", role: PUBLIC_ACCESS }
|
||||
# Restrict access to API to users, which has the API access permission
|
||||
- { path: "^/api", allow_if: 'is_granted("@api.access_api") and is_authenticated()' }
|
||||
# Restrict access to KICAD to users, which has API access permission
|
||||
- { path: "^/kicad-api", allow_if: 'is_granted("@api.access_api") and is_authenticated()' }
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ twig:
|
|||
avatar_helper: '@App\Services\UserSystem\UserAvatarHelper'
|
||||
available_themes: '%partdb.available_themes%'
|
||||
saml_enabled: '%partdb.saml.enabled%'
|
||||
part_preview_generator: '@App\Services\Attachments\PartPreviewGenerator'
|
||||
|
||||
when@test:
|
||||
twig:
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ parameters:
|
|||
partdb.banner: '%env(trim:string:BANNER)%' # The info text shown in the homepage, if empty config/banner.md is used
|
||||
partdb.default_currency: '%env(string:BASE_CURRENCY)%' # The currency that is used inside the DB (and is assumed when no currency is set). This can not be changed later, so be sure to set it the currency used in your country
|
||||
partdb.global_theme: '' # The theme to use globally (see public/build/themes/ for choices, use name without .css). Set to '' for default bootstrap theme
|
||||
partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja'] # The languages that are shown in user drop down menu
|
||||
partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da'] # The languages that are shown in user drop down menu
|
||||
partdb.enforce_change_comments_for: '%env(csv:ENFORCE_CHANGE_COMMENTS_FOR)%' # The actions for which a change comment is required (e.g. "part_edit", "part_create", etc.). If this is empty, change comments are not required at all.
|
||||
|
||||
partdb.default_uri: '%env(string:DEFAULT_URI)%' # The default URI to use for the Part-DB instance (e.g. https://part-db.example.com/). This is used for generating links in emails
|
||||
|
|
@ -35,6 +35,7 @@ parameters:
|
|||
# Attachments and files
|
||||
######################################################################################################################
|
||||
partdb.attachments.allow_downloads: '%env(bool:ALLOW_ATTACHMENT_DOWNLOADS)%' # Allow users to download attachments to server. Warning: This can be dangerous, because via that feature attackers maybe can access ressources on your intranet!
|
||||
partdb.attachments.download_by_default: '%env(bool:ATTACHMENT_DOWNLOAD_BY_DEFAULT)%' # If this is set the 'download external files' checkbox is set by default for new attachments (only if allow_downloads is set to true)
|
||||
partdb.attachments.dir.media: 'public/media/' # The folder where uploaded attachment files are saved (must be in public folder)
|
||||
partdb.attachments.dir.secure: 'uploads/' # The folder where secured attachment files are saved (must not be in public/)
|
||||
partdb.attachments.max_file_size: '%env(string:MAX_ATTACHMENT_FILE_SIZE)%' # The maximum size of an attachment file (in bytes, you can use M for megabytes and G for gigabytes)
|
||||
|
|
@ -114,6 +115,8 @@ parameters:
|
|||
env(USE_GRAVATAR): '0'
|
||||
env(MAX_ATTACHMENT_FILE_SIZE): '100M'
|
||||
|
||||
env(REDIRECT_TO_HTTPS): 0
|
||||
|
||||
env(ENFORCE_CHANGE_COMMENTS_FOR): ''
|
||||
|
||||
env(ERROR_PAGE_ADMIN_EMAIL): ''
|
||||
|
|
@ -141,3 +144,4 @@ parameters:
|
|||
env(HISTORY_SAVE_REMOVED_DATA): 1
|
||||
env(HISTORY_SAVE_NEW_DATA): 1
|
||||
|
||||
env(EDA_KICAD_CATEGORY_DEPTH): 0
|
||||
|
|
|
|||
3
config/routes/security.yaml
Normal file
3
config/routes/security.yaml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
_security_logout:
|
||||
resource: security.route_loader.logout
|
||||
type: service
|
||||
|
|
@ -93,6 +93,7 @@ services:
|
|||
arguments:
|
||||
$allow_attachments_download: '%partdb.attachments.allow_downloads%'
|
||||
$max_file_size: '%partdb.attachments.max_file_size%'
|
||||
$download_by_default: '%partdb.attachments.download_by_default%'
|
||||
|
||||
App\Services\Attachments\AttachmentSubmitHandler:
|
||||
arguments:
|
||||
|
|
@ -140,6 +141,19 @@ services:
|
|||
$saml_role_mapping: '%env(json:SAML_ROLE_MAPPING)%'
|
||||
$update_group_on_login: '%env(bool:SAML_UPDATE_GROUP_ON_LOGIN)%'
|
||||
|
||||
|
||||
security.access_token_extractor.header.token:
|
||||
class: Symfony\Component\Security\Http\AccessToken\HeaderAccessTokenExtractor
|
||||
arguments:
|
||||
$tokenType: 'Token'
|
||||
|
||||
security.access_token_extractor.main:
|
||||
class: Symfony\Component\Security\Http\AccessToken\ChainAccessTokenExtractor
|
||||
arguments:
|
||||
$accessTokenExtractors:
|
||||
- '@security.access_token_extractor.header'
|
||||
- '@security.access_token_extractor.header.token'
|
||||
|
||||
####################################################################################################################
|
||||
# Cache
|
||||
####################################################################################################################
|
||||
|
|
@ -302,6 +316,13 @@ services:
|
|||
$global_locale: '%partdb.locale%'
|
||||
$global_timezone: '%partdb.timezone%'
|
||||
|
||||
####################################################################################################################
|
||||
# EDA system
|
||||
####################################################################################################################
|
||||
App\Services\EDA\KiCadHelper:
|
||||
arguments:
|
||||
$category_depth: '%env(int:EDA_KICAD_CATEGORY_DEPTH)%'
|
||||
|
||||
####################################################################################################################
|
||||
# Symfony overrides
|
||||
####################################################################################################################
|
||||
|
|
@ -349,6 +370,10 @@ services:
|
|||
$partdb_banner: '%partdb.banner%'
|
||||
$project_dir: '%kernel.project_dir%'
|
||||
|
||||
App\Doctrine\Middleware\MySQLSSLConnectionMiddlewareWrapper:
|
||||
arguments:
|
||||
$enabled: '%env(bool:DATABASE_MYSQL_USE_SSL_CA)%'
|
||||
$verify: '%env(bool:DATABASE_MYSQL_SSL_VERIFY_CERT)%'
|
||||
|
||||
####################################################################################################################
|
||||
# Monolog
|
||||
|
|
@ -376,4 +401,4 @@ when@test:
|
|||
arguments:
|
||||
- '@doctrine.fixtures.loader'
|
||||
- '@doctrine'
|
||||
- { default: '@App\Doctrine\Purger\ResetAutoIncrementPurgerFactory' }
|
||||
- { default: '@App\Doctrine\Purger\DoNotUsePurgerFactory' }
|
||||
|
|
@ -20,8 +20,8 @@ The only method currently available for authentication is to use API tokens:
|
|||
|
||||
An API token is a long alphanumeric string, which is bound to a specific user and can be used to authenticate as this
|
||||
user, when accessing the API.
|
||||
The API token is passed via the `Authentication` HTTP header during the API request, like the
|
||||
following: `Authentication: Bearer tcp_sdjfks....`.
|
||||
The API token is passed via the `Authorization` HTTP header during the API request, like the
|
||||
following: `Authorization: Bearer tcp_sdjfks....`.
|
||||
|
||||
{: .important }
|
||||
> Everybody who knows the API token can access the API as the user, which is bound to the token. So you should treat the
|
||||
|
|
|
|||
|
|
@ -175,4 +175,29 @@ which will be
|
|||
visible in the log of the entity.
|
||||
|
||||
You can pass the text for this via the `_comment` query parameter (beware the proper encoding). For
|
||||
example `/api/parts/123?_comment=This%20is%20a%20change%20comment`.
|
||||
example `/api/parts/123?_comment=This%20is%20a%20change%20comment`.
|
||||
|
||||
## Creating attachments and parameters
|
||||
|
||||
{: .warning }
|
||||
> The way described below is more a workaround than a proper solution. This might break in future versions of Part-DB!
|
||||
|
||||
Currently it is not possible to create attachments or parameters via a `POST` operation on the entity endpoint.
|
||||
The workaround for this is to send a patch request to the owning entity endpoint (e.g. parts `/api/parts/123`):
|
||||
|
||||
```
|
||||
PATCH /api/parts/123
|
||||
|
||||
{
|
||||
"attachments": [
|
||||
{"name": "front68", "attachment_type": "/api/attachment_types/1", "url": "https://invalid.invalid/test.url"}
|
||||
],
|
||||
"parameters": [
|
||||
{"name": "value", "unit": "Ohm", "value": 100}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The limitation of this is, that this will override/delete all existing attachments/parameters of the entity.
|
||||
|
||||
See [issue #502](https://github.com/Part-DB/Part-DB-server/issues/502) for more details on this topic.
|
||||
|
|
@ -37,6 +37,9 @@ options listed, see `.env` file for full list of possible env variables.
|
|||
(e.g. `DATABASE_URL=mysql://user:password@127.0.0.1:3306/part-db`). For sqlite use the following format to specify the
|
||||
absolute path where it should be located `sqlite:///path/part/app.db`. You can use `%kernel.project_dir%` as
|
||||
placeholder for the Part-DB root folder (e.g. `sqlite:///%kernel.project_dir%/var/app.db`)
|
||||
* `DATABASE_MYSQL_USE_SSL_CA`: If this value is set to `1` or `true` and a MySQL connection is used, then the connection
|
||||
is encrypted by SSL/TLS and the server certificate is verified against the system CA certificates or the CA certificate
|
||||
bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept all certificates.
|
||||
* `DEFAULT_LANG`: The default language to use server wide (when no language is explicitly specified by a user or via
|
||||
language chooser). Must be something like `en`, `de`, `fr`, etc.
|
||||
* `DEFAULT_TIMEZONE`: The default timezone to use globally, when a user has no timezone specified. Must be something
|
||||
|
|
@ -53,6 +56,9 @@ options listed, see `.env` file for full list of possible env variables.
|
|||
download a file specified as a URL and create it as local file. Please note that this allows users access to all
|
||||
resources publicly available to the server (so full access to other servers in the same local network), which could
|
||||
be a security risk.
|
||||
* `ATTACHMENT_DOWNLOAD_BY_DEFAULT`: When this is set to 1, the "download external file" checkbox is checked by default
|
||||
when adding a new attachment. Otherwise, it is unchecked by default. Use this if you wanna download all attachments
|
||||
locally by default. Attachment download is only possible, when `ALLOW_ATTACHMENT_DOWNLOADS` is set to 1.
|
||||
* `USE_GRAVATAR`: Set to `1` to use [gravatar.com](https://gravatar.com/) images for user avatars (as long as they have
|
||||
not set their own picture). The users browsers have to download the pictures from a third-party (gravatar) server, so
|
||||
this might be a privacy risk.
|
||||
|
|
@ -125,6 +131,14 @@ then `HISTORY_SAVE_CHANGED_FIELDS`, `HISTORY_SAVE_CHANGED_DATA` and `HISTORY_SAV
|
|||
* `ERROR_PAGE_SHOW_HELP`: Set this 0, to disable the solution hints shown on an error page. These hints should not
|
||||
contain sensitive information, but could confuse end-users.
|
||||
|
||||
### EDA related settings
|
||||
|
||||
* `EDA_KICAD_CATEGORY_DEPTH`: A number, which determines how many levels of Part-DB categories should be shown inside KiCad.
|
||||
All parts in the selected category and all subcategories are shown in KiCad.
|
||||
For performance reason this value should not be too high. The default is 0, which means that only the top level categories are shown in KiCad.
|
||||
All parts in the selected category and all subcategories are shown in KiCad. Set this to a higher value, if you want to show more categories in KiCad.
|
||||
When you set this value to -1, all parts are shown inside a single category in KiCad.
|
||||
|
||||
### SAML SSO settings
|
||||
|
||||
The following settings can be used to enable and configure Single-Sign on via SAML. This allows users to log in to
|
||||
|
|
@ -137,6 +151,8 @@ want to edit it on docker, you have to map the file to a volume.
|
|||
|
||||
* `SAML_ENABLED`: When this is set to 1, SAML SSO is enabled and the SSO Login button is shown in the login form. You
|
||||
have to configure the SAML settings below, before you can use this feature.
|
||||
* `SAML_BEHIND_PROXY`: Set this to 1, if Part-DB is behind a reverse proxy. See [here]({% link installation/reverse-proxy.md %})
|
||||
for more information. Otherwise, leave it to 0 (default.)
|
||||
* `SAML_ROLE_MAPPING`: A [JSON](https://en.wikipedia.org/wiki/JSON) encoded map which specifies how Part-DB should
|
||||
convert the user roles given by SAML attribute `group` should be converted to a Part-DB group (specified by ID). You
|
||||
can use a wildcard `*` to map all otherwise unmapped roles to a certain group.
|
||||
|
|
@ -180,6 +196,9 @@ See the [information providers]({% link usage/information_provider_system.md %})
|
|||
* `NO_URL_REWRITE_AVAILABLE` (allowed values `true` or `false`): Set this value to true, if your webserver does not
|
||||
support rewrite. In this case, all URL paths will contain index.php/, which is needed then. Normally this setting do
|
||||
not need to be changed.
|
||||
* `REDIRECT_TO_HTTPS`: If this is set to true, all requests to http will be redirected to https. This is useful, if your
|
||||
webserver does not already do this (like the one used in the demo instance). If your webserver already redirects to
|
||||
https, you don't need to set this. Ensure that Part-DB is accessible via https, before you enable this setting.
|
||||
* `FIXER_API_KEY`: If you want to automatically retrieve exchange rates for base currencies other than euros, you have to
|
||||
configure an exchange rate provider API. [Fixer.io](https://fixer.io/) is preconfigured, and you just have to register
|
||||
there and set the retrieved API key in this environment variable.
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ It is installed on a web server and so can be accessed with any browser without
|
|||
* Use cloud providers (like Octopart, Digikey, farnell or TME) to automatically get part information, datasheets and
|
||||
prices for parts (see [here]({% link usage/information_provider_system.md %}))
|
||||
* API to access Part-DB from other applications/scripts
|
||||
* [Integration with KiCad]({%link usage/eda_integration.md %}): Use Part-DB as central datasource for your
|
||||
KiCad and see available parts from Part-DB directly inside KiCad.
|
||||
|
||||
With these features Part-DB is useful to hobbyists, who want to keep track of their private electronic parts inventory,
|
||||
or makerspaces, where many users have should have (controlled) access to the shared inventory.
|
||||
|
|
|
|||
|
|
@ -30,7 +30,10 @@ automatically
|
|||
|
||||
However, SQLite does not support certain operations like regex search, which has to be emulated by PHP and therefore are
|
||||
pretty slow compared to the same operation at MySQL. In future there might be features that may only be available, when
|
||||
using MySQL.
|
||||
using MySQL. Also SQLite has limitations in comparisons and sorting of unicode characters, which might lead to unexpected
|
||||
behavior when using non-ASCII characters in your data. For example `µ` (micro sign) is not seen as equal to `μ`(greek minuscle mu),
|
||||
therefore searching for `µ` (micro sign) will not find parts containing `μ` (mu) and vice versa. In MySQL identical
|
||||
looking characters are seen as equal, which is more intuitive in most cases.
|
||||
|
||||
In general MySQL might perform better for big Part-DB instances with many entries, lots of users and high activity, than
|
||||
SQLite.
|
||||
|
|
|
|||
|
|
@ -47,8 +47,9 @@ services:
|
|||
|
||||
# You can configure Part-DB using environment variables
|
||||
# Below you can find the most essential ones predefined
|
||||
# However you can add add any other environment configuration you want here
|
||||
# However you can add any other environment configuration you want here
|
||||
# See .env file for all available options or https://docs.part-db.de/configuration.html
|
||||
# !!! Do not use quotes around the values, as they will be interpreted as part of the value and this will lead to errors !!!
|
||||
|
||||
# The language to use serverwide as default (en, de, ru, etc.)
|
||||
- DEFAULT_LANG=en
|
||||
|
|
@ -65,9 +66,12 @@ services:
|
|||
# Use gravatars for user avatars, when user has no own avatar defined
|
||||
- USE_GRAVATAR=0
|
||||
|
||||
# Override value if you want to show to show a given text on homepage.
|
||||
# Override value if you want to show a given text on homepage.
|
||||
# When this is empty the content of config/banner.md is used as banner
|
||||
#- BANNER=This is a test banner<br>with a line break
|
||||
|
||||
# If you use a reverse proxy in front of Part-DB, you must configure the trusted proxies IP addresses here (see reverse proxy documentation for more information):
|
||||
# - TRUSTED_PROXIES=127.0.0.0/8,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
|
||||
```
|
||||
|
||||
4. Customize the settings by changing the environment variables (or add new ones). See [Configuration]({% link
|
||||
|
|
|
|||
|
|
@ -230,3 +230,8 @@ Normally you don't have to change anything here.
|
|||
Please note that this file is not saved by the Part-DB backup tool, so you have to save it manually if you want to keep
|
||||
your changes. On docker containers you have to configure a volume mapping for it.
|
||||
|
||||
## SAML behind a reverse proxy
|
||||
|
||||
If you are running Part-DB behind a reverse proxy, configure the `TRUSTED_PROXIES` environment and other reverse proxy
|
||||
settings as described in the [reverse proxy guide]({% link installation/reverse-proxy.md %}).
|
||||
If you want to use SAML you also need to set `SAML_BEHIND_PROXY` to `true` to enable the SAML proxy mode.
|
||||
|
|
|
|||
|
|
@ -8,11 +8,23 @@ parent: Usage
|
|||
|
||||
Part-DB provides some console commands to display various information or perform some tasks.
|
||||
The commands are invoked from the main directory of Part-DB with the command `php bin/console [command]` in the context
|
||||
of the database user (so usually the webserver user), so you maybe have to use `sudo` or `su` to execute the commands.
|
||||
of the database user (so usually the webserver user), so you maybe have to use `sudo` or `su` to execute the commands:
|
||||
|
||||
```bash
|
||||
sudo -u www-data php bin/console [command]
|
||||
```
|
||||
|
||||
You can get help for every command with the parameter `--help`. See `php bin/console` for a list of all available
|
||||
commands.
|
||||
|
||||
If you are running Part-DB in a docker container, you must either execute the commands from a shell inside a container,
|
||||
or use the `docker exec` command to execute the command directly inside the container. For example if you docker container
|
||||
is named `partdb`, you can execute the command `php bin/console cache:clear` with the following command:
|
||||
|
||||
```bash
|
||||
docker exec --user=www-data partdb php bin/console cache:clear
|
||||
```
|
||||
|
||||
## User management commands
|
||||
|
||||
* `php bin/console partdb:users:list`: List all users of this Part-DB instance
|
||||
|
|
|
|||
79
docs/usage/eda_integration.md
Normal file
79
docs/usage/eda_integration.md
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
layout: default
|
||||
title: EDA / KiCad integration
|
||||
parent: Usage
|
||||
---
|
||||
|
||||
# EDA / KiCad integration
|
||||
|
||||
Part-DB can function as central database for [EDA](https://en.wikipedia.org/wiki/Electronic_design_automation) or ECAD software used to design electronic schematics and PCBs.
|
||||
You can connect your EDA software and can view your available parts, with the data saved from Part-DB directly in your EDA software.
|
||||
Part-DB allows to configure additional metadata for the EDA, to associate symbols and footprints for use inside the EDA software, so the part becomes
|
||||
directly usable inside the EDA software.
|
||||
This also allows to configure available and usable parts and their properties in a central place, which is especially useful in teams, where multiple persons design PCBs.
|
||||
|
||||
**Currently only KiCad is supported!**
|
||||
|
||||
## KiCad Setup
|
||||
|
||||
{: .important }
|
||||
> Part-DB uses the HTTP library feature of KiCad, which is experimental and not part of the stable KiCad 7 releases. If you want to use this feature, you need to install a KiCad nightly build (7.99 version). This feature will most likely also be part of KiCad 8.
|
||||
|
||||
Part-DB should be accessible from the PCs with KiCAD. The URL should be stable (so no dynamically changing IP).
|
||||
You require a user account in Part-DB, which has the permission to access Part-DB API and create API tokens. Every user can has its own account, or you setup a shared read-only account.
|
||||
|
||||
To connect KiCad with Part-DB do following steps:
|
||||
|
||||
1. Create an API token on the user settings page for the KiCAD application and copy/save it, when it is shown. Currently KiCAD can only read Part-DB database, so a token with read only scope is enough.
|
||||
2. Add some EDA metadata to parts, categories or footprints. Only parts with useable info will show up in KiCad. See below for more info.
|
||||
3. Create a file `partd.kicad_httplib` (or similar, only the extension is important) with the following content:
|
||||
```
|
||||
{
|
||||
"meta": {
|
||||
"version": 1.0
|
||||
},
|
||||
"name": "Part-DB library",
|
||||
"description": "This KiCAD library fetches information externally from ",
|
||||
"source": {
|
||||
"type": "REST_API",
|
||||
"api_version": "v1",
|
||||
"root_url": "http://kicad-instance.invalid/en/kicad-api/",
|
||||
"token": "THE_GENERATED_API_TOKEN"
|
||||
}
|
||||
}
|
||||
```
|
||||
4. Replace the `root_url` with the URL of your Part-DB instance plus `/en/kicad-api/`. You can find the right value for this in the Part-DB user settings page under "API endpoints" in the "API tokens" panel.
|
||||
5. Replace the `token` field value with the token you have generated in step 1.
|
||||
6. Open KiCad and add this created file as library in the KiCad symbol table under (Preferences --> Manage Symbol Libraries)
|
||||
|
||||
If you then place a new part, the library dialog opens, and you should be able to see the categories and parts from Part-DB.
|
||||
|
||||
### How to associate footprints and symbols with parts
|
||||
|
||||
Part-DB dont save any concrete footprints or symbols for the part. Instead Part-DB just contains a reference string in the part metadata, which points to a symbol/footprint in KiCads local library.
|
||||
|
||||
You can define this on a per-part basis using the KiCad symbol and KiCad footprint field in the EDA tab of the part editor. Or you can define it at a category (symbol) or footprint level, to assign this value to all parts with this category and footprint.
|
||||
|
||||
For example to configure the values for an BC547 transistor you would put `Transistor_BJT:BC547` on the parts Kicad symbol to give it the right schematic symbol in EEschema and `Package_TO_SOT_THT:TO-92` to give it the right footprint in PcbNew.
|
||||
|
||||
If you type in a character, you will get an autocomplete list of all symbols and footprints available in the kicad standard library. You can also input your own value.
|
||||
|
||||
### Parts and category visibility
|
||||
|
||||
Only parts and their categories, on which there is any kind of EDA metadata are defined show up in KiCad. So if you want to see parts in KiCad,
|
||||
you need to define at least a symbol, footprint, reference prefix or value on a part, category or footprint.
|
||||
|
||||
You can use the "Force visibility" checkbox on a part or category to override this behavior and force parts to be visible or hidden in KiCad.
|
||||
|
||||
*Please note that KiCad caches the library categories. So if you change something, which would change the visibile categories in KiCad, you have to reload EEschema to see the changes.*
|
||||
|
||||
### Category depth in KiCad
|
||||
|
||||
For performance reasons, only the most top level categories of Part-DB are shown as categories in KiCad. All parts in the subcategories are shown in the top level category.
|
||||
|
||||
You can configure the depth of the categories shown in KiCad, via the `EDA_KICAD_CATEGORY_DEPTH` env option. The default value is 0, which meabs only the top level categories are shown.
|
||||
To show more levels of categories, you can set this value to a higher number.
|
||||
|
||||
If you set this value to -1, all parts are shown inside a single category in KiCad, without any subcategories.
|
||||
|
||||
You can view the "real" category path of a part in the part details dialog in KiCad.
|
||||
|
|
@ -37,6 +37,10 @@ filled in.
|
|||
|
||||

|
||||
|
||||
If you want to update an existing part, go to the parts info page and click on the "Update from info provider" button in
|
||||
the tools tab. You will be redirected to a search page, where you can search the info providers to automatically update this
|
||||
part.
|
||||
|
||||
## Alternative names
|
||||
|
||||
Part-DB tries to automatically find existing elements from your database for the information it got from the providers
|
||||
|
|
@ -183,6 +187,10 @@ the [Mouser API page](https://www.mouser.de/api-home/).
|
|||
You will receive an API token, which you have to put in the Part-DB env configuration (see below):
|
||||
At the registration you choose a country, language and currency in which you want to get the results.
|
||||
|
||||
*Attention*: Currently (January 2024) the mouser API seems to be somewhat broken, in the way that it does not return any
|
||||
information about datasheets and part specifications. Therefore Part-DB can not retrieve them, even if they are shown
|
||||
at the mouser page. See [issue #503](https://github.com/Part-DB/Part-DB-server/issues/503) for more infos.
|
||||
|
||||
Following env configuration options are available:
|
||||
|
||||
* `PROVIDER_MOUSER_KEY`: The API key you got from Mouser (mandatory)
|
||||
|
|
|
|||
|
|
@ -83,9 +83,12 @@ final class Version20221114193325 extends AbstractMultiPlatformMigration impleme
|
|||
//Reset the permissions of the admin user, to allow admin permissions (like the admins group)
|
||||
$this->addSql("UPDATE `users` SET permissions_data = '$admin' WHERE id = 2;");
|
||||
|
||||
//This warning should not be needed, anymore, as almost everybody should have updated to the new version by now, and this warning would just irritate new users of the software
|
||||
/*
|
||||
$this->logger->warning('<bg=cyan;fg=black>!!! All permissions were reset! Please change them to the desired state, immediately !!!</>');
|
||||
$this->logger->warning('<bg=cyan;fg=black>!!! For security reasons all users (except the admin user) were disabled. Login with admin user and reenable other users after checking their permissions !!!</>');
|
||||
$this->logger->warning('<bg=cyan;fg=black>!!! For more infos see: https://github.com/Part-DB/Part-DB-symfony/discussions/193 !!!</>');
|
||||
*/
|
||||
}
|
||||
|
||||
public function mySQLUp(Schema $schema): void
|
||||
|
|
|
|||
71
migrations/Version20231114223101.php
Normal file
71
migrations/Version20231114223101.php
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Migration\AbstractMultiPlatformMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20231114223101 extends AbstractMultiPlatformMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add schema for part associations and vendor barcodes';
|
||||
}
|
||||
|
||||
public function mySQLUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE part_association (id INT AUTO_INCREMENT NOT NULL, owner_id INT NOT NULL, other_id INT NOT NULL, type SMALLINT NOT NULL, other_type VARCHAR(255) DEFAULT NULL, comment LONGTEXT DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, INDEX IDX_61B952E07E3C61F9 (owner_id), INDEX IDX_61B952E0998D9879 (other_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('ALTER TABLE part_association ADD CONSTRAINT FK_61B952E07E3C61F9 FOREIGN KEY (owner_id) REFERENCES `parts` (id) ON DELETE CASCADE');
|
||||
$this->addSql('ALTER TABLE part_association ADD CONSTRAINT FK_61B952E0998D9879 FOREIGN KEY (other_id) REFERENCES `parts` (id) ON DELETE CASCADE');
|
||||
$this->addSql('ALTER TABLE part_lots ADD vendor_barcode VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('CREATE INDEX part_lots_idx_barcode ON part_lots (vendor_barcode)');
|
||||
}
|
||||
|
||||
public function mySQLDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE part_association DROP FOREIGN KEY FK_61B952E07E3C61F9');
|
||||
$this->addSql('ALTER TABLE part_association DROP FOREIGN KEY FK_61B952E0998D9879');
|
||||
$this->addSql('DROP TABLE part_association');
|
||||
$this->addSql('DROP INDEX part_lots_idx_barcode ON part_lots');
|
||||
$this->addSql('ALTER TABLE part_lots DROP vendor_barcode');
|
||||
}
|
||||
|
||||
public function sqLiteUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE part_association (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, owner_id INTEGER NOT NULL, other_id INTEGER NOT NULL, type SMALLINT NOT NULL, other_type VARCHAR(255) DEFAULT NULL, comment CLOB DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_61B952E07E3C61F9 FOREIGN KEY (owner_id) REFERENCES "parts" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_61B952E0998D9879 FOREIGN KEY (other_id) REFERENCES "parts" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('CREATE INDEX IDX_61B952E07E3C61F9 ON part_association (owner_id)');
|
||||
$this->addSql('CREATE INDEX IDX_61B952E0998D9879 ON part_association (other_id)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__part_lots AS SELECT id, id_store_location, id_part, id_owner, description, comment, expiration_date, instock_unknown, amount, needs_refill, last_modified, datetime_added FROM part_lots');
|
||||
$this->addSql('DROP TABLE part_lots');
|
||||
$this->addSql('CREATE TABLE part_lots (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_store_location INTEGER DEFAULT NULL, id_part INTEGER NOT NULL, id_owner INTEGER DEFAULT NULL, description CLOB NOT NULL, comment CLOB NOT NULL, expiration_date DATETIME DEFAULT NULL, instock_unknown BOOLEAN NOT NULL, amount DOUBLE PRECISION NOT NULL, needs_refill BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, vendor_barcode VARCHAR(255) DEFAULT NULL, CONSTRAINT FK_EBC8F9435D8F4B37 FOREIGN KEY (id_store_location) REFERENCES storelocations (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_EBC8F943C22F6CC4 FOREIGN KEY (id_part) REFERENCES parts (id) ON UPDATE NO ACTION ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_EBC8F94321E5A74C FOREIGN KEY (id_owner) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO part_lots (id, id_store_location, id_part, id_owner, description, comment, expiration_date, instock_unknown, amount, needs_refill, last_modified, datetime_added) SELECT id, id_store_location, id_part, id_owner, description, comment, expiration_date, instock_unknown, amount, needs_refill, last_modified, datetime_added FROM __temp__part_lots');
|
||||
$this->addSql('DROP TABLE __temp__part_lots');
|
||||
$this->addSql('CREATE INDEX IDX_EBC8F94321E5A74C ON part_lots (id_owner)');
|
||||
$this->addSql('CREATE INDEX IDX_EBC8F943C22F6CC4 ON part_lots (id_part)');
|
||||
$this->addSql('CREATE INDEX IDX_EBC8F9435D8F4B37 ON part_lots (id_store_location)');
|
||||
$this->addSql('CREATE INDEX part_lots_idx_instock_un_expiration_id_part ON part_lots (instock_unknown, expiration_date, id_part)');
|
||||
$this->addSql('CREATE INDEX part_lots_idx_needs_refill ON part_lots (needs_refill)');
|
||||
$this->addSql('CREATE INDEX part_lots_idx_barcode ON part_lots (vendor_barcode)');
|
||||
}
|
||||
|
||||
public function sqLiteDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP TABLE part_association');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__part_lots AS SELECT id, id_store_location, id_part, id_owner, description, comment, expiration_date, instock_unknown, amount, needs_refill, last_modified, datetime_added FROM part_lots');
|
||||
$this->addSql('DROP TABLE part_lots');
|
||||
$this->addSql('CREATE TABLE part_lots (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_store_location INTEGER DEFAULT NULL, id_part INTEGER NOT NULL, id_owner INTEGER DEFAULT NULL, description CLOB NOT NULL, comment CLOB NOT NULL, expiration_date DATETIME DEFAULT NULL, instock_unknown BOOLEAN NOT NULL, amount DOUBLE PRECISION NOT NULL, needs_refill BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_EBC8F9435D8F4B37 FOREIGN KEY (id_store_location) REFERENCES "storelocations" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_EBC8F943C22F6CC4 FOREIGN KEY (id_part) REFERENCES "parts" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_EBC8F94321E5A74C FOREIGN KEY (id_owner) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO part_lots (id, id_store_location, id_part, id_owner, description, comment, expiration_date, instock_unknown, amount, needs_refill, last_modified, datetime_added) SELECT id, id_store_location, id_part, id_owner, description, comment, expiration_date, instock_unknown, amount, needs_refill, last_modified, datetime_added FROM __temp__part_lots');
|
||||
$this->addSql('DROP TABLE __temp__part_lots');
|
||||
$this->addSql('CREATE INDEX IDX_EBC8F9435D8F4B37 ON part_lots (id_store_location)');
|
||||
$this->addSql('CREATE INDEX IDX_EBC8F943C22F6CC4 ON part_lots (id_part)');
|
||||
$this->addSql('CREATE INDEX IDX_EBC8F94321E5A74C ON part_lots (id_owner)');
|
||||
$this->addSql('CREATE INDEX part_lots_idx_instock_un_expiration_id_part ON part_lots (instock_unknown, expiration_date, id_part)');
|
||||
$this->addSql('CREATE INDEX part_lots_idx_needs_refill ON part_lots (needs_refill)');
|
||||
}
|
||||
}
|
||||
88
migrations/Version20231130180903.php
Normal file
88
migrations/Version20231130180903.php
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Migration\AbstractMultiPlatformMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
final class Version20231130180903 extends AbstractMultiPlatformMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Added EDA fields';
|
||||
}
|
||||
|
||||
public function mySQLUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE categories ADD eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, ADD eda_info_invisible TINYINT(1) DEFAULT NULL, ADD eda_info_exclude_from_bom TINYINT(1) DEFAULT NULL, ADD eda_info_exclude_from_board TINYINT(1) DEFAULT NULL, ADD eda_info_exclude_from_sim TINYINT(1) DEFAULT NULL, ADD eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE footprints ADD eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE parts ADD eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, ADD eda_info_value VARCHAR(255) DEFAULT NULL, ADD eda_info_invisible TINYINT(1) DEFAULT NULL, ADD eda_info_exclude_from_bom TINYINT(1) DEFAULT NULL, ADD eda_info_exclude_from_board TINYINT(1) DEFAULT NULL, ADD eda_info_exclude_from_sim TINYINT(1) DEFAULT NULL, ADD eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL, ADD eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function mySQLDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE `categories` DROP eda_info_reference_prefix, DROP eda_info_invisible, DROP eda_info_exclude_from_bom, DROP eda_info_exclude_from_board, DROP eda_info_exclude_from_sim, DROP eda_info_kicad_symbol');
|
||||
$this->addSql('ALTER TABLE `footprints` DROP eda_info_kicad_footprint');
|
||||
$this->addSql('ALTER TABLE `parts` DROP eda_info_reference_prefix, DROP eda_info_value, DROP eda_info_invisible, DROP eda_info_exclude_from_bom, DROP eda_info_exclude_from_board, DROP eda_info_exclude_from_sim, DROP eda_info_kicad_symbol, DROP eda_info_kicad_footprint');
|
||||
}
|
||||
|
||||
public function sqLiteUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE categories ADD COLUMN eda_info_reference_prefix VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE categories ADD COLUMN eda_info_invisible BOOLEAN DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE categories ADD COLUMN eda_info_exclude_from_bom BOOLEAN DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE categories ADD COLUMN eda_info_exclude_from_board BOOLEAN DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE categories ADD COLUMN eda_info_exclude_from_sim BOOLEAN DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE categories ADD COLUMN eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE footprints ADD COLUMN eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE parts ADD COLUMN eda_info_reference_prefix VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE parts ADD COLUMN eda_info_value VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE parts ADD COLUMN eda_info_invisible BOOLEAN DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE parts ADD COLUMN eda_info_exclude_from_bom BOOLEAN DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE parts ADD COLUMN eda_info_exclude_from_board BOOLEAN DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE parts ADD COLUMN eda_info_exclude_from_sim BOOLEAN DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE parts ADD COLUMN eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE parts ADD COLUMN eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function sqLiteDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__categories AS SELECT id, parent_id, id_preview_attachment, name, last_modified, datetime_added, comment, not_selectable, alternative_names, partname_hint, partname_regex, disable_footprints, disable_manufacturers, disable_autodatasheets, disable_properties, default_description, default_comment 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, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names CLOB 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, 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, name, last_modified, datetime_added, comment, not_selectable, alternative_names, partname_hint, partname_regex, disable_footprints, disable_manufacturers, disable_autodatasheets, disable_properties, default_description, default_comment) SELECT id, parent_id, id_preview_attachment, name, last_modified, datetime_added, comment, not_selectable, alternative_names, partname_hint, partname_regex, disable_footprints, disable_manufacturers, disable_autodatasheets, disable_properties, default_description, default_comment 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__footprints AS SELECT id, parent_id, id_preview_attachment, id_footprint_3d, name, last_modified, datetime_added, comment, not_selectable, alternative_names 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_preview_attachment INTEGER DEFAULT NULL, id_footprint_3d INTEGER DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names CLOB DEFAULT NULL, CONSTRAINT FK_A34D68A2727ACA70 FOREIGN KEY (parent_id) REFERENCES "footprints" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_A34D68A2EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_A34D68A232A38C34 FOREIGN KEY (id_footprint_3d) REFERENCES "attachments" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "footprints" (id, parent_id, id_preview_attachment, id_footprint_3d, name, last_modified, datetime_added, comment, not_selectable, alternative_names) SELECT id, parent_id, id_preview_attachment, id_footprint_3d, name, last_modified, datetime_added, comment, not_selectable, alternative_names FROM __temp__footprints');
|
||||
$this->addSql('DROP TABLE __temp__footprints');
|
||||
$this->addSql('CREATE INDEX IDX_A34D68A2727ACA70 ON "footprints" (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_A34D68A2EA7100A1 ON "footprints" (id_preview_attachment)');
|
||||
$this->addSql('CREATE INDEX IDX_A34D68A232A38C34 ON "footprints" (id_footprint_3d)');
|
||||
$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__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, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated 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 CLOB NOT NULL, manufacturer_product_number VARCHAR(255) NOT NULL, manufacturing_status VARCHAR(255) DEFAULT NULL, order_quantity INTEGER NOT NULL, manual_order BOOLEAN NOT NULL, 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_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, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated) 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, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated 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)');
|
||||
}
|
||||
}
|
||||
|
|
@ -87,7 +87,7 @@
|
|||
"json-formatter-js": "^2.3.4",
|
||||
"jszip": "^3.2.0",
|
||||
"katex": "^0.16.0",
|
||||
"marked": "^9.1.0",
|
||||
"marked": "^11.1.1",
|
||||
"marked-gfm-heading-id": "^3.0.4",
|
||||
"marked-mangle": "^1.0.1",
|
||||
"pdfmake": "^0.2.2",
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
1
public/img/default_avatar.svg
Normal file
1
public/img/default_avatar.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g id="Ebene1"><rect x="0.021" y="-0.023" width="19.977" height="20" style="fill:#95bbdf;"/></g><path d="M10,10c2.194,0 4,-1.806 4,-4c0,-2.194 -1.806,-4 -4,-4c-2.194,0 -4,1.806 -4,4c0,2.194 1.806,4 4,4Zm-1.428,1.5c-3.078,0 -5.572,2.494 -5.572,5.572c0,0.512 0.416,0.928 0.928,0.928l12.144,0c0.512,0 0.928,-0.416 0.928,-0.928c0,-3.078 -2.494,-5.572 -5.572,-5.572l-2.856,0Z" style="fill:#fff;fill-rule:nonzero;"/></svg>
|
||||
|
After Width: | Height: | Size: 856 B |
13272
public/kicad/footprints.txt
Normal file
13272
public/kicad/footprints.txt
Normal file
File diff suppressed because it is too large
Load diff
19594
public/kicad/symbols.txt
Normal file
19594
public/kicad/symbols.txt
Normal file
File diff suppressed because it is too large
Load diff
75
src/ApiPlatform/ErrorHandler.php
Normal file
75
src/ApiPlatform/ErrorHandler.php
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2024 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\ApiPlatform;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
use Doctrine\ORM\ORMInvalidArgumentException;
|
||||
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
|
||||
use ApiPlatform\State\ApiResource\Error;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
|
||||
/**
|
||||
* This class adds a custom error if the user tries to create a new entity through a relation, and suggests to do reference it through an IRI instead.
|
||||
* This class decorates the default error handler of API Platform.
|
||||
*/
|
||||
#[AsDecorator('api_platform.state.error_provider')]
|
||||
final class ErrorHandler implements ProviderInterface
|
||||
{
|
||||
public function __construct(private readonly ProviderInterface $decorated, #[Autowire('%kernel.debug%')] private readonly bool $debug)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
|
||||
{
|
||||
$request = $context['request'];
|
||||
$format = $request->getRequestFormat();
|
||||
$exception = $request->attributes->get('exception');
|
||||
|
||||
//Check if the exception is a ORM InvalidArgument exception and complains about a not-persisted entity through relation
|
||||
if ($exception instanceof ORMInvalidArgumentException && str_contains($exception->getMessage(), 'A new entity was found through the relationship')) {
|
||||
//Extract the entity class and property name from the exception message
|
||||
$matches = [];
|
||||
preg_match('/A new entity was found through the relationship \'(?<property>.*)\'/i', $exception->getMessage(), $matches);
|
||||
|
||||
$property = $matches['property'] ?? "unknown";
|
||||
|
||||
//Create a new error response
|
||||
$error = Error::createFromException($exception, 400);
|
||||
|
||||
//Return the error response
|
||||
$detail = "You tried to create a new entity through the relation '$property', but this is not allowed. Please create the entity first and then reference it through an IRI!";
|
||||
//If we are in debug mode, add the exception message to the error response
|
||||
if ($this->debug) {
|
||||
$detail .= " Original exception message: " . $exception->getMessage();
|
||||
}
|
||||
$error->setDetail($detail);
|
||||
return $error;
|
||||
}
|
||||
|
||||
|
||||
return $this->decorated->provide($operation, $uriVariables, $context);
|
||||
}
|
||||
}
|
||||
76
src/Command/LoadFixturesCommand.php
Normal file
76
src/Command/LoadFixturesCommand.php
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
<?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\Command;
|
||||
|
||||
use App\Doctrine\Purger\ResetAutoIncrementORMPurger;
|
||||
use App\Doctrine\Purger\DoNotUsePurgerFactory;
|
||||
use App\Doctrine\Purger\ResetAutoIncrementPurgerFactory;
|
||||
use Doctrine\Bundle\FixturesBundle\Purger\ORMPurgerFactory;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
/**
|
||||
* This command does basically the same as doctrine:fixtures:load, but it purges the database before loading the fixtures.
|
||||
* It does so in another transaction, so we can modify the purger to reset the autoincrement, which would not be possible
|
||||
* because the implicit commit otherwise.
|
||||
*/
|
||||
#[AsCommand(name: 'partdb:fixtures:load', description: 'Load test fixtures into the database and allows to reset the autoincrement before loading the fixtures.', hidden: true)]
|
||||
class LoadFixturesCommand extends Command
|
||||
{
|
||||
public function __construct(private readonly EntityManagerInterface $entityManager)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$ui = new SymfonyStyle($input, $output);
|
||||
|
||||
$ui->warning('This command is for development and testing purposes only. It will purge the database and load fixtures afterwards. Do not use in production!');
|
||||
|
||||
if (! $ui->confirm(sprintf('Careful, database "%s" will be purged. Do you want to continue?', $this->entityManager->getConnection()->getDatabase()), ! $input->isInteractive())) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$factory = new ResetAutoIncrementPurgerFactory();
|
||||
$purger = $factory->createForEntityManager(null, $this->entityManager);
|
||||
|
||||
$purger->purge();
|
||||
|
||||
//Afterwards run the load fixtures command as normal, but with the --append option
|
||||
$new_input = new ArrayInput([
|
||||
'command' => 'doctrine:fixtures:load',
|
||||
'--append' => true,
|
||||
]);
|
||||
|
||||
$returnCode = $this->getApplication()?->doRun($new_input, $output);
|
||||
|
||||
return $returnCode ?? Command::FAILURE;
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Exceptions\AttachmentDownloadException;
|
||||
use App\Form\InfoProviderSystem\PartSearchType;
|
||||
use App\Form\Part\PartBaseType;
|
||||
|
|
@ -32,13 +33,18 @@ use App\Services\InfoProviderSystem\ProviderRegistry;
|
|||
use App\Services\LogSystem\EventCommentHelper;
|
||||
use App\Services\Parts\PartFormHelper;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpClient\Exception\ClientException;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
use function Symfony\Component\Translation\t;
|
||||
|
||||
#[Route('/tools/info_providers')]
|
||||
class InfoProviderController extends AbstractController
|
||||
{
|
||||
|
|
@ -61,7 +67,8 @@ class InfoProviderController extends AbstractController
|
|||
}
|
||||
|
||||
#[Route('/search', name: 'info_providers_search')]
|
||||
public function search(Request $request): Response
|
||||
#[Route('/update/{target}', name: 'info_providers_update_part_search')]
|
||||
public function search(Request $request, #[MapEntity(id: 'target')] ?Part $update_target, LoggerInterface $exceptionLogger): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@info_providers.create_parts');
|
||||
|
||||
|
|
@ -70,16 +77,30 @@ class InfoProviderController extends AbstractController
|
|||
|
||||
$results = null;
|
||||
|
||||
//When we are updating a part, use its name as keyword, to make searching easier
|
||||
//However we can only do this, if the form was not submitted yet
|
||||
if ($update_target !== null && !$form->isSubmitted()) {
|
||||
$form->get('keyword')->setData($update_target->getName());
|
||||
}
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$keyword = $form->get('keyword')->getData();
|
||||
$providers = $form->get('providers')->getData();
|
||||
|
||||
$results = $this->infoRetriever->searchByKeyword(keyword: $keyword, providers: $providers);
|
||||
try {
|
||||
$results = $this->infoRetriever->searchByKeyword(keyword: $keyword, providers: $providers);
|
||||
} catch (ClientException $e) {
|
||||
$this->addFlash('error', t('info_providers.search.error.client_exception'));
|
||||
$this->addFlash('error',$e->getMessage());
|
||||
//Log the exception
|
||||
$exceptionLogger->error('Error during info provider search: ' . $e->getMessage(), ['exception' => $e]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('info_providers/search/part_search.html.twig', [
|
||||
'form' => $form,
|
||||
'results' => $results,
|
||||
'update_target' => $update_target
|
||||
]);
|
||||
}
|
||||
}
|
||||
84
src/Controller/KiCadApiController.php
Normal file
84
src/Controller/KiCadApiController.php
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
<?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\Entity\Parts\Category;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Services\EDA\KiCadHelper;
|
||||
use App\Services\Trees\NodesListBuilder;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
#[Route('/kicad-api/v1')]
|
||||
class KiCadApiController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly KiCadHelper $kiCADHelper,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
#[Route('/', name: 'kicad_api_root')]
|
||||
public function root(): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('HAS_ACCESS_PERMISSIONS');
|
||||
|
||||
//The API documentation says this can be either blank or the URL to the endpoints
|
||||
return $this->json([
|
||||
'categories' => '',
|
||||
'parts' => '',
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/categories.json', name: 'kicad_api_categories')]
|
||||
public function categories(): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@categories.read');
|
||||
|
||||
return $this->json($this->kiCADHelper->getCategories());
|
||||
}
|
||||
|
||||
#[Route('/parts/category/{category}.json', name: 'kicad_api_category')]
|
||||
public function categoryParts(?Category $category): Response
|
||||
{
|
||||
if ($category) {
|
||||
$this->denyAccessUnlessGranted('read', $category);
|
||||
} else {
|
||||
$this->denyAccessUnlessGranted('@categories.read');
|
||||
}
|
||||
$this->denyAccessUnlessGranted('@parts.read');
|
||||
|
||||
return $this->json($this->kiCADHelper->getCategoryParts($category));
|
||||
}
|
||||
|
||||
#[Route('/parts/{part}.json', name: 'kicad_api_part')]
|
||||
public function partDetails(Part $part): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('read', $part);
|
||||
|
||||
return $this->json($this->kiCADHelper->getKiCADPart($part));
|
||||
}
|
||||
}
|
||||
|
|
@ -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\EntityMergers\Mergers\PartMerger;
|
||||
use App\Services\InfoProviderSystem\PartInfoRetriever;
|
||||
use App\Services\LogSystem\EventCommentHelper;
|
||||
use App\Services\LogSystem\HistoryHelper;
|
||||
|
|
@ -233,6 +234,48 @@ class PartController extends AbstractController
|
|||
]);
|
||||
}
|
||||
|
||||
#[Route('/{target}/merge/{other}', name: 'part_merge')]
|
||||
public function merge(Request $request, Part $target, Part $other, PartMerger $partMerger): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('edit', $target);
|
||||
$this->denyAccessUnlessGranted('delete', $other);
|
||||
|
||||
//Save the old name of the target part for the template
|
||||
$target_name = $target->getName();
|
||||
|
||||
$this->addFlash('notice', t('part.merge.flash.please_review'));
|
||||
|
||||
$merged = $partMerger->merge($target, $other);
|
||||
return $this->renderPartForm('merge', $request, $merged, [], [
|
||||
'tname_before' => $target_name,
|
||||
'other_part' => $other,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route(path: '/{id}/from_info_provider/{providerKey}/{providerId}/update', name: 'info_providers_update_part', requirements: ['providerId' => '.+'])]
|
||||
public function updateFromInfoProvider(Part $part, Request $request, string $providerKey, string $providerId,
|
||||
PartInfoRetriever $infoRetriever, PartMerger $partMerger): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('edit', $part);
|
||||
$this->denyAccessUnlessGranted('@info_providers.create_parts');
|
||||
|
||||
//Save the old name of the target part for the template
|
||||
$old_name = $part->getName();
|
||||
|
||||
$dto = $infoRetriever->getDetails($providerKey, $providerId);
|
||||
$provider_part = $infoRetriever->dtoToPart($dto);
|
||||
|
||||
$part = $partMerger->merge($part, $provider_part);
|
||||
|
||||
$this->addFlash('notice', t('part.merge.flash.please_review'));
|
||||
|
||||
return $this->renderPartForm('update_from_ip', $request, $part, [
|
||||
'info_provider_dto' => $dto,
|
||||
], [
|
||||
'tname_before' => $old_name
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function provides a common implementation for methods, which use the part form.
|
||||
* @param Request $request
|
||||
|
|
@ -240,10 +283,10 @@ class PartController extends AbstractController
|
|||
* @param array $form_options
|
||||
* @return Response
|
||||
*/
|
||||
private function renderPartForm(string $mode, Request $request, Part $data, array $form_options = []): Response
|
||||
private function renderPartForm(string $mode, Request $request, Part $data, array $form_options = [], array $merge_infos = []): Response
|
||||
{
|
||||
//Ensure that mode is either 'new' or 'edit
|
||||
if (!in_array($mode, ['new', 'edit'], true)) {
|
||||
if (!in_array($mode, ['new', 'edit', 'merge', 'update_from_ip'], true)) {
|
||||
throw new \InvalidArgumentException('Invalid mode given');
|
||||
}
|
||||
|
||||
|
|
@ -276,6 +319,12 @@ class PartController extends AbstractController
|
|||
$this->commentHelper->setMessage($form['log_comment']->getData());
|
||||
|
||||
$this->em->persist($new_part);
|
||||
|
||||
//When we are in merge mode, we have to remove the other part
|
||||
if ($mode === 'merge') {
|
||||
$this->em->remove($merge_infos['other_part']);
|
||||
}
|
||||
|
||||
$this->em->flush();
|
||||
if ($mode === 'new') {
|
||||
$this->addFlash('success', 'part.created_flash');
|
||||
|
|
@ -310,12 +359,18 @@ class PartController extends AbstractController
|
|||
$template = 'parts/edit/new_part.html.twig';
|
||||
} else if ($mode === 'edit') {
|
||||
$template = 'parts/edit/edit_part_info.html.twig';
|
||||
} else if ($mode === 'merge') {
|
||||
$template = 'parts/edit/merge_parts.html.twig';
|
||||
} else if ($mode === 'update_from_ip') {
|
||||
$template = 'parts/edit/update_from_ip.html.twig';
|
||||
}
|
||||
|
||||
return $this->render($template,
|
||||
[
|
||||
'part' => $new_part,
|
||||
'form' => $form,
|
||||
'merge_old_name' => $merge_infos['tname_before'] ?? null,
|
||||
'merge_other' => $merge_infos['other_part'] ?? null
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -345,23 +400,41 @@ class PartController extends AbstractController
|
|||
$amount = (float) $request->request->get('amount');
|
||||
$comment = $request->request->get('comment');
|
||||
$action = $request->request->get('action');
|
||||
$delete_lot_if_empty = $request->request->getBoolean('delete_lot_if_empty', false);
|
||||
|
||||
$timestamp = null;
|
||||
$timestamp_str = $request->request->getString('timestamp', '');
|
||||
//Try to parse the timestamp
|
||||
if($timestamp_str !== '') {
|
||||
$timestamp = new DateTime($timestamp_str);
|
||||
}
|
||||
|
||||
//Ensure that the timestamp is not in the future
|
||||
if($timestamp !== null && $timestamp > new DateTime("+20min")) {
|
||||
throw new \LogicException("The timestamp must not be in the future!");
|
||||
}
|
||||
|
||||
//Ensure that the amount is not null or negative
|
||||
if ($amount <= 0) {
|
||||
$this->addFlash('warning', 'part.withdraw.zero_amount');
|
||||
goto err;
|
||||
}
|
||||
|
||||
try {
|
||||
switch ($action) {
|
||||
case "withdraw":
|
||||
case "remove":
|
||||
$this->denyAccessUnlessGranted('withdraw', $partLot);
|
||||
$withdrawAddHelper->withdraw($partLot, $amount, $comment);
|
||||
$withdrawAddHelper->withdraw($partLot, $amount, $comment, $timestamp, $delete_lot_if_empty);
|
||||
break;
|
||||
case "add":
|
||||
$this->denyAccessUnlessGranted('add', $partLot);
|
||||
$withdrawAddHelper->add($partLot, $amount, $comment);
|
||||
$withdrawAddHelper->add($partLot, $amount, $comment, $timestamp);
|
||||
break;
|
||||
case "move":
|
||||
$this->denyAccessUnlessGranted('move', $partLot);
|
||||
$this->denyAccessUnlessGranted('move', $targetLot);
|
||||
$withdrawAddHelper->move($partLot, $targetLot, $amount, $comment);
|
||||
$withdrawAddHelper->move($partLot, $targetLot, $amount, $comment, $timestamp, $delete_lot_if_empty);
|
||||
break;
|
||||
default:
|
||||
throw new \RuntimeException("Unknown action!");
|
||||
|
|
|
|||
71
src/DataFixtures/EDADataFixtures.php
Normal file
71
src/DataFixtures/EDADataFixtures.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\DataFixtures;
|
||||
|
||||
use App\Entity\Parts\Category;
|
||||
use App\Entity\Parts\Footprint;
|
||||
use App\Entity\Parts\Part;
|
||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
|
||||
class EDADataFixtures extends Fixture implements DependentFixtureInterface
|
||||
{
|
||||
|
||||
public function getDependencies(): array
|
||||
{
|
||||
return [PartFixtures::class];
|
||||
}
|
||||
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
//Load elements from DB
|
||||
$category1 = $manager->find(Category::class, 1);
|
||||
$footprint1 = $manager->find(Footprint::class, 1);
|
||||
|
||||
$part1 = $manager->find(Part::class, 1);
|
||||
|
||||
//Put some data into category1 and foorprint1
|
||||
$category1?->getEdaInfo()
|
||||
->setExcludeFromBoard(true)
|
||||
->setKicadSymbol('Category:1')
|
||||
->setReferencePrefix('C')
|
||||
;
|
||||
|
||||
$footprint1?->getEdaInfo()
|
||||
->setKicadFootprint('Footprint:1')
|
||||
;
|
||||
|
||||
//Put some data into part1 (which overrides the data from category1 and footprint1 on part1)
|
||||
$part1?->getEdaInfo()
|
||||
->setExcludeFromSim(false)
|
||||
->setKicadSymbol('Part:1')
|
||||
->setKicadFootprint('Part:1')
|
||||
->setReferencePrefix('P')
|
||||
;
|
||||
|
||||
//Flush the changes
|
||||
$manager->flush();
|
||||
}
|
||||
}
|
||||
|
|
@ -103,6 +103,7 @@ class PartFixtures extends Fixture implements DependentFixtureInterface
|
|||
$partLot2->setComment('Test');
|
||||
$partLot2->setNeedsRefill(true);
|
||||
$partLot2->setStorageLocation($manager->find(StorageLocation::class, 3));
|
||||
$partLot2->setVendorBarcode('lot2_vendor_barcode');
|
||||
$part->addPartLot($partLot2);
|
||||
|
||||
$orderdetail = new Orderdetail();
|
||||
|
|
|
|||
|
|
@ -79,10 +79,7 @@ class LocaleDateTimeColumn extends AbstractColumn
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver): self
|
||||
protected function configureOptions(OptionsResolver $resolver): static
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
|
|
|
|||
|
|
@ -57,10 +57,7 @@ class LogEntryTargetColumn extends AbstractColumn
|
|||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver): self
|
||||
public function configureOptions(OptionsResolver $resolver): static
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
$resolver->setDefault('show_associated', true);
|
||||
|
|
|
|||
|
|
@ -79,10 +79,7 @@ class PartAttachmentsColumn extends AbstractColumn
|
|||
return $tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver): self
|
||||
public function configureOptions(OptionsResolver $resolver): static
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
|
|
|
|||
|
|
@ -28,11 +28,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
|||
|
||||
class RowClassColumn extends AbstractColumn
|
||||
{
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver): self
|
||||
public function configureOptions(OptionsResolver $resolver): static
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
|
|
@ -56,7 +52,7 @@ class RowClassColumn extends AbstractColumn
|
|||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function normalize($value)
|
||||
public function normalize($value): mixed
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,10 +32,7 @@ class SIUnitNumberColumn extends AbstractColumn
|
|||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver): self
|
||||
public function configureOptions(OptionsResolver $resolver): static
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
|
|
|
|||
|
|
@ -30,10 +30,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
|||
*/
|
||||
class SelectColumn extends AbstractColumn
|
||||
{
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver): self
|
||||
public function configureOptions(OptionsResolver $resolver): static
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
|
|
|
|||
|
|
@ -67,8 +67,10 @@ class ErrorDataTable implements DataTableTypeInterface
|
|||
|
||||
//Build the array containing data
|
||||
$data = [];
|
||||
$n = 0;
|
||||
foreach ($options['errors'] as $error) {
|
||||
$data[] = ['error' => $error];
|
||||
$data['error_' . $n] = ['error' => $error];
|
||||
$n++;
|
||||
}
|
||||
|
||||
$dataTable->createAdapter(ArrayAdapter::class, $data);
|
||||
|
|
|
|||
|
|
@ -20,14 +20,17 @@ declare(strict_types=1);
|
|||
* 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\DataTables\Helpers;
|
||||
|
||||
use App\Entity\Parts\StorageLocation;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Services\Attachments\AttachmentURLGenerator;
|
||||
use App\Services\Attachments\PartPreviewGenerator;
|
||||
use App\Services\EntityURLGenerator;
|
||||
use App\Services\Formatters\AmountFormatter;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
|
|
@ -35,8 +38,13 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
|||
*/
|
||||
class PartDataTableHelper
|
||||
{
|
||||
public function __construct(private readonly PartPreviewGenerator $previewGenerator, private readonly AttachmentURLGenerator $attachmentURLGenerator, private readonly EntityURLGenerator $entityURLGenerator, private readonly TranslatorInterface $translator)
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PartPreviewGenerator $previewGenerator,
|
||||
private readonly AttachmentURLGenerator $attachmentURLGenerator,
|
||||
private readonly EntityURLGenerator $entityURLGenerator,
|
||||
private readonly TranslatorInterface $translator,
|
||||
private readonly AmountFormatter $amountFormatter,
|
||||
) {
|
||||
}
|
||||
|
||||
public function renderName(Part $context): string
|
||||
|
|
@ -45,14 +53,16 @@ class PartDataTableHelper
|
|||
|
||||
//Depending on the part status we show a different icon (the later conditions have higher priority)
|
||||
if ($context->isFavorite()) {
|
||||
$icon = sprintf('<i class="fa-solid fa-star fa-fw me-1" title="%s"></i>', $this->translator->trans('part.favorite.badge'));
|
||||
$icon = sprintf('<i class="fa-solid fa-star fa-fw me-1" title="%s"></i>',
|
||||
$this->translator->trans('part.favorite.badge'));
|
||||
}
|
||||
if ($context->isNeedsReview()) {
|
||||
$icon = sprintf('<i class="fa-solid fa-ambulance fa-fw me-1" title="%s"></i>', $this->translator->trans('part.needs_review.badge'));
|
||||
$icon = sprintf('<i class="fa-solid fa-ambulance fa-fw me-1" title="%s"></i>',
|
||||
$this->translator->trans('part.needs_review.badge'));
|
||||
}
|
||||
if ($context->getBuiltProject() instanceof Project) {
|
||||
$icon = sprintf('<i class="fa-solid fa-box-archive fa-fw me-1" title="%s"></i>',
|
||||
$this->translator->trans('part.info.projectBuildPart.hint') . ': ' . $context->getBuiltProject()->getName());
|
||||
$this->translator->trans('part.info.projectBuildPart.hint').': '.$context->getBuiltProject()->getName());
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -85,4 +95,62 @@ class PartDataTableHelper
|
|||
$title
|
||||
);
|
||||
}
|
||||
|
||||
public function renderStorageLocations(Part $context): string
|
||||
{
|
||||
$tmp = [];
|
||||
foreach ($context->getPartLots() as $lot) {
|
||||
//Ignore lots without storelocation
|
||||
if (!$lot->getStorageLocation() instanceof StorageLocation) {
|
||||
continue;
|
||||
}
|
||||
$tmp[] = sprintf(
|
||||
'<a href="%s" title="%s">%s</a>',
|
||||
$this->entityURLGenerator->listPartsURL($lot->getStorageLocation()),
|
||||
htmlspecialchars($lot->getStorageLocation()->getFullPath()),
|
||||
htmlspecialchars($lot->getStorageLocation()->getName())
|
||||
);
|
||||
}
|
||||
|
||||
return implode('<br>', $tmp);
|
||||
}
|
||||
|
||||
public function renderAmount(Part $context): string
|
||||
{
|
||||
$amount = $context->getAmountSum();
|
||||
$expiredAmount = $context->getExpiredAmountSum();
|
||||
|
||||
$ret = '';
|
||||
|
||||
if ($context->isAmountUnknown()) {
|
||||
//When all amounts are unknown, we show a question mark
|
||||
if ($amount === 0.0) {
|
||||
$ret .= sprintf('<b class="text-primary" title="%s">?</b>',
|
||||
$this->translator->trans('part_lots.instock_unknown'));
|
||||
} else { //Otherwise mark it with greater equal and the (known) amount
|
||||
$ret .= sprintf('<b class="text-primary" title="%s">≥</b>',
|
||||
$this->translator->trans('part_lots.instock_unknown')
|
||||
);
|
||||
$ret .= htmlspecialchars($this->amountFormatter->format($amount, $context->getPartUnit()));
|
||||
}
|
||||
} else {
|
||||
$ret .= htmlspecialchars($this->amountFormatter->format($amount, $context->getPartUnit()));
|
||||
}
|
||||
|
||||
//If we have expired lots, we show them in parentheses behind
|
||||
if ($expiredAmount > 0) {
|
||||
$ret .= sprintf(' <span title="%s" class="text-muted">(+%s)</span>',
|
||||
$this->translator->trans('part_lots.is_expired'),
|
||||
htmlspecialchars($this->amountFormatter->format($expiredAmount, $context->getPartUnit())));
|
||||
}
|
||||
|
||||
//When the amount is below the minimum amount, we highlight the number red
|
||||
if ($context->isNotEnoughInstock()) {
|
||||
$ret = sprintf('<b class="text-danger" title="%s">%s</b>',
|
||||
$this->translator->trans('part.info.amount.less_than_desired'),
|
||||
$ret);
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ class LogDataTable implements DataTableTypeInterface
|
|||
if ($context instanceof PartStockChangedLogEntry) {
|
||||
$text .= sprintf(
|
||||
' (<i>%s</i>)',
|
||||
$this->translator->trans('log.part_stock_changed.' . $context->getInstockChangeType()->toExtraShortType())
|
||||
$this->translator->trans($context->getInstockChangeType()->toTranslationKey())
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -139,63 +139,11 @@ final class PartsDataTable implements DataTableTypeInterface
|
|||
->add('storelocation', TextColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.storeLocations'),
|
||||
'orderField' => 'storelocations.name',
|
||||
'render' => function ($value, Part $context): string {
|
||||
$tmp = [];
|
||||
foreach ($context->getPartLots() as $lot) {
|
||||
//Ignore lots without storelocation
|
||||
if (!$lot->getStorageLocation() instanceof StorageLocation) {
|
||||
continue;
|
||||
}
|
||||
$tmp[] = sprintf(
|
||||
'<a href="%s" title="%s">%s</a>',
|
||||
$this->urlGenerator->listPartsURL($lot->getStorageLocation()),
|
||||
htmlspecialchars($lot->getStorageLocation()->getFullPath()),
|
||||
htmlspecialchars($lot->getStorageLocation()->getName())
|
||||
);
|
||||
}
|
||||
|
||||
return implode('<br>', $tmp);
|
||||
},
|
||||
'render' => fn ($value, Part $context) => $this->partDataTableHelper->renderStorageLocations($context),
|
||||
], alias: 'storage_location')
|
||||
->add('amount', TextColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.amount'),
|
||||
'render' => function ($value, Part $context) {
|
||||
$amount = $context->getAmountSum();
|
||||
$expiredAmount = $context->getExpiredAmountSum();
|
||||
|
||||
$ret = '';
|
||||
|
||||
if ($context->isAmountUnknown()) {
|
||||
//When all amounts are unknown, we show a question mark
|
||||
if ($amount === 0.0) {
|
||||
$ret .= sprintf('<b class="text-primary" title="%s">?</b>',
|
||||
$this->translator->trans('part_lots.instock_unknown'));
|
||||
} else { //Otherwise mark it with greater equal and the (known) amount
|
||||
$ret .= sprintf('<b class="text-primary" title="%s">≥</b>',
|
||||
$this->translator->trans('part_lots.instock_unknown')
|
||||
);
|
||||
$ret .= htmlspecialchars($this->amountFormatter->format($amount, $context->getPartUnit()));
|
||||
}
|
||||
} else {
|
||||
$ret .= htmlspecialchars($this->amountFormatter->format($amount, $context->getPartUnit()));
|
||||
}
|
||||
|
||||
//If we have expired lots, we show them in parentheses behind
|
||||
if ($expiredAmount > 0) {
|
||||
$ret .= sprintf(' <span title="%s" class="text-muted">(+%s)</span>',
|
||||
$this->translator->trans('part_lots.is_expired'),
|
||||
htmlspecialchars($this->amountFormatter->format($expiredAmount, $context->getPartUnit())));
|
||||
}
|
||||
|
||||
//When the amount is below the minimum amount, we highlight the number red
|
||||
if ($context->isNotEnoughInstock()) {
|
||||
$ret = sprintf('<b class="text-danger" title="%s">%s</b>',
|
||||
$this->translator->trans('part.info.amount.less_than_desired'),
|
||||
$ret);
|
||||
}
|
||||
|
||||
return $ret;
|
||||
},
|
||||
'render' => fn ($value, Part $context) => $this->partDataTableHelper->renderAmount($context),
|
||||
'orderField' => 'amountSum'
|
||||
])
|
||||
->add('minamount', TextColumn::class, [
|
||||
|
|
|
|||
|
|
@ -151,6 +151,28 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
|
|||
},
|
||||
])
|
||||
|
||||
->add('instockAmount', TextColumn::class, [
|
||||
'label' => 'project.bom.instockAmount',
|
||||
'visible' => false,
|
||||
'render' => function ($value, ProjectBOMEntry $context) {
|
||||
if ($context->getPart()) {
|
||||
return $this->partDataTableHelper->renderAmount($context->getPart());
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
])
|
||||
->add('storageLocations', TextColumn::class, [
|
||||
'label' => 'part.table.storeLocations',
|
||||
'visible' => false,
|
||||
'render' => function ($value, ProjectBOMEntry $context) {
|
||||
if ($context->getPart()) {
|
||||
return $this->partDataTableHelper->renderStorageLocations($context->getPart());
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
])
|
||||
|
||||
->add('addedDate', LocaleDateTimeColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.addedDate'),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
<?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\Doctrine\Middleware;
|
||||
|
||||
use Composer\CaBundle\CaBundle;
|
||||
use Doctrine\DBAL\Driver;
|
||||
use Doctrine\DBAL\Driver\Connection;
|
||||
use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;
|
||||
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
|
||||
|
||||
/**
|
||||
* This middleware sets SSL options for MySQL connections
|
||||
*/
|
||||
class MySQLSSLConnectionMiddlewareDriver extends AbstractDriverMiddleware
|
||||
{
|
||||
public function __construct(Driver $wrappedDriver, private readonly bool $enabled, private readonly bool $verify = true)
|
||||
{
|
||||
parent::__construct($wrappedDriver);
|
||||
}
|
||||
|
||||
public function connect(array $params): Connection
|
||||
{
|
||||
//Only set this on MySQL connections, as other databases don't support this parameter
|
||||
if($this->enabled && $this->getDatabasePlatform() instanceof AbstractMySQLPlatform) {
|
||||
$params['driverOptions'][\PDO::MYSQL_ATTR_SSL_CA] = CaBundle::getSystemCaRootBundlePath();
|
||||
$params['driverOptions'][\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = $this->verify;
|
||||
}
|
||||
|
||||
return parent::connect($params);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?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\Doctrine\Middleware;
|
||||
|
||||
use Doctrine\DBAL\Driver;
|
||||
use Doctrine\DBAL\Driver\Middleware;
|
||||
|
||||
class MySQLSSLConnectionMiddlewareWrapper implements Middleware
|
||||
{
|
||||
public function __construct(private readonly bool $enabled, private readonly bool $verify = true)
|
||||
{
|
||||
}
|
||||
|
||||
public function wrap(Driver $driver): Driver
|
||||
{
|
||||
return new MySQLSSLConnectionMiddlewareDriver($driver, $this->enabled, $this->verify);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,8 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
* 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
|
||||
|
|
@ -20,29 +17,31 @@ declare(strict_types=1);
|
|||
* 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\Doctrine;
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Doctrine\Middleware;
|
||||
|
||||
use App\Exceptions\InvalidRegexException;
|
||||
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
||||
use Doctrine\Bundle\DoctrineBundle\EventSubscriber\EventSubscriberInterface;
|
||||
use Doctrine\DBAL\Event\ConnectionEventArgs;
|
||||
use Doctrine\DBAL\Events;
|
||||
use Doctrine\DBAL\Driver\Connection;
|
||||
use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;
|
||||
use Doctrine\DBAL\Platforms\SqlitePlatform;
|
||||
|
||||
/**
|
||||
* This subscriber is used to add the regexp operator to the SQLite platform.
|
||||
* This middleware is used to add the regexp operator to the SQLite platform.
|
||||
* As a PHP callback is called for every entry to compare it is most likely much slower than using regex on MySQL.
|
||||
* But as regex is not often used, this should be fine for most use cases, also it is almost impossible to implement a better solution.
|
||||
*/
|
||||
#[AsDoctrineListener(Events::postConnect)]
|
||||
class SQLiteRegexExtension
|
||||
class SQLiteRegexExtensionMiddlewareDriver extends AbstractDriverMiddleware
|
||||
{
|
||||
public function postConnect(ConnectionEventArgs $eventArgs): void
|
||||
public function connect(#[\SensitiveParameter] array $params): Connection
|
||||
{
|
||||
$connection = $eventArgs->getConnection();
|
||||
//Do connect process first
|
||||
$connection = parent::connect($params); // TODO: Change the autogenerated stub
|
||||
|
||||
//We only execute this on SQLite databases
|
||||
if ($connection->getDatabasePlatform() instanceof SqlitePlatform) {
|
||||
//Then add the functions if we are on SQLite
|
||||
if ($this->getDatabasePlatform() instanceof SqlitePlatform) {
|
||||
$native_connection = $connection->getNativeConnection();
|
||||
|
||||
//Ensure that the function really exists on the connection, as it is marked as experimental according to PHP documentation
|
||||
|
|
@ -52,6 +51,9 @@ class SQLiteRegexExtension
|
|||
$native_connection->sqliteCreateFunction('FIELD2', self::field2(...), 2, \PDO::SQLITE_DETERMINISTIC);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -60,8 +62,12 @@ class SQLiteRegexExtension
|
|||
* @param string $value
|
||||
* @return int
|
||||
*/
|
||||
final public static function regexp(string $pattern, string $value): int
|
||||
final public static function regexp(string $pattern, ?string $value): int
|
||||
{
|
||||
if ($value === null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
return (mb_ereg($pattern, $value)) ? 1 : 0;
|
||||
|
||||
|
|
@ -107,4 +113,4 @@ class SQLiteRegexExtension
|
|||
|
||||
return $index + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?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\Doctrine\Middleware;
|
||||
|
||||
use Doctrine\DBAL\Driver;
|
||||
use Doctrine\DBAL\Driver\Middleware;
|
||||
|
||||
class SQLiteRegexExtensionMiddlewareWrapper implements Middleware
|
||||
{
|
||||
public function wrap(Driver $driver): Driver
|
||||
{
|
||||
return new SQLiteRegexExtensionMiddlewareDriver($driver);
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ declare(strict_types=1);
|
|||
* 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\Doctrine\SetSQLMode;
|
||||
namespace App\Doctrine\Middleware;
|
||||
|
||||
use Doctrine\DBAL\Driver\Connection;
|
||||
use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;
|
||||
|
|
@ -37,7 +37,7 @@ class SetSQLModeMiddlewareDriver extends AbstractDriverMiddleware
|
|||
//Only set this on MySQL connections, as other databases don't support this parameter
|
||||
if($this->getDatabasePlatform() instanceof AbstractMySQLPlatform) {
|
||||
//1002 is \PDO::MYSQL_ATTR_INIT_COMMAND constant value
|
||||
$params['driverOptions'][1002] = 'SET SESSION sql_mode=(SELECT REPLACE(@@sql_mode, \'ONLY_FULL_GROUP_BY\', \'\'))';
|
||||
$params['driverOptions'][\PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET SESSION sql_mode=(SELECT REPLACE(@@sql_mode, \'ONLY_FULL_GROUP_BY\', \'\'))';
|
||||
}
|
||||
|
||||
return parent::connect($params);
|
||||
|
|
@ -20,7 +20,7 @@ declare(strict_types=1);
|
|||
* 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\Doctrine\SetSQLMode;
|
||||
namespace App\Doctrine\Middleware;
|
||||
|
||||
use Doctrine\DBAL\Driver;
|
||||
use Doctrine\DBAL\Driver\Middleware;
|
||||
|
|
@ -30,7 +30,6 @@ use Doctrine\DBAL\Driver\Middleware;
|
|||
*/
|
||||
class SetSQLModeMiddlewareWrapper implements Middleware
|
||||
{
|
||||
|
||||
public function wrap(Driver $driver): Driver
|
||||
{
|
||||
return new SetSQLModeMiddlewareDriver($driver);
|
||||
53
src/Doctrine/Purger/DoNotUsePurgerFactory.php
Normal file
53
src/Doctrine/Purger/DoNotUsePurgerFactory.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\Doctrine\Purger;
|
||||
|
||||
use Doctrine\Bundle\FixturesBundle\Purger\PurgerFactory;
|
||||
use Doctrine\Common\DataFixtures\Purger\ORMPurgerInterface;
|
||||
use Doctrine\Common\DataFixtures\Purger\PurgerInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
class DoNotUsePurgerFactory implements PurgerFactory
|
||||
{
|
||||
|
||||
public function createForEntityManager(
|
||||
?string $emName,
|
||||
EntityManagerInterface $em,
|
||||
array $excluded = [],
|
||||
bool $purgeWithTruncate = false
|
||||
): PurgerInterface {
|
||||
return new class() implements ORMPurgerInterface {
|
||||
|
||||
public function purge(): void
|
||||
{
|
||||
throw new \LogicException('Do not use doctrine:fixtures:load directly. Use partdb:fixtures:load instead!');
|
||||
}
|
||||
|
||||
public function setEntityManager(EntityManagerInterface $em)
|
||||
{
|
||||
// TODO: Implement setEntityManager() method.
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -190,7 +190,6 @@ class ResetAutoIncrementORMPurger implements PurgerInterface, ORMPurgerInterface
|
|||
|
||||
//Reseting autoincrement is only supported on MySQL platforms
|
||||
if ($platform instanceof AbstractMySQLPlatform ) { //|| $platform instanceof SqlitePlatform) {
|
||||
$connection->beginTransaction();
|
||||
$connection->executeQuery($this->getResetAutoIncrementSQL($tbl, $platform));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,20 @@ class TinyIntType extends Type
|
|||
return 'tinyint';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @param T $value
|
||||
*
|
||||
* @return (T is null ? null : int)
|
||||
*
|
||||
* @template T
|
||||
*/
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform): ?int
|
||||
{
|
||||
return $value === null ? null : (int) $value;
|
||||
}
|
||||
|
||||
public function requiresSQLCommentHint(AbstractPlatform $platform): bool
|
||||
{
|
||||
//We use the comment, so that doctrine migrations can properly detect, that nothing has changed and no migration is needed.
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ use LogicException;
|
|||
operations: [
|
||||
new Get(security: 'is_granted("read", object)'),
|
||||
new GetCollection(security: 'is_granted("@attachments.list_attachments")'),
|
||||
new Post(securityPostDenormalize: 'is_granted("create", object)'),
|
||||
//new Post(securityPostDenormalize: 'is_granted("create", object)'),
|
||||
new Patch(security: 'is_granted("edit", object)'),
|
||||
new Delete(security: 'is_granted("delete", object)'),
|
||||
],
|
||||
|
|
@ -153,7 +153,7 @@ abstract class Attachment extends AbstractNamedDBElement
|
|||
#[ORM\ManyToOne(targetEntity: AttachmentType::class, inversedBy: 'attachments_with_type')]
|
||||
#[ORM\JoinColumn(name: 'type_id', nullable: false)]
|
||||
#[Selectable()]
|
||||
#[Groups(['attachment:read', 'attachment_write'])]
|
||||
#[Groups(['attachment:read', 'attachment:write'])]
|
||||
protected ?AttachmentType $attachment_type = null;
|
||||
|
||||
#[Groups(['attachment:read'])]
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
new Delete(security: 'is_granted("delete", object)'),
|
||||
],
|
||||
normalizationContext: ['groups' => ['attachment_type:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
|
||||
denormalizationContext: ['groups' => ['attachment_type:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'],
|
||||
denormalizationContext: ['groups' => ['attachment_type:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'],
|
||||
)]
|
||||
#[ApiResource(
|
||||
uriTemplate: '/attachment_types/{id}/children.{_format}',
|
||||
|
|
|
|||
132
src/Entity/EDA/EDACategoryInfo.php
Normal file
132
src/Entity/EDA/EDACategoryInfo.php
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
<?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\EDA;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Embeddable;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
#[Embeddable]
|
||||
class EDACategoryInfo
|
||||
{
|
||||
/**
|
||||
* @var string|null The reference prefix of the Part in the schematic. E.g. "R" for resistors, or "C" for capacitors.
|
||||
*/
|
||||
#[Column(type: Types::STRING, nullable: true)]
|
||||
#[Groups(['full', 'category:read', 'category:write'])]
|
||||
private ?string $reference_prefix = null;
|
||||
|
||||
/** @var bool|null Visibility of this part to EDA software in trinary logic. True=Visible, False=Invisible, Null=Auto */
|
||||
#[Column(name: 'invisible', type: Types::BOOLEAN, nullable: true)] //TODO: Rename column to visibility
|
||||
#[Groups(['full', 'category:read', 'category:write'])]
|
||||
private ?bool $visibility = null;
|
||||
|
||||
/** @var bool|null If this is set to true, then this part will be excluded from the BOM */
|
||||
#[Column(type: Types::BOOLEAN, nullable: true)]
|
||||
#[Groups(['full', 'category:read', 'category:write'])]
|
||||
private ?bool $exclude_from_bom = null;
|
||||
|
||||
/** @var bool|null If this is set to true, then this part will be excluded from the board/the PCB */
|
||||
#[Column(type: Types::BOOLEAN, nullable: true)]
|
||||
#[Groups(['full', 'category:read', 'category:write'])]
|
||||
private ?bool $exclude_from_board = null;
|
||||
|
||||
/** @var bool|null If this is set to true, then this part will be excluded in the simulation */
|
||||
#[Column(type: Types::BOOLEAN, nullable: true)]
|
||||
#[Groups(['full', 'category:read', 'category:write'])]
|
||||
private ?bool $exclude_from_sim = true;
|
||||
|
||||
/** @var string|null The KiCAD schematic symbol, which should be used (the path to the library) */
|
||||
#[Column(type: Types::STRING, nullable: true)]
|
||||
#[Groups(['full', 'category:read', 'category:write'])]
|
||||
private ?string $kicad_symbol = null;
|
||||
|
||||
public function getReferencePrefix(): ?string
|
||||
{
|
||||
return $this->reference_prefix;
|
||||
}
|
||||
|
||||
public function setReferencePrefix(?string $reference_prefix): EDACategoryInfo
|
||||
{
|
||||
$this->reference_prefix = $reference_prefix;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getVisibility(): ?bool
|
||||
{
|
||||
return $this->visibility;
|
||||
}
|
||||
|
||||
public function setVisibility(?bool $visibility): EDACategoryInfo
|
||||
{
|
||||
$this->visibility = $visibility;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getExcludeFromBom(): ?bool
|
||||
{
|
||||
return $this->exclude_from_bom;
|
||||
}
|
||||
|
||||
public function setExcludeFromBom(?bool $exclude_from_bom): EDACategoryInfo
|
||||
{
|
||||
$this->exclude_from_bom = $exclude_from_bom;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getExcludeFromBoard(): ?bool
|
||||
{
|
||||
return $this->exclude_from_board;
|
||||
}
|
||||
|
||||
public function setExcludeFromBoard(?bool $exclude_from_board): EDACategoryInfo
|
||||
{
|
||||
$this->exclude_from_board = $exclude_from_board;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getExcludeFromSim(): ?bool
|
||||
{
|
||||
return $this->exclude_from_sim;
|
||||
}
|
||||
|
||||
public function setExcludeFromSim(?bool $exclude_from_sim): EDACategoryInfo
|
||||
{
|
||||
$this->exclude_from_sim = $exclude_from_sim;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getKicadSymbol(): ?string
|
||||
{
|
||||
return $this->kicad_symbol;
|
||||
}
|
||||
|
||||
public function setKicadSymbol(?string $kicad_symbol): EDACategoryInfo
|
||||
{
|
||||
$this->kicad_symbol = $kicad_symbol;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
49
src/Entity/EDA/EDAFootprintInfo.php
Normal file
49
src/Entity/EDA/EDAFootprintInfo.php
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<?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\EDA;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Embeddable;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
#[Embeddable]
|
||||
class EDAFootprintInfo
|
||||
{
|
||||
/** @var string|null The KiCAD footprint, which should be used (the path to the library) */
|
||||
#[Column(type: Types::STRING, nullable: true)]
|
||||
#[Groups(['full', 'footprint:read', 'footprint:write'])]
|
||||
private ?string $kicad_footprint = null;
|
||||
|
||||
public function getKicadFootprint(): ?string
|
||||
{
|
||||
return $this->kicad_footprint;
|
||||
}
|
||||
|
||||
public function setKicadFootprint(?string $kicad_footprint): EDAFootprintInfo
|
||||
{
|
||||
$this->kicad_footprint = $kicad_footprint;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
170
src/Entity/EDA/EDAPartInfo.php
Normal file
170
src/Entity/EDA/EDAPartInfo.php
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
<?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\EDA;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Embeddable;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
#[Embeddable]
|
||||
class EDAPartInfo
|
||||
{
|
||||
/**
|
||||
* @var string|null The reference prefix of the Part in the schematic. E.g. "R" for resistors, or "C" for capacitors.
|
||||
*/
|
||||
#[Column(type: Types::STRING, nullable: true)]
|
||||
#[Groups(['full', 'eda_info:read', 'eda_info:write'])]
|
||||
private ?string $reference_prefix = null;
|
||||
|
||||
/** @var string|null The value, which should be shown together with the part (e.g. 470 for a 470 Ohm resistor) */
|
||||
#[Column(type: Types::STRING, nullable: true)]
|
||||
#[Groups(['full', 'eda_info:read', 'eda_info:write'])]
|
||||
private ?string $value = null;
|
||||
|
||||
/** @var bool|null Visibility of this part to EDA software in trinary logic. True=Visible, False=Invisible, Null=Auto */
|
||||
#[Column(name: 'invisible', type: Types::BOOLEAN, nullable: true)] //TODO: Rename column to visibility
|
||||
#[Groups(['full', 'eda_info:read', 'eda_info:write'])]
|
||||
private ?bool $visibility = null;
|
||||
|
||||
/** @var bool|null If this is set to true, then this part will be excluded from the BOM */
|
||||
#[Column(type: Types::BOOLEAN, nullable: true)]
|
||||
#[Groups(['full', 'eda_info:read', 'eda_info:write'])]
|
||||
private ?bool $exclude_from_bom = null;
|
||||
|
||||
/** @var bool|null If this is set to true, then this part will be excluded from the board/the PCB */
|
||||
#[Column(type: Types::BOOLEAN, nullable: true)]
|
||||
#[Groups(['full', 'eda_info:read', 'eda_info:write'])]
|
||||
private ?bool $exclude_from_board = null;
|
||||
|
||||
/** @var bool|null If this is set to true, then this part will be excluded in the simulation */
|
||||
#[Column(type: Types::BOOLEAN, nullable: true)]
|
||||
#[Groups(['full', 'eda_info:read', 'eda_info:write'])]
|
||||
private ?bool $exclude_from_sim = null;
|
||||
|
||||
/** @var string|null The KiCAD schematic symbol, which should be used (the path to the library) */
|
||||
#[Column(type: Types::STRING, nullable: true)]
|
||||
#[Groups(['full', 'eda_info:read', 'eda_info:write'])]
|
||||
private ?string $kicad_symbol = null;
|
||||
|
||||
/** @var string|null The KiCAD footprint, which should be used (the path to the library) */
|
||||
#[Column(type: Types::STRING, nullable: true)]
|
||||
#[Groups(['full', 'eda_info:read', 'eda_info:write'])]
|
||||
private ?string $kicad_footprint = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function getReferencePrefix(): ?string
|
||||
{
|
||||
return $this->reference_prefix;
|
||||
}
|
||||
|
||||
public function setReferencePrefix(?string $reference_prefix): EDAPartInfo
|
||||
{
|
||||
$this->reference_prefix = $reference_prefix;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getValue(): ?string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function setValue(?string $value): EDAPartInfo
|
||||
{
|
||||
$this->value = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getVisibility(): ?bool
|
||||
{
|
||||
return $this->visibility;
|
||||
}
|
||||
|
||||
public function setVisibility(?bool $visibility): EDAPartInfo
|
||||
{
|
||||
$this->visibility = $visibility;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getExcludeFromBom(): ?bool
|
||||
{
|
||||
return $this->exclude_from_bom;
|
||||
}
|
||||
|
||||
public function setExcludeFromBom(?bool $exclude_from_bom): EDAPartInfo
|
||||
{
|
||||
$this->exclude_from_bom = $exclude_from_bom;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getExcludeFromBoard(): ?bool
|
||||
{
|
||||
return $this->exclude_from_board;
|
||||
}
|
||||
|
||||
public function setExcludeFromBoard(?bool $exclude_from_board): EDAPartInfo
|
||||
{
|
||||
$this->exclude_from_board = $exclude_from_board;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getExcludeFromSim(): ?bool
|
||||
{
|
||||
return $this->exclude_from_sim;
|
||||
}
|
||||
|
||||
public function setExcludeFromSim(?bool $exclude_from_sim): EDAPartInfo
|
||||
{
|
||||
$this->exclude_from_sim = $exclude_from_sim;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getKicadSymbol(): ?string
|
||||
{
|
||||
return $this->kicad_symbol;
|
||||
}
|
||||
|
||||
public function setKicadSymbol(?string $kicad_symbol): EDAPartInfo
|
||||
{
|
||||
$this->kicad_symbol = $kicad_symbol;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getKicadFootprint(): ?string
|
||||
{
|
||||
return $this->kicad_footprint;
|
||||
}
|
||||
|
||||
public function setKicadFootprint(?string $kicad_footprint): EDAPartInfo
|
||||
{
|
||||
$this->kicad_footprint = $kicad_footprint;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -29,6 +29,7 @@ use App\Entity\Parts\Footprint;
|
|||
use App\Entity\Parts\Manufacturer;
|
||||
use App\Entity\Parts\MeasurementUnit;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\Parts\PartAssociation;
|
||||
use App\Entity\Parts\PartLot;
|
||||
use App\Entity\Parts\StorageLocation;
|
||||
use App\Entity\Parts\Supplier;
|
||||
|
|
@ -63,6 +64,8 @@ enum LogTargetType: int
|
|||
case PARAMETER = 18;
|
||||
case LABEL_PROFILE = 19;
|
||||
|
||||
case PART_ASSOCIATION = 20;
|
||||
|
||||
/**
|
||||
* Returns the class name of the target type or null if the target type is NONE.
|
||||
* @return string|null
|
||||
|
|
@ -90,6 +93,7 @@ enum LogTargetType: int
|
|||
self::MEASUREMENT_UNIT => MeasurementUnit::class,
|
||||
self::PARAMETER => AbstractParameter::class,
|
||||
self::LABEL_PROFILE => LabelProfile::class,
|
||||
self::PART_ASSOCIATION => PartAssociation::class,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,11 @@ enum PartStockChangeType: string
|
|||
};
|
||||
}
|
||||
|
||||
public function toTranslationKey(): string
|
||||
{
|
||||
return 'log.part_stock_changed.' . $this->value;
|
||||
}
|
||||
|
||||
public static function fromExtraShortType(string $value): self
|
||||
{
|
||||
return match ($value) {
|
||||
|
|
|
|||
|
|
@ -41,8 +41,10 @@ class PartStockChangedLogEntry extends AbstractLogEntry
|
|||
* @param float $new_total_part_instock The new total instock of the part.
|
||||
* @param string $comment The comment associated with the change.
|
||||
* @param PartLot|null $move_to_target The target lot if the type is TYPE_MOVE.
|
||||
* @param \DateTimeInterface|null $action_timestamp The optional timestamp, where the action happened. Useful if the action happened in the past, and the log entry is created afterwards.
|
||||
*/
|
||||
protected function __construct(PartStockChangeType $type, PartLot $lot, float $old_stock, float $new_stock, float $new_total_part_instock, string $comment, ?PartLot $move_to_target = null)
|
||||
protected function __construct(PartStockChangeType $type, PartLot $lot, float $old_stock, float $new_stock, float $new_total_part_instock, string $comment, ?PartLot $move_to_target = null,
|
||||
?\DateTimeInterface $action_timestamp = null)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
|
|
@ -62,6 +64,11 @@ class PartStockChangedLogEntry extends AbstractLogEntry
|
|||
$this->extra['c'] = mb_strimwidth($comment, 0, self::COMMENT_MAX_LENGTH, '...');
|
||||
}
|
||||
|
||||
if ($action_timestamp instanceof \DateTimeInterface) {
|
||||
//The action timestamp is saved as an ISO 8601 string
|
||||
$this->extra['a'] = $action_timestamp->format(\DateTimeInterface::ATOM);
|
||||
}
|
||||
|
||||
if ($move_to_target instanceof PartLot) {
|
||||
if ($type !== PartStockChangeType::MOVE) {
|
||||
throw new \InvalidArgumentException('The move_to_target parameter can only be set if the type is "move"!');
|
||||
|
|
@ -78,11 +85,12 @@ class PartStockChangedLogEntry extends AbstractLogEntry
|
|||
* @param float $new_stock The new stock of the lot.
|
||||
* @param float $new_total_part_instock The new total instock of the part.
|
||||
* @param string $comment The comment associated with the change.
|
||||
* @param \DateTimeInterface|null $action_timestamp The optional timestamp, where the action happened. Useful if the action happened in the past, and the log entry is created afterwards.
|
||||
* @return self
|
||||
*/
|
||||
public static function add(PartLot $lot, float $old_stock, float $new_stock, float $new_total_part_instock, string $comment): self
|
||||
public static function add(PartLot $lot, float $old_stock, float $new_stock, float $new_total_part_instock, string $comment, ?\DateTimeInterface $action_timestamp = null): self
|
||||
{
|
||||
return new self(PartStockChangeType::ADD, $lot, $old_stock, $new_stock, $new_total_part_instock, $comment);
|
||||
return new self(PartStockChangeType::ADD, $lot, $old_stock, $new_stock, $new_total_part_instock, $comment, action_timestamp: $action_timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -92,11 +100,12 @@ class PartStockChangedLogEntry extends AbstractLogEntry
|
|||
* @param float $new_stock The new stock of the lot.
|
||||
* @param float $new_total_part_instock The new total instock of the part.
|
||||
* @param string $comment The comment associated with the change.
|
||||
* @param \DateTimeInterface|null $action_timestamp The optional timestamp, where the action happened. Useful if the action happened in the past, and the log entry is created afterwards.
|
||||
* @return self
|
||||
*/
|
||||
public static function withdraw(PartLot $lot, float $old_stock, float $new_stock, float $new_total_part_instock, string $comment): self
|
||||
public static function withdraw(PartLot $lot, float $old_stock, float $new_stock, float $new_total_part_instock, string $comment, ?\DateTimeInterface $action_timestamp = null): self
|
||||
{
|
||||
return new self(PartStockChangeType::WITHDRAW, $lot, $old_stock, $new_stock, $new_total_part_instock, $comment);
|
||||
return new self(PartStockChangeType::WITHDRAW, $lot, $old_stock, $new_stock, $new_total_part_instock, $comment, action_timestamp: $action_timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -107,10 +116,12 @@ class PartStockChangedLogEntry extends AbstractLogEntry
|
|||
* @param float $new_total_part_instock The new total instock of the part.
|
||||
* @param string $comment The comment associated with the change.
|
||||
* @param PartLot $move_to_target The target lot.
|
||||
* @param \DateTimeInterface|null $action_timestamp The optional timestamp, where the action happened. Useful if the action happened in the past, and the log entry is created afterwards.
|
||||
* @return self
|
||||
*/
|
||||
public static function move(PartLot $lot, float $old_stock, float $new_stock, float $new_total_part_instock, string $comment, PartLot $move_to_target): self
|
||||
public static function move(PartLot $lot, float $old_stock, float $new_stock, float $new_total_part_instock, string $comment, PartLot $move_to_target, ?\DateTimeInterface $action_timestamp = null): self
|
||||
{
|
||||
return new self(PartStockChangeType::MOVE, $lot, $old_stock, $new_stock, $new_total_part_instock, $comment, $move_to_target);
|
||||
return new self(PartStockChangeType::MOVE, $lot, $old_stock, $new_stock, $new_total_part_instock, $comment, $move_to_target, action_timestamp: $action_timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -169,4 +180,18 @@ class PartStockChangedLogEntry extends AbstractLogEntry
|
|||
{
|
||||
return $this->extra['m'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the timestamp when this action was performed and not when the log entry was created.
|
||||
* This is useful if the action happened in the past, and the log entry is created afterwards.
|
||||
* If the timestamp is not set, null is returned.
|
||||
* @return \DateTimeInterface|null
|
||||
*/
|
||||
public function getActionTimestamp(): ?\DateTimeInterface
|
||||
{
|
||||
if (!empty($this->extra['a'])) {
|
||||
return \DateTimeImmutable::createFromFormat(\DateTimeInterface::ATOM, $this->extra['a']);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ use function sprintf;
|
|||
shortName: 'Parameter',
|
||||
operations: [
|
||||
new Get(security: 'is_granted("read", object)'),
|
||||
new Post(securityPostDenormalize: 'is_granted("create", object)'),
|
||||
//new Post(securityPostDenormalize: 'is_granted("create", object)'),
|
||||
new Patch(security: 'is_granted("edit", object)'),
|
||||
new Delete(security: 'is_granted("delete", object)'),
|
||||
],
|
||||
|
|
|
|||
46
src/Entity/Parts/AssociationType.php
Normal file
46
src/Entity/Parts/AssociationType.php
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<?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;
|
||||
|
||||
/**
|
||||
* The values of this enums are used to describe how two parts are associated with each other.
|
||||
*/
|
||||
enum AssociationType: int
|
||||
{
|
||||
/** A user definable association type, which can be described in the comment field */
|
||||
case OTHER = 0;
|
||||
/** The owning part is compatible with the other part */
|
||||
case COMPATIBLE = 1;
|
||||
/** The owning part supersedes the other part (owner is newer version) */
|
||||
case SUPERSEDES = 2;
|
||||
|
||||
/**
|
||||
* Returns the translation key for this association type.
|
||||
* @return string
|
||||
*/
|
||||
public function getTranslationKey(): string
|
||||
{
|
||||
return 'part_association.type.' . strtolower($this->name);
|
||||
}
|
||||
}
|
||||
|
|
@ -38,6 +38,8 @@ use ApiPlatform\OpenApi\Model\Operation;
|
|||
use ApiPlatform\Serializer\Filter\PropertyFilter;
|
||||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\EDA\EDACategoryInfo;
|
||||
use App\Entity\EDA\EDAPartInfo;
|
||||
use App\Repository\Parts\CategoryRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
|
@ -47,6 +49,7 @@ use App\Entity\Base\AbstractStructuralDBElement;
|
|||
use App\Entity\Parameters\CategoryParameter;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
|
|
@ -68,7 +71,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
new Delete(security: 'is_granted("delete", object)'),
|
||||
],
|
||||
normalizationContext: ['groups' => ['category:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
|
||||
denormalizationContext: ['groups' => ['category:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'],
|
||||
denormalizationContext: ['groups' => ['category:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'],
|
||||
)]
|
||||
#[ApiResource(
|
||||
uriTemplate: '/categories/{id}/children.{_format}',
|
||||
|
|
@ -185,6 +188,19 @@ class Category extends AbstractPartsContainingDBElement
|
|||
#[Groups(['category:read'])]
|
||||
protected ?\DateTimeInterface $lastModified = null;
|
||||
|
||||
#[Assert\Valid]
|
||||
#[ORM\Embedded(class: EDACategoryInfo::class)]
|
||||
#[Groups(['full', 'category:read', 'category:write'])]
|
||||
protected EDACategoryInfo $eda_info;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->children = new ArrayCollection();
|
||||
$this->attachments = new ArrayCollection();
|
||||
$this->parameters = new ArrayCollection();
|
||||
$this->eda_info = new EDACategoryInfo();
|
||||
}
|
||||
|
||||
public function getPartnameHint(): string
|
||||
{
|
||||
|
|
@ -278,14 +294,17 @@ class Category extends AbstractPartsContainingDBElement
|
|||
public function setDefaultComment(string $default_comment): self
|
||||
{
|
||||
$this->default_comment = $default_comment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
public function __construct()
|
||||
|
||||
public function getEdaInfo(): EDACategoryInfo
|
||||
{
|
||||
parent::__construct();
|
||||
$this->children = new ArrayCollection();
|
||||
$this->attachments = new ArrayCollection();
|
||||
$this->parameters = new ArrayCollection();
|
||||
return $this->eda_info;
|
||||
}
|
||||
|
||||
public function setEdaInfo(EDACategoryInfo $eda_info): Category
|
||||
{
|
||||
$this->eda_info = $eda_info;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,9 @@ use ApiPlatform\Serializer\Filter\PropertyFilter;
|
|||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Attachments\AttachmentTypeAttachment;
|
||||
use App\Entity\EDA\EDACategoryInfo;
|
||||
use App\Entity\EDA\EDAFootprintInfo;
|
||||
use App\Entity\EDA\EDAPartInfo;
|
||||
use App\Repository\Parts\FootprintRepository;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
|
@ -47,6 +50,7 @@ use App\Entity\Base\AbstractPartsContainingDBElement;
|
|||
use App\Entity\Parameters\FootprintParameter;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
|
|
@ -68,7 +72,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
new Delete(security: 'is_granted("delete", object)'),
|
||||
],
|
||||
normalizationContext: ['groups' => ['footprint:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
|
||||
denormalizationContext: ['groups' => ['footprint:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'],
|
||||
denormalizationContext: ['groups' => ['footprint:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'],
|
||||
)]
|
||||
#[ApiResource(
|
||||
uriTemplate: '/footprints/{id}/children.{_format}',
|
||||
|
|
@ -137,6 +141,19 @@ class Footprint extends AbstractPartsContainingDBElement
|
|||
#[Groups(['footprint:read'])]
|
||||
protected ?\DateTimeInterface $lastModified = null;
|
||||
|
||||
#[Assert\Valid]
|
||||
#[ORM\Embedded(class: EDAFootprintInfo::class)]
|
||||
#[Groups(['full', 'footprint:read', 'footprint:write'])]
|
||||
protected EDAFootprintInfo $eda_info;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->children = new ArrayCollection();
|
||||
$this->attachments = new ArrayCollection();
|
||||
$this->parameters = new ArrayCollection();
|
||||
$this->eda_info = new EDAFootprintInfo();
|
||||
}
|
||||
|
||||
/****************************************
|
||||
* Getters
|
||||
|
|
@ -166,11 +183,15 @@ class Footprint extends AbstractPartsContainingDBElement
|
|||
|
||||
return $this;
|
||||
}
|
||||
public function __construct()
|
||||
|
||||
public function getEdaInfo(): EDAFootprintInfo
|
||||
{
|
||||
parent::__construct();
|
||||
$this->children = new ArrayCollection();
|
||||
$this->attachments = new ArrayCollection();
|
||||
$this->parameters = new ArrayCollection();
|
||||
return $this->eda_info;
|
||||
}
|
||||
|
||||
public function setEdaInfo(EDAFootprintInfo $eda_info): Footprint
|
||||
{
|
||||
$this->eda_info = $eda_info;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
new Delete(security: 'is_granted("delete", object)'),
|
||||
],
|
||||
normalizationContext: ['groups' => ['manufacturer:read', 'company:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
|
||||
denormalizationContext: ['groups' => ['manufacturer:write', 'company:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'],
|
||||
denormalizationContext: ['groups' => ['manufacturer:write', 'company:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'],
|
||||
)]
|
||||
#[ApiResource(
|
||||
uriTemplate: '/manufacturers/{id}/children.{_format}',
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
new Delete(security: 'is_granted("delete", object)'),
|
||||
],
|
||||
normalizationContext: ['groups' => ['measurement_unit:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
|
||||
denormalizationContext: ['groups' => ['measurement_unit:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'],
|
||||
denormalizationContext: ['groups' => ['measurement_unit:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'],
|
||||
)]
|
||||
#[ApiResource(
|
||||
uriTemplate: '/footprints/{id}/children.{_format}',
|
||||
|
|
|
|||
|
|
@ -27,34 +27,32 @@ use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
|
|||
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
|
||||
use ApiPlatform\Doctrine\Orm\Filter\RangeFilter;
|
||||
use ApiPlatform\Metadata\ApiFilter;
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Link;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\Serializer\Filter\PropertyFilter;
|
||||
use App\ApiPlatform\DocumentedAPIProperty;
|
||||
use App\ApiPlatform\Filter\EntityFilter;
|
||||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\ApiPlatform\Filter\PartStoragelocationFilter;
|
||||
use App\Entity\Attachments\AttachmentTypeAttachment;
|
||||
use App\Repository\PartRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Attachments\AttachmentContainingDBElement;
|
||||
use App\Entity\Attachments\PartAttachment;
|
||||
use App\Entity\Parts\PartTraits\ProjectTrait;
|
||||
use App\Entity\EDA\EDAPartInfo;
|
||||
use App\Entity\Parameters\ParametersTrait;
|
||||
use App\Entity\Parameters\PartParameter;
|
||||
use App\Entity\Parts\PartTraits\AdvancedPropertyTrait;
|
||||
use App\Entity\Parts\PartTraits\AssociationTrait;
|
||||
use App\Entity\Parts\PartTraits\BasicPropertyTrait;
|
||||
use App\Entity\Parts\PartTraits\EDATrait;
|
||||
use App\Entity\Parts\PartTraits\InstockTrait;
|
||||
use App\Entity\Parts\PartTraits\ManufacturerTrait;
|
||||
use App\Entity\Parts\PartTraits\OrderTrait;
|
||||
use DateTime;
|
||||
use App\Entity\Parts\PartTraits\ProjectTrait;
|
||||
use App\EntityListeners\TreeCacheInvalidationListener;
|
||||
use App\Repository\PartRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
|
@ -74,6 +72,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
|||
*/
|
||||
#[UniqueEntity(fields: ['ipn'], message: 'part.ipn.must_be_unique')]
|
||||
#[ORM\Entity(repositoryClass: PartRepository::class)]
|
||||
#[ORM\EntityListeners([TreeCacheInvalidationListener::class])]
|
||||
#[ORM\Table('`parts`')]
|
||||
#[ORM\Index(name: 'parts_idx_datet_name_last_id_needs', columns: ['datetime_added', 'name', 'last_modified', 'id', 'needs_review'])]
|
||||
#[ORM\Index(name: 'parts_idx_name', columns: ['name'])]
|
||||
|
|
@ -81,7 +80,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
|||
#[ApiResource(
|
||||
operations: [
|
||||
new Get(normalizationContext: ['groups' => ['part:read', 'provider_reference:read', 'api:basic:read', 'part_lot:read',
|
||||
'orderdetail:read', 'pricedetail:read', 'parameter:read', 'attachment:read'],
|
||||
'orderdetail:read', 'pricedetail:read', 'parameter:read', 'attachment:read', 'eda_info:read'],
|
||||
'openapi_definition_name' => 'Read',
|
||||
], security: 'is_granted("read", object)'),
|
||||
new GetCollection(security: 'is_granted("@parts.read")'),
|
||||
|
|
@ -90,7 +89,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
|||
new Delete(security: 'is_granted("delete", object)'),
|
||||
],
|
||||
normalizationContext: ['groups' => ['part:read', 'provider_reference:read', 'api:basic:read', 'part_lot:read'], 'openapi_definition_name' => 'Read'],
|
||||
denormalizationContext: ['groups' => ['part:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'],
|
||||
denormalizationContext: ['groups' => ['part:write', 'api:basic:write', 'eda_info:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'],
|
||||
)]
|
||||
#[ApiFilter(PropertyFilter::class)]
|
||||
#[ApiFilter(EntityFilter::class, properties: ["category", "footprint", "manufacturer", "partUnit"])]
|
||||
|
|
@ -100,8 +99,6 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
|||
#[ApiFilter(RangeFilter::class, properties: ["mass", "minamount"])]
|
||||
#[ApiFilter(DateFilter::class, strategy: DateFilter::EXCLUDE_NULL)]
|
||||
#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])]
|
||||
#[DocumentedAPIProperty(schemaName: 'Part-Read', property: 'total_instock', type: 'number', nullable: false,
|
||||
description: 'The total amount of this part in stock (sum of all part lots).')]
|
||||
class Part extends AttachmentContainingDBElement
|
||||
{
|
||||
use AdvancedPropertyTrait;
|
||||
|
|
@ -112,6 +109,8 @@ class Part extends AttachmentContainingDBElement
|
|||
use OrderTrait;
|
||||
use ParametersTrait;
|
||||
use ProjectTrait;
|
||||
use AssociationTrait;
|
||||
use EDATrait;
|
||||
|
||||
/** @var Collection<int, PartParameter>
|
||||
*/
|
||||
|
|
@ -165,8 +164,12 @@ class Part extends AttachmentContainingDBElement
|
|||
$this->parameters = new ArrayCollection();
|
||||
$this->project_bom_entries = new ArrayCollection();
|
||||
|
||||
$this->associated_parts_as_owner = new ArrayCollection();
|
||||
$this->associated_parts_as_other = new ArrayCollection();
|
||||
|
||||
//By default, the part has no provider
|
||||
$this->providerReference = InfoProviderReference::noProvider();
|
||||
$this->eda_info = new EDAPartInfo();
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
|
|
@ -193,8 +196,16 @@ class Part extends AttachmentContainingDBElement
|
|||
$this->addParameter(clone $parameter);
|
||||
}
|
||||
|
||||
//Deep clone the owned part associations (the owned ones make not much sense without the owner)
|
||||
$ownedAssociations = $this->associated_parts_as_owner;
|
||||
$this->associated_parts_as_owner = new ArrayCollection();
|
||||
foreach ($ownedAssociations as $association) {
|
||||
$this->addAssociatedPartsAsOwner(clone $association);
|
||||
}
|
||||
|
||||
//Deep clone info provider
|
||||
$this->providerReference = clone $this->providerReference;
|
||||
$this->eda_info = clone $this->eda_info;
|
||||
}
|
||||
parent::__clone();
|
||||
}
|
||||
|
|
|
|||
234
src/Entity/Parts/PartAssociation.php
Normal file
234
src/Entity/Parts/PartAssociation.php
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
<?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 ApiPlatform\Doctrine\Orm\Filter\BooleanFilter;
|
||||
use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
|
||||
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
|
||||
use ApiPlatform\Doctrine\Orm\Filter\RangeFilter;
|
||||
use ApiPlatform\Metadata\ApiFilter;
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\Serializer\Filter\PropertyFilter;
|
||||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\Entity\Contracts\TimeStampableInterface;
|
||||
use App\Repository\DBElementRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use App\Entity\Base\AbstractDBElement;
|
||||
use App\Entity\Base\TimestampTrait;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* This entity describes a part association, which is a semantic connection between two parts.
|
||||
* For example, a part association can be used to describe that a part is a replacement for another part.
|
||||
*/
|
||||
#[ORM\Entity(repositoryClass: DBElementRepository::class)]
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
#[UniqueEntity(fields: ['other', 'owner', 'type'], message: 'validator.part_association.already_exists')]
|
||||
#[ApiResource(
|
||||
operations: [
|
||||
new Get(security: 'is_granted("read", object)'),
|
||||
new GetCollection(security: 'is_granted("@parts.read")'),
|
||||
new Post(securityPostDenormalize: 'is_granted("create", object)'),
|
||||
new Patch(security: 'is_granted("edit", object)'),
|
||||
new Delete(security: 'is_granted("delete", object)'),
|
||||
],
|
||||
normalizationContext: ['groups' => ['part_assoc:read', 'part_assoc:read:standalone', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
|
||||
denormalizationContext: ['groups' => ['part_assoc:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'],
|
||||
)]
|
||||
#[ApiFilter(PropertyFilter::class)]
|
||||
#[ApiFilter(LikeFilter::class, properties: ["other_type", "comment"])]
|
||||
#[ApiFilter(DateFilter::class, strategy: DateFilter::EXCLUDE_NULL)]
|
||||
#[ApiFilter(OrderFilter::class, properties: ['comment', 'addedDate', 'lastModified'])]
|
||||
class PartAssociation extends AbstractDBElement implements TimeStampableInterface
|
||||
{
|
||||
use TimestampTrait;
|
||||
|
||||
/**
|
||||
* @var AssociationType The type of this association (how the two parts are related)
|
||||
*/
|
||||
#[ORM\Column(type: Types::SMALLINT, enumType: AssociationType::class)]
|
||||
#[Groups(['part_assoc:read', 'part_assoc:write'])]
|
||||
protected AssociationType $type = AssociationType::OTHER;
|
||||
|
||||
/**
|
||||
* @var string|null A user definable association type, which can be described in the comment field, which
|
||||
* is used if the type is OTHER
|
||||
*/
|
||||
#[ORM\Column(type: Types::STRING, length: 255, nullable: true)]
|
||||
#[Assert\Expression("this.getType().value !== 0 or this.getOtherType() !== null",
|
||||
message: 'validator.part_association.must_set_an_value_if_type_is_other')]
|
||||
#[Groups(['part_assoc:read', 'part_assoc:write'])]
|
||||
protected ?string $other_type = null;
|
||||
|
||||
/**
|
||||
* @var string|null A comment describing this association further.
|
||||
*/
|
||||
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
||||
#[Groups(['part_assoc:read', 'part_assoc:write'])]
|
||||
protected ?string $comment = null;
|
||||
|
||||
/**
|
||||
* @var Part|null The part which "owns" this association, e.g. the part which is a replacement for another part
|
||||
*/
|
||||
#[ORM\ManyToOne(targetEntity: Part::class, inversedBy: 'associated_parts_as_owner')]
|
||||
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
|
||||
#[Assert\NotNull]
|
||||
#[Groups(['part_assoc:read:standalone', 'part_assoc:write'])]
|
||||
protected ?Part $owner = null;
|
||||
|
||||
/**
|
||||
* @var Part|null The part which is "owned" by this association, e.g. the part which is replaced by another part
|
||||
*/
|
||||
#[ORM\ManyToOne(targetEntity: Part::class, inversedBy: 'associated_parts_as_other')]
|
||||
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
|
||||
#[Assert\NotNull]
|
||||
#[Assert\Expression("this.getOwner() !== this.getOther()",
|
||||
message: 'validator.part_association.part_cannot_be_associated_with_itself')]
|
||||
#[Groups(['part_assoc:read', 'part_assoc:write'])]
|
||||
protected ?Part $other = null;
|
||||
|
||||
/**
|
||||
* Returns the (semantic) relation type of this association as an AssociationType enum value.
|
||||
* If the type is set to OTHER, then the other_type field value is used for the user defined type.
|
||||
* @return AssociationType
|
||||
*/
|
||||
public function getType(): AssociationType
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the (semantic) relation type of this association as an AssociationType enum value.
|
||||
* @param AssociationType $type
|
||||
* @return $this
|
||||
*/
|
||||
public function setType(AssociationType $type): PartAssociation
|
||||
{
|
||||
$this->type = $type;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a comment, which describes this association further.
|
||||
* @return string|null
|
||||
*/
|
||||
public function getComment(): ?string
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a comment, which describes this association further.
|
||||
* @param string|null $comment
|
||||
* @return $this
|
||||
*/
|
||||
public function setComment(?string $comment): PartAssociation
|
||||
{
|
||||
$this->comment = $comment;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the part which "owns" this association, e.g. the part which is a replacement for another part.
|
||||
* @return Part|null
|
||||
*/
|
||||
public function getOwner(): ?Part
|
||||
{
|
||||
return $this->owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the part which "owns" this association, e.g. the part which is a replacement for another part.
|
||||
* @param Part|null $owner
|
||||
* @return $this
|
||||
*/
|
||||
public function setOwner(?Part $owner): PartAssociation
|
||||
{
|
||||
$this->owner = $owner;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the part which is "owned" by this association, e.g. the part which is replaced by another part.
|
||||
* @return Part|null
|
||||
*/
|
||||
public function getOther(): ?Part
|
||||
{
|
||||
return $this->other;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the part which is "owned" by this association, e.g. the part which is replaced by another part.
|
||||
* @param Part|null $other
|
||||
* @return $this
|
||||
*/
|
||||
public function setOther(?Part $other): PartAssociation
|
||||
{
|
||||
$this->other = $other;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user defined association type, which is used if the type is set to OTHER.
|
||||
* @return string|null
|
||||
*/
|
||||
public function getOtherType(): ?string
|
||||
{
|
||||
return $this->other_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user defined association type, which is used if the type is set to OTHER.
|
||||
* @param string|null $other_type
|
||||
* @return $this
|
||||
*/
|
||||
public function setOtherType(?string $other_type): PartAssociation
|
||||
{
|
||||
$this->other_type = $other_type;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the translation key for the type of this association.
|
||||
* If the type is set to OTHER, then the other_type field value is used.
|
||||
* @return string
|
||||
*/
|
||||
public function getTypeTranslationKey(): string
|
||||
{
|
||||
if ($this->type === AssociationType::OTHER) {
|
||||
return $this->other_type ?? 'Unknown';
|
||||
}
|
||||
return $this->type->getTranslationKey();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
|
|||
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
|
||||
use ApiPlatform\Doctrine\Orm\Filter\RangeFilter;
|
||||
use ApiPlatform\Metadata\ApiFilter;
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
|
|
@ -47,6 +48,7 @@ use App\Validator\Constraints\ValidPartLot;
|
|||
use DateTime;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Exception;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
|
|
@ -60,9 +62,11 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
|||
#[ORM\Entity]
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
#[ORM\Table(name: 'part_lots')]
|
||||
#[ORM\Index(name: 'part_lots_idx_instock_un_expiration_id_part', columns: ['instock_unknown', 'expiration_date', 'id_part'])]
|
||||
#[ORM\Index(name: 'part_lots_idx_needs_refill', columns: ['needs_refill'])]
|
||||
#[ORM\Index(columns: ['instock_unknown', 'expiration_date', 'id_part'], name: 'part_lots_idx_instock_un_expiration_id_part')]
|
||||
#[ORM\Index(columns: ['needs_refill'], name: 'part_lots_idx_needs_refill')]
|
||||
#[ORM\Index(columns: ['vendor_barcode'], name: 'part_lots_idx_barcode')]
|
||||
#[ValidPartLot]
|
||||
#[UniqueEntity(['vendor_barcode'], message: 'validator.part_lot.vendor_barcode_must_be_unique')]
|
||||
#[ApiResource(
|
||||
operations: [
|
||||
new Get(security: 'is_granted("read", object)'),
|
||||
|
|
@ -144,6 +148,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named
|
|||
#[ORM\ManyToOne(targetEntity: Part::class, inversedBy: 'partLots')]
|
||||
#[ORM\JoinColumn(name: 'id_part', nullable: false, onDelete: 'CASCADE')]
|
||||
#[Groups(['part_lot:read:standalone', 'part_lot:write'])]
|
||||
#[ApiProperty(writableLink: false)]
|
||||
protected ?Part $part = null;
|
||||
|
||||
/**
|
||||
|
|
@ -152,8 +157,16 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named
|
|||
#[ORM\ManyToOne(targetEntity: User::class)]
|
||||
#[ORM\JoinColumn(name: 'id_owner', onDelete: 'SET NULL')]
|
||||
#[Groups(['part_lot:read', 'part_lot:write'])]
|
||||
#[ApiProperty(writableLink: false)]
|
||||
protected ?User $owner = null;
|
||||
|
||||
/**
|
||||
* @var string|null The content of the barcode of this part lot (e.g. a barcode on the package put by the vendor)
|
||||
*/
|
||||
#[ORM\Column(type: Types::STRING, nullable: true)]
|
||||
#[Groups(['part_lot:read', 'part_lot:write'])]
|
||||
protected ?string $vendor_barcode = null;
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
if ($this->id) {
|
||||
|
|
@ -354,6 +367,29 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named
|
|||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* The content of the barcode of this part lot (e.g. a barcode on the package put by the vendor), or
|
||||
* null if no barcode is set.
|
||||
* @return string|null
|
||||
*/
|
||||
public function getVendorBarcode(): ?string
|
||||
{
|
||||
return $this->vendor_barcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the content of the barcode of this part lot (e.g. a barcode on the package put by the vendor).
|
||||
* @param string|null $vendor_barcode
|
||||
* @return $this
|
||||
*/
|
||||
public function setVendorBarcode(?string $vendor_barcode): PartLot
|
||||
{
|
||||
$this->vendor_barcode = $vendor_barcode;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[Assert\Callback]
|
||||
public function validate(ExecutionContextInterface $context, $payload): void
|
||||
{
|
||||
|
|
|
|||
111
src/Entity/Parts/PartTraits/AssociationTrait.php
Normal file
111
src/Entity/Parts/PartTraits/AssociationTrait.php
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
<?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\PartTraits;
|
||||
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\Parts\PartAssociation;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints\Valid;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
trait AssociationTrait
|
||||
{
|
||||
/**
|
||||
* @var Collection<PartAssociation> All associations where this part is the owner
|
||||
*/
|
||||
#[Valid]
|
||||
#[ORM\OneToMany(mappedBy: 'owner', targetEntity: PartAssociation::class,
|
||||
cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
#[Groups(['part:read', 'part:write'])]
|
||||
protected Collection $associated_parts_as_owner;
|
||||
|
||||
/**
|
||||
* @var Collection<PartAssociation> All associations where this part is the owned/other part
|
||||
*/
|
||||
#[Valid]
|
||||
#[ORM\OneToMany(mappedBy: 'other', targetEntity: PartAssociation::class,
|
||||
cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
#[Groups(['part:read'])]
|
||||
protected Collection $associated_parts_as_other;
|
||||
|
||||
/**
|
||||
* Returns all associations where this part is the owner.
|
||||
* @return Collection<PartAssociation>
|
||||
*/
|
||||
public function getAssociatedPartsAsOwner(): Collection
|
||||
{
|
||||
return $this->associated_parts_as_owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new association where this part is the owner.
|
||||
* @param PartAssociation $association
|
||||
* @return $this
|
||||
*/
|
||||
public function addAssociatedPartsAsOwner(PartAssociation $association): self
|
||||
{
|
||||
//Ensure that the association is really owned by this part
|
||||
$association->setOwner($this);
|
||||
|
||||
$this->associated_parts_as_owner->add($association);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an association where this part is the owner.
|
||||
* @param PartAssociation $association
|
||||
* @return $this
|
||||
*/
|
||||
public function removeAssociatedPartsAsOwner(PartAssociation $association): self
|
||||
{
|
||||
$this->associated_parts_as_owner->removeElement($association);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all associations where this part is the owned/other part.
|
||||
* If you want to modify the association, do it on the owning part
|
||||
* @return Collection<PartAssociation>
|
||||
*/
|
||||
public function getAssociatedPartsAsOther(): Collection
|
||||
{
|
||||
return $this->associated_parts_as_other;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all associations where this part is the owned or other part.
|
||||
* @return Collection<PartAssociation>
|
||||
*/
|
||||
public function getAssociatedPartsAll(): Collection
|
||||
{
|
||||
return new ArrayCollection(
|
||||
array_merge(
|
||||
$this->associated_parts_as_owner->toArray(),
|
||||
$this->associated_parts_as_other->toArray()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
54
src/Entity/Parts/PartTraits/EDATrait.php
Normal file
54
src/Entity/Parts/PartTraits/EDATrait.php
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<?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\PartTraits;
|
||||
|
||||
use App\Entity\EDA\EDAPartInfo;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Embedded;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints\Valid;
|
||||
|
||||
trait EDATrait
|
||||
{
|
||||
#[Valid]
|
||||
#[Embedded(class: EDAPartInfo::class)]
|
||||
#[Groups(['full', 'part:read', 'part:write'])]
|
||||
protected EDAPartInfo $eda_info;
|
||||
|
||||
public function getEdaInfo(): EDAPartInfo
|
||||
{
|
||||
return $this->eda_info;
|
||||
}
|
||||
|
||||
public function setEdaInfo(?EDAPartInfo $eda_info): self
|
||||
{
|
||||
if ($eda_info !== null) {
|
||||
//Do a clone, to ensure that the property is updated in the database
|
||||
$eda_info = clone $eda_info;
|
||||
}
|
||||
|
||||
$this->eda_info = $eda_info;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -28,6 +28,7 @@ use App\Entity\Parts\PartLot;
|
|||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Serializer\Attribute\SerializedName;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
|
|
@ -181,6 +182,8 @@ trait InstockTrait
|
|||
*
|
||||
* @return float The amount of parts given in partUnit
|
||||
*/
|
||||
#[Groups(['simple', 'extended', 'full', 'part:read'])]
|
||||
#[SerializedName('total_instock')]
|
||||
public function getAmountSum(): float
|
||||
{
|
||||
//TODO: Find a method to do this natively in SQL, the current method could be a bit slow
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
new Delete(security: 'is_granted("delete", object)'),
|
||||
],
|
||||
normalizationContext: ['groups' => ['location:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
|
||||
denormalizationContext: ['groups' => ['location:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'],
|
||||
denormalizationContext: ['groups' => ['location:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'],
|
||||
)]
|
||||
#[ApiResource(
|
||||
uriTemplate: '/storage_locations/{id}/children.{_format}',
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
new Delete(security: 'is_granted("delete", object)'),
|
||||
],
|
||||
normalizationContext: ['groups' => ['supplier:read', 'company:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
|
||||
denormalizationContext: ['groups' => ['supplier:write', 'company:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'],
|
||||
denormalizationContext: ['groups' => ['supplier:write', 'company:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'],
|
||||
)]
|
||||
#[ApiResource(
|
||||
uriTemplate: '/suppliers/{id}/children.{_format}',
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue