Merge branch 'Part-DB:master' into master

This commit is contained in:
d-buchmann 2024-02-26 11:29:07 +01:00 committed by GitHub
commit 42928947cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
70 changed files with 12637 additions and 2042 deletions

9
.env
View file

@ -168,6 +168,15 @@ PROVIDER_MOUSER_SEARCH_LIMIT=50
# Used when searching for keywords in the language specified when you signed up for Search API. # Used when searching for keywords in the language specified when you signed up for Search API.
PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE='true' PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE='true'
# LCSC Provider:
# LCSC does not provide an offical API, so this used the API LCSC uses to render their webshop.
# LCSC did not intended the use of this API and it could break any time, so use it at your own risk.
# We dont require an API key for LCSC, just set this to 1 to enable LCSC support
PROVIDER_LCSC_ENABLED=0
# The currency to get prices in (e.g. EUR, USD, etc.)
PROVIDER_LCSC_CURRENCY=EUR
################################################################################## ##################################################################################
# EDA integration related settings # EDA integration related settings
################################################################################## ##################################################################################

View file

@ -117,7 +117,7 @@ jobs:
run: ./bin/phpunit --coverage-clover=coverage.xml run: ./bin/phpunit --coverage-clover=coverage.xml
- name: Upload coverage - name: Upload coverage
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v4
with: with:
env_vars: PHP_VERSION,DB_TYPE env_vars: PHP_VERSION,DB_TYPE
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}

View file

