Compare commits

..

272 Commits

Author SHA1 Message Date
Jan Böhmer
021e28aca8 Bumped to version 1.3.2 2023-04-29 22:43:03 +02:00
Jan Böhmer
334d81db08 Mark that amount is unknown in part tables and info page
Fixes issue #282
2023-04-29 22:33:46 +02:00
Jan Böhmer
6ffd45a82a We are in development of 1.3.2 now 2023-04-29 22:07:14 +02:00
Jan Böhmer
2fe3902d8d Updated dependencies. 2023-04-29 22:06:13 +02:00
Jan Böhmer
4dceda8251 Bumped version to 1.3.1 2023-04-24 12:01:12 +02:00
Jan Böhmer
09cf33f530 Use another method to submit forms after the delete confirm dialog
The old method caused some weird behavior on Firefox. This fixes issue #273
2023-04-24 01:39:42 +02:00
Jan Böhmer
3e851a65e9 Updated dependencies. 2023-04-24 00:00:31 +02:00
Jan Böhmer
e8ec536a5a Allow to order by storelocation column
Related to discussion #269
2023-04-23 23:38:59 +02:00
Jan Böhmer
967be4451a Reworked keybinding for special character input
Related to issue #275
2023-04-23 23:21:52 +02:00
Jan Böhmer
bc65a18f3c Added greek characters to special characters plugin in CKEDITOR
Fixes #275
2023-04-23 21:20:53 +02:00
Jan Böhmer
1eb9c38aee Fixed problem that MPN was not used as search field
Fixes issue #277 and #276
2023-04-22 23:26:48 +02:00
Jan Böhmer
ccaad1c305 Updated composer dependencies. 2023-04-22 22:34:14 +02:00
Jan Böhmer
963a22783f Use proper implementation of recursion depth limit, that really use the number of recusive calls, not the number of child elements
This fixes issue #267
2023-04-17 23:59:59 +02:00
Jan Böhmer
303a3690e8 Normalize class_names for attachments migrated from legacy Part-DB database
This fixes issue #272
2023-04-17 23:52:08 +02:00
Jan Böhmer
29fa0818f6 We are in development of v1.3.1 2023-04-17 00:56:07 +02:00
Jan Böhmer
1a21a3ed42 Do not use igbinary for cache, as it is causing excpetions with the doctrine proxies 2023-04-17 00:55:41 +02:00
Jan Böhmer
7baad04e39 Updated dependencies 2023-04-16 23:58:03 +02:00
Jan Böhmer
273293479d Hopefully fix phpunit issue on github actions 2023-04-16 01:46:44 +02:00
Jan Böhmer
37fb895d67 Only try to retrieve the targetLot from DB if the parameter is existing
This fixes an excpetion occuring during withdrawal of parts, when moving is disabled for the lot.
2023-04-16 01:22:58 +02:00
Jan Böhmer
0dcdd252f5 Fixed static analysis issues 2023-04-16 00:55:25 +02:00
Jan Böhmer
d04d743520 Fixed typos 2023-04-15 23:14:53 +02:00
Jan Böhmer
63df16a369 Removed unused imports 2023-04-15 22:27:19 +02:00
Jan Böhmer
13209c3236 Improved typing of properties 2023-04-15 22:25:03 +02:00
Jan Böhmer
29d1d49aca Fixed some more inspection issues 2023-04-15 22:05:29 +02:00
Jan Böhmer
de96aae9a5 Fixed inspection issues 2023-04-15 21:49:19 +02:00
Jan Böhmer
5f29ee9052 Fixed some deprecation messages 2023-04-15 21:18:11 +02:00
Jan Böhmer
b3ecee749e Removed deprecated SessionInterface service 2023-04-15 21:07:04 +02:00
Jan Böhmer
1cee1abe00 Fixed some return type deprecation messages 2023-04-15 19:33:39 +02:00
Jan Böhmer
558440168d Fixed LiipImagine deprecation 2023-04-15 19:11:06 +02:00
Jan Böhmer
d0cb7ab486 Fixed deprecated use of FlashBag Service 2023-04-15 19:05:45 +02:00
Jan Böhmer
c317bc020a Theme config migration should now work properly when migrating from legacy DB 2023-04-15 00:51:32 +02:00
Jan Böhmer
4065fb77da Properly escape group table name in legacy DB migration for compatibility with MySQL 8
Fixes issue #271
2023-04-15 00:49:02 +02:00
Jan Böhmer
8351f38ee7 Keep query parameters when adding locale part in RedirectController
This fixes issue #268
2023-04-15 00:38:11 +02:00
Jan Böhmer
6e6e203f8a Update VERSION 2023-04-11 12:26:36 +02:00
Jan Böhmer
2192149b5a Merge remote-tracking branch 'origin/l10n_master' 2023-04-11 12:14:10 +02:00
Pyromane
a4e19196a7 Update troubleshooting.md (#264)
* Update troubleshooting.md

Enhanced how to list users and reset a user's password.

* Update troubleshooting.md

---------

Co-authored-by: Jan Böhmer <mail@jan-boehmer.de>
2023-04-11 12:11:08 +02:00
Jan Böhmer
0c744c5444 New translations validators.en.xlf (German) 2023-04-11 12:05:25 +02:00
Jan Böhmer
e0e5fb3d5a Do not double escape tag link. Tag links with space in it now work properly 2023-04-09 01:38:12 +02:00
Jan Böhmer
1125096e5a Fixed RoundingNecessaryException in certain cases 2023-04-09 01:30:29 +02:00
Jan Böhmer
fc1d2269d0 Fixed error with default values on older MySQL version
We have removed the default values for the columns completly, as it were only needed on SQLite when adding the column to existing row.
As this was done in an earlier migration, we can now safely remove it.

The MySQL now correctly detects no more changes. SQLite however still generates some wrong migration changes.
2023-04-09 01:17:48 +02:00
Jan Böhmer
cc033d5be7 Properly escape users and groups table name for newer MySQL version 2023-04-09 00:08:08 +02:00
Jan Böhmer
7eee3de965 Added fixing migrations for sqlite 2023-04-09 00:07:23 +02:00
Jan Böhmer
0c6245fe8e Removed unused migration 2023-04-09 00:04:56 +02:00
Jan Böhmer
342ed382e3 Properly mark the tinyint column with a comment, so that migrations can properly detect that no changes are needed 2023-04-09 00:04:13 +02:00
Jan Böhmer
aaf6c37871 Fixed some minor issues in database schema of MySQL 2023-04-08 23:49:47 +02:00
Jan Böhmer
65e1346a11 Improved output of some messages during migration 2023-04-08 23:39:45 +02:00
Jan Böhmer
7f9307feec Perform an explicit type conversion in doesFKExists function 2023-04-08 23:32:38 +02:00
Jan Böhmer
036eaf3bae Removed warnings about changed permissions, as the old changes are reset later, and we now do the permission migration in Part-DB directly 2023-04-08 23:29:23 +02:00
Jan Böhmer
2717d7d311 Only drop the foreign keys during migration from legacy Part-DB DBs if they really exist
This should fix issue #260
2023-04-08 23:27:10 +02:00
Jan Böhmer
577b841ee0 Fixed TypeError on certain old ElementCreatedLogEntries
Fixes issue #261
2023-04-08 22:57:07 +02:00
Jan Böhmer
857eb0517c New translations messages.en.xlf (English) 2023-04-08 21:25:56 +02:00
Jan Böhmer
ec50197b40 Fixed PHPUnit tests 2023-04-08 21:21:53 +02:00
Jan Böhmer
4ace7dd370 Merge remote-tracking branch 'origin/master' 2023-04-08 21:02:51 +02:00
Jan Böhmer
0eea7f8d4d Fixed static analyis issue 2023-04-08 21:00:41 +02:00
Jan Böhmer
80c7680d17 Do not use a horizontal layout in the comment dropdown for edit_part_info 2023-04-08 20:57:01 +02:00
Jan Böhmer
3edc0a7f53 Added documentation for ENFORCE_CHANGE_COMMENTS_FOR
Related to issue #220
2023-04-08 20:52:46 +02:00
Jan Böhmer
29af14f588 Added an option to enforce log comments for certain actions
This implements issue #220
2023-04-08 20:43:19 +02:00
Jan Böhmer
5f2408b791 Reveal invalid fields in dropdowns while browser validation
Preparation work for issue #220
2023-04-08 20:06:08 +02:00
Jan Böhmer
5b5e8a4fd5 Allow users (and admins) to decide whether their email should be shown on their public profile 2023-04-08 19:53:05 +02:00
Jan Böhmer
71b0c2d83e Properly quote users table for compatibility with newer MySQL databases 2023-04-08 19:51:29 +02:00
Jan Böhmer
363b7bc314 Do not show a unecessary label in front of the boolean constraint types checkboxes 2023-04-08 01:24:17 +02:00
Jan Böhmer
448032c5b7 New translations validators.en.xlf (English) 2023-04-08 01:16:15 +02:00
Jan Böhmer
2af1234cfd New translations messages.en.xlf (English) 2023-04-08 01:16:14 +02:00
Jan Böhmer
d258235430 Improved naming and documentation of CLIUser functions on AbstractLogEntry 2023-04-08 01:13:13 +02:00
Jan Böhmer
c060d6ebb1 Updated dependencies 2023-04-08 01:09:45 +02:00
Jan Böhmer
72dab2bc4e Added tests for CLI user functions on AbstractLogEntry 2023-04-08 01:07:59 +02:00
Jan Böhmer
b0d2a22f62 Make user info page public for all logged in user 2023-04-08 01:04:10 +02:00
Jan Böhmer
bcda71cb25 Ensure that the a lot / storage location owner is not the anonymous user 2023-04-08 00:50:42 +02:00
Jan Böhmer
d32e902d17 Allow to filter by the lot owner 2023-04-08 00:44:34 +02:00
Jan Böhmer
f91b719542 Added a filter constraint for parts where instock is "less than desired"
Fixes issue #257
2023-04-08 00:35:31 +02:00
Jan Böhmer
8bccab258a Prevent appearance of a popup for a short time after deletion of an element on firefox
Related to issue #258
2023-04-07 23:12:08 +02:00
Jan Böhmer
6443d8e2bf Log the name of the CLI user, when actions were done from the CLI. 2023-04-07 22:44:59 +02:00
Jan Böhmer
286759f232 New translations validators.en.xlf (German) 2023-04-05 17:36:10 +02:00
Jan Böhmer
0dba32fdf2 New translations messages.en.xlf (German) 2023-04-05 17:36:09 +02:00
Jan Böhmer
54c6757bc7 Added some documentation about the stock owner system. 2023-04-05 16:35:29 +02:00
Jan Böhmer
c91a6640ff Fixed static analysis issues 2023-04-03 23:34:15 +02:00
Jan Böhmer
80ef617949 New translations messages.en.xlf (English) 2023-04-03 23:26:13 +02:00
Jan Böhmer
72dd3f92f9 Show expired amountSum in instock row on info page, similar to the part tables 2023-04-03 23:21:18 +02:00
Jan Böhmer
5330476dbe Highlight amount sum in part tables and part info page, when amount is less than minAmount 2023-04-03 23:15:29 +02:00
Jan Böhmer
69fdc85c99 Use new user select type for log filter 2023-04-03 22:54:07 +02:00
Jan Böhmer
f7293508ff Added example content for owner placeholders in labels 2023-04-03 22:48:52 +02:00
Jan Böhmer
4aedce9668 Allow to use storelocation owner field in labels
Related to issue #221
2023-04-03 22:41:18 +02:00
Jan Böhmer
9244fe5944 Fixed internal server error, when using owner placeholder on stored label profile 2023-04-03 22:23:53 +02:00
Jan Böhmer
35710b17d1 New translations validators.en.xlf (English) 2023-04-03 01:37:05 +02:00
Jan Böhmer
fb78ce5679 New translations messages.en.xlf (English) 2023-04-03 01:37:04 +02:00
Jan Böhmer
749e7dbdf9 Rempve default value definitions, which cause problems on MySQL 8 2023-04-03 01:03:16 +02:00
Jan Böhmer
ccae58cb2f Merge branch 'part_owners' 2023-04-03 00:54:29 +02:00
Jan Böhmer
64199b91d5 Synchronized MySQL schema with entity definitions 2023-04-03 00:53:58 +02:00
Jan Böhmer
c8218f6891 Added an explicit type for an old migration, so that (new) sqlite databases do not have a phase where a field has no type 2023-04-03 00:49:24 +02:00
Jan Böhmer
8e2f297839 Added migrations for sqlite 2023-04-03 00:47:51 +02:00
Jan Böhmer
0feb9661df Allow to use owner placeholders in labels 2023-04-03 00:03:56 +02:00
Jan Böhmer
1acceae81e Enforece that part lot owner matches storage location owner, if option is selected 2023-04-02 23:58:15 +02:00
Jan Böhmer
a7ff690891 Restrict part lot withdraw/add/move operations to the owner of a part lot 2023-04-02 23:35:18 +02:00
Jan Böhmer
447b54fa4b Allow to set and view the owner of a part lot 2023-04-02 23:17:24 +02:00
Jan Böhmer
5f5541ca12 Added UserSelectType and allow to set owner of a storage location 2023-04-02 21:50:22 +02:00
Jan Böhmer
f101e1b184 Only show SAML user badge in user admin, if the user is really a SAML user 2023-04-02 20:30:30 +02:00
Jan Böhmer
065417038c Added possibility to edit and view the aboutMe information of users 2023-04-02 20:26:42 +02:00
Jan Böhmer
047c82791b Added basic fields and migration for MySQL 2023-04-02 19:10:36 +02:00
Jan Böhmer
f1672c7076 New translations messages.en.xlf (German) 2023-04-02 17:16:02 +02:00
Jan Böhmer
e7e57fa412 Added test for StructuralElementDenormalizer 2023-04-02 17:09:38 +02:00
Jan Böhmer
5536fcce00 New translations messages.en.xlf (English) 2023-04-02 01:26:34 +02:00
Jan Böhmer
8a3ce36c65 Fixed static analysis issue 2023-04-02 01:17:19 +02:00
Jan Böhmer
325812fe95 Improved title of measurement unit admin admin form 2023-04-02 01:11:58 +02:00
Jan Böhmer
421a5d27dd Show part name as manufacturer URL link, when no MPN was set. 2023-04-02 01:03:33 +02:00
Jan Böhmer
27b43041f9 Allow to import orderdetails and partLots of parts 2023-04-02 01:00:40 +02:00
Jan Böhmer
a7ea12d07d Fixed import errors and reuse existing datastructrues from DB while importing complex data
Also now imports should not create duplicate instances of the same data elements. This fixes issue #101.
2023-04-02 00:55:20 +02:00
Jan Böhmer
927f570283 Fixed error popup window, when a server error occurs 2023-04-01 19:43:59 +02:00
Jan Böhmer
66c1eff79f Generate WebP thumbnails even for builtin footprints 2023-04-01 18:43:57 +02:00
Jan Böhmer
4cb1313a77 Use WebP for thumbnails, this reduces the thumbnail size drastically (~ 50%) 2023-04-01 00:16:38 +02:00
Jan Böhmer
52bdde40a1 Use network path instead of absolute URL for attachment thumbnails.
This should fix issue #237
2023-03-31 23:30:37 +02:00
Jan Böhmer
8295ed716b Updated dependencies. 2023-03-31 22:49:31 +02:00
Jan Böhmer
d84ee57354 We are in development of v1.3.0 2023-03-26 13:04:49 +02:00
Jan Böhmer
0ae57b8b7b Merge branch 'partkeepr_import' 2023-03-26 13:04:14 +02:00
Jan Böhmer
a4e68ea2d6 Added documentation about PartKeepr migration process 2023-03-26 00:32:03 +01:00
Jan Böhmer
a48b4ccaa8 Added an check that the user really knows that the command will delete all data. 2023-03-25 23:09:12 +01:00
Jan Böhmer
bcaf8e9912 Allow to import PartKeepr attachments 2023-03-25 22:59:31 +01:00
Jan Böhmer
ae438f1650 Ensure that the PartKeepr Version is correct. 2023-03-25 21:24:58 +01:00
Jan Böhmer
563d6bccd3 Added possibility to import users and projects 2023-03-25 21:09:02 +01:00
Jan Böhmer
7220d752ac Added possibilities to import part distributor infos 2023-03-25 16:26:39 +01:00
Jan Böhmer
46beb21ba7 Improved structure of the PartKeepr import 2023-03-25 00:25:18 +01:00
Jan Böhmer
c972f0ac59 Added possibility to import Part manufacturer and parameter information 2023-03-25 00:12:36 +01:00
Jan Böhmer
21c74fbcc8 Added basic import for parts 2023-03-24 23:43:05 +01:00
Jan Böhmer
1ca839ab26 Added import for storelocations 2023-03-24 22:51:41 +01:00
Jan Böhmer
34aefd32e8 Added possibility to import categories and footprints 2023-03-24 22:41:33 +01:00
Jan Böhmer
fce32e70b9 Started to work on an import possibility for Partkeepr databases 2023-03-23 01:16:12 +01:00
suuppl
0550c045c7 add missing '-' to code block (#254) 2023-03-20 16:28:29 +01:00
Jan Böhmer
69b1c062f5 New translations security.en.xlf (English) 2023-03-18 22:47:02 +01:00
Jan Böhmer
4713b2f079 New translations validators.en.xlf (English) 2023-03-18 22:47:01 +01:00
Jan Böhmer
6fe907a13d New translations messages.en.xlf (English) 2023-03-18 22:47:00 +01:00
Jan Böhmer
45ce4ac1ba New translations validators.en.xlf (German) 2023-03-18 22:46:53 +01:00
Jan Böhmer
9313f870bc Bumped version to 1.2.0 2023-03-18 22:29:59 +01:00
dependabot[bot]
9e72e88930 Bump symfonycorp/security-checker-action from 4 to 5 (#246)
Bumps [symfonycorp/security-checker-action](https://github.com/symfonycorp/security-checker-action) from 4 to 5.
- [Release notes](https://github.com/symfonycorp/security-checker-action/releases)
- [Commits](https://github.com/symfonycorp/security-checker-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: symfonycorp/security-checker-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-18 22:27:01 +01:00
Jan Böhmer
dcb64bf0a6 Merge remote-tracking branch 'origin/master' 2023-03-18 22:26:40 +01:00
Jan Böhmer
5d07070558 Do not build docker images for pull requests 2023-03-18 22:26:36 +01:00
dependabot[bot]
8c6ba9175b Bump actions/checkout from 2 to 3 (#247)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-18 22:25:31 +01:00
dependabot[bot]
ccaa2c48e2 Bump github/codeql-action from 1 to 2 (#248)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 1 to 2.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-18 22:25:07 +01:00
Jan Böhmer
5d38bf2e66 Use Github dependabot to check for outdated github actions 2023-03-18 22:20:04 +01:00
Jan Böhmer
15331da389 Removed actions updater workflow, as it is not compatiblee with our auto generated jekyll page build action 2023-03-18 22:17:28 +01:00
Jan Böhmer
477171abac Fixed actions updater workflow 2023-03-18 22:11:50 +01:00
Jan Böhmer
dc85e4f4a4 Run actions updater on every push 2023-03-18 22:05:30 +01:00
Jan Böhmer
ac402a6697 Updated some github actions workflows and added an workflow to automatically update actions 2023-03-18 22:03:34 +01:00
Jan Böhmer
f86d35f8d1 Dont disable the table multi action submit button, when user can not change parts as we use it for exporting and label generation too 2023-03-18 21:52:29 +01:00
Jan Böhmer
7d6c04e3cf Improved documentation 2023-03-18 21:41:00 +01:00
Jan Böhmer
5c059ce9fe Merge remote-tracking branch 'origin/l10n_master' 2023-03-18 20:36:09 +01:00
Jan Böhmer
575bffe0bf New translations messages.en.xlf (German) 2023-03-18 20:27:32 +01:00
Jan Böhmer
d0b70253fa New translations messages.en.xlf (German) 2023-03-18 20:06:47 +01:00
Jan Böhmer
5f04b2649f Updated dependencies. 2023-03-18 19:54:27 +01:00
Jan Böhmer
f0099859bb New translations messages.en.xlf (English) 2023-03-17 00:46:48 +01:00
Jan Böhmer
906b654afa Bumped version to 1.2.0-dev 2023-03-17 00:11:53 +01:00
Jan Böhmer
14740fad58 Merge branch 'part_import' 2023-03-17 00:11:16 +01:00
Jan Böhmer
e97a149474 Fixed static analysis issues 2023-03-17 00:11:01 +01:00
Jan Böhmer
c1d1270d59 Added documentation for BOM import 2023-03-17 00:08:49 +01:00
Jan Böhmer
e550918d7c Added links to bom import to project edit and info page 2023-03-16 23:56:46 +01:00
Jan Böhmer
f3449babc1 Added bom import to ApplicationAvailabilityFunctionalTest 2023-03-16 23:39:28 +01:00
Jan Böhmer
e444388517 Added tests for PCBnew BOM type 2023-03-16 23:32:12 +01:00
Jan Böhmer
bd2559c37b Added the basic possibility to import KiCAD BOMs into projects 2023-03-16 00:05:46 +01:00
Jan Böhmer
7abf44e893 Merge branch 'master' into part_import 2023-03-15 23:01:04 +01:00
Jan Böhmer
0b94a31d15 New translations messages.en.xlf (English) 2023-03-15 22:38:00 +01:00
Jan Böhmer
989e09b610 New translations messages.en.xlf (Russian) 2023-03-15 22:37:57 +01:00
Jan Böhmer
7e69e80290 New translations messages.en.xlf (Japanese) 2023-03-15 22:37:54 +01:00
Jan Böhmer
a3177dcfaf New translations messages.en.xlf (German) 2023-03-15 22:37:50 +01:00
Jan Böhmer
10e54d7a2d New translations messages.en.xlf (French) 2023-03-15 22:37:47 +01:00
Jan Böhmer
ed514a01bb Fixed exception when attachment file is not openable 2023-03-15 22:15:30 +01:00
Jan Böhmer
47fce4e914 Updated composer dependencies 2023-03-15 21:59:33 +01:00
Jan Böhmer
54276e19e9 Merge branch 'part_import' 2023-03-15 21:52:08 +01:00
Jan Böhmer
193650efd4 Added option to mark all imported parts as "needs review" 2023-03-15 21:46:14 +01:00
Jan Böhmer
b7aae7d87b Improved documentation and added example CSV file 2023-03-15 21:33:18 +01:00
Jan Böhmer
2c799d894b Fixed static analysis issues 2023-03-15 21:05:30 +01:00
Jan Böhmer
5745fc1046 Make import/export documentation a child of usage section 2023-03-14 00:20:44 +01:00
Jan Böhmer
80085abe16 Show better error messages for entity import at admin pages 2023-03-14 00:19:10 +01:00
Jan Böhmer
fe5dd065ed Added tests for EntityImporter service 2023-03-14 00:17:13 +01:00
Jan Böhmer
945fd988b3 Added tests for serializer normalizers 2023-03-14 00:02:40 +01:00
Jan Böhmer
3bbff0aecf Fixed errors that prevented import of users 2023-03-13 22:43:26 +01:00
Jan Böhmer
9188331c1e Fixed error popup behavior, when turbo could not find a matching turbo-fram in the response. 2023-03-13 22:39:07 +01:00
Jan Böhmer
be5663c468 Allow import/export of users 2023-03-13 22:16:02 +01:00
Jan Böhmer
9ac8098f15 Deny access to part import tool without permission and added to tools menu 2023-03-13 22:02:55 +01:00
Jan Böhmer
bd5ee837f4 Added permissions for importing data 2023-03-13 21:51:56 +01:00
Jan Böhmer
4be6cb2459 Added documentation on import/export function 2023-03-13 17:42:48 +01:00
Jan Böhmer
c466cb68b9 Allow to import supplier, supplier part number and price via CSV 2023-03-13 01:04:49 +01:00
Jan Böhmer
820be46ed3 Make more fiields importable 2023-03-13 00:52:22 +01:00
Jan Böhmer
4437f206af Allow alternative names for import for parts 2023-03-13 00:44:05 +01:00
Jan Böhmer
a1f4b35749 Explicitly mark our normalizers as cachabel or not 2023-03-13 00:35:31 +01:00
Jan Böhmer
b38f49a90e Added possibility to import storelocation and instock amount 2023-03-13 00:22:46 +01:00
Jan Böhmer
5d318b2693 Removed left over dump tag 2023-03-12 22:10:55 +01:00
Jan Böhmer
c7b9f9e50a Fixed PHPunit tests 2023-03-12 22:07:48 +01:00
Jan Böhmer
256d628543 Allow to control the path delimiter and create unknown datastructures
Also the labeling of form fields was improved
2023-03-12 22:03:02 +01:00
Jan Böhmer
508641d1e8 Added possibility to autoselect the import format 2023-03-12 21:43:40 +01:00
Jan Böhmer
61e2dde400 Allow to import category, footprint and manufacturer by giving a string in the CSV file 2023-03-12 21:10:48 +01:00
Jan Böhmer
85ae862381 Allow to set basic data via import 2023-03-12 20:01:29 +01:00
Jan Böhmer
7a9b7c87a4 Added a very basic import dialog for Parts 2023-03-12 19:53:55 +01:00
Jan Böhmer
8f033910ce Refactored EntityImporter service 2023-03-12 19:16:49 +01:00
Jan Böhmer
38b5e95842 Improved serialization result for parts 2023-03-12 01:41:44 +01:00
Jan Böhmer
2c67586873 Improved serialized fields 2023-03-12 01:12:35 +01:00
Jan Böhmer
b99e6c9a21 Updated serializer discriminator map 2023-03-12 00:35:48 +01:00
Jan Böhmer
49944cda87 Added possibility to export Parts from part tables 2023-03-12 00:27:04 +01:00
Jan Böhmer
3b36b2a4dc Improved exporter service 2023-03-11 22:40:53 +01:00
Jan Böhmer
1dfcffe70d We are in development of 1.1.2 now 2023-03-11 19:50:05 +01:00
Jan Böhmer
a9b3dcd2c2 Do the color inversion for the IC logos when darkmode is enabled, the logos are then shown as white on black background.
This fixes issue #242
2023-03-11 19:48:42 +01:00
Jan Böhmer
31f9145d3f Fixed jump to letter buttons on IC logos page 2023-03-11 19:43:43 +01:00
Jan Böhmer
ba04b94964 Bumped to version 1.1.0 2023-03-10 11:01:52 +01:00
Jan Böhmer
4ecf99c17e Don't fail when datatables state was not saved before
This should fix issue #241
2023-03-10 01:40:54 +01:00
Jan Böhmer
80389ff236 Bumped to version 1.1.0 2023-03-07 15:44:30 +01:00
Jan Böhmer
9e80b23726 New translations security.en.xlf (English) 2023-03-06 01:31:11 +01:00
Jan Böhmer
494a1c49f9 New translations security.en.xlf (German) 2023-03-06 01:31:08 +01:00
Jan Böhmer
4a77064826 New translations validators.en.xlf (English) 2023-03-06 01:31:07 +01:00
Jan Böhmer
ce90f10243 New translations validators.en.xlf (German) 2023-03-06 01:31:04 +01:00
Jan Böhmer
426aa4e41d New translations messages.en.xlf (English) 2023-03-06 01:31:02 +01:00
Jan Böhmer
bdc953cab0 New translations messages.en.xlf (German) 2023-03-06 01:30:58 +01:00
Jan Böhmer
15725a9f38 New Crowdin updates (#235)
* New translations messages.en.xlf (German)

* New translations messages.en.xlf (English)

* New translations validators.en.xlf (German)

* New translations validators.en.xlf (English)

* New translations security.en.xlf (English)

* New translations messages.en.xlf (German)

* New translations validators.en.xlf (German)

* New translations security.en.xlf (German)
2023-03-06 00:27:00 +01:00
Jan Böhmer
cc7d290feb Updated dependencies. 2023-03-06 00:14:53 +01:00
Jan Böhmer
40a2a46a5e Fixed phpunit tests 2023-03-06 00:12:46 +01:00
Jan Böhmer
2e160b0b0b Fixed static analysis issue 2023-03-06 00:05:51 +01:00
Jan Böhmer
5aaba102a7 Improved rendering of attachment icons 2023-03-06 00:01:54 +01:00
Jan Böhmer
52e459ec60 Use the name of an uploaded file for an attachment when no explicit name was set. 2023-03-05 23:47:45 +01:00
Jan Böhmer
4a30819ea5 Show error messages for attachments file field 2023-03-05 23:26:06 +01:00
Jan Böhmer
27969a1f65 Replaced leftover bootstrap_4 form theme usages with BS5 2023-03-05 23:12:44 +01:00
Jan Böhmer
c68b13b075 Removed accidentially added import 2023-03-05 23:06:25 +01:00
Jan Böhmer
1446aab451 Correctly map the errors of newly created elements in CollectionTypes
Before there were just shown on the parent, now they get mapped to the right field
2023-03-05 23:05:58 +01:00
Jan Böhmer
86f77fde1a Improved sorting possibilities for Project info BOM view 2023-03-05 01:20:32 +01:00
Jan Böhmer
02134dc959 Do not persist the selected datatable page number, as we always want to start at the first page after a page reload. 2023-03-05 01:11:57 +01:00
Jan Böhmer
c27b02512f Fixed problem with part tables that the wrong number of parts (and therefore pages) were displayed.
This hopefully does not break anything else.
2023-03-05 00:57:01 +01:00
Jan Böhmer
222e76ce47 Added option to search in internal part number (enabled by default)
This should fix issue #232
2023-03-04 23:37:38 +01:00
Jan Böhmer
0efb32c891 Updated composer dependencies. 2023-03-04 22:33:45 +01:00
Jan Böhmer
e808964913 Default docker container uses php-fpm and preloading now
This gives us a approx. 12% performance boost
2023-03-04 20:25:48 +01:00
Jan Böhmer
9ed1e896cb Pass environment variables used to configure SAML to dockers PHP 2023-03-04 17:30:27 +01:00
Jan Böhmer
49e521404a Show if SAML is enabled in the server info tool 2023-03-04 17:27:09 +01:00
Jan Böhmer
2ae34b856a Added hint about advanced saml config options to documentation 2023-03-04 17:21:22 +01:00
Jan Böhmer
6230ad971b Merge branch 'keycloak' 2023-03-04 17:15:50 +01:00
Jan Böhmer
20caad24ed Improved documentation 2023-03-04 17:15:17 +01:00
Jan Böhmer
eabdd3b11f Improved documentation for SAML SSO 2023-03-04 16:56:41 +01:00
Jan Böhmer
8fad743e85 Allow to select the priority of SAML role mapping based on the order in the configuration option 2023-03-04 16:52:17 +01:00
Jan Böhmer
f9fd015ecb Show configured and effective maximum file size in server info page. 2023-03-03 23:42:02 +01:00
Jan Böhmer
27de5ae387 Fixed static analysis issue 2023-03-02 23:57:32 +01:00
Jan Böhmer
4f43f10672 Bumped version to 1.0.3 2023-03-02 23:53:38 +01:00
Jan Böhmer
fb45ef432e Added documentation for MAX_ATTACHMENT_FILE_SIZE env 2023-03-02 23:53:16 +01:00
Jan Böhmer
d0a8e33bf2 Updated dependencies 2023-03-02 23:48:52 +01:00
Jan Böhmer
5a19024bec Use 10 based prefixes for byte sizes instead of 2-based
This way we are consistent with the way symfony interprets the prefixes
2023-03-02 23:39:12 +01:00
Jan Böhmer
e0635f7ead Show maximum allowed file size below the upload field for attachments 2023-03-02 23:38:23 +01:00
Jan Böhmer
6fa5efc4ca Increased the maximum file size from 16M to 100M and make it configurable
This fixes issue #228
2023-03-02 23:08:14 +01:00
Jan Böhmer
7394a23a83 Fixed infinite loop when an element gets assigned itself as parent
This fixes issue #230
2023-03-02 22:55:22 +01:00
Jan Böhmer
bbe4de996a Added documentation about the SAML_UPDATE_GROUP_ON_LOGIN env 2023-03-01 15:24:47 +01:00
Jan Böhmer
7030e752fc Added documentation about permission mapping. 2023-03-01 14:56:05 +01:00
Jan Böhmer
d845f8b7e3 Added documentation about the convert-to-saml-user command 2023-03-01 14:36:46 +01:00
Jan Böhmer
8a18951562 Fixed static analysis issue. 2023-02-28 17:03:57 +01:00
Jan Böhmer
cb9433902c Added SAML configuration options to docs 2023-02-28 16:34:51 +01:00
Jan Böhmer
472e1ce0a3 Added documentation on how to setup SAML. 2023-02-28 00:28:31 +01:00
Jan Böhmer
5e85c52a57 Allow to automatically assign SAML users to a group based on SAML attributes 2023-02-27 23:47:42 +01:00
Jan Böhmer
6a06a24296 Improved translations 2023-02-27 22:29:19 +01:00
Jan Böhmer
99f04d71af Revert "Moved all user info updating logic into SAMLUserFactory"
This reverts commit 960ee342e4.
2023-02-27 22:28:23 +01:00
Jan Böhmer
d1b8a36b93 Update SECURITY.md 2023-02-26 19:23:58 +01:00
Jan Böhmer
f20da0f049 Bumped version to 1.0.2 2023-02-26 18:58:34 +01:00
Jan Böhmer
5d3ab01176 Updated dependencies. 2023-02-26 18:57:35 +01:00
Jan Böhmer
83cd91f1d1 Fixed potential XSS injection vectors in datatables columns 2023-02-26 01:23:36 +01:00
Jan Böhmer
5f39d8e594 Properly escape user provided data in trans with data to prevent possible XSS attack vectors. 2023-02-26 00:52:00 +01:00
Jan Böhmer
6ff60e556e Properly escape user provided data in trans with data to prevent possible XSS attack vectors. 2023-02-26 00:41:08 +01:00
Jan Böhmer
5b7f44f4ea Merge pull request #225 from sascha988/patch-2
vulnerability XSS fix
2023-02-25 23:47:48 +01:00
Sascha Lenk
dc906bfb0f vulnerability XSS fix
The "trans with" command is not automatically escaping the string, so this is a XSS (Cross-Site Scripting) vulnerability.
Tested string: https://URL-TO-PART-DB-SERVER/de/parts/search?keyword=%22'%3E%3Cqss%20a%3D X147208852Y1_1Z%3E

QUALYS Enterprise WAS Scan Report classifies this as level 5 security risk
2023-02-25 22:42:03 +01:00
Jan Böhmer
b70c9d4f00 Merge pull request #223 from sascha988/patch-1
Translated parts_list.search.searching_for
2023-02-25 21:06:44 +01:00
Sascha
03e0584279 Translated parts_list.search.searching_for
Translated english text string parts_list.search.searching_for into german.
2023-02-25 21:05:00 +01:00
Jan Böhmer
960ee342e4 Moved all user info updating logic into SAMLUserFactory 2023-02-24 00:12:44 +01:00
Jan Böhmer
f5a5114999 Fixed PHPunit tests 2023-02-23 23:43:01 +01:00
Jan Böhmer
e6d9237bda Allow to specify a user by username or email with set-password commannd 2023-02-23 23:39:29 +01:00
Jan Böhmer
c831d57614 Added an console command to convert local to SAML users and vice versa 2023-02-23 23:36:40 +01:00
Jan Böhmer
c5904303e3 Allow to configure SAML via env variables 2023-02-22 00:50:51 +01:00
Jan Böhmer
586a57c2c9 Allow X500 attributes for user info and added some tests 2023-02-21 23:41:02 +01:00
Jan Böhmer
91fb861fd3 Use login form page to show error messages on Part-DB side 2023-02-21 23:11:16 +01:00
Jan Böhmer
b13655e951 Prevent login of local users via SSO with the same username 2023-02-21 22:36:43 +01:00
Jan Böhmer
e064ee4263 Prevent change of password of SAML users via CLI 2023-02-21 21:58:27 +01:00
Jan Böhmer
60f926924b Add a specific role to SAML user 2023-02-21 00:42:03 +01:00
Jan Böhmer
97c3b9002a Mark SAML users as so in database and disable local password changing then. 2023-02-21 00:29:50 +01:00
Jan Böhmer
78ec0f1ea3 Create a new DB user when somebody logs in using SAML 2023-02-20 23:04:20 +01:00
Jan Böhmer
c0b74d83a5 Started to work on interfacing with keycloak 2023-02-20 22:10:24 +01:00
415 changed files with 21067 additions and 3847 deletions

View File

@@ -39,6 +39,9 @@ if [ -d /var/www/html/var/db ]; then
fi
fi
# Start PHP-FPM
service php8.1-fpm start
# first arg is `-f` or `--some-option` (taken from https://github.com/docker-library/php/blob/master/8.2/bullseye/apache/docker-php-entrypoint)
if [ "${1#-}" != "$1" ]; then
set -- apache2-foreground "$@"

View File

@@ -26,12 +26,13 @@
# Pass the configuration from the docker env to the PHP environment (here you should list all .env options)
PassEnv APP_ENV APP_DEBUG APP_SECRET
PassEnv DATABASE_URL
PassEnv DEFAULT_LANG DEFAULT_TIMEZONE BASE_CURRENCY INSTANCE_NAME ALLOW_ATTACHMENT_DOWNLOADS USE_GRAVATAR
PassEnv DATABASE_URL ENFORCE_CHANGE_COMMENTS_FOR
PassEnv DEFAULT_LANG DEFAULT_TIMEZONE BASE_CURRENCY INSTANCE_NAME ALLOW_ATTACHMENT_DOWNLOADS USE_GRAVATAR MAX_ATTACHMENT_FILE_SIZE DEFAULT_URI
PassEnv MAILER_DSN ALLOW_EMAIL_PW_RESET EMAIL_SENDER_EMAIL EMAIL_SENDER_NAME
PassEnv HISTORY_SAVE_CHANGED_FIELDS HISTORY_SAVE_CHANGED_DATA HISTORY_SAVE_REMOVED_DATA
PassEnv ERROR_PAGE_ADMIN_EMAIL ERROR_PAGE_SHOW_HELP
PassEnv DEMO_MODE NO_URL_REWRITE_AVAILABLE FIXER_API_KEY BANNER
PassEnv SAML_ENABLED SAML_ROLE_MAPPING SAML_UPDATE_GROUP_ON_LOGIN SAML_IDP_ENTITY_ID SAML_IDP_SINGLE_SIGN_ON_SERVICE SAML_IDP_SINGLE_LOGOUT_SERVICE SAML_IDP_X509_CERT SAML_SP_ENTITY_ID SAML_SP_X509_CERT SAMLP_SP_PRIVATE_KEY
# For most configuration files from conf-available/, which are

47
.env
View File

@@ -31,6 +31,18 @@ INSTANCE_NAME="Part-DB"
ALLOW_ATTACHMENT_DOWNLOADS=0
# Use gravatars for user avatars, when user has no own avatar defined
USE_GRAVATAR=0
# The maximum allowed size for attachment files in bytes (you can use M for megabytes and G for gigabytes)
# Please note that the php.ini setting upload_max_filesize also limits the maximum size of uploaded files
MAX_ATTACHMENT_FILE_SIZE="100M"
# The public reachable URL of this Part-DB installation. This is used for generating links to the website in emails and so on
# This must end with a slash!
DEFAULT_URI="https://partdb.changeme.invalid/"
# With this option you can configure, where users are enforced to give a change reason, which will be logged
# This is a comma separated list of values, see documentation for available values
# Leave this empty, to make all change reasons optional
ENFORCE_CHANGE_COMMENTS_FOR=""
###################################################################################
# Email settings
@@ -69,6 +81,41 @@ ERROR_PAGE_ADMIN_EMAIL=''
# If this is set to true, solutions to common problems are shown on error pages. Disable this, if you do not want your users to see them...
ERROR_PAGE_SHOW_HELP=1
###################################################################################
# SAML Single sign on-settings
###################################################################################
# Set this to 1 to enable SAML single sign on
SAML_ENABLED=0
# A JSON encoded array of role mappings in the form { "saml_role": PARTDB_GROUP_ID, "*": PARTDB_GROUP_ID }
# The first match is used, so the order is important! Put the group mapping with the most privileges first.
# Please not to only use single quotes to enclose the JSON string
SAML_ROLE_MAPPING='{}'
# A mapping could look like the following
#SAML_ROLE_MAPPING='{ "*": 2, "admin": 1, "editor": 3}'
# When this is set to 1, the group of SAML users will be updated everytime they login based on their SAML roles
SAML_UPDATE_GROUP_ON_LOGIN=1
# The entity ID of your SAML IDP (e.g. the realm name of your Keycloak server)
SAML_IDP_ENTITY_ID="https://idp.changeme.invalid/realms/master"
# The URL of your SAML IDP SingleSignOnService (e.g. the endpoint of your Keycloak server)
SAML_IDP_SINGLE_SIGN_ON_SERVICE="https://idp.changeme.invalid/realms/master/protocol/saml"
# The URL of your SAML IDP SingleLogoutService (e.g. the endpoint of your Keycloak server)
SAML_IDP_SINGLE_LOGOUT_SERVICE="https://idp.changeme.invalid/realms/master/protocol/saml"
# The public certificate of the SAML IDP (e.g. the certificate of your Keycloak server)
SAML_IDP_X509_CERT="MIIC..."
# The entity of your SAML SP, must match the SP entityID configured in your SAML IDP (e.g. Keycloak).
# This should be a the domain name of your Part-DB installation, followed by "/sp"
SAML_SP_ENTITY_ID="https://partdb.changeme.invalid/sp"
# The public certificate of the SAML SP
SAML_SP_X509_CERT="MIIC..."
# The private key of the SAML SP
SAMLP_SP_PRIVATE_KEY="MIIE..."
######################################################################################
# Other settings
######################################################################################

11
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "github-actions" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"

View File

@@ -16,7 +16,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
@@ -29,7 +29,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
# Override language selection by uncommenting this and choosing your languages
# with:
# languages: go, javascript, csharp, python, cpp, java
@@ -37,7 +37,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -51,4 +51,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2

View File

@@ -10,9 +10,6 @@ on:
tags:
- 'v*.*.*'
- 'v*.*.*-**'
pull_request:
branches:
- 'master'
jobs:
docker:

View File

@@ -43,7 +43,7 @@ jobs:
run: ./bin/console lint:xliff translations
- name: Check dependencies for security
uses: symfonycorp/security-checker-action@v3
uses: symfonycorp/security-checker-action@v5
- name: Check doctrine mapping
run: ./bin/console doctrine:schema:validate --skip-sync -vvv --no-interaction

View File

@@ -63,7 +63,7 @@ jobs:
id: composer-cache
run: |
echo "::set-output name=dir::$(composer config cache-files-dir)"
- uses: actions/cache@v1
- uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}

View File

@@ -9,7 +9,7 @@ RUN apt-get update && apt-get -y install apt-transport-https lsb-release ca-cert
&& curl -sSLo /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg \
&& sh -c 'echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list' \
&& apt-get update && apt-get upgrade -y \
&& apt-get install -y apache2 php8.1 libapache2-mod-php8.1 php8.1-opcache php8.1-curl php8.1-gd php8.1-mbstring php8.1-xml php8.1-bcmath php8.1-intl php8.1-zip php8.1-xsl php8.1-sqlite3 php8.1-mysql gpg \
&& apt-get install -y apache2 php8.1 php8.1-fpm php8.1-opcache php8.1-curl php8.1-gd php8.1-mbstring php8.1-xml php8.1-bcmath php8.1-intl php8.1-zip php8.1-xsl php8.1-sqlite3 php8.1-mysql gpg \
&& apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*;
ENV APACHE_CONFDIR /etc/apache2
@@ -34,10 +34,11 @@ RUN sed -ri 's/^export ([^=]+)=(.*)$/: ${\1:=\2}\nexport \1/' "$APACHE_ENVVARS"
ln -sfT /dev/stderr "$APACHE_LOG_DIR/error.log"; \
ln -sfT /dev/stdout "$APACHE_LOG_DIR/access.log"; \
ln -sfT /dev/stdout "$APACHE_LOG_DIR/other_vhosts_access.log"; \
ln -sfT /dev/stderr /var/log/php8.1-fpm.log; \
chown -R --no-dereference "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$APACHE_LOG_DIR";
# Enable mpm_prefork
RUN a2dismod mpm_event && a2enmod mpm_prefork
# Enable php-fpm
RUN a2enmod proxy_fcgi setenvif && a2enconf php8.1-fpm
# PHP files should be handled by PHP, and should be preferred over any other file type
RUN { \
@@ -64,14 +65,16 @@ RUN \
# Configure Realpath cache for performance
echo 'realpath_cache_size=4096K'; \
echo 'realpath_cache_ttl=600'; \
} > /etc/php/8.1/apache2/conf.d/symfony-recommended.ini
} > /etc/php/8.1/fpm/conf.d/symfony-recommended.ini
# Increase upload limit
# Increase upload limit and enable preloading
RUN \
{ \
echo 'upload_max_filesize=256M'; \
echo 'post_max_size=300M'; \
} > /etc/php/8.1/apache2/conf.d/partdb.ini
echo 'opcache.preload_user=www-data'; \
echo 'opcache.preload=/var/www/html/config/preload.php'; \
} > /etc/php/8.1/fpm/conf.d/partdb.ini
# Install node and yarn
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -

View File

@@ -41,7 +41,8 @@ and multiple store locations and price information. Parts can be grouped using t
* Barcodes/Labels generator for parts and storage locations, scan barcodes via webcam using the builtin barcode scanner
* User system with groups and detailed (fine granular) permissions.
Two-factor authentication is supported (Google Authenticator and Webauthn/U2F keys) and can be enforced for groups. Password reset via email can be setuped.
* Import/Export system (partial working)
* Optional support for single sign-on (SSO) via SAML (using an intermediate service like [Keycloak](https://www.keycloak.org/) you can connect Part-DB to an existing LDAP or Active Directory server)
* Import/Export system for parts and datastructure. BOM import for projects from KiCAD is supported.
* Project management: Create projects and assign parts to the bill of material (BOM), to show how often you could build this project and directly withdraw all components needed from DB
* Event log: Track what changes happens to your inventory, track which user does what. Revert your parts to older versions.
* Responsive design: You can use Part-DB on your PC, your tablet and your smartphone using the same interface.

View File

@@ -9,4 +9,4 @@ fixed before the next release. However, if you find a security vulnerability in
## Reporting a Vulnerability
If you find a security vulnerability, contact the maintainer directly (Email: security@part-db.de).
If you find a security vulnerability, report a vulnerability in the [security section of GitHub](https://github.com/Part-DB/Part-DB-server/security/advisories) or contact the maintainer directly (Email: security@part-db.de)

View File

@@ -1 +1 @@
1.0.1
1.3.2

View File

@@ -96,6 +96,8 @@ const PLACEHOLDERS = [
['[[AMOUNT]]', 'Lot amount'],
['[[LOCATION]]', 'Storage location'],
['[[LOCATION_FULL]]', 'Storage location (Full path)'],
['[[OWNER]]', 'Full name of the lot owner'],
['[[OWNER_USERNAME]]', 'Username of the lot owner'],
]
},
{
@@ -110,6 +112,8 @@ const PLACEHOLDERS = [
['[[COMMENT_T]]', 'Comment (plain text)'],
['[[LAST_MODIFIED]]', 'Last modified datetime'],
['[[CREATION_DATE]]', 'Creation datetime'],
['[[OWNER]]', 'Full name of the location owner'],
['[[OWNER_USERNAME]]', 'Username of the location owner'],
]
},
{

View File

@@ -55,6 +55,8 @@ Object.assign( window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary, {
'Lot amount': 'Lot Menge',
'Storage location': 'Lagerort',
'Storage location (Full path)': 'Lagerort (Vollständiger Pfad)',
'Full name of the lot owner': 'Name des Besitzers des Lots',
'Username of the lot owner': 'Benutzername des Besitzers des Lots',
'Barcodes': 'Barcodes',
@@ -69,6 +71,8 @@ Object.assign( window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary, {
'Full path': 'Vollständiger Pfad',
'Parent name': 'Name des Übergeordneten Elements',
'Parent full path': 'Ganzer Pfad des Übergeordneten Elements',
'Full name of the location owner': 'Name des Besitzers des Lagerorts',
'Username of the location owner': 'Benutzername des Besitzers des Lagerorts',
'Username': 'Benutzername',
'Username (including name)': 'Benutzername (inklusive Name)',

View File

@@ -30,9 +30,73 @@ export default class SpecialCharactersEmoji extends Plugin {
const editor = this.editor;
const specialCharsPlugin = editor.plugins.get('SpecialCharacters');
//Add greek characters to special characters
specialCharsPlugin.addItems('Greek', this.getGreek());
//Add Emojis to special characters
specialCharsPlugin.addItems('Emoji', this.getEmojis());
}
getGreek() {
return [
{ title: 'Alpha', character: 'Α' },
{ title: 'Beta', character: 'Β' },
{ title: 'Gamma', character: 'Γ' },
{ title: 'Delta', character: 'Δ' },
{ title: 'Epsilon', character: 'Ε' },
{ title: 'Zeta', character: 'Ζ' },
{ title: 'Eta', character: 'Η' },
{ title: 'Theta', character: 'Θ' },
{ title: 'Iota', character: 'Ι' },
{ title: 'Kappa', character: 'Κ' },
{ title: 'Lambda', character: 'Λ' },
{ title: 'Mu', character: 'Μ' },
{ title: 'Nu', character: 'Ν' },
{ title: 'Xi', character: 'Ξ' },
{ title: 'Omicron', character: 'Ο' },
{ title: 'Pi', character: 'Π' },
{ title: 'Rho', character: 'Ρ' },
{ title: 'Sigma', character: 'Σ' },
{ title: 'Tau', character: 'Τ' },
{ title: 'Upsilon', character: 'Υ' },
{ title: 'Phi', character: 'Φ' },
{ title: 'Chi', character: 'Χ' },
{ title: 'Psi', character: 'Ψ' },
{ title: 'Omega', character: 'Ω' },
{ title: 'alpha', character: 'α' },
{ title: 'beta', character: 'β' },
{ title: 'gamma', character: 'γ' },
{ title: 'delta', character: 'δ' },
{ title: 'epsilon', character: 'ε' },
{ title: 'zeta', character: 'ζ' },
{ title: 'eta', character: 'η' },
{ title: 'theta', character: 'θ' },
{ title: 'alternate theta', character: 'ϑ' },
{ title: 'iota', character: 'ι' },
{ title: 'kappa', character: 'κ' },
{ title: 'lambda', character: 'λ' },
{ title: 'mu', character: 'μ' },
{ title: 'nu', character: 'ν' },
{ title: 'xi', character: 'ξ' },
{ title: 'omicron', character: 'ο' },
{ title: 'pi', character: 'π' },
{ title: 'rho', character: 'ρ' },
{ title: 'sigma', character: 'σ' },
{ title: 'tau', character: 'τ' },
{ title: 'upsilon', character: 'υ' },
{ title: 'phi', character: 'φ' },
{ title: 'chi', character: 'χ' },
{ title: 'psi', character: 'ψ' },
{ title: 'omega', character: 'ω' },
{ title: 'digamma', character: 'Ϝ' },
{ title: 'stigma', character: 'Ϛ' },
{ title: 'heta', character: 'Ͱ' },
{ title: 'sampi', character: 'Ϡ' },
{ title: 'koppa', character: 'Ϟ' },
{ title: 'san', character: 'Ϻ' },
];
}
getEmojis() {
//Map our emoji data to the format the plugin expects
return emoji.map(emoji => {

View File

@@ -66,7 +66,14 @@ export default class extends Controller {
}
stateLoadCallback(settings) {
return JSON.parse( localStorage.getItem(this.getStateSaveKey()) );
const data = JSON.parse( localStorage.getItem(this.getStateSaveKey()) );
if (data) {
//Do not save the start value (current page), as we want to always start at the first page on a page reload
data.start = 0;
}
return data;
}
connect() {
@@ -196,4 +203,4 @@ export default class extends Controller {
return this.element.dataset.select ?? false;
}
}
}

View File

@@ -107,6 +107,13 @@ export default class extends DatatablesController {
//Hide the select element (the tomselect button is the sibling of the select element)
select_target.nextElementSibling.classList.add('d-none');
}
//If the selected option has a data-turbo attribute, set it to the form
if (selected_option.dataset.turbo) {
this.element.dataset.turbo = selected_option.dataset.turbo;
} else {
this.element.dataset.turbo = true;
}
}
confirmDeletionAtSubmit(event) {

View File

@@ -29,42 +29,16 @@ export default class extends Controller
this._confirmed = false;
}
click(event) {
//If a user has not already confirmed the deletion, just let turbo do its work
if(this._confirmed) {
this._confirmed = false;
return;
}
event.preventDefault();
const message = this.element.dataset.deleteMessage;
const title = this.element.dataset.deleteTitle;
const that = this;
const confirm = bootbox.confirm({
message: message, title: title, callback: function (result) {
//If the dialog was confirmed, then submit the form.
if (result) {
that._confirmed = true;
event.target.click();
} else {
that._confirmed = false;
}
}
});
}
submit(event) {
//If a user has not already confirmed the deletion, just let turbo do its work
if(this._confirmed) {
if (this._confirmed) {
this._confirmed = false;
return;
}
//Prevent turbo from doing its work
event.preventDefault();
event.stopPropagation();
const message = this.element.dataset.deleteMessage;
const title = this.element.dataset.deleteTitle;
@@ -72,19 +46,20 @@ export default class extends Controller
const form = this.element;
const that = this;
//Create a clone of the event with the same submitter, so we can redispatch it if needed
//We need to do this that way, as we need the submitter info, just calling form.submit() would not work
this._our_event = new SubmitEvent('submit', {
submitter: event.submitter,
bubbles: true, //This line is important, otherwise Turbo will not receive the event
});
const confirm = bootbox.confirm({
message: message, title: title, callback: function (result) {
//If the dialog was confirmed, then submit the form.
if (result) {
//Set a flag to prevent the dialog from popping up again and allowing turbo to submit the form
that._confirmed = true;
form.dispatchEvent(that._our_event);
//Create a submit button in the form and click it to submit the form
//Before a submit event was dispatched, but this caused weird issues on Firefox causing the delete request being posted twice (and the second time was returning 404). See https://github.com/Part-DB/Part-DB-server/issues/273
const submit_btn = document.createElement('button');
submit_btn.type = 'submit';
submit_btn.style.display = 'none';
form.appendChild(submit_btn);
submit_btn.click();
} else {
that._confirmed = false;
}

View File

@@ -31,3 +31,7 @@
.darkmode--activated .hoverpic:hover {
background: black;
}
.tools-ic-logos img {
mix-blend-mode: normal;
}

View File

@@ -27,14 +27,67 @@ class ErrorHandlerHelper {
constructor() {
console.log('Error Handler registered');
const content = document.getElementById('content');
content.addEventListener('turbo:before-fetch-response', (event) => this.handleError(event));
//const content = document.getElementById('content');
//It seems that the content element is unreliable for these events, so we use the document instead
const content = document;
//content.addEventListener('turbo:before-fetch-response', (event) => this.handleError(event));
content.addEventListener('turbo:fetch-request-error', (event) => this.handleError(event));
content.addEventListener('turbo:frame-missing', (event) => this.handleError(event));
$(document).ajaxError(this.handleJqueryErrror.bind(this));
}
_showAlert(statusText, statusCode, location, responseHTML)
{
const httpStatusToText = {
'200': 'OK',
'201': 'Created',
'202': 'Accepted',
'203': 'Non-Authoritative Information',
'204': 'No Content',
'205': 'Reset Content',
'206': 'Partial Content',
'300': 'Multiple Choices',
'301': 'Moved Permanently',
'302': 'Found',
'303': 'See Other',
'304': 'Not Modified',
'305': 'Use Proxy',
'306': 'Unused',
'307': 'Temporary Redirect',
'400': 'Bad Request',
'401': 'Unauthorized',
'402': 'Payment Required',
'403': 'Forbidden',
'404': 'Not Found',
'405': 'Method Not Allowed',
'406': 'Not Acceptable',
'407': 'Proxy Authentication Required',
'408': 'Request Timeout',
'409': 'Conflict',
'410': 'Gone',
'411': 'Length Required',
'412': 'Precondition Required',
'413': 'Request Entry Too Large',
'414': 'Request-URI Too Long',
'415': 'Unsupported Media Type',
'416': 'Requested Range Not Satisfiable',
'417': 'Expectation Failed',
'418': 'I\'m a teapot',
'429': 'Too Many Requests',
'500': 'Internal Server Error',
'501': 'Not Implemented',
'502': 'Bad Gateway',
'503': 'Service Unavailable',
'504': 'Gateway Timeout',
'505': 'HTTP Version Not Supported',
};
//If the statusText is empty, we use the status code as text
if (!statusText) {
statusText = httpStatusToText[statusCode];
}
//Create error text
const title = statusText + ' (Status ' + statusCode + ')';
@@ -87,8 +140,10 @@ class ErrorHandlerHelper {
}
handleError(event) {
const fetchResponse = event.detail.fetchResponse;
const response = fetchResponse.response;
//Prevent default error handling
event.preventDefault();
const response = event.detail.response;
//Ignore aborted requests.
if (response.statusText === 'abort' || response.status == 0) {
@@ -100,11 +155,17 @@ class ErrorHandlerHelper {
return;
}
if(fetchResponse.failed) {
//Skip 404 errors, on admin pages (as this causes a popup on deletion in firefox)
if (response.status == 404 && event.target.id === 'admin-content-frame') {
return;
}
if(!response.ok) {
response.text().then(responseHTML => {
this._showAlert(response.statusText, response.status, fetchResponse.location.toString(), responseHTML);
this._showAlert(response.statusText, response.status, response.url, responseHTML);
}).catch(err => {
this._showAlert(response.statusText, response.status, fetchResponse.location.toString(), '<pre>' + err + '</pre>');
this._showAlert(response.statusText, response.status, response.url, '<pre>' + err + '</pre>');
});
}
}

View File

@@ -72,63 +72,223 @@ class RegisterEventHelper {
this.registerLoadHandler(() => {
//@ts-ignore
$("input[type=text], input[type=search]").unbind("keydown").keydown(function (event) {
let greek = event.altKey;
let use_special_char = event.altKey;
let greek_char = "";
if (greek){
if (use_special_char){
//Use the key property to determine the greek letter (as it is independent of the keyboard layout)
switch(event.key) {
case "w": //Omega
greek_char = '\u2126';
break;
case "u":
case "m": //Micro
greek_char = "\u00B5";
break;
case "p": //Phi
greek_char = "\u03C6";
break;
case "a": //Alpha
//Greek letters
case "a": //Alpha (lowercase)
greek_char = "\u03B1";
break;
case "b": //Beta
case "A": //Alpha (uppercase)
greek_char = "\u0391";
break;
case "b": //Beta (lowercase)
greek_char = "\u03B2";
break;
case "c": //Gamma
case "B": //Beta (uppercase)
greek_char = "\u0392";
break;
case "g": //Gamma (lowercase)
greek_char = "\u03B3";
break;
case "d": //Delta
case "G": //Gamma (uppercase)
greek_char = "\u0393";
break;
case "d": //Delta (lowercase)
greek_char = "\u03B4";
break;
case "l": //Pound
greek_char = "\u00A3";
case "D": //Delta (uppercase)
greek_char = "\u0394";
break;
case "y": //Yen
greek_char = "\u00A5";
case "e": //Epsilon (lowercase)
greek_char = "\u03B5";
break;
case "o": //Yen
greek_char = "\u00A4";
case "E": //Epsilon (uppercase)
greek_char = "\u0395";
break;
case "1": //Sum symbol
greek_char = "\u2211";
case "z": //Zeta (lowercase)
greek_char = "\u03B6";
break;
case "2": //Integral
greek_char = "\u222B";
case "Z": //Zeta (uppercase)
greek_char = "\u0396";
break;
case "3": //Less-than or equal
greek_char = "\u2264";
case "h": //Eta (lowercase)
greek_char = "\u03B7";
break;
case "4": //Greater than or equal
greek_char = "\u2265";
case "H": //Eta (uppercase)
greek_char = "\u0397";
break;
case "5": //PI
greek_char = "\u03c0";
case "q": //Theta (lowercase)
greek_char = "\u03B8";
break;
case "q": //Copyright
greek_char = "\u00A9";
case "Q": //Theta (uppercase)
greek_char = "\u0398";
break;
case "e": //Euro
greek_char = "\u20AC";
case "i": //Iota (lowercase)
greek_char = "\u03B9";
break;
case "I": //Iota (uppercase)
greek_char = "\u0399";
break;
case "k": //Kappa (lowercase)
greek_char = "\u03BA";
break;
case "K": //Kappa (uppercase)
greek_char = "\u039A";
break;
case "l": //Lambda (lowercase)
greek_char = "\u03BB";
break;
case "L": //Lambda (uppercase)
greek_char = "\u039B";
break;
case "m": //Mu (lowercase)
greek_char = "\u03BC";
break;
case "M": //Mu (uppercase)
greek_char = "\u039C";
break;
case "n": //Nu (lowercase)
greek_char = "\u03BD";
break;
case "N": //Nu (uppercase)
greek_char = "\u039D";
break;
case "x": //Xi (lowercase)
greek_char = "\u03BE";
break;
case "X": //Xi (uppercase)
greek_char = "\u039E";
break;
case "o": //Omicron (lowercase)
greek_char = "\u03BF";
break;
case "O": //Omicron (uppercase)
greek_char = "\u039F";
break;
case "p": //Pi (lowercase)
greek_char = "\u03C0";
break;
case "P": //Pi (uppercase)
greek_char = "\u03A0";
break;
case "r": //Rho (lowercase)
greek_char = "\u03C1";
break;
case "R": //Rho (uppercase)
greek_char = "\u03A1";
break;
case "s": //Sigma (lowercase)
greek_char = "\u03C3";
break;
case "S": //Sigma (uppercase)
greek_char = "\u03A3";
break;
case "t": //Tau (lowercase)
greek_char = "\u03C4";
break;
case "T": //Tau (uppercase)
greek_char = "\u03A4";
break;
case "u": //Upsilon (lowercase)
greek_char = "\u03C5";
break;
case "U": //Upsilon (uppercase)
greek_char = "\u03A5";
break;
case "f": //Phi (lowercase)
greek_char = "\u03C6";
break;
case "F": //Phi (uppercase)
greek_char = "\u03A6";
break;
case "c": //Chi (lowercase)
greek_char = "\u03C7";
break;
case "C": //Chi (uppercase)
greek_char = "\u03A7";
break;
case "y": //Psi (lowercase)
greek_char = "\u03C8";
break;
case "Y": //Psi (uppercase)
greek_char = "\u03A8";
break;
case "w": //Omega (lowercase)
greek_char = "\u03C9";
break;
case "W": //Omega (uppercase)
greek_char = "\u03A9";
break;
}
//Use keycodes for special characters as the shift char on the number keys are layout dependent
switch (event.keyCode) {
case 49: //1 key
//Product symbol on shift, sum on no shift
greek_char = event.shiftKey ? "\u220F" : "\u2211";
break;
case 50: //2 key
//Integral on no shift, partial derivative on shift
greek_char = event.shiftKey ? "\u2202" : "\u222B";
break;
case 51: //3 key
//Less than or equal on no shift, greater than or equal on shift
greek_char = event.shiftKey ? "\u2265" : "\u2264";
break;
case 52: //4 key
//Empty set on shift, infinity on no shift
greek_char = event.shiftKey ? "\u2205" : "\u221E";
break;
case 53: //5 key
//Not equal on shift, approx equal on no shift
greek_char = event.shiftKey ? "\u2260" : "\u2248";
break;
case 54: //6 key
//Element of on no shift, not element of on shift
greek_char = event.shiftKey ? "\u2209" : "\u2208";
break;
case 55: //7 key
//And on shift, or on no shift
greek_char = event.shiftKey ? "\u2227" : "\u2228";
break;
case 56: //8 key
//Proportional to on shift, angle on no shift
greek_char = event.shiftKey ? "\u221D" : "\u2220";
break;
case 57: //9 key
//Cube root on shift, square root on no shift
greek_char = event.shiftKey ? "\u221B" : "\u221A";
break;
case 48: //0 key
//Minus-Plus on shift, plus-minus on no shift
greek_char = event.shiftKey ? "\u2213" : "\u00B1";
break;
//Special characters
case 219: //hyphen (or ß on german layout)
//Copyright on no shift, TM on shift
greek_char = event.shiftKey ? "\u2122" : "\u00A9";
break;
case 191: //forward slash (or # on german layout)
//Generic currency on no shift, paragraph on shift
greek_char = event.shiftKey ? "\u00B6" : "\u00A4";
break;
//Currency symbols
case 192: //: or (ö on german layout)
//Euro on no shift, pound on shift
greek_char = event.shiftKey ? "\u00A3" : "\u20AC";
break;
case 221: //; or (ä on german layout)
//Yen on no shift, dollar on shift
greek_char = event.shiftKey ? "\u0024" : "\u00A5";
break;
}
if(greek_char=="") return;

View File

@@ -19,7 +19,7 @@
"use strict";
import {Tab} from "bootstrap";
import {Tab, Dropdown} from "bootstrap";
import tab from "bootstrap/js/src/tab";
/**
@@ -63,6 +63,16 @@ class TabRememberHelper {
*/
onInvalid(event) {
this.revealElementOnTab(event.target);
this.revealElementInDropdown(event.target);
}
revealElementInDropdown(element) {
let dropdown = element.closest('.dropdown-menu');
if(dropdown) {
let bs_dropdown = Dropdown.getOrCreateInstance(dropdown);
bs_dropdown.show();
}
}
revealElementOnTab(element) {

View File

@@ -9,6 +9,7 @@
"ext-intl": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-dom": "*",
"beberlei/doctrineextensions": "^1.2",
"brick/math": "^0.8.15",
"composer/package-versions-deprecated": "1.11.99.4",
@@ -22,7 +23,9 @@
"florianv/swap": "^4.0",
"florianv/swap-bundle": "dev-master",
"gregwar/captcha-bundle": "^2.1.0",
"hslavich/oneloginsaml-bundle": "^2.10",
"jbtronics/2fa-webauthn": "^1.0.0",
"league/csv": "^9.8.0",
"league/html-to-markdown": "^5.0.1",
"liip/imagine-bundle": "^2.2",
"nelexa/zip": "^4.0",

1540
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -27,4 +27,5 @@ return [
Scheb\TwoFactorBundle\SchebTwoFactorBundle::class => ['all' => true],
SpomkyLabs\CborBundle\SpomkyLabsCborBundle::class => ['all' => true],
Webauthn\Bundle\WebauthnBundle::class => ['all' => true],
Hslavich\OneloginSamlBundle\HslavichOneloginSamlBundle::class => ['all' => true],
];

View File

@@ -12,6 +12,8 @@ doctrine:
class: App\Doctrine\Types\UTCDateTimeType
big_decimal:
class: App\Doctrine\Types\BigDecimalType
tinyint:
class: App\Doctrine\Types\TinyIntType
schema_filter: ~^(?!internal)~
# Only enable this when needed

View File

@@ -0,0 +1,60 @@
# See https://github.com/SAML-Toolkits/php-saml for more information about the SAML settings
hslavich_onelogin_saml:
# Basic settings
idp:
entityId: '%env(string:SAML_IDP_ENTITY_ID)%'
singleSignOnService:
url: '%env(string:SAML_IDP_SINGLE_SIGN_ON_SERVICE)%'
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
singleLogoutService:
url: '%env(string:SAML_IDP_SINGLE_LOGOUT_SERVICE)%'
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
x509cert: '%env(string:SAML_IDP_X509_CERT)%'
sp:
entityId: '%env(string:SAML_SP_ENTITY_ID)%'
assertionConsumerService:
url: '%partdb.default_uri%saml/acs'
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
singleLogoutService:
url: '%partdb.default_uri%logout'
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
x509cert: '%env(string:SAML_SP_X509_CERT)%'
privateKey: '%env(string:SAMLP_SP_PRIVATE_KEY)%'
# Optional settings
#baseurl: 'http://myapp.com'
strict: true
debug: false
security:
allowRepeatAttributeName: true
# nameIdEncrypted: false
authnRequestsSigned: true
logoutRequestSigned: true
logoutResponseSigned: true
# wantMessagesSigned: false
# wantAssertionsSigned: true
# wantNameIdEncrypted: false
# requestedAuthnContext: true
# signMetadata: false
# wantXMLValidation: true
# relaxDestinationValidation: false
# destinationStrictlyMatches: true
# rejectUnsolicitedResponsesWithInResponseTo: false
# signatureAlgorithm: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
# digestAlgorithm: 'http://www.w3.org/2001/04/xmlenc#sha256'
#contactPerson:
# technical:
# givenName: 'Tech User'
# emailAddress: 'techuser@example.com'
# support:
# givenName: 'Support User'
# emailAddress: 'supportuser@example.com'
# administrative:
# givenName: 'Administrative User'
# emailAddress: 'administrativeuser@example.com'
#organization:
# en:
# name: 'Part-DB-name'
# displayname: 'Displayname'
# url: 'http://example.com'

View File

@@ -3,9 +3,15 @@ liip_imagine:
# valid drivers options include "gd" or "gmagick" or "imagick"
driver: "gd"
twig:
mode: lazy
default_filter_set_settings:
format: webp
filter_sets:
thumbnail_sm:
quality: 90
quality: 65
filters:
thumbnail:
size: [150, 150]
@@ -20,7 +26,7 @@ liip_imagine:
mode: inset
thumbnail_xs:
quality: 90
quality: 60
filters:
thumbnail:
size: [50, 50]

View File

@@ -4,7 +4,7 @@ framework:
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
#default_uri: http://localhost
default_uri: '%env(DEFAULT_URI)%'
when@prod:
framework:

View File

@@ -4,7 +4,6 @@ security:
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
@@ -12,6 +11,7 @@ security:
class: App\Entity\UserSystem\User
property: name
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
@@ -20,6 +20,7 @@ security:
provider: app_user_provider
lazy: true
user_checker: App\Security\UserChecker
entry_point: form_login
two_factor:
auth_form_path: 2fa_login
@@ -29,6 +30,14 @@ security:
login_throttling:
max_attempts: 5 # per minute
saml:
use_referer: true
user_factory: saml_user_factory
persist_user: true
check_path: saml_acs
login_path: saml_login
failure_path: login
# https://symfony.com/doc/current/security/form_login_setup.html
form_login:
login_path: login

View File

@@ -19,6 +19,7 @@ twig:
sidebar_tree_updater: '@App\Services\Trees\SidebarTreeUpdater'
avatar_helper: '@App\Services\UserSystem\UserAvatarHelper'
available_themes: '%partdb.available_themes%'
saml_enabled: '%partdb.saml.enabled%'
when@test:
twig:

View File

@@ -12,6 +12,9 @@ parameters:
partdb.default_currency: '%env(string:BASE_CURRENCY)%' # The currency that is used inside the DB (and is assumed when no currency is set). This can not be changed later, so be sure to set it the currency used in your country
partdb.global_theme: '' # The theme to use globally (see public/build/themes/ for choices, use name without .css). Set to '' for default bootstrap theme
partdb.locale_menu: ['en', 'de', 'fr', 'ru', 'ja'] # The languages that are shown in user drop down menu
partdb.enforce_change_comments_for: '%env(csv:ENFORCE_CHANGE_COMMENTS_FOR)%' # The actions for which a change comment is required (e.g. "part_edit", "part_create", etc.). If this is empty, change comments are not required at all.
partdb.default_uri: '%env(string:DEFAULT_URI)%' # The default URI to use for the Part-DB instance (e.g. https://part-db.example.com/). This is used for generating links in emails
######################################################################################################################
# Users and Privacy
@@ -29,9 +32,10 @@ parameters:
######################################################################################################################
# Attachments and files
######################################################################################################################
partdb.attachments.allow_downloads: '%env(bool:ALLOW_ATTACHMENT_DOWNLOADS)%' # Allow users to download attachments to server. Warning: This can be dangerous, because via that feature attackers maybe can access ressources on your intranet!
partdb.attachments.dir.media: 'public/media/' # The folder where uploaded attachment files are saved (must be in public folder)
partdb.attachments.dir.secure: 'uploads/' # The folder where secured attachment files are saved (must not be in public/)
partdb.attachments.allow_downloads: '%env(bool:ALLOW_ATTACHMENT_DOWNLOADS)%' # Allow users to download attachments to server. Warning: This can be dangerous, because via that feature attackers maybe can access ressources on your intranet!
partdb.attachments.dir.media: 'public/media/' # The folder where uploaded attachment files are saved (must be in public folder)
partdb.attachments.dir.secure: 'uploads/' # The folder where secured attachment files are saved (must not be in public/)
partdb.attachments.max_file_size: '%env(string:MAX_ATTACHMENT_FILE_SIZE)%' # The maximum size of an attachment file (in bytes, you can use M for megabytes and G for gigabytes)
######################################################################################################################
# Error pages
@@ -39,6 +43,11 @@ parameters:
partdb.error_pages.admin_email: '%env(trim:string:ERROR_PAGE_ADMIN_EMAIL)%' # You can set an email address here, which is shown on an error page, how to contact an administrator
partdb.error_pages.show_help: '%env(trim:string:ERROR_PAGE_SHOW_HELP)%' # If this is set to true, solutions to common problems are shown on error pages. Disable this, if you do not want your users to see them...
######################################################################################################################
# SAML
######################################################################################################################
partdb.saml.enabled: '%env(bool:SAML_ENABLED)%' # If this is set to true, SAML authentication is enabled
######################################################################################################################
# Sidebar
######################################################################################################################
@@ -95,7 +104,9 @@ parameters:
env(INSTANCE_NAME): 'Part-DB'
env(BASE_CURRENCY): 'EUR'
env(USE_GRAVATAR): '0'
env(ALLOW_ATTACHMENT_DOWNLOADS): 0
env(MAX_ATTACHMENT_FILE_SIZE): '100M'
env(ENFORCE_CHANGE_COMMENTS_FOR): ''
env(ERROR_PAGE_ADMIN_EMAIL): ''
env(ERROR_PAGE_SHOW_HELP): 1
@@ -110,3 +121,7 @@ parameters:
env(TRUSTED_PROXIES): '127.0.0.1' #By default trust only our own server
env(TRUSTED_HOSTS): '' # Trust all host names by default
env(DEFAULT_URI): 'https://partdb.changeme.invalid/'
env(SAML_ROLE_MAPPING): '{}'

View File

@@ -43,6 +43,9 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
revert_element:
label: "perm.revert_elements"
alsoSet: ["read", "edit", "create", "delete", "show_history"]
import:
label: "perm.import"
alsoSet: ["read", "edit", "create"]
parts_stock:
group: "data"
@@ -76,6 +79,9 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
revert_element:
label: "perm.revert_elements"
alsoSet: ["read", "edit", "create", "delete", "show_history"]
import:
label: "perm.import"
alsoSet: [ "read", "edit", "create" ]
footprints:
<<: *PART_CONTAINING
@@ -156,6 +162,9 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
revert_element:
label: "perm.revert_elements"
alsoSet: ["read", "edit", "create", "delete", "edit_permissions", "show_history"]
import:
label: "perm.import"
alsoSet: [ "read", "edit", "create" ]
users:
label: "perm.users"
@@ -188,6 +197,9 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
revert_element:
label: "perm.revert_elements"
alsoSet: ["read", "create", "delete", "edit_permissions", "show_history", "edit_infos", "edit_username"]
import:
label: "perm.import"
alsoSet: [ "read", "create" ]
#database:
# label: "perm.database"

View File

@@ -0,0 +1,4 @@
hslavich_saml_sp:
resource: "@HslavichOneloginSamlBundle/Resources/config/routing.yml"
# Only load the SAML routes if SAML is enabled
condition: "env('SAML_ENABLED') == '1' or env('SAML_ENABLED') == 'true'"

View File

@@ -88,11 +88,13 @@ services:
App\Form\AttachmentFormType:
arguments:
$allow_attachments_downloads: '%partdb.attachments.allow_downloads%'
$max_file_size: '%partdb.attachments.max_file_size%'
App\Services\Attachments\AttachmentSubmitHandler:
arguments:
$allow_attachments_downloads: '%partdb.attachments.allow_downloads%'
$mimeTypes: '@mime_types'
$max_upload_size: '%partdb.attachments.max_file_size%'
App\EventSubscriber\LogSystem\LogoutLoggerListener:
tags:
@@ -100,6 +102,10 @@ services:
event: 'Symfony\Component\Security\Http\Event\LogoutEvent'
dispatcher: security.event_dispatcher.main
App\Services\LogSystem\EventCommentNeededHelper:
arguments:
$enforce_change_comments_for: '%partdb.enforce_change_comments_for%'
####################################################################################################################
# Attachment system
####################################################################################################################
@@ -127,6 +133,15 @@ services:
# Security
####################################################################################################################
saml_user_factory:
alias: App\Security\SamlUserFactory
public: true
App\Security\SamlUserFactory:
arguments:
$saml_role_mapping: '%env(json:SAML_ROLE_MAPPING)%'
$update_group_on_login: '%env(bool:SAML_UPDATE_GROUP_ON_LOGIN)%'
####################################################################################################################
# Cache
####################################################################################################################
@@ -194,6 +209,10 @@ services:
arguments:
$available_themes: '%partdb.available_themes%'
App\Command\User\ConvertToSAMLUserCommand:
arguments:
$saml_enabled: '%partdb.saml.enabled%'
####################################################################################################################
# Label system
@@ -225,6 +244,13 @@ services:
tags:
- {name: serializer.normalizer, priority: -9000}
# Disable igbinary serialization for cache even when igbinary is available, as it causes issues with the doctrine
# proxy objects (see https://github.com/igbinary/igbinary/issues/377 and https://github.com/igbinary/igbinary/issues/273)
cache.default_marshaller:
class: Symfony\Component\Cache\Marshaller\DefaultMarshaller
arguments:
$useIgbinarySerialize: false
####################################################################################################################
# Miscellaneous

View File

@@ -0,0 +1,4 @@
name;description;category;notes;footprint;tags;quantity;storage_location;mass;ipn;mpn;manufacturing_status;manufacturer;supplier;spn;price;favorite;needs_review;minamount;partUnit;manufacturing_status
BC547;NPN transistor;Transistors -> NPN;very important notes;TO -> TO-92;NPN,Transistor;5;Room 1 -> Shelf 1 -> Box 2;10;;;Manufacturer;;You need to fill this line, to use spn and price;BC547C;2,3;0;;;;
BC557;PNP transistor;<b>HTML</b>;;TO -> TO-92;PNP,Transistor;10;Room 2-> Box 3;;Internal1234;;;;;;;;1;;;active
Copper Wire;;Wire;;;;;;;;;;;;;;;;;Meter;
1 name description category notes footprint tags quantity storage_location mass ipn mpn manufacturing_status manufacturer supplier spn price favorite needs_review minamount partUnit manufacturing_status
2 BC547 NPN transistor Transistors -> NPN very important notes TO -> TO-92 NPN,Transistor 5 Room 1 -> Shelf 1 -> Box 2 10 Manufacturer You need to fill this line, to use spn and price BC547C 2,3 0
3 BC557 PNP transistor <b>HTML</b> TO -> TO-92 PNP,Transistor 10 Room 2-> Box 3 Internal1234 1 active
4 Copper Wire Wire Meter

View File

@@ -26,7 +26,16 @@ The following configuration options can only be changed by the server administra
* `INSTANCE_NAME`: The name of your installation. It will be shown as a title in the navbar and other places. By default `Part-DB`, but you can customize it to something likes `ExampleCorp. Inventory`.
* `ALLOW_ATTACHMENT_DOWNLOADS` (allowed values `0` or `1`): By setting this option to 1, users can make Part-DB directly download a file specified as an URL and create it as local file. Please not that this allows users access to all ressources publicly available to the server (so full access to other servers in the same local network), which could be a security risk.
* `USE_GRAVATAR`: Set to `1` to use [gravatar.com](gravatar.com) images for user avatars (as long as they have not set their own picture). The users browsers have to download the pictures from a third-party (gravatars) server, so this might be a privacy risk.
* `MAX_ATTACHMENT_FILE_SIZE`: The maximum file size (in bytes) for attachments. You can use the suffix `K`, `M` or `G` to specify the size in kilobytes, megabytes or gigabytes. By default `100M` (100 megabytes). Please note that this only the limit of Part-DB. You still need to configure the php.ini `upload_max_filesize` and `post_max_size` to allow bigger files to be uploaded.
* `DEFAULT_URI`: The default URI base to use for the Part-DB, when no URL can be determined from the browser request. This should be the primary URL/Domain, which is used to access Part-DB. This value is used to create correct links in emails and other places, where the URL is needed. It is also used, when SAML is enabled.s If you are using a reverse proxy, you should set this to the URL of the reverse proxy (e.g. `https://part-db.example.com`). **This value must end with a slash**.
* `ENFORCE_CHANGE_COMMENTS_FOR`: With this option you can configure, where users are enforced to give a change reason, which will be written to the log. This is a comma separated list of values (e.g. `part_edit,part_delete`). Leave empty to make change comments optional everywhere. Possible values are:
* `part_edit`: Edit operation of a existing part
* `part_delete`: Delete operation of a existing part
* `part_create`: Creation of a new part
* `part_stock_operation`: Stock operation on a part (therefore withdraw, add or move stock)
* `datastructure_edit`: Edit operation of a existing datastructure (e.g. category, manufacturer, ...)
* `datastructure_delete`: Delete operation of a existing datastructure (e.g. category, manufacturer, ...)
* `datastructure_create`: Creation of a new datastructure (e.g. category, manufacturer, ...)
### E-Mail settings
* `MAILER_DSN`: You can configure the mail provider which should be used for email delivery (see https://symfony.com/doc/current/components/mailer.html for full documentation). If you just want to use an SMTP mail account, you can use the following syntax `MAILER_DSN=smtp://user:password@smtp.mailserver.invalid:587`
@@ -46,6 +55,22 @@ If you wanna use want to revert changes or view older revisions of entities, the
* `ERROR_PAGE_ADMIN_EMAIL`: You can set an email-address here, which is shown on the error page, who should be contacted about the issue (e.g. an IT support email of your company)
* `ERROR_PAGE_SHOW_HELP`: Set this 0, to disable the solution hints shown on an error page. These hints should not contain senstive informations, but could confuse end-users.
### SAML SSO settings
The following settings can be used to enable and configure Single-Sign on via SAML. This allows users to login to Part-DB without entering a username and password, but instead they are redirected to a SAML Identity Provider (IdP) and are logged in automatically. This is especially useful, when you want to use Part-DB in a company, where all users have a SAML account (e.g. via Active Directory or LDAP).
You can find more advanced settings in the `config/packages/hslavich_onelogin_saml.yaml` file. Please note that this file is not backuped by the backup script, so you have to backup it manually, if you want to keep your changes. If you want to edit it on docker, you have to map the file to a volume.
* `SAML_ENABLED`: When this is set to 1, SAML SSO is enabled and the SSO Login button is shown in the login form. You have to configure the SAML settings below, before you can use this feature.
* `SAML_ROLE_MAPPING`: A [JSON](https://en.wikipedia.org/wiki/JSON) encoded map which specifies how Part-DB should convert the user roles given by SAML attribute `group` should be converted to a Part-DB group (specified by ID). You can use a wildcard `*` to map all otherwise unmapped roles to a certain group. Example: `{"*": 1, "admin": 2, "editor": 3}`. This would map all roles to the group with ID 1, except the role `admin`, which is mapped to the group with ID 2 and the role `editor`, which is mapped to the group with ID 3.
* `SAML_UPDATE_GROUP_ON_LOGIN`: When this is enabled the group of the user is updated on every login of the user based on the SAML role attributes. When this is disabled, the group is only assigned on the first login of the user, and a Part-DB administrator can change the group afterwards by editing the user.
* `SAML_IDP_ENTITY_ID`: The entity ID of your SAML Identity Provider (IdP). You can find this value in the metadata XML file or configuration UI of your IdP.
* `SAML_IDP_SINGLE_SIGN_ON_SERVICE`: The URL of the SAML IdP Single Sign-On Service (SSO). You can find this value in the metadata XML file or configuration UI of your IdP.
* `SAML_IDP_SINGLE_LOGOUT_SERVICE`: The URL of the SAML IdP Single Logout Service (SLO). You can find this value in the metadata XML file or configuration UI of your IdP.
* `SAML_IDP_X509_CERT`: The base64 encoded X.509 public certificate of your SAML IdP. You can find this value in the metadata XML file or configuration UI of your IdP. It should start with `MIIC` and end with `=`.
* `SAML_SP_ENTITY_ID`: The entity ID of your SAML Service Provider (SP). This is the value you have configured for the Part-DB client in your IdP.
* `SAML_SP_X509_CERT`: The public X.509 certificate of your SAML SP (here Part-DB). This is the value you have configured for the Part-DB client in your IdP. It should start with `MIIC` and end with `=`. IdPs like keycloak allows you to generate a public/private key pair for the client which you can setup here and in the `SAML_SP_PRIVATE_KEY` setting.
* `SAML_SP_PRIVATE_KEY`: The private key of your SAML SP (here Part-DB), corresponding the public key specified in `SAML_SP_X509_CERT`. This is the value you have configured for the Part-DB client in your IdP. It should start with `MIIE` and end with `=`. IdPs like keycloak allows you to generate a public/private key pair for the client which you can setup here and in the `SAML_SP_X509_CERT` setting.
### Other / less used options
* `TRUSTED_PROXIES`: Set the IP addresses (or IP blocks) of trusted reverse proxies here. This is needed to get correct IP informations (see [here](https://symfony.com/doc/current/deployment/proxies.html) for more info).
* `TRUSTED_HOSTS`: To prevent `HTTP Host header attacks` you can set a regex containing all host names via which Part-DB should be accessible. If accessed via the wrong hostname, an error will be shown.

View File

@@ -27,7 +27,8 @@ It is installed on a web server and so can be accessed with any browser without
* Barcodes/Labels generator for parts and storage locations, scan barcodes via webcam using the builtin barcode scanner
* User system with groups and detailed (fine granular) permissions.
Two-factor authentication is supported (Google Authenticator and Webauthn/U2F keys) and can be enforced for groups. Password reset via email can be setuped.
* Import/Export system (partial working)
* Optional support for single sign-on (SSO) via SAML (using an intermediate service like [Keycloak](https://www.keycloak.org/) you can connect Part-DB to an existing LDAP or Active Directory server)
* Import/Export system
* Project management: Create projects and assign parts to the bill of material (BOM), to show how often you could build this project and directly withdraw all components needed from DB
* Event log: Track what changes happens to your inventory, track which user does what. Revert your parts to older versions.
* Responsive design: You can use Part-DB on your PC, your tablet and your smartphone using the same interface.
@@ -35,7 +36,7 @@ It is installed on a web server and so can be accessed with any browser without
* Support for rich text descriptions and comments in parts
* Support for multiple currencies and automatic update of exchange rates supported
* Powerful search and filter function, including parametric search (search for parts according to some specifications)
* Easy migration from an existing PartKeepr instance (see [here]({%link partkeepr_migration.md %}))
With these features Part-DB is useful to hobbyists, who want to keep track of their private electronic parts inventory,
or makerspaces, where many users have should have (controlled) access to the shared inventory.

View File

@@ -199,7 +199,7 @@ sudo nano config/parameters.yaml
sudo -u www-data php bin/console doctrine:migrations:migrate
# Clear Part-DB cache
sudo u www-data php bin/console cache:clear
sudo -u www-data php bin/console cache:clear
```
## MySQL/MariaDB database

View File

@@ -17,3 +17,5 @@ For example, if your reverse proxy has the IP address `192.168.2.10`, your value
```
TRUSTED_PROXIES=192.168.2.10
```
Set the `DEFAULT_URI` environment variable to the URL of your Part-DB installation, available from the outside (so via the reverse proxy).

View File

@@ -0,0 +1,150 @@
---
title: Single Sign-On via SAML
layout: default
parent: Installation
nav_order: 12
---
# Single Sign-On via SAML
Part-DB supports Single Sign-On via SAML. This means that you can use your existing SAML identity provider to log in to Part-DB.
Using an intermediate SAML server like [Keycloak](https://www.keycloak.org/), also allows you to connect Part-DB to a LDAP or Active Directory server.
{: .important }
> This feature is for advanced users only. Single Sign-On is useful for large organizations with many users, which are already using SAML for other services.
> If you have only one or a few users, you should use the built-in authentication system of Part-DB.
> This guide assumes that you already have an SAML identity provider set up and working, and have a basic understanding of how SAML works.
{: .warning }
> This feature is currently in beta. Please report any bugs you find.
> So far it has only tested with Keycloak, but it should work with any SAML 2.0 compatible identity provider.
This guide will show you how to configure Part-DB with [Keycloak](https://www.keycloak.org/) as the SAML identity provider,
but it should work with any SAML 2.0 compatible identity provider.
This guide assumes that you have a working Keycloak installation with some users. If you don't, you can follow the [Keycloak Getting Started Guide](https://www.keycloak.org/docs/latest/getting_started/index.html).
{: .important }
> Part-DB associates local users with SAML users by their username. That means if the username of a SAML user changes, a new local user will be created (and the old account can not be accessed).
> You should make sure that the username of a SAML user does not change. If you use Keycloak make sure that the possibility to change the username is disabled (which is by default).
> If you really have to rename a SAML user, a Part-DB admin can rename the local user in the Part-DB in the admin panel, to match the new username of the SAML user.
## Configure basic SAML connection
### Create a new SAML client
1. First, you need to configure a new SAML client in Keycloak. Login in to your Keycloak admin console and go to the `Clients` page.
2. Click on `Create client` and select `SAML` as type from the dropdown menu. For the client ID, you can use anything you want, but it should be unique.
*It is recommended to set this value to the domain name of your Part-DB installation, with an attached `/sp` (e.g. `https://partdb.yourdomain.invalid/sp`)*.
The name field should be set to something human-readable, like `Part-DB`.
3. Click on `Save` to create the new client.
### Configure the SAML client
1. Now you need to configure the SAML client. Go to the `Settings` tab and set the following values:
* Set `Home URL` to the homepage of your Part-DB installation (e.g. `https://partdb.yourdomain.invalid/`).
* Set `Valid redirect URIs` to your homepage with a wildcard at the end (e.g. `https://partdb.yourdomain.invalid/*`).
* Set `Valid post logout redirect URIs` to `+` to allow all urls from the `Valid redirect URIs`.
* Set `Name ID format` to `username`
* Ensure `Force POST binding` is enabled.
* Ensure `Sign documents` is enabled.
* Ensure `Front channel logout` is enabled.
* Ensure `Signature Algorithm` is set to `RSA_SHA256`.
Click on `Save` to save the changes.
2. Go to the `Advanced` tab and set the following values:
* Assertion Consumer Service POST Binding URL to your homepage with `/saml/acs` at the end (e.g. `https://partdb.yourdomain.invalid/saml/acs`).
* Logout Service POST Binding URL to your homepage with `/logout` at the end (e.g. `https://partdb.yourdomain.invalid/logout`).
3. Go to Keys tab and ensure `Client Signature Required` is enabled.
4. In the Keys tab click on `Generate new keys`. This will generate a new key pair for the SAML client. The private key will be downloaded to your computer.
### Configure Part-DB to use SAML
1. Open the `.env.local` file of Part-DB (or the docker-compose.yaml) for edit
2. Set the `SAMLP_SP_PRIVATE_KEY` environment variable to the content of the private key file you downloaded in the previous step. It should start with `MIEE` and end with `=`.
3. Set the `SAML_SP_X509_CERT` environment variable to the content of the Certificate field shown in the `Keys` tab of the SAML client in Keycloak. It should start with `MIIC` and end with `=`.
4. Set the `SAML_SP_ENTITY_ID` environment variable to the entityID of the SAML client in Keycloak (e.g. `https://partdb.yourdomain.invalid/sp`).
5. In Keycloak navigate to `Realm Settings` -> `SAML 2.0 Identity Provider` (by default something like `https://idp.yourdomain.invalid/realms/master/protocol/saml/descriptor) to show the SAML metadata.
6. Copy the `entityID` value from the metadata to the `SAML_IDP_ENTITY_ID` configuration variable of Part-DB (by default something like `https://idp.yourdomain.invalid/realms/master`).
7. Copy the `Single Sign-On Service` value from the metadata to the `SAML_IDP_SINGLE_SIGN_ON_SERVICE` configuration variable of Part-DB (by default something like `https://idp.yourdomain.invalid/realms/master/protocol/saml`).
8. Copy the `Single Logout Service` value from the metadata to the `SAML_IDP_SINGLE_LOGOUT_SERVICE` configuration variable of Part-DB (by default something like `https://idp.yourdomain.invalid/realms/master/protocol/saml/logout`).
9. Copy the `X.509 Certificate` value from the metadata to the `SAML_IDP_X509_CERT` configuration variable of Part-DB (it should start with `MIIC` and should be pretty long).
10. Set the `DEFAULT_URI` to the homepage (on the publicly available domain) of your Part-DB installation (e.g. `https://partdb.yourdomain.invalid/`). It must end with a slash.
11. Set the `SAML_ENABLED` configuration in Part-DB to 1 to enable SAML authentication.
When you access the Part-DB login form now, you should see a new button to log in via SSO. Click on it to be redirected to the SAML identity provider and log in.
In the following sections, you will learn how to configure that Part-DB uses the data provided by the SAML identity provider to fill out user informations.
### Set user information based on SAML attributes
Part-DB can set basic user information like the username, the real name and the email address based on the SAML attributes provided by the SAML identity provider.
To do this, you need to configure your SAML identity provider to provide the following attributes:
* `email` or `urn:oid:1.2.840.113549.1.9.1` for the email address
* `firstName` or `urn:oid:2.5.4.42` for the first name
* `lastName` or `urn:oid:2.5.4.4` for the last name
* `department` for the department field of the user
You can omit any of these attributes, but then the corresponding field will be empty (but can be overriden by an administrator).
These values are written to Part-DB database, whenever the user logs in via SAML (the user is created on the first login, and updated on every login).
To configure Keycloak to provide these attributes, you need to go to the `Client scopes` page and select the `sp-dedicatd` client scope (or create a new one).
In the scope configuration page, click on `Add mappers` and `From predefined mappers`. Select the following mappers:
* `X500 email`
* `X500 givenName`
* `X500 surname`
and click `Add`. Now Part-DB will be provided with the email, first name and last name of the user based on the Keycloak user database.
### Configure permissions for SAML users
On the first login of a SAML user, Part-DB will create a new user in the database. This user will have the same username as the SAML user, but no password set. The user will be marked as a SAML user, so he can only login via SAML in the future. However in other aspects the user is a normal user, so Part-DB admins can set permissions for SAML users like for any other user and override permissions assigned via groups.
However for large organizations you maybe want to automatically assign permissions to SAML users based on the roles or groups configured in the identity provider. For this purpose Part-DB allows you to map SAML roles or groups to Part-DB groups. See the next section for details.
### Map SAML roles to Part-DB groups
Part-DB allows you to configure a mapping between SAML roles or groups and Part-DB groups. This allows you to automatically assign permissions to SAML users based on the roles or groups configured in the identity provider. For example if a user at your SAML provider has the role `admin`, you can configure Part-DB to assign the `admin` group to this user. This will give the user all permissions of the `admin` group.
For this you need first have to create the groups in Part-DB, to which you want to assign the users and configure their permissions. You will need the IDs of the groups, which you can find in the `System->Group` page of Part-DB in the Info tab.
The map is provided as [JSON](https://en.wikipedia.org/wiki/JSON) encoded map between the SAML role and the group ID, which has the form `{"saml_role": group_id, "*": group_id, ...}`. You can use the `*` key to assign a group to all users, which are not in any other group. The map is configured via the `SAML_ROLE_MAPPING` environment variable, which you can configure via the `.env.local` or `docker-compose.yml` file. Please note that you have to enclose the JSON string in single quotes here, as JSON itself uses double quotes (e.g. `SAML_ROLE_MAPPING='{ "*": 2, "editor": 3, "admin": 1 }`).
For example if you want to assign the group with ID 1 (by default admin) to every SAML user which has the role `admin`, the role with ID 3 (by default editor) to every SAML user with the role `editor` and everybody else to the group with ID 2 (by default readonly), you can configure the following map:
```
SAML_ROLE_MAPPING='{"admin": 1, "editor": 3, "*": 2}'
```
Please not that the order of the mapping is important. The first matching role will be assigned to the user. So if you have a user with the roles `admin` and `editor`, he will be assigned to the group with ID 1 (admin) and not to the group with ID 3 (editor), as the `admin` role comes first in the JSON map.
This mean that you should always put the most specific roles (e.g. admins) first of the map and the most general roles (e.g. normal users) later.
If you want to assign users with a certain role to a empty group, provide the group ID -1 as the value. This is not a valid group ID, so the user will not be assigned to any group.
The SAML roles (or groups depending on your configuration), have to be supplied via a SAML attribute `group`. You have to configure your SAML identity provider to provide this attribute. For example in Keycloak you can configure this attribute in the `Client scopes` page. Select the `sp-dedicated` client scope (or create a new one) and click on `Add mappers`. Select `Role mapping` or `Group membership`, change the field name and click `Add`. Now Part-DB will be provided with the groups of the user based on the Keycloak user database.
By default, the group is assigned to the user on the first login and updated on every login based on the SAML attributes. This allows you to configure the groups in the SAML identity provider and the users will automatically stay up to date with their permissions. However, if you want to disable this behavior (and let the Part-DB admins configure the groups manually, after the first login), you can set the `SAML_UPDATE_GROUP_ON_LOGIN` environment variable to `false`. If you want to disable the automatic group assignment completly (so not even on the first login of a user), set the `SAML_ROLE_MAPPING` to `{}` (empty JSON object).
## Overview of possible SAML attributes used by Part-DB
The following table shows all SAML attributes, which can be usedby Part-DB. If your identity provider is configured to provide these attributes, you can use to automatically fill the corresponding fields of the user in Part-DB.
| SAML attribute | Part-DB user field | Description |
|-------------------------------------------|-------------------|-------------------------------------------------------------------|
| `urn:oid:1.2.840.113549.1.9.1` or `email` | email | The email address of the user. |
| `urn:oid:2.5.4.42` or `firstName` | firstName | The first name of the user. |
| `urn:oid:2.5.4.4` or `lastName` | lastName | The last name of the user. |
| `department` | department | The department of the user. |
| `group` | group | The group of the user (determined by `SAML_ROLE_MAPPING` option). |
## Use SAML Login for existing users
Part-DB distinguishes between local users and SAML users. Local users are users, which can login via Part-DB login form and which use the password (hash) saved in the Part-DB database. SAML users are stored in the database too (they are created on the first login of the user via SAML), but they use the SAML identity provider to authenticate the user and have no password stored in the database. When you try you will get an error message.
For security reasons it is not possible to authenticate via SAML as a local user (and vice versa). So if you have existing users in your Part-DB database and want them to be able to login via SAML in the future, you can use the `php bin/console partdb:user:convert-to-saml-user username` command to convert them to SAML users. This will remove the password hash from the database and mark them as SAML users, so they can login via SAML in the future.
The reverse is also possible: If you have existing SAML users and want them to be able to login via the Part-DB login form, you can use the `php bin/console partdb:user:convert-to-saml-user --to-local username` command to convert them to local users. You have to set an password for the user afterwards.
{: .important }
> It is recommended that you let the original admin user (ID: 2) be a local user, so you can still login to Part-DB if the SAML identity provider is not available.
## Advanced SAML configuration
You can find some more advanced SAML configuration options in the `config/packages/hslavich_onelogin_saml.yaml` file. Refer to the file for more information.
Normally you don't have to change anything here.
Please note that this file is not saved by the Part-DB backup tool, so you have to save it manually if you want to keep your changes. On docker containers you have to configure a volume mapping for it.

View File

@@ -0,0 +1,51 @@
---
layout: default
title: Migrate from PartKeepr to Part-DB
nav_order: 101
---
# Migrate from PartKeepr to Part-DB
{: .warning }
> This feature is currently in beta. Please report any bugs you find.
This guide describes how to migrate from [PartKeepr](https://partkeepr.org/) to Part-DB.
Part-DB has a built-in migration tool, which can be used to migrate the data from an existing PartKeepr instance to
a new Part-DB instance. Most of the data can be migrated, however there are some limitations, you can find below.
## What can be imported
* Datastructures (Categories, Footprints, Storage Locations, Manufacturers, Distributors, Part Measurement Units)
* Basic part informations (Name, Description, Comment, etc.)
* Attachments and images of parts, projects, footprints, manufacturers and storage locations
* Part prices (distributor infos)
* Part parameters
* Projects (including parts and attachments)
* Users (optional): Passwords however will be not migrated, and need to be reset later
## What can't be imported
* Metaparts (A dummy version of the metapart will be created in Part-DB, however it will not function as metapart)
* Multiple manufacturers per part (only the last manufacturer of a part will be migrated)
* Overage information for project parts (the overage info will be set as comment in the project BOM, but will have no effect)
* Batch Jobs
* Parameter Units (the units will be written into the parameters)
* Project Reports and Project Runs
* Stock history
* Any kind of PartKeepr preferences
## How to migrate
1. Install Part-DB like described in the installation guide. You can use any database backend you want (mysql or sqlite). Run the database migration, but do not create any new data yet.
2. Export your PartKeepr database as XML file using [mysqldump](https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html):
When the MySQL database is running on the local computer and you are root you can just run the command `mysqldump --xml PARTKEEPR_DATABASE --result-file pk.xml`.
If your server is remote or your MySQL authentication is different, you need to run `mysqldump --xml -h PARTKEEPR_HOST -u PARTKEEPR_USER -p PARTKEEPR_DATABASE`, where you replace `PARTKEEPR_HOST`
with the hostname of your MySQL database and `PARTKEEPR_USER` with the username of MySQL user which has access to the PartKeepr database. You will be asked for the MySQL user password.
3. Go the Part-DB main folder and run the command `php bin/console partdb:migrations:import-partkeepr path/to/pk.xml`. This step will delete all existing data in the Part-DB database and import the contents of PartKeepr.
4. Copy the contents of `data/files/` from your PartKeepr installation to the `uploads/` folder of your Part-DB installation and the contents of `data/images` from PartKeepr to `public/media/` of Part-DB.
5. Clear the cache of Part-DB by running: `php bin/console cache:clear`
6. Go to the Part-DB web interface. You can login with the username `admin` and the password, which is shown during the installation process of Part-DB (step 1). You should be able to see all the data from PartKeepr.
## Import users
If you want to import the users (mostly the username and email address) from PartKeepr, you can add the `--import-users` option on the database import command (step 3): `php bin/console partdb:migrations:import-partkeepr --import-users path/to/pk.xml`.
All imported users of PartKeepr will be assigned to a new group "PartKeepr Users", which has normal user permissions (so editing data, but no administrative tasks). You can change the group and permissions later in Part-DB users managment.
Passwords can not be imported from PartKeepr and all imported users get marked as disabled user. So to allow users to login, you need to enable them in the user management and assign a password.

View File

@@ -22,6 +22,11 @@ php bin/console doctrine:migrations:migrate
If this does not help, please [open an issue on GitHub](https://github.com/Part-DB/Part-DB-symfony).
## Search for user and reset password:
You can list all users with the following command: `php bin/console partdb:users:list`
To reset the password of a user you can use the following command: `php bin/console partdb:users:set-password [username]`
## Error logs
Detailed error logs can be found in the `var/log` directory.
When Part-DB is installed directly, the errors are written to the `var/log/prod.log` file.
@@ -35,4 +40,4 @@ docker-compose logs -f
Please include the error logs in your issue on GitHub, if you open an issue.
## Report Issue
If an error occurs, or you found a bug, please [open an issue on GitHub](https://github.com/Part-DB/Part-DB-symfony).
If an error occurs, or you found a bug, please [open an issue on GitHub](https://github.com/Part-DB/Part-DB-symfony).

View File

@@ -1,6 +1,7 @@
---
layout: default
title: Upgrade from legacy Part-DB version (<1.0)
nav_order: 100
---
# Upgrade from legacy Part-DB version
@@ -23,13 +24,14 @@ Some things changed however to the old version and some features are still missi
* Configuration is now done via configuration files / environment variables instead of the WebUI (this maybe change in the future).
* Database updated are now done via console instead of the WebUI
* Permission system changed: **You will have to newly set the permissions of all users and groups!**
* Import / Export file format changed. Fields must be english now (unlike in legacy Part-DB versions, where german fields in CSV were possible)
and you maybe have to change the header line/field names of your CSV files.
## Missing features
* No possibility to mark parts for ordering (yet)
* No import / export possibility for parts (yet), however you can import/export other datastructures like Categories, Footprints, etc. (yet)
* No support for 3D models of footprints (yet)
* No possibility to disable footprints, manufacturers globally (or per category). This should not have a big impact, when you forbid users to edit/create them.
* No resitor calculator or SMD labels tools
* No resistor calculator or SMD labels tools
## Upgrade process

29
docs/usage/bom_import.md Normal file
View File

@@ -0,0 +1,29 @@
---
layout: default
title: Import Bill of Material (BOM) for Projects
nav_order: 5
parent: Usage
---
# Import Bill of Material (BOM) for Projects
Part-DB supports the import of Bill of Material (BOM) files for projects. This allows you to directly import a BOM file from your ECAD software into your Part-DB project.
The import process is currently semi-automatic. This means Part-DB will take the BOM file and create entries for all parts in the BOM file in your project and assign fields like
mountnames (e.g. 'C1, C2, C3'), quantity and more.
However, you still have to assign the parts from Part-DB database to the entries (if applicable) after the import by hand,
as Part-DB can not know which part you had in mind when you designed your schematic.
## Usage
In the project view or edit click on the "Import BOM" button, below the BOM table. This will open a dialog where you can
select the BOM file you want to import and some options for the import process:
* **Type**: The format/type of the BOM file. See below for explanations of the different types.
* **Clear existing BOM entries before import**: If this is checked, all existing BOM entries, which are currently associated with the project, will be deleted before the import.
### Supported BOM file formats
* **KiCAD Pcbnew BOM (CSV file)**: A CSV file of the Bill of Material (BOM) generated by [KiCAD Pcbnew](https://www.kicad.org/).
Please note that you have to export the BOM from the PCB editor, the BOM generated by the schematic editor (Eeschema) has a different format and does not work with this type.
You can generate this BOM file by going to "File" -> "Fabrication Outputs" -> "Bill of Materials" in Pcbnew and save the file to your desired location.

View File

@@ -19,6 +19,7 @@ You can get help for every command with the parameter `--help`. See `php bin/con
* `php bin/console partdb:users:permissions`: View/Change the permissions of the user with the given username
* `php bin/console partdb:users:upgrade-permissions-schema`: Upgrade the permissions schema of users to the latest version (this is normally automatically done when the user visits a page)
* `php bin/console partdb:logs:show`: Show the most recent entries of the Part-DB event log / recent activity
* `php bin/console partdb:user:convert-to-saml-user`: Convert a local user to a SAML/SSO user. This is needed, if you want to use SAML/SSO authentication for a user, which was created before you enabled SAML/SSO authentication.
## Currency commands
* `php bin/console partdb:currencies:update-exchange-rates`: Update the exchange rates of all currencies from the internet)
@@ -30,6 +31,7 @@ You can get help for every command with the parameter `--help`. See `php bin/con
* `partdb:migrations:convert-bbcode`: Migrate the old BBCode markup codes used in legacy Part-DB versions (< 1.0.0) to the new markdown syntax
* `partdb:attachments:clean-unused`: Remove all attachments which are not used by any database entry (e.g. orphaned attachments)
* `partdb:cache:clear`: Clears all caches, so the next page load will be slower, but the cache will be rebuild. This can maybe fix some issues, when the cache were corrupted. This command is also needed after changing things in the `parameters.yaml` file or upgrading Part-DB.
* `partdb:migrations:import-partkeepr`: Imports an mysqldump XML dump of a PartKeepr database into Part-DB. This is only needed for users, which want to migrate from PartKeepr to Part-DB. *All existing data in the Part-DB database is deleted!*
## Database commands
* `php bin/console doctrine:migrations:migrate`: Migrate the database to the latest version

103
docs/usage/import_export.md Normal file
View File

@@ -0,0 +1,103 @@
---
layout: default
title: Import & Export data
nav_order: 4
parent: Usage
---
# Import & Export data
Part-DB offers the possibility to import existing data (parts, datastructures, etc.) from existing datasources into Part-DB. Data can also be exported from Part-DB into various formats.
## Import
{: .note }
> As data import is a very powerful feature and can easily fill up your database with lots of data, import is by default only available for
> administrators. If you want to allow other users to import data, or can not import data, check the permissions of the user. You can enable import for each data structure
> individually in the permissions settings.
If you want to import data from PartKeepr you might want to look into the [PartKeepr migration guide]({% link upgrade_legacy.md %}).
### Import parts
Part-DB supports the import of parts from CSV files and other formats. This can be used to import existing parts from other databases or datasources into Part-DB. The import can be done via the "Tools -> Import parts" page, which you can find in the "Tools" sidebar panel.
{: .important }
> When importing data, the data is immediatley written to database during the import process, when the data is formally valid.
> You will not be able to check the data before it is written to the database, so you should review the data before using the import tool.
You can upload the file which should be imported here and choose various options on how the data should be treated:
* **Format**: By default "auto" is selected here and Part-DB will try to detect the format of the file automatically based on its file extension. If you want to force a specific format or Part-DB can not auto-detect the format, you can select it here.
* **CSV delimiter**: If you upload an CSV file, you can select the delimiter character which is used to separate the columns in the CSV file. Depending on the CSV file, this might be a comma (`,`), semicolon (`;`).
* **Category override**: You can select (or create) a category here, to which all imported parts should be assigned, no matter what was specified in the import file. This can be useful if you want to assign all imports to a certain category or if no category is specified in the data. If you leave this field empty, the category will be determined by the import file (or the export will error, if no category is specified).
* **Mark all imported parts as "Needs review"**: If this is selected, all imported parts will be marked as "Needs review" after the import. This can be useful if you want to review all imported parts before using them.
* **Create unknown datastructures**: If this is selected Part-DB will create new datastructures (like categories, manufacturers, etc.) if no datastructure(s) with the same name and path already exists. If this is not selected, only existing datastructures will be used and if no matching datastrucure is found, the imported parts field will be empty.
* **Path delimiter**: Part-DB allows you to create/select nested datastructures (like categories, manufacturers, etc.) by using a path (e.g. `Category 1->Category 1.1`, which will select/create the `Category 1.1` whose parent is `Category 1`). This path is separated by the path delimiter. If you want to use a different path delimiter than the default one (which is `>`), you can select it here.
* **Abort on validation error**: If this is selected, the import will be aborted if a validation error occurs (e.g. if a required field is empty) for any of the imported parts and validation errors will be shown on top of the page. If this is not selected, the import will continue for the other parts and only the invalid parts will be skipped.
After you have selected the options, you can start the import by clicking the "Import" button. When the import is finished, you will see the results of the import in the lower half of the page. You find a table with the imported parts (including links to them) there.
#### Fields description
For the importing of parts, you can use the following fields which will be imported into each part. Please note that the field names are case sensitive (so `name` is not the same as `Name`). All fields (besides name) are optional, so you can leave them empty or do not include the column in your file.
* **`name`** (required): The name of the part. This is the only required field, all other fields are optional.
* **`description`**: The description of the part, you can use markdown/HTML syntax here for rich text formatting.
* **`notes`** or **`comment`**: The notes of the part, you can use markdown/HTML syntax here for rich text formatting.
* **`category`**: The category of the part. This can be a path (e.g. `Category 1->Category 1.1`), which will select/create the `Category 1.1` whose parent is `Category 1`. If you want to use a different path delimiter than the default one (which is `->`), you can select it in the import options. If the category does not exist and the option "Create unknown datastructures" is selected, it will be created.
* **`footprint`**: The footprint of the part. Can be a path similar to the category field.
* **`favorite`**: If this is set to `1`, the part will be marked as favorite.
* **`manufacturer`**: The manufacturer of the part. Can be a path similar to the category field.
* **`manufacturer_product_number`** or **`mpn`**: The manufacturer product number of the part.
* **`manufacturer_product_url`: The URL to the product page of the manufacturer of the part.
* **`manufacturing_status`**: The manufacturing status of the part, must be one of the following values: `announced`, `active`, `nrfnd`, `eol`, `discontinued` or left empty.
* **`needs_review`** or **`needs_review`**: If this is set to `1`, the part will be marked as "needs review".
* **`tags`**: A comma separated list of tags for the part.
* **`mass`**: The mass of the part in grams.
* **`ipn`**: The IPN (Item Part Number) of the part.
* **`minamount`**: The minimum amount of the part which should be in stock.
* **`partUnit`**: The measurement unit of the part to use. Can be a path similar to the category field.
With the following fields you can specify storage locations and amount / quantiy in stock of the part. An PartLot will be created automatically from the data and assigned to the part. The following fields are helpers for an easy import for parts at one storage location. If you need to create a Part with multiple PartLots you have to use JSON format (or CSV) with nested objects:
**`storage_location`** or **`storelocation`**: The storage location of the part. Can be a path similar to the category field.
**`amount`**, **`quantity`** or **`instock`**: The amount of the part in stock. If this value is not set, the part lot will be marked with "unknown amount"
The following fields can be used to specify the supplier/distributor, supplier product number and the price of the part. This is only possible for a single supplier/distributor and price with this fields. If you need to specify multiple suppliers/distributors or prices, you have to use JSON format (or CSV) with nested objects.
**Please note that the supplier fields is required, if you want to import prices or supplier product numbers.**. If the supplier is not specified, the price and supplier product number fields will be ignored:
* **`supplier`**: The supplier of the part. Can be a path similar to the category field.
* **`supplier_product_number`** or **`supplier_part_number`** or * **`spn`**: The supplier product number of the part.
* **`price`**: The price of the part in the base currency of the database (by default euro).
#### Example data
Here you can find some example data for the import of parts, you can use it as a template for your own import (especially the CSV file).
* [Part import CSV example]({% link assets/usage/import_export/part_import_example.csv %}) with all possible fields
## Export
By default every user, who can read the datastructure, can also export the data of this datastructure, as this does not give the user any additional information.
### Exporting data structures (categories, manufacturers, etc.)
You can export data structures (like categories, manufacturers, etc.) in the respective edit page (e.g. Tools Panel -> Edit -> Category).
If you select a certain datastructure from your list, you can export it (and optionally all sub-datastructures) in the "Export" tab.
If you want to export all datastructures of a certain type (e.g. all categories in your database), you can select the "Export all" function in the "Import / Export" tab of the "new element" page.
You can select between the following export formats:
* **CSV** (Comma Separated Values): A semicolon separated list of values, where every line represents an element. This format can be imported into Excel or LibreOffice Calc and is easy to work with. However it does not support nested datastructures or sub data (like parameters, attachments, etc.), very well (many columns are generated, as every possible sub data is exported as a separate column).
* **JSON** (JavaScript Object Notation): A text-based format, which is easy to work with programming laguages. It supports nested datastructures and sub data (like parameters, attachments, etc.) very well. However it is not easy to work with in Excel or LibreOffice Calc and you maybe need to write some code to work with the exported data efficiently.
* **YAML** (Yet another Markup Language): Very similar to JSON
* **XML** (Extensible Markup Language): Good support with nested datastructures. Similar usecase as JSON and YAML.
Also you can select between the following export levels:
* **Simple**: This will only export very basic information about the name (like the name, or description for parts)
* **Extended**: This will export all commonly used information about this datastructure (like notes, options, etc)
* **Full**: This will export all available information about this datastructure (like all parameters, attachments)
Please note that the level will also be applied to all sub data or children elements. So if you select "Full" for a part, all the associated categories, manufacturers, footprints, etc. will also be exported with all available information, this can lead to very large export files.
### Exporting parts
You can export parts in all part tables. Select the parts you want via the checkbox in the table line and select the export format and level in the appearing menu.
See the section about exporting datastructures for more information about the export formats and levels.

119
docs/usage/keybindings.md Normal file
View File

@@ -0,0 +1,119 @@
---
title: Keybindings
layout: default
parent: Usage
---
# Keybindings
This page lists all the keybindings of Part-DB. Currently, there are only the special character keybindings.
## Special characters
Using the keybindings below (Alt + key) you can insert special characters into the text fields of Part-DB. This works on all text and search fields in Part-DB.
### Greek letters
| Key | Character |
|---------------------|---------------------|
| **Alt + a** | α (Alpha) |
| **Alt + Shift + A** | Α (Alpha uppercase) |
| **Alt + b** | β (Beta) |
| **Alt + Shift + B** | Β (Beta uppercase) |
| **Alt + g** | γ (Gamma) |
| **Alt + Shift + G** | Γ (Gamma uppercase) |
| **Alt + d** | δ (Delta) |
| **Alt + Shift + D** | Δ (Delta uppercase) |
| **Alt + e** | ε (Epsilon) |
| **Alt + Shift + E** | Ε (Epsilon uppercase) |
| **Alt + z** | ζ (Zeta) |
| **Alt + Shift + Z** | Ζ (Zeta uppercase) |
| **Alt + h** | η (Eta) |
| **Alt + Shift + H** | Η (Eta uppercase) |
| **Alt + q** | θ (Theta) |
| **Alt + Shift + Q** | Θ (Theta uppercase) |
| **Alt + i** | ι (Iota) |
| **Alt + Shift + I** | Ι (Iota uppercase) |
| **Alt + k** | κ (Kappa) |
| **Alt + Shift + K** | Κ (Kappa uppercase) |
| **Alt + l** | λ (Lambda) |
| **Alt + Shift + L** | Λ (Lambda uppercase) |
| **Alt + m** | μ (Mu) |
| **Alt + Shift + M** | Μ (Mu uppercase) |
| **Alt + n** | ν (Nu) |
| **Alt + Shift + N** | Ν (Nu uppercase) |
| **Alt + x** | ξ (Xi) |
| **Alt + Shift + x** | Ξ (Xi uppercase) |
| **Alt + o** | ο (Omicron) |
| **Alt + Shift + O** | Ο (Omicron uppercase) |
| **Alt + p** | π (Pi) |
| **Alt + Shift + P** | Π (Pi uppercase) |
| **Alt + r** | ρ (Rho) |
| **Alt + Shift + R** | Ρ (Rho uppercase) |
| **Alt + s** | σ (Sigma) |
| **Alt + Shift + S** | Σ (Sigma uppercase) |
| **Alt + t** | τ (Tau) |
| **Alt + Shift + T** | Τ (Tau uppercase) |
| **Alt + u** | υ (Upsilon) |
| **Alt + Shift + U** | Υ (Upsilon uppercase) |
| **Alt + f** | φ (Phi) |
| **Alt + Shift + F** | Φ (Phi uppercase) |
| **Alt + y** | ψ (Psi) |
| **Alt + Shift + Y** | Ψ (Psi uppercase) |
| **Alt + c** | χ (Chi) |
| **Alt + Shift + C** | Χ (Chi uppercase) |
| **Alt + w** | ω (Omega) |
| **Alt + Shift + W** | Ω (Omega uppercase) |
### Mathematical symbols
| Key | Character |
|----------------------|-------------------------------------------|
| **Alt + 1** | ∑ (Sum symbol) |
| **Alt + Shift + 1** | ∏ (Product symbol) |
| **Alt + 2** | ∫ (Integral symbol) |
| **Alt + Shift + 2** | ∂ (Partial derivation) |
| **Alt + 3** | ≤ (Less or equal symbol) |
| **Alt + Shift + 3** | ≥ (Greater or equal symbol) |
| **Alt + 4** | ∞ (Infinity symbol) |
| **Alt + Shift + 4** | ∅ (Empty set symbol) |
| **Alt + 5** | ≈ (Approximatley) |
| **Alt + Shift + 5** | ≠ (Not equal symbol) |
| **Alt + 6** | ∈ (Element of) |
| **Alt + Shift + 6** | ∉ (Not element of) |
| **Alt + 7** | (Logical or) |
| **Alt + Shift + 7** | ∧ (Logical and) |
| **Alt + 8** | ∠ (Angle symbol) |
| **Alt + Shift + 8** | ∝ (Proportional to) |
| **Alt + 9** | √ (Square root) |
| **Alt + Shift + 9** | ∛ (Cube root) |
| **Alt + 0** | ± (Plus minus) |
| **Alt + Shift + 0** | ∓ (Minus plus) |
### Currency symbols
Please not the following keybindings are bound to a specific keycode. The key character is not the same on all keyboards.
It is given here for a US keyboard layout.
For a German keyboard layout, replace ; with ö, and ' with ä.
| Key | Character |
|---------------------------------|---------------------------|
| **Alt + ;** (code 192) | € (Euro currency symbol) |
| **Alt + Shift + ;** (code 192) | £ (Pound currency symbol) |
| **Alt + '** (code 222) | ¥ (Yen currency symbol) |
| **Alt + Shift + '** (code 222) | $ (Dollar currency symbol) |
### Others
Please not the following keybindings are bound to a specific keycode. The key character is not the same on all keyboards.
It is given here for a US keyboard layout.
For a German keyboard layout, replace `[` with `0`, and `]` with `´`.
| Key | Character |
|--------------------------------|--------------------|
| **Alt + [** (code 219) | © (Copyright char) |
| **Alt + Shift + [** (code 219) | (Registered char) |
| **Alt + ]** (code 221) | ™ (Trademark char) |
| **Alt + Shift + ]** (code 221) | (Degree char) |

View File

@@ -62,4 +62,14 @@ by calling the `php bin/console partdb:currencies:update-exchange-rates`.
If you call this command regularly (e.g. with a cronjob), you can keep the exchange rates up-to-date.
Please note that if you use a base currency, which is not the Euro, you have to configure an exchange rate API, as the
free API used by default only supports the Euro as base currency.
free API used by default only supports the Euro as base currency.
## Enforce log comments
On almost any editing operation it is possible to add a comment describing, what or why you changed something.
This comment will be written to change log and can be viewed later.
If you want to enforce your users to add comments to certain operations, you can do this by setting the `ENFORCE_CHANGE_COMMENTS_FOR` option.
See the configuration reference for more information.
## Personal stocks and stock locations
For makerspaces and universities with a lot of users, where each user can have his own stock, which only he should be able to access, you can assign
the user as "owner" of a part lot. This way, only him is allowed to add or remove parts from this lot.

View File

@@ -100,11 +100,17 @@ final class Version20190902140506 extends AbstractMultiPlatformMigration
$this->addSql('ALTER TABLE part_lots ADD CONSTRAINT FK_EBC8F9435D8F4B37 FOREIGN KEY (id_store_location) REFERENCES `storelocations` (id)');
$this->addSql('ALTER TABLE part_lots ADD CONSTRAINT FK_EBC8F943C22F6CC4 FOREIGN KEY (id_part) REFERENCES `parts` (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE parts DROP INDEX parts_order_orderdetails_id_k, ADD UNIQUE INDEX UNIQ_6940A7FE81081E9B (order_orderdetails_id)');
$this->addSql('ALTER TABLE parts DROP FOREIGN KEY parts_id_storelocation_fk');
if ($this->doesFKExists('parts', 'parts_id_storelocation_fk')) {
$this->addSql('ALTER TABLE parts DROP FOREIGN KEY parts_id_storelocation_fk');
}
$this->addSql('DROP INDEX favorite ON parts');
$this->addSql('DROP INDEX parts_id_storelocation_k ON parts');
$this->addSql('ALTER TABLE parts DROP FOREIGN KEY parts_id_footprint_fk');
$this->addSql('ALTER TABLE parts DROP FOREIGN KEY parts_id_manufacturer_fk');
if ($this->doesFKExists('parts', 'parts_id_footprint_fk')) {
$this->addSql('ALTER TABLE parts DROP FOREIGN KEY parts_id_footprint_fk');
}
if ($this->doesFKExists('parts', 'parts_id_manufacturer_fk')) {
$this->addSql('ALTER TABLE parts DROP FOREIGN KEY parts_id_manufacturer_fk');
}
$this->addSql('ALTER TABLE parts CHANGE mininstock minamount DOUBLE PRECISION NOT NULL, ADD id_part_unit INT DEFAULT NULL, ADD manufacturer_product_number VARCHAR(255) NOT NULL, ADD manufacturing_status VARCHAR(255) DEFAULT NULL, ADD needs_review TINYINT(1) NOT NULL, ADD tags LONGTEXT NOT NULL, ADD mass DOUBLE PRECISION DEFAULT NULL, DROP instock, CHANGE id_category id_category INT NOT NULL, CHANGE name name VARCHAR(255) NOT NULL, CHANGE order_quantity order_quantity INT NOT NULL, CHANGE manual_order manual_order TINYINT(1) NOT NULL, CHANGE manufacturer_product_url manufacturer_product_url VARCHAR(255) NOT NULL, CHANGE last_modified last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CHANGE favorite favorite TINYINT(1) NOT NULL, DROP id_storelocation');
$this->addSql('ALTER TABLE parts ADD CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES `categories` (id)');
$this->addSql('ALTER TABLE parts ADD CONSTRAINT FK_6940A7FEEBBCC786 FOREIGN KEY (id_master_picture_attachement) REFERENCES `attachments` (id)');
@@ -119,39 +125,53 @@ final class Version20190902140506 extends AbstractMultiPlatformMigration
$this->addSql('CREATE INDEX IDX_6940A7FE1ECB93AE ON parts (id_manufacturer)');
$this->addSql('ALTER TABLE parts ADD CONSTRAINT parts_id_footprint_fk FOREIGN KEY (id_footprint) REFERENCES footprints (id)');
$this->addSql('ALTER TABLE parts ADD CONSTRAINT parts_id_manufacturer_fk FOREIGN KEY (id_manufacturer) REFERENCES manufacturers (id)');
$this->addSql('ALTER TABLE attachment_types DROP FOREIGN KEY attachement_types_parent_id_fk');
if ($this->doesFKExists('attachment_types', 'attachement_types_parent_id_fk')) {
$this->addSql('ALTER TABLE attachment_types DROP FOREIGN KEY attachement_types_parent_id_fk');
}
$this->addSql('ALTER TABLE attachment_types ADD filetype_filter LONGTEXT NOT NULL, ADD not_selectable TINYINT(1) NOT NULL, CHANGE name name VARCHAR(255) NOT NULL, CHANGE comment comment LONGTEXT NOT NULL, CHANGE last_modified last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL');
$this->addSql('DROP INDEX attachement_types_parent_id_k ON attachment_types');
$this->addSql('CREATE INDEX IDX_EFAED719727ACA70 ON attachment_types (parent_id)');
$this->addSql('ALTER TABLE attachment_types ADD CONSTRAINT attachement_types_parent_id_fk FOREIGN KEY (parent_id) REFERENCES attachment_types (id)');
$this->addSql('ALTER TABLE categories DROP FOREIGN KEY categories_parent_id_fk');
if ($this->doesFKExists('categories', 'categories_parent_id_fk')) {
$this->addSql('ALTER TABLE categories DROP FOREIGN KEY categories_parent_id_fk');
}
$this->addSql('ALTER TABLE categories ADD not_selectable TINYINT(1) NOT NULL, CHANGE name name VARCHAR(255) NOT NULL, CHANGE disable_footprints disable_footprints TINYINT(1) NOT NULL, CHANGE disable_manufacturers disable_manufacturers TINYINT(1) NOT NULL, CHANGE disable_autodatasheets disable_autodatasheets TINYINT(1) NOT NULL, CHANGE disable_properties disable_properties TINYINT(1) NOT NULL, CHANGE comment comment LONGTEXT NOT NULL, CHANGE last_modified last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL');
$this->addSql('DROP INDEX categories_parent_id_k ON categories');
$this->addSql('CREATE INDEX IDX_3AF34668727ACA70 ON categories (parent_id)');
$this->addSql('ALTER TABLE categories ADD CONSTRAINT categories_parent_id_fk FOREIGN KEY (parent_id) REFERENCES categories (id)');
$this->addSql('ALTER TABLE devices DROP FOREIGN KEY devices_parent_id_fk');
if ($this->doesFKExists('devices', 'devices_parent_id_fk')) {
$this->addSql('ALTER TABLE devices DROP FOREIGN KEY devices_parent_id_fk');
}
$this->addSql('ALTER TABLE devices ADD not_selectable TINYINT(1) NOT NULL, CHANGE name name VARCHAR(255) NOT NULL, CHANGE order_quantity order_quantity INT NOT NULL, CHANGE order_only_missing_parts order_only_missing_parts TINYINT(1) NOT NULL, CHANGE last_modified last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CHANGE comment comment LONGTEXT NOT NULL');
$this->addSql('DROP INDEX devices_parent_id_k ON devices');
$this->addSql('CREATE INDEX IDX_11074E9A727ACA70 ON devices (parent_id)');
$this->addSql('ALTER TABLE devices ADD CONSTRAINT devices_parent_id_fk FOREIGN KEY (parent_id) REFERENCES devices (id)');
$this->addSql('ALTER TABLE footprints DROP FOREIGN KEY footprints_parent_id_fk');
if ($this->doesFKExists('footprints', 'footprints_parent_id_fk')) {
$this->addSql('ALTER TABLE footprints DROP FOREIGN KEY footprints_parent_id_fk');
}
$this->addSql('ALTER TABLE footprints ADD not_selectable TINYINT(1) NOT NULL, CHANGE name name VARCHAR(255) NOT NULL, CHANGE comment comment LONGTEXT NOT NULL, CHANGE last_modified last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL');
$this->addSql('DROP INDEX footprints_parent_id_k ON footprints');
$this->addSql('CREATE INDEX IDX_A34D68A2727ACA70 ON footprints (parent_id)');
$this->addSql('ALTER TABLE footprints ADD CONSTRAINT footprints_parent_id_fk FOREIGN KEY (parent_id) REFERENCES footprints (id)');
$this->addSql('ALTER TABLE manufacturers DROP FOREIGN KEY manufacturers_parent_id_fk');
if ($this->doesFKExists('manufacturers', 'manufacturers_parent_id_fk')) {
$this->addSql('ALTER TABLE manufacturers DROP FOREIGN KEY manufacturers_parent_id_fk');
}
$this->addSql('ALTER TABLE manufacturers ADD not_selectable TINYINT(1) NOT NULL, CHANGE name name VARCHAR(255) NOT NULL, CHANGE address address VARCHAR(255) NOT NULL, CHANGE phone_number phone_number VARCHAR(255) NOT NULL, CHANGE fax_number fax_number VARCHAR(255) NOT NULL, CHANGE email_address email_address VARCHAR(255) NOT NULL, CHANGE website website VARCHAR(255) NOT NULL, CHANGE auto_product_url auto_product_url VARCHAR(255) NOT NULL, CHANGE comment comment LONGTEXT NOT NULL, CHANGE last_modified last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL');
$this->addSql('DROP INDEX manufacturers_parent_id_k ON manufacturers');
$this->addSql('CREATE INDEX IDX_94565B12727ACA70 ON manufacturers (parent_id)');
$this->addSql('ALTER TABLE manufacturers ADD CONSTRAINT manufacturers_parent_id_fk FOREIGN KEY (parent_id) REFERENCES manufacturers (id)');
$this->addSql('ALTER TABLE storelocations DROP FOREIGN KEY storelocations_parent_id_fk');
if ($this->doesFKExists('storelocations', 'storelocations_parent_id_fk')) {
$this->addSql('ALTER TABLE storelocations DROP FOREIGN KEY storelocations_parent_id_fk');
}
$this->addSql('ALTER TABLE storelocations ADD storage_type_id INT DEFAULT NULL, ADD only_single_part TINYINT(1) NOT NULL, ADD limit_to_existing_parts TINYINT(1) NOT NULL, ADD not_selectable TINYINT(1) NOT NULL, CHANGE name name VARCHAR(255) NOT NULL, CHANGE is_full is_full TINYINT(1) NOT NULL, CHANGE comment comment LONGTEXT NOT NULL, CHANGE last_modified last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL');
$this->addSql('ALTER TABLE storelocations ADD CONSTRAINT FK_7517020B270BFF1 FOREIGN KEY (storage_type_id) REFERENCES `measurement_units` (id)');
$this->addSql('CREATE INDEX IDX_7517020B270BFF1 ON storelocations (storage_type_id)');
$this->addSql('DROP INDEX storelocations_parent_id_k ON storelocations');
$this->addSql('CREATE INDEX IDX_7517020727ACA70 ON storelocations (parent_id)');
$this->addSql('ALTER TABLE storelocations ADD CONSTRAINT storelocations_parent_id_fk FOREIGN KEY (parent_id) REFERENCES storelocations (id)');
$this->addSql('ALTER TABLE suppliers DROP FOREIGN KEY suppliers_parent_id_fk');
if ($this->doesFKExists('suppliers', 'suppliers_parent_id_fk')) {
$this->addSql('ALTER TABLE suppliers DROP FOREIGN KEY suppliers_parent_id_fk');
}
$this->addSql('ALTER TABLE suppliers ADD default_currency_id INT DEFAULT NULL, ADD shipping_costs NUMERIC(11, 5) DEFAULT NULL, ADD not_selectable TINYINT(1) NOT NULL, CHANGE name name VARCHAR(255) NOT NULL, CHANGE address address VARCHAR(255) NOT NULL, CHANGE phone_number phone_number VARCHAR(255) NOT NULL, CHANGE fax_number fax_number VARCHAR(255) NOT NULL, CHANGE email_address email_address VARCHAR(255) NOT NULL, CHANGE website website VARCHAR(255) NOT NULL, CHANGE auto_product_url auto_product_url VARCHAR(255) NOT NULL, CHANGE comment comment LONGTEXT NOT NULL, CHANGE last_modified last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL');
$this->addSql('ALTER TABLE suppliers ADD CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id)');
$this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON suppliers (default_currency_id)');
@@ -159,7 +179,9 @@ final class Version20190902140506 extends AbstractMultiPlatformMigration
$this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON suppliers (parent_id)');
$this->addSql('ALTER TABLE suppliers ADD CONSTRAINT suppliers_parent_id_fk FOREIGN KEY (parent_id) REFERENCES suppliers (id)');
$this->addSql('DROP INDEX attachements_class_name_k ON attachments');
$this->addSql('ALTER TABLE attachments DROP FOREIGN KEY attachements_type_id_fk');
if ($this->doesFKExists('attachments', 'attachements_type_id_fk')) {
$this->addSql('ALTER TABLE attachments DROP FOREIGN KEY attachements_type_id_fk');
}
$this->addSql('ALTER TABLE attachments ADD datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CHANGE type_id type_id INT DEFAULT NULL, CHANGE name name VARCHAR(255) NOT NULL, CHANGE filename filename VARCHAR(255) NOT NULL, CHANGE show_in_table show_in_table TINYINT(1) NOT NULL, CHANGE last_modified last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL');
$this->addSql('ALTER TABLE attachments ADD CONSTRAINT FK_47C4FAD61F1F2A24 FOREIGN KEY (element_id) REFERENCES `parts` (id) ON DELETE CASCADE');
$this->addSql('DROP INDEX attachements_type_id_fk ON attachments');
@@ -194,10 +216,10 @@ final class Version20190902140506 extends AbstractMultiPlatformMigration
$this->addSql('CREATE INDEX IDX_C68C4459398D64AA ON pricedetails (id_currency)');
$this->addSql('CREATE INDEX IDX_C68C44594A01DDC7 ON pricedetails (orderdetails_id)');
$this->addSql('DROP INDEX pricedetails_orderdetails_id_k ON pricedetails');
$this->addSql('DROP INDEX name ON groups');
$this->addSql('ALTER TABLE groups ADD not_selectable TINYINT(1) NOT NULL, CHANGE name name VARCHAR(255) NOT NULL, CHANGE comment comment LONGTEXT NOT NULL, CHANGE perms_labels perms_labels INT NOT NULL, CHANGE last_modified last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL');
$this->addSql('ALTER TABLE groups ADD CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES `groups` (id)');
$this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON groups (parent_id)');
$this->addSql('DROP INDEX name ON `groups`');
$this->addSql('ALTER TABLE `groups` ADD not_selectable TINYINT(1) NOT NULL, CHANGE name name VARCHAR(255) NOT NULL, CHANGE comment comment LONGTEXT NOT NULL, CHANGE perms_labels perms_labels INT NOT NULL, CHANGE last_modified last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL');
$this->addSql('ALTER TABLE `groups` ADD CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES `groups` (id)');
$this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON `groups` (parent_id)');
//Fill empty timestamps with current date
$tables = ['attachments', 'attachment_types', 'categories', 'devices', 'footprints', 'manufacturers',
@@ -210,6 +232,10 @@ final class Version20190902140506 extends AbstractMultiPlatformMigration
//Set the dbVersion to a high value, to prevent the old Part-DB versions to upgrade DB!
$this->addSql("UPDATE `internal` SET `keyValue` = '99' WHERE `internal`.`keyName` = 'dbVersion'");
//Migrate theme config to new format
$this->addSql('UPDATE users SET users.config_theme = REPLACE(users.config_theme ,".min.css", "") WHERE users.config_theme LIKE "%.min.css"');
$this->addSql('UPDATE users SET users.config_theme = REPLACE(users.config_theme ,".css", "") WHERE users.config_theme LIKE "%.css"');
}
public function mySQLDown(Schema $schema): void
@@ -335,10 +361,6 @@ final class Version20190902140506 extends AbstractMultiPlatformMigration
$this->addSql('ALTER TABLE `users` CHANGE name name VARCHAR(32) NOT NULL COLLATE utf8_general_ci, CHANGE need_pw_change need_pw_change TINYINT(1) DEFAULT \'0\' NOT NULL, CHANGE first_name first_name TINYTEXT DEFAULT NULL COLLATE utf8_general_ci, CHANGE last_name last_name TINYTEXT DEFAULT NULL COLLATE utf8_general_ci, CHANGE department department TINYTEXT DEFAULT NULL COLLATE utf8_general_ci, CHANGE email email TINYTEXT DEFAULT NULL COLLATE utf8_general_ci, CHANGE config_language config_language TINYTEXT DEFAULT NULL COLLATE utf8_general_ci, CHANGE config_timezone config_timezone TINYTEXT DEFAULT NULL COLLATE utf8_general_ci, CHANGE config_theme config_theme TINYTEXT DEFAULT NULL COLLATE utf8_general_ci, CHANGE config_currency config_currency TINYTEXT DEFAULT NULL COLLATE utf8_general_ci, CHANGE last_modified last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CHANGE perms_labels perms_labels SMALLINT NOT NULL');
$this->addSql('DROP INDEX uniq_1483a5e95e237e06 ON `users`');
$this->addSql('CREATE UNIQUE INDEX name ON `users` (name)');
//Migrate theme config to new format
$this->addSql('UPDATE users SET users.config_theme = REPLACE(users.config_theme ,".min.css", "") WHERE users.config_theme LIKE "%.min.css"');
$this->addSql('UPDATE users SET users.config_theme = REPLACE(users.config_theme ,".css", "") WHERE users.config_theme LIKE "%.css"');
}
public function sqLiteUp(Schema $schema): void

View File

@@ -66,8 +66,6 @@ final class Version20190913141126 extends AbstractMultiPlatformMigration
';
$this->addSql($sql);
$this->printPermissionUpdateMessage();
}
public function mySQLDown(Schema $schema): void

View File

@@ -34,8 +34,6 @@ final class Version20200311204104 extends AbstractMultiPlatformMigration
$sql = 'UPDATE `groups`'.
'SET perms_parts_parameters = 681 WHERE (id = 2 AND name = "readonly");';
$this->addSql($sql);
$this->printPermissionUpdateMessage();
}
public function mySQLDown(Schema $schema): void

View File

@@ -134,8 +134,6 @@ EOD;
(2, 1, 'admin', '{$admin_pw}', 1, '', '', '', '', NULL, NULL, NULL, '', '', '2020-06-13 23:39:26', '2020-06-13 23:39:26', 21845, 21845, 21845, 21, 85, 21, 349525, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 0, NULL, '', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, NULL, NULL, 0, NULL, '', NULL, 0, 0);
EOD;
$this->addSql($sql);
$this->printPermissionUpdateMessage();
}
public function sqLiteDown(Schema $schema): void

View File

@@ -83,9 +83,9 @@ final class Version20221114193325 extends AbstractMultiPlatformMigration impleme
//Reset the permissions of the admin user, to allow admin permissions (like the admins group)
$this->addSql("UPDATE `users` SET permissions_data = '$admin' WHERE id = 2;");
$this->warnIf(true, "\x1b[1;37;43m\n!!! All permissions were reset! Please change them to the desired state, immediately !!!\x1b[0;39;49m");
$this->warnIf(true, "\x1b[1;37;43m\n!!! For security reasons all users (except the admin user) were disabled. Login with admin user and reenable other users after checking their permissions !!!\x1b[0;39;49m");
$this->warnIf(true, "\x1b[1;37;43m\n!!! For more infos see: https://github.com/Part-DB/Part-DB-symfony/discussions/193 !!!\x1b[0;39;49m");
$this->logger->warning('<bg=cyan;fg=black>!!! All permissions were reset! Please change them to the desired state, immediately !!!</>');
$this->logger->warning('<bg=cyan;fg=black>!!! For security reasons all users (except the admin user) were disabled. Login with admin user and reenable other users after checking their permissions !!!</>');
$this->logger->warning('<bg=cyan;fg=black>!!! For more infos see: https://github.com/Part-DB/Part-DB-symfony/discussions/193 !!!</>');
}
public function mySQLUp(Schema $schema): void

View File

@@ -281,7 +281,7 @@ final class Version20230219225340 extends AbstractMultiPlatformMigration
$this->addSql('CREATE INDEX IDX_1AA2DD313FFDCD60 ON project_bom_entries (price_currency_id)');
$this->addSql('CREATE TEMPORARY TABLE __temp__projects AS SELECT id, parent_id, id_preview_attachement, order_quantity, order_only_missing_parts, comment, not_selectable, name, last_modified, datetime_added, status, description FROM projects');
$this->addSql('DROP TABLE projects');
$this->addSql('CREATE TABLE projects (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, order_quantity INTEGER NOT NULL, order_only_missing_parts BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, status VARCHAR(64) DEFAULT NULL, description DEFAULT \'\', CONSTRAINT FK_11074E9A727ACA70 FOREIGN KEY (parent_id) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_5C93B3A4EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('CREATE TABLE projects (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, order_quantity INTEGER NOT NULL, order_only_missing_parts BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, status VARCHAR(64) DEFAULT NULL, description CLOB DEFAULT \'\', CONSTRAINT FK_11074E9A727ACA70 FOREIGN KEY (parent_id) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_5C93B3A4EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO projects (id, parent_id, id_preview_attachment, order_quantity, order_only_missing_parts, comment, not_selectable, name, last_modified, datetime_added, status, description) SELECT id, parent_id, id_preview_attachement, order_quantity, order_only_missing_parts, comment, not_selectable, name, last_modified, datetime_added, status, description FROM __temp__projects');
$this->addSql('DROP TABLE __temp__projects');
$this->addSql('CREATE INDEX IDX_5C93B3A4727ACA70 ON projects (parent_id)');

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use App\Migration\AbstractMultiPlatformMigration;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20230220221024 extends AbstractMultiPlatformMigration
{
public function getDescription(): string
{
return 'Added support for SAML/Keycloak';
}
public function mySQLUp(Schema $schema): void
{
$this->addSql('ALTER TABLE `users` ADD saml_user TINYINT(1) NOT NULL DEFAULT 0');
}
public function mySQLDown(Schema $schema): void
{
$this->addSql('ALTER TABLE `users` DROP saml_user');
}
public function sqLiteUp(Schema $schema): void
{
$this->addSql('ALTER TABLE users ADD saml_user BOOLEAN NOT NULL DEFAULT 0');
}
public function sqLiteDown(Schema $schema): void
{
$this->addSql('ALTER TABLE `users` DROP saml_user');
}
}

View File

@@ -0,0 +1,300 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use App\Migration\AbstractMultiPlatformMigration;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20230402170923 extends AbstractMultiPlatformMigration
{
public function getDescription(): string
{
return 'Create database schema for user aboutMe and part lot owners';
}
public function mySQLUp(Schema $schema): void
{
$this->addSql('ALTER TABLE part_lots ADD id_owner INT DEFAULT NULL');
$this->addSql('ALTER TABLE part_lots ADD CONSTRAINT FK_EBC8F94321E5A74C FOREIGN KEY (id_owner) REFERENCES `users` (id) ON DELETE SET NULL');
$this->addSql('CREATE INDEX IDX_EBC8F94321E5A74C ON part_lots (id_owner)');
$this->addSql('ALTER TABLE projects ADD CONSTRAINT FK_5C93B3A4727ACA70 FOREIGN KEY (parent_id) REFERENCES projects (id)');
$this->addSql('ALTER TABLE storelocations ADD id_owner INT DEFAULT NULL, ADD part_owner_must_match TINYINT(1) DEFAULT 0 NOT NULL');
$this->addSql('ALTER TABLE storelocations ADD CONSTRAINT FK_751702021E5A74C FOREIGN KEY (id_owner) REFERENCES `users` (id) ON DELETE SET NULL');
$this->addSql('CREATE INDEX IDX_751702021E5A74C ON storelocations (id_owner)');
$this->addSql('ALTER TABLE `users` ADD about_me LONGTEXT NOT NULL');
$this->addSql('ALTER TABLE projects CHANGE description description LONGTEXT NOT NULL');
}
public function mySQLDown(Schema $schema): void
{
$this->addSql('ALTER TABLE log CHANGE level level TINYINT(1) NOT NULL');
$this->addSql('ALTER TABLE part_lots DROP FOREIGN KEY FK_EBC8F94321E5A74C');
$this->addSql('DROP INDEX IDX_EBC8F94321E5A74C ON part_lots');
$this->addSql('ALTER TABLE part_lots DROP id_owner');
$this->addSql('ALTER TABLE projects DROP FOREIGN KEY FK_5C93B3A4727ACA70');
$this->addSql('ALTER TABLE `storelocations` DROP FOREIGN KEY FK_751702021E5A74C');
$this->addSql('DROP INDEX IDX_751702021E5A74C ON `storelocations`');
$this->addSql('ALTER TABLE `storelocations` DROP id_owner, DROP part_owner_must_match');
$this->addSql('ALTER TABLE `users` DROP about_me');
}
public function sqLiteUp(Schema $schema): void
{
//In Version20230219225340 the type of project description was set to an empty string, which caused problems with doctrine migrations, fix it here
$this->addSql('CREATE TEMPORARY TABLE __temp__projects AS SELECT id, parent_id, id_preview_attachment, order_quantity, order_only_missing_parts, comment, not_selectable, name, last_modified, datetime_added, status, description FROM projects');
$this->addSql('DROP TABLE projects');
$this->addSql('CREATE TABLE projects (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, order_quantity INTEGER NOT NULL, order_only_missing_parts BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, status VARCHAR(64) DEFAULT NULL, description CLOB DEFAULT \'\', CONSTRAINT FK_11074E9A727ACA70 FOREIGN KEY (parent_id) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_5C93B3A4EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO projects (id, parent_id, id_preview_attachment, order_quantity, order_only_missing_parts, comment, not_selectable, name, last_modified, datetime_added, status, description) SELECT id, parent_id, id_preview_attachment, order_quantity, order_only_missing_parts, comment, not_selectable, name, last_modified, datetime_added, status, description FROM __temp__projects');
$this->addSql('DROP TABLE __temp__projects');
$this->addSql('CREATE INDEX IDX_5C93B3A4727ACA70 ON projects (parent_id)');
$this->addSql('CREATE INDEX IDX_5C93B3A4EA7100A1 ON projects (id_preview_attachment)');
$this->addSql('CREATE TEMPORARY TABLE __temp__currencies AS SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added FROM currencies');
$this->addSql('DROP TABLE currencies');
$this->addSql('CREATE TABLE currencies (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, exchange_rate NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal)
, iso_code VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_37C44693727ACA70 FOREIGN KEY (parent_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_37C44693EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO currencies (id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added FROM __temp__currencies');
$this->addSql('DROP TABLE __temp__currencies');
$this->addSql('CREATE INDEX IDX_37C44693EA7100A1 ON currencies (id_preview_attachment)');
$this->addSql('CREATE INDEX currency_idx_parent_name ON currencies (parent_id, name)');
$this->addSql('CREATE INDEX currency_idx_name ON currencies (name)');
$this->addSql('CREATE INDEX IDX_37C44693727ACA70 ON currencies (parent_id)');
$this->addSql('CREATE TEMPORARY TABLE __temp__groups AS SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data FROM groups');
$this->addSql('DROP TABLE groups');
$this->addSql('CREATE TABLE groups (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, enforce_2fa BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB DEFAULT \'[]\' NOT NULL --(DC2Type:json)
, CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES groups (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_F06D3970EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO groups (id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data) SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data FROM __temp__groups');
$this->addSql('DROP TABLE __temp__groups');
$this->addSql('CREATE INDEX IDX_F06D3970EA7100A1 ON groups (id_preview_attachment)');
$this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON groups (parent_id)');
$this->addSql('CREATE INDEX group_idx_name ON groups (name)');
$this->addSql('CREATE INDEX group_idx_parent_name ON groups (parent_id, name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__log AS SELECT id, id_user, datetime, level, target_id, target_type, extra, type, username FROM log');
$this->addSql('DROP TABLE log');
$this->addSql('CREATE TABLE log (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_user INTEGER DEFAULT NULL, datetime DATETIME NOT NULL, level TINYINT(4) NOT NULL, target_id INTEGER NOT NULL, target_type SMALLINT NOT NULL, extra CLOB NOT NULL --(DC2Type:json)
, type SMALLINT NOT NULL, username VARCHAR(255) NOT NULL, CONSTRAINT FK_8F3F68C56B3CA4B FOREIGN KEY (id_user) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO log (id, id_user, datetime, level, target_id, target_type, extra, type, username) SELECT id, id_user, datetime, level, target_id, target_type, extra, type, username FROM __temp__log');
$this->addSql('DROP TABLE __temp__log');
$this->addSql('CREATE INDEX IDX_8F3F68C56B3CA4B ON log (id_user)');
$this->addSql('CREATE INDEX log_idx_type ON log (type)');
$this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)');
$this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)');
$this->addSql('CREATE TEMPORARY TABLE __temp__part_lots AS SELECT id, id_store_location, id_part, description, comment, expiration_date, instock_unknown, amount, needs_refill, last_modified, datetime_added FROM part_lots');
$this->addSql('DROP TABLE part_lots');
$this->addSql('CREATE TABLE part_lots (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_store_location INTEGER DEFAULT NULL, id_part INTEGER NOT NULL, id_owner INTEGER DEFAULT NULL, description CLOB NOT NULL, comment CLOB NOT NULL, expiration_date DATETIME DEFAULT NULL, instock_unknown BOOLEAN NOT NULL, amount DOUBLE PRECISION NOT NULL, needs_refill BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_EBC8F9435D8F4B37 FOREIGN KEY (id_store_location) REFERENCES storelocations (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_EBC8F943C22F6CC4 FOREIGN KEY (id_part) REFERENCES parts (id) ON UPDATE NO ACTION ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_EBC8F94321E5A74C FOREIGN KEY (id_owner) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO part_lots (id, id_store_location, id_part, description, comment, expiration_date, instock_unknown, amount, needs_refill, last_modified, datetime_added) SELECT id, id_store_location, id_part, description, comment, expiration_date, instock_unknown, amount, needs_refill, last_modified, datetime_added FROM __temp__part_lots');
$this->addSql('DROP TABLE __temp__part_lots');
$this->addSql('CREATE INDEX part_lots_idx_needs_refill ON part_lots (needs_refill)');
$this->addSql('CREATE INDEX part_lots_idx_instock_un_expiration_id_part ON part_lots (instock_unknown, expiration_date, id_part)');
$this->addSql('CREATE INDEX IDX_EBC8F9435D8F4B37 ON part_lots (id_store_location)');
$this->addSql('CREATE INDEX IDX_EBC8F943C22F6CC4 ON part_lots (id_part)');
$this->addSql('CREATE INDEX IDX_EBC8F94321E5A74C ON part_lots (id_owner)');
$this->addSql('CREATE TEMPORARY TABLE __temp__pricedetails AS SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM pricedetails');
$this->addSql('DROP TABLE pricedetails');
$this->addSql('CREATE TABLE pricedetails (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_currency INTEGER DEFAULT NULL, orderdetails_id INTEGER NOT NULL, price NUMERIC(11, 5) NOT NULL --(DC2Type:big_decimal)
, price_related_quantity DOUBLE PRECISION NOT NULL, min_discount_quantity DOUBLE PRECISION NOT NULL, manual_input BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_C68C4459398D64AA FOREIGN KEY (id_currency) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_C68C44594A01DDC7 FOREIGN KEY (orderdetails_id) REFERENCES orderdetails (id) ON UPDATE NO ACTION ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO pricedetails (id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added) SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM __temp__pricedetails');
$this->addSql('DROP TABLE __temp__pricedetails');
$this->addSql('CREATE INDEX pricedetails_idx_min_discount_price_qty ON pricedetails (min_discount_quantity, price_related_quantity)');
$this->addSql('CREATE INDEX pricedetails_idx_min_discount ON pricedetails (min_discount_quantity)');
$this->addSql('CREATE INDEX IDX_C68C4459398D64AA ON pricedetails (id_currency)');
$this->addSql('CREATE INDEX IDX_C68C44594A01DDC7 ON pricedetails (orderdetails_id)');
$this->addSql('CREATE TEMPORARY TABLE __temp__project_bom_entries AS SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM project_bom_entries');
$this->addSql('DROP TABLE project_bom_entries');
$this->addSql('CREATE TABLE project_bom_entries (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_device INTEGER DEFAULT NULL, id_part INTEGER DEFAULT NULL, price_currency_id INTEGER DEFAULT NULL, quantity DOUBLE PRECISION NOT NULL, mountnames CLOB NOT NULL, name VARCHAR(255) DEFAULT NULL, comment CLOB NOT NULL, price NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal)
, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_AFC547992F180363 FOREIGN KEY (id_device) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AFC54799C22F6CC4 FOREIGN KEY (id_part) REFERENCES parts (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD313FFDCD60 FOREIGN KEY (price_currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO project_bom_entries (id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added) SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM __temp__project_bom_entries');
$this->addSql('DROP TABLE __temp__project_bom_entries');
$this->addSql('CREATE INDEX IDX_1AA2DD313FFDCD60 ON project_bom_entries (price_currency_id)');
$this->addSql('CREATE INDEX IDX_1AA2DD312F180363 ON project_bom_entries (id_device)');
$this->addSql('CREATE INDEX IDX_1AA2DD31C22F6CC4 ON project_bom_entries (id_part)');
$this->addSql('CREATE TEMPORARY TABLE __temp__projects AS SELECT id, parent_id, id_preview_attachment, order_quantity, order_only_missing_parts, comment, not_selectable, name, last_modified, datetime_added, status, description FROM projects');
$this->addSql('DROP TABLE projects');
$this->addSql('CREATE TABLE projects (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, order_quantity INTEGER NOT NULL, order_only_missing_parts BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, status VARCHAR(64) DEFAULT NULL, description CLOB DEFAULT \'\' NOT NULL, CONSTRAINT FK_11074E9A727ACA70 FOREIGN KEY (parent_id) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_5C93B3A4EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO projects (id, parent_id, id_preview_attachment, order_quantity, order_only_missing_parts, comment, not_selectable, name, last_modified, datetime_added, status, description) SELECT id, parent_id, id_preview_attachment, order_quantity, order_only_missing_parts, comment, not_selectable, name, last_modified, datetime_added, status, description FROM __temp__projects');
$this->addSql('DROP TABLE __temp__projects');
$this->addSql('CREATE INDEX IDX_5C93B3A4EA7100A1 ON projects (id_preview_attachment)');
$this->addSql('CREATE INDEX IDX_5C93B3A4727ACA70 ON projects (parent_id)');
$this->addSql('CREATE TEMPORARY TABLE __temp__storelocations AS SELECT id, parent_id, storage_type_id, id_preview_attachment, is_full, only_single_part, limit_to_existing_parts, comment, not_selectable, name, last_modified, datetime_added FROM storelocations');
$this->addSql('DROP TABLE storelocations');
$this->addSql('CREATE TABLE storelocations (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, storage_type_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, id_owner INTEGER DEFAULT NULL, is_full BOOLEAN NOT NULL, only_single_part BOOLEAN NOT NULL, limit_to_existing_parts BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, part_owner_must_match BOOLEAN DEFAULT 0 NOT NULL, CONSTRAINT FK_7517020727ACA70 FOREIGN KEY (parent_id) REFERENCES storelocations (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_7517020B270BFF1 FOREIGN KEY (storage_type_id) REFERENCES measurement_units (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_7517020EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_751702021E5A74C FOREIGN KEY (id_owner) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO storelocations (id, parent_id, storage_type_id, id_preview_attachment, is_full, only_single_part, limit_to_existing_parts, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, storage_type_id, id_preview_attachment, is_full, only_single_part, limit_to_existing_parts, comment, not_selectable, name, last_modified, datetime_added FROM __temp__storelocations');
$this->addSql('DROP TABLE __temp__storelocations');
$this->addSql('CREATE INDEX IDX_7517020EA7100A1 ON storelocations (id_preview_attachment)');
$this->addSql('CREATE INDEX IDX_7517020B270BFF1 ON storelocations (storage_type_id)');
$this->addSql('CREATE INDEX IDX_7517020727ACA70 ON storelocations (parent_id)');
$this->addSql('CREATE INDEX location_idx_name ON storelocations (name)');
$this->addSql('CREATE INDEX location_idx_parent_name ON storelocations (parent_id, name)');
$this->addSql('CREATE INDEX IDX_751702021E5A74C ON storelocations (id_owner)');
$this->addSql('CREATE TEMPORARY TABLE __temp__suppliers AS SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM suppliers');
$this->addSql('DROP TABLE suppliers');
$this->addSql('CREATE TABLE suppliers (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, default_currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal)
, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_AC28B95C727ACA70 FOREIGN KEY (parent_id) REFERENCES suppliers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO suppliers (id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM __temp__suppliers');
$this->addSql('DROP TABLE __temp__suppliers');
$this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON suppliers (id_preview_attachment)');
$this->addSql('CREATE INDEX supplier_idx_parent_name ON suppliers (parent_id, name)');
$this->addSql('CREATE INDEX supplier_idx_name ON suppliers (name)');
$this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON suppliers (parent_id)');
$this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON suppliers (default_currency_id)');
$this->addSql('CREATE TEMPORARY TABLE __temp__users AS SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data, saml_user FROM users');
$this->addSql('DROP TABLE users');
$this->addSql('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, group_id INTEGER DEFAULT NULL, currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a CLOB NOT NULL, config_instock_comment_w CLOB NOT NULL, trusted_device_cookie_version INTEGER NOT NULL, backup_codes CLOB NOT NULL --(DC2Type:json)
, google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, name VARCHAR(180) NOT NULL, settings CLOB NOT NULL --(DC2Type:json)
, backup_codes_generation_date DATETIME DEFAULT NULL, pw_reset_expires DATETIME DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB DEFAULT \'[]\' NOT NULL --(DC2Type:json)
, saml_user BOOLEAN NOT NULL, about_me CLOB DEFAULT \'\' NOT NULL, CONSTRAINT FK_1483A5E9FE54D947 FOREIGN KEY (group_id) REFERENCES groups (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E9EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO users (id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data, saml_user) SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data, saml_user FROM __temp__users');
$this->addSql('DROP TABLE __temp__users');
$this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON users (id_preview_attachment)');
$this->addSql('CREATE INDEX IDX_1483A5E938248176 ON users (currency_id)');
$this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON users (group_id)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON users (name)');
$this->addSql('CREATE INDEX user_idx_username ON users (name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM webauthn_keys');
$this->addSql('DROP TABLE webauthn_keys');
$this->addSql('CREATE TABLE webauthn_keys (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, public_key_credential_id CLOB NOT NULL --(DC2Type:base64)
, type VARCHAR(255) NOT NULL, transports CLOB NOT NULL --(DC2Type:array)
, attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL --(DC2Type:trust_path)
, aaguid CLOB NOT NULL --(DC2Type:aaguid)
, credential_public_key CLOB NOT NULL --(DC2Type:base64)
, user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO webauthn_keys (id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added) SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM __temp__webauthn_keys');
$this->addSql('DROP TABLE __temp__webauthn_keys');
$this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)');
}
public function sqLiteDown(Schema $schema): void
{
$this->addSql('CREATE TEMPORARY TABLE __temp__currencies AS SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added FROM currencies');
$this->addSql('DROP TABLE currencies');
$this->addSql('CREATE TABLE currencies (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, exchange_rate NUMERIC(11, 5) DEFAULT NULL --
(DC2Type:big_decimal)
, iso_code VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_37C44693727ACA70 FOREIGN KEY (parent_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_37C44693EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO currencies (id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added FROM __temp__currencies');
$this->addSql('DROP TABLE __temp__currencies');
$this->addSql('CREATE INDEX IDX_37C44693727ACA70 ON currencies (parent_id)');
$this->addSql('CREATE INDEX IDX_37C44693EA7100A1 ON currencies (id_preview_attachment)');
$this->addSql('CREATE INDEX currency_idx_name ON currencies (name)');
$this->addSql('CREATE INDEX currency_idx_parent_name ON currencies (parent_id, name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__groups AS SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data FROM "groups"');
$this->addSql('DROP TABLE "groups"');
$this->addSql('CREATE TABLE "groups" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, enforce_2fa BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB DEFAULT \'[]\' NOT NULL --
(DC2Type:json)
, CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_F06D3970EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "groups" (id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data) SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data FROM __temp__groups');
$this->addSql('DROP TABLE __temp__groups');
$this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON "groups" (parent_id)');
$this->addSql('CREATE INDEX IDX_F06D3970EA7100A1 ON "groups" (id_preview_attachment)');
$this->addSql('CREATE INDEX group_idx_name ON "groups" (name)');
$this->addSql('CREATE INDEX group_idx_parent_name ON "groups" (parent_id, name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__log AS SELECT id, id_user, username, datetime, level, target_id, target_type, extra, type FROM log');
$this->addSql('DROP TABLE log');
$this->addSql('CREATE TABLE log (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_user INTEGER DEFAULT NULL, username VARCHAR(255) NOT NULL, datetime DATETIME NOT NULL, level BOOLEAN NOT NULL, target_id INTEGER NOT NULL, target_type SMALLINT NOT NULL, extra CLOB NOT NULL --
(DC2Type:json)
, type SMALLINT NOT NULL, CONSTRAINT FK_8F3F68C56B3CA4B FOREIGN KEY (id_user) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO log (id, id_user, username, datetime, level, target_id, target_type, extra, type) SELECT id, id_user, username, datetime, level, target_id, target_type, extra, type FROM __temp__log');
$this->addSql('DROP TABLE __temp__log');
$this->addSql('CREATE INDEX IDX_8F3F68C56B3CA4B ON log (id_user)');
$this->addSql('CREATE INDEX log_idx_type ON log (type)');
$this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)');
$this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)');
$this->addSql('CREATE TEMPORARY TABLE __temp__part_lots AS SELECT id, id_store_location, id_part, description, comment, expiration_date, instock_unknown, amount, needs_refill, last_modified, datetime_added FROM part_lots');
$this->addSql('DROP TABLE part_lots');
$this->addSql('CREATE TABLE part_lots (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_store_location INTEGER DEFAULT NULL, id_part INTEGER NOT NULL, description CLOB NOT NULL, comment CLOB NOT NULL, expiration_date DATETIME DEFAULT NULL, instock_unknown BOOLEAN NOT NULL, amount DOUBLE PRECISION NOT NULL, needs_refill BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_EBC8F9435D8F4B37 FOREIGN KEY (id_store_location) REFERENCES "storelocations" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_EBC8F943C22F6CC4 FOREIGN KEY (id_part) REFERENCES "parts" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO part_lots (id, id_store_location, id_part, description, comment, expiration_date, instock_unknown, amount, needs_refill, last_modified, datetime_added) SELECT id, id_store_location, id_part, description, comment, expiration_date, instock_unknown, amount, needs_refill, last_modified, datetime_added FROM __temp__part_lots');
$this->addSql('DROP TABLE __temp__part_lots');
$this->addSql('CREATE INDEX IDX_EBC8F9435D8F4B37 ON part_lots (id_store_location)');
$this->addSql('CREATE INDEX IDX_EBC8F943C22F6CC4 ON part_lots (id_part)');
$this->addSql('CREATE INDEX part_lots_idx_instock_un_expiration_id_part ON part_lots (instock_unknown, expiration_date, id_part)');
$this->addSql('CREATE INDEX part_lots_idx_needs_refill ON part_lots (needs_refill)');
$this->addSql('CREATE TEMPORARY TABLE __temp__pricedetails AS SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM "pricedetails"');
$this->addSql('DROP TABLE "pricedetails"');
$this->addSql('CREATE TABLE "pricedetails" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_currency INTEGER DEFAULT NULL, orderdetails_id INTEGER NOT NULL, price NUMERIC(11, 5) NOT NULL --
(DC2Type:big_decimal)
, price_related_quantity DOUBLE PRECISION NOT NULL, min_discount_quantity DOUBLE PRECISION NOT NULL, manual_input BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_C68C4459398D64AA FOREIGN KEY (id_currency) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_C68C44594A01DDC7 FOREIGN KEY (orderdetails_id) REFERENCES "orderdetails" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "pricedetails" (id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added) SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM __temp__pricedetails');
$this->addSql('DROP TABLE __temp__pricedetails');
$this->addSql('CREATE INDEX IDX_C68C4459398D64AA ON "pricedetails" (id_currency)');
$this->addSql('CREATE INDEX IDX_C68C44594A01DDC7 ON "pricedetails" (orderdetails_id)');
$this->addSql('CREATE INDEX pricedetails_idx_min_discount ON "pricedetails" (min_discount_quantity)');
$this->addSql('CREATE INDEX pricedetails_idx_min_discount_price_qty ON "pricedetails" (min_discount_quantity, price_related_quantity)');
$this->addSql('CREATE TEMPORARY TABLE __temp__project_bom_entries AS SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM project_bom_entries');
$this->addSql('DROP TABLE project_bom_entries');
$this->addSql('CREATE TABLE project_bom_entries (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_device INTEGER DEFAULT NULL, id_part INTEGER DEFAULT NULL, price_currency_id INTEGER DEFAULT NULL, quantity DOUBLE PRECISION NOT NULL, mountnames CLOB NOT NULL, name VARCHAR(255) DEFAULT NULL, comment CLOB NOT NULL, price NUMERIC(11, 5) DEFAULT NULL --
(DC2Type:big_decimal)
, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_1AA2DD312F180363 FOREIGN KEY (id_device) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD31C22F6CC4 FOREIGN KEY (id_part) REFERENCES "parts" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD313FFDCD60 FOREIGN KEY (price_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO project_bom_entries (id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added) SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM __temp__project_bom_entries');
$this->addSql('DROP TABLE __temp__project_bom_entries');
$this->addSql('CREATE INDEX IDX_1AA2DD312F180363 ON project_bom_entries (id_device)');
$this->addSql('CREATE INDEX IDX_1AA2DD31C22F6CC4 ON project_bom_entries (id_part)');
$this->addSql('CREATE INDEX IDX_1AA2DD313FFDCD60 ON project_bom_entries (price_currency_id)');
$this->addSql('CREATE TEMPORARY TABLE __temp__projects AS SELECT id, parent_id, id_preview_attachment, order_quantity, status, order_only_missing_parts, description, comment, not_selectable, name, last_modified, datetime_added FROM projects');
$this->addSql('DROP TABLE projects');
$this->addSql('CREATE TABLE projects (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, order_quantity INTEGER NOT NULL, status VARCHAR(64) DEFAULT NULL, order_only_missing_parts BOOLEAN NOT NULL, description CLOB DEFAULT \'\', comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_5C93B3A4727ACA70 FOREIGN KEY (parent_id) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_5C93B3A4EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO projects (id, parent_id, id_preview_attachment, order_quantity, status, order_only_missing_parts, description, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, order_quantity, status, order_only_missing_parts, description, comment, not_selectable, name, last_modified, datetime_added FROM __temp__projects');
$this->addSql('DROP TABLE __temp__projects');
$this->addSql('CREATE INDEX IDX_5C93B3A4727ACA70 ON projects (parent_id)');
$this->addSql('CREATE INDEX IDX_5C93B3A4EA7100A1 ON projects (id_preview_attachment)');
$this->addSql('CREATE TEMPORARY TABLE __temp__storelocations AS SELECT id, parent_id, storage_type_id, id_preview_attachment, is_full, only_single_part, limit_to_existing_parts, comment, not_selectable, name, last_modified, datetime_added FROM "storelocations"');
$this->addSql('DROP TABLE "storelocations"');
$this->addSql('CREATE TABLE "storelocations" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, storage_type_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, is_full BOOLEAN NOT NULL, only_single_part BOOLEAN NOT NULL, limit_to_existing_parts BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_7517020727ACA70 FOREIGN KEY (parent_id) REFERENCES "storelocations" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_7517020B270BFF1 FOREIGN KEY (storage_type_id) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_7517020EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "storelocations" (id, parent_id, storage_type_id, id_preview_attachment, is_full, only_single_part, limit_to_existing_parts, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, storage_type_id, id_preview_attachment, is_full, only_single_part, limit_to_existing_parts, comment, not_selectable, name, last_modified, datetime_added FROM __temp__storelocations');
$this->addSql('DROP TABLE __temp__storelocations');
$this->addSql('CREATE INDEX IDX_7517020727ACA70 ON "storelocations" (parent_id)');
$this->addSql('CREATE INDEX IDX_7517020B270BFF1 ON "storelocations" (storage_type_id)');
$this->addSql('CREATE INDEX IDX_7517020EA7100A1 ON "storelocations" (id_preview_attachment)');
$this->addSql('CREATE INDEX location_idx_name ON "storelocations" (name)');
$this->addSql('CREATE INDEX location_idx_parent_name ON "storelocations" (parent_id, name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__suppliers AS SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM "suppliers"');
$this->addSql('DROP TABLE "suppliers"');
$this->addSql('CREATE TABLE "suppliers" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, default_currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL --
(DC2Type:big_decimal)
, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_AC28B95C727ACA70 FOREIGN KEY (parent_id) REFERENCES "suppliers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "suppliers" (id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM __temp__suppliers');
$this->addSql('DROP TABLE __temp__suppliers');
$this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON "suppliers" (parent_id)');
$this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON "suppliers" (default_currency_id)');
$this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON "suppliers" (id_preview_attachment)');
$this->addSql('CREATE INDEX supplier_idx_name ON "suppliers" (name)');
$this->addSql('CREATE INDEX supplier_idx_parent_name ON "suppliers" (parent_id, name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__users AS SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, saml_user, last_modified, datetime_added, permissions_data FROM "users"');
$this->addSql('DROP TABLE "users"');
$this->addSql('CREATE TABLE "users" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, group_id INTEGER DEFAULT NULL, currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a CLOB NOT NULL, config_instock_comment_w CLOB NOT NULL, trusted_device_cookie_version INTEGER NOT NULL, backup_codes CLOB NOT NULL --
(DC2Type:json)
, google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, name VARCHAR(180) NOT NULL, settings CLOB NOT NULL --
(DC2Type:json)
, backup_codes_generation_date DATETIME DEFAULT NULL, pw_reset_expires DATETIME DEFAULT NULL, saml_user BOOLEAN DEFAULT 0 NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB DEFAULT \'[]\' NOT NULL --
(DC2Type:json)
, CONSTRAINT FK_1483A5E9FE54D947 FOREIGN KEY (group_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E9EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "users" (id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, saml_user, last_modified, datetime_added, permissions_data) SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, saml_user, last_modified, datetime_added, permissions_data FROM __temp__users');
$this->addSql('DROP TABLE __temp__users');
$this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON "users" (name)');
$this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON "users" (group_id)');
$this->addSql('CREATE INDEX IDX_1483A5E938248176 ON "users" (currency_id)');
$this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON "users" (id_preview_attachment)');
$this->addSql('CREATE INDEX user_idx_username ON "users" (name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM webauthn_keys');
$this->addSql('DROP TABLE webauthn_keys');
$this->addSql('CREATE TABLE webauthn_keys (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, public_key_credential_id CLOB NOT NULL --
(DC2Type:base64)
, type VARCHAR(255) NOT NULL, transports CLOB NOT NULL --
(DC2Type:array)
, attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL --
(DC2Type:trust_path)
, aaguid CLOB NOT NULL --
(DC2Type:aaguid)
, credential_public_key CLOB NOT NULL --
(DC2Type:base64)
, user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO webauthn_keys (id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added) SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM __temp__webauthn_keys');
$this->addSql('DROP TABLE __temp__webauthn_keys');
$this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)');
}
}

View File

@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use App\Migration\AbstractMultiPlatformMigration;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20230408170059 extends AbstractMultiPlatformMigration
{
public function getDescription(): string
{
return 'Add show_email_on_profile option';
}
public function mySQLUp(Schema $schema): void
{
$this->addSql('ALTER TABLE `users` ADD show_email_on_profile TINYINT(1) DEFAULT 0 NOT NULL');
}
public function mySQLDown(Schema $schema): void
{
$this->addSql('ALTER TABLE `users` DROP show_email_on_profile');
}
public function sqLiteUp(Schema $schema): void
{
$this->addSql('ALTER TABLE users ADD COLUMN show_email_on_profile BOOLEAN DEFAULT 0 NOT NULL');
}
public function sqLiteDown(Schema $schema): void
{
$this->addSql('CREATE TEMPORARY TABLE __temp__users AS SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, saml_user, last_modified, datetime_added, permissions_data FROM "users"');
$this->addSql('DROP TABLE "users"');
$this->addSql('CREATE TABLE "users" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, group_id INTEGER DEFAULT NULL, currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a CLOB NOT NULL, config_instock_comment_w CLOB NOT NULL, about_me CLOB DEFAULT \'\' NOT NULL, trusted_device_cookie_version INTEGER NOT NULL, backup_codes CLOB NOT NULL --(DC2Type:json)
, google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, name VARCHAR(180) NOT NULL, settings CLOB NOT NULL --(DC2Type:json)
, backup_codes_generation_date DATETIME DEFAULT NULL, pw_reset_expires DATETIME DEFAULT NULL, saml_user BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB DEFAULT \'[]\' NOT NULL --(DC2Type:json)
, CONSTRAINT FK_1483A5E9FE54D947 FOREIGN KEY (group_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E9EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "users" (id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, saml_user, last_modified, datetime_added, permissions_data) SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, saml_user, last_modified, datetime_added, permissions_data FROM __temp__users');
$this->addSql('DROP TABLE __temp__users');
$this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON "users" (name)');
$this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON "users" (group_id)');
$this->addSql('CREATE INDEX IDX_1483A5E938248176 ON "users" (currency_id)');
$this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON "users" (id_preview_attachment)');
$this->addSql('CREATE INDEX user_idx_username ON "users" (name)');
}
}

View File

@@ -0,0 +1,437 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use App\Migration\AbstractMultiPlatformMigration;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20230408213957 extends AbstractMultiPlatformMigration
{
public function getDescription(): string
{
return 'Fixed some minor issues in the database schema and indifference between new and legacy-migrated databases';
}
public function mySQLUp(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE `groups` CHANGE permissions_data permissions_data LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\'');
$this->addSql('ALTER TABLE `log` CHANGE level level TINYINT(4) NOT NULL');
if ($this->doesFKExists('projects', 'FK_11074E9A727ACA70')){
$this->addSql('ALTER TABLE projects DROP FOREIGN KEY FK_11074E9A727ACA70');
}
$this->addSql('ALTER TABLE projects CHANGE description description LONGTEXT NOT NULL');
$this->addSql('ALTER TABLE `users` CHANGE permissions_data permissions_data LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\', CHANGE saml_user saml_user TINYINT(1) NOT NULL, CHANGE about_me about_me LONGTEXT NOT NULL');
$this->addSql('ALTER TABLE `log` CHANGE level level TINYINT NOT NULL COMMENT \'(DC2Type:tinyint)\'');
}
public function mySQLDown(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE `groups` CHANGE permissions_data permissions_data LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\'');
$this->addSql('ALTER TABLE log CHANGE level level TINYINT(1) NOT NULL');
$this->addSql('ALTER TABLE projects CHANGE description description LONGTEXT NOT NULL');
$this->addSql('ALTER TABLE `users` CHANGE about_me about_me LONGTEXT NOT NULL, CHANGE saml_user saml_user TINYINT(1) DEFAULT 0 NOT NULL, CHANGE permissions_data permissions_data LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\'');
$this->addSql('ALTER TABLE log CHANGE level level TINYINT(1) NOT NULL');
}
public function sqLiteUp(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TEMPORARY TABLE __temp__currencies AS SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added FROM currencies');
$this->addSql('DROP TABLE currencies');
$this->addSql('CREATE TABLE currencies (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, exchange_rate NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal)
, iso_code VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_37C44693727ACA70 FOREIGN KEY (parent_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_37C44693EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO currencies (id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added FROM __temp__currencies');
$this->addSql('DROP TABLE __temp__currencies');
$this->addSql('CREATE INDEX IDX_37C44693727ACA70 ON currencies (parent_id)');
$this->addSql('CREATE INDEX currency_idx_name ON currencies (name)');
$this->addSql('CREATE INDEX currency_idx_parent_name ON currencies (parent_id, name)');
$this->addSql('CREATE INDEX IDX_37C44693EA7100A1 ON currencies (id_preview_attachment)');
$this->addSql('CREATE TEMPORARY TABLE __temp__groups AS SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data FROM groups');
$this->addSql('DROP TABLE groups');
$this->addSql('CREATE TABLE groups (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, enforce_2fa BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB DEFAULT \'[]\' NOT NULL --(DC2Type:json)
, CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES groups (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_F06D3970EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO groups (id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data) SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data FROM __temp__groups');
$this->addSql('DROP TABLE __temp__groups');
$this->addSql('CREATE INDEX group_idx_parent_name ON groups (parent_id, name)');
$this->addSql('CREATE INDEX group_idx_name ON groups (name)');
$this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON groups (parent_id)');
$this->addSql('CREATE INDEX IDX_F06D3970EA7100A1 ON groups (id_preview_attachment)');
$this->addSql('CREATE TEMPORARY TABLE __temp__log AS SELECT id, id_user, datetime, level, target_id, target_type, extra, type, username FROM log');
$this->addSql('DROP TABLE log');
$this->addSql('CREATE TABLE log (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_user INTEGER DEFAULT NULL, datetime DATETIME NOT NULL, level TINYINT NOT NULL --(DC2Type:tinyint)
, target_id INTEGER NOT NULL, target_type SMALLINT NOT NULL, extra CLOB NOT NULL --(DC2Type:json)
, type SMALLINT NOT NULL, username VARCHAR(255) NOT NULL, CONSTRAINT FK_8F3F68C56B3CA4B FOREIGN KEY (id_user) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO log (id, id_user, datetime, level, target_id, target_type, extra, type, username) SELECT id, id_user, datetime, level, target_id, target_type, extra, type, username FROM __temp__log');
$this->addSql('DROP TABLE __temp__log');
$this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)');
$this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)');
$this->addSql('CREATE INDEX log_idx_type ON log (type)');
$this->addSql('CREATE INDEX IDX_8F3F68C56B3CA4B ON log (id_user)');
$this->addSql('CREATE TEMPORARY TABLE __temp__pricedetails AS SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM pricedetails');
$this->addSql('DROP TABLE pricedetails');
$this->addSql('CREATE TABLE pricedetails (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_currency INTEGER DEFAULT NULL, orderdetails_id INTEGER NOT NULL, price NUMERIC(11, 5) NOT NULL --(DC2Type:big_decimal)
, price_related_quantity DOUBLE PRECISION NOT NULL, min_discount_quantity DOUBLE PRECISION NOT NULL, manual_input BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_C68C4459398D64AA FOREIGN KEY (id_currency) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_C68C44594A01DDC7 FOREIGN KEY (orderdetails_id) REFERENCES orderdetails (id) ON UPDATE NO ACTION ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO pricedetails (id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added) SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM __temp__pricedetails');
$this->addSql('DROP TABLE __temp__pricedetails');
$this->addSql('CREATE INDEX IDX_C68C44594A01DDC7 ON pricedetails (orderdetails_id)');
$this->addSql('CREATE INDEX IDX_C68C4459398D64AA ON pricedetails (id_currency)');
$this->addSql('CREATE INDEX pricedetails_idx_min_discount ON pricedetails (min_discount_quantity)');
$this->addSql('CREATE INDEX pricedetails_idx_min_discount_price_qty ON pricedetails (min_discount_quantity, price_related_quantity)');
$this->addSql('CREATE TEMPORARY TABLE __temp__project_bom_entries AS SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM project_bom_entries');
$this->addSql('DROP TABLE project_bom_entries');
$this->addSql('CREATE TABLE project_bom_entries (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_device INTEGER DEFAULT NULL, id_part INTEGER DEFAULT NULL, price_currency_id INTEGER DEFAULT NULL, quantity DOUBLE PRECISION NOT NULL, mountnames CLOB NOT NULL, name VARCHAR(255) DEFAULT NULL, comment CLOB NOT NULL, price NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal)
, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_AFC547992F180363 FOREIGN KEY (id_device) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AFC54799C22F6CC4 FOREIGN KEY (id_part) REFERENCES parts (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD313FFDCD60 FOREIGN KEY (price_currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO project_bom_entries (id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added) SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM __temp__project_bom_entries');
$this->addSql('DROP TABLE __temp__project_bom_entries');
$this->addSql('CREATE INDEX IDX_1AA2DD31C22F6CC4 ON project_bom_entries (id_part)');
$this->addSql('CREATE INDEX IDX_1AA2DD312F180363 ON project_bom_entries (id_device)');
$this->addSql('CREATE INDEX IDX_1AA2DD313FFDCD60 ON project_bom_entries (price_currency_id)');
$this->addSql('CREATE TEMPORARY TABLE __temp__suppliers AS SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM suppliers');
$this->addSql('DROP TABLE suppliers');
$this->addSql('CREATE TABLE suppliers (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, default_currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal)
, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_AC28B95C727ACA70 FOREIGN KEY (parent_id) REFERENCES suppliers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO suppliers (id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM __temp__suppliers');
$this->addSql('DROP TABLE __temp__suppliers');
$this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON suppliers (default_currency_id)');
$this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON suppliers (parent_id)');
$this->addSql('CREATE INDEX supplier_idx_name ON suppliers (name)');
$this->addSql('CREATE INDEX supplier_idx_parent_name ON suppliers (parent_id, name)');
$this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON suppliers (id_preview_attachment)');
$this->addSql('CREATE TEMPORARY TABLE __temp__users AS SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data, saml_user, about_me, show_email_on_profile FROM users');
$this->addSql('DROP TABLE users');
$this->addSql('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, group_id INTEGER DEFAULT NULL, currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a CLOB NOT NULL, config_instock_comment_w CLOB NOT NULL, trusted_device_cookie_version INTEGER NOT NULL, backup_codes CLOB NOT NULL --(DC2Type:json)
, google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, name VARCHAR(180) NOT NULL, settings CLOB NOT NULL --(DC2Type:json)
, backup_codes_generation_date DATETIME DEFAULT NULL, pw_reset_expires DATETIME DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB DEFAULT \'[]\' NOT NULL --(DC2Type:json)
, saml_user BOOLEAN NOT NULL, about_me CLOB DEFAULT \'\' NOT NULL, show_email_on_profile BOOLEAN DEFAULT 0 NOT NULL, CONSTRAINT FK_1483A5E9FE54D947 FOREIGN KEY (group_id) REFERENCES groups (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E9EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO users (id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data, saml_user, about_me, show_email_on_profile) SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data, saml_user, about_me, show_email_on_profile FROM __temp__users');
$this->addSql('DROP TABLE __temp__users');
$this->addSql('CREATE INDEX user_idx_username ON users (name)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON users (name)');
$this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON users (group_id)');
$this->addSql('CREATE INDEX IDX_1483A5E938248176 ON users (currency_id)');
$this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON users (id_preview_attachment)');
$this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM webauthn_keys');
$this->addSql('DROP TABLE webauthn_keys');
$this->addSql('CREATE TABLE webauthn_keys (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, public_key_credential_id CLOB NOT NULL --(DC2Type:base64)
, type VARCHAR(255) NOT NULL, transports CLOB NOT NULL --(DC2Type:array)
, attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL --(DC2Type:trust_path)
, aaguid CLOB NOT NULL --(DC2Type:aaguid)
, credential_public_key CLOB NOT NULL --(DC2Type:base64)
, user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO webauthn_keys (id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added) SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM __temp__webauthn_keys');
$this->addSql('DROP TABLE __temp__webauthn_keys');
$this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)');
$this->addSql('CREATE TEMPORARY TABLE __temp__currencies AS SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added FROM currencies');
$this->addSql('DROP TABLE currencies');
$this->addSql('CREATE TABLE currencies (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, exchange_rate NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal)
, iso_code VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_37C44693727ACA70 FOREIGN KEY (parent_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_37C44693EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO currencies (id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added FROM __temp__currencies');
$this->addSql('DROP TABLE __temp__currencies');
$this->addSql('CREATE INDEX IDX_37C44693EA7100A1 ON currencies (id_preview_attachment)');
$this->addSql('CREATE INDEX currency_idx_parent_name ON currencies (parent_id, name)');
$this->addSql('CREATE INDEX currency_idx_name ON currencies (name)');
$this->addSql('CREATE INDEX IDX_37C44693727ACA70 ON currencies (parent_id)');
$this->addSql('CREATE TEMPORARY TABLE __temp__groups AS SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data FROM groups');
$this->addSql('DROP TABLE groups');
$this->addSql('CREATE TABLE groups (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, enforce_2fa BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB NOT NULL --(DC2Type:json)
, CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES groups (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_F06D3970EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO groups (id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data) SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data FROM __temp__groups');
$this->addSql('DROP TABLE __temp__groups');
$this->addSql('CREATE INDEX IDX_F06D3970EA7100A1 ON groups (id_preview_attachment)');
$this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON groups (parent_id)');
$this->addSql('CREATE INDEX group_idx_name ON groups (name)');
$this->addSql('CREATE INDEX group_idx_parent_name ON groups (parent_id, name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__log AS SELECT id, id_user, datetime, level, target_id, target_type, extra, type, username FROM log');
$this->addSql('DROP TABLE log');
$this->addSql('CREATE TABLE log (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_user INTEGER DEFAULT NULL, datetime DATETIME NOT NULL, level TINYINT NOT NULL --(DC2Type:tinyint)
, target_id INTEGER NOT NULL, target_type SMALLINT NOT NULL, extra CLOB NOT NULL --(DC2Type:json)
, type SMALLINT NOT NULL, username VARCHAR(255) NOT NULL, CONSTRAINT FK_8F3F68C56B3CA4B FOREIGN KEY (id_user) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO log (id, id_user, datetime, level, target_id, target_type, extra, type, username) SELECT id, id_user, datetime, level, target_id, target_type, extra, type, username FROM __temp__log');
$this->addSql('DROP TABLE __temp__log');
$this->addSql('CREATE INDEX IDX_8F3F68C56B3CA4B ON log (id_user)');
$this->addSql('CREATE INDEX log_idx_type ON log (type)');
$this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)');
$this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)');
$this->addSql('CREATE TEMPORARY TABLE __temp__pricedetails AS SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM pricedetails');
$this->addSql('DROP TABLE pricedetails');
$this->addSql('CREATE TABLE pricedetails (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_currency INTEGER DEFAULT NULL, orderdetails_id INTEGER NOT NULL, price NUMERIC(11, 5) NOT NULL --(DC2Type:big_decimal)
, price_related_quantity DOUBLE PRECISION NOT NULL, min_discount_quantity DOUBLE PRECISION NOT NULL, manual_input BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_C68C4459398D64AA FOREIGN KEY (id_currency) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_C68C44594A01DDC7 FOREIGN KEY (orderdetails_id) REFERENCES orderdetails (id) ON UPDATE NO ACTION ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO pricedetails (id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added) SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM __temp__pricedetails');
$this->addSql('DROP TABLE __temp__pricedetails');
$this->addSql('CREATE INDEX pricedetails_idx_min_discount_price_qty ON pricedetails (min_discount_quantity, price_related_quantity)');
$this->addSql('CREATE INDEX pricedetails_idx_min_discount ON pricedetails (min_discount_quantity)');
$this->addSql('CREATE INDEX IDX_C68C4459398D64AA ON pricedetails (id_currency)');
$this->addSql('CREATE INDEX IDX_C68C44594A01DDC7 ON pricedetails (orderdetails_id)');
$this->addSql('CREATE TEMPORARY TABLE __temp__project_bom_entries AS SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM project_bom_entries');
$this->addSql('DROP TABLE project_bom_entries');
$this->addSql('CREATE TABLE project_bom_entries (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_device INTEGER DEFAULT NULL, id_part INTEGER DEFAULT NULL, price_currency_id INTEGER DEFAULT NULL, quantity DOUBLE PRECISION NOT NULL, mountnames CLOB NOT NULL, name VARCHAR(255) DEFAULT NULL, comment CLOB NOT NULL, price NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal)
, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_AFC547992F180363 FOREIGN KEY (id_device) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AFC54799C22F6CC4 FOREIGN KEY (id_part) REFERENCES parts (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD313FFDCD60 FOREIGN KEY (price_currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO project_bom_entries (id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added) SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM __temp__project_bom_entries');
$this->addSql('DROP TABLE __temp__project_bom_entries');
$this->addSql('CREATE INDEX IDX_1AA2DD313FFDCD60 ON project_bom_entries (price_currency_id)');
$this->addSql('CREATE INDEX IDX_1AA2DD312F180363 ON project_bom_entries (id_device)');
$this->addSql('CREATE INDEX IDX_1AA2DD31C22F6CC4 ON project_bom_entries (id_part)');
$this->addSql('CREATE TEMPORARY TABLE __temp__projects AS SELECT id, parent_id, id_preview_attachment, order_quantity, order_only_missing_parts, comment, not_selectable, name, last_modified, datetime_added, status, description FROM projects');
$this->addSql('DROP TABLE projects');
$this->addSql('CREATE TABLE projects (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, order_quantity INTEGER NOT NULL, order_only_missing_parts BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, status VARCHAR(64) DEFAULT NULL, description CLOB NOT NULL, CONSTRAINT FK_11074E9A727ACA70 FOREIGN KEY (parent_id) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_5C93B3A4EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO projects (id, parent_id, id_preview_attachment, order_quantity, order_only_missing_parts, comment, not_selectable, name, last_modified, datetime_added, status, description) SELECT id, parent_id, id_preview_attachment, order_quantity, order_only_missing_parts, comment, not_selectable, name, last_modified, datetime_added, status, description FROM __temp__projects');
$this->addSql('DROP TABLE __temp__projects');
$this->addSql('CREATE INDEX IDX_5C93B3A4727ACA70 ON projects (parent_id)');
$this->addSql('CREATE INDEX IDX_5C93B3A4EA7100A1 ON projects (id_preview_attachment)');
$this->addSql('CREATE TEMPORARY TABLE __temp__suppliers AS SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM suppliers');
$this->addSql('DROP TABLE suppliers');
$this->addSql('CREATE TABLE suppliers (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, default_currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal)
, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_AC28B95C727ACA70 FOREIGN KEY (parent_id) REFERENCES suppliers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO suppliers (id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM __temp__suppliers');
$this->addSql('DROP TABLE __temp__suppliers');
$this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON suppliers (id_preview_attachment)');
$this->addSql('CREATE INDEX supplier_idx_parent_name ON suppliers (parent_id, name)');
$this->addSql('CREATE INDEX supplier_idx_name ON suppliers (name)');
$this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON suppliers (parent_id)');
$this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON suppliers (default_currency_id)');
$this->addSql('CREATE TEMPORARY TABLE __temp__users AS SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data, saml_user, about_me, show_email_on_profile FROM users');
$this->addSql('DROP TABLE users');
$this->addSql('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, group_id INTEGER DEFAULT NULL, currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a CLOB NOT NULL, config_instock_comment_w CLOB NOT NULL, trusted_device_cookie_version INTEGER NOT NULL, backup_codes CLOB NOT NULL --(DC2Type:json)
, google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, name VARCHAR(180) NOT NULL, settings CLOB NOT NULL --(DC2Type:json)
, backup_codes_generation_date DATETIME DEFAULT NULL, pw_reset_expires DATETIME DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB NOT NULL --(DC2Type:json)
, saml_user BOOLEAN NOT NULL, about_me CLOB NOT NULL, show_email_on_profile BOOLEAN DEFAULT 0 NOT NULL, CONSTRAINT FK_1483A5E9FE54D947 FOREIGN KEY (group_id) REFERENCES groups (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E9EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO users (id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data, saml_user, about_me, show_email_on_profile) SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data, saml_user, about_me, show_email_on_profile FROM __temp__users');
$this->addSql('DROP TABLE __temp__users');
$this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON users (id_preview_attachment)');
$this->addSql('CREATE INDEX IDX_1483A5E938248176 ON users (currency_id)');
$this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON users (group_id)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON users (name)');
$this->addSql('CREATE INDEX user_idx_username ON users (name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM webauthn_keys');
$this->addSql('DROP TABLE webauthn_keys');
$this->addSql('CREATE TABLE webauthn_keys (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, public_key_credential_id CLOB NOT NULL --(DC2Type:base64)
, type VARCHAR(255) NOT NULL, transports CLOB NOT NULL --(DC2Type:array)
, attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL --(DC2Type:trust_path)
, aaguid CLOB NOT NULL --(DC2Type:aaguid)
, credential_public_key CLOB NOT NULL --(DC2Type:base64)
, user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO webauthn_keys (id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added) SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM __temp__webauthn_keys');
$this->addSql('DROP TABLE __temp__webauthn_keys');
$this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)');
}
public function sqLiteDown(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TEMPORARY TABLE __temp__currencies AS SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added FROM currencies');
$this->addSql('DROP TABLE currencies');
$this->addSql('CREATE TABLE currencies (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, exchange_rate NUMERIC(11, 5) DEFAULT NULL --
(DC2Type:big_decimal)
, iso_code VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_37C44693727ACA70 FOREIGN KEY (parent_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_37C44693EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO currencies (id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added FROM __temp__currencies');
$this->addSql('DROP TABLE __temp__currencies');
$this->addSql('CREATE INDEX IDX_37C44693727ACA70 ON currencies (parent_id)');
$this->addSql('CREATE INDEX IDX_37C44693EA7100A1 ON currencies (id_preview_attachment)');
$this->addSql('CREATE INDEX currency_idx_name ON currencies (name)');
$this->addSql('CREATE INDEX currency_idx_parent_name ON currencies (parent_id, name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__groups AS SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data FROM "groups"');
$this->addSql('DROP TABLE "groups"');
$this->addSql('CREATE TABLE "groups" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, enforce_2fa BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB DEFAULT \'[]\' NOT NULL --
(DC2Type:json)
, CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_F06D3970EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "groups" (id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data) SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data FROM __temp__groups');
$this->addSql('DROP TABLE __temp__groups');
$this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON "groups" (parent_id)');
$this->addSql('CREATE INDEX IDX_F06D3970EA7100A1 ON "groups" (id_preview_attachment)');
$this->addSql('CREATE INDEX group_idx_name ON "groups" (name)');
$this->addSql('CREATE INDEX group_idx_parent_name ON "groups" (parent_id, name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__log AS SELECT id, id_user, username, datetime, level, target_id, target_type, extra, type FROM log');
$this->addSql('DROP TABLE log');
$this->addSql('CREATE TABLE log (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_user INTEGER DEFAULT NULL, username VARCHAR(255) NOT NULL, datetime DATETIME NOT NULL, level BOOLEAN NOT NULL, target_id INTEGER NOT NULL, target_type SMALLINT NOT NULL, extra CLOB NOT NULL --
(DC2Type:json)
, type SMALLINT NOT NULL, CONSTRAINT FK_8F3F68C56B3CA4B FOREIGN KEY (id_user) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO log (id, id_user, username, datetime, level, target_id, target_type, extra, type) SELECT id, id_user, username, datetime, level, target_id, target_type, extra, type FROM __temp__log');
$this->addSql('DROP TABLE __temp__log');
$this->addSql('CREATE INDEX IDX_8F3F68C56B3CA4B ON log (id_user)');
$this->addSql('CREATE INDEX log_idx_type ON log (type)');
$this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)');
$this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)');
$this->addSql('CREATE TEMPORARY TABLE __temp__pricedetails AS SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM "pricedetails"');
$this->addSql('DROP TABLE "pricedetails"');
$this->addSql('CREATE TABLE "pricedetails" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_currency INTEGER DEFAULT NULL, orderdetails_id INTEGER NOT NULL, price NUMERIC(11, 5) NOT NULL --
(DC2Type:big_decimal)
, price_related_quantity DOUBLE PRECISION NOT NULL, min_discount_quantity DOUBLE PRECISION NOT NULL, manual_input BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_C68C4459398D64AA FOREIGN KEY (id_currency) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_C68C44594A01DDC7 FOREIGN KEY (orderdetails_id) REFERENCES "orderdetails" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "pricedetails" (id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added) SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM __temp__pricedetails');
$this->addSql('DROP TABLE __temp__pricedetails');
$this->addSql('CREATE INDEX IDX_C68C4459398D64AA ON "pricedetails" (id_currency)');
$this->addSql('CREATE INDEX IDX_C68C44594A01DDC7 ON "pricedetails" (orderdetails_id)');
$this->addSql('CREATE INDEX pricedetails_idx_min_discount ON "pricedetails" (min_discount_quantity)');
$this->addSql('CREATE INDEX pricedetails_idx_min_discount_price_qty ON "pricedetails" (min_discount_quantity, price_related_quantity)');
$this->addSql('CREATE TEMPORARY TABLE __temp__project_bom_entries AS SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM project_bom_entries');
$this->addSql('DROP TABLE project_bom_entries');
$this->addSql('CREATE TABLE project_bom_entries (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_device INTEGER DEFAULT NULL, id_part INTEGER DEFAULT NULL, price_currency_id INTEGER DEFAULT NULL, quantity DOUBLE PRECISION NOT NULL, mountnames CLOB NOT NULL, name VARCHAR(255) DEFAULT NULL, comment CLOB NOT NULL, price NUMERIC(11, 5) DEFAULT NULL --
(DC2Type:big_decimal)
, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_1AA2DD312F180363 FOREIGN KEY (id_device) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD31C22F6CC4 FOREIGN KEY (id_part) REFERENCES "parts" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD313FFDCD60 FOREIGN KEY (price_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO project_bom_entries (id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added) SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM __temp__project_bom_entries');
$this->addSql('DROP TABLE __temp__project_bom_entries');
$this->addSql('CREATE INDEX IDX_1AA2DD312F180363 ON project_bom_entries (id_device)');
$this->addSql('CREATE INDEX IDX_1AA2DD31C22F6CC4 ON project_bom_entries (id_part)');
$this->addSql('CREATE INDEX IDX_1AA2DD313FFDCD60 ON project_bom_entries (price_currency_id)');
$this->addSql('CREATE TEMPORARY TABLE __temp__suppliers AS SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM "suppliers"');
$this->addSql('DROP TABLE "suppliers"');
$this->addSql('CREATE TABLE "suppliers" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, default_currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL --
(DC2Type:big_decimal)
, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_AC28B95C727ACA70 FOREIGN KEY (parent_id) REFERENCES "suppliers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "suppliers" (id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM __temp__suppliers');
$this->addSql('DROP TABLE __temp__suppliers');
$this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON "suppliers" (parent_id)');
$this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON "suppliers" (default_currency_id)');
$this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON "suppliers" (id_preview_attachment)');
$this->addSql('CREATE INDEX supplier_idx_name ON "suppliers" (name)');
$this->addSql('CREATE INDEX supplier_idx_parent_name ON "suppliers" (parent_id, name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__users AS SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, show_email_on_profile, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, saml_user, last_modified, datetime_added, permissions_data FROM "users"');
$this->addSql('DROP TABLE "users"');
$this->addSql('CREATE TABLE "users" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, group_id INTEGER DEFAULT NULL, currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a CLOB NOT NULL, config_instock_comment_w CLOB NOT NULL, about_me CLOB DEFAULT \'\' NOT NULL, trusted_device_cookie_version INTEGER NOT NULL, backup_codes CLOB NOT NULL --
(DC2Type:json)
, google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, show_email_on_profile BOOLEAN DEFAULT 0 NOT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, name VARCHAR(180) NOT NULL, settings CLOB NOT NULL --
(DC2Type:json)
, backup_codes_generation_date DATETIME DEFAULT NULL, pw_reset_expires DATETIME DEFAULT NULL, saml_user BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB DEFAULT \'[]\' NOT NULL --
(DC2Type:json)
, CONSTRAINT FK_1483A5E9FE54D947 FOREIGN KEY (group_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E9EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "users" (id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, show_email_on_profile, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, saml_user, last_modified, datetime_added, permissions_data) SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, show_email_on_profile, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, saml_user, last_modified, datetime_added, permissions_data FROM __temp__users');
$this->addSql('DROP TABLE __temp__users');
$this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON "users" (name)');
$this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON "users" (group_id)');
$this->addSql('CREATE INDEX IDX_1483A5E938248176 ON "users" (currency_id)');
$this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON "users" (id_preview_attachment)');
$this->addSql('CREATE INDEX user_idx_username ON "users" (name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM webauthn_keys');
$this->addSql('DROP TABLE webauthn_keys');
$this->addSql('CREATE TABLE webauthn_keys (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, public_key_credential_id CLOB NOT NULL --
(DC2Type:base64)
, type VARCHAR(255) NOT NULL, transports CLOB NOT NULL --
(DC2Type:array)
, attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL --
(DC2Type:trust_path)
, aaguid CLOB NOT NULL --
(DC2Type:aaguid)
, credential_public_key CLOB NOT NULL --
(DC2Type:base64)
, user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO webauthn_keys (id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added) SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM __temp__webauthn_keys');
$this->addSql('DROP TABLE __temp__webauthn_keys');
$this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)');
$this->addSql('CREATE TEMPORARY TABLE __temp__currencies AS SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added FROM currencies');
$this->addSql('DROP TABLE currencies');
$this->addSql('CREATE TABLE currencies (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, exchange_rate NUMERIC(11, 5) DEFAULT NULL --
(DC2Type:big_decimal)
, iso_code VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_37C44693727ACA70 FOREIGN KEY (parent_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_37C44693EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO currencies (id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added FROM __temp__currencies');
$this->addSql('DROP TABLE __temp__currencies');
$this->addSql('CREATE INDEX IDX_37C44693727ACA70 ON currencies (parent_id)');
$this->addSql('CREATE INDEX IDX_37C44693EA7100A1 ON currencies (id_preview_attachment)');
$this->addSql('CREATE INDEX currency_idx_name ON currencies (name)');
$this->addSql('CREATE INDEX currency_idx_parent_name ON currencies (parent_id, name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__groups AS SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data FROM "groups"');
$this->addSql('DROP TABLE "groups"');
$this->addSql('CREATE TABLE "groups" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, enforce_2fa BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB DEFAULT \'[]\' NOT NULL --
(DC2Type:json)
, CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_F06D3970EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "groups" (id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data) SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data FROM __temp__groups');
$this->addSql('DROP TABLE __temp__groups');
$this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON "groups" (parent_id)');
$this->addSql('CREATE INDEX IDX_F06D3970EA7100A1 ON "groups" (id_preview_attachment)');
$this->addSql('CREATE INDEX group_idx_name ON "groups" (name)');
$this->addSql('CREATE INDEX group_idx_parent_name ON "groups" (parent_id, name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__log AS SELECT id, id_user, username, datetime, level, target_id, target_type, extra, type FROM log');
$this->addSql('DROP TABLE log');
$this->addSql('CREATE TABLE log (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_user INTEGER DEFAULT NULL, username VARCHAR(255) NOT NULL, datetime DATETIME NOT NULL, level TINYINT NOT NULL --
(DC2Type:tinyint)
, target_id INTEGER NOT NULL, target_type SMALLINT NOT NULL, extra CLOB NOT NULL --
(DC2Type:json)
, type SMALLINT NOT NULL, CONSTRAINT FK_8F3F68C56B3CA4B FOREIGN KEY (id_user) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO log (id, id_user, username, datetime, level, target_id, target_type, extra, type) SELECT id, id_user, username, datetime, level, target_id, target_type, extra, type FROM __temp__log');
$this->addSql('DROP TABLE __temp__log');
$this->addSql('CREATE INDEX IDX_8F3F68C56B3CA4B ON log (id_user)');
$this->addSql('CREATE INDEX log_idx_type ON log (type)');
$this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)');
$this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)');
$this->addSql('CREATE TEMPORARY TABLE __temp__pricedetails AS SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM "pricedetails"');
$this->addSql('DROP TABLE "pricedetails"');
$this->addSql('CREATE TABLE "pricedetails" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_currency INTEGER DEFAULT NULL, orderdetails_id INTEGER NOT NULL, price NUMERIC(11, 5) NOT NULL --
(DC2Type:big_decimal)
, price_related_quantity DOUBLE PRECISION NOT NULL, min_discount_quantity DOUBLE PRECISION NOT NULL, manual_input BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_C68C4459398D64AA FOREIGN KEY (id_currency) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_C68C44594A01DDC7 FOREIGN KEY (orderdetails_id) REFERENCES "orderdetails" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "pricedetails" (id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added) SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM __temp__pricedetails');
$this->addSql('DROP TABLE __temp__pricedetails');
$this->addSql('CREATE INDEX IDX_C68C4459398D64AA ON "pricedetails" (id_currency)');
$this->addSql('CREATE INDEX IDX_C68C44594A01DDC7 ON "pricedetails" (orderdetails_id)');
$this->addSql('CREATE INDEX pricedetails_idx_min_discount ON "pricedetails" (min_discount_quantity)');
$this->addSql('CREATE INDEX pricedetails_idx_min_discount_price_qty ON "pricedetails" (min_discount_quantity, price_related_quantity)');
$this->addSql('CREATE TEMPORARY TABLE __temp__project_bom_entries AS SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM project_bom_entries');
$this->addSql('DROP TABLE project_bom_entries');
$this->addSql('CREATE TABLE project_bom_entries (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_device INTEGER DEFAULT NULL, id_part INTEGER DEFAULT NULL, price_currency_id INTEGER DEFAULT NULL, quantity DOUBLE PRECISION NOT NULL, mountnames CLOB NOT NULL, name VARCHAR(255) DEFAULT NULL, comment CLOB NOT NULL, price NUMERIC(11, 5) DEFAULT NULL --
(DC2Type:big_decimal)
, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_1AA2DD312F180363 FOREIGN KEY (id_device) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD31C22F6CC4 FOREIGN KEY (id_part) REFERENCES "parts" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD313FFDCD60 FOREIGN KEY (price_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO project_bom_entries (id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added) SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM __temp__project_bom_entries');
$this->addSql('DROP TABLE __temp__project_bom_entries');
$this->addSql('CREATE INDEX IDX_1AA2DD312F180363 ON project_bom_entries (id_device)');
$this->addSql('CREATE INDEX IDX_1AA2DD31C22F6CC4 ON project_bom_entries (id_part)');
$this->addSql('CREATE INDEX IDX_1AA2DD313FFDCD60 ON project_bom_entries (price_currency_id)');
$this->addSql('CREATE TEMPORARY TABLE __temp__projects AS SELECT id, parent_id, id_preview_attachment, order_quantity, status, order_only_missing_parts, description, comment, not_selectable, name, last_modified, datetime_added FROM projects');
$this->addSql('DROP TABLE projects');
$this->addSql('CREATE TABLE projects (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, order_quantity INTEGER NOT NULL, status VARCHAR(64) DEFAULT NULL, order_only_missing_parts BOOLEAN NOT NULL, description CLOB DEFAULT \'\' NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_5C93B3A4727ACA70 FOREIGN KEY (parent_id) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_5C93B3A4EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO projects (id, parent_id, id_preview_attachment, order_quantity, status, order_only_missing_parts, description, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, order_quantity, status, order_only_missing_parts, description, comment, not_selectable, name, last_modified, datetime_added FROM __temp__projects');
$this->addSql('DROP TABLE __temp__projects');
$this->addSql('CREATE INDEX IDX_5C93B3A4727ACA70 ON projects (parent_id)');
$this->addSql('CREATE INDEX IDX_5C93B3A4EA7100A1 ON projects (id_preview_attachment)');
$this->addSql('CREATE TEMPORARY TABLE __temp__suppliers AS SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM "suppliers"');
$this->addSql('DROP TABLE "suppliers"');
$this->addSql('CREATE TABLE "suppliers" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, default_currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL --
(DC2Type:big_decimal)
, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_AC28B95C727ACA70 FOREIGN KEY (parent_id) REFERENCES "suppliers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "suppliers" (id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM __temp__suppliers');
$this->addSql('DROP TABLE __temp__suppliers');
$this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON "suppliers" (parent_id)');
$this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON "suppliers" (default_currency_id)');
$this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON "suppliers" (id_preview_attachment)');
$this->addSql('CREATE INDEX supplier_idx_name ON "suppliers" (name)');
$this->addSql('CREATE INDEX supplier_idx_parent_name ON "suppliers" (parent_id, name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__users AS SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, show_email_on_profile, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, saml_user, last_modified, datetime_added, permissions_data FROM "users"');
$this->addSql('DROP TABLE "users"');
$this->addSql('CREATE TABLE "users" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, group_id INTEGER DEFAULT NULL, currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a CLOB NOT NULL, config_instock_comment_w CLOB NOT NULL, about_me CLOB DEFAULT \'\' NOT NULL, trusted_device_cookie_version INTEGER NOT NULL, backup_codes CLOB NOT NULL --
(DC2Type:json)
, google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, show_email_on_profile BOOLEAN DEFAULT 0 NOT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, name VARCHAR(180) NOT NULL, settings CLOB NOT NULL --
(DC2Type:json)
, backup_codes_generation_date DATETIME DEFAULT NULL, pw_reset_expires DATETIME DEFAULT NULL, saml_user BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB DEFAULT \'[]\' NOT NULL --
(DC2Type:json)
, CONSTRAINT FK_1483A5E9FE54D947 FOREIGN KEY (group_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E9EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "users" (id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, show_email_on_profile, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, saml_user, last_modified, datetime_added, permissions_data) SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, show_email_on_profile, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, saml_user, last_modified, datetime_added, permissions_data FROM __temp__users');
$this->addSql('DROP TABLE __temp__users');
$this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON "users" (name)');
$this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON "users" (group_id)');
$this->addSql('CREATE INDEX IDX_1483A5E938248176 ON "users" (currency_id)');
$this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON "users" (id_preview_attachment)');
$this->addSql('CREATE INDEX user_idx_username ON "users" (name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM webauthn_keys');
$this->addSql('DROP TABLE webauthn_keys');
$this->addSql('CREATE TABLE webauthn_keys (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, public_key_credential_id CLOB NOT NULL --
(DC2Type:base64)
, type VARCHAR(255) NOT NULL, transports CLOB NOT NULL --
(DC2Type:array)
, attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL --
(DC2Type:trust_path)
, aaguid CLOB NOT NULL --
(DC2Type:aaguid)
, credential_public_key CLOB NOT NULL --
(DC2Type:base64)
, user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO webauthn_keys (id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added) SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM __temp__webauthn_keys');
$this->addSql('DROP TABLE __temp__webauthn_keys');
$this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)');
}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use App\Migration\AbstractMultiPlatformMigration;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20230417211732 extends AbstractMultiPlatformMigration
{
public function getDescription(): string
{
return 'Fix class names in attachments table for databases migrated from legacy Part-DB';
}
public function mySQLUp(Schema $schema): void
{
//Delete all attachments where the corresponding part or device was deleted in legacy Part-DB (and therefore does not exist in the new Part-DB
$this->addSql('DELETE FROM attachments WHERE class_name = "PartDB\\\\Part" AND NOT EXISTS (SELECT id FROM parts WHERE id = attachments.element_id)');
$this->addSql('DELETE FROM attachments WHERE class_name = "PartDB\\\\Device" AND NOT EXISTS (SELECT id FROM projects WHERE id = attachments.element_id)');
// Replace all attachments where class_name is the legacy "PartDB\Part" with the new version "Part"
//We have to use 4 backslashes here, as PHP reduces them to 2 backslashes, which MySQL interprets as an escaped backslash.
$this->addSql('UPDATE attachments SET class_name = "Part" WHERE class_name = "PartDB\\\\Part"');
//Do the same with PartDB\Device and Device
$this->addSql('UPDATE attachments SET class_name = "Device" WHERE class_name = "PartDB\\\\Device"');
}
public function mySQLDown(Schema $schema): void
{
// We can not revert this migration, because we don't know the old class name.
}
public function sqLiteUp(Schema $schema): void
{
//As legacy database can only be migrated to MySQL, we don't need to implement this method.
$this->skipIf(true, 'Not needed for SQLite');
}
public function sqLiteDown(Schema $schema): void
{
//As we done nothing, we don't need to implement this method.
}
}

View File

@@ -10,7 +10,6 @@ use PhpZip\ZipFile;
use Spatie\DbDumper\Databases\MySql;
use Spatie\DbDumper\DbDumper;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\Input;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;

View File

@@ -147,11 +147,21 @@ class ShowEventLogCommand extends Command
$target_class = $this->elementTypeNameGenerator->getLocalizedTypeLabel($entry->getTargetClass());
}
if ($entry->getUser()) {
$user = $entry->getUser()->getFullName(true);
} else {
if ($entry->isCLIEntry()) {
$user = $entry->getCLIUsername() . ' [CLI]';
} else {
$user = $entry->getUsername() . ' [deleted]';
}
}
$row = [
$entry->getID(),
$entry->getTimestamp()->format('Y-m-d H:i:s'),
$entry->getType(),
$entry->getUser()->getFullName(true),
$user,
$target_class,
$target_name,
];

View File

@@ -45,12 +45,12 @@ use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use function count;
/**
* This command converts the BBCode used by old Part-DB versions (<1.0), to the current used markdown format.
* This command converts the BBCode used by old Part-DB versions (<1.0), to the current used Markdown format.
*/
class ConvertBBCodeCommand extends Command
{
/**
* @var string The LIKE criteria used to detect on SQL server if a entry contains BBCode
* @var string The LIKE criteria used to detect on SQL server if an entry contains BBCode
*/
protected const BBCODE_CRITERIA = '%[%]%[/%]%';
/**

View File

@@ -0,0 +1,158 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Command\Migrations;
use App\Services\ImportExportSystem\PartKeeprImporter\PKDatastructureImporter;
use App\Services\ImportExportSystem\PartKeeprImporter\MySQLDumpXMLConverter;
use App\Services\ImportExportSystem\PartKeeprImporter\PKImportHelper;
use App\Services\ImportExportSystem\PartKeeprImporter\PKPartImporter;
use App\Services\ImportExportSystem\PartKeeprImporter\PKOptionalImporter;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class ImportPartKeeprCommand extends Command
{
protected static $defaultName = 'partdb:migrations:import-partkeepr';
protected EntityManagerInterface $em;
protected MySQLDumpXMLConverter $xml_converter;
protected PKDatastructureImporter $datastructureImporter;
protected PKImportHelper $importHelper;
protected PKPartImporter $partImporter;
protected PKOptionalImporter $optionalImporter;
public function __construct(EntityManagerInterface $em, MySQLDumpXMLConverter $xml_converter,
PKDatastructureImporter $datastructureImporter, PKPartImporter $partImporter, PKImportHelper $importHelper,
PKOptionalImporter $optionalImporter)
{
parent::__construct(self::$defaultName);
$this->em = $em;
$this->datastructureImporter = $datastructureImporter;
$this->importHelper = $importHelper;
$this->partImporter = $partImporter;
$this->xml_converter = $xml_converter;
$this->optionalImporter = $optionalImporter;
}
protected function configure()
{
$this->setDescription('Import a PartKeepr database XML dump into Part-DB');
$this->setHelp('This command allows you to import a PartKeepr database exported by mysqldump as XML file into Part-DB');
$this->addArgument('file', InputArgument::REQUIRED, 'The file to which should be imported.');
$this->addOption('--no-projects', null, InputOption::VALUE_NONE, 'Do not import projects.');
$this->addOption('--import-users', null, InputOption::VALUE_NONE, 'Import users (passwords will not be imported).');
}
public function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$input_path = $input->getArgument('file');
$no_projects_import = $input->getOption('no-projects');
$import_users = $input->getOption('import-users');
$io->note('This command is still in development. If you encounter any problems, please report them to the issue tracker on GitHub.');
$io->warning('This command will delete all existing data in the database (except users). Make sure that you have no important data in the database before you continue!');
$io->ask('Please type "DELETE ALL DATA" to continue.', '', function ($answer) {
if (strtoupper($answer) !== 'DELETE ALL DATA') {
throw new \RuntimeException('You did not type "DELETE ALL DATA"!');
}
return $answer;
});
//Make more checks here
//$io->confirm('This will delete all data in the database. Do you want to continue?', false);
//Purge the databse, so we will not have any conflicts
$this->importHelper->purgeDatabaseForImport();
//Convert the XML file to an array
$xml = file_get_contents($input_path);
$data = $this->xml_converter->convertMySQLDumpXMLDataToArrayStructure($xml);
if (!$this->importHelper->checkVersion($data)) {
$db_version = $this->importHelper->getDatabaseSchemaVersion($data);
$io->error('The version of the imported database is not supported! (Version: '.$db_version.')');
return 1;
}
//Import the mandatory data
$this->doImport($io, $data);
if (!$no_projects_import) {
$io->info('Importing projects...');
$count = $this->optionalImporter->importProjects($data);
$io->success('Imported '.$count.' projects.');
}
if ($import_users) {
$io->info('Importing users...');
$count = $this->optionalImporter->importUsers($data);
$io->success('Imported '.$count.' users.');
}
return 0;
}
private function doImport(SymfonyStyle $io, array $data): void
{
//First import the distributors
$io->info('Importing distributors...');
$count = $this->datastructureImporter->importDistributors($data);
$io->success('Imported '.$count.' distributors.');
//Import the measurement units
$io->info('Importing part measurement units...');
$count = $this->datastructureImporter->importPartUnits($data);
$io->success('Imported '.$count.' measurement units.');
//Import manufacturers
$io->info('Importing manufacturers...');
$count = $this->datastructureImporter->importManufacturers($data);
$io->success('Imported '.$count.' manufacturers.');
$io->info('Importing categories...');
$count = $this->datastructureImporter->importCategories($data);
$io->success('Imported '.$count.' categories.');
$io->info('Importing Footprints...');
$count = $this->datastructureImporter->importFootprints($data);
$io->success('Imported '.$count.' footprints.');
$io->info('Importing storage locations...');
$count = $this->datastructureImporter->importStorelocations($data);
$io->success('Imported '.$count.' storage locations.');
$io->info('Importing parts...');
$count = $this->partImporter->importParts($data);
$io->success('Imported '.$count.' parts.');
}
}

View File

@@ -0,0 +1,115 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Command\User;
use App\Entity\UserSystem\User;
use App\Security\SamlUserFactory;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class ConvertToSAMLUserCommand extends Command
{
protected static $defaultName = 'partdb:user:convert-to-saml-user|partdb:users:convert-to-saml-user';
protected EntityManagerInterface $entityManager;
protected bool $saml_enabled;
public function __construct(EntityManagerInterface $entityManager, bool $saml_enabled)
{
parent::__construct();
$this->entityManager = $entityManager;
$this->saml_enabled = $saml_enabled;
}
protected function configure(): void
{
$this
->setDescription('Converts a local user to a SAML user (and vice versa)')
->setHelp('This converts a local user, which can login via the login form, to a SAML user, which can only login via SAML. This is useful if you want to migrate from a local user system to a SAML user system.')
->addArgument('user', InputArgument::REQUIRED, 'The username (or email) of the user')
->addOption('to-local', null, InputOption::VALUE_NONE, 'Converts a SAML user to a local user')
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$user_name = $input->getArgument('user');
$to_local = $input->getOption('to-local');
if (!$this->saml_enabled && !$to_local) {
$io->confirm('SAML login is not configured. You will not be able to login with this user anymore, when SSO is not configured. Do you really want to continue?');
}
/** @var User $user */
$user = $this->entityManager->getRepository(User::class)->findByEmailOrName($user_name);
if (!$user) {
$io->error('User not found!');
return 1;
}
$io->info('User found: '.$user->getFullName(true) . ': '.$user->getEmail().' [ID: ' . $user->getID() . ']');
if ($to_local) {
return $this->toLocal($user, $io);
}
return $this->toSAML($user, $io);
}
public function toLocal(User $user, SymfonyStyle $io): int
{
$io->confirm('You are going to convert a SAML user to a local user. This means, that the user can only login via the login form. '
. 'The permissions and groups settings of the user will remain unchanged. Do you really want to continue?');
$user->setSAMLUser(false);
$user->setPassword(SamlUserFactory::SAML_PASSWORD_PLACEHOLDER);
$this->entityManager->flush();
$io->success('User converted to local user! You will need to set a password for this user, before you can login with it.');
return 0;
}
public function toSAML(User $user, SymfonyStyle $io): int
{
$io->confirm('You are going to convert a local user to a SAML user. This means, that the user can only login via SAML afterwards. The password in the DB will be removed. '
. 'The permissions and groups settings of the user will remain unchanged. Do you really want to continue?');
$user->setSAMLUser(true);
$user->setPassword(SamlUserFactory::SAML_PASSWORD_PLACEHOLDER);
$this->entityManager->flush();
$io->success('User converted to SAML user! You can now login with this user via SAML.');
return 0;
}
}

View File

@@ -56,7 +56,7 @@ class SetPasswordCommand extends Command
$this
->setDescription('Sets the password of a user')
->setHelp('This password allows you to set the password of a user, without knowing the old password.')
->addArgument('user', InputArgument::REQUIRED, 'The name of the user')
->addArgument('user', InputArgument::REQUIRED, 'The username or email of the user')
;
}
@@ -65,19 +65,21 @@ class SetPasswordCommand extends Command
$io = new SymfonyStyle($input, $output);
$user_name = $input->getArgument('user');
/** @var User[] $users */
$users = $this->entityManager->getRepository(User::class)->findBy(['name' => $user_name]);
$user = $this->entityManager->getRepository(User::class)->findByEmailOrName($user_name);
if (empty($users)) {
if (!$user) {
$io->error(sprintf('No user with the given username %s found in the database!', $user_name));
return 1;
}
$user = $users[0];
$io->note('User found!');
if ($user->isSamlUser()) {
$io->error('This user is a SAML user, so you can not change the password!');
return 1;
}
$proceed = $io->confirm(
sprintf('You are going to change the password of %s with ID %d. Proceed?',
$user->getFullName(true), $user->getID()));

View File

@@ -27,9 +27,7 @@ use App\Services\LogSystem\EventCommentHelper;
use App\Services\UserSystem\PermissionSchemaUpdater;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
@@ -91,12 +89,12 @@ final class UpgradePermissionsSchemaCommand extends Command
//List all users and groups that need an update
$io->section('Groups that need an update:');
$io->listing(array_map(function (Group $group) {
$io->listing(array_map(static function (Group $group) {
return $group->getName() . ' (ID: '. $group->getID() .', Current version: ' . $group->getPermissions()->getSchemaVersion() . ')';
}, $groups_to_upgrade));
$io->section('Users that need an update:');
$io->listing(array_map(function (User $user) {
$io->listing(array_map(static function (User $user) {
return $user->getUsername() . ' (ID: '. $user->getID() .', Current version: ' . $user->getPermissions()->getSchemaVersion() . ')';
}, $users_to_upgrade));

View File

@@ -87,7 +87,7 @@ class UserEnableCommand extends Command
$io->note('The following users will be enabled:');
}
$io->table(['Username', 'Enabled/Disabled'],
array_map(function(User $user) {
array_map(static function(User $user) {
return [$user->getFullName(true), $user->isDisabled() ? 'Disabled' : 'Enabled'];
}, $users));

View File

@@ -46,22 +46,39 @@ class UserListCommand extends Command
$this
->setDescription('Lists all users')
->setHelp('This command lists all users in the database.')
->addOption('local', 'l', null, 'Only list local users')
->addOption('saml', 's', null, 'Only list SAML users')
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$only_local = $input->getOption('local');
$only_saml = $input->getOption('saml');
//Get all users from database
$users = $this->entityManager->getRepository(User::class)->findAll();
if ($only_local && $only_saml) {
$io->error('You can not use --local and --saml at the same time!');
return Command::FAILURE;
}
$repo = $this->entityManager->getRepository(User::class);
if ($only_local) {
$users = $repo->onlyLocalUsers();
} elseif ($only_saml) {
$users = $repo->onlySAMLUsers();
} else {
$users = $repo->findAll();
}
$io->info(sprintf("Found %d users in database.", count($users)));
$io->title('Users:');
$table = new Table($output);
$table->setHeaders(['ID', 'Username', 'Name', 'Email', 'Group', 'Login Disabled']);
$table->setHeaders(['ID', 'Username', 'Name', 'Email', 'Group', 'Login Disabled', 'Type']);
foreach ($users as $user) {
$table->addRow([
@@ -71,6 +88,7 @@ class UserListCommand extends Command
$user->getEmail(),
$user->getGroup() !== null ? $user->getGroup()->getName() . ' (ID: ' . $user->getGroup()->getID() . ')' : 'No group',
$user->isDisabled() ? 'Yes' : 'No',
$user->isSAMLUser() ? 'SAML' : 'Local',
]);
}

View File

@@ -57,7 +57,7 @@ class UsersPermissionsCommand extends Command
protected function configure(): void
{
$this
->addArgument('user', InputArgument::REQUIRED, 'The username of the user to view')
->addArgument('user', InputArgument::REQUIRED, 'The username or email of the user to view')
->addOption('noInherit', null, InputOption::VALUE_NONE, 'Do not inherit permissions from groups')
->addOption('edit', '', InputOption::VALUE_NONE, 'Edit the permissions of the user')
;

View File

@@ -39,7 +39,7 @@ use App\Repository\AbstractPartsContainingRepository;
use App\Services\Attachments\AttachmentSubmitHandler;
use App\Services\ImportExportSystem\EntityExporter;
use App\Services\ImportExportSystem\EntityImporter;
use App\Services\LabelSystem\Barcodes\BarcodeExampleElementsGenerator;
use App\Services\LabelSystem\LabelExampleElementsGenerator;
use App\Services\LabelSystem\LabelGenerator;
use App\Services\LogSystem\EventCommentHelper;
use App\Services\LogSystem\HistoryHelper;
@@ -58,6 +58,7 @@ use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Contracts\Translation\TranslatorInterface;
@@ -85,14 +86,14 @@ abstract class BaseAdminController extends AbstractController
*/
protected $eventDispatcher;
protected LabelGenerator $labelGenerator;
protected BarcodeExampleElementsGenerator $barcodeExampleGenerator;
protected LabelExampleElementsGenerator $barcodeExampleGenerator;
protected EntityManagerInterface $entityManager;
public function __construct(TranslatorInterface $translator, UserPasswordHasherInterface $passwordEncoder,
AttachmentSubmitHandler $attachmentSubmitHandler,
EventCommentHelper $commentHelper, HistoryHelper $historyHelper, TimeTravel $timeTravel,
DataTableFactory $dataTableFactory, EventDispatcherInterface $eventDispatcher, BarcodeExampleElementsGenerator $barcodeExampleGenerator,
DataTableFactory $dataTableFactory, EventDispatcherInterface $eventDispatcher, LabelExampleElementsGenerator $barcodeExampleGenerator,
LabelGenerator $labelGenerator, EntityManagerInterface $entityManager)
{
if ('' === $this->entity_class || '' === $this->form_class || '' === $this->twig_template || '' === $this->route_base) {
@@ -268,7 +269,6 @@ abstract class BaseAdminController extends AbstractController
protected function _new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?AbstractNamedDBElement $entity = null)
{
$master_picture_backup = null;
if (null === $entity) {
/** @var AbstractStructuralDBElement|User $new_entity */
$new_entity = new $this->entity_class();
@@ -338,20 +338,39 @@ abstract class BaseAdminController extends AbstractController
$file = $import_form['file']->getData();
$data = $import_form->getData();
if ($data['format'] === 'auto') {
$format = $importer->determineFormat($file->getClientOriginalExtension());
if (null === $format) {
$this->addFlash('error', 'parts.import.flash.error.unknown_format');
goto ret;
}
} else {
$format = $data['format'];
}
$options = [
'parent' => $data['parent'],
'preserve_children' => $data['preserve_children'],
'format' => $data['format'],
'csv_separator' => $data['csv_separator'],
'parent' => $data['parent'] ?? null,
'preserve_children' => $data['preserve_children'] ?? false,
'format' => $format,
'class' => $this->entity_class,
'csv_delimiter' => $data['csv_delimiter'],
'abort_on_validation_error' => $data['abort_on_validation_error'],
];
$this->commentHelper->setMessage('Import '.$file->getClientOriginalName());
$errors = $importer->fileToDBEntities($file, $this->entity_class, $options);
try {
$errors = $importer->importFileAndPersistToDB($file, $options);
foreach ($errors as $name => $error) {
/** @var ConstraintViolationList $error */
$this->addFlash('error', $name.':'.$error);
foreach ($errors as $name => $error) {
foreach ($error['violations'] as $violation) {
$this->addFlash('error', $name.': '.$violation->getMessage());
}
}
}
catch (UnexpectedValueException $e) {
$this->addFlash('error', 'parts.import.flash.error.invalid_file');
}
}
@@ -370,7 +389,7 @@ abstract class BaseAdminController extends AbstractController
foreach ($errors as $error) {
if ($error['entity'] instanceof AbstractStructuralDBElement) {
$this->addFlash('error', $error['entity']->getFullPath().':'.$error['violations']);
} else { //When we dont have a structural element, we can only show the name
} else { //When we don't have a structural element, we can only show the name
$this->addFlash('error', $error['entity']->getName().':'.$error['violations']);
}
}
@@ -382,6 +401,7 @@ abstract class BaseAdminController extends AbstractController
$em->flush();
}
ret:
return $this->renderForm($this->twig_template, [
'entity' => $new_entity,
'form' => $form,
@@ -392,11 +412,11 @@ abstract class BaseAdminController extends AbstractController
}
/**
* Performs checks if the element can be deleted safely. Otherwise an flash message is added.
* Performs checks if the element can be deleted safely. Otherwise, a flash message is added.
*
* @param AbstractNamedDBElement $entity the element that should be checked
*
* @return bool True if the the element can be deleted, false if not
* @return bool True if the element can be deleted, false if not
*/
protected function deleteCheck(AbstractNamedDBElement $entity): bool
{

View File

@@ -31,7 +31,7 @@ use App\Services\Attachments\AttachmentSubmitHandler;
use App\Services\ImportExportSystem\EntityExporter;
use App\Services\ImportExportSystem\EntityImporter;
use App\Services\Tools\ExchangeRateUpdater;
use App\Services\LabelSystem\Barcodes\BarcodeExampleElementsGenerator;
use App\Services\LabelSystem\LabelExampleElementsGenerator;
use App\Services\LabelSystem\LabelGenerator;
use App\Services\LogSystem\EventCommentHelper;
use App\Services\LogSystem\HistoryHelper;
@@ -76,7 +76,7 @@ class CurrencyController extends BaseAdminController
TimeTravel $timeTravel,
DataTableFactory $dataTableFactory,
EventDispatcherInterface $eventDispatcher,
BarcodeExampleElementsGenerator $barcodeExampleGenerator,
LabelExampleElementsGenerator $barcodeExampleGenerator,
LabelGenerator $labelGenerator,
EntityManagerInterface $entityManager,
ExchangeRateUpdater $exchangeRateUpdater

View File

@@ -25,7 +25,6 @@ namespace App\Controller\AdminPages;
use App\Entity\Attachments\ProjectAttachment;
use App\Entity\ProjectSystem\Project;
use App\Entity\Parameters\ProjectParameter;
use App\Form\AdminPages\BaseEntityAdminForm;
use App\Form\AdminPages\ProjectAdminForm;
use App\Services\ImportExportSystem\EntityExporter;
use App\Services\ImportExportSystem\EntityImporter;

View File

@@ -24,12 +24,8 @@ namespace App\Controller;
use App\DataTables\AttachmentDataTable;
use App\DataTables\Filters\AttachmentFilter;
use App\DataTables\Filters\PartFilter;
use App\DataTables\PartsDataTable;
use App\Entity\Attachments\Attachment;
use App\Entity\Attachments\PartAttachment;
use App\Form\Filters\AttachmentFilterType;
use App\Form\Filters\PartFilterType;
use App\Services\Attachments\AttachmentManager;
use App\Services\Trees\NodesListBuilder;
use Omines\DataTablesBundle\DataTableFactory;
@@ -106,10 +102,8 @@ class AttachmentFileController extends AbstractController
/**
* @Route("/attachment/list", name="attachment_list")
*
* @return JsonResponse|Response
*/
public function attachmentsTable(Request $request, DataTableFactory $dataTableFactory, NodesListBuilder $nodesListBuilder)
public function attachmentsTable(Request $request, DataTableFactory $dataTableFactory, NodesListBuilder $nodesListBuilder): Response
{
$this->denyAccessUnlessGranted('@attachments.list_attachments');

View File

@@ -74,9 +74,9 @@ class GroupController extends BaseAdminController
//We need to stop the execution here, or our permissions changes will be overwritten by the form values
return $this->redirectToRoute('group_edit', ['id' => $entity->getID()]);
} else {
$this->addFlash('danger', 'csfr_invalid');
}
$this->addFlash('danger', 'csfr_invalid');
}
return $this->_edit($entity, $request, $em, $timestamp);

View File

@@ -64,9 +64,9 @@ class LogController extends AbstractController
/**
* @Route("/", name="log_view")
*
* @return JsonResponse|Response
* @return Response
*/
public function showLogs(Request $request, DataTableFactory $dataTable)
public function showLogs(Request $request, DataTableFactory $dataTable): Response
{
$this->denyAccessUnlessGranted('@system.show_logs');

View File

@@ -53,10 +53,12 @@ use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Contracts\Translation\TranslatorInterface;
use function Symfony\Component\Translation\t;
/**
* @Route("/part")
*/
@@ -348,8 +350,10 @@ class PartController extends AbstractController
if($partLot->getPart() !== $part) {
throw new \RuntimeException("The origin partlot does not belong to the part!");
}
//Try to determine the target lot (used for move actions)
$targetLot = $em->find(PartLot::class, $request->request->get('target_id'));
//Try to determine the target lot (used for move actions), if the parameter is existing
$targetId = $request->request->get('target_id', null);
$targetLot = $targetId ? $em->find(PartLot::class, $targetId) : null;
if ($targetLot && $targetLot->getPart() !== $part) {
throw new \RuntimeException("The target partlot does not belong to the part!");
}
@@ -360,23 +364,28 @@ class PartController extends AbstractController
$action = $request->request->get('action');
switch ($action) {
case "withdraw":
case "remove":
$this->denyAccessUnlessGranted('withdraw', $partLot);
$withdrawAddHelper->withdraw($partLot, $amount, $comment);
break;
case "add":
$this->denyAccessUnlessGranted('add', $partLot);
$withdrawAddHelper->add($partLot, $amount, $comment);
break;
case "move":
$this->denyAccessUnlessGranted('move', $partLot);
$withdrawAddHelper->move($partLot, $targetLot, $amount, $comment);
break;
default:
throw new \RuntimeException("Unknown action!");
try {
switch ($action) {
case "withdraw":
case "remove":
$this->denyAccessUnlessGranted('withdraw', $partLot);
$withdrawAddHelper->withdraw($partLot, $amount, $comment);
break;
case "add":
$this->denyAccessUnlessGranted('add', $partLot);
$withdrawAddHelper->add($partLot, $amount, $comment);
break;
case "move":
$this->denyAccessUnlessGranted('move', $partLot);
$this->denyAccessUnlessGranted('move', $targetLot);
$withdrawAddHelper->move($partLot, $targetLot, $amount, $comment);
break;
default:
throw new \RuntimeException("Unknown action!");
}
} catch (AccessDeniedException $exception) {
$this->addFlash('error', t('part.withdraw.access_denied'));
goto err;
}
//Save the changes to the DB
@@ -387,7 +396,8 @@ class PartController extends AbstractController
$this->addFlash('error', 'CSRF Token invalid!');
}
//If an redirect was passed, then redirect there
err:
//If a redirect was passed, then redirect there
if($request->request->get('_redirect')) {
return $this->redirect($request->request->get('_redirect'));
}

View File

@@ -0,0 +1,139 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Controller;
use App\Entity\Parts\Part;
use App\Form\AdminPages\ImportType;
use App\Services\ImportExportSystem\EntityExporter;
use App\Services\ImportExportSystem\EntityImporter;
use App\Services\LogSystem\EventCommentHelper;
use App\Services\Parts\PartsTableActionHandler;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
use UnexpectedValueException;
class PartImportExportController extends AbstractController
{
private PartsTableActionHandler $partsTableActionHandler;
private EntityImporter $entityImporter;
private EventCommentHelper $commentHelper;
public function __construct(PartsTableActionHandler $partsTableActionHandler,
EntityImporter $entityImporter, EventCommentHelper $commentHelper)
{
$this->partsTableActionHandler = $partsTableActionHandler;
$this->entityImporter = $entityImporter;
$this->commentHelper = $commentHelper;
}
/**
* @Route("/parts/import", name="parts_import")
* @param Request $request
* @return Response
*/
public function importParts(Request $request): Response
{
$this->denyAccessUnlessGranted('@parts.import');
$import_form = $this->createForm(ImportType::class, ['entity_class' => Part::class]);
$import_form->handleRequest($request);
if ($import_form->isSubmitted() && $import_form->isValid()) {
/** @var UploadedFile $file */
$file = $import_form['file']->getData();
$data = $import_form->getData();
if ($data['format'] === 'auto') {
$format = $this->entityImporter->determineFormat($file->getClientOriginalExtension());
if (null === $format) {
$this->addFlash('error', 'parts.import.flash.error.unknown_format');
goto ret;
}
} else {
$format = $data['format'];
}
$options = [
'create_unknown_datastructures' => $data['create_unknown_datastructures'],
'path_delimiter' => $data['path_delimiter'],
'format' => $format,
'part_category' => $data['part_category'],
'class' => Part::class,
'csv_delimiter' => $data['csv_delimiter'],
'part_needs_review' => $data['part_needs_review'],
'abort_on_validation_error' => $data['abort_on_validation_error'],
];
$this->commentHelper->setMessage('Import '.$file->getClientOriginalName());
$entities = [];
try {
$errors = $this->entityImporter->importFileAndPersistToDB($file, $options, $entities);
} catch (UnexpectedValueException $e) {
$this->addFlash('error', 'parts.import.flash.error.invalid_file');
if ($e instanceof NotNormalizableValueException) {
$this->addFlash('error', $e->getMessage());
}
goto ret;
}
if (!isset($errors) || $errors) {
$this->addFlash('error', 'parts.import.flash.error');
} else {
$this->addFlash('success', 'parts.import.flash.success');
}
}
ret:
return $this->renderForm('parts/import/parts_import.html.twig', [
'import_form' => $import_form,
'imported_entities' => $entities ?? [],
'import_errors' => $errors ?? [],
]);
}
/**
* @Route("/parts/export", name="parts_export", methods={"GET"})
* @return Response
*/
public function exportParts(Request $request, EntityExporter $entityExporter): Response
{
$ids = $request->query->get('ids', '');
$parts = $this->partsTableActionHandler->idStringToArray($ids);
if (empty($parts)) {
throw new \RuntimeException('No parts found!');
}
//Ensure that we have access to the parts
foreach ($parts as $part) {
$this->denyAccessUnlessGranted('read', $part);
}
return $entityExporter->exportEntityFromRequest($parts, $request);
}
}

View File

@@ -158,7 +158,7 @@ class PartListsController extends AbstractController
*
* @return JsonResponse|Response
*/
public function showCategory(Category $category, Request $request)
public function showCategory(Category $category, Request $request): Response
{
$this->denyAccessUnlessGranted('@categories.read');
@@ -180,7 +180,7 @@ class PartListsController extends AbstractController
*
* @return JsonResponse|Response
*/
public function showFootprint(Footprint $footprint, Request $request)
public function showFootprint(Footprint $footprint, Request $request): Response
{
$this->denyAccessUnlessGranted('@footprints.read');
@@ -202,7 +202,7 @@ class PartListsController extends AbstractController
*
* @return JsonResponse|Response
*/
public function showManufacturer(Manufacturer $manufacturer, Request $request)
public function showManufacturer(Manufacturer $manufacturer, Request $request): Response
{
$this->denyAccessUnlessGranted('@manufacturers.read');
@@ -224,7 +224,7 @@ class PartListsController extends AbstractController
*
* @return JsonResponse|Response
*/
public function showStorelocation(Storelocation $storelocation, Request $request)
public function showStorelocation(Storelocation $storelocation, Request $request): Response
{
$this->denyAccessUnlessGranted('@storelocations.read');
@@ -246,7 +246,7 @@ class PartListsController extends AbstractController
*
* @return JsonResponse|Response
*/
public function showSupplier(Supplier $supplier, Request $request)
public function showSupplier(Supplier $supplier, Request $request): Response
{
$this->denyAccessUnlessGranted('@suppliers.read');
@@ -268,7 +268,7 @@ class PartListsController extends AbstractController
*
* @return JsonResponse|Response
*/
public function showTag(string $tag, Request $request, DataTableFactory $dataTable)
public function showTag(string $tag, Request $request): Response
{
$tag = trim($tag);
@@ -291,14 +291,17 @@ class PartListsController extends AbstractController
$filter->setName($request->query->getBoolean('name', true));
$filter->setCategory($request->query->getBoolean('category', true));
$filter->setDescription($request->query->getBoolean('description', true));
$filter->setMpn($request->query->getBoolean('mpn', true));
$filter->setTags($request->query->getBoolean('tags', true));
$filter->setStorelocation($request->query->getBoolean('storelocation', true));
$filter->setComment($request->query->getBoolean('comment', true));
$filter->setIPN($request->query->getBoolean('ipn', true));
$filter->setOrdernr($request->query->getBoolean('ordernr', true));
$filter->setSupplier($request->query->getBoolean('supplier', false));
$filter->setManufacturer($request->query->getBoolean('manufacturer', false));
$filter->setFootprint($request->query->getBoolean('footprint', false));
$filter->setRegex($request->query->getBoolean('regex', false));
return $filter;
@@ -309,7 +312,7 @@ class PartListsController extends AbstractController
*
* @return JsonResponse|Response
*/
public function showSearch(Request $request, DataTableFactory $dataTable)
public function showSearch(Request $request, DataTableFactory $dataTable): Response
{
$searchFilter = $this->searchRequestToFilter($request);
@@ -330,9 +333,9 @@ class PartListsController extends AbstractController
/**
* @Route("/parts", name="parts_show_all")
*
* @return JsonResponse|Response
* @return Response
*/
public function showAll(Request $request, DataTableFactory $dataTable)
public function showAll(Request $request): Response
{
return $this->showListWithFilter($request,'parts/lists/all_list.html.twig');
}

View File

@@ -28,17 +28,24 @@ use App\Form\ProjectSystem\ProjectBOMEntryCollectionType;
use App\Form\ProjectSystem\ProjectBuildType;
use App\Form\Type\StructuralEntityType;
use App\Helpers\Projects\ProjectBuildRequest;
use App\Services\ImportExportSystem\BOMImporter;
use App\Services\ProjectSystem\ProjectBuildHelper;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface;
use League\Csv\SyntaxError;
use Omines\DataTablesBundle\DataTableFactory;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Validator\Constraints\NotNull;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use function Symfony\Component\Translation\t;
/**
* @Route("/project")
@@ -55,7 +62,7 @@ class ProjectController extends AbstractController
/**
* @Route("/{id}/info", name="project_info", requirements={"id"="\d+"})
*/
public function info(Project $project, Request $request, ProjectBuildHelper $buildHelper)
public function info(Project $project, Request $request, ProjectBuildHelper $buildHelper): Response
{
$this->denyAccessUnlessGranted('read', $project);
@@ -105,9 +112,9 @@ class ProjectController extends AbstractController
$request->get('_redirect',
$this->generateUrl('project_info', ['id' => $project->getID()]
)));
} else {
$this->addFlash('error', 'project.build.flash.invalid_input');
}
$this->addFlash('error', 'project.build.flash.invalid_input');
}
return $this->renderForm('projects/build/build.html.twig', [
@@ -119,6 +126,82 @@ class ProjectController extends AbstractController
]);
}
/**
* @Route("/{id}/import_bom", name="project_import_bom", requirements={"id"="\d+"})
*/
public function importBOM(Request $request, EntityManagerInterface $entityManager, Project $project,
BOMImporter $BOMImporter, ValidatorInterface $validator): Response
{
$this->denyAccessUnlessGranted('edit', $project);
$builder = $this->createFormBuilder();
$builder->add('file', FileType::class, [
'label' => 'import.file',
'required' => true,
'attr' => [
'accept' => '.csv'
]
]);
$builder->add('type', ChoiceType::class, [
'label' => 'project.bom_import.type',
'required' => true,
'choices' => [
'project.bom_import.type.kicad_pcbnew' => 'kicad_pcbnew',
]
]);
$builder->add('clear_existing_bom', CheckboxType::class, [
'label' => 'project.bom_import.clear_existing_bom',
'required' => false,
'data' => false,
'help' => 'project.bom_import.clear_existing_bom.help',
]);
$builder->add('submit', SubmitType::class, [
'label' => 'import.btn',
]);
$form = $builder->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
//Clear existing BOM entries if requested
if ($form->get('clear_existing_bom')->getData()) {
$project->getBomEntries()->clear();
$entityManager->flush();
}
try {
$entries = $BOMImporter->importFileIntoProject($form->get('file')->getData(), $project, [
'type' => $form->get('type')->getData(),
]);
//Validate the project entries
$errors = $validator->validateProperty($project, 'bom_entries');
//If no validation errors occured, save the changes and redirect to edit page
if (count ($errors) === 0) {
$this->addFlash('success', t('project.bom_import.flash.success', ['%count%' => count($entries)]));
$entityManager->flush();
return $this->redirectToRoute('project_edit', ['id' => $project->getID()]);
}
if (count ($errors) > 0) {
$this->addFlash('error', t('project.bom_import.flash.invalid_entries'));
}
} catch (\UnexpectedValueException $e) {
$this->addFlash('error', t('project.bom_import.flash.invalid_file', ['%message%' => $e->getMessage()]));
} catch (SyntaxError $e) {
$this->addFlash('error', t('project.bom_import.flash.invalid_file', ['%message%' => $e->getMessage()]));
}
}
return $this->renderForm('projects/import_bom.html.twig', [
'project' => $project,
'form' => $form,
'errors' => $errors ?? null,
]);
}
/**
* @Route("/add_parts", name="project_add_parts_no_id")
* @Route("/{id}/add_parts", name="project_add_parts", requirements={"id"="\d+"})

View File

@@ -28,20 +28,17 @@ use function in_array;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class RedirectController extends AbstractController
{
protected string $default_locale;
protected TranslatorInterface $translator;
protected SessionInterface $session;
protected bool $enforce_index_php;
public function __construct(string $default_locale, TranslatorInterface $translator, SessionInterface $session, bool $enforce_index_php)
public function __construct(string $default_locale, TranslatorInterface $translator, bool $enforce_index_php)
{
$this->default_locale = $default_locale;
$this->session = $session;
$this->translator = $translator;
$this->enforce_index_php = $enforce_index_php;
}
@@ -52,7 +49,7 @@ class RedirectController extends AbstractController
*/
public function addLocalePart(Request $request): RedirectResponse
{
//By default we use the global default locale
//By default, we use the global default locale
$locale = $this->default_locale;
//Check if a user has set a preferred language setting:
@@ -61,7 +58,6 @@ class RedirectController extends AbstractController
$locale = $user->getLanguage();
}
//$new_url = str_replace($request->getPathInfo(), '/' . $locale . $request->getPathInfo(), $request->getUri());
$new_url = $request->getUriForPath('/'.$locale.$request->getPathInfo());
//If either mod_rewrite is not enabled or the index.php version is enforced, add index.php to the string
@@ -71,6 +67,9 @@ class RedirectController extends AbstractController
$new_url = $request->getSchemeAndHttpHost().$request->getBaseUrl().'/index.php/'.$locale.$request->getPathInfo();
}
//Add the query string
$new_url .= $request->getQueryString() ? '?'.$request->getQueryString() : '';
return $this->redirect($new_url);
}

View File

@@ -22,7 +22,6 @@ namespace App\Controller;
use App\Entity\Base\AbstractNamedDBElement;
use App\Entity\Base\AbstractStructuralDBElement;
use App\Entity\Contracts\NamedElementInterface;
use App\Entity\LabelSystem\LabelProfile;
use App\Entity\Parts\Category;
use App\Entity\Parts\Footprint;
@@ -95,6 +94,25 @@ class SelectAPIController extends AbstractController
return $this->getResponseForClass(Project::class, false);
}
/**
* @Route("/export_level", name="select_export_level")
*/
public function exportLevel(): Response
{
$entries = [
1 => $this->translator->trans('export.level.simple'),
2 => $this->translator->trans('export.level.extended'),
3 => $this->translator->trans('export.level.full'),
];
return $this->json(array_map(static function ($key, $value) {
return [
'text' => $value,
'value' => $key,
];
}, array_keys($entries), $entries));
}
/**
* @Route("/label_profiles", name="select_label_profiles")
* @return Response
@@ -179,7 +197,7 @@ class SelectAPIController extends AbstractController
]);
//Remove the data-* prefix for each key
$data = array_combine(
array_map(function ($key) {
array_map(static function ($key) {
if (strpos($key, 'data-') === 0) {
return substr($key, 5);
}

View File

@@ -21,6 +21,7 @@
namespace App\Controller;
use App\Services\Attachments\AttachmentPathResolver;
use App\Services\Attachments\AttachmentSubmitHandler;
use App\Services\Attachments\AttachmentURLGenerator;
use App\Services\Attachments\BuiltinAttachmentsFinder;
use App\Services\Misc\GitVersionInfo;
@@ -49,7 +50,8 @@ class ToolsController extends AbstractController
/**
* @Route("/server_infos", name="tools_server_infos")
*/
public function systemInfos(GitVersionInfo $versionInfo, DBInfoHelper $DBInfoHelper): Response
public function systemInfos(GitVersionInfo $versionInfo, DBInfoHelper $DBInfoHelper,
AttachmentSubmitHandler $attachmentSubmitHandler): Response
{
$this->denyAccessUnlessGranted('@system.server_infos');
@@ -73,6 +75,9 @@ class ToolsController extends AbstractController
'allow_attachments_downloads' => $this->getParameter('partdb.attachments.allow_downloads'),
'detailed_error_pages' => $this->getParameter('partdb.error_pages.show_help'),
'error_page_admin_email' => $this->getParameter('partdb.error_pages.admin_email'),
'configured_max_file_size' => $this->getParameter('partdb.attachments.max_file_size'),
'effective_max_file_size' => $attachmentSubmitHandler->getMaximumAllowedUploadSize(),
'saml_enabled' => $this->getParameter('partdb.saml.enabled'),
//PHP section
'php_version' => PHP_VERSION,

View File

@@ -95,7 +95,7 @@ class TypeaheadController extends AbstractController
}
/**
* This functions map the parameter type to the class, so we can access its repository
* This function map the parameter type to the class, so we can access its repository
* @param string $type
* @return class-string
*/

View File

@@ -25,7 +25,6 @@ namespace App\Controller;
use App\DataTables\LogDataTable;
use App\Entity\Attachments\UserAttachment;
use App\Entity\Base\AbstractNamedDBElement;
use App\Entity\Parameters\AbstractParameter;
use App\Entity\UserSystem\User;
use App\Events\SecurityEvent;
use App\Events\SecurityEvents;
@@ -62,11 +61,11 @@ class UserController extends AdminPages\BaseAdminController
protected function additionalActionEdit(FormInterface $form, AbstractNamedDBElement $entity): bool
{
//Check if we editing a user and if we need to change the password of it
//Check if we're editing a user and if we need to change the password of it
if ($entity instanceof User && !empty($form['new_password']->getData())) {
$password = $this->passwordEncoder->hashPassword($entity, $form['new_password']->getData());
$entity->setPassword($password);
//By default the user must change the password afterwards
//By default, the user must change the password afterward
$entity->setNeedPwChange(true);
$event = new SecurityEvent($entity);
@@ -129,9 +128,9 @@ class UserController extends AdminPages\BaseAdminController
//We need to stop the execution here, or our permissions changes will be overwritten by the form values
return $this->redirectToRoute('user_edit', ['id' => $entity->getID()]);
} else {
$this->addFlash('danger', 'csfr_invalid');
}
$this->addFlash('danger', 'csfr_invalid');
}
return $this->_edit($entity, $request, $em, $timestamp);
@@ -142,7 +141,7 @@ class UserController extends AdminPages\BaseAdminController
if ($entity instanceof User && !empty($form['new_password']->getData())) {
$password = $this->passwordEncoder->hashPassword($entity, $form['new_password']->getData());
$entity->setPassword($password);
//By default the user must change the password afterwards
//By default, the user must change the password afterward
$entity->setNeedPwChange(true);
}
@@ -202,21 +201,24 @@ class UserController extends AdminPages\BaseAdminController
$user = $tmp;
} else {
//Else we must check, if the current user is allowed to access $user
$this->denyAccessUnlessGranted('read', $user);
$this->denyAccessUnlessGranted('info', $user);
}
$table = $this->dataTableFactory->createFromType(
LogDataTable::class,
[
'filter_elements' => $user,
'mode' => 'element_history',
],
['pageLength' => 10]
)
->handleRequest($request);
//Only show the history table, if the user is the current user
if ($user === $this->getUser()) {
$table = $this->dataTableFactory->createFromType(
LogDataTable::class,
[
'filter_elements' => $user,
'mode' => 'element_history',
],
['pageLength' => 10]
)
->handleRequest($request);
if ($table->isCallback()) {
return $table->getResponse();
if ($table->isCallback()) {
return $table->getResponse();
}
}
//Show permissions to user
@@ -230,7 +232,7 @@ class UserController extends AdminPages\BaseAdminController
return $this->renderForm('users/user_info.html.twig', [
'user' => $user,
'form' => $builder->getForm(),
'datatable' => $table,
'datatable' => $table ?? null,
]);
}
}

View File

@@ -83,6 +83,10 @@ class UserSettingsController extends AbstractController
return new RuntimeException('This controller only works only for Part-DB User objects!');
}
if ($user->isSamlUser()) {
throw new RuntimeException('You can not remove U2F keys from SAML users!');
}
if (empty($user->getBackupCodes())) {
$this->addFlash('error', 'tfa_backup.no_codes_enabled');
@@ -112,6 +116,10 @@ class UserSettingsController extends AbstractController
throw new RuntimeException('This controller only works only for Part-DB User objects!');
}
if ($user->isSamlUser()) {
throw new RuntimeException('You can not remove U2F keys from SAML users!');
}
if ($this->isCsrfTokenValid('delete'.$user->getId(), $request->request->get('_token'))) {
//Handle U2F key removal
if ($request->request->has('key_id')) {
@@ -192,6 +200,10 @@ class UserSettingsController extends AbstractController
return new RuntimeException('This controller only works only for Part-DB User objects!');
}
if ($user->isSamlUser()) {
throw new RuntimeException('You can not remove U2F keys from SAML users!');
}
if ($this->isCsrfTokenValid('devices_reset'.$user->getId(), $request->request->get('_token'))) {
$user->invalidateTrustedDeviceTokens();
$entityManager->flush();
@@ -213,7 +225,7 @@ class UserSettingsController extends AbstractController
*/
public function userSettings(Request $request, EntityManagerInterface $em, UserPasswordHasherInterface $passwordEncoder, GoogleAuthenticator $googleAuthenticator, BackupCodeManager $backupCodeManager, FormFactoryInterface $formFactory, UserAvatarHelper $avatarHelper)
{
/** @var User */
/** @var User $user */
$user = $this->getUser();
$page_need_reload = false;
@@ -249,7 +261,7 @@ class UserSettingsController extends AbstractController
$page_need_reload = true;
}
/** @var Form $form We need an form implementation for the next calls */
/** @var Form $form We need a form implementation for the next calls */
if ($form->getClickedButton() && 'remove_avatar' === $form->getClickedButton()->getName()) {
//Remove the avatar attachment from the user if requested
if ($user->getMasterPictureAttachment() !== null) {
@@ -281,14 +293,14 @@ class UserSettingsController extends AbstractController
])
->add('old_password', PasswordType::class, [
'label' => 'user.settings.pw_old.label',
'disabled' => $this->demo_mode,
'disabled' => $this->demo_mode || $user->isSamlUser(),
'attr' => [
'autocomplete' => 'current-password',
],
'constraints' => [new UserPassword()],
]) //This constraint checks, if the current user pw was inputted.
->add('new_password', RepeatedType::class, [
'disabled' => $this->demo_mode,
'disabled' => $this->demo_mode || $user->isSamlUser(),
'type' => PasswordType::class,
'first_options' => [
'label' => 'user.settings.pw_new.label',
@@ -307,12 +319,15 @@ class UserSettingsController extends AbstractController
'max' => 128,
])],
])
->add('submit', SubmitType::class, ['label' => 'save'])
->add('submit', SubmitType::class, [
'label' => 'save',
'disabled' => $this->demo_mode || $user->isSamlUser(),
])
->getForm();
$pw_form->handleRequest($request);
//Check if password if everything was correct, then save it to User and DB
//Check if everything was correct, then save it to User and DB
if (!$this->demo_mode && $pw_form->isSubmitted() && $pw_form->isValid()) {
$password = $passwordEncoder->hashPassword($user, $pw_form['new_password']->getData());
$user->setPassword($password);
@@ -327,7 +342,9 @@ class UserSettingsController extends AbstractController
}
//Handle 2FA things
$google_form = $this->createForm(TFAGoogleSettingsType::class, $user);
$google_form = $this->createForm(TFAGoogleSettingsType::class, $user, [
'disabled' => $this->demo_mode || $user->isSamlUser(),
]);
$google_enabled = $user->isGoogleAuthenticatorEnabled();
if (!$google_enabled && !$form->isSubmitted()) {
$user->setGoogleAuthenticatorSecret($googleAuthenticator->generateSecret());
@@ -335,7 +352,7 @@ class UserSettingsController extends AbstractController
}
$google_form->handleRequest($request);
if (!$this->demo_mode && $google_form->isSubmitted() && $google_form->isValid()) {
if (!$this->demo_mode && !$user->isSamlUser() && $google_form->isSubmitted() && $google_form->isValid()) {
if (!$google_enabled) {
//Save 2FA settings (save secrets)
$user->setGoogleAuthenticatorSecret($google_form->get('googleAuthenticatorSecret')->getData());
@@ -369,7 +386,7 @@ class UserSettingsController extends AbstractController
])->getForm();
$backup_form->handleRequest($request);
if (!$this->demo_mode && $backup_form->isSubmitted() && $backup_form->isValid()) {
if (!$this->demo_mode && !$user->isSamlUser() && $backup_form->isSubmitted() && $backup_form->isValid()) {
$backupCodeManager->regenerateBackupCodes($user);
$em->flush();
$this->addFlash('success', 'user.settings.2fa.backup_codes.regenerated');

View File

@@ -20,9 +20,11 @@
namespace App\Controller;
use App\Entity\UserSystem\User;
use App\Entity\UserSystem\WebauthnKey;
use Doctrine\ORM\EntityManagerInterface;
use Jbtronics\TFAWebauthn\Services\TFAWebauthnRegistrationHelper;
use RuntimeException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
@@ -31,6 +33,13 @@ use function Symfony\Component\Translation\t;
class WebauthnKeyRegistrationController extends AbstractController
{
private bool $demo_mode;
public function __construct(bool $demo_mode)
{
$this->demo_mode = $demo_mode;
}
/**
* @Route("/webauthn/register", name="webauthn_register")
*/
@@ -39,6 +48,20 @@ class WebauthnKeyRegistrationController extends AbstractController
//When user change its settings, he should be logged in fully.
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
if ($this->demo_mode) {
throw new RuntimeException('You can not do 2FA things in demo mode');
}
$user = $this->getUser();
if (!$user instanceof User) {
throw new RuntimeException('This controller only works only for Part-DB User objects!');
}
if ($user->isSamlUser()) {
throw new RuntimeException('You can not remove U2F keys from SAML users!');
}
//If form was submitted, check the auth response
if ($request->getMethod() === 'POST') {
$webauthnResponse = $request->request->get('_auth_code');

View File

@@ -0,0 +1,53 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\DataTables\Adapters;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\Tools\Pagination\Paginator;
use Omines\DataTablesBundle\Adapter\Doctrine\FetchJoinORMAdapter;
/**
* This class is a workaround for a bug (or edge case behavior) in the FetchJoinORMAdapter or better the used Paginator
* and CountOutputWalker.
* If the query contains multiple GROUP BY clauses, the result of the count query is wrong, as some lines are counted
* multiple times. This is because the CountOutputWalker does not use DISTINCT in the count query, if it contains a GROUP BY.
*
* We work around this by removing the GROUP BY clause from the query, and only adding the first root alias as GROUP BY (the part table).
* This way we get the correct count, without breaking the query (we need a GROUP BY for the HAVING clauses).
*
* As a side effect this also seems to improve the performance of the count query a bit (which makes up a lot of the total query time).
*/
class CustomFetchJoinORMAdapter extends FetchJoinORMAdapter
{
public function getCount(QueryBuilder $queryBuilder, $identifier): ?int
{
$qb_without_group_by = clone $queryBuilder;
//Remove the groupBy clause from the query for the count
//And add the root alias as group by, so we can use HAVING clauses
$qb_without_group_by->resetDQLPart('groupBy');
$qb_without_group_by->addGroupBy($queryBuilder->getRootAliases()[0]);
$paginator = new Paginator($qb_without_group_by);
return $paginator->count();
}
}

View File

@@ -22,9 +22,7 @@ declare(strict_types=1);
namespace App\DataTables\Column;
use App\Entity\Base\AbstractDBElement;
use App\Entity\Base\AbstractNamedDBElement;
use App\Entity\Parts\Part;
use App\Services\EntityURLGenerator;
use Omines\DataTablesBundle\Column\AbstractColumn;
use Symfony\Component\OptionsResolver\Options;
@@ -79,7 +77,7 @@ class EntityColumn extends AbstractColumn
return sprintf(
'<a href="%s">%s</a>',
$this->urlGenerator->listPartsURL($entity),
$entity->getName()
htmlspecialchars($entity->getName())
);
}

View File

@@ -31,7 +31,7 @@ use Omines\DataTablesBundle\Column\AbstractColumn;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Similar to the built in DateTimeColumn, but the datetime is formatted using a IntlDateFormatter,
* Similar to the built-in DateTimeColumn, but the datetime is formatted using a IntlDateFormatter,
* to get prettier locale based formatting.
*/
class LocaleDateTimeColumn extends AbstractColumn
@@ -45,7 +45,9 @@ class LocaleDateTimeColumn extends AbstractColumn
{
if (null === $value) {
return $this->options['nullValue'];
} elseif (!$value instanceof DateTimeInterface) {
}
if (!$value instanceof DateTimeInterface) {
$value = new DateTime((string) $value);
}

View File

@@ -41,7 +41,7 @@ class PrettyBoolColumn extends AbstractColumn
return (bool) $value;
}
public function render($value, $context)
public function render($value, $context): string
{
if ($value === true) {
return '<span class="badge bg-success"><i class="fa-solid fa-circle-check fa-fw"></i> '

View File

@@ -27,7 +27,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
class RowClassColumn extends AbstractColumn
{
public function configureOptions(OptionsResolver $resolver)
public function configureOptions(OptionsResolver $resolver): self
{
parent::configureOptions($resolver);
@@ -48,6 +48,9 @@ class RowClassColumn extends AbstractColumn
parent::initialize('$$rowClass', $index, $options, $dataTable); // TODO: Change the autogenerated stub
}
/**
* @return mixed
*/
public function normalize($value)
{
return $value;

View File

@@ -33,7 +33,7 @@ class SIUnitNumberColumn extends AbstractColumn
$this->formatter = $formatter;
}
public function configureOptions(OptionsResolver $resolver)
public function configureOptions(OptionsResolver $resolver): self
{
parent::configureOptions($resolver);
@@ -43,13 +43,13 @@ class SIUnitNumberColumn extends AbstractColumn
return $this;
}
public function normalize($value)
public function normalize($value): string
{
//Ignore null values
if ($value === null) {
return '';
}
return $this->formatter->format((float) $value, $this->options['unit'], $this->options['precision']);
return htmlspecialchars($this->formatter->format((float) $value, $this->options['unit'], $this->options['precision']));
}
}

View File

@@ -28,7 +28,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
*/
class SelectColumn extends AbstractColumn
{
public function configureOptions(OptionsResolver $resolver)
public function configureOptions(OptionsResolver $resolver): self
{
parent::configureOptions($resolver);
@@ -43,12 +43,15 @@ class SelectColumn extends AbstractColumn
return $this;
}
/**
* @return mixed
*/
public function normalize($value)
{
return $value;
}
public function render($value, $context)
public function render($value, $context): string
{
//Return empty string, as it this column is filled by datatables on client side
return '';

View File

@@ -21,7 +21,6 @@
namespace App\DataTables\Filters\Constraints;
use App\DataTables\Filters\FilterInterface;
use Doctrine\ORM\QueryBuilder;
abstract class AbstractConstraint implements FilterInterface
{

View File

@@ -20,7 +20,6 @@
namespace App\DataTables\Filters\Constraints;
use App\DataTables\Filters\FilterInterface;
use Doctrine\ORM\QueryBuilder;
class BooleanConstraint extends AbstractConstraint

View File

@@ -20,7 +20,6 @@
namespace App\DataTables\Filters\Constraints;
use Doctrine\DBAL\ParameterType;
use Doctrine\ORM\QueryBuilder;
trait FilterTrait
@@ -51,7 +50,7 @@ trait FilterTrait
protected function generateParameterIdentifier(string $property): string
{
//Replace all special characters with underscores
$property = preg_replace('/[^a-zA-Z0-9_]/', '_', $property);
$property = preg_replace('/\W/', '_', $property);
//Add a random number to the end of the property name for uniqueness
return $property . '_' . uniqid("", false);
}

View File

@@ -98,7 +98,7 @@ class InstanceOfConstraint extends AbstractConstraint
if ($this->operator === 'ANY' || $this->operator === 'NONE') {
foreach($this->value as $value) {
//We cannnot use an paramater here, as this is the only way to pass the FCQN to the query (via binded params, we would need to use ClassMetaData). See: https://github.com/doctrine/orm/issues/4462
//We can not use a parameter here, as this is the only way to pass the FCQN to the query (via binded params, we would need to use ClassMetaData). See: https://github.com/doctrine/orm/issues/4462
$expressions[] = ($queryBuilder->expr()->isInstanceOf($this->property, $value));
}

View File

@@ -20,7 +20,6 @@
namespace App\DataTables\Filters\Constraints;
use Doctrine\DBAL\ParameterType;
use Doctrine\ORM\QueryBuilder;
use RuntimeException;
@@ -42,7 +41,7 @@ class NumberConstraint extends AbstractConstraint
protected $value2;
/**
* @var string The operator to use
* @var string|null The operator to use
*/
protected ?string $operator;

View File

@@ -0,0 +1,47 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\DataTables\Filters\Constraints\Part;
use App\DataTables\Filters\Constraints\BooleanConstraint;
use Doctrine\ORM\QueryBuilder;
class LessThanDesiredConstraint extends BooleanConstraint
{
public function __construct(string $property = null, string $identifier = null, ?bool $default_value = null)
{
parent::__construct($property ?? 'amountSum', $identifier, $default_value);
}
public function apply(QueryBuilder $queryBuilder): void
{
//Do not apply a filter if value is null (filter is set to ignore)
if(!$this->isEnabled()) {
return;
}
//If value is true, we want to filter for parts with stock < desired stock
if ($this->value) {
$queryBuilder->andHaving('amountSum < part.minamount');
} else {
$queryBuilder->andHaving('amountSum >= part.minamount');
}
}
}

Some files were not shown because too many files have changed in this diff Show More