Merge branch 'master' into feature/automatic-ipn-generation
2
VERSION
|
|
@ -1 +1 @@
|
|||
2.2.0
|
||||
2.2.1
|
||||
|
|
|
|||
|
|
@ -20,11 +20,12 @@
|
|||
import {Plugin} from 'ckeditor5';
|
||||
|
||||
require('./lang/de.js');
|
||||
require('./lang/en.js');
|
||||
|
||||
import { addListToDropdown, createDropdown } from 'ckeditor5';
|
||||
|
||||
import {Collection} from 'ckeditor5';
|
||||
import {Model} from 'ckeditor5';
|
||||
import {UIModel} from 'ckeditor5';
|
||||
|
||||
export default class PartDBLabelUI extends Plugin {
|
||||
init() {
|
||||
|
|
@ -151,18 +152,28 @@ const PLACEHOLDERS = [
|
|||
function getDropdownItemsDefinitions(t) {
|
||||
const itemDefinitions = new Collection();
|
||||
|
||||
let first = true;
|
||||
|
||||
for ( const group of PLACEHOLDERS) {
|
||||
|
||||
//Add group header
|
||||
itemDefinitions.add({
|
||||
'type': 'separator',
|
||||
model: new Model( {
|
||||
withText: true,
|
||||
})
|
||||
});
|
||||
|
||||
//Skip separator for first group
|
||||
if (!first) {
|
||||
|
||||
itemDefinitions.add({
|
||||
'type': 'separator',
|
||||
model: new UIModel( {
|
||||
withText: true,
|
||||
})
|
||||
});
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
|
||||
itemDefinitions.add({
|
||||
type: 'button',
|
||||
model: new Model( {
|
||||
model: new UIModel( {
|
||||
label: t(group.label),
|
||||
withText: true,
|
||||
isEnabled: false,
|
||||
|
|
@ -173,7 +184,7 @@ function getDropdownItemsDefinitions(t) {
|
|||
for ( const entry of group.entries) {
|
||||
const definition = {
|
||||
type: 'button',
|
||||
model: new Model( {
|
||||
model: new UIModel( {
|
||||
commandParam: entry[0],
|
||||
label: t(entry[1]),
|
||||
tooltip: entry[0],
|
||||
|
|
|
|||
|
|
@ -17,15 +17,9 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// Make sure that the global object is defined. If not, define it.
|
||||
window.CKEDITOR_TRANSLATIONS = window.CKEDITOR_TRANSLATIONS || {};
|
||||
import {add} from "ckeditor5";
|
||||
|
||||
// Make sure that the dictionary for Polish translations exist.
|
||||
window.CKEDITOR_TRANSLATIONS[ 'de' ] = window.CKEDITOR_TRANSLATIONS[ 'de' ] || {};
|
||||
window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary = window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary || {};
|
||||
|
||||
// Extend the dictionary for Polish translations with your translations:
|
||||
Object.assign( window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary, {
|
||||
add( "de", {
|
||||
'Label Placeholder': 'Label Platzhalter',
|
||||
'Part': 'Bauteil',
|
||||
|
||||
|
|
@ -88,5 +82,4 @@ Object.assign( window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary, {
|
|||
'Instance name': 'Instanzname',
|
||||
'Target type': 'Zieltyp',
|
||||
'URL of this Part-DB instance': 'URL dieser Part-DB Instanz',
|
||||
|
||||
} );
|
||||
});
|
||||
|
|
|
|||
84
assets/ckeditor/plugins/PartDBLabel/lang/en.js
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2025 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 {add} from "ckeditor5";
|
||||
|
||||
add( "en", {
|
||||
'Label Placeholder': 'Label placeholder',
|
||||
'Part': 'Part',
|
||||
|
||||
'Database ID': 'Database ID',
|
||||
'Part name': 'Part name',
|
||||
'Category': 'Category',
|
||||
'Category (Full path)': 'Category (full path)',
|
||||
'Manufacturer': 'Manufacturer',
|
||||
'Manufacturer (Full path)': 'Manufacturer (full path)',
|
||||
'Footprint': 'Footprint',
|
||||
'Footprint (Full path)': 'Footprint (full path)',
|
||||
'Mass': 'Mass',
|
||||
'Manufacturer Product Number (MPN)': 'Manufacturer Product Number (MPN)',
|
||||
'Internal Part Number (IPN)': 'Internal Part Number (IPN)',
|
||||
'Tags': 'Tags',
|
||||
'Manufacturing status': 'Manufacturing status',
|
||||
'Description': 'Description',
|
||||
'Description (plain text)': 'Description (plain text)',
|
||||
'Comment': 'Comment',
|
||||
'Comment (plain text)': 'Comment (plain text)',
|
||||
'Last modified datetime': 'Last modified datetime',
|
||||
'Creation datetime': 'Creation datetime',
|
||||
'IPN as QR code': 'IPN as QR code',
|
||||
'IPN as Code 128 barcode': 'IPN as Code 128 barcode',
|
||||
'IPN as Code 39 barcode': 'IPN as Code 39 barcode',
|
||||
|
||||
'Lot ID': 'Lot ID',
|
||||
'Lot name': 'Lot name',
|
||||
'Lot comment': 'Lot comment',
|
||||
'Lot expiration date': 'Lot expiration date',
|
||||
'Lot amount': 'Lot amount',
|
||||
'Storage location': 'Storage location',
|
||||
'Storage location (Full path)': 'Storage location (full path)',
|
||||
'Full name of the lot owner': 'Full name of the lot owner',
|
||||
'Username of the lot owner': 'Username of the lot owner',
|
||||
|
||||
'Barcodes': 'Barcodes',
|
||||
'Content of the 1D barcodes (like Code 39)': 'Content of the 1D barcodes (like Code 39)',
|
||||
'Content of the 2D barcodes (QR codes)': 'Content of the 2D barcodes (QR codes)',
|
||||
'QR code linking to this element': 'QR code linking to this element',
|
||||
'Code 128 barcode linking to this element': 'Code 128 barcode linking to this element',
|
||||
'Code 39 barcode linking to this element': 'Code 39 barcode linking to this element',
|
||||
'Code 93 barcode linking to this element': 'Code 93 barcode linking to this element',
|
||||
'Datamatrix code linking to this element': 'Datamatrix code linking to this element',
|
||||
|
||||
'Location ID': 'Location ID',
|
||||
'Name': 'Name',
|
||||
'Full path': 'Full path',
|
||||
'Parent name': 'Parent name',
|
||||
'Parent full path': 'Parent full path',
|
||||
'Full name of the location owner': 'Full name of the location owner',
|
||||
'Username of the location owner': 'Username of the location owner',
|
||||
|
||||
'Username': 'Username',
|
||||
'Username (including name)': 'Username (including name)',
|
||||
'Current datetime': 'Current datetime',
|
||||
'Current date': 'Current date',
|
||||
'Current time': 'Current time',
|
||||
'Instance name': 'Instance name',
|
||||
'Target type': 'Target type',
|
||||
'URL of this Part-DB instance': 'URL of this Part-DB instance',
|
||||
} );
|
||||
|
|
@ -34,6 +34,11 @@ export default class extends Controller {
|
|||
|
||||
connect() {
|
||||
|
||||
let dropdownParent = "body";
|
||||
if (this.element.closest('.modal')) {
|
||||
dropdownParent = null
|
||||
}
|
||||
|
||||
let settings = {
|
||||
persistent: false,
|
||||
create: true,
|
||||
|
|
@ -42,7 +47,7 @@ export default class extends Controller {
|
|||
selectOnTab: true,
|
||||
//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',
|
||||
dropdownParent: 'body',
|
||||
dropdownParent: dropdownParent,
|
||||
render: {
|
||||
item: (data, escape) => {
|
||||
return '<span>' + escape(data.label) + '</span>';
|
||||
|
|
|
|||
|
|
@ -28,6 +28,27 @@ import {EditorWatchdog} from 'ckeditor5';
|
|||
import "ckeditor5/ckeditor5.css";;
|
||||
import "../../css/components/ckeditor.css";
|
||||
|
||||
const translationContext = require.context(
|
||||
'ckeditor5/translations',
|
||||
false,
|
||||
//Only load the translation files we will really need
|
||||
/(de|it|fr|ru|ja|cs|da|zh|pl|hu)\.js$/
|
||||
);
|
||||
|
||||
function loadTranslation(language) {
|
||||
if (!language || language === 'en') {
|
||||
return null;
|
||||
}
|
||||
const lang = language.slice(0, 2);
|
||||
const path = `./${lang}.js`;
|
||||
if (translationContext.keys().includes(path)) {
|
||||
const module = translationContext(path);
|
||||
return module.default;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/* stimulusFetch: 'lazy' */
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
|
|
@ -63,6 +84,13 @@ export default class extends Controller {
|
|||
}
|
||||
}
|
||||
|
||||
//Load translations if not english
|
||||
let translations = loadTranslation(language);
|
||||
if (translations) {
|
||||
//Keep existing translations (e.g. from other plugins), if any
|
||||
config.translations = [window.CKEDITOR_TRANSLATIONS, translations];
|
||||
}
|
||||
|
||||
const watchdog = new EditorWatchdog();
|
||||
watchdog.setCreator((elementOrData, editorConfig) => {
|
||||
return EDITOR_TYPE.create(elementOrData, editorConfig)
|
||||
|
|
|
|||
|
|
@ -10,13 +10,19 @@ export default class extends Controller {
|
|||
|
||||
connect() {
|
||||
|
||||
//Check if tomselect is inside an modal and do not attach the dropdown to body in that case (as it breaks the modal)
|
||||
let dropdownParent = "body";
|
||||
if (this.element.closest('.modal')) {
|
||||
dropdownParent = null
|
||||
}
|
||||
|
||||
let settings = {
|
||||
allowEmptyOption: true,
|
||||
plugins: ['dropdown_input'],
|
||||
searchField: ["name", "description", "category", "footprint"],
|
||||
valueField: "id",
|
||||
labelField: "name",
|
||||
dropdownParent: 'body',
|
||||
dropdownParent: dropdownParent,
|
||||
preload: "focus",
|
||||
render: {
|
||||
item: (data, escape) => {
|
||||
|
|
|
|||
|
|
@ -38,13 +38,17 @@ export default class extends Controller {
|
|||
this._emptyMessage = this.element.getAttribute('title');
|
||||
}
|
||||
|
||||
let dropdownParent = "body";
|
||||
if (this.element.closest('.modal')) {
|
||||
dropdownParent = null
|
||||
}
|
||||
|
||||
let settings = {
|
||||
plugins: ["clear_button"],
|
||||
allowEmptyOption: true,
|
||||
selectOnTab: true,
|
||||
maxOptions: null,
|
||||
dropdownParent: 'body',
|
||||
dropdownParent: dropdownParent,
|
||||
|
||||
render: {
|
||||
item: this.renderItem.bind(this),
|
||||
|
|
|
|||
|
|
@ -26,10 +26,15 @@ export default class extends Controller {
|
|||
_tomSelect;
|
||||
|
||||
connect() {
|
||||
let dropdownParent = "body";
|
||||
if (this.element.closest('.modal')) {
|
||||
dropdownParent = null
|
||||
}
|
||||
|
||||
this._tomSelect = new TomSelect(this.element, {
|
||||
maxItems: 1000,
|
||||
allowEmptyOption: true,
|
||||
dropdownParent: 'body',
|
||||
dropdownParent: dropdownParent,
|
||||
plugins: ['remove_button'],
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,11 @@ export default class extends Controller {
|
|||
|
||||
connect() {
|
||||
|
||||
let dropdownParent = "body";
|
||||
if (this.element.closest('.modal')) {
|
||||
dropdownParent = null
|
||||
}
|
||||
|
||||
let settings = {
|
||||
persistent: false,
|
||||
create: true,
|
||||
|
|
@ -50,7 +55,7 @@ export default class extends Controller {
|
|||
valueField: 'text',
|
||||
searchField: 'text',
|
||||
orderField: 'text',
|
||||
dropdownParent: 'body',
|
||||
dropdownParent: dropdownParent,
|
||||
|
||||
//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',
|
||||
|
|
|
|||
|
|
@ -40,7 +40,10 @@ export default class extends Controller {
|
|||
const allowAdd = this.element.getAttribute("data-allow-add") === "true";
|
||||
const addHint = this.element.getAttribute("data-add-hint") ?? "";
|
||||
|
||||
|
||||
let dropdownParent = "body";
|
||||
if (this.element.closest('.modal')) {
|
||||
dropdownParent = null
|
||||
}
|
||||
|
||||
|
||||
let settings = {
|
||||
|
|
@ -54,7 +57,7 @@ export default class extends Controller {
|
|||
maxItems: 1,
|
||||
delimiter: "$$VERY_LONG_DELIMITER_THAT_SHOULD_NEVER_APPEAR$$",
|
||||
splitOn: null,
|
||||
dropdownParent: 'body',
|
||||
dropdownParent: dropdownParent,
|
||||
|
||||
searchField: [
|
||||
{field: "text", weight : 2},
|
||||
|
|
|
|||
|
|
@ -33,6 +33,11 @@ export default class extends Controller {
|
|||
_tomSelect;
|
||||
|
||||
connect() {
|
||||
let dropdownParent = "body";
|
||||
if (this.element.closest('.modal')) {
|
||||
dropdownParent = null
|
||||
}
|
||||
|
||||
let settings = {
|
||||
plugins: {
|
||||
remove_button:{},
|
||||
|
|
@ -43,7 +48,7 @@ export default class extends Controller {
|
|||
selectOnTab: true,
|
||||
createOnBlur: true,
|
||||
create: true,
|
||||
dropdownParent: 'body',
|
||||
dropdownParent: dropdownParent,
|
||||
};
|
||||
|
||||
if(this.element.dataset.autocomplete) {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
"doctrine/doctrine-bundle": "^2.0",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.0",
|
||||
"doctrine/orm": "^3.2.0",
|
||||
"dompdf/dompdf": "^v3.0.0",
|
||||
"dompdf/dompdf": "^3.1.2",
|
||||
"gregwar/captcha-bundle": "^2.1.0",
|
||||
"hshn/base64-encoded-file": "^5.0",
|
||||
"jbtronics/2fa-webauthn": "^3.0.0",
|
||||
|
|
|
|||
919
composer.lock
generated
|
|
@ -8,7 +8,7 @@ parameters:
|
|||
|
||||
# This is used as workaround for places where we can not access the settings directly (like the 2FA application names)
|
||||
partdb.title: '%env(string:settings:customization:instanceName)%' # The title shown inside of Part-DB (e.g. in the navbar and on homepage)
|
||||
partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh', 'pl'] # The languages that are shown in user drop down menu
|
||||
partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh', 'pl', 'hu'] # The languages that are shown in user drop down menu
|
||||
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
|||
label: "perm.read"
|
||||
# If a part can be read by a user, he can also see all the datastructures (except devices)
|
||||
alsoSet: ['storelocations.read', 'footprints.read', 'categories.read', 'suppliers.read', 'manufacturers.read',
|
||||
'currencies.read', 'attachment_types.read', 'measurement_units.read']
|
||||
'currencies.read', 'attachment_types.read', 'measurement_units.read', 'part_custom_states.read']
|
||||
apiTokenRole: ROLE_API_READ_ONLY
|
||||
edit:
|
||||
label: "perm.edit"
|
||||
|
|
@ -133,6 +133,10 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
|||
<<: *PART_CONTAINING
|
||||
label: "perm.measurement_units"
|
||||
|
||||
part_custom_states:
|
||||
<<: *PART_CONTAINING
|
||||
label: "perm.part_custom_states"
|
||||
|
||||
tools:
|
||||
label: "perm.part.tools"
|
||||
operations:
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept
|
|||
* `TABLE_PARTS_DEFAULT_COLUMNS`: The columns in parts tables, which are visible by default (when loading table for first
|
||||
time).
|
||||
Also specify the default order of the columns. This is a comma separated list of column names. Available columns
|
||||
are: `name`, `id`, `ipn`, `description`, `category`, `footprint`, `manufacturer`, `storage_location`, `amount`, `minamount`, `partUnit`, `addedDate`, `lastModified`, `needs_review`, `favorite`, `manufacturing_status`, `manufacturer_product_number`, `mass`, `tags`, `attachments`, `edit`.
|
||||
are: `name`, `id`, `ipn`, `description`, `category`, `footprint`, `manufacturer`, `storage_location`, `amount`, `minamount`, `partUnit`, `partCustomState`, `addedDate`, `lastModified`, `needs_review`, `favorite`, `manufacturing_status`, `manufacturer_product_number`, `mass`, `tags`, `attachments`, `edit`.
|
||||
|
||||
### History/Eventlog-related settings
|
||||
|
||||
|
|
|
|||
|
|
@ -48,14 +48,15 @@ The upgrade process works very similar to a normal (minor release) upgrade.
|
|||
1. Make a backup of your existing Part-DB installation, including the database, data directories and the configuration files and `.env.local` file.
|
||||
The `php bin/console partdb:backup` command can help you with this.
|
||||
2. Pull the v2 version. For git installation you can do this with `git checkout v2.0.0` (or newer version)
|
||||
3. Run `composer install --no-dev -o` to update the dependencies.
|
||||
4. Run `yarn install` and `yarn build` to update the frontend assets.
|
||||
5. Rund `php bin/console doctrine:migrations:migrate` to update the database schema.
|
||||
6. Clear the cache with `php bin/console cache:clear`.
|
||||
7. Open your Part-DB instance in the browser and log in as an admin user.
|
||||
8. Go to the user or group permissions page, and give yourself (and other administrators) the right to change system settings (under "System" and "Configuration").
|
||||
9. You can now go to the settings page (under "System" and "Settings") and check if all settings are correct.
|
||||
10. Parameters which were previously set via environment variables are greyed out and cannot be changed in the web interface.
|
||||
3. Remove the `var/cache/` directory inside the Part-DB installation to ensure that no old cache files remain.
|
||||
4. Run `composer install --no-dev -o` to update the dependencies.
|
||||
5. Run `yarn install` and `yarn build` to update the frontend assets.
|
||||
6. Rund `php bin/console doctrine:migrations:migrate` to update the database schema.
|
||||
7. Clear the cache with `php bin/console cache:clear`.
|
||||
8. Open your Part-DB instance in the browser and log in as an admin user.
|
||||
9. Go to the user or group permissions page, and give yourself (and other administrators) the right to change system settings (under "System" and "Configuration").
|
||||
10. You can now go to the settings page (under "System" and "Settings") and check if all settings are correct.
|
||||
11. Parameters which were previously set via environment variables are greyed out and cannot be changed in the web interface.
|
||||
If you want to change them, you must migrate them to the settings interface as described below.
|
||||
|
||||
### Docker installation
|
||||
|
|
@ -87,3 +88,15 @@ After the migration run successfully, the contents of your environment variables
|
|||
Go through the environment variables listed by the command and remove them from your environment variable configuration (e.g. `.env.local` file or docker compose file), or just comment them out for now.
|
||||
|
||||
If you want to keep some environment variables, just leave them as they are, they will still work as before, the migration command only affects the settings stored in the database.
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### cache:clear fails: You have requested a non-existent parameter "jbtronics.settings.proxy_dir".
|
||||
If you receive an error like
|
||||
```
|
||||
In App_KernelProdContainer.php line 2839:
|
||||
You have requested a non-existent parameter "jbtronics.settings.proxy_dir".
|
||||
```
|
||||
when running `php bin/console cache:clear` or `composer install`. You have to manually delete the `var/cache/`
|
||||
directory inside your Part-DB installation and try again.
|
||||
|
|
|
|||
605
migrations/Version20250321075747.php
Normal file
|
|
@ -0,0 +1,605 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Migration\AbstractMultiPlatformMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
final class Version20250321075747 extends AbstractMultiPlatformMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Create entity table for custom part states and add custom state to parts';
|
||||
}
|
||||
|
||||
public function mySQLUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE part_custom_states (
|
||||
id INT AUTO_INCREMENT NOT NULL,
|
||||
parent_id INT DEFAULT NULL,
|
||||
id_preview_attachment INT DEFAULT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
comment LONGTEXT NOT NULL,
|
||||
not_selectable TINYINT(1) NOT NULL,
|
||||
alternative_names LONGTEXT DEFAULT NULL,
|
||||
last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
INDEX IDX_F552745D727ACA70 (parent_id),
|
||||
INDEX IDX_F552745DEA7100A1 (id_preview_attachment),
|
||||
INDEX part_custom_state_name (name),
|
||||
PRIMARY KEY(id)
|
||||
)
|
||||
DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci`
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE part_custom_states ADD CONSTRAINT FK_F552745D727ACA70 FOREIGN KEY (parent_id) REFERENCES part_custom_states (id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE part_custom_states ADD CONSTRAINT FK_F552745DEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON DELETE SET NULL
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE parts ADD id_part_custom_state INT DEFAULT NULL AFTER id_part_unit
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE parts ADD CONSTRAINT FK_6940A7FEA3ED1215 FOREIGN KEY (id_part_custom_state) REFERENCES part_custom_states (id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FEA3ED1215 ON parts (id_part_custom_state)
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function mySQLDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE parts DROP FOREIGN KEY FK_6940A7FEA3ED1215
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP INDEX IDX_6940A7FEA3ED1215 ON parts
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE parts DROP id_part_custom_state
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE part_custom_states DROP FOREIGN KEY FK_F552745D727ACA70
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE part_custom_states DROP FOREIGN KEY FK_F552745DEA7100A1
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE part_custom_states
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function sqLiteUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE "part_custom_states" (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
parent_id INTEGER DEFAULT NULL,
|
||||
id_preview_attachment INTEGER DEFAULT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
comment CLOB NOT NULL,
|
||||
not_selectable BOOLEAN NOT NULL,
|
||||
alternative_names CLOB DEFAULT NULL,
|
||||
last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
CONSTRAINT FK_F552745D727ACA70 FOREIGN KEY (parent_id) REFERENCES "part_custom_states" (id) NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_F5AF83CFEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_F552745D727ACA70 ON "part_custom_states" (parent_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX part_custom_state_name ON "part_custom_states" (name)
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TEMPORARY TABLE __temp__parts AS
|
||||
SELECT
|
||||
id,
|
||||
id_preview_attachment,
|
||||
id_category,
|
||||
id_footprint,
|
||||
id_part_unit,
|
||||
id_manufacturer,
|
||||
order_orderdetails_id,
|
||||
built_project_id,
|
||||
datetime_added,
|
||||
name,
|
||||
last_modified,
|
||||
needs_review,
|
||||
tags,
|
||||
mass,
|
||||
description,
|
||||
comment,
|
||||
visible,
|
||||
favorite,
|
||||
minamount,
|
||||
manufacturer_product_url,
|
||||
manufacturer_product_number,
|
||||
manufacturing_status,
|
||||
order_quantity,
|
||||
manual_order,
|
||||
ipn,
|
||||
provider_reference_provider_key,
|
||||
provider_reference_provider_id,
|
||||
provider_reference_provider_url,
|
||||
provider_reference_last_updated,
|
||||
eda_info_reference_prefix,
|
||||
eda_info_value,
|
||||
eda_info_invisible,
|
||||
eda_info_exclude_from_bom,
|
||||
eda_info_exclude_from_board,
|
||||
eda_info_exclude_from_sim,
|
||||
eda_info_kicad_symbol,
|
||||
eda_info_kicad_footprint
|
||||
FROM parts
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE parts
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE parts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
id_preview_attachment INTEGER DEFAULT NULL,
|
||||
id_category INTEGER NOT NULL,
|
||||
id_footprint INTEGER DEFAULT NULL,
|
||||
id_part_unit INTEGER DEFAULT NULL,
|
||||
id_manufacturer INTEGER DEFAULT NULL,
|
||||
id_part_custom_state INTEGER DEFAULT NULL,
|
||||
order_orderdetails_id INTEGER DEFAULT NULL,
|
||||
built_project_id INTEGER DEFAULT NULL,
|
||||
datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
needs_review BOOLEAN NOT NULL,
|
||||
tags CLOB NOT NULL,
|
||||
mass DOUBLE PRECISION DEFAULT NULL,
|
||||
description CLOB NOT NULL,
|
||||
comment CLOB NOT NULL,
|
||||
visible BOOLEAN NOT NULL,
|
||||
favorite BOOLEAN NOT NULL,
|
||||
minamount DOUBLE PRECISION NOT NULL,
|
||||
manufacturer_product_url CLOB NOT NULL,
|
||||
manufacturer_product_number VARCHAR(255) NOT NULL,
|
||||
manufacturing_status VARCHAR(255) DEFAULT NULL,
|
||||
order_quantity INTEGER NOT NULL,
|
||||
manual_order BOOLEAN NOT NULL,
|
||||
ipn VARCHAR(100) DEFAULT NULL,
|
||||
provider_reference_provider_key VARCHAR(255) DEFAULT NULL,
|
||||
provider_reference_provider_id VARCHAR(255) DEFAULT NULL,
|
||||
provider_reference_provider_url VARCHAR(255) DEFAULT NULL,
|
||||
provider_reference_last_updated DATETIME DEFAULT NULL,
|
||||
eda_info_reference_prefix VARCHAR(255) DEFAULT NULL,
|
||||
eda_info_value VARCHAR(255) DEFAULT NULL,
|
||||
eda_info_invisible BOOLEAN DEFAULT NULL,
|
||||
eda_info_exclude_from_bom BOOLEAN DEFAULT NULL,
|
||||
eda_info_exclude_from_board BOOLEAN DEFAULT NULL,
|
||||
eda_info_exclude_from_sim BOOLEAN DEFAULT NULL,
|
||||
eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL,
|
||||
eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL,
|
||||
CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES categories (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES footprints (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES measurement_units (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES manufacturers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FEA3ED1215 FOREIGN KEY (id_part_custom_state) REFERENCES "part_custom_states" (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES orderdetails (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
)
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
INSERT INTO parts (
|
||||
id,
|
||||
id_preview_attachment,
|
||||
id_category,
|
||||
id_footprint,
|
||||
id_part_unit,
|
||||
id_manufacturer,
|
||||
order_orderdetails_id,
|
||||
built_project_id,
|
||||
datetime_added,
|
||||
name,
|
||||
last_modified,
|
||||
needs_review,
|
||||
tags,
|
||||
mass,
|
||||
description,
|
||||
comment,
|
||||
visible,
|
||||
favorite,
|
||||
minamount,
|
||||
manufacturer_product_url,
|
||||
manufacturer_product_number,
|
||||
manufacturing_status,
|
||||
order_quantity,
|
||||
manual_order,
|
||||
ipn,
|
||||
provider_reference_provider_key,
|
||||
provider_reference_provider_id,
|
||||
provider_reference_provider_url,
|
||||
provider_reference_last_updated,
|
||||
eda_info_reference_prefix,
|
||||
eda_info_value,
|
||||
eda_info_invisible,
|
||||
eda_info_exclude_from_bom,
|
||||
eda_info_exclude_from_board,
|
||||
eda_info_exclude_from_sim,
|
||||
eda_info_kicad_symbol,
|
||||
eda_info_kicad_footprint)
|
||||
SELECT
|
||||
id,
|
||||
id_preview_attachment,
|
||||
id_category,
|
||||
id_footprint,
|
||||
id_part_unit,
|
||||
id_manufacturer,
|
||||
order_orderdetails_id,
|
||||
built_project_id,
|
||||
datetime_added,
|
||||
name,
|
||||
last_modified,
|
||||
needs_review,
|
||||
tags,
|
||||
mass,
|
||||
description,
|
||||
comment,
|
||||
visible,
|
||||
favorite,
|
||||
minamount,
|
||||
manufacturer_product_url,
|
||||
manufacturer_product_number,
|
||||
manufacturing_status,
|
||||
order_quantity,
|
||||
manual_order,
|
||||
ipn,
|
||||
provider_reference_provider_key,
|
||||
provider_reference_provider_id,
|
||||
provider_reference_provider_url,
|
||||
provider_reference_last_updated,
|
||||
eda_info_reference_prefix,
|
||||
eda_info_value,
|
||||
eda_info_invisible,
|
||||
eda_info_exclude_from_bom,
|
||||
eda_info_exclude_from_board,
|
||||
eda_info_exclude_from_sim,
|
||||
eda_info_kicad_symbol,
|
||||
eda_info_kicad_footprint
|
||||
FROM __temp__parts
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE __temp__parts
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX parts_idx_name ON parts (name)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX parts_idx_ipn ON parts (ipn)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX parts_idx_datet_name_last_id_needs ON parts (datetime_added, name, last_modified, id, needs_review)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON parts (built_project_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON parts (order_orderdetails_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON parts (ipn)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FEEA7100A1 ON parts (id_preview_attachment)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FE7E371A10 ON parts (id_footprint)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FE5697F554 ON parts (id_category)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FE2626CEF9 ON parts (id_part_unit)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FE1ECB93AE ON parts (id_manufacturer)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FEA3ED1215 ON parts (id_part_custom_state)
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function sqLiteDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TEMPORARY TABLE __temp__parts AS
|
||||
SELECT
|
||||
id,
|
||||
id_preview_attachment,
|
||||
id_category,
|
||||
id_footprint,
|
||||
id_part_unit,
|
||||
id_manufacturer,
|
||||
order_orderdetails_id,
|
||||
built_project_id,
|
||||
datetime_added,
|
||||
name,
|
||||
last_modified,
|
||||
needs_review,
|
||||
tags,
|
||||
mass,
|
||||
description,
|
||||
comment,
|
||||
visible,
|
||||
favorite,
|
||||
minamount,
|
||||
manufacturer_product_url,
|
||||
manufacturer_product_number,
|
||||
manufacturing_status,
|
||||
order_quantity,
|
||||
manual_order,
|
||||
ipn,
|
||||
provider_reference_provider_key,
|
||||
provider_reference_provider_id,
|
||||
provider_reference_provider_url,
|
||||
provider_reference_last_updated,
|
||||
eda_info_reference_prefix,
|
||||
eda_info_value,
|
||||
eda_info_invisible,
|
||||
eda_info_exclude_from_bom,
|
||||
eda_info_exclude_from_board,
|
||||
eda_info_exclude_from_sim,
|
||||
eda_info_kicad_symbol,
|
||||
eda_info_kicad_footprint
|
||||
FROM "parts"
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE "parts"
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE "parts" (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
id_preview_attachment INTEGER DEFAULT NULL,
|
||||
id_category INTEGER NOT NULL,
|
||||
id_footprint INTEGER DEFAULT NULL,
|
||||
id_part_unit INTEGER DEFAULT NULL,
|
||||
id_manufacturer INTEGER DEFAULT NULL,
|
||||
order_orderdetails_id INTEGER DEFAULT NULL,
|
||||
built_project_id INTEGER DEFAULT NULL,
|
||||
datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
needs_review BOOLEAN NOT NULL,
|
||||
tags CLOB NOT NULL,
|
||||
mass DOUBLE PRECISION DEFAULT NULL,
|
||||
description CLOB NOT NULL,
|
||||
comment CLOB NOT NULL,
|
||||
visible BOOLEAN NOT NULL,
|
||||
favorite BOOLEAN NOT NULL,
|
||||
minamount DOUBLE PRECISION NOT NULL,
|
||||
manufacturer_product_url CLOB NOT NULL,
|
||||
manufacturer_product_number VARCHAR(255) NOT NULL,
|
||||
manufacturing_status VARCHAR(255) DEFAULT NULL,
|
||||
order_quantity INTEGER NOT NULL,
|
||||
manual_order BOOLEAN NOT NULL,
|
||||
ipn VARCHAR(100) DEFAULT NULL,
|
||||
provider_reference_provider_key VARCHAR(255) DEFAULT NULL,
|
||||
provider_reference_provider_id VARCHAR(255) DEFAULT NULL,
|
||||
provider_reference_provider_url VARCHAR(255) DEFAULT NULL,
|
||||
provider_reference_last_updated DATETIME DEFAULT NULL,
|
||||
eda_info_reference_prefix VARCHAR(255) DEFAULT NULL,
|
||||
eda_info_value VARCHAR(255) DEFAULT NULL,
|
||||
eda_info_invisible BOOLEAN DEFAULT NULL,
|
||||
eda_info_exclude_from_bom BOOLEAN DEFAULT NULL,
|
||||
eda_info_exclude_from_board BOOLEAN DEFAULT NULL,
|
||||
eda_info_exclude_from_sim BOOLEAN DEFAULT NULL,
|
||||
eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL,
|
||||
eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL,
|
||||
CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES "categories" (id) NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES "footprints" (id) NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES "manufacturers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES "orderdetails" (id) NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
INSERT INTO "parts" (
|
||||
id,
|
||||
id_preview_attachment,
|
||||
id_category,
|
||||
id_footprint,
|
||||
id_part_unit,
|
||||
id_manufacturer,
|
||||
order_orderdetails_id,
|
||||
built_project_id,
|
||||
datetime_added,
|
||||
name,
|
||||
last_modified,
|
||||
needs_review,
|
||||
tags,
|
||||
mass,
|
||||
description,
|
||||
comment,
|
||||
visible,
|
||||
favorite,
|
||||
minamount,
|
||||
manufacturer_product_url,
|
||||
manufacturer_product_number,
|
||||
manufacturing_status,
|
||||
order_quantity,
|
||||
manual_order,
|
||||
ipn,
|
||||
provider_reference_provider_key,
|
||||
provider_reference_provider_id,
|
||||
provider_reference_provider_url,
|
||||
provider_reference_last_updated,
|
||||
eda_info_reference_prefix,
|
||||
eda_info_value,
|
||||
eda_info_invisible,
|
||||
eda_info_exclude_from_bom,
|
||||
eda_info_exclude_from_board,
|
||||
eda_info_exclude_from_sim,
|
||||
eda_info_kicad_symbol,
|
||||
eda_info_kicad_footprint
|
||||
) SELECT
|
||||
id,
|
||||
id_preview_attachment,
|
||||
id_category,
|
||||
id_footprint,
|
||||
id_part_unit,
|
||||
id_manufacturer,
|
||||
order_orderdetails_id,
|
||||
built_project_id,
|
||||
datetime_added,
|
||||
name,
|
||||
last_modified,
|
||||
needs_review,
|
||||
tags,
|
||||
mass,
|
||||
description,
|
||||
comment,
|
||||
visible,
|
||||
favorite,
|
||||
minamount,
|
||||
manufacturer_product_url,
|
||||
manufacturer_product_number,
|
||||
manufacturing_status,
|
||||
order_quantity,
|
||||
manual_order,
|
||||
ipn,
|
||||
provider_reference_provider_key,
|
||||
provider_reference_provider_id,
|
||||
provider_reference_provider_url,
|
||||
provider_reference_last_updated,
|
||||
eda_info_reference_prefix,
|
||||
eda_info_value,
|
||||
eda_info_invisible,
|
||||
eda_info_exclude_from_bom,
|
||||
eda_info_exclude_from_board,
|
||||
eda_info_exclude_from_sim,
|
||||
eda_info_kicad_symbol,
|
||||
eda_info_kicad_footprint
|
||||
FROM __temp__parts
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE __temp__parts
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON "parts" (ipn)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FEEA7100A1 ON "parts" (id_preview_attachment)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FE5697F554 ON "parts" (id_category)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FE7E371A10 ON "parts" (id_footprint)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FE2626CEF9 ON "parts" (id_part_unit)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FE1ECB93AE ON "parts" (id_manufacturer)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON "parts" (order_orderdetails_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON "parts" (built_project_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX parts_idx_datet_name_last_id_needs ON "parts" (datetime_added, name, last_modified, id, needs_review)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX parts_idx_name ON "parts" (name)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX parts_idx_ipn ON "parts" (ipn)
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE "part_custom_states"
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function postgreSQLUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE "part_custom_states" (
|
||||
id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
parent_id INT DEFAULT NULL,
|
||||
id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
comment TEXT NOT NULL,
|
||||
not_selectable BOOLEAN NOT NULL,
|
||||
alternative_names TEXT DEFAULT NULL,
|
||||
last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_F552745D727ACA70 ON "part_custom_states" (parent_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_F552745DEA7100A1 ON "part_custom_states" (id_preview_attachment)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE "part_custom_states"
|
||||
ADD CONSTRAINT FK_F552745D727ACA70
|
||||
FOREIGN KEY (parent_id) REFERENCES "part_custom_states" (id) NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE "part_custom_states"
|
||||
ADD CONSTRAINT FK_F552745DEA7100A1
|
||||
FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
SQL);
|
||||
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE parts ADD id_part_custom_state INT DEFAULT NULL
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE parts ADD CONSTRAINT FK_6940A7FEA3ED1215 FOREIGN KEY (id_part_custom_state) REFERENCES "part_custom_states" (id) NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FEA3ED1215 ON parts (id_part_custom_state)
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function postgreSQLDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FEA3ED1215
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP INDEX IDX_6940A7FEA3ED1215
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE "parts" DROP id_part_custom_state
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE "part_custom_states" DROP CONSTRAINT FK_F552745D727ACA70
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE "part_custom_states" DROP CONSTRAINT FK_F552745DEA7100A1
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE "part_custom_states"
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
|
|
@ -50,7 +50,7 @@
|
|||
"bootbox": "^6.0.0",
|
||||
"bootswatch": "^5.1.3",
|
||||
"bs-custom-file-input": "^1.3.4",
|
||||
"ckeditor5": "^46.0.0",
|
||||
"ckeditor5": "^47.0.0",
|
||||
"clipboard": "^2.0.4",
|
||||
"compression-webpack-plugin": "^11.1.0",
|
||||
"datatables.net": "^2.0.0",
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 482 B |
|
Before Width: | Height: | Size: 600 B |
|
Before Width: | Height: | Size: 352 B |
|
Before Width: | Height: | Size: 364 B |
|
Before Width: | Height: | Size: 489 B |
|
Before Width: | Height: | Size: 558 B |
|
Before Width: | Height: | Size: 477 B |
|
Before Width: | Height: | Size: 346 B |
|
Before Width: | Height: | Size: 476 B |
|
Before Width: | Height: | Size: 591 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 779 B |
|
Before Width: | Height: | Size: 1,004 B |
|
Before Width: | Height: | Size: 645 B |
|
Before Width: | Height: | Size: 459 B |
|
Before Width: | Height: | Size: 1 KiB |
|
Before Width: | Height: | Size: 362 B |
|
Before Width: | Height: | Size: 471 B |
|
Before Width: | Height: | Size: 510 B |
|
Before Width: | Height: | Size: 134 B |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 1,000 B |
|
Before Width: | Height: | Size: 96 B |
|
Before Width: | Height: | Size: 23 KiB |
|
|
@ -1,131 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
width="384"
|
||||
height="448"
|
||||
viewBox="0 0 384 448"
|
||||
id="svg7"
|
||||
sodipodi:docname="file_all.svg"
|
||||
inkscape:version="0.92.1 r15371">
|
||||
<metadata
|
||||
id="metadata13">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs11" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
id="namedview9"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.52678571"
|
||||
inkscape:cx="192"
|
||||
inkscape:cy="192.54785"
|
||||
inkscape:window-x="1272"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg7" />
|
||||
<g
|
||||
id="icomoon-ignore" />
|
||||
<path
|
||||
d="M367 95c9.25 9.25 17 27.75 17 41v288c0 13.25-10.75 24-24 24h-336c-13.25 0-24-10.75-24-24v-400c0-13.25 10.75-24 24-24h224c13.25 0 31.75 7.75 41 17zM256 34v94h94c-1.5-4.25-3.75-8.5-5.5-10.25l-78.25-78.25c-1.75-1.75-6-4-10.25-5.5zM352 416v-256h-104c-13.25 0-24-10.75-24-24v-104h-192v384h320z"
|
||||
id="path5"
|
||||
style="fill:#1a1a1a" />
|
||||
<flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot3687"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;font-family:sans-serif;font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;letter-spacing:0px;word-spacing:0px;"><flowRegion
|
||||
id="flowRegion3689"
|
||||
style="fill:#ffffff;"><rect
|
||||
id="rect3691"
|
||||
width="251.68207"
|
||||
height="110.74011"
|
||||
x="69.128677"
|
||||
y="214.43904"
|
||||
style="fill:#ffffff;" /></flowRegion><flowPara
|
||||
id="flowPara3693" /></flowRoot> <g
|
||||
aria-label="ALL "
|
||||
transform="matrix(1.7053159,0,0,1.4411413,-124.25849,-88.403923)"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#1a1a1a;fill-opacity:1;stroke:none"
|
||||
id="flowRoot3699">
|
||||
<path
|
||||
d="m 114.24512,247.89827 -6.32813,16.17188 h 6.9375 v 4.64062 H 98.260742 v -4.64062 h 4.031248 l 25.92188,-65.90625 h 5.57812 l 25.92188,65.90625 h 4.03125 v 4.64062 h -20.90625 v -4.64062 h 6.32812 l -6.375,-16.17188 z m 1.82812,-4.64062 h 24.89063 l -12.46875,-31.64063 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:96px;font-family:'Lucida Fax';-inkscape-font-specification:'Lucida Fax';text-align:center;text-anchor:middle;fill:#1a1a1a"
|
||||
id="path40" />
|
||||
<path
|
||||
d="m 218.72949,268.71077 h -48.5625 v -4.64062 h 6.9375 v -60.14063 h -6.9375 v -4.59375 h 23.10938 v 4.59375 h -6.32813 v 59.57813 h 26.01563 v -8.67188 h 5.76562 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:96px;font-family:'Lucida Fax';-inkscape-font-specification:'Lucida Fax';text-align:center;text-anchor:middle;fill:#1a1a1a"
|
||||
id="path42" />
|
||||
<path
|
||||
d="m 273.66699,268.71077 h -48.5625 v -4.64062 h 6.9375 v -60.14063 h -6.9375 v -4.59375 h 23.10938 v 4.59375 h -6.32813 v 59.57813 h 26.01563 v -8.67188 h 5.76562 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:96px;font-family:'Lucida Fax';-inkscape-font-specification:'Lucida Fax';text-align:center;text-anchor:middle;fill:#1a1a1a"
|
||||
id="path44" />
|
||||
</g>
|
||||
<g
|
||||
aria-label="DATASHEET"
|
||||
transform="matrix(1.3097344,0,0,1.4436797,-64.263952,-115.73324)"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#1a1a1a;fill-opacity:1;stroke:none"
|
||||
id="flowRoot3709">
|
||||
<path
|
||||
d="m 85.748047,302.43555 v 22.67578 h 4.765625 q 6.035156,0 8.828125,-2.73438 2.812503,-2.73437 2.812503,-8.63281 0,-5.85937 -2.812503,-8.57422 -2.792969,-2.73437 -8.828125,-2.73437 z m -3.945313,-3.24219 h 8.105469 q 8.476563,0 12.441407,3.53516 3.96484,3.51562 3.96484,11.01562 0,7.53906 -3.98437,11.07422 -3.984377,3.53516 -12.421877,3.53516 h -8.105469 z"
|
||||
style="text-align:center;text-anchor:middle;fill:#1a1a1a"
|
||||
id="path21" />
|
||||
<path
|
||||
d="m 121.62695,303.08008 -5.35156,14.51172 h 10.72266 z m -2.22656,-3.88672 h 4.47266 l 11.11328,29.16016 h -4.10156 l -2.65625,-7.48047 h -13.14454 l -2.65625,7.48047 h -4.16015 z"
|
||||
style="text-align:center;text-anchor:middle;fill:#1a1a1a"
|
||||
id="path23" />
|
||||
<path
|
||||
d="m 132.05664,299.19336 h 24.66797 v 3.32031 h -10.35156 v 25.83985 h -3.96485 v -25.83985 h -10.35156 z"
|
||||
style="text-align:center;text-anchor:middle;fill:#1a1a1a"
|
||||
id="path25" />
|
||||
<path
|
||||
d="m 167.17383,303.08008 -5.35156,14.51172 h 10.72265 z m -2.22656,-3.88672 h 4.47265 l 11.11328,29.16016 h -4.10156 l -2.65625,-7.48047 h -13.14453 l -2.65625,7.48047 h -4.16016 z"
|
||||
style="text-align:center;text-anchor:middle;fill:#1a1a1a"
|
||||
id="path27" />
|
||||
<path
|
||||
d="m 202.25195,300.15039 v 3.84766 q -2.24609,-1.07422 -4.23828,-1.60157 -1.99219,-0.52734 -3.84765,-0.52734 -3.22266,0 -4.98047,1.25 -1.73828,1.25 -1.73828,3.55469 0,1.93359 1.15234,2.92969 1.17187,0.97656 4.41406,1.58203 l 2.38281,0.48828 q 4.41407,0.83984 6.50391,2.96875 2.10938,2.10937 2.10938,5.66406 0,4.23828 -2.85157,6.42578 -2.83203,2.1875 -8.32031,2.1875 -2.07031,0 -4.41406,-0.46875 -2.32422,-0.46875 -4.82422,-1.38672 v -4.0625 q 2.40234,1.34766 4.70703,2.03125 2.30469,0.6836 4.53125,0.6836 3.37891,0 5.21484,-1.32813 1.83594,-1.32812 1.83594,-3.78906 0,-2.14844 -1.32812,-3.35938 -1.3086,-1.21093 -4.31641,-1.8164 l -2.40234,-0.46875 q -4.41407,-0.87891 -6.38672,-2.75391 -1.97266,-1.875 -1.97266,-5.21484 0,-3.86719 2.71485,-6.09375 2.73437,-2.22656 7.51953,-2.22656 2.05078,0 4.17968,0.37109 2.12891,0.37109 4.35547,1.11328 z"
|
||||
style="text-align:center;text-anchor:middle;fill:#1a1a1a"
|
||||
id="path29" />
|
||||
<path
|
||||
d="m 210.16211,299.19336 h 3.94531 v 11.95312 h 14.33594 v -11.95312 h 3.94531 v 29.16016 h -3.94531 V 314.4668 h -14.33594 v 13.88672 h -3.94531 z"
|
||||
style="text-align:center;text-anchor:middle;fill:#1a1a1a"
|
||||
id="path31" />
|
||||
<path
|
||||
d="m 240.24023,299.19336 h 18.4375 v 3.32031 h -14.49218 v 8.63281 h 13.88672 v 3.32032 h -13.88672 v 10.5664 h 14.84375 v 3.32032 h -18.78907 z"
|
||||
style="text-align:center;text-anchor:middle;fill:#1a1a1a"
|
||||
id="path33" />
|
||||
<path
|
||||
d="m 265.55273,299.19336 h 18.4375 v 3.32031 h -14.49218 v 8.63281 h 13.88672 v 3.32032 h -13.88672 v 10.5664 h 14.84375 v 3.32032 h -18.78907 z"
|
||||
style="text-align:center;text-anchor:middle;fill:#1a1a1a"
|
||||
id="path35" />
|
||||
<path
|
||||
d="m 286.82227,299.19336 h 24.66796 v 3.32031 h -10.35156 v 25.83985 h -3.96484 v -25.83985 h -10.35156 z"
|
||||
style="text-align:center;text-anchor:middle;fill:#1a1a1a"
|
||||
id="path37" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 7.4 KiB |
|
|
@ -1,90 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
width="384"
|
||||
height="448"
|
||||
viewBox="0 0 384 448"
|
||||
id="svg7"
|
||||
sodipodi:docname="file_dc.svg"
|
||||
inkscape:version="0.92.1 r15371">
|
||||
<metadata
|
||||
id="metadata13">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs11" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
id="namedview9"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.0535715"
|
||||
inkscape:cx="192"
|
||||
inkscape:cy="219.39394"
|
||||
inkscape:window-x="1272"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg7" />
|
||||
<g
|
||||
id="icomoon-ignore" />
|
||||
<path
|
||||
d="M367 95c9.25 9.25 17 27.75 17 41v288c0 13.25-10.75 24-24 24h-336c-13.25 0-24-10.75-24-24v-400c0-13.25 10.75-24 24-24h224c13.25 0 31.75 7.75 41 17zM256 34v94h94c-1.5-4.25-3.75-8.5-5.5-10.25l-78.25-78.25c-1.75-1.75-6-4-10.25-5.5zM352 416v-256h-104c-13.25 0-24-10.75-24-24v-104h-192v384h320z"
|
||||
id="path5"
|
||||
style="fill:#1a1a1a" />
|
||||
<rect
|
||||
id="rect3685"
|
||||
width="289.93774"
|
||||
height="149.66695"
|
||||
x="48.32296"
|
||||
y="188.2641"
|
||||
style="fill:#1a1a1a" />
|
||||
<flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot3687"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;font-family:sans-serif;font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;letter-spacing:0px;word-spacing:0px;"><flowRegion
|
||||
id="flowRegion3689"
|
||||
style="fill:#ffffff;"><rect
|
||||
id="rect3691"
|
||||
width="251.68207"
|
||||
height="110.74011"
|
||||
x="69.128677"
|
||||
y="214.43904"
|
||||
style="fill:#ffffff;" /></flowRegion><flowPara
|
||||
id="flowPara3693" /></flowRoot> <text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:191.63136292px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:4.79078484"
|
||||
x="33.330128"
|
||||
y="354.68042"
|
||||
id="text3697"
|
||||
transform="scale(1.0793658,0.92646993)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3695"
|
||||
x="33.330128"
|
||||
y="354.68042"
|
||||
style="fill:#ffffff;stroke-width:4.79078484">DC</tspan></text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3 KiB |
|
|
@ -1,5 +0,0 @@
|
|||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="28" viewBox="0 0 24 28">
|
||||
<title>google</title>
|
||||
<path d="M12 12.281h11.328c0.109 0.609 0.187 1.203 0.187 2 0 6.844-4.594 11.719-11.516 11.719-6.641 0-12-5.359-12-12s5.359-12 12-12c3.234 0 5.953 1.188 8.047 3.141l-3.266 3.141c-0.891-0.859-2.453-1.859-4.781-1.859-4.094 0-7.438 3.391-7.438 7.578s3.344 7.578 7.438 7.578c4.75 0 6.531-3.406 6.813-5.172h-6.813v-4.125z"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 485 B |
|
|
@ -1,5 +0,0 @@
|
|||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="28" viewBox="0 0 24 28">
|
||||
<title>cog</title>
|
||||
<path d="M16 14c0-2.203-1.797-4-4-4s-4 1.797-4 4 1.797 4 4 4 4-1.797 4-4zM24 12.297v3.469c0 0.234-0.187 0.516-0.438 0.562l-2.891 0.438c-0.172 0.5-0.359 0.969-0.609 1.422 0.531 0.766 1.094 1.453 1.672 2.156 0.094 0.109 0.156 0.25 0.156 0.391s-0.047 0.25-0.141 0.359c-0.375 0.5-2.484 2.797-3.016 2.797-0.141 0-0.281-0.063-0.406-0.141l-2.156-1.687c-0.453 0.234-0.938 0.438-1.422 0.594-0.109 0.953-0.203 1.969-0.453 2.906-0.063 0.25-0.281 0.438-0.562 0.438h-3.469c-0.281 0-0.531-0.203-0.562-0.469l-0.438-2.875c-0.484-0.156-0.953-0.344-1.406-0.578l-2.203 1.672c-0.109 0.094-0.25 0.141-0.391 0.141s-0.281-0.063-0.391-0.172c-0.828-0.75-1.922-1.719-2.578-2.625-0.078-0.109-0.109-0.234-0.109-0.359 0-0.141 0.047-0.25 0.125-0.359 0.531-0.719 1.109-1.406 1.641-2.141-0.266-0.5-0.484-1.016-0.641-1.547l-2.859-0.422c-0.266-0.047-0.453-0.297-0.453-0.562v-3.469c0-0.234 0.187-0.516 0.422-0.562l2.906-0.438c0.156-0.5 0.359-0.969 0.609-1.437-0.531-0.75-1.094-1.453-1.672-2.156-0.094-0.109-0.156-0.234-0.156-0.375s0.063-0.25 0.141-0.359c0.375-0.516 2.484-2.797 3.016-2.797 0.141 0 0.281 0.063 0.406 0.156l2.156 1.672c0.453-0.234 0.938-0.438 1.422-0.594 0.109-0.953 0.203-1.969 0.453-2.906 0.063-0.25 0.281-0.438 0.562-0.438h3.469c0.281 0 0.531 0.203 0.562 0.469l0.438 2.875c0.484 0.156 0.953 0.344 1.406 0.578l2.219-1.672c0.094-0.094 0.234-0.141 0.375-0.141s0.281 0.063 0.391 0.156c0.828 0.766 1.922 1.734 2.578 2.656 0.078 0.094 0.109 0.219 0.109 0.344 0 0.141-0.047 0.25-0.125 0.359-0.531 0.719-1.109 1.406-1.641 2.141 0.266 0.5 0.484 1.016 0.641 1.531l2.859 0.438c0.266 0.047 0.453 0.297 0.453 0.562z"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
|
|
@ -1,98 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
width="384"
|
||||
height="448"
|
||||
viewBox="0 0 384 448"
|
||||
id="svg7"
|
||||
sodipodi:docname="file_reichelt.svg"
|
||||
inkscape:version="0.92.1 r15371">
|
||||
<metadata
|
||||
id="metadata13">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs11" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
id="namedview9"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.74498751"
|
||||
inkscape:cx="192"
|
||||
inkscape:cy="218.60367"
|
||||
inkscape:window-x="1272"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg7" />
|
||||
<g
|
||||
id="icomoon-ignore" />
|
||||
<path
|
||||
d="M367 95c9.25 9.25 17 27.75 17 41v288c0 13.25-10.75 24-24 24h-336c-13.25 0-24-10.75-24-24v-400c0-13.25 10.75-24 24-24h224c13.25 0 31.75 7.75 41 17zM256 34v94h94c-1.5-4.25-3.75-8.5-5.5-10.25l-78.25-78.25c-1.75-1.75-6-4-10.25-5.5zM352 416v-256h-104c-13.25 0-24-10.75-24-24v-104h-192v384h320z"
|
||||
id="path5"
|
||||
style="fill:#1a1a1a" />
|
||||
<flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot3687"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;font-family:sans-serif;font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;letter-spacing:0px;word-spacing:0px;"><flowRegion
|
||||
id="flowRegion3689"
|
||||
style="fill:#ffffff;"><rect
|
||||
id="rect3691"
|
||||
width="251.68207"
|
||||
height="110.74011"
|
||||
x="69.128677"
|
||||
y="214.43904"
|
||||
style="fill:#ffffff;" /></flowRegion><flowPara
|
||||
id="flowPara3693" /></flowRoot> <rect
|
||||
style="fill:#666666;stroke-width:1.27060354"
|
||||
id="rect3719"
|
||||
width="150"
|
||||
height="150"
|
||||
x="98.65937"
|
||||
y="204.70981" />
|
||||
<rect
|
||||
style="fill:#1a1a1a;stroke-width:1.30443311"
|
||||
id="rect3717"
|
||||
width="150"
|
||||
height="150"
|
||||
x="130.20366"
|
||||
y="175.17915" />
|
||||
<rect
|
||||
style="fill:#ffffff;stroke-width:1.07838833"
|
||||
id="rect3721"
|
||||
width="39.59798"
|
||||
height="138.9285"
|
||||
x="153.02271"
|
||||
y="198.33139" />
|
||||
<circle
|
||||
style="fill:#ffffff;stroke-width:1.69401228"
|
||||
id="path3723"
|
||||
cx="227.01927"
|
||||
cy="214.12033"
|
||||
r="30.69738" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 706 B |
|
Before Width: | Height: | Size: 606 B |
|
|
@ -121,6 +121,11 @@ class ImportPartKeeprCommand extends Command
|
|||
$count = $this->datastructureImporter->importPartUnits($data);
|
||||
$io->success('Imported '.$count.' measurement units.');
|
||||
|
||||
//Import the custom states
|
||||
$io->info('Importing custom states...');
|
||||
$count = $this->datastructureImporter->importPartCustomStates($data);
|
||||
$io->success('Imported '.$count.' custom states.');
|
||||
|
||||
//Import manufacturers
|
||||
$io->info('Importing manufacturers...');
|
||||
$count = $this->datastructureImporter->importManufacturers($data);
|
||||
|
|
|
|||
|
|
@ -232,6 +232,7 @@ abstract class BaseAdminController extends AbstractController
|
|||
'timeTravel' => $timeTravel_timestamp,
|
||||
'repo' => $repo,
|
||||
'partsContainingElement' => $repo instanceof PartsContainingRepositoryInterface,
|
||||
'showParameters' => !($this instanceof PartCustomStateController),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -382,6 +383,7 @@ abstract class BaseAdminController extends AbstractController
|
|||
'import_form' => $import_form,
|
||||
'mass_creation_form' => $mass_creation_form,
|
||||
'route_base' => $this->route_base,
|
||||
'showParameters' => !($this instanceof PartCustomStateController),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
83
src/Controller/AdminPages/PartCustomStateController.php
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\AdminPages;
|
||||
|
||||
use App\Entity\Attachments\PartCustomStateAttachment;
|
||||
use App\Entity\Parameters\PartCustomStateParameter;
|
||||
use App\Entity\Parts\PartCustomState;
|
||||
use App\Form\AdminPages\PartCustomStateAdminForm;
|
||||
use App\Services\ImportExportSystem\EntityExporter;
|
||||
use App\Services\ImportExportSystem\EntityImporter;
|
||||
use App\Services\Trees\StructuralElementRecursionHelper;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
/**
|
||||
* @see \App\Tests\Controller\AdminPages\PartCustomStateControllerTest
|
||||
*/
|
||||
#[Route(path: '/part_custom_state')]
|
||||
class PartCustomStateController extends BaseAdminController
|
||||
{
|
||||
protected string $entity_class = PartCustomState::class;
|
||||
protected string $twig_template = 'admin/part_custom_state_admin.html.twig';
|
||||
protected string $form_class = PartCustomStateAdminForm::class;
|
||||
protected string $route_base = 'part_custom_state';
|
||||
protected string $attachment_class = PartCustomStateAttachment::class;
|
||||
protected ?string $parameter_class = PartCustomStateParameter::class;
|
||||
|
||||
#[Route(path: '/{id}', name: 'part_custom_state_delete', methods: ['DELETE'])]
|
||||
public function delete(Request $request, PartCustomState $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
|
||||
{
|
||||
return $this->_delete($request, $entity, $recursionHelper);
|
||||
}
|
||||
|
||||
#[Route(path: '/{id}/edit/{timestamp}', name: 'part_custom_state_edit', requirements: ['id' => '\d+'])]
|
||||
#[Route(path: '/{id}', requirements: ['id' => '\d+'])]
|
||||
public function edit(PartCustomState $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
|
||||
{
|
||||
return $this->_edit($entity, $request, $em, $timestamp);
|
||||
}
|
||||
|
||||
#[Route(path: '/new', name: 'part_custom_state_new')]
|
||||
#[Route(path: '/{id}/clone', name: 'part_custom_state_clone')]
|
||||
#[Route(path: '/')]
|
||||
public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?PartCustomState $entity = null): Response
|
||||
{
|
||||
return $this->_new($request, $em, $importer, $entity);
|
||||
}
|
||||
|
||||
#[Route(path: '/export', name: 'part_custom_state_export_all')]
|
||||
public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportAll($em, $exporter, $request);
|
||||
}
|
||||
|
||||
#[Route(path: '/{id}/export', name: 'part_custom_state_export')]
|
||||
public function exportEntity(PartCustomState $entity, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportEntity($entity, $exporter, $request);
|
||||
}
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ namespace App\Controller;
|
|||
|
||||
use App\Entity\Parts\Manufacturer;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Exceptions\OAuthReconnectRequiredException;
|
||||
use App\Form\InfoProviderSystem\PartSearchType;
|
||||
use App\Services\InfoProviderSystem\ExistingPartFinder;
|
||||
use App\Services\InfoProviderSystem\PartInfoRetriever;
|
||||
|
|
@ -175,8 +176,11 @@ class InfoProviderController extends AbstractController
|
|||
$this->addFlash('error',$e->getMessage());
|
||||
//Log the exception
|
||||
$exceptionLogger->error('Error during info provider search: ' . $e->getMessage(), ['exception' => $e]);
|
||||
} catch (OAuthReconnectRequiredException $e) {
|
||||
$this->addFlash('error', t('info_providers.search.error.oauth_reconnect', ['%provider%' => $e->getProviderName()]));
|
||||
}
|
||||
|
||||
|
||||
// modify the array to an array of arrays that has a field for a matching local Part
|
||||
// the advantage to use that format even when we don't look for local parts is that we
|
||||
// always work with the same interface
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ use App\Exceptions\InvalidRegexException;
|
|||
use App\Form\Filters\PartFilterType;
|
||||
use App\Services\Parts\PartsTableActionHandler;
|
||||
use App\Services\Trees\NodesListBuilder;
|
||||
use App\Settings\BehaviorSettings\SidebarSettings;
|
||||
use App\Settings\BehaviorSettings\TableSettings;
|
||||
use Doctrine\DBAL\Exception\DriverException;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
|
@ -56,11 +57,21 @@ class PartListsController extends AbstractController
|
|||
private readonly NodesListBuilder $nodesListBuilder,
|
||||
private readonly DataTableFactory $dataTableFactory,
|
||||
private readonly TranslatorInterface $translator,
|
||||
private readonly TableSettings $tableSettings
|
||||
private readonly TableSettings $tableSettings,
|
||||
private readonly SidebarSettings $sidebarSettings,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the filter operator to use by default (INCLUDING_CHILDREN or =)
|
||||
* @return string
|
||||
*/
|
||||
private function getFilterOperator(): string
|
||||
{
|
||||
return $this->sidebarSettings->dataStructureNodesTableIncludeChildren ? 'INCLUDING_CHILDREN' : '=';
|
||||
}
|
||||
|
||||
#[Route(path: '/table/action', name: 'table_action', methods: ['POST'])]
|
||||
public function tableAction(Request $request, PartsTableActionHandler $actionHandler): Response
|
||||
{
|
||||
|
|
@ -203,7 +214,7 @@ class PartListsController extends AbstractController
|
|||
return $this->showListWithFilter($request,
|
||||
'parts/lists/category_list.html.twig',
|
||||
function (PartFilter $filter) use ($category) {
|
||||
$filter->category->setOperator('INCLUDING_CHILDREN')->setValue($category);
|
||||
$filter->category->setOperator($this->getFilterOperator())->setValue($category);
|
||||
}, function (FormInterface $filterForm) {
|
||||
$this->disableFormFieldAfterCreation($filterForm->get('category')->get('value'));
|
||||
}, [
|
||||
|
|
@ -221,7 +232,7 @@ class PartListsController extends AbstractController
|
|||
return $this->showListWithFilter($request,
|
||||
'parts/lists/footprint_list.html.twig',
|
||||
function (PartFilter $filter) use ($footprint) {
|
||||
$filter->footprint->setOperator('INCLUDING_CHILDREN')->setValue($footprint);
|
||||
$filter->footprint->setOperator($this->getFilterOperator())->setValue($footprint);
|
||||
}, function (FormInterface $filterForm) {
|
||||
$this->disableFormFieldAfterCreation($filterForm->get('footprint')->get('value'));
|
||||
}, [
|
||||
|
|
@ -239,7 +250,7 @@ class PartListsController extends AbstractController
|
|||
return $this->showListWithFilter($request,
|
||||
'parts/lists/manufacturer_list.html.twig',
|
||||
function (PartFilter $filter) use ($manufacturer) {
|
||||
$filter->manufacturer->setOperator('INCLUDING_CHILDREN')->setValue($manufacturer);
|
||||
$filter->manufacturer->setOperator($this->getFilterOperator())->setValue($manufacturer);
|
||||
}, function (FormInterface $filterForm) {
|
||||
$this->disableFormFieldAfterCreation($filterForm->get('manufacturer')->get('value'));
|
||||
}, [
|
||||
|
|
@ -257,7 +268,7 @@ class PartListsController extends AbstractController
|
|||
return $this->showListWithFilter($request,
|
||||
'parts/lists/store_location_list.html.twig',
|
||||
function (PartFilter $filter) use ($storelocation) {
|
||||
$filter->storelocation->setOperator('INCLUDING_CHILDREN')->setValue($storelocation);
|
||||
$filter->storelocation->setOperator($this->getFilterOperator())->setValue($storelocation);
|
||||
}, function (FormInterface $filterForm) {
|
||||
$this->disableFormFieldAfterCreation($filterForm->get('storelocation')->get('value'));
|
||||
}, [
|
||||
|
|
@ -275,7 +286,7 @@ class PartListsController extends AbstractController
|
|||
return $this->showListWithFilter($request,
|
||||
'parts/lists/supplier_list.html.twig',
|
||||
function (PartFilter $filter) use ($supplier) {
|
||||
$filter->supplier->setOperator('INCLUDING_CHILDREN')->setValue($supplier);
|
||||
$filter->supplier->setOperator($this->getFilterOperator())->setValue($supplier);
|
||||
}, function (FormInterface $filterForm) {
|
||||
$this->disableFormFieldAfterCreation($filterForm->get('supplier')->get('value'));
|
||||
}, [
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ namespace App\DataFixtures;
|
|||
|
||||
use App\Entity\Attachments\AttachmentType;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use App\Entity\Parts\PartCustomState;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Entity\Parts\Category;
|
||||
use App\Entity\Parts\Footprint;
|
||||
|
|
@ -50,7 +51,7 @@ class DataStructureFixtures extends Fixture implements DependentFixtureInterface
|
|||
{
|
||||
//Reset autoincrement
|
||||
$types = [AttachmentType::class, Project::class, Category::class, Footprint::class, Manufacturer::class,
|
||||
MeasurementUnit::class, StorageLocation::class, Supplier::class,];
|
||||
MeasurementUnit::class, StorageLocation::class, Supplier::class, PartCustomState::class];
|
||||
|
||||
foreach ($types as $type) {
|
||||
$this->createNodesForClass($type, $manager);
|
||||
|
|
|
|||
|
|
@ -96,14 +96,15 @@ class TextConstraint extends AbstractConstraint
|
|||
|
||||
//The CONTAINS, LIKE, STARTS and ENDS operators use the LIKE operator, but we have to build the value string differently
|
||||
$like_value = null;
|
||||
$escaped_value = str_replace(['%', '_'], ['\%', '\_'], $this->value);
|
||||
if ($this->operator === 'LIKE') {
|
||||
$like_value = $this->value;
|
||||
$like_value = $this->value; //Here we do not escape anything, as the user may provide % and _ wildcards
|
||||
} elseif ($this->operator === 'STARTS') {
|
||||
$like_value = $this->value . '%';
|
||||
$like_value = $escaped_value . '%';
|
||||
} elseif ($this->operator === 'ENDS') {
|
||||
$like_value = '%' . $this->value;
|
||||
$like_value = '%' . $escaped_value;
|
||||
} elseif ($this->operator === 'CONTAINS') {
|
||||
$like_value = '%' . $this->value . '%';
|
||||
$like_value = '%' . $escaped_value . '%';
|
||||
}
|
||||
|
||||
if ($like_value !== null) {
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ use App\Entity\Parts\Category;
|
|||
use App\Entity\Parts\Footprint;
|
||||
use App\Entity\Parts\Manufacturer;
|
||||
use App\Entity\Parts\MeasurementUnit;
|
||||
use App\Entity\Parts\PartCustomState;
|
||||
use App\Entity\Parts\PartLot;
|
||||
use App\Entity\Parts\StorageLocation;
|
||||
use App\Entity\Parts\Supplier;
|
||||
|
|
@ -86,6 +87,7 @@ class PartFilter implements FilterInterface
|
|||
public readonly EntityConstraint $lotOwner;
|
||||
|
||||
public readonly EntityConstraint $measurementUnit;
|
||||
public readonly EntityConstraint $partCustomState;
|
||||
public readonly TextConstraint $manufacturer_product_url;
|
||||
public readonly TextConstraint $manufacturer_product_number;
|
||||
public readonly IntConstraint $attachmentsCount;
|
||||
|
|
@ -128,6 +130,7 @@ class PartFilter implements FilterInterface
|
|||
$this->favorite = new BooleanConstraint('part.favorite');
|
||||
$this->needsReview = new BooleanConstraint('part.needs_review');
|
||||
$this->measurementUnit = new EntityConstraint($nodesListBuilder, MeasurementUnit::class, 'part.partUnit');
|
||||
$this->partCustomState = new EntityConstraint($nodesListBuilder, PartCustomState::class, 'part.partCustomState');
|
||||
$this->mass = new NumberConstraint('part.mass');
|
||||
$this->dbId = new IntConstraint('part.id');
|
||||
$this->ipn = new TextConstraint('part.ipn');
|
||||
|
|
|
|||
|
|
@ -144,6 +144,8 @@ class PartSearchFilter implements FilterInterface
|
|||
if ($this->regex) {
|
||||
$queryBuilder->setParameter('search_query', $this->keyword);
|
||||
} else {
|
||||
//Escape % and _ characters in the keyword
|
||||
$this->keyword = str_replace(['%', '_'], ['\%', '\_'], $this->keyword);
|
||||
$queryBuilder->setParameter('search_query', '%' . $this->keyword . '%');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -174,6 +174,19 @@ final class PartsDataTable implements DataTableTypeInterface
|
|||
return $tmp;
|
||||
}
|
||||
])
|
||||
->add('partCustomState', TextColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.partCustomState'),
|
||||
'orderField' => 'NATSORT(_partCustomState.name)',
|
||||
'render' => function($value, Part $context): string {
|
||||
$partCustomState = $context->getPartCustomState();
|
||||
|
||||
if ($partCustomState === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return htmlspecialchars($partCustomState->getName());
|
||||
}
|
||||
])
|
||||
->add('addedDate', LocaleDateTimeColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.addedDate'),
|
||||
])
|
||||
|
|
@ -309,6 +322,7 @@ final class PartsDataTable implements DataTableTypeInterface
|
|||
->addSelect('footprint')
|
||||
->addSelect('manufacturer')
|
||||
->addSelect('partUnit')
|
||||
->addSelect('partCustomState')
|
||||
->addSelect('master_picture_attachment')
|
||||
->addSelect('footprint_attachment')
|
||||
->addSelect('partLots')
|
||||
|
|
@ -327,6 +341,7 @@ final class PartsDataTable implements DataTableTypeInterface
|
|||
->leftJoin('orderdetails.supplier', 'suppliers')
|
||||
->leftJoin('part.attachments', 'attachments')
|
||||
->leftJoin('part.partUnit', 'partUnit')
|
||||
->leftJoin('part.partCustomState', 'partCustomState')
|
||||
->leftJoin('part.parameters', 'parameters')
|
||||
->where('part.id IN (:ids)')
|
||||
->setParameter('ids', $ids)
|
||||
|
|
@ -344,6 +359,7 @@ final class PartsDataTable implements DataTableTypeInterface
|
|||
->addGroupBy('suppliers')
|
||||
->addGroupBy('attachments')
|
||||
->addGroupBy('partUnit')
|
||||
->addGroupBy('partCustomState')
|
||||
->addGroupBy('parameters');
|
||||
|
||||
//Get the results in the same order as the IDs were passed
|
||||
|
|
@ -415,6 +431,10 @@ final class PartsDataTable implements DataTableTypeInterface
|
|||
$builder->leftJoin('part.partUnit', '_partUnit');
|
||||
$builder->addGroupBy('_partUnit');
|
||||
}
|
||||
if (str_contains($dql, '_partCustomState')) {
|
||||
$builder->leftJoin('part.partCustomState', '_partCustomState');
|
||||
$builder->addGroupBy('_partCustomState');
|
||||
}
|
||||
if (str_contains($dql, '_parameters')) {
|
||||
$builder->leftJoin('part.parameters', '_parameters');
|
||||
//Do not group by many-to-* relations, as it would restrict the COUNT having clauses to be maximum 1
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ class ILike extends FunctionNode
|
|||
{
|
||||
$platform = $sqlWalker->getConnection()->getDatabasePlatform();
|
||||
|
||||
//
|
||||
if ($platform instanceof AbstractMySQLPlatform || $platform instanceof SQLitePlatform) {
|
||||
$operator = 'LIKE';
|
||||
} elseif ($platform instanceof PostgreSQLPlatform) {
|
||||
|
|
@ -66,6 +65,12 @@ class ILike extends FunctionNode
|
|||
throw new \RuntimeException('Platform ' . gettype($platform) . ' does not support case insensitive like expressions.');
|
||||
}
|
||||
|
||||
return '(' . $this->value->dispatch($sqlWalker) . ' ' . $operator . ' ' . $this->expr->dispatch($sqlWalker) . ')';
|
||||
$escape = "";
|
||||
if ($platform instanceof SQLitePlatform) {
|
||||
//SQLite needs ESCAPE explicitly defined backslash as escape character
|
||||
$escape = " ESCAPE '\\'";
|
||||
}
|
||||
|
||||
return '(' . $this->value->dispatch($sqlWalker) . ' ' . $operator . ' ' . $this->expr->dispatch($sqlWalker) . $escape . ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ use function in_array;
|
|||
#[DiscriminatorMap(typeProperty: '_type', mapping: self::API_DISCRIMINATOR_MAP)]
|
||||
abstract class Attachment extends AbstractNamedDBElement
|
||||
{
|
||||
private const ORM_DISCRIMINATOR_MAP = ['Part' => PartAttachment::class, 'Device' => ProjectAttachment::class,
|
||||
private const ORM_DISCRIMINATOR_MAP = ['Part' => PartAttachment::class, 'PartCustomState' => PartCustomStateAttachment::class, 'Device' => ProjectAttachment::class,
|
||||
'AttachmentType' => AttachmentTypeAttachment::class,
|
||||
'Category' => CategoryAttachment::class, 'Footprint' => FootprintAttachment::class, 'Manufacturer' => ManufacturerAttachment::class,
|
||||
'Currency' => CurrencyAttachment::class, 'Group' => GroupAttachment::class, 'MeasurementUnit' => MeasurementUnitAttachment::class,
|
||||
|
|
@ -107,7 +107,8 @@ abstract class Attachment extends AbstractNamedDBElement
|
|||
/*
|
||||
* The discriminator map used for API platform. The key should be the same as the api platform short type (the @type JSONLD field).
|
||||
*/
|
||||
private const API_DISCRIMINATOR_MAP = ["Part" => PartAttachment::class, "Project" => ProjectAttachment::class, "AttachmentType" => AttachmentTypeAttachment::class,
|
||||
private const API_DISCRIMINATOR_MAP = ["Part" => PartAttachment::class, "PartCustomState" => PartCustomStateAttachment::class, "Project" => ProjectAttachment::class,
|
||||
"AttachmentType" => AttachmentTypeAttachment::class,
|
||||
"Category" => CategoryAttachment::class, "Footprint" => FootprintAttachment::class, "Manufacturer" => ManufacturerAttachment::class,
|
||||
"Currency" => CurrencyAttachment::class, "Group" => GroupAttachment::class, "MeasurementUnit" => MeasurementUnitAttachment::class,
|
||||
"StorageLocation" => StorageLocationAttachment::class, "Supplier" => SupplierAttachment::class, "User" => UserAttachment::class, "LabelProfile" => LabelAttachment::class];
|
||||
|
|
|
|||
45
src/Entity/Attachments/PartCustomStateAttachment.php
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\Attachments;
|
||||
|
||||
use App\Entity\Parts\PartCustomState;
|
||||
use App\Serializer\APIPlatform\OverrideClassDenormalizer;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Serializer\Attribute\Context;
|
||||
|
||||
/**
|
||||
* An attachment attached to a part custom state element.
|
||||
* @extends Attachment<PartCustomState>
|
||||
*/
|
||||
#[UniqueEntity(['name', 'attachment_type', 'element'])]
|
||||
#[ORM\Entity]
|
||||
class PartCustomStateAttachment extends Attachment
|
||||
{
|
||||
final public const ALLOWED_ELEMENT_CLASS = PartCustomState::class;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: PartCustomState::class, inversedBy: 'attachments')]
|
||||
#[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')]
|
||||
#[Context(denormalizationContext: [OverrideClassDenormalizer::CONTEXT_KEY => self::ALLOWED_ELEMENT_CLASS])]
|
||||
protected ?AttachmentContainingDBElement $element = null;
|
||||
}
|
||||
|
|
@ -33,6 +33,7 @@ use App\Entity\Attachments\LabelAttachment;
|
|||
use App\Entity\Attachments\ManufacturerAttachment;
|
||||
use App\Entity\Attachments\MeasurementUnitAttachment;
|
||||
use App\Entity\Attachments\PartAttachment;
|
||||
use App\Entity\Attachments\PartCustomStateAttachment;
|
||||
use App\Entity\Attachments\ProjectAttachment;
|
||||
use App\Entity\Attachments\StorageLocationAttachment;
|
||||
use App\Entity\Attachments\SupplierAttachment;
|
||||
|
|
@ -40,6 +41,7 @@ use App\Entity\Attachments\UserAttachment;
|
|||
use App\Entity\Parameters\AbstractParameter;
|
||||
use App\Entity\Parts\Category;
|
||||
use App\Entity\PriceInformations\Pricedetail;
|
||||
use App\Entity\Parts\PartCustomState;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Entity\ProjectSystem\ProjectBOMEntry;
|
||||
use App\Entity\Parts\Footprint;
|
||||
|
|
@ -68,7 +70,41 @@ use Symfony\Component\Serializer\Annotation\Groups;
|
|||
* Every database table which are managed with this class (or a subclass of it)
|
||||
* must have the table row "id"!! The ID is the unique key to identify the elements.
|
||||
*/
|
||||
#[DiscriminatorMap(typeProperty: 'type', mapping: ['attachment_type' => AttachmentType::class, 'attachment' => Attachment::class, 'attachment_type_attachment' => AttachmentTypeAttachment::class, 'category_attachment' => CategoryAttachment::class, 'currency_attachment' => CurrencyAttachment::class, 'footprint_attachment' => FootprintAttachment::class, 'group_attachment' => GroupAttachment::class, 'label_attachment' => LabelAttachment::class, 'manufacturer_attachment' => ManufacturerAttachment::class, 'measurement_unit_attachment' => MeasurementUnitAttachment::class, 'part_attachment' => PartAttachment::class, 'project_attachment' => ProjectAttachment::class, 'storelocation_attachment' => StorageLocationAttachment::class, 'supplier_attachment' => SupplierAttachment::class, 'user_attachment' => UserAttachment::class, 'category' => Category::class, 'project' => Project::class, 'project_bom_entry' => ProjectBOMEntry::class, 'footprint' => Footprint::class, 'group' => Group::class, 'manufacturer' => Manufacturer::class, 'orderdetail' => Orderdetail::class, 'part' => Part::class, 'pricedetail' => Pricedetail::class, 'storelocation' => StorageLocation::class, 'part_lot' => PartLot::class, 'currency' => Currency::class, 'measurement_unit' => MeasurementUnit::class, 'parameter' => AbstractParameter::class, 'supplier' => Supplier::class, 'user' => User::class])]
|
||||
#[DiscriminatorMap(typeProperty: 'type', mapping: [
|
||||
'attachment_type' => AttachmentType::class,
|
||||
'attachment' => Attachment::class,
|
||||
'attachment_type_attachment' => AttachmentTypeAttachment::class,
|
||||
'category_attachment' => CategoryAttachment::class,
|
||||
'currency_attachment' => CurrencyAttachment::class,
|
||||
'footprint_attachment' => FootprintAttachment::class,
|
||||
'group_attachment' => GroupAttachment::class,
|
||||
'label_attachment' => LabelAttachment::class,
|
||||
'manufacturer_attachment' => ManufacturerAttachment::class,
|
||||
'measurement_unit_attachment' => MeasurementUnitAttachment::class,
|
||||
'part_attachment' => PartAttachment::class,
|
||||
'part_custom_state_attachment' => PartCustomStateAttachment::class,
|
||||
'project_attachment' => ProjectAttachment::class,
|
||||
'storelocation_attachment' => StorageLocationAttachment::class,
|
||||
'supplier_attachment' => SupplierAttachment::class,
|
||||
'user_attachment' => UserAttachment::class,
|
||||
'category' => Category::class,
|
||||
'project' => Project::class,
|
||||
'project_bom_entry' => ProjectBOMEntry::class,
|
||||
'footprint' => Footprint::class,
|
||||
'group' => Group::class,
|
||||
'manufacturer' => Manufacturer::class,
|
||||
'orderdetail' => Orderdetail::class,
|
||||
'part' => Part::class,
|
||||
'part_custom_state' => PartCustomState::class,
|
||||
'pricedetail' => Pricedetail::class,
|
||||
'storelocation' => StorageLocation::class,
|
||||
'part_lot' => PartLot::class,
|
||||
'currency' => Currency::class,
|
||||
'measurement_unit' => MeasurementUnit::class,
|
||||
'parameter' => AbstractParameter::class,
|
||||
'supplier' => Supplier::class,
|
||||
'user' => User::class]
|
||||
)]
|
||||
#[ORM\MappedSuperclass(repositoryClass: DBElementRepository::class)]
|
||||
abstract class AbstractDBElement implements JsonSerializable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ use App\Entity\Attachments\AttachmentType;
|
|||
use App\Entity\Attachments\AttachmentTypeAttachment;
|
||||
use App\Entity\Attachments\CategoryAttachment;
|
||||
use App\Entity\Attachments\CurrencyAttachment;
|
||||
use App\Entity\Attachments\PartCustomStateAttachment;
|
||||
use App\Entity\Attachments\ProjectAttachment;
|
||||
use App\Entity\Attachments\FootprintAttachment;
|
||||
use App\Entity\Attachments\GroupAttachment;
|
||||
|
|
@ -58,6 +59,8 @@ use App\Entity\Attachments\UserAttachment;
|
|||
use App\Entity\Base\AbstractDBElement;
|
||||
use App\Entity\Contracts\LogWithEventUndoInterface;
|
||||
use App\Entity\Contracts\NamedElementInterface;
|
||||
use App\Entity\Parameters\PartCustomStateParameter;
|
||||
use App\Entity\Parts\PartCustomState;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Entity\Parameters\AbstractParameter;
|
||||
use App\Entity\Parameters\AttachmentTypeParameter;
|
||||
|
|
@ -158,6 +161,7 @@ class CollectionElementDeleted extends AbstractLogEntry implements LogWithEventU
|
|||
Part::class => PartParameter::class,
|
||||
StorageLocation::class => StorageLocationParameter::class,
|
||||
Supplier::class => SupplierParameter::class,
|
||||
PartCustomState::class => PartCustomStateParameter::class,
|
||||
default => throw new \RuntimeException('Unknown target class for parameter: '.$this->getTargetClass()),
|
||||
};
|
||||
}
|
||||
|
|
@ -173,6 +177,7 @@ class CollectionElementDeleted extends AbstractLogEntry implements LogWithEventU
|
|||
Manufacturer::class => ManufacturerAttachment::class,
|
||||
MeasurementUnit::class => MeasurementUnitAttachment::class,
|
||||
Part::class => PartAttachment::class,
|
||||
PartCustomState::class => PartCustomStateAttachment::class,
|
||||
StorageLocation::class => StorageLocationAttachment::class,
|
||||
Supplier::class => SupplierAttachment::class,
|
||||
User::class => UserAttachment::class,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ use App\Entity\Parts\Manufacturer;
|
|||
use App\Entity\Parts\MeasurementUnit;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\Parts\PartAssociation;
|
||||
use App\Entity\Parts\PartCustomState;
|
||||
use App\Entity\Parts\PartLot;
|
||||
use App\Entity\Parts\StorageLocation;
|
||||
use App\Entity\Parts\Supplier;
|
||||
|
|
@ -71,6 +72,7 @@ enum LogTargetType: int
|
|||
case PART_ASSOCIATION = 20;
|
||||
case BULK_INFO_PROVIDER_IMPORT_JOB = 21;
|
||||
case BULK_INFO_PROVIDER_IMPORT_JOB_PART = 22;
|
||||
case PART_CUSTOM_STATE = 23;
|
||||
|
||||
/**
|
||||
* Returns the class name of the target type or null if the target type is NONE.
|
||||
|
|
@ -102,6 +104,7 @@ enum LogTargetType: int
|
|||
self::PART_ASSOCIATION => PartAssociation::class,
|
||||
self::BULK_INFO_PROVIDER_IMPORT_JOB => BulkInfoProviderImportJob::class,
|
||||
self::BULK_INFO_PROVIDER_IMPORT_JOB_PART => BulkInfoProviderImportJobPart::class,
|
||||
self::PART_CUSTOM_STATE => PartCustomState::class
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -73,7 +73,8 @@ use function sprintf;
|
|||
#[ORM\DiscriminatorMap([0 => CategoryParameter::class, 1 => CurrencyParameter::class, 2 => ProjectParameter::class,
|
||||
3 => FootprintParameter::class, 4 => GroupParameter::class, 5 => ManufacturerParameter::class,
|
||||
6 => MeasurementUnitParameter::class, 7 => PartParameter::class, 8 => StorageLocationParameter::class,
|
||||
9 => SupplierParameter::class, 10 => AttachmentTypeParameter::class])]
|
||||
9 => SupplierParameter::class, 10 => AttachmentTypeParameter::class,
|
||||
12 => PartCustomStateParameter::class])]
|
||||
#[ORM\Table('parameters')]
|
||||
#[ORM\Index(columns: ['name'], name: 'parameter_name_idx')]
|
||||
#[ORM\Index(columns: ['param_group'], name: 'parameter_group_idx')]
|
||||
|
|
@ -105,7 +106,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement implements Uniqu
|
|||
"AttachmentType" => AttachmentTypeParameter::class, "Category" => CategoryParameter::class, "Currency" => CurrencyParameter::class,
|
||||
"Project" => ProjectParameter::class, "Footprint" => FootprintParameter::class, "Group" => GroupParameter::class,
|
||||
"Manufacturer" => ManufacturerParameter::class, "MeasurementUnit" => MeasurementUnitParameter::class,
|
||||
"StorageLocation" => StorageLocationParameter::class, "Supplier" => SupplierParameter::class];
|
||||
"StorageLocation" => StorageLocationParameter::class, "Supplier" => SupplierParameter::class, "PartCustomState" => PartCustomStateParameter::class];
|
||||
|
||||
/**
|
||||
* @var string The class of the element that can be passed to this attachment. Must be overridden in subclasses.
|
||||
|
|
|
|||
65
src/Entity/Parameters/PartCustomStateParameter.php
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Entity\Parameters;
|
||||
|
||||
use App\Entity\Base\AbstractDBElement;
|
||||
use App\Entity\Parts\PartCustomState;
|
||||
use App\Repository\ParameterRepository;
|
||||
use App\Serializer\APIPlatform\OverrideClassDenormalizer;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Serializer\Attribute\Context;
|
||||
|
||||
#[UniqueEntity(fields: ['name', 'group', 'element'])]
|
||||
#[ORM\Entity(repositoryClass: ParameterRepository::class)]
|
||||
class PartCustomStateParameter extends AbstractParameter
|
||||
{
|
||||
final public const ALLOWED_ELEMENT_CLASS = PartCustomState::class;
|
||||
|
||||
/**
|
||||
* @var PartCustomState the element this para is associated with
|
||||
*/
|
||||
#[ORM\ManyToOne(targetEntity: PartCustomState::class, inversedBy: 'parameters')]
|
||||
#[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')]
|
||||
#[Context(denormalizationContext: [OverrideClassDenormalizer::CONTEXT_KEY => self::ALLOWED_ELEMENT_CLASS])]
|
||||
protected ?AbstractDBElement $element = null;
|
||||
}
|
||||
|
|
@ -105,7 +105,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
|||
denormalizationContext: ['groups' => ['part:write', 'api:basic:write', 'eda_info:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'],
|
||||
)]
|
||||
#[ApiFilter(PropertyFilter::class)]
|
||||
#[ApiFilter(EntityFilter::class, properties: ["category", "footprint", "manufacturer", "partUnit"])]
|
||||
#[ApiFilter(EntityFilter::class, properties: ["category", "footprint", "manufacturer", "partUnit", "partCustomState"])]
|
||||
#[ApiFilter(PartStoragelocationFilter::class, properties: ["storage_location"])]
|
||||
#[ApiFilter(LikeFilter::class, properties: ["name", "comment", "description", "ipn", "manufacturer_product_number"])]
|
||||
#[ApiFilter(TagFilter::class, properties: ["tags"])]
|
||||
|
|
|
|||
127
src/Entity/Parts/PartCustomState.php
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\Parts;
|
||||
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Attachments\PartCustomStateAttachment;
|
||||
use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface;
|
||||
use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
|
||||
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
|
||||
use ApiPlatform\Metadata\ApiFilter;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\Serializer\Filter\PropertyFilter;
|
||||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\Entity\Base\AbstractPartsContainingDBElement;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use App\Entity\Parameters\PartCustomStateParameter;
|
||||
use App\Repository\Parts\PartCustomStateRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* This entity represents a custom part state.
|
||||
* If an organisation uses Part-DB and has its custom part states, this is useful.
|
||||
*
|
||||
* @extends AbstractPartsContainingDBElement<PartCustomStateAttachment,PartCustomStateParameter>
|
||||
*/
|
||||
#[ORM\Entity(repositoryClass: PartCustomStateRepository::class)]
|
||||
#[ORM\Table('`part_custom_states`')]
|
||||
#[ORM\Index(columns: ['name'], name: 'part_custom_state_name')]
|
||||
#[ApiResource(
|
||||
operations: [
|
||||
new Get(security: 'is_granted("read", object)'),
|
||||
new GetCollection(security: 'is_granted("@part_custom_states.read")'),
|
||||
new Post(securityPostDenormalize: 'is_granted("create", object)'),
|
||||
new Patch(security: 'is_granted("edit", object)'),
|
||||
new Delete(security: 'is_granted("delete", object)'),
|
||||
],
|
||||
normalizationContext: ['groups' => ['part_custom_state:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
|
||||
denormalizationContext: ['groups' => ['part_custom_state:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'],
|
||||
)]
|
||||
#[ApiFilter(PropertyFilter::class)]
|
||||
#[ApiFilter(LikeFilter::class, properties: ["name"])]
|
||||
#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)]
|
||||
#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])]
|
||||
class PartCustomState extends AbstractPartsContainingDBElement
|
||||
{
|
||||
/**
|
||||
* @var string The comment info for this element as markdown
|
||||
*/
|
||||
#[Groups(['part_custom_state:read', 'part_custom_state:write', 'full', 'import'])]
|
||||
protected string $comment = '';
|
||||
|
||||
#[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class, cascade: ['persist'])]
|
||||
#[ORM\OrderBy(['name' => Criteria::ASC])]
|
||||
protected Collection $children;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')]
|
||||
#[ORM\JoinColumn(name: 'parent_id')]
|
||||
#[Groups(['part_custom_state:read', 'part_custom_state:write'])]
|
||||
#[ApiProperty(readableLink: false, writableLink: false)]
|
||||
protected ?AbstractStructuralDBElement $parent = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, PartCustomStateAttachment>
|
||||
*/
|
||||
#[Assert\Valid]
|
||||
#[ORM\OneToMany(targetEntity: PartCustomStateAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
#[ORM\OrderBy(['name' => Criteria::ASC])]
|
||||
#[Groups(['part_custom_state:read', 'part_custom_state:write'])]
|
||||
protected Collection $attachments;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: PartCustomStateAttachment::class)]
|
||||
#[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')]
|
||||
#[Groups(['part_custom_state:read', 'part_custom_state:write'])]
|
||||
protected ?Attachment $master_picture_attachment = null;
|
||||
|
||||
/** @var Collection<int, PartCustomStateParameter>
|
||||
*/
|
||||
#[Assert\Valid]
|
||||
#[ORM\OneToMany(mappedBy: 'element', targetEntity: PartCustomStateParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
#[ORM\OrderBy(['name' => 'ASC'])]
|
||||
#[Groups(['part_custom_state:read', 'part_custom_state:write'])]
|
||||
protected Collection $parameters;
|
||||
|
||||
#[Groups(['part_custom_state:read'])]
|
||||
protected ?\DateTimeImmutable $addedDate = null;
|
||||
#[Groups(['part_custom_state:read'])]
|
||||
protected ?\DateTimeImmutable $lastModified = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->children = new ArrayCollection();
|
||||
$this->attachments = new ArrayCollection();
|
||||
$this->parameters = new ArrayCollection();
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
|||
namespace App\Entity\Parts\PartTraits;
|
||||
|
||||
use App\Entity\Parts\InfoProviderReference;
|
||||
use App\Entity\Parts\PartCustomState;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use App\Entity\Parts\Part;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
|
@ -75,6 +76,14 @@ trait AdvancedPropertyTrait
|
|||
#[Groups(['full', 'part:read'])]
|
||||
protected InfoProviderReference $providerReference;
|
||||
|
||||
/**
|
||||
* @var ?PartCustomState the custom state for the part
|
||||
*/
|
||||
#[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])]
|
||||
#[ORM\ManyToOne(targetEntity: PartCustomState::class)]
|
||||
#[ORM\JoinColumn(name: 'id_part_custom_state')]
|
||||
protected ?PartCustomState $partCustomState = null;
|
||||
|
||||
/**
|
||||
* Checks if this part is marked, for that it needs further review.
|
||||
*/
|
||||
|
|
@ -182,7 +191,24 @@ trait AdvancedPropertyTrait
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the custom part state for the part
|
||||
* Returns null if no specific part state is set.
|
||||
*/
|
||||
public function getPartCustomState(): ?PartCustomState
|
||||
{
|
||||
return $this->partCustomState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom part state.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setPartCustomState(?PartCustomState $partCustomState): self
|
||||
{
|
||||
$this->partCustomState = $partCustomState;
|
||||
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ trait ProjectTrait
|
|||
/**
|
||||
* @var Collection<ProjectBOMEntry> $project_bom_entries
|
||||
*/
|
||||
#[ORM\OneToMany(mappedBy: 'part', targetEntity: ProjectBOMEntry::class, cascade: ['remove'], orphanRemoval: true)]
|
||||
#[ORM\OneToMany(targetEntity: ProjectBOMEntry::class, mappedBy: 'part')]
|
||||
protected Collection $project_bom_entries;
|
||||
|
||||
/**
|
||||
|
|
|
|||
59
src/EntityListeners/PartProjectBOMEntryUnlinkListener.php
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2025 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\EntityListeners;
|
||||
|
||||
use App\Entity\Parts\Part;
|
||||
use Doctrine\Bundle\DoctrineBundle\Attribute\AsEntityListener;
|
||||
use Doctrine\ORM\Event\PreRemoveEventArgs;
|
||||
|
||||
/**
|
||||
* If an part is deleted, this listener makes sure that all ProjectBOMEntries that reference this part, are updated
|
||||
* to not reference the part anymore, but instead store the part name in the name field.
|
||||
*/
|
||||
#[AsEntityListener(event: "preRemove", entity: Part::class)]
|
||||
class PartProjectBOMEntryUnlinkListener
|
||||
{
|
||||
public function preRemove(Part $part, PreRemoveEventArgs $event): void
|
||||
{
|
||||
// Iterate over all ProjectBOMEntries that use this part and put the part name into the name field
|
||||
foreach ($part->getProjectBomEntries() as $bom_entry) {
|
||||
$old_name = $bom_entry->getName();
|
||||
if ($old_name === null || trim($old_name) === '') {
|
||||
$bom_entry->setName($part->getName());
|
||||
} else {
|
||||
$bom_entry->setName($old_name . ' (' . $part->getName() . ')');
|
||||
}
|
||||
|
||||
$old_comment = $bom_entry->getComment();
|
||||
if ($old_comment === null || trim($old_comment) === '') {
|
||||
$bom_entry->setComment('Part was deleted: ' . $part->getName());
|
||||
} else {
|
||||
$bom_entry->setComment($old_comment . "\n\n Part was deleted: " . $part->getName());
|
||||
}
|
||||
|
||||
//Remove the part reference
|
||||
$bom_entry->setPart(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
48
src/Exceptions/OAuthReconnectRequiredException.php
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2025 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\Exceptions;
|
||||
|
||||
use Throwable;
|
||||
|
||||
class OAuthReconnectRequiredException extends \RuntimeException
|
||||
{
|
||||
private string $providerName = "unknown";
|
||||
|
||||
public function __construct(string $message = "You need to reconnect the OAuth connection for this provider!", int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
public static function forProvider(string $providerName): self
|
||||
{
|
||||
$exception = new self("You need to reconnect the OAuth connection for the provider '$providerName'!");
|
||||
$exception->providerName = $providerName;
|
||||
return $exception;
|
||||
}
|
||||
|
||||
public function getProviderName(): string
|
||||
{
|
||||
return $this->providerName;
|
||||
}
|
||||
}
|
||||
27
src/Form/AdminPages/PartCustomStateAdminForm.php
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\AdminPages;
|
||||
|
||||
class PartCustomStateAdminForm extends BaseEntityAdminForm
|
||||
{
|
||||
}
|
||||
|
|
@ -130,6 +130,7 @@ class LogFilterType extends AbstractType
|
|||
LogTargetType::PART_ASSOCIATION => 'part_association.label',
|
||||
LogTargetType::BULK_INFO_PROVIDER_IMPORT_JOB => 'bulk_info_provider_import_job.label',
|
||||
LogTargetType::BULK_INFO_PROVIDER_IMPORT_JOB_PART => 'bulk_info_provider_import_job_part.label',
|
||||
LogTargetType::PART_CUSTOM_STATE => 'part_custom_state.label',
|
||||
},
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ use App\Entity\Parts\Category;
|
|||
use App\Entity\Parts\Footprint;
|
||||
use App\Entity\Parts\Manufacturer;
|
||||
use App\Entity\Parts\MeasurementUnit;
|
||||
use App\Entity\Parts\PartCustomState;
|
||||
use App\Entity\Parts\StorageLocation;
|
||||
use App\Entity\Parts\Supplier;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
|
|
@ -139,6 +140,11 @@ class PartFilterType extends AbstractType
|
|||
'entity_class' => MeasurementUnit::class
|
||||
]);
|
||||
|
||||
$builder->add('partCustomState', StructuralEntityConstraintType::class, [
|
||||
'label' => 'part.edit.partCustomState',
|
||||
'entity_class' => PartCustomState::class
|
||||
]);
|
||||
|
||||
$builder->add('lastModified', DateTimeConstraintType::class, [
|
||||
'label' => 'lastModified'
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ use App\Entity\Parts\Manufacturer;
|
|||
use App\Entity\Parts\ManufacturingStatus;
|
||||
use App\Entity\Parts\MeasurementUnit;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\Parts\PartCustomState;
|
||||
use App\Entity\PriceInformations\Orderdetail;
|
||||
use App\Form\AttachmentFormType;
|
||||
use App\Form\ParameterType;
|
||||
|
|
@ -209,6 +210,12 @@ class PartBaseType extends AbstractType
|
|||
'disable_not_selectable' => true,
|
||||
'label' => 'part.edit.partUnit',
|
||||
])
|
||||
->add('partCustomState', StructuralEntityType::class, [
|
||||
'class' => PartCustomState::class,
|
||||
'required' => false,
|
||||
'disable_not_selectable' => true,
|
||||
'label' => 'part.edit.partCustomState',
|
||||
])
|
||||
->add('ipn', TextType::class, $ipnOptions);
|
||||
|
||||
//Comment section
|
||||
|
|
|
|||
57
src/Form/Type/LanguageMenuEntriesType.php
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Form\Type;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\LanguageType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\LocaleType;
|
||||
use Symfony\Component\Intl\Languages;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class LanguageMenuEntriesType extends AbstractType
|
||||
{
|
||||
public function __construct(#[Autowire(param: 'partdb.locale_menu')] private readonly array $preferred_languages)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function getParent(): string
|
||||
{
|
||||
return LanguageType::class;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$choices = [];
|
||||
foreach ($this->preferred_languages as $lang_code) {
|
||||
$choices[Languages::getName($lang_code)] = $lang_code;
|
||||
}
|
||||
|
||||
$resolver->setDefaults([
|
||||
'choice_loader' => null,
|
||||
'choices' => $choices,
|
||||
]);
|
||||
}
|
||||
}
|
||||
48
src/Repository/Parts/PartCustomStateRepository.php
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<?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)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace App\Repository\Parts;
|
||||
|
||||
use App\Entity\Parts\PartCustomState;
|
||||
use App\Repository\AbstractPartsContainingRepository;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class PartCustomStateRepository extends AbstractPartsContainingRepository
|
||||
{
|
||||
public function getParts(object $element, string $nameOrderDirection = "ASC"): array
|
||||
{
|
||||
if (!$element instanceof PartCustomState) {
|
||||
throw new InvalidArgumentException('$element must be an PartCustomState!');
|
||||
}
|
||||
|
||||
return $this->getPartsByField($element, $nameOrderDirection, 'partUnit');
|
||||
}
|
||||
|
||||
public function getPartsCount(object $element): int
|
||||
{
|
||||
if (!$element instanceof PartCustomState) {
|
||||
throw new InvalidArgumentException('$element must be an PartCustomState!');
|
||||
}
|
||||
|
||||
return $this->getPartsCountByField($element, 'partUnit');
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Security\Voter;
|
||||
|
||||
use App\Entity\Attachments\PartCustomStateAttachment;
|
||||
use App\Services\UserSystem\VoterHelper;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use App\Entity\Attachments\AttachmentContainingDBElement;
|
||||
|
|
@ -99,6 +100,8 @@ final class AttachmentVoter extends Voter
|
|||
$param = 'measurement_units';
|
||||
} elseif (is_a($subject, PartAttachment::class, true)) {
|
||||
$param = 'parts';
|
||||
} elseif (is_a($subject, PartCustomStateAttachment::class, true)) {
|
||||
$param = 'part_custom_states';
|
||||
} elseif (is_a($subject, StorageLocationAttachment::class, true)) {
|
||||
$param = 'storelocations';
|
||||
} elseif (is_a($subject, SupplierAttachment::class, true)) {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||
*/
|
||||
namespace App\Security\Voter;
|
||||
|
||||
use App\Entity\Parameters\PartCustomStateParameter;
|
||||
use App\Services\UserSystem\VoterHelper;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use App\Entity\Base\AbstractDBElement;
|
||||
|
|
@ -97,6 +98,8 @@ final class ParameterVoter extends Voter
|
|||
$param = 'measurement_units';
|
||||
} elseif (is_a($subject, PartParameter::class, true)) {
|
||||
$param = 'parts';
|
||||
} elseif (is_a($subject, PartCustomStateParameter::class, true)) {
|
||||
$param = 'part_custom_states';
|
||||
} elseif (is_a($subject, StorageLocationParameter::class, true)) {
|
||||
$param = 'storelocations';
|
||||
} elseif (is_a($subject, SupplierParameter::class, true)) {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
|||
namespace App\Security\Voter;
|
||||
|
||||
use App\Entity\Attachments\AttachmentType;
|
||||
use App\Entity\Parts\PartCustomState;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Entity\Parts\Category;
|
||||
use App\Entity\Parts\Footprint;
|
||||
|
|
@ -53,6 +54,7 @@ final class StructureVoter extends Voter
|
|||
Supplier::class => 'suppliers',
|
||||
Currency::class => 'currencies',
|
||||
MeasurementUnit::class => 'measurement_units',
|
||||
PartCustomState::class => 'part_custom_states',
|
||||
];
|
||||
|
||||
public function __construct(private readonly VoterHelper $helper)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ use App\Entity\Attachments\AttachmentUpload;
|
|||
use App\Entity\Attachments\CategoryAttachment;
|
||||
use App\Entity\Attachments\CurrencyAttachment;
|
||||
use App\Entity\Attachments\LabelAttachment;
|
||||
use App\Entity\Attachments\PartCustomStateAttachment;
|
||||
use App\Entity\Attachments\ProjectAttachment;
|
||||
use App\Entity\Attachments\FootprintAttachment;
|
||||
use App\Entity\Attachments\GroupAttachment;
|
||||
|
|
@ -80,6 +81,7 @@ class AttachmentSubmitHandler
|
|||
//The mapping used to determine which folder will be used for an attachment type
|
||||
$this->folder_mapping = [
|
||||
PartAttachment::class => 'part',
|
||||
PartCustomStateAttachment::class => 'part_custom_state',
|
||||
AttachmentTypeAttachment::class => 'attachment_type',
|
||||
CategoryAttachment::class => 'category',
|
||||
CurrencyAttachment::class => 'currency',
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
|||
namespace App\Services\Attachments;
|
||||
|
||||
use App\Entity\Parts\Footprint;
|
||||
use App\Entity\Parts\PartCustomState;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Entity\Parts\Category;
|
||||
use App\Entity\Parts\StorageLocation;
|
||||
|
|
|
|||
|
|
@ -233,6 +233,10 @@ class KiCadHelper
|
|||
}
|
||||
$result["fields"]["Part-DB Unit"] = $this->createField($unit);
|
||||
}
|
||||
if ($part->getPartCustomState() !== null) {
|
||||
$customState = $part->getPartCustomState()->getName();
|
||||
$result["fields"]["Part-DB Custom state"] = $this->createField($customState);
|
||||
}
|
||||
if ($part->getMass()) {
|
||||
$result["fields"]["Mass"] = $this->createField($part->getMass() . ' g');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ use App\Entity\Parts\Manufacturer;
|
|||
use App\Entity\Parts\MeasurementUnit;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\Parts\PartAssociation;
|
||||
use App\Entity\Parts\PartCustomState;
|
||||
use App\Entity\Parts\PartLot;
|
||||
use App\Entity\Parts\StorageLocation;
|
||||
use App\Entity\Parts\Supplier;
|
||||
|
|
@ -83,6 +84,7 @@ class ElementTypeNameGenerator
|
|||
PartAssociation::class => $this->translator->trans('part_association.label'),
|
||||
BulkInfoProviderImportJob::class => $this->translator->trans('bulk_info_provider_import_job.label'),
|
||||
BulkInfoProviderImportJobPart::class => $this->translator->trans('bulk_info_provider_import_job_part.label'),
|
||||
PartCustomState::class => $this->translator->trans('part_custom_state.label'),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ class PartMerger implements EntityMergerInterface
|
|||
$this->useOtherValueIfNotNull($target, $other, 'footprint');
|
||||
$this->useOtherValueIfNotNull($target, $other, 'category');
|
||||
$this->useOtherValueIfNotNull($target, $other, 'partUnit');
|
||||
$this->useOtherValueIfNotNull($target, $other, 'partCustomState');
|
||||
|
||||
//We assume that the higher value is the correct one for minimum instock
|
||||
$this->useLargerValue($target, $other, 'minamount');
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ use App\Entity\Attachments\AttachmentType;
|
|||
use App\Entity\Attachments\PartAttachment;
|
||||
use App\Entity\Base\AbstractDBElement;
|
||||
use App\Entity\Parameters\PartParameter;
|
||||
use App\Entity\Parts\PartCustomState;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Entity\LabelSystem\LabelProfile;
|
||||
use App\Entity\Parts\Category;
|
||||
|
|
@ -107,6 +108,7 @@ class EntityURLGenerator
|
|||
MeasurementUnit::class => 'measurement_unit_edit',
|
||||
Group::class => 'group_edit',
|
||||
LabelProfile::class => 'label_profile_edit',
|
||||
PartCustomState::class => 'part_custom_state_edit',
|
||||
];
|
||||
|
||||
try {
|
||||
|
|
@ -213,6 +215,7 @@ class EntityURLGenerator
|
|||
MeasurementUnit::class => 'measurement_unit_edit',
|
||||
Group::class => 'group_edit',
|
||||
LabelProfile::class => 'label_profile_edit',
|
||||
PartCustomState::class => 'part_custom_state_edit',
|
||||
];
|
||||
|
||||
return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]);
|
||||
|
|
@ -243,6 +246,7 @@ class EntityURLGenerator
|
|||
MeasurementUnit::class => 'measurement_unit_edit',
|
||||
Group::class => 'group_edit',
|
||||
LabelProfile::class => 'label_profile_edit',
|
||||
PartCustomState::class => 'part_custom_state_edit',
|
||||
];
|
||||
|
||||
return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]);
|
||||
|
|
@ -274,6 +278,7 @@ class EntityURLGenerator
|
|||
MeasurementUnit::class => 'measurement_unit_new',
|
||||
Group::class => 'group_new',
|
||||
LabelProfile::class => 'label_profile_new',
|
||||
PartCustomState::class => 'part_custom_state_new',
|
||||
];
|
||||
|
||||
return $this->urlGenerator->generate($this->mapToController($map, $entity));
|
||||
|
|
@ -305,6 +310,7 @@ class EntityURLGenerator
|
|||
MeasurementUnit::class => 'measurement_unit_clone',
|
||||
Group::class => 'group_clone',
|
||||
LabelProfile::class => 'label_profile_clone',
|
||||
PartCustomState::class => 'part_custom_state_clone',
|
||||
];
|
||||
|
||||
return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]);
|
||||
|
|
@ -350,6 +356,7 @@ class EntityURLGenerator
|
|||
MeasurementUnit::class => 'measurement_unit_delete',
|
||||
Group::class => 'group_delete',
|
||||
LabelProfile::class => 'label_profile_delete',
|
||||
PartCustomState::class => 'part_custom_state_delete',
|
||||
];
|
||||
|
||||
return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]);
|
||||
|
|
|
|||
|
|
@ -38,6 +38,11 @@ class SIFormatter
|
|||
*/
|
||||
public function getMagnitude(float $value): int
|
||||
{
|
||||
//Check for zero, as log10(0) is undefined/gives -infinity, which leads to casting issues in PHP8.5+
|
||||
if (0.0 === $value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int) floor(log10(abs($value)));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ use App\Entity\Parts\Category;
|
|||
use App\Entity\Parts\Footprint;
|
||||
use App\Entity\Parts\Manufacturer;
|
||||
use App\Entity\Parts\MeasurementUnit;
|
||||
use App\Entity\Parts\PartCustomState;
|
||||
use App\Entity\Parts\StorageLocation;
|
||||
use App\Entity\Parts\Supplier;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
|
@ -148,6 +149,26 @@ class PKDatastructureImporter
|
|||
return is_countable($partunit_data) ? count($partunit_data) : 0;
|
||||
}
|
||||
|
||||
public function importPartCustomStates(array $data): int
|
||||
{
|
||||
if (!isset($data['partcustomstate'])) {
|
||||
throw new \RuntimeException('$data must contain a "partcustomstate" key!');
|
||||
}
|
||||
|
||||
$partCustomStateData = $data['partcustomstate'];
|
||||
foreach ($partCustomStateData as $partCustomState) {
|
||||
$customState = new PartCustomState();
|
||||
$customState->setName($partCustomState['name']);
|
||||
|
||||
$this->setIDOfEntity($customState, $partCustomState['id']);
|
||||
$this->em->persist($customState);
|
||||
}
|
||||
|
||||
$this->em->flush();
|
||||
|
||||
return is_countable($partCustomStateData) ? count($partCustomStateData) : 0;
|
||||
}
|
||||
|
||||
public function importCategories(array $data): int
|
||||
{
|
||||
if (!isset($data['partcategory'])) {
|
||||
|
|
|
|||
|
|
@ -91,6 +91,8 @@ class PKPartImporter
|
|||
$this->setAssociationField($entity, 'partUnit', MeasurementUnit::class, $part['partUnit_id']);
|
||||
}
|
||||
|
||||
$this->setAssociationField($entity, 'partCustomState', MeasurementUnit::class, $part['partCustomState_id']);
|
||||
|
||||
//Create a part lot to store the stock level and location
|
||||
$lot = new PartLot();
|
||||
$lot->setAmount((float) ($part['stockLevel'] ?? 0));
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ final class DTOtoEntityConverter
|
|||
$attachment = $this->convertFile($image, $image_type);
|
||||
|
||||
$attachments_grouped[$attachment->getName()][] = $attachment;
|
||||
if (count($attachments_grouped[$attachment->getName()] ?? []) > 1) {
|
||||
if (count($attachments_grouped[$attachment->getName()]) > 1) {
|
||||
$attachment->setName($attachment->getName() . ' (' . (count($attachments_grouped[$attachment->getName()]) + 1) . ')');
|
||||
}
|
||||
|
||||
|
|
@ -236,7 +236,7 @@ final class DTOtoEntityConverter
|
|||
$attachment = $this->convertFile($datasheet, $datasheet_type);
|
||||
|
||||
$attachments_grouped[$attachment->getName()][] = $attachment;
|
||||
if (count($attachments_grouped[$attachment->getName()] ?? []) > 1) {
|
||||
if (count($attachments_grouped[$attachment->getName()]) > 1) {
|
||||
$attachment->setName($attachment->getName() . ' (' . (count($attachments_grouped[$attachment->getName()])) . ')');
|
||||
}
|
||||
|
||||
|
|
@ -357,4 +357,4 @@ final class DTOtoEntityConverter
|
|||
return $tmp;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ declare(strict_types=1);
|
|||
namespace App\Services\InfoProviderSystem\Providers;
|
||||
|
||||
use App\Entity\Parts\ManufacturingStatus;
|
||||
use App\Exceptions\OAuthReconnectRequiredException;
|
||||
use App\Services\InfoProviderSystem\DTOs\FileDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\ParameterDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
|
||||
|
|
@ -117,12 +118,22 @@ class DigikeyProvider implements InfoProviderInterface
|
|||
];
|
||||
|
||||
//$response = $this->digikeyClient->request('POST', '/Search/v3/Products/Keyword', [
|
||||
$response = $this->digikeyClient->request('POST', '/products/v4/search/keyword', [
|
||||
'json' => $request,
|
||||
'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME)
|
||||
]);
|
||||
try {
|
||||
$response = $this->digikeyClient->request('POST', '/products/v4/search/keyword', [
|
||||
'json' => $request,
|
||||
'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME)
|
||||
]);
|
||||
|
||||
$response_array = $response->toArray();
|
||||
} catch (\InvalidArgumentException $exception) {
|
||||
//Check if the exception was caused by an invalid or expired token
|
||||
if (str_contains($exception->getMessage(), 'access_token')) {
|
||||
throw OAuthReconnectRequiredException::forProvider($this->getProviderKey());
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
$response_array = $response->toArray();
|
||||
|
||||
|
||||
$result = [];
|
||||
|
|
@ -150,9 +161,18 @@ class DigikeyProvider implements InfoProviderInterface
|
|||
|
||||
public function getDetails(string $id): PartDetailDTO
|
||||
{
|
||||
$response = $this->digikeyClient->request('GET', '/products/v4/search/' . urlencode($id) . '/productdetails', [
|
||||
'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME)
|
||||
]);
|
||||
try {
|
||||
$response = $this->digikeyClient->request('GET', '/products/v4/search/' . urlencode($id) . '/productdetails', [
|
||||
'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME)
|
||||
]);
|
||||
} catch (\InvalidArgumentException $exception) {
|
||||
//Check if the exception was caused by an invalid or expired token
|
||||
if (str_contains($exception->getMessage(), 'access_token')) {
|
||||
throw OAuthReconnectRequiredException::forProvider($this->getProviderKey());
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
$response_array = $response->toArray();
|
||||
$product = $response_array['Product'];
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ final class SandboxedTwigFactory
|
|||
Supplier::class => ['getShippingCosts', 'getDefaultCurrency'],
|
||||
Part::class => ['isNeedsReview', 'getTags', 'getMass', 'getIpn', 'getProviderReference',
|
||||
'getDescription', 'getComment', 'isFavorite', 'getCategory', 'getFootprint',
|
||||
'getPartLots', 'getPartUnit', 'useFloatAmount', 'getMinAmount', 'getAmountSum', 'isNotEnoughInstock', 'isAmountUnknown', 'getExpiredAmountSum',
|
||||
'getPartLots', 'getPartUnit', 'getPartCustomState', 'useFloatAmount', 'getMinAmount', 'getAmountSum', 'isNotEnoughInstock', 'isAmountUnknown', 'getExpiredAmountSum',
|
||||
'getManufacturerProductUrl', 'getCustomProductURL', 'getManufacturingStatus', 'getManufacturer',
|
||||
'getManufacturerProductNumber', 'getOrderdetails', 'isObsolete',
|
||||
'getParameters', 'getGroupedParameters',
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@ use App\Services\Parts\PartLotWithdrawAddHelper;
|
|||
/**
|
||||
* @see \App\Tests\Services\ProjectSystem\ProjectBuildHelperTest
|
||||
*/
|
||||
class ProjectBuildHelper
|
||||
final readonly class ProjectBuildHelper
|
||||
{
|
||||
public function __construct(private readonly PartLotWithdrawAddHelper $withdraw_add_helper)
|
||||
public function __construct(private PartLotWithdrawAddHelper $withdraw_add_helper)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -63,20 +63,37 @@ class ProjectBuildHelper
|
|||
*/
|
||||
public function getMaximumBuildableCount(Project $project): int
|
||||
{
|
||||
$bom_entries = $project->getBomEntries();
|
||||
if ($bom_entries->isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
$maximum_buildable_count = PHP_INT_MAX;
|
||||
foreach ($project->getBomEntries() as $bom_entry) {
|
||||
foreach ($bom_entries as $bom_entry) {
|
||||
//Skip BOM entries without a part (as we can not determine that)
|
||||
if (!$bom_entry->isPartBomEntry()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//The maximum buildable count for the whole project is the minimum of all BOM entries
|
||||
$maximum_buildable_count = min($maximum_buildable_count, $this->getMaximumBuildableCountForBOMEntry($bom_entry));
|
||||
}
|
||||
|
||||
return $maximum_buildable_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum buildable amount of the given project as string, based on the stock of the used parts in the BOM.
|
||||
* If the maximum buildable count is infinite, the string '∞' is returned.
|
||||
* @param Project $project
|
||||
* @return string
|
||||
*/
|
||||
public function getMaximumBuildableCountAsString(Project $project): string
|
||||
{
|
||||
$max_count = $this->getMaximumBuildableCount($project);
|
||||
if ($max_count === PHP_INT_MAX) {
|
||||
return '∞';
|
||||
}
|
||||
return (string) $max_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given project can be built with the current stock.
|
||||
* This means that the maximum buildable count is greater or equal than the requested $number_of_projects
|
||||
|
|
|
|||