@ -30,7 +30,7 @@ If you want to test Part-DB without installing it, you can use [this](https://de
You can log in with username: *user* and password: *user*. You can log in with username: *user* and password: *user*.
Every change to the master branch gets automatically deployed, so it represents the current development progress and is Every change to the master branch gets automatically deployed, so it represents the current development progress and is
maybe not completely stable. Please mind, that the free Heroku instance is used, so it can take some time when loading may not completely stable. Please mind, that the free Heroku instance is used, so it can take some time when loading
the page the page
for the first time. for the first time.
@ -39,35 +39,35 @@ for the first time.
## Features ## Features
* Inventory management of your electronic parts. Each part can be assigned to a category, footprint, manufacturer * Inventory management of your electronic parts. Each part can be assigned to a category, footprint, manufacturer,
and multiple store locations and price information. Parts can be grouped using tags. You can associate various files and multiple store locations and price information. Parts can be grouped using tags. You can associate various files
like datasheets or pictures with the parts. like datasheets or pictures with the parts.
* Multi-Language support (currently German, English, Russian, Japanese and French (experimental)) * Multi-language support (currently German, English, Russian, Japanese, French, Czech, Danish, and Chinese)
* Barcodes/Labels generator for parts and storage locations, scan barcodes via webcam using the builtin barcode scanner * 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. * 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. Two-factor authentication is supported (Google Authenticator and Webauthn/U2F keys) and can be enforced for groups.
Password reset via email can be setup. Password reset via email can be set up.
* Optional support for single sign-on (SSO) via SAML (using an intermediate service * 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) 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. * Import/Export system for parts and data structure. 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 * 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 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 * Event log: Track what changes happen to your inventory, track which user does what. Revert your parts to older
versions. versions.
* Responsive design: You can use Part-DB on your PC, your tablet and your smartphone using the same interface. * Responsive design: You can use Part-DB on your PC, your tablet, and your smartphone using the same interface.
* MySQL and SQLite supported as database backends * MySQL and SQLite are supported as database backends
* Support for rich text descriptions and comments in parts * Support for rich text descriptions and comments in parts
* Support for multiple currencies and automatic update of exchange rates supported * 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) * Powerful search and filter function, including parametric search (search for parts according to some specifications)
* Automatic thumbnail generation for pictures * Automatic thumbnail generation for pictures
* Use cloud providers (like Octopart, Digikey, farnell or TME) to automatically get part information, datasheets and * Use cloud providers (like Octopart, Digikey, Farnell, LCSC or TME) to automatically get part information, datasheets, and
prices for parts prices for parts
* API to access Part-DB from other applications/scripts * API to access Part-DB from other applications/scripts
* [Integration with KiCad](https://docs.part-db.de/usage/eda_integration.html): Use Part-DB as central datasource for your * [Integration with KiCad](https://docs.part-db.de/usage/eda_integration.html): Use Part-DB as the central datasource for your
KiCad and see available parts from Part-DB directly inside KiCad. KiCad and see available parts from Part-DB directly inside KiCad.
With these features Part-DB is useful to hobbyists, who want to keep track of their private electronic parts inventory, 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. or maker spaces, where many users should have (controlled) access to the shared inventory.
Part-DB is also used by small companies and universities for managing their inventory. Part-DB is also used by small companies and universities for managing their inventory.
@ -78,7 +78,7 @@ Part-DB is also used by small companies and universities for managing their inve
this includes a minimum PHP version of **PHP 8.1** this includes a minimum PHP version of **PHP 8.1**
* A **MySQL** (at least 5.7) /**MariaDB** (at least 10.2.2) database server if you do not want to use SQLite. * A **MySQL** (at least 5.7) /**MariaDB** (at least 10.2.2) database server if you do not want to use SQLite.
* Shell access to your server is highly suggested! * Shell access to your server is highly suggested!
* For building the client side assets **yarn** and **nodejs** (>= 18.0) is needed. * For building the client-side assets **yarn** and **nodejs** (>= 18.0) is needed.
## Installation ## Installation
@ -88,8 +88,8 @@ read [this](https://docs.part-db.de/upgrade_legacy.html) first.
*Hint:* A docker image is available under [jbtronics/part-db1](https://hub.docker.com/r/jbtronics/part-db1). How to set *Hint:* A docker image is available under [jbtronics/part-db1](https://hub.docker.com/r/jbtronics/part-db1). How to set
up Part-DB via docker is described [here](https://docs.part-db.de/installation/installation_docker.html). up Part-DB via docker is described [here](https://docs.part-db.de/installation/installation_docker.html).
**Below you find some very rough outline of the installation process, see [here](https://docs.part-db.de/installation/) **Below you find a very rough outline of the installation process, see [here](https://docs.part-db.de/installation/)
for a detailed guide how to install Part-DB.** for a detailed guide on how to install Part-DB.**
1. Copy or clone this repository into a folder on your server. 1. Copy or clone this repository into a folder on your server.
2. Configure your webserver to serve from the `public/` folder. 2. Configure your webserver to serve from the `public/` folder.
@ -107,13 +107,13 @@ for a detailed guide how to install Part-DB.**
6. _Optional_ (speeds up first load): Warmup cache: `php bin/console cache:warmup` 6. _Optional_ (speeds up first load): Warmup cache: `php bin/console cache:warmup`
7. Upgrade database to new scheme (or create it, when it was empty): `php bin/console doctrine:migrations:migrate` and 7. Upgrade database to new scheme (or create it, when it was empty): `php bin/console doctrine:migrations:migrate` and
follow the instructions given. During the process the password for the admin is user is shown. Copy it. **Caution**: follow the instructions given. During the process the password for the admin is user is shown. Copy it. **Caution**:
This steps tamper with your database and could potentially destroy it. So make sure to make a backup of your These steps tamper with your database and could potentially destroy it. So make sure to make a backup of your
database. database.
8. You can configure Part-DB via `config/parameters.yaml`. You should check if settings match your expectations, after 8. You can configure Part-DB via `config/parameters.yaml`. You should check if settings match your expectations after
you installed/upgraded Part-DB. Check if `partdb.default_currency` matches your mainly used currency (this can not be you installed/upgraded Part-DB. Check if `partdb.default_currency` matches your mainly used currency (this can not be
changed after creating price information). changed after creating price information).
Run `php bin/console cache:clear` when you changed something. Run `php bin/console cache:clear` when you change something.
9. Access Part-DB in your browser (under the URL you put it) and login with user *admin*. Password is the one outputted 9. Access Part-DB in your browser (under the URL you put it) and log in with user *admin*. Password is the one outputted
during DB setup. during DB setup.
If you can not remember the password, set a new one with `php bin/console app:set-password admin`. You can create If you can not remember the password, set a new one with `php bin/console app:set-password admin`. You can create
new users with the admin user and start using Part-DB. new users with the admin user and start using Part-DB.
@ -122,23 +122,23 @@ When you want to upgrade to a newer version, then just copy the new files into t
and repeat the steps 4. to 7. and repeat the steps 4. to 7.
Normally a random password is generated when the admin user is created during initial database creation, Normally a random password is generated when the admin user is created during initial database creation,
however you can set the initial admin password, by setting the `INITIAL_ADMIN_PW` env var. however, you can set the initial admin password, by setting the `INITIAL_ADMIN_PW` env var.
You can configure Part-DB to your needs by changing environment variables in the `.env.local` file. You can configure Part-DB to your needs by changing environment variables in the `.env.local` file.
See [here](https://docs.part-db.de/configuration.html) for more information. See [here](https://docs.part-db.de/configuration.html) for more information.
### Reverse proxy ### Reverse proxy
If you are using a reverse proxy, you have to ensure that the proxies sets the `X-Forwarded-*` headers correctly, or you If you are using a reverse proxy, you have to ensure that the proxies set the `X-Forwarded-*` headers correctly, or you
will get HTTP/HTTPS mixup and wrong hostnames. will get HTTP/HTTPS mixup and wrong hostnames.
If the reverse proxy is on a different server (or it cannot access Part-DB via localhost) you have to set If the reverse proxy is on a different server (or it cannot access Part-DB via localhost) you have to set
the `TRUSTED_PROXIES` env variable to match your reverse proxies IP-address (or IP block). You can do this in the `TRUSTED_PROXIES` env variable to match your reverse proxy's IP address (or IP block). You can do this in
your `.env.local` or (when using docker) in your `docker-compose.yml` file. your `.env.local` or (when using docker) in your `docker-compose.yml` file.
## Donate for development ## Donate for development
If you want to donate to the Part-DB developer, see the sponsor button in the top bar (next to the repo name). If you want to donate to the Part-DB developer, see the sponsor button in the top bar (next to the repo name).
There you will find various methods to support development on a monthly or a one time base. There you will find various methods to support development on a monthly or a one-time base.
## Built with ## Built with

View file

@ -1 +1 @@
1.10.6 1.11.0-dev

View file

@ -145,6 +145,12 @@ export default class extends Controller {
//Fix height of the length selector //Fix height of the length selector
promise.then((dt) => { promise.then((dt) => {
//Draw the rows to make sure the correct status text is displayed ("No matching records found" instead of "Loading...")
if (dt.data().length === 0) {
dt.rows().draw()
}
//Find all length selectors (select with name dt_length), which are inside a label //Find all length selectors (select with name dt_length), which are inside a label
const lengthSelectors = document.querySelectorAll('label select[name="dt_length"]'); const lengthSelectors = document.querySelectorAll('label select[name="dt_length"]');
//And remove the surrounding label, while keeping the select with all event handlers //And remove the surrounding label, while keeping the select with all event handlers

View file

@ -84,7 +84,7 @@ th.select-checkbox {
* Datatables definitions/overrides * Datatables definitions/overrides
********************************************************************/ ********************************************************************/
.dataTables_length { .dt-length {
display: inline-flex; display: inline-flex;
} }
@ -94,6 +94,16 @@ table.dataTable tr.selected td.select-checkbox:after
margin-top: -20px !important; margin-top: -20px !important;
} }
/** Show pagination right aligned */
.dt-paging .pagination {
justify-content: flex-end;
}
/** Fix table row coloring */
table.table.dataTable > :not(caption) > * > * {
background-color: var(--bs-table-bg);
}
/****************************************************** /******************************************************
Classes for Datatables export Classes for Datatables export
@ -132,6 +142,7 @@ table.dataTable > thead > tr > th.select-checkbox:before {
table.dataTable > tbody > tr > td.select-checkbox:before, table.dataTable > tbody > tr > th.select-checkbox:before { table.dataTable > tbody > tr > td.select-checkbox:before, table.dataTable > tbody > tr > th.select-checkbox:before {
border: 2px solid var(--bs-tertiary-color) !important; border: 2px solid var(--bs-tertiary-color) !important;
font-weight: bold;
} }
table.dataTable > tbody > tr > td.select-checkbox:before, table.dataTable > tbody > tr > td.select-checkbox:after, table.dataTable > tbody > tr > th.select-checkbox:before, table.dataTable > tbody > tr > th.select-checkbox:after { table.dataTable > tbody > tr > td.select-checkbox:before, table.dataTable > tbody > tr > td.select-checkbox:after, table.dataTable > tbody > tr > th.select-checkbox:before, table.dataTable > tbody > tr > th.select-checkbox:after {
@ -142,6 +153,7 @@ table.dataTable > tbody > tr > td.select-checkbox:before, table.dataTable > tbod
table.dataTable > thead > tr.selected > th.select-checkbox:after { table.dataTable > thead > tr.selected > th.select-checkbox:after {
content: "✓"; content: "✓";
font-size: 20px; font-size: 20px;
font-weight: bold;
margin-top: -20px; margin-top: -20px;
margin-left: -6px; margin-left: -6px;
text-align: center; text-align: center;

1656
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -11,7 +11,7 @@ parameters:
partdb.banner: '%env(trim:string:BANNER)%' # The info text shown in the homepage, if empty config/banner.md is used partdb.banner: '%env(trim:string:BANNER)%' # The info text shown in the homepage, if empty config/banner.md is used
partdb.default_currency: '%env(string:BASE_CURRENCY)%' # The currency that is used inside the DB (and is assumed when no currency is set). This can not be changed later, so be sure to set it the currency used in your country partdb.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.global_theme: '' # The theme to use globally (see public/build/themes/ for choices, use name without .css). Set to '' for default bootstrap theme
partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da'] # The languages that are shown in user drop down menu partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh'] # 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.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 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

View file

@ -307,6 +307,11 @@ services:
$options: '%env(string:PROVIDER_MOUSER_SEARCH_OPTION)%' $options: '%env(string:PROVIDER_MOUSER_SEARCH_OPTION)%'
$search_limit: '%env(int:PROVIDER_MOUSER_SEARCH_LIMIT)%' $search_limit: '%env(int:PROVIDER_MOUSER_SEARCH_LIMIT)%'
App\Services\InfoProviderSystem\Providers\LCSCProvider:
arguments:
$enabled: '%env(bool:PROVIDER_LCSC_ENABLED)%'
$currency: '%env(string:PROVIDER_LCSC_CURRENCY)%'
#################################################################################################################### ####################################################################################################################
# API system # API system
#################################################################################################################### ####################################################################################################################

View file

@ -11,15 +11,14 @@ To use API endpoints, the external application has to authenticate itself, so th
the data and which permissions the data and which permissions
the application should have during the access. Authentication is always bound to a specific user, so the external the application should have during the access. Authentication is always bound to a specific user, so the external
applications is acting on behalf of a applications is acting on behalf of a
specific user. This user limits the permissions of the application, so that it can only access data, which the user is specific user. This user limits the permissions of the application so that it can only access data, which the user is
allowed to access. allowed to access.
The only method currently available for authentication is to use API tokens: The only method currently available for authentication is to use API tokens:
## API tokens ## API tokens
An API token is a long alphanumeric string, which is bound to a specific user and can be used to authenticate as this An API token is a long alphanumeric string, which is bound to a specific user and can be used to authenticate as this user when accessing the API.
user, when accessing the API.
The API token is passed via the `Authorization` HTTP header during the API request, like the The API token is passed via the `Authorization` HTTP header during the API request, like the
following: `Authorization: Bearer tcp_sdjfks....`. following: `Authorization: Bearer tcp_sdjfks....`.
@ -36,7 +35,7 @@ not access the API anymore with this token.
### Token permissions and scopes ### Token permissions and scopes
API tokens are ultimately limited by the permissions of the user, which belongs to the token. That means that the token API tokens are ultimately limited by the permissions of the user, which belongs to the token. That means that the token
can only access data, which the user is allowed to access, no matter the token permissions. can only access data, that the user is allowed to access, no matter the token permissions.
But you can further limit the permissions of a token by choosing a specific scope for the token. The scope defines which But you can further limit the permissions of a token by choosing a specific scope for the token. The scope defines which
subset of permissions the token has, which can be less than the permissions of the user. For example, you can have a subset of permissions the token has, which can be less than the permissions of the user. For example, you can have a
@ -50,15 +49,15 @@ change anything in the database.
> Only use the full or admin scope, if you really need it, as they could potentially be used to do a lot of damage to > Only use the full or admin scope, if you really need it, as they could potentially be used to do a lot of damage to
> your Part-DB instance. > your Part-DB instance.
Following token scopes are available: The following token scopes are available:
* **Read-Only**: The token can only read non-sensitive data (like parts, but no users or groups) from the API and can * **Read-Only**: The token can only read non-sensitive data (like parts, but no users or groups) from the API and can
not change anything. not change anything.
* **Edit**: The token can read and write non-sensitive data via the API. This includes creating, updating and deleting * **Edit**: The token can read and write non-sensitive data via the API. This includes creating, updating and deleting
data. This should be enough for most applications. data. This should be enough for most applications.
* **Admin**: The token can read and write all data via the API, including sensitive data like users and groups. This * **Admin**: The token can read and write all data via the API, including sensitive data like users and groups. This
should only be used for trusted applications, which need to access sensitive data, and perform administrative actions. should only be used for trusted applications, which need to access sensitive data and perform administrative actions.
* **Full**: The token can do anything the user can do, including changing the users password and create new tokens. This * **Full**: The token can do anything the user can do, including changing the user's password and creating new tokens. This
should only be used for highly trusted applications!! should only be used for highly trusted applications!!
Please note, that in early versions of the API, there might be no endpoints yet, to really perform the actions, which Please note, that in early versions of the API, there might be no endpoints yet, to really perform the actions, which

View file

@ -29,15 +29,14 @@ features and how to use them.
The API is available under the `/api` path, but not reachable without proper permissions. The API is available under the `/api` path, but not reachable without proper permissions.
You have to give the users, which should be able to access the API the proper permissions (Miscellaneous -> API). You have to give the users, which should be able to access the API the proper permissions (Miscellaneous -> API).
Please note that there are two relevant permissions, the first one allows users to access the `/api/` path at all and Please note that there are two relevant permissions, the first one allows users to access the `/api/` path at all and show the documentation,
showing the documentation, and the second one allows them to create API tokens which are needed for the authentication of external applications.
and the second one allows them to create API tokens which is needed for authentication of external applications.
## Authentication ## Authentication
To use API endpoints, the external application has to authenticate itself, so that Part-DB knows which user is accessing To use API endpoints, the external application has to authenticate itself, so that Part-DB knows which user is accessing
the data and the data and
which permissions the application should have. Basically this is done by creating an API token for a user and then which permissions the application should have. Basically, this is done by creating an API token for a user and then
passing it on every request passing it on every request
with the `Authorization` header as bearer token, so you add a header `Authorization: Bearer <your token>`. with the `Authorization` header as bearer token, so you add a header `Authorization: Bearer <your token>`.
@ -46,11 +45,10 @@ See [Authentication chapter]({% link api/authentication.md %}) for more details.
## API endpoints ## API endpoints
The API is split into different endpoints, which are reachable under the `/api/` path of your Part-DB instance ( The API is split into different endpoints, which are reachable under the `/api/` path of your Part-DB instance (
so `https://your-part-db.local/api/`). e.g. `https://your-part-db.local/api/`).
There are various endpoints for each entity type (like `part`, `manufacturer`, etc.), which allow you to read and write There are various endpoints for each entity type (like `part`, `manufacturer`, etc.), which allow you to read and write data, and some special endpoints like `search` or `statistics`.
data and some special endpoints like `search` or `statistics`.
For example all API endpoints for managing categories are available under `/api/categories/`. Depending on the exact For example, all API endpoints for managing categories are available under `/api/categories/`. Depending on the exact
path and the HTTP method used, you can read, create, update or delete categories. path and the HTTP method used, you can read, create, update or delete categories.
For most entities, there are endpoints like this: For most entities, there are endpoints like this:
@ -66,29 +64,28 @@ For most entities, there are endpoints like this:
A full (interactive) list of endpoints can be displayed when visiting the `/api/` path in your browser, when you are A full (interactive) list of endpoints can be displayed when visiting the `/api/` path in your browser, when you are
logged in with a user, which is allowed to access the API. logged in with a user, which is allowed to access the API.
There is also a link to this page, on the user settings page in the API token section. There is also a link to this page, on the user settings page in the API token section.
This documentation also list all available fields for each entity type and the allowed operations. This documentation also lists all available fields for each entity type and the allowed operations.
## Formats ## Formats
The API supports different formats for the request and response data, which you can control via the `Accept` The API supports different formats for the request and response data, which you can control via the `Accept`
and `Content-Type` headers. and `Content-Type` headers.
You should use [JSON-LD](https://json-ld.org/) as format, which is basically JSON with some additional metadata, which You should use [JSON-LD](https://json-ld.org/) as format, which is basically JSON with some additional metadata, which
allows allows you to describe the data in a more structured way and also allows to link between different entities. You can achieve this
to describe the data in a more structured way and also allows to link between different entities. You can achieve this
by setting `Accept: application/ld+json` header to the API requests. by setting `Accept: application/ld+json` header to the API requests.
To get plain JSON without any metadata or links, use the `Accept: application/json` header. To get plain JSON without any metadata or links, use the `Accept: application/json` header.
Without an `Accept` header (e.g. when you call the endpoint in a browser), the API will return an HTML page with the Without an `Accept` header (e.g. when you call the endpoint in a browser), the API will return an HTML page with the
documentation, so be sure to include the desired `Accept` header in your API requests. documentation, so be sure to include the desired `Accept` header in your API requests.
If you can not control the `Accept` header, you can add an `.json` or `.jsonld` suffix to the URL to enforce a JSON or If you can not control the `Accept` header, you can add a `.json` or `.jsonld` suffix to the URL to enforce a JSON or
JSON-LD response (e.g. `/api/parts.jsonld`). JSON-LD response (e.g. `/api/parts.jsonld`).
## OpenAPI schema ## OpenAPI schema
Part-DB provides a [OpenAPI](https://swagger.io/specification/) (formally Swagger) schema for the API Part-DB provides a [OpenAPI](https://swagger.io/specification/) (formally Swagger) schema for the API
under `/api/docs.json` (so `https://your-part-db.local/api/docs.json`). under `/api/docs.json` (so `https://your-part-db.local/api/docs.json`).
This schema is a machine-readable description of the API, which can be imported in software to test the API or even This schema is a machine-readable description of the API, which can be imported into software to test the API or even
automatically generate client libraries for the API. automatically generate client libraries for the API.
API generators which can generate a client library for the API from the schema are available for many programming API generators which can generate a client library for the API from the schema are available for many programming
@ -120,14 +117,10 @@ See [API Platform docs](https://api-platform.com/docs/core/pagination) for more
## Filtering results / Searching ## Filtering results / Searching
When retrieving a list of entities, you can restrict the results by various filters. Almost all entities have a search When retrieving a list of entities, you can restrict the results by various filters. Almost all entities have a search
filter, filter, which allows you to only include entities, which (text) fields match the given search term: For example, if you only want
which allows you to only include entities, which (text) fields match the given search term: For example if you only want to get parts, with the Name "BC547", you can use `/api/parts.jsonld?name=BC547`. You can use `%` as a wildcard for multiple
to characters in the search term (Be sure to properly encode the search term, if you use special characters). For example, if you want
get parts, with the Name "BC547", you can use `/api/parts.jsonld?name=BC547`. You can use `%` as wildcard for multiple to get all parts, whose name starts with "BC", you can use `/api/parts.jsonld?name=BC%25` (the `%25` is the url encoded version of `%`).
characters
in the search term (Be sure to properly encode the search term, if you use special characters). For example if you want
to get all parts,
whose name starts with "BC", you can use `/api/parts.jsonld?name=BC%25` (the `%25` is the url encoded version of `%`).
There are other filters available for some entities, allowing you to search on other fields, or restricting the results There are other filters available for some entities, allowing you to search on other fields, or restricting the results
by numeric values or dates. See the endpoint documentation for the available filters. by numeric values or dates. See the endpoint documentation for the available filters.
@ -136,8 +129,8 @@ by numeric values or dates. See the endpoint documentation for the available fil
To get all parts with a certain category, manufacturer, etc. you can use the `category`, `manufacturer`, etc. query To get all parts with a certain category, manufacturer, etc. you can use the `category`, `manufacturer`, etc. query
parameters of the `/api/parts` endpoint. parameters of the `/api/parts` endpoint.
They are so-called entity filters and accept a comma separated list of IDs of the entities you want to filter by. They are so-called entity filters and accept a comma-separated list of IDs of the entities you want to filter by.
For example if you want to get all parts with the category "Resistor" (Category ID 1) and "Capacitor" (Category ID 2), For example, if you want to get all parts with the category "Resistor" (Category ID 1) and "Capacitor" (Category ID 2),
you can use `/api/parts.jsonld?category=1,2`. you can use `/api/parts.jsonld?category=1,2`.
Suffix an id with `+` to suffix, to include all direct children categories of the given category. Use the `++` suffix to Suffix an id with `+` to suffix, to include all direct children categories of the given category. Use the `++` suffix to
@ -150,7 +143,7 @@ See the endpoint documentation for the available entity filters.
## Ordering results ## Ordering results
When retrieving a list of entities, you can order the results by various fields using the `order` query parameter. When retrieving a list of entities, you can order the results by various fields using the `order` query parameter.
For example if you want to get all parts ordered by their name, you can use `/api/parts/?order[name]=asc`. You can use For example, if you want to get all parts ordered by their name, you can use `/api/parts/?order[name]=asc`. You can use
this parameter multiple times to order by multiple fields. this parameter multiple times to order by multiple fields.
See the endpoint documentation for the available fields to order by. See the endpoint documentation for the available fields to order by.
@ -161,12 +154,12 @@ Sometimes you only want to get a subset of the properties of an entity, for exam
part, but not all the other properties. part, but not all the other properties.
You can achieve this using the `properties[]` query parameter with the name of the field you want to get. You can use You can achieve this using the `properties[]` query parameter with the name of the field you want to get. You can use
this parameter multiple times to get multiple fields. this parameter multiple times to get multiple fields.
For example if you only want to get the name and the description of a part, you can For example, if you only want to get the name and the description of a part, you can
use `/api/parts/123?properties[]=name&properties[]=description`. use `/api/parts/123?properties[]=name&properties[]=description`.
It is also possible to use this filters on list endpoints (get collection), to only get a subset of the properties of It is also possible to use these filters on list endpoints (get collection), to only get a subset of the properties of
all entities in the collection. all entities in the collection.
See [API Platform docs](https://api-platform.com/docs/core/filters/#property-filter) for more infos. See [API Platform docs](https://api-platform.com/docs/core/filters/#property-filter) for more info.
## Change comment ## Change comment
@ -174,7 +167,7 @@ Similar to the changes using Part-DB web interface, you can add a change comment
which will be which will be
visible in the log of the entity. visible in the log of the entity.
You can pass the text for this via the `_comment` query parameter (beware the proper encoding). For You can pass the text for this via the `_comment` query parameter (beware of the proper encoding). For
example `/api/parts/123?_comment=This%20is%20a%20change%20comment`. example `/api/parts/123?_comment=This%20is%20a%20change%20comment`.
## Creating attachments and parameters ## Creating attachments and parameters
@ -182,7 +175,7 @@ example `/api/parts/123?_comment=This%20is%20a%20change%20comment`.
{: .warning } {: .warning }
> The way described below is more a workaround than a proper solution. This might break in future versions of Part-DB! > The way described below is more a workaround than a proper solution. This might break in future versions of Part-DB!
Currently it is not possible to create attachments or parameters via a `POST` operation on the entity endpoint. Currently, it is not possible to create attachments or parameters via a `POST` operation on the entity endpoint.
The workaround for this is to send a patch request to the owning entity endpoint (e.g. parts `/api/parts/123`): The workaround for this is to send a patch request to the owning entity endpoint (e.g. parts `/api/parts/123`):
``` ```

View file

@ -11,55 +11,55 @@ This page explains the different concepts of Part-DB and what their intended use
1. TOC 1. TOC
{:toc} {:toc}
## Part managment ## Part management
### Part ### Part
A part is the central concept of Part-DB. A part represents a single kind (or type) of a thing, like an electronic A part is the central concept of Part-DB. A part represents a single kind (or type) of a thing, like an electronic
component, a device, a book or similar (depending on what you use Part-DB for). A part entity just represents a certain component, a device, a book or similar (depending on what you use Part-DB for). A part entity just represents a certain
type of thing, so if you have 1000 times an BC547 transistor you would create ONE part with the name BC547 and set its type of thing, so if you have 1000 times a BC547 transistor you would create ONE part with the name BC547 and set its
quantity to 1000. The individual quantities (so a single BC547 transistor) of a part, should be indistinguishable from quantity to 1000. The individual quantities (so a single BC547 transistor) of a part, should be indistinguishable from
each other, so that it does not matter which one of your 1000 things of Part you use. each other so that it does not matter which one of your 1000 things of Part you use.
A part entity have many fields, which can be used to describe it better. Most of the fields are optional: A part entity has many fields, which can be used to describe it better. Most of the fields are optional:
* **Name** (Required): The name of the part or how you want to call it. This could be a manufacturer provided name, or a * **Name** (Required): The name of the part or how you want to call it. This could be a manufacturer-provided name, or a
name you thought of your self. The name have to be unique in a single category. name you thought of yourself. The name have to be unique in a single category.
* **Description**: A short (single-line) description of what this part is/does. For longer information you should use * **Description**: A short (single-line) description of what this part is/does. For longer information, you should use
the comment field or the specifications the comment field or the specifications
* **Category** (Required): The category (see there) to which this part belongs to. * **Category** (Required): The category (see there) to which this part belongs to.
* **Tags**: The list of tags this part belong to. Tags can be used to group parts logically (similar to the category), * **Tags**: The list of tags this part belongs to. Tags can be used to group parts logically (similar to the category),
but tags are much less strict and formal (they don't have to be defined forehands) and you can assign multiple tags to but tags are much less strict and formal (they don't have to be defined forehands) and you can assign multiple tags to
a part. When clicking on a tag, a list with all parts which have the same tag, is shown. a part. When clicking on a tag, a list with all parts which have the same tag, is shown.
* **Min Instock**: *Not really implemented yet*. Parts where the total instock is below this value, will show up for * **Min Instock**: *Not really implemented yet*. Parts where the total instock is below this value, will show up for
ordering. ordering.
* **Footprint**: See there. Useful especially for electronic parts, which have one of the common electronic footprints ( * **Footprint**: See there. Useful especially for electronic parts, which have one of the common electronic footprints (
like DIP8, SMD0805 or similar). If a part has no explicit defined preview picture, the preview picture of its like DIP8, SMD0805 or similar). If a part has no explicitly defined preview picture, the preview picture of its
footprint will be shown instead in tables. footprint will be shown instead in tables.
* **Manufacturer**: The manufacturer which has manufactured (not sold) this part. See Manufacturer entity for more info. * **Manufacturer**: The manufacturer which has manufactured (not sold) this part. See Manufacturer entity for more info.
* **Manufacturer part number** (MPN): If you have used your own name for a part, you can put the part number the * **Manufacturer part number** (MPN): If you have used your own name for a part, you can put the part number the
manufacturer uses in this field, so that you can find a part also under its manufacturer number. manufacturer uses in this field so that you can find a part also under its manufacturer number.
* **Link to product page**: If you want to link to the manufacturer website of a part, and it is not possible to * **Link to product page**: If you want to link to the manufacturer website of a part, and it is not possible to
determine it automatically from the part name, set in the manufacturer entity (or no manufacturer is set), you can set determine it automatically from the part name, set in the manufacturer entity (or no manufacturer is set), you can set
the link here for each part individually. the link here for each part individually.
* **Manufacturing Status**: The manufacturing status of this part, meaning the information about where the part is in * **Manufacturing Status**: The manufacturing status of this part, meaning the information about where the part is in
its manufacturing lifecycle. its manufacturing lifecycle.
* **Needs review**: If you think parts information maybe are inaccurate or incomplete and needs some later * **Needs review**: If you think parts information may be inaccurate or incomplete and needs some later
review/checking, you can set this flag. A part with this flag is marked, so that users know the information are not review/checking, you can set this flag. A part with this flag is marked, so that users know the information is not
completely trustworthy. completely trustworthy.
* **Favorite**: Parts with this flag are highlighted in parts lists * **Favorite**: Parts with this flag are highlighted in parts lists
* **Mass**: The mass of a single piece of this part (so of a single transistor). Given in grams. * **Mass**: The mass of a single piece of this part (so of a single transistor). Given in grams.
* **Internal Part number** (IPN): Each part is automatically assigned a numerical ID which identifies a part in the * **Internal Part number** (IPN): Each part is automatically assigned a numerical ID that identifies a part in the
database. This ID depends on when a part was created and can not be changed. If you want to assign your own unique database. This ID depends on when a part was created and can not be changed. If you want to assign your own unique
identifiers, or sync parts identifiers with the identifiers of another database you can use this field. identifiers, or sync parts identifiers with the identifiers of another database you can use this field.
### Stock / Part lot ### Stock / Part lot
A part can have many stock at multiple different locations. This is represented by part lots / stocks, which consists A part can have many stocks at multiple different locations. This is represented by part lots/stocks, which consists
basically of a storage location (so where are the parts of this lot are stored) and an amount (how many parts are there). basically of a storage location (so where the parts of this lot are stored) and an amount (how many parts are there).
### Purchase Information ### Purchase Information
The purchase information describe where the part can be bought (at which vendors) and to which prices. The purchase information describes where the part can be bought (at which vendors) and at which prices.
The first part (the order information) describes at which supplier the part can be bought and which is the name of the The first part (the order information) describes at which supplier the part can be bought and which is the name of the
part under which you can order the part there. part under which you can order the part there.
An order information can contain multiple price information, which describes the prices for the part at the supplier An order information can contain multiple price information, which describes the prices for the part at the supplier
@ -67,13 +67,13 @@ including bulk discount, etc.
### Parameters ### Parameters
Parameters represents various specifications / parameters of a part, like the maximum current of a diode, etc. The Parameters represent various specifications/parameters of a part, like the maximum current of a diode, etc. The
advantage of using parameters instead of just putting the data in the comment field or so, is that you can filter for advantage of using parameters instead of just putting the data in the comment field or so, is that you can filter for
parameters values (including ranges and more) later on. parameter's values (including ranges and more) later on.
Parameters describe can describe numeric values and/or text values for which they can be filtered. This basically allows Parameters can describe numeric values and/or text values for which they can be filtered. This allows
you to define custom fields on a part. you to define custom fields on a part.
Using the group field a parameter allows you to group parameters together in the info page later (all parameters with Using the group field as a parameter allows you to group parameters together on the info page later (all parameters with
the same group value will be shown under the same group title). the same group value will be shown under the same group title).
## Core data ## Core data
@ -99,17 +99,17 @@ possible category tree could look like this:
### Supplier ### Supplier
A Supplier is a vendor / distributor where you can buy/order parts. Price information of parts are associated with a A Supplier is a vendor/distributor where you can buy/order parts. Price information of parts is associated with a
supplier. supplier.
### Manufacturer ### Manufacturer
A manufacturer represents the company that manufacturer / build various parts (not necessary sell them). If the A manufacturer represents the company that manufacturers/builds various parts (not necessarily sell them). If the
manufacturer also sell the parts, you have to create a supplier for that. manufacturer also sells the parts, you have to create a supplier for that.
### Storage location ### Storage location
A storage location represents a place where parts can be stored. This could be a box, a shelf or other things (like the A storage location represents a place where parts can be stored. This could be a box, a shelf, or other things (like the
SMD feeder of a machine or so). SMD feeder of a machine or so).
Storage locations are hierarchical to represent storage locations contained in each other. Storage locations are hierarchical to represent storage locations contained in each other.
@ -129,12 +129,12 @@ Storage locations should be defined down to the smallest possible location, to m
### Footprint ### Footprint
In electronics many components have one of the common components cases / footprints. The footprint entity describes such In electronics, many components have one of the common components cases/footprints. The footprint entity describes such
common footprints, which can be assigned to parts. common footprints, which can be assigned to parts.
You can assign an image (and an 3D model) as an attachment to a footprint, which will be used as preview for parts with You can assign an image (and a 3D model) as an attachment to a footprint, which will be used as preview for parts with
this footprint, even if the parts do not have an explicitly assigned preview image. this footprint, even if the parts do not have an explicitly assigned preview image.
Footprints are a hierarchically which allows you to build logical sorted trees. An example tree could look like this: Footprints are hierarchically which allows you to build logically sorted trees. An example tree could look like this:
* Through-Hole components * Through-Hole components
* DIP * DIP
@ -153,17 +153,17 @@ Footprints are a hierarchically which allows you to build logical sorted trees.
### Measurement Unit ### Measurement Unit
By default, part instock is counted in number of individual parts, which is fine for things like electronic components, By default, part instock is counted in number of individual parts, which is fine for things like electronic components,
which exists only in integer quantities. However, if you have things with fractional units like the length of a wire or which exist only in integer quantities. However, if you have things with fractional units like the length of a wire or
the volume of a liquid, you have to define a measurement unit. the volume of a liquid, you have to define a measurement unit.
The measurement unit represents a physical quantity like mass, volume or length. The measurement unit represents a physical quantity like mass, volume, or length.
You can define a short unit for it (like m for Meters, or g for gramms) which will be shown, when a quantity of a part You can define a short unit for it (like m for Meters, or g for grams) which will be shown when a quantity of a part
with this unit is shown. with this unit is shown.
### Currency ### Currency
By default, all prices are set in the base currency configured for the instance (by default euros). If you want to use By default, all prices are set in the base currency configured for the instance (by default euros). If you want to use
multiple currencies together (as e.g. vendors use foreign currencies for their price, and you do not want to update the multiple currencies together (e.g. vendors use foreign currencies for their price, and you do not want to update the
prices for every exchange rate change), you have to define these currencies here. prices for every exchange rate change), you have to define these currencies here.
You can set an exchange rate here in terms of the base currency (or fetch it from the internet if configured). The You can set an exchange rate here in terms of the base currency (or fetch it from the internet if configured). The
@ -173,57 +173,57 @@ exchange rate will be used to show users the prices in their preferred currency.
### Attachment ### Attachment
An attachment is a file that can be associated with another entity (like a Part, Storelocation, User, etc.). This could An attachment is a file that can be associated with another entity (like a Part, location, User, etc.). This could
for example be a datasheet in a Part, the logo of a vendor or some CAD drawing of a footprint. for example be a datasheet in a Part, the logo of a vendor or some CAD drawing of a footprint.
An attachment has an attachment type (see below), which groups the attachments logically (and optionally restricts the An attachment has an attachment type (see below), which groups the attachments logically (and optionally restricts the
allowed file types), a name describing the attachment and a file. The file can either be uploaded to the server and allowed file types), a name describing the attachment and a file. The file can either be uploaded to the server and
stored there, or given as a link to a file on another web path. If configured in the settings, it is also possible that stored there, or given as a link to a file on another web path. If configured in the settings, it is also possible that
the webserver downloads the file from the supplied website and stores it locally on the server. the web server downloads the file from the supplied website and stores it locally on the server.
By default, all uploaded files, are accessible for everyone (even non-logged-in users), if the link is known. If your By default, all uploaded files, are accessible for everyone (even non-logged-in users), if the link is known. If your
Part-DB instance is publicly available, and you want to store private/sensitive files on it, you should mark the Part-DB instance is publicly available, and you want to store private/sensitive files on it, you should mark the
attachment as "Private attachment". Private attachments are only accessible to users, which has the permission to access attachment as "Private attachment". Private attachments are only accessible to users, which has permission to access
private attachments. private attachments.
Please not, that no thumbnails are generated for private attachments, which can have a performance impact. Please note, that no thumbnails are generated for private attachments, which can have a performance impact.
Part-DB ships some preview images for various common footprints like DIP-8 and others, as internal resources. These can Part-DB ships some preview images for various common footprints like DIP-8 and others, as internal resources. These can
be accessed/searched by typing the keyword in the URL field of a part and choosing one of the choices from the dropdown. be accessed/searched by typing the keyword in the URL field of a part and choosing one of the choices from the dropdown.
### Preview image / attachment ### Preview image/attachment
Most entities with attachments allow you to select one of the defined attachments as "Preview image". You can select an Most entities with attachments allow you to select one of the defined attachments as "Preview image". You can select an
image attachment here, that previews the entity, this could be a picture of a Part, the logo of a manufacturer or image attachment here, that previews the entity, this could be a picture of a Part, the logo of a manufacturer or
supplier, the schematic symbol of a category or the image of a footprint. supplier, the schematic symbol of a category or the image of a footprint.
The preview image will be shown in various locations together with the entities name. The preview image will be shown in various locations together with the entity's name.
Please note that as long as the picture is not secret, it should be stored on the Part-DB instance (by upload, or Please note that as long as the picture is not secret, it should be stored on the Part-DB instance (by uploading, or
letting Part-DB download the file) and *not* be marked as a private attachments, so that thumbnails can be generated for letting Part-DB download the file) and *not* be marked as a private attachment, so that thumbnails can be generated for
the picture (which improves performance). the picture (which improves performance).
### Attachment types ### Attachment types
Attachment types define logical groups of attachments. For example, you could define an attachment group "Datasheets" Attachment types define logical groups of attachments. For example, you could define an attachment group "Datasheets"
where all datasheets of Parts, Footprints, etc. belong in, "Pictures" for preview images and more. where all datasheets of Parts, Footprints, etc. belong in, "Pictures" for preview images and more.
You can define file type restrictions, which file types and extensions are allowed for files with that attachment type. You can define file type restrictions, and which file types and extensions are allowed for files with that attachment type.
## User System ## User System
### User ### User
Each person which should be able to use Part-DB (by logging in) is represented by a user entity, which defines things Each person who should be able to use Part-DB (by logging in) is represented by a user entity, which defines things
like access rights, the password, and other things. For security reasons, every person which will use Part-DB should use like access rights, the password, and other things. For security reasons, every person who will use Part-DB should use
its own personal account with a secret password. This allows to track activity of the users via the log. their own personal account with a secret password. This allows to track activity of the users via the log.
There is a special user called `anonymous`, whose access rights are used to determine what a non-logged in user can do. There is a special user called `anonymous`, whose access rights are used to determine what a non-logged-in user can do.
Normally the anonymous user should be the most restricted user. Normally the anonymous user should be the most restricted user.
For simplification of access management users can be assigned to groups. For simplification of access management users can be assigned to groups.
### Group ### Group
A group is entity, to which users can be assigned to. This can be used to logically group users by for example A group is an entity, to which users can be assigned to. This can be used to logically group users by for example
organisational structures and to simplify permissions management, as you can define groups with access rights for common organizational structures and to simplify permissions management, as you can define groups with access rights for common
use cases and then just assign users to them, without the need to change every permission on the users individually. use cases and then just assign users to them, without the need to change every permission on the users individually.
## Labels ## Labels
@ -231,9 +231,9 @@ use cases and then just assign users to them, without the need to change every p
### Label profiles ### Label profiles
A label profile represents a template for a label (for a storage location, a part or part lot). It consists of a size, a A label profile represents a template for a label (for a storage location, a part or part lot). It consists of a size, a
barcode type and the content. There are various placeholders which can be inserted in the text content and which will be barcode type and the content. There are various placeholders that can be inserted in the text content and which will be
used replaced with data for the actual thing. replaced with data for the actual thing.
You do not have to define a label profile to generate labels (you can just set the settings on the fly in the label You do not have to define a label profile to generate labels (you can just set the settings on the fly in the label
dialog), however if you want to generate many labels, it is recommended to save the settings as label profile, to save dialog), however, if you want to generate many labels, it is recommended to save the settings as a label profile, to save
it for later usage. This ensures that all generated labels look the same. it for later usage. This ensures that all generated labels look the same.

View file

@ -6,21 +6,21 @@ nav_order: 5
# Configuration # Configuration
Part-DBs behavior can be configured to your needs. There are different kind of configuration options: Options which are Part-DBs behavior can be configured to your needs. There are different kinds of configuration options: Options, which are
user changeable (changeable dynamically via frontend), options which can be configured by environment variables, and user-changeable (changeable dynamically via frontend), options that can be configured by environment variables, and
options which are only configurable via symfony config files. options that are only configurable via Symfony config files.
## User changeable ## User changeable
Following things can be changed for every user and a user can change it for himself (if he has the correct permission The following things can be changed for every user and a user can change it for himself (if he has the correct permission
for it). Configuration is either possible via the users own setting page (where you can also change the password) or via for it). Configuration is either possible via the user's own settings page (where you can also change the password) or via
the user admin page: the user admin page:
* **Language**: The language that the users prefers, and which will be used when no language is explicitly specified. * **Language**: The language that the users prefer, and which will be used when no language is explicitly specified.
Language can still always be changed via the language selector. By default, the global configured language is used. Language can still always be changed via the language selector. By default, the globally configured language is used.
* **Timezone**: The timezone which the user resides in and in which all dates and times should be shown. By default, the * **Timezone**: The timezone in which the user resides and in which all dates and times should be shown. By default, the
globally configured language. globally configured language.
* **Theme**: The theme to use for the frontend. Allows the user to choose the frontend design, he prefers. * **Theme**: The theme to use for the front end. Allows the user to choose the front end design, he prefers.
* **Preferred currency**: One of the defined currencies, in which all prices should be shown, if possible. Prices with * **Preferred currency**: One of the defined currencies, in which all prices should be shown, if possible. Prices with
other currencies will be converted to the price selected here other currencies will be converted to the price selected here
@ -28,19 +28,19 @@ the user admin page:
The following configuration options can only be changed by the server administrator, by either changing the server The following configuration options can only be changed by the server administrator, by either changing the server
variables, changing the `.env.local` file or setting env for your docker container. Here are just the most important variables, changing the `.env.local` file or setting env for your docker container. Here are just the most important
options listed, see `.env` file for full list of possible env variables. options listed, see `.env` file for the full list of possible env variables.
### General options ### General options
* `DATABASE_URL`: Configures the database which Part-DB uses. For mysql use a string in the form * `DATABASE_URL`: Configures the database which Part-DB uses. For mysql use a string in the form
of `mysql://<USERNAME>:<PASSWORD>@<HOST>:<PORT>/<TABLE_NAME>` here of `mysql://<USERNAME>:<PASSWORD>@<HOST>:<PORT>/<TABLE_NAME>` here
(e.g. `DATABASE_URL=mysql://user:password@127.0.0.1:3306/part-db`). For sqlite use the following format to specify the (e.g. `DATABASE_URL=mysql://user:password@127.0.0.1:3306/part-db`). For SQLite use the following format to specify the
absolute path where it should be located `sqlite:///path/part/app.db`. You can use `%kernel.project_dir%` as absolute path where it should be located `sqlite:///path/part/app.db`. You can use `%kernel.project_dir%` as
placeholder for the Part-DB root folder (e.g. `sqlite:///%kernel.project_dir%/var/app.db`) placeholder for the Part-DB root folder (e.g. `sqlite:///%kernel.project_dir%/var/app.db`)
* `DATABASE_MYSQL_USE_SSL_CA`: If this value is set to `1` or `true` and a MySQL connection is used, then the connection * `DATABASE_MYSQL_USE_SSL_CA`: If this value is set to `1` or `true` and a MySQL connection is used, then the connection
is encrypted by SSL/TLS and the server certificate is verified against the system CA certificates or the CA certificate is encrypted by SSL/TLS and the server certificate is verified against the system CA certificates or the CA certificate
bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept all certificates. bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept all certificates.
* `DEFAULT_LANG`: The default language to use server wide (when no language is explicitly specified by a user or via * `DEFAULT_LANG`: The default language to use server-wide (when no language is explicitly specified by a user or via
language chooser). Must be something like `en`, `de`, `fr`, etc. language chooser). Must be something like `en`, `de`, `fr`, etc.
* `DEFAULT_TIMEZONE`: The default timezone to use globally, when a user has no timezone specified. Must be something * `DEFAULT_TIMEZONE`: The default timezone to use globally, when a user has no timezone specified. Must be something
like `Europe/Berlin`. See [here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) under TZ Database name like `Europe/Berlin`. See [here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) under TZ Database name
@ -53,7 +53,7 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept
* `INSTANCE_NAME`: The name of your installation. It will be shown as a title in the navbar and other places. By * `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`. 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 * `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 a URL and create it as local file. Please note that this allows users access to all download a file specified as a URL and create it as a local file. Please note that this allows users access to all
resources publicly available to the server (so full access to other servers in the same local network), which could resources publicly available to the server (so full access to other servers in the same local network), which could
be a security risk. be a security risk.
* `ATTACHMENT_DOWNLOAD_BY_DEFAULT`: When this is set to 1, the "download external file" checkbox is checked by default * `ATTACHMENT_DOWNLOAD_BY_DEFAULT`: When this is set to 1, the "download external file" checkbox is checked by default
@ -63,7 +63,7 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept
not set their own picture). The users browsers have to download the pictures from a third-party (gravatar) server, so not set their own picture). The users browsers have to download the pictures from a third-party (gravatar) server, so
this might be a privacy risk. 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` * `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 to specify the size in kilobytes, megabytes or gigabytes. By default `100M` (100 megabytes). Please note that this is
only the limit of Part-DB. You still need to configure the php.ini `upload_max_filesize` and `post_max_size` to allow 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. 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. * `DEFAULT_URI`: The default URI base to use for the Part-DB, when no URL can be determined from the browser request.
@ -71,8 +71,8 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept
emails and other places, where the URL is needed. It is also used, when SAML is enabled.s If you are using a reverse 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 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**. with a slash**.
* `ENFORCE_CHANGE_COMMENTS_FOR`: With this option you can configure, where users are enforced to give a change reason, * `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 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: to make change comments optional everywhere. Possible values are:
* `part_edit`: Edit operation of an existing part * `part_edit`: Edit operation of an existing part
* `part_delete`: Delete operation of an existing part * `part_delete`: Delete operation of an existing part
@ -91,10 +91,10 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept
mail account, you can use the following syntax `MAILER_DSN=smtp://user:password@smtp.mailserver.invalid:587` mail account, you can use the following syntax `MAILER_DSN=smtp://user:password@smtp.mailserver.invalid:587`
* `EMAIL_SENDER_EMAIL`: The email address from which emails should be sent from (in most cases this has to be the same * `EMAIL_SENDER_EMAIL`: The email address from which emails should be sent from (in most cases this has to be the same
as the email address used for SMTP access) as the email address used for SMTP access)
* `EMAIL_SENDER_NAME`: Similar to `EMAIL_SENDER_EMAIL` but this allows you to specify the name from which the mails are * `EMAIL_SENDER_NAME`: Similar to `EMAIL_SENDER_EMAIL`, but this allows you to specify the name from which the mails are
sent from. sent from.
* `ALLOW_EMAIL_PW_RESET`: Set this value to true, if you want to allow users to reset their password via an email * `ALLOW_EMAIL_PW_RESET`: Set this value to true, if you want to allow users to reset their password via an email
notification. You have to configure the mailprovider first before via the MAILER_DSN setting. notification. You have to configure the mail provider first before via the MAILER_DSN setting.
### Table related settings ### Table related settings
@ -105,15 +105,15 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept
Also specify the default order of the columns. This is a comma separated list of column names. Available columns Also specify the default order of the columns. This is a comma separated list of column names. Available columns
are: `name`, `id`, `ipn`, `description`, `category`, `footprint`, `manufacturer`, `storage_location`, `amount`, `minamount`, `partUnit`, `addedDate`, `lastModified`, `needs_review`, `favorite`, `manufacturing_status`, `manufacturer_product_number`, `mass`, `tags`, `attachments`, `edit`. are: `name`, `id`, `ipn`, `description`, `category`, `footprint`, `manufacturer`, `storage_location`, `amount`, `minamount`, `partUnit`, `addedDate`, `lastModified`, `needs_review`, `favorite`, `manufacturing_status`, `manufacturer_product_number`, `mass`, `tags`, `attachments`, `edit`.
### History/Eventlog related settings ### History/Eventlog-related settings
The following options are used to configure, which (and how much) data is written to the system log: The following options are used to configure, which (and how much) data is written to the system log:
* `HISTORY_SAVE_CHANGED_FIELDS`: When this option is set to true, the name of the fields which are changed, are saved to * `HISTORY_SAVE_CHANGED_FIELDS`: When this option is set to true, the name of the fields that are changed, are saved to
the DB (so for example it is logged that a user has changed, that the user has changed the name and description of the the DB (so for example it is logged that a user has changed, that the user has changed the name and description of the
field, but not the data/content of these changes) field, but not the data/content of these changes)
* `HISTORY_SAVE_CHANGED_DATA`: When this option is set to true, the changed data is saved to log (so it is logged, that * `HISTORY_SAVE_CHANGED_DATA`: When this option is set to true, the changed data is saved to log (so it is logged, that
a user has changed the name of a part and what the name was before). This can increase database size, when you have a a user has changed the name of a part and what the name was before). This can increase database size when you have a
lot of changes to entities. lot of changes to entities.
* `HISTORY_SAVE_REMOVED_DATA`: When this option is set to true, removed data is saved to log, meaning that you can * `HISTORY_SAVE_REMOVED_DATA`: When this option is set to true, removed data is saved to log, meaning that you can
easily undelete an entity, when it was removed accidentally. easily undelete an entity, when it was removed accidentally.
@ -126,10 +126,10 @@ then `HISTORY_SAVE_CHANGED_FIELDS`, `HISTORY_SAVE_CHANGED_DATA` and `HISTORY_SAV
### Error pages settings ### Error pages settings
* `ERROR_PAGE_ADMIN_EMAIL`: You can set an email-address here, which is shown on the error page, who should be contacted * `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) 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 * `ERROR_PAGE_SHOW_HELP`: Set this 0, to disable the solution hints shown on an error page. These hints should not
contain sensitive information, but could confuse end-users. contain sensitive information but could confuse end-users.
### EDA related settings ### EDA related settings
@ -143,21 +143,21 @@ then `HISTORY_SAVE_CHANGED_FIELDS`, `HISTORY_SAVE_CHANGED_DATA` and `HISTORY_SAV
The following settings can be used to enable and configure Single-Sign on via SAML. This allows users to log in to The following settings can be used to enable and configure Single-Sign on via SAML. This allows users to log in to
Part-DB without entering a username and password, but instead they are redirected to a SAML Identity Provider (IdP) and 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 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). 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 You can find more advanced settings in the `config/packages/hslavich_onelogin_saml.yaml` file. Please note that this
file is not backed up by the backup script, so you have to back up it manually, if you want to keep your changes. If you file is not backed up by the backup script, so you have to back up 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. 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 * `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. have to configure the SAML settings below before you can use this feature.
* `SAML_BEHIND_PROXY`: Set this to 1, if Part-DB is behind a reverse proxy. See [here]({% link installation/reverse-proxy.md %}) * `SAML_BEHIND_PROXY`: Set this to 1, if Part-DB is behind a reverse proxy. See [here]({% link installation/reverse-proxy.md %})
for more information. Otherwise, leave it to 0 (default.) for more information. Otherwise, leave it to 0 (default.)
* `SAML_ROLE_MAPPING`: A [JSON](https://en.wikipedia.org/wiki/JSON) encoded map which specifies how Part-DB should * `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 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. 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 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. 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 * `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 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 afterward by editing the user. Part-DB administrator can change the group afterward by editing the user.
@ -185,27 +185,27 @@ want to edit it on docker, you have to map the file to a volume.
The settings prefixes with `PROVIDER_*` are used to configure the information providers. The settings prefixes with `PROVIDER_*` are used to configure the information providers.
See the [information providers]({% link usage/information_provider_system.md %}) page for more information. See the [information providers]({% link usage/information_provider_system.md %}) page for more information.
### Other / less used options ### Other / less-used options
* `TRUSTED_PROXIES`: Set the IP addresses (or IP blocks) of trusted reverse proxies here. This is needed to get correct * `TRUSTED_PROXIES`: Set the IP addresses (or IP blocks) of trusted reverse proxies here. This is needed to get correct
IP information (see [here](https://symfony.com/doc/current/deployment/proxies.html) for more info). IP information (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 * `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. should be accessible. If accessed via the wrong hostname, an error will be shown.
* `DEMO_MODE`: Set Part-DB into demo mode, which forbids users to change their passwords and settings. Used for the demo * `DEMO_MODE`: Set Part-DB into demo mode, which forbids users to change their passwords and settings. Used for the demo
instance, should not be needed for normal installations. instance. This should not be needed for normal installations.
* `NO_URL_REWRITE_AVAILABLE` (allowed values `true` or `false`): Set this value to true, if your webserver does not * `NO_URL_REWRITE_AVAILABLE` (allowed values `true` or `false`): Set this value to true, if your webserver does not
support rewrite. In this case, all URL paths will contain index.php/, which is needed then. Normally this setting do support rewrite. In this case, all URL paths will contain index.php/, which is needed then. Normally this setting does
not need to be changed. not need to be changed.
* `REDIRECT_TO_HTTPS`: If this is set to true, all requests to http will be redirected to https. This is useful, if your * `REDIRECT_TO_HTTPS`: If this is set to true, all requests to http will be redirected to https. This is useful if your
webserver does not already do this (like the one used in the demo instance). If your webserver already redirects to web server does not already do this (like the one used in the demo instance). If your web server already redirects to
https, you don't need to set this. Ensure that Part-DB is accessible via https, before you enable this setting. https, you don't need to set this. Ensure that Part-DB is accessible via HTTPS before you enable this setting.
* `FIXER_API_KEY`: If you want to automatically retrieve exchange rates for base currencies other than euros, you have to * `FIXER_API_KEY`: If you want to automatically retrieve exchange rates for base currencies other than euros, you have to
configure an exchange rate provider API. [Fixer.io](https://fixer.io/) is preconfigured, and you just have to register configure an exchange rate provider API. [Fixer.io](https://fixer.io/) is preconfigured, and you just have to register
there and set the retrieved API key in this environment variable. there and set the retrieved API key in this environment variable.
* `APP_ENV`: This value should always be set to `prod` in normal use. Set it to `dev` to enable debug/development * `APP_ENV`: This value should always be set to `prod` in normal use. Set it to `dev` to enable debug/development
mode. (**You should not do this on a publicly accessible server, as it will leak sensitive information!**) mode. (**You should not do this on a publicly accessible server, as it will leak sensitive information!**)
* `BANNER`: You can configure the text that should be shown as the banner on the homepage. Useful especially for docker * `BANNER`: You can configure the text that should be shown as the banner on the homepage. Useful especially for docker
container. In all other applications you can just change the `config/banner.md` file. containers. In all other applications you can just change the `config/banner.md` file.
## Banner ## Banner
@ -218,8 +218,7 @@ markdown (and even some subset of HTML) syntax to format the text.
You can also configure some options via the `config/parameters.yaml` file. This should normally not need, You can also configure some options via the `config/parameters.yaml` file. This should normally not need,
and you should know what you are doing, when you change something here. You should expect, that you will have to do some and you should know what you are doing, when you change something here. You should expect, that you will have to do some
manual merge, when you have changed something here and update to a newer version of Part-DB. It is possible that manual merge, when you have changed something here and update to a newer version of Part-DB. It is possible that
configuration configuration options here will change or be completely removed in future versions of Part-DB.
options here will change or completely removed in future versions of Part-DB.
If you change something here, you have to clear the cache, before the changes will take effect with the If you change something here, you have to clear the cache, before the changes will take effect with the
command `bin/console cache:clear`. command `bin/console cache:clear`.

View file

@ -25,10 +25,10 @@ It is installed on a web server and so can be accessed with any browser without
## Features ## Features
* Inventory management of your electronic parts. Each part can be assigned to a category, footprint, manufacturer * Inventory management of your electronic parts. Each part can be assigned to a category, footprint, manufacturer,
and multiple store locations and price information. Parts can be grouped using tags. You can associate various files and multiple store locations and price information. Parts can be grouped using tags. You can associate various files
like datasheets or pictures with the parts. like datasheets or pictures with the parts.
* Multi-Language support (currently German, English, Russian, Japanese and French (experimental)) * Multi-language support (currently German, English, Russian, Japanese and French (experimental))
* Barcodes/Labels generator for parts and storage locations, scan barcodes via webcam using the builtin barcode scanner * 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. * 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. Two-factor authentication is supported (Google Authenticator and Webauthn/U2F keys) and can be enforced for groups.
@ -46,7 +46,7 @@ It is installed on a web server and so can be accessed with any browser without
* Support for multiple currencies and automatic update of exchange rates supported * 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) * 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 %})) * Easy migration from an existing PartKeepr instance (see [here]({%link partkeepr_migration.md %}))
* Use cloud providers (like Octopart, Digikey, farnell or TME) to automatically get part information, datasheets and * Use cloud providers (like Octopart, Digikey, Farnell or TME) to automatically get part information, datasheets and
prices for parts (see [here]({% link usage/information_provider_system.md %})) prices for parts (see [here]({% link usage/information_provider_system.md %}))
* API to access Part-DB from other applications/scripts * API to access Part-DB from other applications/scripts
* [Integration with KiCad]({%link usage/eda_integration.md %}): Use Part-DB as central datasource for your * [Integration with KiCad]({%link usage/eda_integration.md %}): Use Part-DB as central datasource for your

View file

@ -24,16 +24,15 @@ To use **MySQL/MariaDB** as database, you have to install and configure the MySQ
database and user for Part-DB, which needs some additional work. When using docker you need an additional docker database and user for Part-DB, which needs some additional work. When using docker you need an additional docker
container, and volume for the data container, and volume for the data
When using **SQLite** The database can be backuped easily by just copying the SQLite file to a safe place. Ideally the * When using **SQLite** The database can be backuped easily by just copying the SQLite file to a safe place. Ideally, the *
*MySQL** database has to be dumped to a SQL file (using `mysqldump`). The `console partdb:backup` command can do this *MySQL** database has to be dumped to a SQL file (using `mysqldump`). The `console partdb:backup` command can do this
automatically automatically
However, SQLite does not support certain operations like regex search, which has to be emulated by PHP and therefore are However, SQLite does not support certain operations like regex search, which has to be emulated by PHP and therefore is
pretty slow compared to the same operation at MySQL. In future there might be features that may only be available, when pretty slow compared to the same operation at MySQL. In the future, there might be features that may only be available, when
using MySQL. Also SQLite has limitations in comparisons and sorting of unicode characters, which might lead to unexpected using MySQL. Also, SQLite has limitations in comparisons and sorting of Unicode characters, which might lead to unexpected
behavior when using non-ASCII characters in your data. For example `µ` (micro sign) is not seen as equal to `μ`(greek minuscle mu), behavior when using non-ASCII characters in your data. For example `µ` (micro sign) is not seen as equal to `μ(greek minuscule mu),
therefore searching for `µ` (micro sign) will not find parts containing `μ` (mu) and vice versa. In MySQL identical therefore searching for `µ` (micro sign) will not find parts containing `μ` (mu) and vice versa. In MySQL identical-looking characters are seen as equal, which is more intuitive in most cases.
looking characters are seen as equal, which is more intuitive in most cases.
In general MySQL might perform better for big Part-DB instances with many entries, lots of users and high activity, than In general MySQL might perform better for big Part-DB instances with many entries, lots of users and high activity, than
SQLite. SQLite.

View file

@ -20,7 +20,7 @@ where docker is available (especially recommended for Windows and macOS).
Docker-compose configures the needed images and automatically creates the needed containers and volumes. Docker-compose configures the needed images and automatically creates the needed containers and volumes.
1. Install docker and docker-compose like described under https://docs.docker.com/compose/install/ 1. Install docker and docker-compose as described under https://docs.docker.com/compose/install/
2. Create a folder where the Part-DB data should live 2. Create a folder where the Part-DB data should live
3. Create a file named docker-compose.yaml with the following content: 3. Create a file named docker-compose.yaml with the following content:
@ -74,7 +74,7 @@ services:
# - TRUSTED_PROXIES=127.0.0.0/8,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 # - TRUSTED_PROXIES=127.0.0.0/8,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
``` ```
4. Customize the settings by changing the environment variables (or add new ones). See [Configuration]({% link 4. Customize the settings by changing the environment variables (or adding new ones). See [Configuration]({% link
configuration.md %}) for more information. configuration.md %}) for more information.
5. Inside the folder, run 5. Inside the folder, run
@ -90,10 +90,10 @@ docker exec --user=www-data partdb php bin/console doctrine:migrations:migrate
and watch for the password output and watch for the password output
6. Part-DB is available under `http://localhost:8080` and you can log in with username `admin` and the password shown 6. Part-DB is available under `http://localhost:8080` and you can log in with the username `admin` and the password shown
before before
The docker image uses a SQLite database and all data (database, uploads and other media) is put into folders relative to The docker image uses a SQLite database and all data (database, uploads, and other media) is put into folders relative to
the docker-compose.yml. the docker-compose.yml.
### MySQL ### MySQL
@ -183,15 +183,15 @@ docker exec --user=www-data partdb php bin/console doctrine:migrations:migrate
## Direct use of docker image ## Direct use of docker image
You can use the `jbtronics/part-db1:master` image directly. You have to expose the port 80 to a host port and configure You can use the `jbtronics/part-db1:master` image directly. You have to expose port 80 to a host port and configure
volumes for `/var/www/html/uploads` and `/var/www/html/public/media`. volumes for `/var/www/html/uploads` and `/var/www/html/public/media`.
If you want to use SQLite database (which is default), you have to configure Part-DB to put the database file in a If you want to use SQLite database (which is default), you have to configure Part-DB to put the database file in a
mapped volume via the `DATABASE_URL` environment variable. mapped volume via the `DATABASE_URL` environment variable.
For example if you set `DATABASE_URL=sqlite:///%kernel.project_dir%/var/db/app.db` then you will have to map For example, if you set `DATABASE_URL=sqlite:///%kernel.project_dir%/var/db/app.db` then you will have to map
the `/var/www/html/var/db/` folder to the docker container (see docker-compose.yaml for example). the `/var/www/html/var/db/` folder to the docker container (see docker-compose.yaml for example).
You also have to create the database like described above in step 4. You also have to create the database as described above in step 4.
## Running console commands ## Running console commands
@ -200,8 +200,8 @@ executing `docker exec --user=www-data -it partdb bin/console [command]`
## Troubleshooting ## Troubleshooting
*Login not possible. Login page is just reloading and no error message is shown or something like "CSFR token invalid"*: *Login is not possible. Login page is just reloading and no error message is shown or something like "CSFR token invalid"*:
Clear all cookies in your browser or use an inkognito tab for Part-DB. Clear all cookies in your browser or use an incognito tab for Part-DB.
This related to the fact that Part-DB can not set cookies via HTTP, after some webpage has set cookies before under This is related to the fact that Part-DB can not set cookies via HTTP after some webpages have set cookies before under
localhost via https. This is a security mechanism of the browser and can not be bypassed by Part-DB. localhost via HTTPS. This is a security mechanism of the browser and can not be bypassed by Part-DB.

View file

@ -8,9 +8,9 @@ nav_order: 4
# Part-DB installation guide for Debian 11 (Bullseye) # Part-DB installation guide for Debian 11 (Bullseye)
This guide shows you how to install Part-DB directly on Debian 11 using apache2 and SQLite. This guide should work with This guide shows you how to install Part-DB directly on Debian 11 using apache2 and SQLite. This guide should work with
recent Ubuntu and other Debian based distributions with little to no changes. recent Ubuntu and other Debian-based distributions with little to no changes.
Depending on what you want to do, using the prebuilt docker images may be a better choice, as you don't need to install Depending on what you want to do, using the prebuilt docker images may be a better choice, as you don't need to install
this many dependencies. See [here]({% link installation/installation_docker.md %}) for more information of the docker this many dependencies. See [here]({% link installation/installation_docker.md %}) for more information on the docker
installation. installation.
{: .warning } {: .warning }
@ -30,8 +30,8 @@ sudo apt install git curl zip ca-certificates software-properties-common apt-tra
### Install PHP and apache2 ### Install PHP and apache2
Part-DB is written in [PHP](https://php.net) and therefore needs an PHP interpreter to run. Part-DB needs PHP 8.1 or Part-DB is written in [PHP](https://php.net) and therefore needs a PHP interpreter to run. Part-DB needs PHP 8.1 or
higher, however it is recommended to use the most recent version of PHP for performance reasons and future higher. However, it is recommended to use the most recent version of PHP for performance reasons and future
compatibility. compatibility.
As Debian 11 does not ship PHP 8.1 in its default repositories, we have to add a repository for it. You can skip this As Debian 11 does not ship PHP 8.1 in its default repositories, we have to add a repository for it. You can skip this
@ -46,7 +46,7 @@ sudo curl -sSL https://packages.sury.org/php/README.txt | sudo bash -x
sudo apt update && sudo apt upgrade sudo apt update && sudo apt upgrade
``` ```
Now you can install PHP 8.1 and required packages (change the 8.1 in the package version according to the version you Now you can install PHP 8.1 and the required packages (change the 8.1 in the package version according to the version you
want to use): want to use):
```bash ```bash
@ -57,8 +57,8 @@ The apache2 webserver should be already installed with this command and configur
### Install composer ### Install composer
Part-DB uses [composer](https://getcomposer.org/) to install required PHP libraries. As the versions shipped in the Part-DB uses [composer](https://getcomposer.org/) to install required PHP libraries. As the version shipped in the
repositories is pretty old we install it manually: repositories is pretty old, we will install it manually:
```bash ```bash
# Download composer installer script # Download composer installer script
@ -71,8 +71,8 @@ chmod +x /usr/local/bin/composer
### Install yarn and nodejs ### Install yarn and nodejs
To build the frontend (the user interface) Part-DB uses [yarn](https://yarnpkg.com/). As it depends on Node.js and the To build the front end (the user interface) Part-DB uses [yarn](https://yarnpkg.com/). As it depends on Node.js and the
shipped versions are pretty old, we install new versions from official Node.js repository: shipped versions are pretty old, we install new versions from the official Node.js repository:
```bash ```bash
# Add recent node repository (nodejs 18 is supported until 2025) # Add recent node repository (nodejs 18 is supported until 2025)
@ -102,7 +102,7 @@ later.
git clone https://github.com/Part-DB/Part-DB-symfony.git /var/www/partdb git clone https://github.com/Part-DB/Part-DB-symfony.git /var/www/partdb
``` ```
By default, you are now on the latest development version. In most cases you want to use the latest stable version. You By default, you are now on the latest development version. In most cases, you want to use the latest stable version. You
can switch to the latest stable version (tagged) by running the following command: can switch to the latest stable version (tagged) by running the following command:
```bash ```bash
@ -110,7 +110,7 @@ can switch to the latest stable version (tagged) by running the following comman
git checkout $(git describe --tags $(git rev-list --tags --max-count=1)) git checkout $(git describe --tags $(git rev-list --tags --max-count=1))
``` ```
Alternatively you can check out a specific version by running ( Alternatively, you can check out a specific version by running (
see [GitHub Releases page](https://github.com/Part-DB/Part-DB-server/releases) for a list of available versions): see [GitHub Releases page](https://github.com/Part-DB/Part-DB-server/releases) for a list of available versions):
```bash ```bash
@ -176,12 +176,12 @@ To check if everything is installed, run the following command:
sudo -u www-data php bin/console partdb:check-requirements sudo -u www-data php bin/console partdb:check-requirements
``` ```
The most things should be green, and no red ones. Yellow messages means optional dependencies which are not important Most things should be green, and no red ones. Yellow messages mean optional dependencies which are not important
but can improve performance and functionality. but can improve performance and functionality.
### Create a database for Part-DB ### Create a database for Part-DB
Part-DB by default uses a file based sqlite database to store the data. Use the following command to create the Part-DB by default uses a file-based SQLite database to store the data. Use the following command to create the
database. The database will normally be created at `/var/www/partdb/var/app.db`. database. The database will normally be created at `/var/www/partdb/var/app.db`.
```bash ```bash
@ -191,8 +191,7 @@ sudo -u www-data php bin/console doctrine:migrations:migrate
The command will warn you about schema changes and potential data loss. Continue with typing `yes`. The command will warn you about schema changes and potential data loss. Continue with typing `yes`.
The command will output several lines of information. Somewhere should be a yellow background message The command will output several lines of information. Somewhere should be a yellow background message
like `The initial password for the "admin" user is: f502481134`. Write down this password as you will need it later for like `The initial password for the "admin" user is: f502481134`. Write down this password as you will need it later for the initial login.
initial login.
### Configure apache2 to show Part-DB ### Configure apache2 to show Part-DB
@ -248,7 +247,7 @@ sudo service apache2 restart
``` ```
and Part-DB should now be available under `http://YourServerIP` (or `http://partdb.lan` if you configured DNS in your and Part-DB should now be available under `http://YourServerIP` (or `http://partdb.lan` if you configured DNS in your
network to point on the server). network to point to the server).
### Login to Part-DB ### Login to Part-DB
@ -288,7 +287,7 @@ sudo -u www-data php bin/console cache:clear
## MySQL/MariaDB database ## MySQL/MariaDB database
To use a MySQL database, follow the steps from above (except the creation of database, we will do this later). To use a MySQL database, follow the steps from above (except the creation of the database, we will do this later).
Debian 11 does not ship MySQL in its repositories anymore, so we use the compatible MariaDB instead: Debian 11 does not ship MySQL in its repositories anymore, so we use the compatible MariaDB instead:
1. Install maria-db with: 1. Install maria-db with:

View file

@ -13,7 +13,7 @@ configured.
## Setup ## Setup
1. Install composer and yarn like described in the [apache guide]({% link installation/installation_guide-debian.md 1. Install composer and yarn as described in the [apache guide]({% link installation/installation_guide-debian.md
%}#install-composer). %}#install-composer).
2. Create a folder for Part-DB and install and configure it as described 2. Create a folder for Part-DB and install and configure it as described
3. Instead of creating the config for apache, add the following snippet to your nginx config: 3. Instead of creating the config for apache, add the following snippet to your nginx config:

View file

@ -48,7 +48,7 @@ the [Keycloak Getting Started Guide](https://www.keycloak.org/docs/latest/gettin
*It is recommended to set this value to the domain name of your Part-DB installation, with an attached `/sp` ( *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`)*. e.g. `https://partdb.yourdomain.invalid/sp`)*.
The name field should be set to something human-readable, like `Part-DB`. The name field should be set to something human-readable, like `Part-DB`.
3. Click on `Save` to create the new client. 3. Click on `Save` to create a new client.
### Configure the SAML client ### Configure the SAML client
@ -56,7 +56,7 @@ the [Keycloak Getting Started Guide](https://www.keycloak.org/docs/latest/gettin
* Set `Home URL` to the homepage of your Part-DB installation (e.g. `https://partdb.yourdomain.invalid/`). * 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 ( * Set `Valid redirect URIs` to your homepage with a wildcard at the end (
e.g. `https://partdb.yourdomain.invalid/*`). e.g. `https://partdb.yourdomain.invalid/*`).
* Set `Valid post logout redirect URIs` to `+` to allow all urls from the `Valid redirect URIs`. * Set `Valid post logout redirect URIs` to `+` to allow all URLs from the `Valid redirect URIs`.
* Set `Name ID format` to `username` * Set `Name ID format` to `username`
* Ensure `Force POST binding` is enabled. * Ensure `Force POST binding` is enabled.
* Ensure `Sign documents` is enabled. * Ensure `Sign documents` is enabled.
@ -135,8 +135,8 @@ On the first login of a SAML user, Part-DB will create a new user in the databas
as the SAML user, but no password set. The user will be marked as a SAML user, so he can only log in via SAML in the as the SAML user, but no password set. The user will be marked as a SAML user, so he can only log in 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 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. for any other user and override permissions assigned via groups.
For large organizations you maybe want to automatically assign permissions to SAML users based on the roles or 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 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. groups. See the next section for details.
@ -144,11 +144,11 @@ groups. See the next section for details.
Part-DB allows you to configure a mapping between SAML roles or groups and Part-DB groups. This allows you to 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 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 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. 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 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 permissions. You will need the IDs of the groups, which you can find on the `System->Group` page of Part-DB in the Info
tab. tab.
The map is provided as [JSON](https://en.wikipedia.org/wiki/JSON) encoded map between the SAML role and the group ID, The map is provided as [JSON](https://en.wikipedia.org/wiki/JSON) encoded map between the SAML role and the group ID,
@ -158,7 +158,7 @@ you can configure via the `.env.local` or `docker-compose.yml` file. Please note
string in single quotes here, as JSON itself uses double quotes ( string in single quotes here, as JSON itself uses double quotes (
e.g. `SAML_ROLE_MAPPING='{ "*": 2, "editor": 3, "admin": 1 }`). 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`, 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 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: 2 (by default readonly), you can configure the following map:
@ -176,9 +176,9 @@ If you want to assign users with a certain role to an empty group, provide the g
valid group ID, so the user will not be assigned to any group. 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 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 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 attribute on 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 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. 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 By default, the group is assigned to the user on the first login and updated on every login based on the SAML
@ -203,12 +203,12 @@ provide these attributes, you can use to automatically fill the corresponding fi
## Use SAML Login for existing users ## Use SAML Login for existing users
Part-DB distinguishes between local users and SAML users. Local users are users, which can log in via Part-DB login form Part-DB distinguishes between local users and SAML users. Local users are users, that can log in via the 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 and 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 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. 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 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 log in via SAML in the future, you can use existing users in your Part-DB database and want them to be able to log in 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 `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 log in via SAML in the future. the password hash from the database and mark them as SAML users, so they can log in via SAML in the future.

View file

@ -12,34 +12,34 @@ nav_order: 101
This guide describes how to migrate from [PartKeepr](https://partkeepr.org/) to Part-DB. 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 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. a new Part-DB instance. Most of the data can be migrated, however, there are some limitations, that you can find below.
## What can be imported ## What can be imported
* Datastructures (Categories, Footprints, Storage Locations, Manufacturers, Distributors, Part Measurement Units) * Data structures (Categories, Footprints, Storage Locations, Manufacturers, Distributors, Part Measurement Units)
* Basic part information's (Name, Description, Comment, etc.) * Basic part information (Name, Description, Comment, etc.)
* Attachments and images of parts, projects, footprints, manufacturers and storage locations * Attachments and images of parts, projects, footprints, manufacturers, and storage locations
* Part prices (distributor infos) * Part prices (distributor infos)
* Part parameters * Part parameters
* Projects (including parts and attachments) * Projects (including parts and attachments)
* Users (optional): Passwords however will be not migrated, and need to be reset later * Users (optional): Passwords however will not be migrated, and need to be reset later
## What can't be imported ## 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) * 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) * 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 * Overage information for project parts (the overage info will be set as a comment in the project BOM, but will have no
effect) effect)
* Batch Jobs * Batch Jobs
* Parameter Units (the units will be written into the parameters) * Parameter Units (the units will be written into the parameters)
* Project Reports and Project Runs * Project Reports and Project Runs
* Stock history * Stock History
* Any kind of PartKeepr preferences * Any kind of PartKeepr preferences
## How to migrate ## How to migrate
1. Install Part-DB like described in the installation guide. You can use any database backend you want (mysql or 1. Install Part-DB as 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. 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): 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 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`. command `mysqldump --xml PARTKEEPR_DATABASE --result-file pk.xml`.
@ -47,7 +47,7 @@ a new Part-DB instance. Most of the data can be migrated, however there are some
run `mysqldump --xml -h PARTKEEPR_HOST -u PARTKEEPR_USER -p PARTKEEPR_DATABASE`, where you replace `PARTKEEPR_HOST` 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 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. 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`. 3. Go to 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. 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 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. installation and the contents of `data/images` from PartKeepr to `public/media/` of Part-DB.
@ -63,5 +63,5 @@ option on the database import command (step 3):
All imported users of PartKeepr will be assigned to a new group "PartKeepr Users", which has normal user permissions (so 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 management. editing data, but no administrative tasks). You can change the group and permissions later in Part-DB users management.
Passwords can not be imported from PartKeepr and all imported users get marked as disabled user. So to allow users to Passwords can not be imported from PartKeepr and all imported users get marked as disabled. So to allow users to
login, you need to enable them in the user management and assign a password. log in, you need to enable them in the user management and assign a password.

View file

@ -16,7 +16,7 @@ on how to fix the problem. If you have a problem that is not listed here, please
If you encounter an error, try the following steps: If you encounter an error, try the following steps:
* Clear cache of Part-DB with the console command: * Clear the cache of Part-DB with the console command:
```bash ```bash
php bin/console cache:clear php bin/console cache:clear
@ -30,7 +30,7 @@ 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). 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: ## Search for the user and reset the password:
You can list all users with the following command: `php bin/console partdb:users:list` 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 To reset the password of a user you can use the following

View file

@ -18,33 +18,32 @@ sections carefully before proceeding to upgrade.
* PHP 8.1 or higher is required now (Part-DB 0.5 required PHP 5.4+, Part-DB 0.6 PHP 7.0). * PHP 8.1 or higher is required now (Part-DB 0.5 required PHP 5.4+, Part-DB 0.6 PHP 7.0).
Releases are available for Windows too, so almost everybody should be able to use PHP 8.1 Releases are available for Windows too, so almost everybody should be able to use PHP 8.1
* **Console access highly required.** The installation of composer and frontend dependencies require console access, * **Console access is highly recommended.** The installation of composer and frontend dependencies require console access,
also more sensitive stuff like database migration work via CLI now, so you should have console access on your server. also more sensitive stuff like database migration works via CLI now, so you should have console access on your server.
* Markdown/HTML is now used instead of BBCode for rich text in description and command fields. * Markdown/HTML is now used instead of BBCode for rich text in description and command fields.
It is possible to migrate your existing BBCode to Markdown It is possible to migrate your existing BBCode to Markdown
via `php bin/console php bin/console partdb:migrations:convert-bbcode`. via `php bin/console php bin/console partdb:migrations:convert-bbcode`.
* Server exceptions are not logged to event log anymore. For security reasons (exceptions can contain sensitive * Server exceptions are not logged into event log anymore. For security reasons (exceptions can contain sensitive
information) information) exceptions are only logged to server log (by default under './var/log'), so only the server admins can access it.
exceptions are only logged to server log (by default under './var/log'), so only the server admins can access it. * Profile labels are now saved in the database (before they were saved in a separate JSON file). **The profiles of legacy
* Profile labels are now saved in Database (before they were saved in a separate JSON file). **The profiles of legacy
Part-DB versions can not be imported into new Part-DB 1.0** Part-DB versions can not be imported into new Part-DB 1.0**
* Label placeholders now use the `[[PLACEHOLDER]]` format instead of `%PLACEHOLDER%`. Also, some placeholders has * Label placeholders now use the `[[PLACEHOLDER]]` format instead of `%PLACEHOLDER%`. Also, some placeholders have
changed. changed.
* Configuration is now done via configuration files / environment variables instead of the WebUI (this maybe change in * Configuration is now done via configuration files/environment variables instead of the WebUI (this may change in
the future). the future).
* Database updated are now done via console instead of the WebUI * Database updates 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!** * 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 * Import / Export file format changed. Fields must be English now (unlike in legacy Part-DB versions, where German
fields in CSV were possible) fields in CSV were possible)
and you maybe have to change the header line/field names of your CSV files. and you may have to change the header line/field names of your CSV files.
## Missing features ## Missing features
* No possibility to mark parts for ordering (yet) * No possibility of marking parts for ordering (yet)
* No support for 3D models of footprints (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, * 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. when you forbid users to edit/create them.
* No resistor calculator or SMD labels tools * No resistor calculator or SMD label tools
## Upgrade process ## Upgrade process
@ -56,12 +55,12 @@ sections carefully before proceeding to upgrade.
> Beware that all user and group permissions will be reset, and you have to set the permissions again > Beware that all user and group permissions will be reset, and you have to set the permissions again
> the new Part-DB as many permissions changed, and automatic migration is not possible. > the new Part-DB as many permissions changed, and automatic migration is not possible.
1. Upgrade your existing Part-DB version the newest Part-DB 0.5.* version (at the moment Part-DB 0.5.8), like described 1. Upgrade your existing Part-DB version the newest Part-DB 0.5.* version (at the moment Part-DB 0.5.8), as described
in the old Part-DB's repository. in the old Part-DB's repository.
2. Make a backup of your database and attachments. If something goes wrong during migration, you can use this backup to 2. Make a backup of your database and attachments. If something goes wrong during migration, you can use this backup to
start over. If you have some more complex permission configuration, you maybe want to do screenshots of it, so you start over. If you have some more complex permission configuration, you maybe want to do screenshots of it, so you
can redo it again later. can redo it again later.
3. Set up the new Part-DB like described in installation section. You will need to do the setup for a MySQL instance ( 3. Set up the new Part-DB as described in the installation section. You will need to do the setup for a MySQL instance (
either via docker or direct installation). Set the `DATABASE_URL` environment variable in your `.env.local` ( either via docker or direct installation). Set the `DATABASE_URL` environment variable in your `.env.local` (
or `docker-compose.yaml`) to your existing database. ( or `docker-compose.yaml`) to your existing database. (
e.g. `DATABASE_URL=mysql://PARTDB_USER:PASSWORD@localhost:3306/DATABASE_NAME`) e.g. `DATABASE_URL=mysql://PARTDB_USER:PASSWORD@localhost:3306/DATABASE_NAME`)

View file

@ -7,8 +7,8 @@ parent: Usage
# Backup and Restore Data # Backup and Restore Data
When working productively you should back up the data and configuration of Part-DB regularly to prevent data loss. This When working productively you should back up the data and configuration of Part-DB regularly to prevent data loss. This
is also useful, if you want to migrate your Part-DB instance from one server to another. In that case you just have to is also useful if you want to migrate your Part-DB instance from one server to another. In that case, you just have to
back up the data on server 1, move the backup to server 2, install Part-DB on server 2 and restore the backup. back up the data on server 1, move the backup to server 2, install Part-DB on server 2, and restore the backup.
## Backup (automatic / Part-DB supported) ## Backup (automatic / Part-DB supported)
@ -23,11 +23,11 @@ To back up all possible data, run the following
command: `php bin/console partdb:backup --full /path/to/backup/partdb_backup.zip`. command: `php bin/console partdb:backup --full /path/to/backup/partdb_backup.zip`.
It is possible to do only partial backups (config, attachments, or database). See `php bin/console partdb:backup --help` It is possible to do only partial backups (config, attachments, or database). See `php bin/console partdb:backup --help`
for more infos about these options. for more info about these options.
## Backup (manual) ## Backup (manual)
There are 3 parts which have to be backup-ed: The configuration files, which contains the instance specific options, the 3 parts have to be backup-ed: The configuration files, which contain the instance-specific options, the
uploaded files of attachments, and the database containing the most data of Part-DB. uploaded files of attachments, and the database containing the most data of Part-DB.
Everything else like thumbnails and cache files, are recreated automatically when needed. Everything else like thumbnails and cache files, are recreated automatically when needed.
@ -42,7 +42,7 @@ You have to recursively copy the `uploads/` folder and the `public/media` folder
### Database ### Database
#### Sqlite #### SQLite
If you are using sqlite, it is sufficient to just copy your `app.db` from your database location (normally `var/app.db`) If you are using sqlite, it is sufficient to just copy your `app.db` from your database location (normally `var/app.db`)
to your backup location. to your backup location.
@ -55,8 +55,8 @@ interface (`mysqldump -uBACKUP -pPASSWORD DATABASE`)
## Restore ## Restore
Install Part-DB as usual as described in the installation section, except the database creation / migration part. You Install Part-DB as usual as described in the installation section, except for the database creation/migration part. You
have to use the same database type (sqlite or mysql) as on the back-up server instance. have to use the same database type (SQLite or MySQL) as on the backuped server instance.
### Restore configuration ### Restore configuration
@ -69,7 +69,7 @@ Copy the `uploads/` and the `public/media/` folder from your backup into your ne
### Restore database ### Restore database
#### Sqlite #### SQLite
Copy the backup-ed `app.db` into the database folder normally `var/app.db` in Part-DB root folder. Copy the backup-ed `app.db` into the database folder normally `var/app.db` in Part-DB root folder.

View file

@ -6,7 +6,7 @@ parent: Usage
# EDA / KiCad integration # EDA / KiCad integration
Part-DB can function as central database for [EDA](https://en.wikipedia.org/wiki/Electronic_design_automation) or ECAD software used to design electronic schematics and PCBs. Part-DB can function as a central database for [EDA](https://en.wikipedia.org/wiki/Electronic_design_automation) or ECAD software used to design electronic schematics and PCBs.
You can connect your EDA software and can view your available parts, with the data saved from Part-DB directly in your EDA software. You can connect your EDA software and can view your available parts, with the data saved from Part-DB directly in your EDA software.
Part-DB allows to configure additional metadata for the EDA, to associate symbols and footprints for use inside the EDA software, so the part becomes Part-DB allows to configure additional metadata for the EDA, to associate symbols and footprints for use inside the EDA software, so the part becomes
directly usable inside the EDA software. directly usable inside the EDA software.
@ -20,12 +20,12 @@ This also allows to configure available and usable parts and their properties in
> Part-DB uses the HTTP library feature of KiCad, which is experimental and not part of the stable KiCad 7 releases. If you want to use this feature, you need to install a KiCad nightly build (7.99 version). This feature will most likely also be part of KiCad 8. > Part-DB uses the HTTP library feature of KiCad, which is experimental and not part of the stable KiCad 7 releases. If you want to use this feature, you need to install a KiCad nightly build (7.99 version). This feature will most likely also be part of KiCad 8.
Part-DB should be accessible from the PCs with KiCAD. The URL should be stable (so no dynamically changing IP). Part-DB should be accessible from the PCs with KiCAD. The URL should be stable (so no dynamically changing IP).
You require a user account in Part-DB, which has the permission to access Part-DB API and create API tokens. Every user can has its own account, or you setup a shared read-only account. You require a user account in Part-DB, which has permission to access Part-DB API and create API tokens. Every user can have its own account, or you set up a shared read-only account.
To connect KiCad with Part-DB do following steps: To connect KiCad with Part-DB do the following steps:
1. Create an API token on the user settings page for the KiCAD application and copy/save it, when it is shown. Currently KiCAD can only read Part-DB database, so a token with read only scope is enough. 1. Create an API token on the user settings page for the KiCAD application and copy/save it, when it is shown. Currently, KiCad can only read Part-DB database, so a token with a read-only scope is enough.
2. Add some EDA metadata to parts, categories or footprints. Only parts with useable info will show up in KiCad. See below for more info. 2. Add some EDA metadata to parts, categories, or footprints. Only parts with usable info will show up in KiCad. See below for more info.
3. Create a file `partd.kicad_httplib` (or similar, only the extension is important) with the following content: 3. Create a file `partd.kicad_httplib` (or similar, only the extension is important) with the following content:
``` ```
{ {
@ -44,36 +44,36 @@ To connect KiCad with Part-DB do following steps:
``` ```
4. Replace the `root_url` with the URL of your Part-DB instance plus `/en/kicad-api/`. You can find the right value for this in the Part-DB user settings page under "API endpoints" in the "API tokens" panel. 4. Replace the `root_url` with the URL of your Part-DB instance plus `/en/kicad-api/`. You can find the right value for this in the Part-DB user settings page under "API endpoints" in the "API tokens" panel.
5. Replace the `token` field value with the token you have generated in step 1. 5. Replace the `token` field value with the token you have generated in step 1.
6. Open KiCad and add this created file as library in the KiCad symbol table under (Preferences --> Manage Symbol Libraries) 6. Open KiCad and add this created file as a library in the KiCad symbol table under (Preferences --> Manage Symbol Libraries)
If you then place a new part, the library dialog opens, and you should be able to see the categories and parts from Part-DB. If you then place a new part, the library dialog opens, and you should be able to see the categories and parts from Part-DB.
### How to associate footprints and symbols with parts ### How to associate footprints and symbols with parts
Part-DB dont save any concrete footprints or symbols for the part. Instead Part-DB just contains a reference string in the part metadata, which points to a symbol/footprint in KiCads local library. Part-DB doesn't save any concrete footprints or symbols for the part. Instead, Part-DB just contains a reference string in the part metadata, which points to a symbol/footprint in KiCad's local library.
You can define this on a per-part basis using the KiCad symbol and KiCad footprint field in the EDA tab of the part editor. Or you can define it at a category (symbol) or footprint level, to assign this value to all parts with this category and footprint. You can define this on a per-part basis using the KiCad symbol and KiCad footprint field in the EDA tab of the part editor. Or you can define it at a category (symbol) or footprint level, to assign this value to all parts with this category and footprint.
For example to configure the values for an BC547 transistor you would put `Transistor_BJT:BC547` on the parts Kicad symbol to give it the right schematic symbol in EEschema and `Package_TO_SOT_THT:TO-92` to give it the right footprint in PcbNew. For example, to configure the values for a BC547 transistor you would put `Transistor_BJT:BC547` on the parts Kicad symbol to give it the right schematic symbol in EEschema and `Package_TO_SOT_THT:TO-92` to give it the right footprint in PcbNew.
If you type in a character, you will get an autocomplete list of all symbols and footprints available in the kicad standard library. You can also input your own value. If you type in a character, you will get an autocomplete list of all symbols and footprints available in the KiCad standard library. You can also input your own value.
### Parts and category visibility ### Parts and category visibility
Only parts and their categories, on which there is any kind of EDA metadata are defined show up in KiCad. So if you want to see parts in KiCad, Only parts and their categories, on which there is any kind of EDA metadata are defined show up in KiCad. So if you want to see parts in KiCad,
you need to define at least a symbol, footprint, reference prefix or value on a part, category or footprint. you need to define at least a symbol, footprint, reference prefix, or value on a part, category or footprint.
You can use the "Force visibility" checkbox on a part or category to override this behavior and force parts to be visible or hidden in KiCad. You can use the "Force visibility" checkbox on a part or category to override this behavior and force parts to be visible or hidden in KiCad.
*Please note that KiCad caches the library categories. So if you change something, which would change the visibile categories in KiCad, you have to reload EEschema to see the changes.* *Please note that KiCad caches the library categories. So if you change something, which would change the visible categories in KiCad, you have to reload EEschema to see the changes.*
### Category depth in KiCad ### Category depth in KiCad
For performance reasons, only the most top level categories of Part-DB are shown as categories in KiCad. All parts in the subcategories are shown in the top level category. For performance reasons, only the most top-level categories of Part-DB are shown as categories in KiCad. All parts in the subcategories are shown in the top-level category.
You can configure the depth of the categories shown in KiCad, via the `EDA_KICAD_CATEGORY_DEPTH` env option. The default value is 0, which meabs only the top level categories are shown. You can configure the depth of the categories shown in KiCad, via the `EDA_KICAD_CATEGORY_DEPTH` env option. The default value is 0, which means only the top-level categories are shown.
To show more levels of categories, you can set this value to a higher number. To show more levels of categories, you can set this value to a higher number.
If you set this value to -1, all parts are shown inside a single category in KiCad, without any subcategories. If you set this value to -1, all parts are shown inside a single category in KiCad, without any subcategories.
You can view the "real" category path of a part in the part details dialog in KiCad. You can view the "real" category path of a part in the part details dialog in KiCad.

View file

@ -18,22 +18,22 @@ Before you start creating data structures, you should configure Part-DB to your
options. options.
This is done either via changing the `.env.local` file in a direct installation or by changing the env variables in This is done either via changing the `.env.local` file in a direct installation or by changing the env variables in
your `docker-compose.yaml` file. your `docker-compose.yaml` file.
A list of possible configuration options, can be found [here]({% link configuration.md %}). A list of possible configuration options can be found [here]({% link configuration.md %}).
## Change password, Set up Two-Factor-Authentication & Customize User settings ## Change password, Set up Two-Factor-Authentication & Customize User settings
If you have not already done, you should change your user password. You can do this in the user settings (available in If you have not already done so, you should change your user password. You can do this in the user settings (available in
the navigation bar drop down with the user symbol). the navigation bar drop-down with the user symbol).
![image]({% link assets/getting_started/change_password.png %}) ![image]({% link assets/getting_started/change_password.png %})
There you can also find the option, to set up Two-Factor Authentication methods like Google Authenticator. Using this is There you can also find the option, to set up Two-Factor Authentication methods like Google Authenticator. Using this is
highly recommended (especially if you have admin permissions) to increase the security of your account. (Two-Factor highly recommended (especially if you have admin permissions) to increase the security of your account. (Two-factor authentication
Authentication even can be enforced for all members of a user group) even can be enforced for all members of a user group)
In the user settings panel you can change account infos like your username, your first and last name (which will be In the user settings panel, you can change account info like your username, your first and last name (which will be
shown alongside your username to identify you better), department information and your email address. The email address shown alongside your username to identify you better), department information, and your email address. The email address
is used to send password reset mails, if your system is configured to use this. is used to send password reset mails if your system is configured to use this.
![image]({% link assets/getting_started/user_settings.png %}) ![image]({% link assets/getting_started/user_settings.png %})
@ -46,7 +46,7 @@ used.
The banner which is shown on the homepage, can be customized/changed by changing the `config/banner.md` file with a text The banner which is shown on the homepage, can be customized/changed by changing the `config/banner.md` file with a text
editor. You can use markdown and (safe) HTML here, to style and customize the banner. editor. You can use markdown and (safe) HTML here, to style and customize the banner.
You can even use Latex style equations by wrapping the expressions into `$` (like `$E=mc^2$`, which is rendered inline: You can even use LaTeX-style equations by wrapping the expressions into `$` (like `$E=mc^2$`, which is rendered inline:
$E=mc^2$) or `$$` (like `$$E=mc^2$$`) which will be rendered as a block, like so: $$E=mc^2$$ $E=mc^2$) or `$$` (like `$$E=mc^2$$`) which will be rendered as a block, like so: $$E=mc^2$$
## Create groups, users and customize permissions ## Create groups, users and customize permissions
@ -55,14 +55,14 @@ $E=mc^2$) or `$$` (like `$$E=mc^2$$`) which will be rendered as a block, like so
When logged in as administrator, you can open the users menu in the `Tools` section of the sidebar When logged in as administrator, you can open the users menu in the `Tools` section of the sidebar
under `System -> Users`. under `System -> Users`.
At this page you can create new users, change their passwords and settings and change their permissions. On this page you can create new users, change their passwords and settings, and change their permissions.
For each user which should use Part-DB you should set up an own account, so that tracking of what user did what works For each user who should use Part-DB you should set up their own account so that tracking of what user did works
properly. properly.
![image]({% link assets/getting_started/user_admin.png %}) ![image]({% link assets/getting_started/user_admin.png %})
You should check the permissions for every user and ensure that they are in the intended way, and no user has more You should check the permissions for every user and ensure that they are in the intended way, and no user has more
permissions than he needs. permissions than he needs.
For each capability you can choose between allow, forbid and inherit. In the last case, the permission is determined by For each capability, you can choose between allow, forbid, and inherit. In the last case, the permission is determined by
the group a user has (if no group is chosen, it equals forbid) the group a user has (if no group is chosen, it equals forbid)
![image]({% link assets/getting_started/user_permissions.png %}) ![image]({% link assets/getting_started/user_permissions.png %})
@ -75,35 +75,34 @@ to restrict the permissions.
### Groups ### Groups
If you have many users which should share the same permissions, it is useful to define the permissions using user If you have many users who should share the same permissions, it is useful to define the permissions using user
groups, which you can create and edit in the `System -> Groups` menu. groups, which you can create and edit in the `System -> Groups` menu.
By default 3 groups are defined: By default, 3 groups are defined:
* `readonly` which users only have read permissions (like viewing, searching parts, attachments, etc.) * `readonly` which users only have read permissions (like viewing, searching parts, attachments, etc.)
* `users` which users also have rights to edit/delete/create elements * `users` which users also have rights to edit/delete/create elements
* `admin` which users can do administrative operations (like creating new users, show global system log, etc.) * `admin` which users can do administrative operations (like creating new users, showing global system log, etc.)
Users only use the setting of a capability from a group, if the user has a group associated and the capability on the Users only use the setting of a capability from a group, if the user has a group associated and the capability on the
user is set to `inherit` (which is the default if creating a new user). You can override the permissions settings of a user is set to `inherit` (which is the default if creating a new user). You can override the permissions settings of a
group per user by explicitly settings the permission at the user. group per user by explicitly setting the permission of the user.
Groups are organized as trees, meaning a group can have parent and child permissions and child groups can inherit Groups are organized as trees, meaning a group can have parent and child permissions and child groups can inherit
permissions from their parents. permissions from their parents.
To inherit the permissions from a parent group set the capability to inherit, otherwise set it explicitly to override To inherit the permissions from a parent group set the capability to inherit, otherwise, set it explicitly to override
the parents' permission. the parents' permission.
## Create Attachment types ## Create Attachment types
Every attachment (that is a file associated with a part, data structure, etc.) must have an attachment type. They can Every attachment (that is a file associated with a part, data structure, etc.) must have an attachment type. They can
be used to group attachments logically, like differentiating between datasheets, pictures and other documents. be used to group attachments logically, like differentiating between datasheets, pictures, and other documents.
You can create/edit attachment types in the tools sidebar under "Edit -> Attachment types": You can create/edit attachment types in the tools sidebar under "Edit -> Attachment types":
![image]({% link assets/getting_started/attachment_type_admin.png %}) ![image]({% link assets/getting_started/attachment_type_admin.png %})
Depending on your use case different entries here make sense. For part management the following (additional) entries Depending on your use case different entries here make sense. For part management the following (additional) entries may make sense:
maybe make sense:
* Datasheets (restricted to pdfs, Allowed filetypes: `application/pdf`) * Datasheets (restricted to pdfs, Allowed filetypes: `application/pdf`)
* Pictures (for generic pictures of components, storage locations, etc., Allowed filetypes: `image/*` * Pictures (for generic pictures of components, storage locations, etc., Allowed filetypes: `image/*`
@ -111,34 +110,34 @@ maybe make sense:
For every attachment type a list of allowed file types, which can be uploaded to an attachment with this attachment For every attachment type a list of allowed file types, which can be uploaded to an attachment with this attachment
type, can be defined. You can either pass a list of allowed file extensions (e.g. `.pdf, .zip, .docx`) and/or a list type, can be defined. You can either pass a list of allowed file extensions (e.g. `.pdf, .zip, .docx`) and/or a list
of [Mime Types](https://en.wikipedia.org/wiki/Media_type) (e.g. `application/pdf, image/jpeg`) or a combination of both of [Mime Types](https://en.wikipedia.org/wiki/Media_type) (e.g. `application/pdf, image/jpeg`) or a combination of both
here. To allow all browser supported images, you can use `image/*` wildcard here. here. To allow all browser-supported images, you can use `image/*` wildcard here.
## (Optional) Create Currencies ## (Optional) Create Currencies
If you want to save price information for parts in a currency different to your global currency (by default Euro), you If you want to save price information for parts in a currency different from your global currency (by default Euro), you
have to define the additional currencies you want to use under `Edit -> Currencies`: have to define the additional currencies you want to use under `Edit -> Currencies`:
![image]({% link assets/getting_started/currencies_admin.png %}) ![image]({% link assets/getting_started/currencies_admin.png %})
You create a new currency, name it however you want (it is recommended to use the official name of the currency) and You create a new currency, name it however you want (it is recommended to use the official name of the currency),
select the currency ISO code from the list and save it. The currency symbol is determined automatically from chose ISO select the currency ISO code from the list, and save it. The currency symbol is determined automatically from the chosen ISO
code. code.
You can define an exchange rate in terms of your base currency (e.g. how many euros is one unit of your currency worth) You can define an exchange rate in terms of your base currency (e.g. how many euros is one unit of your currency worth)
to convert the currencies values in your preferred display currency automatically. to convert the currency values in your preferred display currency automatically.
## (Optional) Create Measurement Units ## (Optional) Create Measurement Units
By default, Part-DB assumes that the parts in inventory can be counted by individual indivisible pieces, like LEDs in a By default, Part-DB assumes that the parts in inventory can be counted by individual indivisible pieces, like LEDs in a
box or books in a shelf. box or books on a shelf.
However, if you want to manage things, that are divisible and the stock is described by a physical quantity, like However, if you want to manage things, that are divisible and the stock is described by a physical quantity, like
length for cables, or volumina of a liquid, you have to define additional measurement units. length for cables, or volumes of a liquid, you have to define additional measurement units.
This is possible under `Edit -> Measurement Units`: This is possible under `Edit -> Measurement Units`:
![image]({% link assets/getting_started/units_admin.png %}) ![image]({% link assets/getting_started/units_admin.png %})
You can give the measurement unit a name and an optional unit symbol (like `m` for meters) which is shown when You can give the measurement unit a name and an optional unit symbol (like `m` for meters) which is shown when
quantities in this unit are displayed. The option `Use SI prefix` is useful for almost all physical quantities, as big quantities in this unit are displayed. The option `Use SI prefix` is useful for almost all physical quantities, as big
and small numbers are automatically formatted with SI-prefixes (like 1.5kg instead 1500 grams). and small numbers are automatically formatted with SI prefixes (like 1.5kg instead 1500 grams).
The measurement unit can be selected for each part individually, by setting the option in the advanced tab of a part`s The measurement unit can be selected for each part individually, by setting the option in the advanced tab of a part`s
edit menu. edit menu.
@ -157,16 +156,16 @@ Every part has to be assigned to a category, so you should create at least one c
## (Optional) Create Footprints ## (Optional) Create Footprints
Footprints are used to describe the physical shape of a part, like a resistor or a capacitor. Footprints are used to describe the physical shape of a part, like a resistor or a capacitor.
They can be used to group parts by their physical shape and to find parts with in the same package. They can be used to group parts by their physical shape and to find parts within the same package.
You can create/edit footprints in the tools sidebar under "Edit -> Footprints". You can create/edit footprints in the tools sidebar under "Edit -> Footprints".
It is useful to create footprints for the most common packages, like SMD resistors, capacitors, etc. to make it easier It is useful to create footprints for the most common packages, like SMD resistors, capacitors, etc. to make it easier
to find parts with the same footprint. to find parts with the same footprint.
You should create these as a tree structure, so that you can group footprints by their type. You should create these as a tree structure so that you can group footprints by their type.
See [Concepts]({% link concepts.md %}) for an example tree structure. See [Concepts]({% link concepts.md %}) for an example tree structure.
You can define attachments here which are associated with the footprint. The attachment set as preview image, will be You can define attachments here which are associated with the footprint. The attachment set as the preview image, will be
used whenever a visual representation of the footprint is needed (e.g. in the part list). used whenever a visual representation of the footprint is needed (e.g. in the part list).
For many common footprints, you can use the built-in footprints, which can be found in the "Builtin footprint image For many common footprints, you can use the built-in footprints, which can be found in the "Builtin footprint image

View file

@ -7,7 +7,7 @@ parent: Usage
# Import & Export data # Import & Export data
Part-DB offers the possibility to import existing data (parts, datastructures, etc.) from existing data sources into Part-DB offers the possibility to import existing data (parts, data structures, etc.) from existing data sources into
Part-DB. Data can also be exported from Part-DB into various formats. Part-DB. Data can also be exported from Part-DB into various formats.
## Import ## Import
@ -34,23 +34,23 @@ find in the "Tools" sidebar panel.
> You will not be able to check the data before it is written to the database, so you should review the data before > 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. > 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: You can upload the file that 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 * **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 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. select it here.
* **CSV delimiter**: If you upload an CSV file, you can select the delimiter character which is used to separate the * **CSV delimiter**: If you upload a 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 (`;`). columns in the CSV file. Depending on the CSV file, this might be a comma (`,`) or semicolon (`;`).
* **Category override**: You can select (or create) a category here, to which all imported parts should be assigned, no * **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 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 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). 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 * **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. 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, * **Create unknown data structures**: If this is selected Part-DB will create new data structures (like categories,
manufacturers, etc.) if no datastructure(s) with the same name and path already exists. If this is not selected, only manufacturers, etc.) if no data structure(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. existing data structures will be used and if no matching data strucure is found, the imported parts field will be empty.
* **Path delimiter**: Part-DB allows you to create/select nested datastructures (like categories, manufacturers, etc.) * **Path delimiter**: Part-DB allows you to create/select nested data structures (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 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 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. default one (which is `>`), you can select it here.
@ -59,7 +59,7 @@ You can upload the file which should be imported here and choose various options
is not selected, the import will continue for the other parts and only the invalid parts will be skipped. 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 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 finished, you will see the results of the import in the lower half of the page. You can find a table with the imported
parts (including links to them) there. parts (including links to them) there.
#### Fields description #### Fields description
@ -83,14 +83,14 @@ leave them empty or do not include the column in your file.
* **`manufacturing_status`**: The manufacturing status of the part, must be one of the following * **`manufacturing_status`**: The manufacturing status of the part, must be one of the following
values: `announced`, `active`, `nrfnd`, `eol`, `discontinued` or left empty. 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". * **`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. * **`tags`**: A comma-separated list of tags for the part.
* **`mass`**: The mass of the part in grams. * **`mass`**: The mass of the part in grams.
* **`ipn`**: The IPN (Item Part Number) of the part. * **`ipn`**: The IPN (Item Part Number) of the part.
* **`minamount`**: The minimum amount of the part which should be in stock. * **`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. * **`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 / quantity in stock of the part. An PartLot will With the following fields, you can specify storage locations and amount/quantity in stock of the part. A PartLot will
be created automatically from the data and assigned to the part. The following fields are helpers for an easy import for be created automatically from the data and assigned to the part. The following fields are helpers for an easy import of
parts at one storage location. If you need to create a Part with multiple PartLots you have to use JSON format (or CSV) 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: with nested objects:
@ -99,7 +99,7 @@ field.
**`amount`**, **`quantity`** or **`instock`**: The amount of the part in stock. If this value is not set, the part lot **`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" 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. 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 these fields. If you need to specify multiple This is only possible for a single supplier/distributor and price with these fields. If you need to specify multiple
suppliers/distributors or prices, you have to use JSON format (or CSV) with nested objects. 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 **Please note that the supplier fields is required, if you want to import prices or supplier product numbers**. If the
@ -125,31 +125,31 @@ give the user any additional information.
You can export data structures (like categories, manufacturers, etc.) in the respective edit page (e.g. Tools Panel -> You can export data structures (like categories, manufacturers, etc.) in the respective edit page (e.g. Tools Panel ->
Edit -> Category). Edit -> Category).
If you select a certain datastructure from your list, you can export it (and optionally all sub-datastructures) in the " If you select a certain data structure from your list, you can export it (and optionally all sub data structures) in the "
Export" tab. Export" tab.
If you want to export all datastructures of a certain type (e.g. all categories in your database), you can select the " If you want to export all data structures 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. Export all" function in the "Import / Export" tab of the "new element" page.
You can select between the following export formats: 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 * **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 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 data structures or sub data (like parameters, attachments, etc.), very well (many columns are generated, as every
possible sub data is exported as a separate column). possible sub-data is exported as a separate column).
* **JSON** (JavaScript Object Notation): A text-based format, which is easy to work with programming languages. It * **JSON** (JavaScript Object Notation): A text-based format, which is easy to work with programming languages. It
supports nested datastructures and sub data (like parameters, attachments, etc.) very well. However, it is not easy to supports nested data structures 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 work with in Excel or LibreOffice Calc and you may need to write some code to work with the exported data
efficiently. efficiently.
* **YAML** (Yet another Markup Language): Very similar to JSON * **YAML** (Yet Another Markup Language): Very similar to JSON
* **XML** (Extensible Markup Language): Good support with nested datastructures. Similar use case as JSON and YAML. * **XML** (Extensible Markup Language): Good support with nested data structures. Similar use cases as JSON and YAML.
Also, you can select between the following export levels: 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) * **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.) * **Extended**: This will export all commonly used information about this data structure (like notes, options, etc.)
* **Full**: This will export all available information about this datastructure (like all parameters, attachments) * **Full**: This will export all available information about this data structure (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 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 part, all the associated categories, manufacturers, footprints, etc. will also be exported with all available
information, this can lead to very large export files. information, this can lead to very large export files.
@ -158,4 +158,4 @@ information, this can lead to very large export files.
You can export parts in all part tables. Select the parts you want via the checkbox in the table line and select the 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. export format and level in the appearing menu.
See the section about exporting datastructures for more information about the export formats and levels. See the section about exporting data structures for more information about the export formats and levels.

View file

@ -6,11 +6,11 @@ parent: Usage
# Information provider system # Information provider system
Part-DB can create parts based on information from external sources: For example with the right setup you can just Part-DB can create parts based on information from external sources: For example, with the right setup you can just
search for a part number search for a part number
and Part-DB will query selected distributors and manufacturers for the part and create a part with the information it and Part-DB will query selected distributors and manufacturers for the part and create a part with the information it
found. found.
This way your Part-DB parts automatically get datasheet links, prices, parameters and more, with just a few clicks. This way your Part-DB parts automatically get datasheet links, prices, parameters, and more, with just a few clicks.
## Usage ## Usage
@ -45,13 +45,13 @@ part.
Part-DB tries to automatically find existing elements from your database for the information it got from the providers Part-DB tries to automatically find existing elements from your database for the information it got from the providers
for fields like manufacturer, footprint, etc. for fields like manufacturer, footprint, etc.
For this it searches for an element with the same name (case-insensitive) as the information it got from the provider. So For this, it searches for an element with the same name (case-insensitive) as the information it got from the provider. So
e.g. if the provider returns "EXAMPLE CORP" as manufacturer, e.g. if the provider returns "EXAMPLE CORP" as the manufacturer,
Part-DB will automatically select the element with the name "Example Corp" from your database. Part-DB will automatically select the element with the name "Example Corp" from your database.
As the names of these fields differ from provider to provider (and maybe not even normalized for the same provider), you As the names of these fields differ from provider to provider (and maybe not even normalized for the same provider), you
can define multiple alternative names for an element (on their editing page). can define multiple alternative names for an element (on their editing page).
For example if define a manufacturer "Example Corp" with the alternative names "Example Corp.", "Example Corp", "Example For example, if you define a manufacturer "Example Corp" with the alternative names "Example Corp.", "Example Corp", "Example
Corp. Inc." and "Example Corporation", Corp. Inc." and "Example Corporation",
then the provider can return any of these names and Part-DB will still automatically select the right element. then the provider can return any of these names and Part-DB will still automatically select the right element.
@ -72,12 +72,12 @@ add the alternative names "Datasheet" and "Image" to the alternative names field
The system tries to be as flexible as possible, so many different information sources can be used. The system tries to be as flexible as possible, so many different information sources can be used.
Each information source is called am "info provider" and handles the communication with the external source. Each information source is called am "info provider" and handles the communication with the external source.
The providers are just a driver which handles the communication with the different external sources and converts them The providers are just a driver that handles the communication with the different external sources and converts them
into a common format Part-DB understands. into a common format Part-DB understands.
That way it is pretty easy to create new providers as they just need to do very little work. That way it is pretty easy to create new providers as they just need to do very little work.
Normally the providers utilize an API of a service, and you need to create an account at the provider and get an API key. Normally the providers utilize an API of a service, and you need to create an account at the provider and get an API key.
Also, there are limits on how many requests you can do per day or months, depending on the provider and your contract Also, there are limits on how many requests you can do per day or month, depending on the provider and your contract
with them. with them.
The following providers are currently available and shipped with Part-DB: The following providers are currently available and shipped with Part-DB:
@ -86,8 +86,7 @@ The following providers are currently available and shipped with Part-DB:
### Octopart ### Octopart
The Octopart provider uses the [Octopart / Nexar API](https://nexar.com/api) to search for parts and getting The Octopart provider uses the [Octopart / Nexar API](https://nexar.com/api) to search for parts and get information.
information.
To use it you have to create an account at Nexar and create a new application on To use it you have to create an account at Nexar and create a new application on
the [Nexar Portal](https://portal.nexar.com/). the [Nexar Portal](https://portal.nexar.com/).
The name does not matter, but it is important that the application has access to the "Supply" scope. The name does not matter, but it is important that the application has access to the "Supply" scope.
@ -100,7 +99,7 @@ can see your current usage on the Nexar portal.
Part-DB caches the search results internally, so if you have searched for a part before, it will not count against your Part-DB caches the search results internally, so if you have searched for a part before, it will not count against your
monthly limit again, when you create it from the search results. monthly limit again, when you create it from the search results.
Following env configuration options are available: The following env configuration options are available:
* `PROVIDER_OCTOPART_CLIENT_ID`: The client ID you got from Nexar (mandatory) * `PROVIDER_OCTOPART_CLIENT_ID`: The client ID you got from Nexar (mandatory)
* `PROVIDER_OCTOPART_SECRET`: The client secret you got from Nexar (mandatory) * `PROVIDER_OCTOPART_SECRET`: The client secret you got from Nexar (mandatory)
@ -109,18 +108,18 @@ Following env configuration options are available:
Part-DB will save the prices in their native currency, and you can use Part-DB currency conversion feature to convert Part-DB will save the prices in their native currency, and you can use Part-DB currency conversion feature to convert
it to your preferred currency. it to your preferred currency.
* `PROVIDER_OCOTPART_COUNTRY`: The country you want to get prices in if available (optional, 2 letter ISO-code, * `PROVIDER_OCOTPART_COUNTRY`: The country you want to get prices in if available (optional, 2 letter ISO-code,
default: `DE`). To get correct prices, you have to set this and the currency setting to the correct value. default: `DE`). To get the correct prices, you have to set this and the currency setting to the correct value.
* `PROVIDER_OCTOPART_SEARCH_LIMIT`: The maximum number of results to return per search (optional, default: `10`). This * `PROVIDER_OCTOPART_SEARCH_LIMIT`: The maximum number of results to return per search (optional, default: `10`). This
affects how quickly your monthly limit is used up. affects how quickly your monthly limit is used up.
* `PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS`: If set to `true`, only offers * `PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS`: If set to `true`, only offers
from [authorized sellers](https://octopart.com/authorized) will be returned (optional, default: `false`). from [authorized sellers](https://octopart.com/authorized) will be returned (optional, default: `false`).
**Attention**: If you change the octopart clientID after you have already used the provider, you have to remove the **Attention**: If you change the Octopart clientID after you have already used the provider, you have to remove the
OAuth token in the Part-DB database. Remove the entry in the table `oauth_tokens` with the name `ip_octopart_oauth`. OAuth token in the Part-DB database. Remove the entry in the table `oauth_tokens` with the name `ip_octopart_oauth`.
### Digi-Key ### Digi-Key
The Digi-Key provider uses the [Digi-Key API](https://developer.digikey.com/) to search for parts and getting shopping The Digi-Key provider uses the [Digi-Key API](https://developer.digikey.com/) to search for parts and get shopping
information from [Digi-Key](https://www.digikey.com/). information from [Digi-Key](https://www.digikey.com/).
To use it you have to create an account at Digi-Key and get an API key on To use it you have to create an account at Digi-Key and get an API key on
the [Digi-Key API page](https://developer.digikey.com/). the [Digi-Key API page](https://developer.digikey.com/).
@ -128,7 +127,7 @@ You must create an organization there and create a "Production app". Most settin
grant access to the "Product Information" API. grant access to the "Product Information" API.
You will get a Client ID and a Client Secret, which you have to put in the Part-DB env configuration (see below). You will get a Client ID and a Client Secret, which you have to put in the Part-DB env configuration (see below).
Following env configuration options are available: The following env configuration options are available:
* `PROVIDER_DIGIKEY_CLIENT_ID`: The client ID you got from Digi-Key (mandatory) * `PROVIDER_DIGIKEY_CLIENT_ID`: The client ID you got from Digi-Key (mandatory)
* `PROVIDER_DIGIKEY_SECRET`: The client secret you got from Digi-Key (mandatory) * `PROVIDER_DIGIKEY_SECRET`: The client secret you got from Digi-Key (mandatory)
@ -138,7 +137,7 @@ Following env configuration options are available:
The Digi-Key provider needs an additional OAuth connection. To do this, go to the information provider The Digi-Key provider needs an additional OAuth connection. To do this, go to the information provider
list (`https://your-partdb-instance.tld/tools/info_providers/providers`), list (`https://your-partdb-instance.tld/tools/info_providers/providers`),
go the Digi-Key provider (in the disabled page) and click on the "Connect OAuth" button. You will be redirected to go to Digi-Key provider (in the disabled page), and click on the "Connect OAuth" button. You will be redirected to
Digi-Key, where you have to log in and grant access to the app. Digi-Key, where you have to log in and grant access to the app.
To do this your user needs the "Manage OAuth tokens" permission from the "System" section in the "System" tab. To do this your user needs the "Manage OAuth tokens" permission from the "System" section in the "System" tab.
The OAuth connection should only be needed once, but if you have any problems with the provider, just click the button The OAuth connection should only be needed once, but if you have any problems with the provider, just click the button
@ -146,13 +145,13 @@ again, to establish a new connection.
### TME ### TME
The TME provider use the API of [TME](https://www.tme.eu/) to search for parts and getting shopping information from The TME provider uses the API of [TME](https://www.tme.eu/) to search for parts and getting shopping information from
them. them.
To use it you have to create an account at TME and get an API key on the [TME API page](https://developers.tme.eu/en/). To use it you have to create an account at TME and get an API key on the [TME API page](https://developers.tme.eu/en/).
You have to generate a new anonymous key there and enter the key and secret in the Part-DB env configuration (see You have to generate a new anonymous key there and enter the key and secret in the Part-DB env configuration (see
below). below).
Following env configuration options are available: The following env configuration options are available:
* `PROVIDER_TME_KEY`: The API key you got from TME (mandatory) * `PROVIDER_TME_KEY`: The API key you got from TME (mandatory)
* `PROVIDER_TME_SECRET`: The API secret you got from TME (mandatory) * `PROVIDER_TME_SECRET`: The API secret you got from TME (mandatory)
@ -171,7 +170,7 @@ You have to create an account at Farnell and get an API key on the [Farnell API
Register a new application there (settings does not matter, as long as you select the "Product Search API") and you will Register a new application there (settings does not matter, as long as you select the "Product Search API") and you will
get an API key. get an API key.
Following env configuration options are available: The following env configuration options are available:
* `PROVIDER_ELEMENT14_KEY`: The API key you got from Farnell (mandatory) * `PROVIDER_ELEMENT14_KEY`: The API key you got from Farnell (mandatory)
* `PROVIDER_ELEMENT14_STORE_ID`: The store ID you want to use. This decides the language of results, currency and * `PROVIDER_ELEMENT14_STORE_ID`: The store ID you want to use. This decides the language of results, currency and
@ -185,11 +184,11 @@ information from [Mouser](https://www.mouser.com/).
You have to create an account at Mouser and register for an API key for the Search API on You have to create an account at Mouser and register for an API key for the Search API on
the [Mouser API page](https://www.mouser.de/api-home/). the [Mouser API page](https://www.mouser.de/api-home/).
You will receive an API token, which you have to put in the Part-DB env configuration (see below): You will receive an API token, which you have to put in the Part-DB env configuration (see below):
At the registration you choose a country, language and currency in which you want to get the results. At the registration you choose a country, language, and currency in which you want to get the results.
*Attention*: Currently (January 2024) the mouser API seems to be somewhat broken, in the way that it does not return any *Attention*: Currently (January 2024) the mouser API seems to be somewhat broken, in the way that it does not return any
information about datasheets and part specifications. Therefore Part-DB can not retrieve them, even if they are shown information about datasheets and part specifications. Therefore Part-DB can not retrieve them, even if they are shown
at the mouser page. See [issue #503](https://github.com/Part-DB/Part-DB-server/issues/503) for more infos. at the mouser page. See [issue #503](https://github.com/Part-DB/Part-DB-server/issues/503) for more info.
Following env configuration options are available: Following env configuration options are available:
@ -200,6 +199,19 @@ Following env configuration options are available:
* `PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE`: A bit of an obscure option. The original description of Mouser is: Used * `PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE`: A bit of an obscure option. The original description of Mouser is: Used
when searching for keywords in the language specified when you signed up for Search API. when searching for keywords in the language specified when you signed up for Search API.
### LCSC
[LCSC](https://www.lcsc.com/) is a Chinese distributor of electronic parts. It does not offer a public API, but the LCSC
webshop uses an internal JSON based API to render the page. Part-DB can use this inofficial API to get part information
from LCSC.
**Please note, that the use of this internal API is not intended or endorsed by LCS and it could break at any time. So use it at your own risk.**
An API key is not required, it is enough to enable the provider using the following env configuration options:
* `PROVIDER_LCSC_ENABLED`: Set this to `1` to enable the LCSC provider
* `PROVIDER_LCSC_CURRENCY`: The currency you want to get prices in (see LCSC webshop for available currencies, default: `EUR`)
### Custom provider ### Custom provider
To create a custom provider, you have to create a new class implementing the `InfoProviderInterface` interface. As long To create a custom provider, you have to create a new class implementing the `InfoProviderInterface` interface. As long

View file

@ -93,7 +93,7 @@ all text and search fields in Part-DB.
### Currency symbols ### Currency symbols
Please not the following keybindings are bound to a specific keycode. The key character is not the same on all Please note, the following keybindings are bound to a specific keycode. The key character is not the same on all
keyboards. keyboards.
It is given here for a US keyboard layout. It is given here for a US keyboard layout.
@ -108,7 +108,7 @@ For a German keyboard layout, replace ; with ö, and ' with ä.
### Others ### Others
Please not the following keybindings are bound to a specific keycode. The key character is not the same on all Please note the following keybindings are bound to a specific keycode. The key character is not the same on all
keyboards. keyboards.
It is given here for a US keyboard layout. It is given here for a US keyboard layout.

View file

@ -7,12 +7,12 @@ parent: Usage
# Labels # Labels
Part-DB support the generation and printing of labels for parts, part lots and storage locations. Part-DB support the generation and printing of labels for parts, part lots and storage locations.
You can use the "Tools -> Label generator" menu entry to create labels, or click the label generation link on the part. You can use the "Tools -> Label generator" menu entry to create labels or click the label generation link on the part.
You can define label templates by creating Label profiles. This way you can create many similar looking labels with for You can define label templates by creating Label profiles. This way you can create many similar-looking labels with for
many parts. many parts.
The content of the labels is defined by the templates content field. You can use the WYSIWYG editor to create and style The content of the labels is defined by the template's content field. You can use the WYSIWYG editor to create and style
the content (or write HTML code). the content (or write HTML code).
Using the "Label placeholder" menu in the editor, you can insert placeholders for the data of the parts. Using the "Label placeholder" menu in the editor, you can insert placeholders for the data of the parts.
It will be replaced by the concrete data when the label is generated. It will be replaced by the concrete data when the label is generated.
@ -20,7 +20,7 @@ It will be replaced by the concrete data when the label is generated.
## Label placeholders ## Label placeholders
A placeholder has the format `[[PLACEHOLDER]]` and will be filled with the concrete data by Part-DB. A placeholder has the format `[[PLACEHOLDER]]` and will be filled with the concrete data by Part-DB.
You can use the "Placeholders" dropdown in content editor, to automatically insert the placeholders. You can use the "Placeholders" dropdown in the content editor, to automatically insert the placeholders.
### Common ### Common
@ -124,12 +124,12 @@ the label generator settings:
The default used font (DejaVu) does not support all characters. Especially characters from non-latin languages like The default used font (DejaVu) does not support all characters. Especially characters from non-latin languages like
Chinese, Japanese, Korean, Arabic, Hebrew, Cyrillic, etc. are not supported. Chinese, Japanese, Korean, Arabic, Hebrew, Cyrillic, etc. are not supported.
For this we use [Unifont](http://unifoundry.com/unifont.html) as fallback font. This font supports all (or most) unicode For this, we use [Unifont](http://unifoundry.com/unifont.html) as fallback font. This font supports all (or most) Unicode
characters, but is not as beautiful as DejaVu. characters but is not as beautiful as DejaVu.
If you want to use a different (more beautiful) font, you can use the [custom fonts](#use-custom-fonts-for-pdf-labels) If you want to use a different (more beautiful) font, you can use the [custom fonts](#use-custom-fonts-for-pdf-labels)
feature. feature.
There is the [Noto](https://www.google.com/get/noto/) font family from Google, which supports a lot of languages and is There is the [Noto](https://www.google.com/get/noto/) font family from Google, which supports a lot of languages and is
available in different styles (regular, bold, italic, bold-italic). available in different styles (regular, bold, italic, bold-italic).
For example, you can use [Noto CJK](https://github.com/notofonts/noto-cjk) for more beautiful Chinese, Japanese For example, you can use [Noto CJK](https://github.com/notofonts/noto-cjk) for more beautiful Chinese, Japanese,
and Korean characters. and Korean characters.

View file

@ -8,35 +8,35 @@ parent: Usage
Following you can find miscellaneous tips and tricks for using Part-DB. Following you can find miscellaneous tips and tricks for using Part-DB.
## Create datastructures directly from part edit page ## Create data structures directly from part edit page
Instead of first creating a category, manufacturer, footprint, etc. and then creating the part, you can create the Instead of first creating a category, manufacturer, footprint, etc., and then creating the part, you can create the
datastructures directly from the part edit page: Just type the name of the datastructure you want to create into the data structures directly from the part edit page: Just type the name of the data structure you want to create into the
select field on the part edit page and press "Create new ...". The new datastructure will be created, when you save select field on the part edit page and press "Create new ...". The new data structure will be created when you save
the part changes. the part changes.
You can create also create nested datastructures this way. For example, if you want to create a new category "AVRs", You can create also create nested data structures this way. For example, if you want to create a new category "AVRs",
as a subcategory of "MCUs", you can just type "MCUs->AVRs" into the category select field and press "Create new". as a subcategory of "MCUs", you can just type "MCUs->AVRs" into the category select field and press "Create new".
The new category "AVRs" will be created as a subcategory of "MCUs". If the category "MCUs" does not exist, it will The new category "AVRs" will be created as a subcategory of "MCUs". If the category "MCUs" does not exist, it will
be created too. be created too.
## Builtin footprint images ## Built-in footprint images
Part-DB includes several builtin images for common footprints. You can use these images in your footprint Part-DB includes several built-in images for common footprints. You can use these images in your footprint
datastructures, data structures,
by creating an attachment on the datastructure and selecting it as preview image. by creating an attachment on the data structure and selecting it as the preview image.
Type the name of the footprint image you want to use into the URL field of the attachment and select it from the Type the name of the footprint image you want to use into the URL field of the attachment and select it from the
dropdown menu. You can find a gallery of all builtin footprint images and their names in the "Builtin footprint image dropdown menu. You can find a gallery of all builtin footprint images and their names in the "Builtin footprint image
gallery", gallery",
which you can find in the "Tools" menu (you maybe need to give your user the permission to access this tool). which you can find in the "Tools" menu (you may need to give your user the permission to access this tool).
## Parametric search ## Parametric search
In the "parameters" tab of the filter panel on parts list page, you can define constraints, which parameter values In the "parameters" tab of the filter panel on parts list page, you can define constraints, and which parameter values
have to fulfill. This allows you to search for parts with specific parameters (or parameter ranges), for example you have to fulfill. This allows you to search for parts with specific parameters (or parameter ranges), for example, you
can search for all parts with a voltage rating of greater than 5 V. can search for all parts with a voltage rating of greater than 5 V.
## View own users permissions ## View own user's permissions
If you want to see which permissions your user has, you can find a list of the permissions in the "Permissions" panel If you want to see which permissions your user has, you can find a list of the permissions in the "Permissions" panel
on the user info page. on the user info page.
@ -49,10 +49,8 @@ part).
You can find a list of supported features in the [KaTeX documentation](https://katex.org/docs/supported.html). You can find a list of supported features in the [KaTeX documentation](https://katex.org/docs/supported.html).
To input a LaTeX equation, you have to wrap it in a pair of dollar signs (`$`). Single dollar signs mark inline To input a LaTeX equation, you have to wrap it in a pair of dollar signs (`$`). Single dollar signs mark inline
equations, equations, double dollar signs mark displayed equations (which will be their own line and centered).
double dollar signs mark displayed equations (which will be its own line and centered). For example, the following For example, the following equation will be rendered as an inline equation:
equation
will be rendered as an inline equation:
``` ```
$E=mc^2$ $E=mc^2$
@ -77,16 +75,16 @@ free API used by default only supports the Euro as base currency.
## Enforce log comments ## Enforce log comments
On almost any editing operation it is possible to add a comment describing, what or why you changed something. 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. This comment will be written to changelog and can be viewed later.
If you want to enforce your users to add comments to certain operations, you can do this by setting If you want to force your users to add comments to certain operations, you can do this by setting
the `ENFORCE_CHANGE_COMMENTS_FOR` option. the `ENFORCE_CHANGE_COMMENTS_FOR` option.
See the configuration reference for more information. See the configuration reference for more information.
## Personal stocks and stock locations ## 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 For maker spaces 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 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. the user as "owner" of a part lot. This way, only he is allowed to add or remove parts from this lot.
## Update notifications ## Update notifications

View file

@ -4,7 +4,7 @@
"@babel/preset-env": "^7.19.4", "@babel/preset-env": "^7.19.4",
"@fortawesome/fontawesome-free": "^6.1.1", "@fortawesome/fontawesome-free": "^6.1.1",
"@hotwired/stimulus": "^3.0.0", "@hotwired/stimulus": "^3.0.0",
"@hotwired/turbo": "^7.0.1", "@hotwired/turbo": "^8.0.1",
"@popperjs/core": "^2.10.2", "@popperjs/core": "^2.10.2",
"@symfony/stimulus-bridge": "^3.2.0", "@symfony/stimulus-bridge": "^3.2.0",
"@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets", "@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets",
@ -30,38 +30,38 @@
"build": "encore production --progress" "build": "encore production --progress"
}, },
"dependencies": { "dependencies": {
"@ckeditor/ckeditor5-alignment": "^40.0.0", "@ckeditor/ckeditor5-alignment": "^41.0.0",
"@ckeditor/ckeditor5-autoformat": "^40.0.0", "@ckeditor/ckeditor5-autoformat": "^41.0.0",
"@ckeditor/ckeditor5-basic-styles": "^40.0.0", "@ckeditor/ckeditor5-basic-styles": "^41.0.0",
"@ckeditor/ckeditor5-block-quote": "^40.0.0", "@ckeditor/ckeditor5-block-quote": "^41.0.0",
"@ckeditor/ckeditor5-code-block": "^40.0.0", "@ckeditor/ckeditor5-code-block": "^41.0.0",
"@ckeditor/ckeditor5-dev-translations": "^39.1.0", "@ckeditor/ckeditor5-dev-translations": "^39.1.0",
"@ckeditor/ckeditor5-dev-utils": "^39.1.0", "@ckeditor/ckeditor5-dev-utils": "^39.1.0",
"@ckeditor/ckeditor5-editor-classic": "^40.0.0", "@ckeditor/ckeditor5-editor-classic": "^41.0.0",
"@ckeditor/ckeditor5-essentials": "^40.0.0", "@ckeditor/ckeditor5-essentials": "^41.0.0",
"@ckeditor/ckeditor5-find-and-replace": "^40.0.0", "@ckeditor/ckeditor5-find-and-replace": "^41.0.0",
"@ckeditor/ckeditor5-font": "^40.0.0", "@ckeditor/ckeditor5-font": "^41.0.0",
"@ckeditor/ckeditor5-heading": "^40.0.0", "@ckeditor/ckeditor5-heading": "^41.0.0",
"@ckeditor/ckeditor5-highlight": "^40.0.0", "@ckeditor/ckeditor5-highlight": "^41.0.0",
"@ckeditor/ckeditor5-horizontal-line": "^40.0.0", "@ckeditor/ckeditor5-horizontal-line": "^41.0.0",
"@ckeditor/ckeditor5-html-embed": "^40.0.0", "@ckeditor/ckeditor5-html-embed": "^41.0.0",
"@ckeditor/ckeditor5-html-support": "^40.0.0", "@ckeditor/ckeditor5-html-support": "^41.0.0",
"@ckeditor/ckeditor5-image": "^40.0.0", "@ckeditor/ckeditor5-image": "^41.0.0",
"@ckeditor/ckeditor5-indent": "^40.0.0", "@ckeditor/ckeditor5-indent": "^41.0.0",
"@ckeditor/ckeditor5-link": "^40.0.0", "@ckeditor/ckeditor5-link": "^41.0.0",
"@ckeditor/ckeditor5-list": "^40.0.0", "@ckeditor/ckeditor5-list": "^41.0.0",
"@ckeditor/ckeditor5-markdown-gfm": "^40.0.0", "@ckeditor/ckeditor5-markdown-gfm": "^41.0.0",
"@ckeditor/ckeditor5-media-embed": "^40.0.0", "@ckeditor/ckeditor5-media-embed": "^41.0.0",
"@ckeditor/ckeditor5-paragraph": "^40.0.0", "@ckeditor/ckeditor5-paragraph": "^41.0.0",
"@ckeditor/ckeditor5-paste-from-office": "^40.0.0", "@ckeditor/ckeditor5-paste-from-office": "^41.0.0",
"@ckeditor/ckeditor5-remove-format": "^40.0.0", "@ckeditor/ckeditor5-remove-format": "^41.0.0",
"@ckeditor/ckeditor5-source-editing": "^40.0.0", "@ckeditor/ckeditor5-source-editing": "^41.0.0",
"@ckeditor/ckeditor5-special-characters": "^40.0.0", "@ckeditor/ckeditor5-special-characters": "^41.0.0",
"@ckeditor/ckeditor5-table": "^40.0.0", "@ckeditor/ckeditor5-table": "^41.0.0",
"@ckeditor/ckeditor5-theme-lark": "^40.0.0", "@ckeditor/ckeditor5-theme-lark": "^41.0.0",
"@ckeditor/ckeditor5-upload": "^40.0.0", "@ckeditor/ckeditor5-upload": "^41.0.0",
"@ckeditor/ckeditor5-watchdog": "^40.0.0", "@ckeditor/ckeditor5-watchdog": "^41.0.0",
"@ckeditor/ckeditor5-word-count": "^40.0.0", "@ckeditor/ckeditor5-word-count": "^41.0.0",
"@jbtronics/bs-treeview": "^1.0.1", "@jbtronics/bs-treeview": "^1.0.1",
"@zxcvbn-ts/core": "^3.0.2", "@zxcvbn-ts/core": "^3.0.2",
"@zxcvbn-ts/language-common": "^3.0.3", "@zxcvbn-ts/language-common": "^3.0.3",
@ -74,12 +74,13 @@
"bs-custom-file-input": "^1.3.4", "bs-custom-file-input": "^1.3.4",
"clipboard": "^2.0.4", "clipboard": "^2.0.4",
"compression-webpack-plugin": "^10.0.0", "compression-webpack-plugin": "^10.0.0",
"datatables.net-bs5": "^1.10.20", "datatables.net": "^2.0.0",
"datatables.net-buttons-bs5": "^2.2.2", "datatables.net-bs5": "^2.0.0",
"datatables.net-colreorder-bs5": "^1.5.1", "datatables.net-buttons-bs5": "^3.0.0",
"datatables.net-fixedheader-bs5": "^3.1.5", "datatables.net-colreorder-bs5": "^2.0.0",
"datatables.net-responsive-bs5": "^2.2.3", "datatables.net-fixedheader-bs5": "^4.0.0",
"datatables.net-select-bs5": "^1.2.7", "datatables.net-responsive-bs5": "^3.0.0",
"datatables.net-select-bs5": "^2.0.0",
"dompurify": "^3.0.3", "dompurify": "^3.0.3",
"emoji.json": "^15.0.0", "emoji.json": "^15.0.0",
"exports-loader": "^3.0.0", "exports-loader": "^3.0.0",
@ -87,7 +88,7 @@
"json-formatter-js": "^2.3.4", "json-formatter-js": "^2.3.4",
"jszip": "^3.2.0", "jszip": "^3.2.0",
"katex": "^0.16.0", "katex": "^0.16.0",
"marked": "^11.1.1", "marked": "^12.0.0",
"marked-gfm-heading-id": "^3.0.4", "marked-gfm-heading-id": "^3.0.4",
"marked-mangle": "^1.0.1", "marked-mangle": "^1.0.1",
"pdfmake": "^0.2.2", "pdfmake": "^0.2.2",

View file

@ -115,31 +115,31 @@ class PartFilter implements FilterInterface
*/ */
//We have to use Having here, as we use an alias column which is not supported on the where clause and would result in an error //We have to use Having here, as we use an alias column which is not supported on the where clause and would result in an error
$this->amountSum = (new IntConstraint('amountSum'))->useHaving(); $this->amountSum = (new IntConstraint('amountSum'))->useHaving();
$this->lotCount = new IntConstraint('COUNT(partLots)'); $this->lotCount = new IntConstraint('COUNT(_partLots)');
$this->lessThanDesired = new LessThanDesiredConstraint(); $this->lessThanDesired = new LessThanDesiredConstraint();
$this->storelocation = new EntityConstraint($nodesListBuilder, StorageLocation::class, 'partLots.storage_location'); $this->storelocation = new EntityConstraint($nodesListBuilder, StorageLocation::class, '_partLots.storage_location');
$this->lotNeedsRefill = new BooleanConstraint('partLots.needs_refill'); $this->lotNeedsRefill = new BooleanConstraint('_partLots.needs_refill');
$this->lotUnknownAmount = new BooleanConstraint('partLots.instock_unknown'); $this->lotUnknownAmount = new BooleanConstraint('_partLots.instock_unknown');
$this->lotExpirationDate = new DateTimeConstraint('partLots.expiration_date'); $this->lotExpirationDate = new DateTimeConstraint('_partLots.expiration_date');
$this->lotDescription = new TextConstraint('partLots.description'); $this->lotDescription = new TextConstraint('_partLots.description');
$this->lotOwner = new EntityConstraint($nodesListBuilder, User::class, 'partLots.owner'); $this->lotOwner = new EntityConstraint($nodesListBuilder, User::class, '_partLots.owner');
$this->manufacturer = new EntityConstraint($nodesListBuilder, Manufacturer::class, 'part.manufacturer'); $this->manufacturer = new EntityConstraint($nodesListBuilder, Manufacturer::class, 'part.manufacturer');
$this->manufacturer_product_number = new TextConstraint('part.manufacturer_product_number'); $this->manufacturer_product_number = new TextConstraint('part.manufacturer_product_number');
$this->manufacturer_product_url = new TextConstraint('part.manufacturer_product_url'); $this->manufacturer_product_url = new TextConstraint('part.manufacturer_product_url');
$this->manufacturing_status = new ChoiceConstraint('part.manufacturing_status'); $this->manufacturing_status = new ChoiceConstraint('part.manufacturing_status');
$this->attachmentsCount = new IntConstraint('COUNT(attachments)'); $this->attachmentsCount = new IntConstraint('COUNT(_attachments)');
$this->attachmentType = new EntityConstraint($nodesListBuilder, AttachmentType::class, 'attachments.attachment_type'); $this->attachmentType = new EntityConstraint($nodesListBuilder, AttachmentType::class, '_attachments.attachment_type');
$this->attachmentName = new TextConstraint('attachments.name'); $this->attachmentName = new TextConstraint('_attachments.name');
$this->supplier = new EntityConstraint($nodesListBuilder, Supplier::class, 'orderdetails.supplier'); $this->supplier = new EntityConstraint($nodesListBuilder, Supplier::class, '_orderdetails.supplier');
$this->orderdetailsCount = new IntConstraint('COUNT(orderdetails)'); $this->orderdetailsCount = new IntConstraint('COUNT(_orderdetails)');
$this->obsolete = new BooleanConstraint('orderdetails.obsolete'); $this->obsolete = new BooleanConstraint('_orderdetails.obsolete');
$this->parameters = new ArrayCollection(); $this->parameters = new ArrayCollection();
$this->parametersCount = new IntConstraint('COUNT(parameters)'); $this->parametersCount = new IntConstraint('COUNT(_parameters)');
} }
public function apply(QueryBuilder $queryBuilder): void public function apply(QueryBuilder $queryBuilder): void

View file

@ -82,7 +82,7 @@ class PartSearchFilter implements FilterInterface
$fields_to_search[] = 'part.name'; $fields_to_search[] = 'part.name';
} }
if($this->category) { if($this->category) {
$fields_to_search[] = 'category.name'; $fields_to_search[] = '_category.name';
} }
if($this->description) { if($this->description) {
$fields_to_search[] = 'part.description'; $fields_to_search[] = 'part.description';
@ -94,22 +94,22 @@ class PartSearchFilter implements FilterInterface
$fields_to_search[] = 'part.tags'; $fields_to_search[] = 'part.tags';
} }
if($this->storelocation) { if($this->storelocation) {
$fields_to_search[] = 'storelocations.name'; $fields_to_search[] = '_storelocations.name';
} }
if($this->ordernr) { if($this->ordernr) {
$fields_to_search[] = 'orderdetails.supplierpartnr'; $fields_to_search[] = '_orderdetails.supplierpartnr';
} }
if($this->mpn) { if($this->mpn) {
$fields_to_search[] = 'part.manufacturer_product_number'; $fields_to_search[] = 'part.manufacturer_product_number';
} }
if($this->supplier) { if($this->supplier) {
$fields_to_search[] = 'suppliers.name'; $fields_to_search[] = '_suppliers.name';
} }
if($this->manufacturer) { if($this->manufacturer) {
$fields_to_search[] = 'manufacturer.name'; $fields_to_search[] = '_manufacturer.name';
} }
if($this->footprint) { if($this->footprint) {
$fields_to_search[] = 'footprint.name'; $fields_to_search[] = '_footprint.name';
} }
if ($this->ipn) { if ($this->ipn) {
$fields_to_search[] = 'part.ipn'; $fields_to_search[] = 'part.ipn';

View file

@ -208,6 +208,9 @@ final class PartsDataTable implements DataTableTypeInterface
'detail_query' => $this->getDetailQuery(...), 'detail_query' => $this->getDetailQuery(...),
'entity' => Part::class, 'entity' => Part::class,
'hydrate' => Query::HYDRATE_OBJECT, 'hydrate' => Query::HYDRATE_OBJECT,
//Use the simple total query, as we just want to get the total number of parts without any conditions
//For this the normal query would be pretty slow
'simple_total_query' => true,
'criteria' => [ 'criteria' => [
function (QueryBuilder $builder) use ($options): void { function (QueryBuilder $builder) use ($options): void {
$this->buildCriteria($builder, $options); $this->buildCriteria($builder, $options);
@ -238,7 +241,7 @@ final class PartsDataTable implements DataTableTypeInterface
) AS HIDDEN amountSum' ) AS HIDDEN amountSum'
) )
->from(Part::class, 'part') ->from(Part::class, 'part')
->leftJoin('part.category', 'category') /*->leftJoin('part.category', 'category')
->leftJoin('part.master_picture_attachment', 'master_picture_attachment') ->leftJoin('part.master_picture_attachment', 'master_picture_attachment')
->leftJoin('part.partLots', 'partLots') ->leftJoin('part.partLots', 'partLots')
->leftJoin('partLots.storage_location', 'storelocations') ->leftJoin('partLots.storage_location', 'storelocations')
@ -249,7 +252,7 @@ final class PartsDataTable implements DataTableTypeInterface
->leftJoin('orderdetails.supplier', 'suppliers') ->leftJoin('orderdetails.supplier', 'suppliers')
->leftJoin('part.attachments', 'attachments') ->leftJoin('part.attachments', 'attachments')
->leftJoin('part.partUnit', 'partUnit') ->leftJoin('part.partUnit', 'partUnit')
->leftJoin('part.parameters', 'parameters') ->leftJoin('part.parameters', 'parameters')*/
//This must be the only group by, or the paginator will not work correctly //This must be the only group by, or the paginator will not work correctly
->addGroupBy('part.id'); ->addGroupBy('part.id');
@ -265,6 +268,8 @@ final class PartsDataTable implements DataTableTypeInterface
* We can do complex fetch joins, as we do not need to filter or sort here (which would kill the performance). * We can do complex fetch joins, as we do not need to filter or sort here (which would kill the performance).
* The only condition should be for the IDs. * The only condition should be for the IDs.
* It is important that elements are ordered the same way, as the IDs are passed, or ordering will be wrong. * It is important that elements are ordered the same way, as the IDs are passed, or ordering will be wrong.
*
* We do not require the subqueries like amountSum here, as it is not used to render the table (and only for sorting)
*/ */
$builder $builder
->select('part') ->select('part')
@ -278,16 +283,6 @@ final class PartsDataTable implements DataTableTypeInterface
->addSelect('orderdetails') ->addSelect('orderdetails')
->addSelect('attachments') ->addSelect('attachments')
->addSelect('storelocations') ->addSelect('storelocations')
//Calculate amount sum using a subquery, so we can filter and sort by it
->addSelect(
'(
SELECT IFNULL(SUM(partLot.amount), 0.0)
FROM '.PartLot::class.' partLot
WHERE partLot.part = part.id
AND partLot.instock_unknown = false
AND (partLot.expiration_date IS NULL OR partLot.expiration_date > CURRENT_DATE())
) AS HIDDEN amountSum'
)
->from(Part::class, 'part') ->from(Part::class, 'part')
->leftJoin('part.category', 'category') ->leftJoin('part.category', 'category')
->leftJoin('part.master_picture_attachment', 'master_picture_attachment') ->leftJoin('part.master_picture_attachment', 'master_picture_attachment')
@ -336,5 +331,40 @@ final class PartsDataTable implements DataTableTypeInterface
$filter = $options['filter']; $filter = $options['filter'];
$filter->apply($builder); $filter->apply($builder);
} }
//Check if the query contains certain conditions, for which we need to add additional joins
//The join fields get prefixed with an underscore, so we can check if they are used in the query easy without confusing them for a part subfield
$dql = $builder->getDQL();
if (str_contains($dql, '_category')) {
$builder->leftJoin('part.category', '_category');
}
if (str_contains($dql, '_master_picture_attachment')) {
$builder->leftJoin('part.master_picture_attachment', '_master_picture_attachment');
}
if (str_contains($dql, '_partLots') || str_contains($dql, '_storelocations')) {
$builder->leftJoin('part.partLots', '_partLots');
$builder->leftJoin('_partLots.storage_location', '_storelocations');
}
if (str_contains($dql, '_footprint')) {
$builder->leftJoin('part.footprint', '_footprint');
}
if (str_contains($dql, '_manufacturer')) {
$builder->leftJoin('part.manufacturer', '_manufacturer');
}
if (str_contains($dql, '_orderdetails') || str_contains($dql, '_suppliers')) {
$builder->leftJoin('part.orderdetails', '_orderdetails');
$builder->leftJoin('_orderdetails.supplier', '_suppliers');
}
if (str_contains($dql, '_attachments')) {
$builder->leftJoin('part.attachments', '_attachments');
}
if (str_contains($dql, '_partUnit')) {
$builder->leftJoin('part.partUnit', '_partUnit');
}
if (str_contains($dql, '_parameters')) {
$builder->leftJoin('part.parameters', '_parameters');
}
} }
} }

View file

@ -77,7 +77,7 @@ use LogicException;
new Delete(security: 'is_granted("delete", object)'), new Delete(security: 'is_granted("delete", object)'),
], ],
normalizationContext: ['groups' => ['attachment:read', 'attachment:read:standalone', 'api:basic:read'], 'openapi_definition_name' => 'Read'], normalizationContext: ['groups' => ['attachment:read', 'attachment:read:standalone', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
denormalizationContext: ['groups' => ['attachment:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], denormalizationContext: ['groups' => ['attachment:write', 'attachment:write:standalone', 'api:basic:write'], 'openapi_definition_name' => 'Write'],
)] )]
#[DocumentedAPIProperty(schemaName: 'Attachment-Read', property: 'media_url', type: 'string', nullable: true, #[DocumentedAPIProperty(schemaName: 'Attachment-Read', property: 'media_url', type: 'string', nullable: true,
description: 'The URL to the file, where the attachment file can be downloaded. This can be an internal or external URL.', description: 'The URL to the file, where the attachment file can be downloaded. This can be an internal or external URL.',
@ -142,7 +142,7 @@ abstract class Attachment extends AbstractNamedDBElement
* ORM mapping is done in subclasses (like PartAttachment). * ORM mapping is done in subclasses (like PartAttachment).
* @phpstan-param T|null $element * @phpstan-param T|null $element
*/ */
#[Groups(['attachment:read:standalone', 'attachment:write'])] #[Groups(['attachment:read:standalone', 'attachment:write:standalone'])]
protected ?AttachmentContainingDBElement $element = null; protected ?AttachmentContainingDBElement $element = null;
#[ORM\Column(type: Types::BOOLEAN)] #[ORM\Column(type: Types::BOOLEAN)]

View file

@ -70,7 +70,7 @@ class LabelProfile extends AttachmentContainingDBElement
#[ORM\OrderBy(['name' => 'ASC'])] #[ORM\OrderBy(['name' => 'ASC'])]
protected Collection $attachments; protected Collection $attachments;
#[ORM\ManyToOne(targetEntity: AttachmentTypeAttachment::class)] #[ORM\ManyToOne(targetEntity: LabelAttachment::class)]
#[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')]
protected ?Attachment $master_picture_attachment = null; protected ?Attachment $master_picture_attachment = null;

View file

@ -87,7 +87,7 @@ use function sprintf;
new Delete(security: 'is_granted("delete", object)'), new Delete(security: 'is_granted("delete", object)'),
], ],
normalizationContext: ['groups' => ['parameter:read', 'parameter:read:standalone', 'api:basic:read'], 'openapi_definition_name' => 'Read'], normalizationContext: ['groups' => ['parameter:read', 'parameter:read:standalone', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
denormalizationContext: ['groups' => ['parameter:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], denormalizationContext: ['groups' => ['parameter:write', 'parameter:write:standalone', 'api:basic:write'], 'openapi_definition_name' => 'Write'],
)] )]
#[ApiFilter(LikeFilter::class, properties: ["name", "symbol", "unit", "group", "value_text"])] #[ApiFilter(LikeFilter::class, properties: ["name", "symbol", "unit", "group", "value_text"])]
#[ApiFilter(DateFilter::class, strategy: DateFilter::EXCLUDE_NULL)] #[ApiFilter(DateFilter::class, strategy: DateFilter::EXCLUDE_NULL)]
@ -161,7 +161,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement
* *
* @var AbstractDBElement|null the element to which this parameter belongs to * @var AbstractDBElement|null the element to which this parameter belongs to
*/ */
#[Groups(['parameter:read:standalone', 'parameter:write'])] #[Groups(['parameter:read:standalone', 'parameter:write:standalone'])]
protected ?AbstractDBElement $element = null; protected ?AbstractDBElement $element = null;
public function __construct() public function __construct()

View file

@ -45,6 +45,9 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use function in_array; use function in_array;
/**
* @phpstan-extends Voter<non-empty-string, Attachment|class-string>
*/
final class AttachmentVoter extends Voter final class AttachmentVoter extends Voter
{ {
private const ALLOWED_ATTRIBUTES = ['read', 'view', 'edit', 'delete', 'create', 'show_private', 'show_history']; private const ALLOWED_ATTRIBUTES = ['read', 'view', 'edit', 'delete', 'create', 'show_private', 'show_history'];

View file

@ -23,15 +23,19 @@ declare(strict_types=1);
namespace App\Security\Voter; namespace App\Security\Voter;
use App\Entity\ProjectSystem\Project;
use App\Entity\ProjectSystem\ProjectBOMEntry; use App\Entity\ProjectSystem\ProjectBOMEntry;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\Authorization\Voter\Voter;
/**
* @phpstan-extends Voter<non-empty-string, ProjectBOMEntry|class-string>
*/
class BOMEntryVoter extends Voter class BOMEntryVoter extends Voter
{ {
private const ALLOWED_ATTRIBUTES = ['read', 'view', 'edit', 'delete', 'create']; private const ALLOWED_ATTRIBUTES = ['read', 'view', 'edit', 'delete', 'create', 'show_history'];
public function __construct(private readonly Security $security) public function __construct(private readonly Security $security)
{ {
@ -39,20 +43,25 @@ class BOMEntryVoter extends Voter
protected function supports(string $attribute, mixed $subject): bool protected function supports(string $attribute, mixed $subject): bool
{ {
return $this->supportsAttribute($attribute) && is_a($subject, ProjectBOMEntry::class); return $this->supportsAttribute($attribute) && is_a($subject, ProjectBOMEntry::class, true);
} }
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{ {
if (!$subject instanceof ProjectBOMEntry) { if (!is_a($subject, ProjectBOMEntry::class, true)) {
return false; return false;
} }
$project = $subject->getProject(); if (is_object($subject)) {
$project = $subject->getProject();
//Allow everything if the project was not set yet //Allow everything if the project was not set yet
if ($project === null) { if ($project === null) {
return true; return true;
}
} else {
//If a string was given, use the general project permissions to resolve permissions
$project = Project::class;
} }
//Entry can be read if the user has read access to the project //Entry can be read if the user has read access to the project
@ -60,6 +69,11 @@ class BOMEntryVoter extends Voter
return $this->security->isGranted('read', $project); return $this->security->isGranted('read', $project);
} }
//History can be shown if the user has show_history access to the project
if ($attribute === 'show_history') {
return $this->security->isGranted('show_history', $project);
}
//Everything else can be done if the user has edit access to the project //Everything else can be done if the user has edit access to the project
return $this->security->isGranted('edit', $project); return $this->security->isGranted('edit', $project);
} }
@ -71,6 +85,6 @@ class BOMEntryVoter extends Voter
public function supportsType(string $subjectType): bool public function supportsType(string $subjectType): bool
{ {
return is_a($subjectType, ProjectBOMEntry::class, true); return $subjectType === 'string' || is_a($subjectType, ProjectBOMEntry::class, true);
} }
} }

View file

@ -28,6 +28,9 @@ use App\Services\UserSystem\VoterHelper;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\Authorization\Voter\Voter;
/**
* @phpstan-extends Voter<non-empty-string, Group|class-string>
*/
final class GroupVoter extends Voter final class GroupVoter extends Voter
{ {

View file

@ -32,6 +32,7 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter;
/** /**
* This voter implements a virtual role, which can be used if the user has any permission set to allowed. * This voter implements a virtual role, which can be used if the user has any permission set to allowed.
* We use this to restrict access to the homepage. * We use this to restrict access to the homepage.
* @phpstan-extends Voter<non-empty-string, null>
*/ */
final class HasAccessPermissionsVoter extends Voter final class HasAccessPermissionsVoter extends Voter
{ {

View file

@ -30,6 +30,11 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserInterface;
/**
* This voter implements a virtual role, which can be used if the user has any permission set to allowed.
* We use this to restrict access to the homepage.
* @phpstan-extends Voter<non-empty-string, User>
*/
final class ImpersonateUserVoter extends Voter final class ImpersonateUserVoter extends Voter
{ {

View file

@ -47,6 +47,9 @@ use App\Services\UserSystem\VoterHelper;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\Authorization\Voter\Voter;
/**
* @phpstan-extends Voter<non-empty-string, LabelProfile|class-string>
*/
final class LabelProfileVoter extends Voter final class LabelProfileVoter extends Voter
{ {
protected const MAPPING = [ protected const MAPPING = [
@ -68,7 +71,7 @@ final class LabelProfileVoter extends Voter
protected function supports($attribute, $subject): bool protected function supports($attribute, $subject): bool
{ {
if ($subject instanceof LabelProfile) { if (is_a($subject, LabelProfile::class, true)) {
if (!isset(self::MAPPING[$attribute])) { if (!isset(self::MAPPING[$attribute])) {
return false; return false;
} }
@ -86,6 +89,6 @@ final class LabelProfileVoter extends Voter
public function supportsType(string $subjectType): bool public function supportsType(string $subjectType): bool
{ {
return is_a($subjectType, LabelProfile::class, true); return $subjectType === 'string' || is_a($subjectType, LabelProfile::class, true);
} }
} }

View file

@ -31,6 +31,9 @@ use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\Authorization\Voter\Voter;
/**
* @phpstan-extends Voter<non-empty-string, AbstractLogEntry>
*/
final class LogEntryVoter extends Voter final class LogEntryVoter extends Voter
{ {
final public const ALLOWED_OPS = ['read', 'show_details', 'delete']; final public const ALLOWED_OPS = ['read', 'show_details', 'delete'];

View file

@ -51,6 +51,9 @@ use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\Authorization\Voter\Voter;
/**
* @phpstan-extends Voter<non-empty-string, Orderdetail|class-string>
*/
final class OrderdetailVoter extends Voter final class OrderdetailVoter extends Voter
{ {
public function __construct(private readonly Security $security, private readonly VoterHelper $helper) public function __construct(private readonly Security $security, private readonly VoterHelper $helper)

View file

@ -44,6 +44,9 @@ use RuntimeException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\Authorization\Voter\Voter;
/**
* @phpstan-extends Voter<non-empty-string, AbstractParameter|class-string>
*/
final class ParameterVoter extends Voter final class ParameterVoter extends Voter
{ {

View file

@ -51,6 +51,7 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter;
/** /**
* This voter handles permissions for part associations. * This voter handles permissions for part associations.
* The permissions are inherited from the part. * The permissions are inherited from the part.
* @phpstan-extends Voter<non-empty-string, PartAssociation|class-string>
*/ */
final class PartAssociationVoter extends Voter final class PartAssociationVoter extends Voter
{ {

View file

@ -51,6 +51,9 @@ use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\Authorization\Voter\Voter;
/**
* @phpstan-extends Voter<non-empty-string, PartLot|class-string>
*/
final class PartLotVoter extends Voter final class PartLotVoter extends Voter
{ {
public function __construct(private readonly Security $security, private readonly VoterHelper $helper) public function __construct(private readonly Security $security, private readonly VoterHelper $helper)

View file

@ -32,6 +32,8 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter;
* A Voter that votes on Part entities. * A Voter that votes on Part entities.
* *
* See parts permissions for valid operations. * See parts permissions for valid operations.
*
* @phpstan-extends Voter<non-empty-string, Part|class-string>
*/ */
final class PartVoter extends Voter final class PartVoter extends Voter
{ {

View file

@ -31,6 +31,7 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter;
* This voter allows you to directly check permissions from the permission structure, without passing an object. * This voter allows you to directly check permissions from the permission structure, without passing an object.
* This use the syntax like "@permission.op" * This use the syntax like "@permission.op"
* However you should use the "normal" object based voters if possible, because they are needed for a future ACL system. * However you should use the "normal" object based voters if possible, because they are needed for a future ACL system.
* @phpstan-extends Voter<non-empty-string, null>
*/ */
final class PermissionVoter extends Voter final class PermissionVoter extends Voter
{ {

View file

@ -52,6 +52,9 @@ use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\Authorization\Voter\Voter;
/**
* @phpstan-extends Voter<non-empty-string, Pricedetail|class-string>
*/
final class PricedetailVoter extends Voter final class PricedetailVoter extends Voter
{ {
public function __construct(private readonly Security $security, private readonly VoterHelper $helper) public function __construct(private readonly Security $security, private readonly VoterHelper $helper)

View file

@ -39,6 +39,9 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use function get_class; use function get_class;
use function is_object; use function is_object;
/**
* @phpstan-extends Voter<non-empty-string, AttachmentType|Category|Project|Footprint|Manufacturer|StorageLocation|Supplier|Currency|MeasurementUnit|class-string>
*/
final class StructureVoter extends Voter final class StructureVoter extends Voter
{ {
protected const OBJ_PERM_MAP = [ protected const OBJ_PERM_MAP = [

View file

@ -30,6 +30,9 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use function in_array; use function in_array;
/**
* @phpstan-extends Voter<non-empty-string, User|class-string>
*/
final class UserVoter extends Voter final class UserVoter extends Voter
{ {
public function __construct(private readonly VoterHelper $helper, private readonly PermissionManager $resolver) public function __construct(private readonly VoterHelper $helper, private readonly PermissionManager $resolver)
@ -62,7 +65,7 @@ final class UserVoter extends Voter
public function supportsAttribute(string $attribute): bool public function supportsAttribute(string $attribute): bool
{ {
return $this->helper->isValidOperation('users', $attribute) || $this->helper->isValidOperation('self', $attribute); return $this->helper->isValidOperation('users', $attribute) || $this->helper->isValidOperation('self', $attribute) || $attribute === 'info';
} }
public function supportsType(string $subjectType): bool public function supportsType(string $subjectType): bool

View file

@ -29,14 +29,24 @@ namespace App\Services\InfoProviderSystem\DTOs;
*/ */
class FileDTO class FileDTO
{ {
/**
* @var string The URL where to get this file
*/
public readonly string $url;
/** /**
* @param string $url The URL where to get this file * @param string $url The URL where to get this file
* @param string|null $name Optionally the name of this file * @param string|null $name Optionally the name of this file
*/ */
public function __construct( public function __construct(
public readonly string $url, string $url,
public readonly ?string $name = null, public readonly ?string $name = null,
) {} ) {
//Find all occurrences of non URL safe characters and replace them with their URL encoded version.
//We only want to replace characters which can not have a valid meaning in a URL (what would break the URL).
//Digikey provided some wrong URLs with a ^ in them, which is not a valid URL character. (https://github.com/Part-DB/Part-DB-server/issues/521)
$this->url = preg_replace_callback('/[^a-zA-Z0-9_\-.$+!*();\/?:@=&#%]/', fn($matches) => rawurlencode($matches[0]), $url);
}
} }

View file

@ -87,14 +87,32 @@ class ParameterDTO
{ {
//Try to extract unit from value //Try to extract unit from value
$unit = null; $unit = null;
if (is_string($value) && preg_match('/^(?<value>[0-9.]+)\s*(?<unit>[°a-zA-Z_]+\s?\w{0,4})$/u', $value, $matches)) { if (is_string($value)) {
$value = $matches['value']; [$number, $unit] = self::splitIntoValueAndUnit($value) ?? [$value, null];
$unit = $matches['unit'];
return self::parseValueField(name: $name, value: $value, unit: $unit, symbol: $symbol, group: $group); return self::parseValueField(name: $name, value: $number, unit: $unit, symbol: $symbol, group: $group);
} }
//Otherwise we assume that no unit is given //Otherwise we assume that no unit is given
return self::parseValueField(name: $name, value: $value, unit: null, symbol: $symbol, group: $group); return self::parseValueField(name: $name, value: $value, unit: null, symbol: $symbol, group: $group);
} }
}
/**
* Splits the given value into a value and a unit part if possible.
* If the value is not in the expected format, null is returned.
* @param string $value The value to split
* @return array|null An array with the value and the unit part or null if the value is not in the expected format
* @phpstan-return array{0: string, 1: string}|null
*/
public static function splitIntoValueAndUnit(string $value): ?array
{
if (preg_match('/^(?<value>-?[0-9\.]+)\s*(?<unit>[%Ω°℃a-z_\/]+\s?\w{0,4})$/iu', $value, $matches)) {
$value = $matches['value'];
$unit = $matches['unit'];
return [$value, $unit];
}
return null;
}
}

View file

@ -0,0 +1,347 @@
<?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)
* Copyright (C) 2024 Nexrem (https://github.com/meganukebmp)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\Services\InfoProviderSystem\Providers;
use App\Services\InfoProviderSystem\DTOs\FileDTO;
use App\Services\InfoProviderSystem\DTOs\ParameterDTO;
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
use App\Services\InfoProviderSystem\DTOs\PriceDTO;
use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\Intl\Currencies;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class LCSCProvider implements InfoProviderInterface
{
private const ENDPOINT_URL = 'https://wmsc.lcsc.com/wmsc';
public const DISTRIBUTOR_NAME = 'LCSC';
public function __construct(private readonly HttpClientInterface $lcscClient, private string $currency, private bool $enabled = true)
{
}
public function getProviderInfo(): array
{
return [
'name' => 'LCSC',
'description' => 'This provider uses the (unofficial) LCSC API to search for parts.',
'url' => 'https://www.lcsc.com/',
'disabled_help' => 'Set PROVIDER_LCSC_ENABLED to 1 (or true) in your environment variable config.'
];
}
public function getProviderKey(): string
{
return 'lcsc';
}
// This provider is always active
public function isActive(): bool
{
return $this->enabled;
}
/**
* @param string $id
* @return PartDetailDTO
*/
private function queryDetail(string $id): PartDetailDTO
{
$response = $this->lcscClient->request('GET', self::ENDPOINT_URL . "/product/detail", [
'headers' => [
'Cookie' => new Cookie('currencyCode', $this->currency)
],
'query' => [
'productCode' => $id,
],
]);
$arr = $response->toArray();
$product = $arr['result'] ?? null;
if ($product === null) {
throw new \RuntimeException('Could not find product code: ' . $id);
}
return $this->getPartDetail($product);
}
/**
* @param string $term
* @return PartDetailDTO[]
*/
private function queryByTerm(string $term): array
{
$response = $this->lcscClient->request('GET', self::ENDPOINT_URL . "/search/global", [
'headers' => [
'Cookie' => new Cookie('currencyCode', $this->currency)
],
'query' => [
'keyword' => $term,
],
]);
$arr = $response->toArray();
// Get products list
$products = $arr['result']['productSearchResultVO']['productList'] ?? [];
// Get product tip
$tipProductCode = $arr['result']['tipProductDetailUrlVO']['productCode'] ?? null;
$result = [];
// LCSC does not display LCSC codes in the search, instead taking you directly to the
// detailed product listing. It does so utilizing a product tip field.
// If product tip exists and there are no products in the product list try a detail query
if (count($products) === 0 && !($tipProductCode === null)) {
$result[] = $this->queryDetail($tipProductCode);
}
foreach ($products as $product) {
$result[] = $this->getPartDetail($product);
}
return $result;
}
/**
* Takes a deserialized json object of the product and returns a PartDetailDTO
* @param array $product
* @return PartDetailDTO
*/
private function getPartDetail(array $product): PartDetailDTO
{
// Get product images in advance
$product_images = $this->getProductImages($product['productImages'] ?? null);
$product['productImageUrl'] = $product['productImageUrl'] ?? null;
// If the product does not have a product image but otherwise has attached images, use the first one.
if (count($product_images) > 0) {
$product['productImageUrl'] = $product['productImageUrl'] ?? $product_images[0]->url;
}
// LCSC puts HTML in footprints and descriptions sometimes randomly
$footprint = $product["encapStandard"] ?? null;
if ($footprint !== null) {
$footprint = strip_tags($footprint);
}
//Build category by concatenating the catalogName and parentCatalogName
$category = null;
if (isset($product['parentCatalogName'])) {
$category = $product['parentCatalogName'];
}
if (isset($product['catalogName'])) {
$category = ($category ?? '') . ' -> ' . $product['catalogName'];
// Replace the / with a -> for better readability
$category = str_replace('/', ' -> ', $category);
}
return new PartDetailDTO(
provider_key: $this->getProviderKey(),
provider_id: $product['productCode'],
name: $product['productModel'],
description: strip_tags($product['productIntroEn']),
category: $category,
manufacturer: $product['brandNameEn'],
mpn: $product['productModel'] ?? null,
preview_image_url: $product['productImageUrl'],
manufacturing_status: null,
provider_url: $this->getProductShortURL($product['productCode']),
footprint: $footprint,
datasheets: $this->getProductDatasheets($product['pdfUrl'] ?? null),
images: $product_images,
parameters: $this->attributesToParameters($product['paramVOList'] ?? []),
vendor_infos: $this->pricesToVendorInfo($product['productCode'], $this->getProductShortURL($product['productCode']), $product['productPriceList'] ?? []),
mass: $product['weight'] ?? null,
);
}
/**
* Converts the price array to a VendorInfoDTO array to be used in the PartDetailDTO
* @param string $sku
* @param string $url
* @param array $prices
* @return array
*/
private function pricesToVendorInfo(string $sku, string $url, array $prices): array
{
$price_dtos = [];
foreach ($prices as $price) {
$price_dtos[] = new PriceDTO(
minimum_discount_amount: $price['ladder'],
price: $price['productPrice'],
currency_iso_code: $this->getUsedCurrency($price['currencySymbol']),
includes_tax: false,
);
}
return [
new PurchaseInfoDTO(
distributor_name: self::DISTRIBUTOR_NAME,
order_number: $sku,
prices: $price_dtos,
product_url: $url,
)
];
}
/**
* Converts LCSC currency symbol to an ISO code.
* @param string $currency
* @return string
*/
private function getUsedCurrency(string $currency): string
{
//Decide based on the currency symbol
return match ($currency) {
'US$' => 'USD',
'€' => 'EUR',
'A$' => 'AUD',
'C$' => 'CAD',
'£' => 'GBP',
'HK$' => 'HKD',
'JP¥' => 'JPY',
'RM' => 'MYR',
'S$' => 'SGD',
'₽' => 'RUB',
'kr' => 'SEK',
'kr.' => 'DKK',
'₹' => 'INR',
default => throw new \RuntimeException('Unknown currency: ' . $currency)
};
}
/**
* Returns a valid LCSC product short URL from product code
* @param string $product_code
* @return string
*/
private function getProductShortURL(string $product_code): string
{
return 'https://www.lcsc.com/product-detail/' . $product_code .'.html';
}
/**
* Returns a product datasheet FileDTO array from a single pdf url
* @param string $url
* @return FileDTO[]
*/
private function getProductDatasheets(?string $url): array
{
if ($url === null) {
return [];
}
return [new FileDTO($url, null)];
}
/**
* Returns a FileDTO array with a list of product images
* @param array|null $images
* @return FileDTO[]
*/
private function getProductImages(?array $images): array
{
return array_map(static fn($image) => new FileDTO($image), $images ?? []);
}
/**
* @param array|null $attributes
* @return ParameterDTO[]
*/
private function attributesToParameters(?array $attributes): array
{
$result = [];
foreach ($attributes as $attribute) {
//Skip this attribute if it's empty
if (in_array(trim($attribute['paramValueEn']), array('', '-'), true)) {
continue;
//If the attribute contains a tilde we assume it is a range
} elseif (str_contains($attribute['paramValueEn'], '~')) {
$parts = explode('~', $attribute['paramValueEn']);
if (count($parts) === 2) {
//Try to extract number and unit from value (allow leading +)
[$number, $unit] = ParameterDTO::splitIntoValueAndUnit(ltrim($parts[0], " +")) ?? [$parts[0], null];
[$number2, $unit2] = ParameterDTO::splitIntoValueAndUnit(ltrim($parts[1], " +")) ?? [$parts[1], null];
//If both parts have the same unit and both values are numerical, we assume it is a range
if ($unit === $unit2 && is_numeric($number) && is_numeric($number2)) {
$result[] = new ParameterDTO(name: $attribute['paramNameEn'], value_min: (float) $number, value_max: (float) $number2, unit: $unit, group: null);
continue;
}
}
//If it's a plus/minus value, we'll also it like a range
} elseif (str_starts_with($attribute['paramValueEn'], '±')) {
[$number, $unit] = ParameterDTO::splitIntoValueAndUnit(ltrim($attribute['paramValueEn'], " ±")) ?? [$attribute['paramValueEn'], null];
if (is_numeric($number)) {
$result[] = new ParameterDTO(name: $attribute['paramNameEn'], value_min: -abs((float) $number), value_max: abs((float) $number), unit: $unit, group: null);
continue;
}
}
$result[] = ParameterDTO::parseValueIncludingUnit(name: $attribute['paramNameEn'], value: $attribute['paramValueEn'], group: null);
}
return $result;
}
public function searchByKeyword(string $keyword): array
{
return $this->queryByTerm($keyword);
}
public function getDetails(string $id): PartDetailDTO
{
$tmp = $this->queryByTerm($id);
if (count($tmp) === 0) {
throw new \RuntimeException('No part found with ID ' . $id);
}
if (count($tmp) > 1) {
throw new \RuntimeException('Multiple parts found with ID ' . $id);
}
return $tmp[0];
}
public function getCapabilities(): array
{
return [
ProviderCapabilities::BASIC,
ProviderCapabilities::PICTURE,
ProviderCapabilities::DATASHEET,
ProviderCapabilities::PRICE,
ProviderCapabilities::FOOTPRINT,
];
}
}

View file

@ -68,6 +68,7 @@ class TreeViewGenerator
/** /**
* Gets a TreeView list for the entities of the given class. * Gets a TreeView list for the entities of the given class.
* The result is cached, if the full tree should be shown and no element should be selected.
* *
* @param string $class The class for which the treeView should be generated * @param string $class The class for which the treeView should be generated
* @param AbstractStructuralDBElement|null $parent The root nodes in the tree should have this element as parent (use null, if you want to get all entities) * @param AbstractStructuralDBElement|null $parent The root nodes in the tree should have this element as parent (use null, if you want to get all entities)
@ -82,6 +83,40 @@ class TreeViewGenerator
?AbstractStructuralDBElement $parent = null, ?AbstractStructuralDBElement $parent = null,
string $mode = 'list_parts', string $mode = 'list_parts',
?AbstractDBElement $selectedElement = null ?AbstractDBElement $selectedElement = null
): array
{
//If we just want a part of a tree, don't cache it or select a specific element, don't cache it
if ($parent instanceof AbstractStructuralDBElement || $selectedElement instanceof AbstractDBElement) {
return $this->getTreeViewUncached($class, $parent, $mode, $selectedElement);
}
$secure_class_name = $this->tagGenerator->getElementTypeCacheTag($class);
$key = 'sidebar_treeview_'.$this->keyGenerator->generateKey().'_'.$secure_class_name;
$key .= $mode;
return $this->cache->get($key, function (ItemInterface $item) use ($class, $parent, $mode, $selectedElement, $secure_class_name) {
// Invalidate when groups, an element with the class or the user changes
$item->tag(['groups', 'tree_treeview', $this->keyGenerator->generateKey(), $secure_class_name]);
return $this->getTreeViewUncached($class, $parent, $mode, $selectedElement);
});
}
/**
* Gets a TreeView list for the entities of the given class.
*
* @param string $class The class for which the treeView should be generated
* @param AbstractStructuralDBElement|null $parent The root nodes in the tree should have this element as parent (use null, if you want to get all entities)
* @param string $mode The link type that will be generated for the hyperlink section of each node (see EntityURLGenerator for possible values).
* Set to empty string, to disable href field.
* @param AbstractDBElement|null $selectedElement The element that should be selected. If set to null, no element will be selected.
*
* @return TreeViewNode[] an array of TreeViewNode[] elements of the root elements
*/
private function getTreeViewUncached(
string $class,
?AbstractStructuralDBElement $parent = null,
string $mode = 'list_parts',
?AbstractDBElement $selectedElement = null
): array { ): array {
$head = []; $head = [];

View file

@ -89,7 +89,8 @@
<a class="dropdown-item" href="{{ path("user_settings") }}"> {# Dont prefetch settings page, as it might require additional authentication #}
<a class="dropdown-item" href="{{ path("user_settings") }}" data-turbo-prefetch="false">
<i class="fa fa-cogs fa-fw" aria-hidden="true"></i> {% trans %}user.settings.label{% endtrans %} <i class="fa fa-cogs fa-fw" aria-hidden="true"></i> {% trans %}user.settings.label{% endtrans %}
</a> </a>
<a class="dropdown-item" href="{{ path("user_info_self") }}"> <a class="dropdown-item" href="{{ path("user_info_self") }}">

View file

@ -70,5 +70,5 @@
</div> </div>
<input type="search" class="form-control me-sm-2 my-2" placeholder="{% trans %}search.placeholder{% endtrans %}" name="keyword" required> <input type="search" class="form-control me-sm-2 my-2" placeholder="{% trans %}search.placeholder{% endtrans %}" name="keyword" required>
<button type="submit" class="form btn btn-outline-secondary my-2">{% trans %}search.submit{% endtrans %}</button> <button type="submit" class="form btn btn-outline-secondary my-2 text-nowrap">{% trans %}search.submit{% endtrans %}</button>
</form> </form>

View file

@ -18,7 +18,8 @@
{# Insert info about when the sidebar trees were updated last time, so the sidebar_tree_controller can decide if it needs to reload the tree #} {# Insert info about when the sidebar trees were updated last time, so the sidebar_tree_controller can decide if it needs to reload the tree #}
<span id="sidebar-last-time-updated" style="display: none;" data-last-update="{{ sidebar_tree_updater.lastTreeUpdate.format("Y-m-d\\TH:i:sP") }}"></span> <span id="sidebar-last-time-updated" style="display: none;" data-last-update="{{ sidebar_tree_updater.lastTreeUpdate.format("Y-m-d\\TH:i:sP") }}"></span>
<div class="d-none" data-title="{% apply trim %}{{ current_page_title }}{% endapply %}" {{ stimulus_controller('turbo/title') }}></div> {# The title block is already escaped, therefore we dont require any additional escaping here #}
<div class="d-none" data-title="{{ current_page_title|trim|raw }}" {{ stimulus_controller('turbo/title') }}></div>
<div class="d-none" {{ stimulus_controller('turbo/locale_menu') }}> <div class="d-none" {{ stimulus_controller('turbo/locale_menu') }}>
{% for locale in locale_menu %} {% for locale in locale_menu %}

View file

@ -17,8 +17,10 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="msapplication-starturl" content="/en/"> <meta name="msapplication-starturl" content="/en/">
{# Turbo control headers #}
<meta name="turbo-cache-control" content="no-cache"> <meta name="turbo-cache-control" content="no-cache">
<meta name="turbo-refresh-method" content="morph">
<meta name="turbo-refresh-scroll" content="preserve">
<link rel="shortcut icon" type="image/x-icon" href="{{ asset('favicon.ico') }}"> <link rel="shortcut icon" type="image/x-icon" href="{{ asset('favicon.ico') }}">
<link rel="apple-touch-icon" sizes="180x180" href="{{ asset('icon/apple-touch-icon.png') }}"> <link rel="apple-touch-icon" sizes="180x180" href="{{ asset('icon/apple-touch-icon.png') }}">
@ -26,8 +28,9 @@
<link rel="icon" type="image/png" href="{{ asset('icon/favicon-16x16.png') }}" sizes="16x16"> <link rel="icon" type="image/png" href="{{ asset('icon/favicon-16x16.png') }}" sizes="16x16">
<link rel="mask-icon" href="{{ asset('icon/safari-pinned-tab.svg') }}" color="#5bbad5"> <link rel="mask-icon" href="{{ asset('icon/safari-pinned-tab.svg') }}" color="#5bbad5">
<title>{% apply trim %}{% block title %}{{ partdb_title}}{% endblock %}{% endapply %}</title> {# The content block is already escaped. so we must not escape it again. #}
{% set current_page_title = block("title") %} <title>{% apply trim|raw %}{% block title %}{{ partdb_title }}{% endblock %}{% endapply %}</title>
{% set current_page_title = block("title")|raw %}
{% block stylesheets %} {% block stylesheets %}
{# Include the main bootstrap theme based on user/global setting #} {# Include the main bootstrap theme based on user/global setting #}

View file

@ -0,0 +1,52 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2024 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Tests\Services\InfoProviderSystem\DTOs;
use App\Services\InfoProviderSystem\DTOs\FileDTO;
use PHPUnit\Framework\TestCase;
class FileDTOTest extends TestCase
{
public static function escapingDataProvider(): array
{
return [
//Normal URLs must be unchanged, even if they contain special characters
["https://localhost:8000/en/part/1335/edit#attachments", "https://localhost:8000/en/part/1335/edit#attachments"],
["https://localhost:8000/en/part/1335/edit?test=%20%20&sfee_aswer=test-223!*()", "https://localhost:8000/en/part/1335/edit?test=%20%20&sfee_aswer=test-223!*()"],
//Remaining URL unsafe characters must be escaped
["test%5Ese", "test^se"],
["test%20se", "test se"],
["test%7Cse", "test|se"],
];
}
/**
* @dataProvider escapingDataProvider
*/
public function testURLEscaping(string $expected, string $input): void
{
$fileDTO = new FileDTO( $input);
self::assertSame($expected, $fileDTO->url);
}
}

View file

@ -161,4 +161,22 @@ class ParameterDTOTest extends TestCase
{ {
$this->assertEquals($expected, ParameterDTO::parseValueIncludingUnit($name, $value, $symbol, $group)); $this->assertEquals($expected, ParameterDTO::parseValueIncludingUnit($name, $value, $symbol, $group));
} }
public function testSplitIntoValueAndUnit(): void
{
$this->assertEquals(['1.0', 'kg'], ParameterDTO::splitIntoValueAndUnit('1.0 kg'));
$this->assertEquals(['1.0', 'kg'], ParameterDTO::splitIntoValueAndUnit('1.0kg'));
$this->assertEquals(['1', 'kg'], ParameterDTO::splitIntoValueAndUnit('1 kg'));
$this->assertEquals(['1.0', '°C'], ParameterDTO::splitIntoValueAndUnit('1.0°C'));
$this->assertEquals(['1.0', '°C'], ParameterDTO::splitIntoValueAndUnit('1.0 °C'));
$this->assertEquals(['1.0', 'C_m'], ParameterDTO::splitIntoValueAndUnit('1.0C_m'));
$this->assertEquals(["70", ""], ParameterDTO::splitIntoValueAndUnit("70℃"));
$this->assertEquals(["-5.0", "kg"], ParameterDTO::splitIntoValueAndUnit("-5.0 kg"));
$this->assertNull(ParameterDTO::splitIntoValueAndUnit('kg'));
$this->assertNull(ParameterDTO::splitIntoValueAndUnit('Test'));
}
} }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -7,5 +7,11 @@
<target>Ваша учетная запись отключена! Свяжитесь с администратором, если вы считаете, что это неправильно.</target> <target>Ваша учетная запись отключена! Свяжитесь с администратором, если вы считаете, что это неправильно.</target>
</segment> </segment>
</unit> </unit>
<unit id="Dpb9AmY" name="saml.error.cannot_login_local_user_per_saml">
<segment state="translated">
<source>saml.error.cannot_login_local_user_per_saml</source>
<target>Вы не можете войти в систему как локальный пользователь через SSO! Используйте локального пользователя и его пароль.</target>
</segment>
</unit>
</file> </file>
</xliff> </xliff>

View file

@ -4,13 +4,13 @@
<unit id="aazoCks" name="user.login_error.user_disabled"> <unit id="aazoCks" name="user.login_error.user_disabled">
<segment state="translated"> <segment state="translated">
<source>user.login_error.user_disabled</source> <source>user.login_error.user_disabled</source>
<target>你的账号已被禁用!如果你认为这有问题,请联系管理员。</target> <target>账户已被禁用。请联系管理员</target>
</segment> </segment>
</unit> </unit>
<unit id="Dpb9AmY" name="saml.error.cannot_login_local_user_per_saml"> <unit id="Dpb9AmY" name="saml.error.cannot_login_local_user_per_saml">
<segment state="translated"> <segment state="translated">
<source>saml.error.cannot_login_local_user_per_saml</source> <source>saml.error.cannot_login_local_user_per_saml</source>
<target>你不能使用 SSO 登录本地用户!请使用本地用户密码。</target> <target>无法通过 SSO 以本地用户身份登录。请使用本地用户密码</target>
</segment> </segment>
</unit> </unit>
</file> </file>

View file

@ -185,12 +185,24 @@
<target>Родитель не может быть дочерним по отношению к себе</target> <target>Родитель не может быть дочерним по отношению к себе</target>
</segment> </segment>
</unit> </unit>
<unit id="ayNr6QK" name="validator.select_valid_category">
<segment state="translated">
<source>validator.select_valid_category</source>
<target>Пожалуйста, выберите действительную категорию!</target>
</segment>
</unit>
<unit id="6vIlN5q" name="validator.part_lot.only_existing"> <unit id="6vIlN5q" name="validator.part_lot.only_existing">
<segment state="translated"> <segment state="translated">
<source>validator.part_lot.only_existing</source> <source>validator.part_lot.only_existing</source>
<target>Вы не можете добавлять новые компоненты в хранилище которое помечено как "только существующие".</target> <target>Вы не можете добавлять новые компоненты в хранилище которое помечено как "только существующие".</target>
</segment> </segment>
</unit> </unit>
<unit id="3xoKOIS" name="validator.part_lot.location_full.no_increase">
<segment state="translated">
<source>validator.part_lot.location_full.no_increase</source>
<target>Место хранения заполнено. Запас не может быть увеличен (новое значение должно быть меньше {{old_amount}}).</target>
</segment>
</unit>
<unit id="R6Ov4Yt" name="validator.part_lot.location_full"> <unit id="R6Ov4Yt" name="validator.part_lot.location_full">
<segment state="translated"> <segment state="translated">
<source>validator.part_lot.location_full</source> <source>validator.part_lot.location_full</source>
@ -203,5 +215,131 @@
<target>Вы не можете добавлять новые компоненты в хранилище которое отмечено как "единственный компонент".</target> <target>Вы не можете добавлять новые компоненты в хранилище которое отмечено как "единственный компонент".</target>
</segment> </segment>
</unit> </unit>
<unit id="4gPskOG" name="validator.attachment.must_not_be_null">
<segment state="translated">
<source>validator.attachment.must_not_be_null</source>
<target>Вы должны выбрать тип файла!</target>
</segment>
</unit>
<unit id="cDDVrWT" name="validator.orderdetail.supplier_must_not_be_null">
<segment state="translated">
<source>validator.orderdetail.supplier_must_not_be_null</source>
<target>Вы должны выбрать поставщика!</target>
</segment>
</unit>
<unit id="k5DDdB4" name="validator.measurement_unit.use_si_prefix_needs_unit">
<segment state="translated">
<source>validator.measurement_unit.use_si_prefix_needs_unit</source>
<target>Чтобы включить префиксы СИ, необходимо установить символ единицы!</target>
</segment>
</unit>
<unit id="DuzIOCr" name="part.ipn.must_be_unique">
<segment state="translated">
<source>part.ipn.must_be_unique</source>
<target>Внутренний номер детали (IPN) должен быть уникальным. Значение {{value}} уже используется!</target>
</segment>
</unit>
<unit id="Z4Kuuo2" name="validator.project.bom_entry.name_or_part_needed">
<segment state="translated">
<source>validator.project.bom_entry.name_or_part_needed</source>
<target>Вам необходимо выбрать компонент или задать имя для BOM, не относящейся к компоненту!</target>
</segment>
</unit>
<unit id="WF_v4ih" name="project.bom_entry.name_already_in_bom">
<segment state="translated">
<source>project.bom_entry.name_already_in_bom</source>
<target>Запись BOM с таким именем уже существует!</target>
</segment>
</unit>
<unit id="5v4p85H" name="project.bom_entry.part_already_in_bom">
<segment state="translated">
<source>project.bom_entry.part_already_in_bom</source>
<target>Этот компонент уже существует в BOM!</target>
</segment>
</unit>
<unit id="3lM32Tw" name="project.bom_entry.mountnames_quantity_mismatch">
<segment state="translated">
<source>project.bom_entry.mountnames_quantity_mismatch</source>
<target>Количество наименований сборок должно соответствовать количеству собираемых компонентов!</target>
</segment>
</unit>
<unit id="x47D5WT" name="project.bom_entry.can_not_add_own_builds_part">
<segment state="translated">
<source>project.bom_entry.can_not_add_own_builds_part</source>
<target>BOM проекта не может содержать собственную производственную составляющую!</target>
</segment>
</unit>
<unit id="2x2XDI_" name="project.bom_has_to_include_all_subelement_parts">
<segment state="translated">
<source>project.bom_has_to_include_all_subelement_parts</source>
<target>BOM проекта должна содержать все производственные компоненты подпроектов. Компонент %part_name% проекта %project_name% отсутствует!</target>
</segment>
</unit>
<unit id="U9b1EzD" name="project.bom_entry.price_not_allowed_on_parts">
<segment state="translated">
<source>project.bom_entry.price_not_allowed_on_parts</source>
<target>Невозможно определить цену для BOM записей компонента. Вместо этого определите цену на сам компонент.</target>
</segment>
</unit>
<unit id="ID056SR" name="validator.project_build.lot_bigger_than_needed">
<segment state="translated">
<source>validator.project_build.lot_bigger_than_needed</source>
<target>Вы выбрали для удаления больше, чем необходимо. Уберите лишнее количество.</target>
</segment>
</unit>
<unit id="6hV5UqD" name="validator.project_build.lot_smaller_than_needed">
<segment state="translated">
<source>validator.project_build.lot_smaller_than_needed</source>
<target>Они выбрали меньшее количество, чем необходимо для сборки! Добавьте больше.</target>
</segment>
</unit>
<unit id="G9ZKt.4" name="part.name.must_match_category_regex">
<segment state="translated">
<source>part.name.must_match_category_regex</source>
<target>Имя компонента не соответствует регулярному выражению, указанному в категории: %regex%</target>
</segment>
</unit>
<unit id="m8kMFhf" name="validator.attachment.name_not_blank">
<segment state="translated">
<source>validator.attachment.name_not_blank</source>
<target>Выберите значение или загрузите файл, чтобы автоматически использовать его имя в качестве имени для этого вложения.</target>
</segment>
</unit>
<unit id="nwGaNBW" name="validator.part_lot.owner_must_match_storage_location_owner">
<segment state="translated">
<source>validator.part_lot.owner_must_match_storage_location_owner</source>
<target>Владелец этого инвентаря и выбранное место хранения должны совпадать (%owner_name%)!</target>
</segment>
</unit>
<unit id="HXSz3nQ" name="validator.part_lot.owner_must_not_be_anonymous">
<segment state="translated">
<source>validator.part_lot.owner_must_not_be_anonymous</source>
<target>Владелец не может быть анонимным пользователем!</target>
</segment>
</unit>
<unit id="N8aA0Uh" name="validator.part_association.must_set_an_value_if_type_is_other">
<segment state="translated">
<source>validator.part_association.must_set_an_value_if_type_is_other</source>
<target>Если тип ссылки установлен на «Другое», необходимо установить описательное значение!</target>
</segment>
</unit>
<unit id="9VYNZ4v" name="validator.part_association.part_cannot_be_associated_with_itself">
<segment state="translated">
<source>validator.part_association.part_cannot_be_associated_with_itself</source>
<target>Компонент не может быть связан сам с собой!</target>
</segment>
</unit>
<unit id="csc1PNn" name="validator.part_association.already_exists">
<segment state="translated">
<source>validator.part_association.already_exists</source>
<target>Ссылка на этот компонент уже существует!</target>
</segment>
</unit>
<unit id="sfW4NYE" name="validator.part_lot.vendor_barcode_must_be_unique">
<segment state="translated">
<source>validator.part_lot.vendor_barcode_must_be_unique</source>
<target>Штрих-код этого поставщика уже используется в другом инвентаре. Штрих-код должен быть уникальным!</target>
</segment>
</unit>
</file> </file>
</xliff> </xliff>

View file

@ -39,7 +39,7 @@
</notes> </notes>
<segment state="translated"> <segment state="translated">
<source>part.master_attachment.must_be_picture</source> <source>part.master_attachment.must_be_picture</source>
<target>预览附件必须是一张有效的图像!</target> <target>预览附件必须是有效的图片</target>
</segment> </segment>
</unit> </unit>
<unit id="VJHTkxx" name="structural.entity.unique_name"> <unit id="VJHTkxx" name="structural.entity.unique_name">
@ -84,7 +84,7 @@
</notes> </notes>
<segment state="translated"> <segment state="translated">
<source>structural.entity.unique_name</source> <source>structural.entity.unique_name</source>
<target>在这个层级上已经存在一个同名的元素!</target> <target>相同层下已存在同名元素</target>
</segment> </segment>
</unit> </unit>
<unit id="3ODUtpU" name="parameters.validator.min_lesser_typical"> <unit id="3ODUtpU" name="parameters.validator.min_lesser_typical">
@ -104,7 +104,7 @@
</notes> </notes>
<segment state="translated"> <segment state="translated">
<source>parameters.validator.min_lesser_typical</source> <source>parameters.validator.min_lesser_typical</source>
<target>这个值必须小于等于典型值 ({{ compared_value }})。</target> <target>值必须小于或等于标称值 ({{compare_value}})。</target>
</segment> </segment>
</unit> </unit>
<unit id="jDBA_WW" name="parameters.validator.min_lesser_max"> <unit id="jDBA_WW" name="parameters.validator.min_lesser_max">
@ -124,7 +124,7 @@
</notes> </notes>
<segment state="translated"> <segment state="translated">
<source>parameters.validator.min_lesser_max</source> <source>parameters.validator.min_lesser_max</source>
<target>这个值必须小于最大值 ({{ compared_value }})。</target> <target>值必须小于最大值 ({{compare_value}})。</target>
</segment> </segment>
</unit> </unit>
<unit id="ygK_e_X" name="parameters.validator.max_greater_typical"> <unit id="ygK_e_X" name="parameters.validator.max_greater_typical">
@ -144,7 +144,7 @@
</notes> </notes>
<segment state="translated"> <segment state="translated">
<source>parameters.validator.max_greater_typical</source> <source>parameters.validator.max_greater_typical</source>
<target>这个值必须大于等于典型值 ({{ compared_value }})。</target> <target>值必须大于或等于标称值 ({{compare_value}})。</target>
</segment> </segment>
</unit> </unit>
<unit id="isXL.ie" name="validator.user.username_already_used"> <unit id="isXL.ie" name="validator.user.username_already_used">
@ -154,7 +154,7 @@
</notes> </notes>
<segment state="translated"> <segment state="translated">
<source>validator.user.username_already_used</source> <source>validator.user.username_already_used</source>
<target>已存在一个同名用户</target> <target>已存在同名用户</target>
</segment> </segment>
</unit> </unit>
<unit id="NcM463r" name="user.invalid_username"> <unit id="NcM463r" name="user.invalid_username">
@ -164,7 +164,7 @@
</notes> </notes>
<segment state="translated"> <segment state="translated">
<source>user.invalid_username</source> <source>user.invalid_username</source>
<target>用户名无效</target> <target>用户名只能包含字母、数字、下划线、点、加号或减号。</target>
</segment> </segment>
</unit> </unit>
<unit id="lZvhKYu" name="validator.noneofitschild.self"> <unit id="lZvhKYu" name="validator.noneofitschild.self">
@ -173,7 +173,7 @@
</notes> </notes>
<segment state="translated"> <segment state="translated">
<source>validator.noneofitschild.self</source> <source>validator.noneofitschild.self</source>
<target>一个元素不能是自己的父元素!</target> <target>一个元素不能是它自己的父元素。</target>
</segment> </segment>
</unit> </unit>
<unit id="pr07aV4" name="validator.noneofitschild.children"> <unit id="pr07aV4" name="validator.noneofitschild.children">
@ -182,139 +182,163 @@
</notes> </notes>
<segment state="translated"> <segment state="translated">
<source>validator.noneofitschild.children</source> <source>validator.noneofitschild.children</source>
<target>你不能将子元素设置为他们父元素 (这会导致循环)</target> <target>不能将子元素指定为父元素(会导致循环)。</target>
</segment> </segment>
</unit> </unit>
<unit id="ayNr6QK" name="validator.select_valid_category"> <unit id="ayNr6QK" name="validator.select_valid_category">
<segment state="translated"> <segment state="translated">
<source>validator.select_valid_category</source> <source>validator.select_valid_category</source>
<target>选择一个有效类别</target> <target>选择一个有效类别</target>
</segment> </segment>
</unit> </unit>
<unit id="6vIlN5q" name="validator.part_lot.only_existing"> <unit id="6vIlN5q" name="validator.part_lot.only_existing">
<segment state="translated"> <segment state="translated">
<source>validator.part_lot.only_existing</source> <source>validator.part_lot.only_existing</source>
<target>不能将一个被标记为 “只存在” 的新元件添加到此区域</target> <target>无法将新部件添加到此位置,因为它被标记为 "仅现有"</target>
</segment> </segment>
</unit> </unit>
<unit id="3xoKOIS" name="validator.part_lot.location_full.no_increase"> <unit id="3xoKOIS" name="validator.part_lot.location_full.no_increase">
<segment state="translated"> <segment state="translated">
<source>validator.part_lot.location_full.no_increase</source> <source>validator.part_lot.location_full.no_increase</source>
<target>区域已满。不能再增加数量 (新值必须小于 {{ old_amount }})。</target> <target>位置已满。数量无法增加(增加值必须小于 {{ old_amount }}。</target>
</segment> </segment>
</unit> </unit>
<unit id="R6Ov4Yt" name="validator.part_lot.location_full"> <unit id="R6Ov4Yt" name="validator.part_lot.location_full">
<segment state="translated"> <segment state="translated">
<source>validator.part_lot.location_full</source> <source>validator.part_lot.location_full</source>
<target>区域已满。不能再添加新元件。</target> <target>位置已满。无法添加新部件。</target>
</segment> </segment>
</unit> </unit>
<unit id="BNQk2e7" name="validator.part_lot.single_part"> <unit id="BNQk2e7" name="validator.part_lot.single_part">
<segment state="translated"> <segment state="translated">
<source>validator.part_lot.single_part</source> <source>validator.part_lot.single_part</source>
<target>这个区域只能添加单个元件,并且该区域已满!</target> <target>该位置只能储存一个部件。</target>
</segment> </segment>
</unit> </unit>
<unit id="4gPskOG" name="validator.attachment.must_not_be_null"> <unit id="4gPskOG" name="validator.attachment.must_not_be_null">
<segment state="translated"> <segment state="translated">
<source>validator.attachment.must_not_be_null</source> <source>validator.attachment.must_not_be_null</source>
<target>你必须选择一个附件类型!</target> <target>必须选择附件类型。</target>
</segment> </segment>
</unit> </unit>
<unit id="cDDVrWT" name="validator.orderdetail.supplier_must_not_be_null"> <unit id="cDDVrWT" name="validator.orderdetail.supplier_must_not_be_null">
<segment state="translated"> <segment state="translated">
<source>validator.orderdetail.supplier_must_not_be_null</source> <source>validator.orderdetail.supplier_must_not_be_null</source>
<target>你必须选择一个供应商!</target> <target>必须选择供应商。</target>
</segment> </segment>
</unit> </unit>
<unit id="k5DDdB4" name="validator.measurement_unit.use_si_prefix_needs_unit"> <unit id="k5DDdB4" name="validator.measurement_unit.use_si_prefix_needs_unit">
<segment state="translated"> <segment state="translated">
<source>validator.measurement_unit.use_si_prefix_needs_unit</source> <source>validator.measurement_unit.use_si_prefix_needs_unit</source>
<target>如果你需要启用 SI 前缀,你必须设置一个单位符号!</target> <target>要启用 SI 前缀,必须设置单位符号。</target>
</segment> </segment>
</unit> </unit>
<unit id="DuzIOCr" name="part.ipn.must_be_unique"> <unit id="DuzIOCr" name="part.ipn.must_be_unique">
<segment state="translated"> <segment state="translated">
<source>part.ipn.must_be_unique</source> <source>part.ipn.must_be_unique</source>
<target>内部元件编号必须是唯一的。{{ value }} 已经被使用了</target> <target>内部部件号是唯一的。{{ value }} 已被使用</target>
</segment> </segment>
</unit> </unit>
<unit id="Z4Kuuo2" name="validator.project.bom_entry.name_or_part_needed"> <unit id="Z4Kuuo2" name="validator.project.bom_entry.name_or_part_needed">
<segment state="translated"> <segment state="translated">
<source>validator.project.bom_entry.name_or_part_needed</source> <source>validator.project.bom_entry.name_or_part_needed</source>
<target>你需要选择一个元件作为 BOM 条目或给非 BOM 条目设置一个名称。</target> <target>您必须为 BOM 条目选择部件,或为非部件 BOM 条目设置名称。</target>
</segment> </segment>
</unit> </unit>
<unit id="WF_v4ih" name="project.bom_entry.name_already_in_bom"> <unit id="WF_v4ih" name="project.bom_entry.name_already_in_bom">
<segment state="translated"> <segment state="translated">
<source>project.bom_entry.name_already_in_bom</source> <source>project.bom_entry.name_already_in_bom</source>
<target>已经存在一个同名的 BOM 条目!</target> <target>已存在具有该名称的 BOM 条目。</target>
</segment> </segment>
</unit> </unit>
<unit id="5v4p85H" name="project.bom_entry.part_already_in_bom"> <unit id="5v4p85H" name="project.bom_entry.part_already_in_bom">
<segment state="translated"> <segment state="translated">
<source>project.bom_entry.part_already_in_bom</source> <source>project.bom_entry.part_already_in_bom</source>
<target>这个元件已经在 BOM 中!</target> <target>该部件已存在于 BOM 中。</target>
</segment> </segment>
</unit> </unit>
<unit id="3lM32Tw" name="project.bom_entry.mountnames_quantity_mismatch"> <unit id="3lM32Tw" name="project.bom_entry.mountnames_quantity_mismatch">
<segment state="translated"> <segment state="translated">
<source>project.bom_entry.mountnames_quantity_mismatch</source> <source>project.bom_entry.mountnames_quantity_mismatch</source>
<target>安装名称的数量必须与 BOM 数量匹配!</target> <target>挂载名称的数量必须与 BOM 数量匹配。</target>
</segment> </segment>
</unit> </unit>
<unit id="x47D5WT" name="project.bom_entry.can_not_add_own_builds_part"> <unit id="x47D5WT" name="project.bom_entry.can_not_add_own_builds_part">
<segment state="translated"> <segment state="translated">
<source>project.bom_entry.can_not_add_own_builds_part</source> <source>project.bom_entry.can_not_add_own_builds_part</source>
<target>您无法将项目自己的构建部分添加到 BOM。</target> <target>您无法将项目自己的生产映射部件添加到 BOM 中。</target>
</segment> </segment>
</unit> </unit>
<unit id="2x2XDI_" name="project.bom_has_to_include_all_subelement_parts"> <unit id="2x2XDI_" name="project.bom_has_to_include_all_subelement_parts">
<segment state="translated"> <segment state="translated">
<source>project.bom_has_to_include_all_subelement_parts</source> <source>project.bom_has_to_include_all_subelement_parts</source>
<target>项目 BOM 必须包括所有子项目构建部件。 项目 %project_name% 的 %part_name% 元件缺失!</target> <target>项目 BOM 必须包括所有子项目生产的部件。项目 %project_name% 的 %part_name% 部件丢失。</target>
</segment> </segment>
</unit> </unit>
<unit id="U9b1EzD" name="project.bom_entry.price_not_allowed_on_parts"> <unit id="U9b1EzD" name="project.bom_entry.price_not_allowed_on_parts">
<segment state="translated"> <segment state="translated">
<source>project.bom_entry.price_not_allowed_on_parts</source> <source>project.bom_entry.price_not_allowed_on_parts</source>
<target>与元件关联的 BOM 条目不允许定价。 请使用元件的价格定义。</target> <target>与部件关联的 BOM 条目上不允许有价格。请在部件上定义价格。</target>
</segment> </segment>
</unit> </unit>
<unit id="ID056SR" name="validator.project_build.lot_bigger_than_needed"> <unit id="ID056SR" name="validator.project_build.lot_bigger_than_needed">
<segment state="translated"> <segment state="translated">
<source>validator.project_build.lot_bigger_than_needed</source> <source>validator.project_build.lot_bigger_than_needed</source>
<target>您选择的退还数量超出了所需数量! 移除不必要的数量。</target> <target>选择的提取数量超出所需数量。</target>
</segment> </segment>
</unit> </unit>
<unit id="6hV5UqD" name="validator.project_build.lot_smaller_than_needed"> <unit id="6hV5UqD" name="validator.project_build.lot_smaller_than_needed">
<segment state="translated"> <segment state="translated">
<source>validator.project_build.lot_smaller_than_needed</source> <source>validator.project_build.lot_smaller_than_needed</source>
<target>您选择的提取数量少于构建所需的数量! 添加额外数量。</target> <target>选择的提取数量少于所需数量。</target>
</segment> </segment>
</unit> </unit>
<unit id="G9ZKt.4" name="part.name.must_match_category_regex"> <unit id="G9ZKt.4" name="part.name.must_match_category_regex">
<segment state="translated"> <segment state="translated">
<source>part.name.must_match_category_regex</source> <source>part.name.must_match_category_regex</source>
<target>部名称与类别指定的正则表达式不匹配:%regex%</target> <target>部名称与类别指定的正则表达式不匹配:%regex%</target>
</segment> </segment>
</unit> </unit>
<unit id="m8kMFhf" name="validator.attachment.name_not_blank"> <unit id="m8kMFhf" name="validator.attachment.name_not_blank">
<segment state="translated"> <segment state="translated">
<source>validator.attachment.name_not_blank</source> <source>validator.attachment.name_not_blank</source>
<target>在此处设置一个值,或上传文件以自动使用其文件名作为附件的名称。</target> <target>手动设置值,或上传文件使用其文件名作为附件的名称。</target>
</segment> </segment>
</unit> </unit>
<unit id="nwGaNBW" name="validator.part_lot.owner_must_match_storage_location_owner"> <unit id="nwGaNBW" name="validator.part_lot.owner_must_match_storage_location_owner">
<segment state="translated"> <segment state="translated">
<source>validator.part_lot.owner_must_match_storage_location_owner</source> <source>validator.part_lot.owner_must_match_storage_location_owner</source>
<target>该批次的所有者必须与所选存储位置的所有者 (%owner_name%) 匹配!</target> <target>该 批次的所有者 必须与 所选存储位置的所有者(%owner_name%) 匹配!</target>
</segment> </segment>
</unit> </unit>
<unit id="HXSz3nQ" name="validator.part_lot.owner_must_not_be_anonymous"> <unit id="HXSz3nQ" name="validator.part_lot.owner_must_not_be_anonymous">
<segment state="translated"> <segment state="translated">
<source>validator.part_lot.owner_must_not_be_anonymous</source> <source>validator.part_lot.owner_must_not_be_anonymous</source>
<target>块所有者不能是匿名用户!</target> <target>批次所有者不能是匿名用户。</target>
</segment>
</unit>
<unit id="N8aA0Uh" name="validator.part_association.must_set_an_value_if_type_is_other">
<segment state="translated">
<source>validator.part_association.must_set_an_value_if_type_is_other</source>
<target>如果将类型设置为 "other" 则必须为其设置一个描述性值。</target>
</segment>
</unit>
<unit id="9VYNZ4v" name="validator.part_association.part_cannot_be_associated_with_itself">
<segment state="translated">
<source>validator.part_association.part_cannot_be_associated_with_itself</source>
<target>部件不能与自己关联。</target>
</segment>
</unit>
<unit id="csc1PNn" name="validator.part_association.already_exists">
<segment state="translated">
<source>validator.part_association.already_exists</source>
<target>与此部件的关联已存在。</target>
</segment>
</unit>
<unit id="sfW4NYE" name="validator.part_lot.vendor_barcode_must_be_unique">
<segment state="translated">
<source>validator.part_lot.vendor_barcode_must_be_unique</source>
<target>该供应商条码已在另一批次中使用。条形码必须是唯一的</target>
</segment> </segment>
</unit> </unit>
</file> </file>

1145
yarn.lock

File diff suppressed because it is too large Load diff