mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-02-26 11:42:33 +01:00
Compare commits
3 Commits
copilot/de
...
copilot/wr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d33b2c725 | ||
|
|
9629e849b9 | ||
|
|
ca246c5e80 |
@@ -26,28 +26,6 @@ if [ "$1" = 'frankenphp' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then
|
||||
composer install --prefer-dist --no-progress --no-interaction
|
||||
fi
|
||||
|
||||
# Install additional composer packages if COMPOSER_EXTRA_PACKAGES is set
|
||||
if [ -n "$COMPOSER_EXTRA_PACKAGES" ]; then
|
||||
echo "Installing additional composer packages: $COMPOSER_EXTRA_PACKAGES"
|
||||
# Note: COMPOSER_EXTRA_PACKAGES is intentionally not quoted to allow word splitting
|
||||
# This enables passing multiple package names separated by spaces
|
||||
# shellcheck disable=SC2086
|
||||
composer require $COMPOSER_EXTRA_PACKAGES --no-install --no-interaction --no-progress
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Running composer install to install packages without dev dependencies..."
|
||||
composer install --no-dev --no-interaction --no-progress --optimize-autoloader
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Successfully installed additional composer packages"
|
||||
else
|
||||
echo "Failed to install composer dependencies"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Failed to add additional composer packages to composer.json"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if grep -q ^DATABASE_URL= .env; then
|
||||
echo "Waiting for database to be ready..."
|
||||
ATTEMPTS_LEFT_TO_REACH_DATABASE=60
|
||||
|
||||
@@ -39,28 +39,6 @@ if [ -d /var/www/html/var/db ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Install additional composer packages if COMPOSER_EXTRA_PACKAGES is set
|
||||
if [ -n "$COMPOSER_EXTRA_PACKAGES" ]; then
|
||||
echo "Installing additional composer packages: $COMPOSER_EXTRA_PACKAGES"
|
||||
# Note: COMPOSER_EXTRA_PACKAGES is intentionally not quoted to allow word splitting
|
||||
# This enables passing multiple package names separated by spaces
|
||||
# shellcheck disable=SC2086
|
||||
sudo -E -u www-data composer require $COMPOSER_EXTRA_PACKAGES --no-install --no-interaction --no-progress
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Running composer install to install packages without dev dependencies..."
|
||||
sudo -E -u www-data composer install --no-dev --no-interaction --no-progress --optimize-autoloader
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Successfully installed additional composer packages"
|
||||
else
|
||||
echo "Failed to install composer dependencies"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Failed to add additional composer packages to composer.json"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Start PHP-FPM (the PHP_VERSION is replaced by the configured version in the Dockerfile)
|
||||
php-fpmPHP_VERSION -F &
|
||||
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
# Entity Inheritance Hierarchy Decomposition
|
||||
|
||||
## Overview
|
||||
|
||||
This refactoring decomposes the deep entity inheritance hierarchy into a more flexible trait-based architecture. This provides better code reusability, composition, and maintainability.
|
||||
|
||||
## Architecture Diagram
|
||||
|
||||
### Before (Deep Inheritance):
|
||||
```
|
||||
AbstractDBElement (ID logic)
|
||||
└─ AbstractNamedDBElement (name + timestamps)
|
||||
└─ AttachmentContainingDBElement (attachments)
|
||||
└─ AbstractStructuralDBElement (tree/hierarchy + parameters)
|
||||
├─ AbstractPartsContainingDBElement
|
||||
│ ├─ Category
|
||||
│ ├─ Footprint
|
||||
│ ├─ StorageLocation
|
||||
│ └─ AbstractCompany (company fields)
|
||||
│ ├─ Manufacturer
|
||||
│ └─ Supplier
|
||||
```
|
||||
|
||||
### After (Trait Composition):
|
||||
```
|
||||
Traits: Interfaces:
|
||||
- DBElementTrait - DBElementInterface
|
||||
- NamedElementTrait - NamedElementInterface
|
||||
- TimestampTrait - TimeStampableInterface
|
||||
- AttachmentsTrait - HasAttachmentsInterface
|
||||
- MasterAttachmentTrait - HasMasterAttachmentInterface
|
||||
- StructuralElementTrait - StructuralElementInterface
|
||||
- ParametersTrait - HasParametersInterface
|
||||
- CompanyTrait - CompanyInterface
|
||||
|
||||
Class Hierarchy (now uses traits):
|
||||
AbstractDBElement (uses DBElementTrait, implements DBElementInterface)
|
||||
└─ AbstractNamedDBElement (uses NamedElementTrait + TimestampTrait)
|
||||
└─ AttachmentContainingDBElement (uses AttachmentsTrait + MasterAttachmentTrait)
|
||||
└─ AbstractStructuralDBElement (uses StructuralElementTrait + ParametersTrait)
|
||||
├─ AbstractPartsContainingDBElement
|
||||
│ ├─ Category (gets all traits via inheritance)
|
||||
│ ├─ Footprint (gets all traits via inheritance)
|
||||
│ └─ AbstractCompany (uses CompanyTrait)
|
||||
│ ├─ Manufacturer
|
||||
│ └─ Supplier
|
||||
```
|
||||
|
||||
## Changes Made
|
||||
|
||||
### New Traits Created
|
||||
|
||||
1. **DBElementTrait** (`src/Entity/Base/DBElementTrait.php`)
|
||||
- Provides basic database element functionality with an ID
|
||||
- Includes `getID()` method and clone helper
|
||||
- Extracted from `AbstractDBElement`
|
||||
|
||||
2. **NamedElementTrait** (`src/Entity/Base/NamedElementTrait.php`)
|
||||
- Provides named element functionality (name property and methods)
|
||||
- Includes `getName()`, `setName()`, and `__toString()` methods
|
||||
- Extracted from `AbstractNamedDBElement`
|
||||
|
||||
3. **AttachmentsTrait** (`src/Entity/Base/AttachmentsTrait.php`)
|
||||
- Provides attachments collection functionality
|
||||
- Includes methods for adding, removing, and getting attachments
|
||||
- Includes clone helper for deep cloning attachments
|
||||
- Extracted from `AttachmentContainingDBElement`
|
||||
|
||||
4. **StructuralElementTrait** (`src/Entity/Base/StructuralElementTrait.php`)
|
||||
- Provides tree/hierarchy functionality for structural elements
|
||||
- Includes parent/child relationships, path calculations, level tracking
|
||||
- Includes methods like `isRoot()`, `isChildOf()`, `getFullPath()`, etc.
|
||||
- Extracted from `AbstractStructuralDBElement`
|
||||
|
||||
5. **CompanyTrait** (`src/Entity/Base/CompanyTrait.php`)
|
||||
- Provides company-specific fields (address, phone, email, website, etc.)
|
||||
- Includes getters and setters for all company fields
|
||||
- Extracted from `AbstractCompany`
|
||||
|
||||
### New Interfaces Created
|
||||
|
||||
1. **DBElementInterface** (`src/Entity/Contracts/DBElementInterface.php`)
|
||||
- Interface for entities with a database ID
|
||||
- Defines `getID()` method
|
||||
|
||||
2. **StructuralElementInterface** (`src/Entity/Contracts/StructuralElementInterface.php`)
|
||||
- Interface for structural/hierarchical elements
|
||||
- Defines methods for tree navigation and hierarchy
|
||||
|
||||
3. **CompanyInterface** (`src/Entity/Contracts/CompanyInterface.php`)
|
||||
- Interface for company entities
|
||||
- Defines basic company information accessors
|
||||
|
||||
4. **HasParametersInterface** (`src/Entity/Contracts/HasParametersInterface.php`)
|
||||
- Interface for entities that have parameters
|
||||
- Defines `getParameters()` method
|
||||
|
||||
### Refactored Classes
|
||||
|
||||
1. **AbstractDBElement**
|
||||
- Now uses `DBElementTrait`
|
||||
- Implements `DBElementInterface`
|
||||
- Simplified to just use the trait instead of duplicating code
|
||||
|
||||
2. **AbstractNamedDBElement**
|
||||
- Now uses `NamedElementTrait` in addition to existing `TimestampTrait`
|
||||
- Cleaner implementation with trait composition
|
||||
|
||||
3. **AttachmentContainingDBElement**
|
||||
- Now uses `AttachmentsTrait` and `MasterAttachmentTrait`
|
||||
- Simplified constructor and clone methods
|
||||
|
||||
4. **AbstractStructuralDBElement**
|
||||
- Now uses `StructuralElementTrait` and `ParametersTrait`
|
||||
- Implements `StructuralElementInterface` and `HasParametersInterface`
|
||||
- Much cleaner with most functionality extracted to trait
|
||||
|
||||
5. **AbstractCompany**
|
||||
- Now uses `CompanyTrait`
|
||||
- Implements `CompanyInterface`
|
||||
- Significantly simplified from ~260 lines to ~20 lines
|
||||
|
||||
## Benefits
|
||||
|
||||
### 1. **Better Code Reusability**
|
||||
- Traits can be reused in different contexts without requiring inheritance
|
||||
- Easier to mix and match functionality
|
||||
|
||||
### 2. **Improved Maintainability**
|
||||
- Each trait focuses on a single concern (SRP - Single Responsibility Principle)
|
||||
- Easier to locate and modify specific functionality
|
||||
- Reduced code duplication
|
||||
|
||||
### 3. **More Flexible Architecture**
|
||||
- Entities can now compose functionality as needed
|
||||
- Not locked into a rigid inheritance hierarchy
|
||||
- Easier to add new functionality without modifying base classes
|
||||
|
||||
### 4. **Better Testability**
|
||||
- Traits can be tested independently
|
||||
- Easier to mock specific functionality
|
||||
|
||||
### 5. **Clearer Contracts**
|
||||
- Interfaces make dependencies explicit
|
||||
- Better IDE support and type hinting
|
||||
|
||||
## Migration Path
|
||||
|
||||
This refactoring is backward compatible - all existing entities continue to work as before. The changes are internal to the base classes and do not affect the public API.
|
||||
|
||||
### For New Entities
|
||||
|
||||
New entities can now:
|
||||
1. Use traits directly without deep inheritance
|
||||
2. Mix and match functionality as needed
|
||||
3. Implement only the interfaces they need
|
||||
|
||||
Example:
|
||||
```php
|
||||
class MyCustomEntity extends AbstractDBElement implements NamedElementInterface
|
||||
{
|
||||
use NamedElementTrait;
|
||||
|
||||
// Custom functionality
|
||||
}
|
||||
```
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Trait Usage Pattern
|
||||
|
||||
All traits follow this pattern:
|
||||
1. Declare properties with appropriate Doctrine/validation annotations
|
||||
2. Provide initialization methods (e.g., `initializeAttachments()`)
|
||||
3. Provide business logic methods
|
||||
4. Provide clone helpers for deep cloning when needed
|
||||
|
||||
### Interface Contracts
|
||||
|
||||
All interfaces define the minimal contract required for that functionality:
|
||||
- DBElementInterface: requires `getID()`
|
||||
- NamedElementInterface: requires `getName()`
|
||||
- StructuralElementInterface: requires hierarchy methods
|
||||
- CompanyInterface: requires company info accessors
|
||||
- HasParametersInterface: requires `getParameters()`
|
||||
|
||||
## Future Improvements
|
||||
|
||||
Potential future enhancements:
|
||||
1. Extract more functionality from remaining abstract classes
|
||||
2. Create more granular traits for specific features
|
||||
3. Add trait-specific unit tests
|
||||
4. Consider creating trait-based mixins for common entity patterns
|
||||
@@ -1,141 +0,0 @@
|
||||
# Entity Inheritance Hierarchy Refactoring - Implementation Summary
|
||||
|
||||
## Task Completed
|
||||
Successfully decomposed the deep entity inheritance hierarchy into traits and interfaces for better architecture.
|
||||
|
||||
## Changes Overview
|
||||
|
||||
### Files Modified (5)
|
||||
1. `src/Entity/Base/AbstractDBElement.php` - Now uses DBElementTrait
|
||||
2. `src/Entity/Base/AbstractNamedDBElement.php` - Now uses NamedElementTrait
|
||||
3. `src/Entity/Attachments/AttachmentContainingDBElement.php` - Now uses AttachmentsTrait
|
||||
4. `src/Entity/Base/AbstractStructuralDBElement.php` - Now uses StructuralElementTrait
|
||||
5. `src/Entity/Base/AbstractCompany.php` - Now uses CompanyTrait
|
||||
|
||||
### New Traits Created (5)
|
||||
1. `src/Entity/Base/DBElementTrait.php` - ID management functionality
|
||||
2. `src/Entity/Base/NamedElementTrait.php` - Name property and methods
|
||||
3. `src/Entity/Base/AttachmentsTrait.php` - Attachment collection management
|
||||
4. `src/Entity/Base/StructuralElementTrait.php` - Tree/hierarchy functionality
|
||||
5. `src/Entity/Base/CompanyTrait.php` - Company-specific fields
|
||||
|
||||
### New Interfaces Created (4)
|
||||
1. `src/Entity/Contracts/DBElementInterface.php` - Contract for DB entities
|
||||
2. `src/Entity/Contracts/StructuralElementInterface.php` - Contract for hierarchical entities
|
||||
3. `src/Entity/Contracts/CompanyInterface.php` - Contract for company entities
|
||||
4. `src/Entity/Contracts/HasParametersInterface.php` - Contract for parametrized entities
|
||||
|
||||
### Documentation Added (2)
|
||||
1. `ENTITY_REFACTORING.md` - Comprehensive documentation with architecture diagrams
|
||||
2. `IMPLEMENTATION_SUMMARY.md` - This file
|
||||
|
||||
## Impact Analysis
|
||||
|
||||
### Code Metrics
|
||||
- **Lines Added**: 1,291 (traits, interfaces, documentation)
|
||||
- **Lines Removed**: 740 (from base classes)
|
||||
- **Net Change**: +551 lines
|
||||
- **Code Reduction in Base Classes**: ~1000 lines moved to reusable traits
|
||||
|
||||
### Affected Classes
|
||||
All entities that extend from the modified base classes now benefit from the trait-based architecture:
|
||||
- Category, Footprint, StorageLocation, MeasurementUnit, PartCustomState
|
||||
- Manufacturer, Supplier
|
||||
- And all other entities in the inheritance chain
|
||||
|
||||
### Breaking Changes
|
||||
**None** - This is a backward-compatible refactoring. All public APIs remain unchanged.
|
||||
|
||||
## Benefits Achieved
|
||||
|
||||
### 1. Improved Code Reusability
|
||||
- Traits can be mixed and matched in different combinations
|
||||
- No longer locked into rigid inheritance hierarchy
|
||||
- Easier to create new entity types with specific functionality
|
||||
|
||||
### 2. Better Maintainability
|
||||
- Each trait has a single, well-defined responsibility
|
||||
- Easier to locate and modify specific functionality
|
||||
- Reduced code duplication across the codebase
|
||||
|
||||
### 3. Enhanced Flexibility
|
||||
- Future entities can compose functionality as needed
|
||||
- Can add new traits without modifying existing class hierarchy
|
||||
- Supports multiple inheritance patterns via trait composition
|
||||
|
||||
### 4. Clearer Contracts
|
||||
- Interfaces make dependencies and capabilities explicit
|
||||
- Better IDE support and auto-completion
|
||||
- Improved static analysis capabilities
|
||||
|
||||
### 5. Preserved Backward Compatibility
|
||||
- All existing entities continue to work unchanged
|
||||
- No modifications required to controllers, services, or repositories
|
||||
- Database schema remains the same
|
||||
|
||||
## Testing Notes
|
||||
|
||||
### Validation Performed
|
||||
- ✅ PHP syntax validation on all modified files
|
||||
- ✅ Verified all traits can be loaded
|
||||
- ✅ Code review feedback addressed
|
||||
- ✅ Documentation completeness checked
|
||||
|
||||
### Recommended Testing
|
||||
Before merging, the following tests should be run:
|
||||
1. Full PHPUnit test suite
|
||||
2. Static analysis (PHPStan level 5)
|
||||
3. Integration tests for entities
|
||||
4. Database migration tests
|
||||
|
||||
## Code Review Feedback Addressed
|
||||
|
||||
All code review comments were addressed:
|
||||
1. ✅ Fixed typo: "addres" → "address"
|
||||
2. ✅ Removed unnecessary comma in docstrings
|
||||
3. ✅ Fixed nullable return type documentation
|
||||
4. ✅ Fixed inconsistent nullable string initialization
|
||||
5. ✅ Replaced isset() with direct null comparison
|
||||
6. ✅ Documented trait dependencies (MasterAttachmentTrait)
|
||||
7. ✅ Fixed grammar: "a most top element" → "the topmost element"
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Potential improvements for future iterations:
|
||||
1. Extract more granular traits for specific features
|
||||
2. Create trait-specific unit tests
|
||||
3. Consider extracting validation logic into traits
|
||||
4. Add more interfaces for fine-grained contracts
|
||||
5. Create documentation for custom entity development
|
||||
|
||||
## Migration Guide for Developers
|
||||
|
||||
### Using Traits in New Entities
|
||||
|
||||
```php
|
||||
// Example: Creating a new entity with specific traits
|
||||
use App\Entity\Base\DBElementTrait;
|
||||
use App\Entity\Base\NamedElementTrait;
|
||||
use App\Entity\Contracts\DBElementInterface;
|
||||
use App\Entity\Contracts\NamedElementInterface;
|
||||
|
||||
class MyEntity implements DBElementInterface, NamedElementInterface
|
||||
{
|
||||
use DBElementTrait;
|
||||
use NamedElementTrait;
|
||||
|
||||
// Custom functionality here
|
||||
}
|
||||
```
|
||||
|
||||
### Trait Dependencies
|
||||
|
||||
Some traits have dependencies on other traits or methods:
|
||||
- **StructuralElementTrait** requires `getName()` and `getID()` methods
|
||||
- **AttachmentsTrait** works best with `MasterAttachmentTrait`
|
||||
|
||||
Refer to trait documentation for specific requirements.
|
||||
|
||||
## Conclusion
|
||||
|
||||
This refactoring successfully modernizes the entity architecture while maintaining full backward compatibility. The trait-based approach provides better code organization, reusability, and maintainability for the Part-DB project.
|
||||
1400
composer.lock
generated
1400
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
framework:
|
||||
default_locale: 'en'
|
||||
# Just enable the locales we need for performance reasons.
|
||||
enabled_locale: '%partdb.locale_menu%'
|
||||
enabled_locale: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh', 'pl']
|
||||
translator:
|
||||
default_path: '%kernel.project_dir%/translations'
|
||||
fallbacks:
|
||||
|
||||
3278
config/reference.php
3278
config/reference.php
File diff suppressed because it is too large
Load Diff
@@ -33,8 +33,8 @@ services:
|
||||
App\:
|
||||
resource: '../src/'
|
||||
exclude:
|
||||
- '../src/Entity/'
|
||||
- '../src/Helpers/'
|
||||
- '../src/DataFixtures/'
|
||||
- '../src/Doctrine/Purger/'
|
||||
|
||||
# controllers are imported separately to make sure services can be injected
|
||||
# as action arguments even if you don't extend any base controller class
|
||||
@@ -274,12 +274,21 @@ services:
|
||||
tags:
|
||||
- { name: monolog.processor }
|
||||
|
||||
App\Doctrine\Purger\ResetAutoIncrementPurgerFactory:
|
||||
tags:
|
||||
- { name: 'doctrine.fixtures.purger_factory', alias: 'reset_autoincrement_purger' }
|
||||
|
||||
when@test: &test
|
||||
services:
|
||||
|
||||
App\DataFixtures\:
|
||||
resource: '../src/DataFixtures/'
|
||||
autoconfigure: true
|
||||
autowire: true
|
||||
|
||||
App\Doctrine\Purger\:
|
||||
resource: '../src/Doctrine/Purger/'
|
||||
|
||||
App\Doctrine\Purger\ResetAutoIncrementPurgerFactory:
|
||||
tags:
|
||||
- { name: 'doctrine.fixtures.purger_factory', alias: 'reset_autoincrement_purger' }
|
||||
|
||||
# Decorate the doctrine fixtures load command to use our custom purger by default
|
||||
doctrine.fixtures_load_command.custom:
|
||||
decorates: doctrine.fixtures_load_command
|
||||
@@ -288,3 +297,6 @@ when@test: &test
|
||||
- '@doctrine.fixtures.loader'
|
||||
- '@doctrine'
|
||||
- { default: '@App\Doctrine\Purger\DoNotUsePurgerFactory' }
|
||||
|
||||
when@dev:
|
||||
*test
|
||||
|
||||
@@ -50,7 +50,10 @@ A part entity has many fields, which can be used to describe it better. Most of
|
||||
* **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 that identifies a part in the
|
||||
database. This ID depends on when a part was created and cannot 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. Part-DB
|
||||
can automatically suggest IPNs based on category prefixes and sequential numbering. See the
|
||||
[IPN Generation documentation]({% link usage/ipn_generation.md %}) for detailed information on how to set up and use
|
||||
this feature.
|
||||
|
||||
### Stock / Part lot
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ It is installed on a web server and so can be accessed with any browser without
|
||||
* 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
|
||||
like datasheets or pictures with the parts.
|
||||
* Automatic Internal Part Number (IPN) generation with customizable prefixes and numbering schemes (see [IPN documentation]({% link usage/ipn_generation.md %}))
|
||||
* Synonym system to customize terminology throughout the application (see [Synonyms documentation]({% link usage/synonyms.md %}))
|
||||
* 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 built-in barcode scanner
|
||||
* User system with groups and detailed (fine-grained) permissions.
|
||||
|
||||
@@ -15,75 +15,13 @@ To make emails work you have to properly configure a mail provider in Part-DB.
|
||||
## Configuration
|
||||
|
||||
Part-DB uses [Symfony Mailer](https://symfony.com/doc/current/mailer.html) to send emails, which supports multiple
|
||||
mail providers (like Mailgun, SendGrid, or Brevo). If you want to use one of these providers, check the Symfony
|
||||
automatic mail providers (like MailChimp or SendGrid). If you want to use one of these providers, check the Symfony
|
||||
Mailer documentation for more information.
|
||||
|
||||
We will only cover the configuration of an SMTP provider here, which is sufficient for most use-cases.
|
||||
You will need an email account, which you can use to send emails from via password-based SMTP authentication, this account
|
||||
should be dedicated to Part-DB.
|
||||
|
||||
### Using specialized mail providers (Mailgun, SendGrid, etc.)
|
||||
|
||||
If you want to use a specialized mail provider like Mailgun, SendGrid, Brevo (formerly Sendinblue), Amazon SES, or
|
||||
Postmark instead of SMTP, you need to install the corresponding Symfony mailer package first.
|
||||
|
||||
#### Docker installation
|
||||
|
||||
If you are using Part-DB in Docker, you can install additional mailer packages by setting the `COMPOSER_EXTRA_PACKAGES`
|
||||
environment variable in your `docker-compose.yaml`:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- COMPOSER_EXTRA_PACKAGES=symfony/mailgun-mailer
|
||||
- MAILER_DSN=mailgun+api://API_KEY:DOMAIN@default
|
||||
- EMAIL_SENDER_EMAIL=noreply@yourdomain.com
|
||||
- EMAIL_SENDER_NAME=Part-DB
|
||||
- ALLOW_EMAIL_PW_RESET=1
|
||||
```
|
||||
|
||||
You can install multiple packages by separating them with spaces:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- COMPOSER_EXTRA_PACKAGES=symfony/mailgun-mailer symfony/sendgrid-mailer
|
||||
```
|
||||
|
||||
The packages will be installed automatically when the container starts.
|
||||
|
||||
Common mailer packages:
|
||||
- `symfony/mailgun-mailer` - For [Mailgun](https://www.mailgun.com/)
|
||||
- `symfony/sendgrid-mailer` - For [SendGrid](https://sendgrid.com/)
|
||||
- `symfony/brevo-mailer` - For [Brevo](https://www.brevo.com/) (formerly Sendinblue)
|
||||
- `symfony/amazon-mailer` - For [Amazon SES](https://aws.amazon.com/ses/)
|
||||
- `symfony/postmark-mailer` - For [Postmark](https://postmarkapp.com/)
|
||||
|
||||
#### Direct installation (non-Docker)
|
||||
|
||||
If you have installed Part-DB directly on your server (not in Docker), you need to manually install the required
|
||||
mailer package using composer.
|
||||
|
||||
Navigate to your Part-DB installation directory and run:
|
||||
|
||||
```bash
|
||||
# Install the package as the web server user
|
||||
sudo -u www-data composer require symfony/mailgun-mailer
|
||||
|
||||
# Clear the cache
|
||||
sudo -u www-data php bin/console cache:clear
|
||||
```
|
||||
|
||||
Replace `symfony/mailgun-mailer` with the package you need. You can install multiple packages at once:
|
||||
|
||||
```bash
|
||||
sudo -u www-data composer require symfony/mailgun-mailer symfony/sendgrid-mailer
|
||||
```
|
||||
|
||||
After installing the package, configure the `MAILER_DSN` in your `.env.local` file according to the provider's
|
||||
documentation (see [Symfony Mailer documentation](https://symfony.com/doc/current/mailer.html) for DSN format for
|
||||
each provider).
|
||||
|
||||
## SMTP Configuration
|
||||
|
||||
To configure the SMTP provider, you have to set the following environment variables:
|
||||
|
||||
`MAILER_DSN`: You have to provide the SMTP server address and the credentials for the email account here. The format is
|
||||
|
||||
@@ -80,11 +80,7 @@ services:
|
||||
#- BANNER=This is a test banner<br>with a line break
|
||||
|
||||
# If you use a reverse proxy in front of Part-DB, you must configure the trusted proxies IP addresses here (see reverse proxy documentation for more information):
|
||||
# - TRUSTED_PROXIES=127.0.0.0/8,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
|
||||
|
||||
# If you need to install additional composer packages (e.g., for specific mailer transports), you can specify them here:
|
||||
# The packages will be installed automatically when the container starts
|
||||
# - COMPOSER_EXTRA_PACKAGES=symfony/mailgun-mailer symfony/sendgrid-mailer
|
||||
# - 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 adding new ones). See [Configuration]({% link
|
||||
@@ -153,9 +149,6 @@ services:
|
||||
# Override value if you want to show a given text on homepage.
|
||||
# When this is commented out the webUI can be used to configure the banner
|
||||
#- BANNER=This is a test banner<br>with a line break
|
||||
|
||||
# If you need to install additional composer packages (e.g., for specific mailer transports), you can specify them here:
|
||||
# - COMPOSER_EXTRA_PACKAGES=symfony/mailgun-mailer symfony/sendgrid-mailer
|
||||
|
||||
database:
|
||||
container_name: partdb_database
|
||||
@@ -176,38 +169,6 @@ services:
|
||||
|
||||
```
|
||||
|
||||
### Installing additional composer packages
|
||||
|
||||
If you need to use specific mailer transports or other functionality that requires additional composer packages, you can
|
||||
install them automatically at container startup using the `COMPOSER_EXTRA_PACKAGES` environment variable.
|
||||
|
||||
For example, if you want to use Mailgun as your email provider, you need to install the `symfony/mailgun-mailer` package.
|
||||
Add the following to your docker-compose.yaml environment section:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- COMPOSER_EXTRA_PACKAGES=symfony/mailgun-mailer
|
||||
- MAILER_DSN=mailgun+api://API_KEY:DOMAIN@default
|
||||
```
|
||||
|
||||
You can specify multiple packages by separating them with spaces:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- COMPOSER_EXTRA_PACKAGES=symfony/mailgun-mailer symfony/sendgrid-mailer
|
||||
```
|
||||
|
||||
{: .info }
|
||||
> The packages will be installed when the container starts. This may increase the container startup time on the first run.
|
||||
> The installed packages will persist in the container until it is recreated.
|
||||
|
||||
Common mailer packages you might need:
|
||||
- `symfony/mailgun-mailer` - For Mailgun email service
|
||||
- `symfony/sendgrid-mailer` - For SendGrid email service
|
||||
- `symfony/brevo-mailer` - For Brevo (formerly Sendinblue) email service
|
||||
- `symfony/amazon-mailer` - For Amazon SES email service
|
||||
- `symfony/postmark-mailer` - For Postmark email service
|
||||
|
||||
### Update Part-DB
|
||||
|
||||
You can update Part-DB by pulling the latest image and restarting the container.
|
||||
|
||||
@@ -50,21 +50,6 @@ docker-compose logs -f
|
||||
|
||||
Please include the error logs in your issue on GitHub, if you open an issue.
|
||||
|
||||
## KiCad Integration Issues
|
||||
|
||||
### "API responded with error code: 0: Unknown"
|
||||
|
||||
If you get this error when trying to connect KiCad to Part-DB, it is most likely caused by KiCad not trusting your SSL/TLS certificate.
|
||||
|
||||
**Cause:** KiCad does not trust self-signed SSL/TLS certificates.
|
||||
|
||||
**Solutions:**
|
||||
- Use HTTP instead of HTTPS for the `root_url` in your KiCad library configuration (only recommended for local networks)
|
||||
- Use a certificate from a trusted Certificate Authority (CA) like [Let's Encrypt](https://letsencrypt.org/)
|
||||
- Add your self-signed certificate to the system's trusted certificate store on the computer running KiCad (the exact steps depend on your operating system)
|
||||
|
||||
For more information about KiCad integration, see the [EDA / KiCad integration](../usage/eda_integration.md) documentation.
|
||||
|
||||
## Report Issue
|
||||
|
||||
If an error occurs, or you found a bug, please [open an issue on GitHub](https://github.com/Part-DB/Part-DB-server).
|
||||
|
||||
@@ -22,16 +22,6 @@ This also allows to configure available and usable parts and their properties in
|
||||
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 permission to access the Part-DB API and create API tokens. Every user can have their own account, or you set up a shared read-only account.
|
||||
|
||||
{: .warning }
|
||||
> **HTTPS with Self-Signed Certificates**
|
||||
>
|
||||
> KiCad does not trust self-signed SSL/TLS certificates. If your Part-DB instance uses HTTPS with a self-signed certificate, KiCad will fail to connect and show an error like: `API responded with error code: 0: Unknown`.
|
||||
>
|
||||
> To resolve this issue, you have the following options:
|
||||
> - Use HTTP instead of HTTPS for the `root_url` (only recommended for local networks)
|
||||
> - Use a certificate from a trusted Certificate Authority (CA) like [Let's Encrypt](https://letsencrypt.org/)
|
||||
> - Add your self-signed certificate to the system's trusted certificate store on the computer running KiCad (the exact steps depend on your operating system)
|
||||
|
||||
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 the Part-DB database, so a token with a read-only scope is enough.
|
||||
|
||||
306
docs/usage/ipn_generation.md
Normal file
306
docs/usage/ipn_generation.md
Normal file
@@ -0,0 +1,306 @@
|
||||
---
|
||||
title: Internal Part Number (IPN) Generation
|
||||
layout: default
|
||||
parent: Usage
|
||||
nav_order: 12
|
||||
---
|
||||
|
||||
# Internal Part Number (IPN) Generation
|
||||
|
||||
Part-DB supports automatic generation and management of Internal Part Numbers (IPNs) for your parts. IPNs are unique identifiers that help you organize and track your inventory in a structured way.
|
||||
|
||||
1. TOC
|
||||
{:toc}
|
||||
|
||||
## What is an IPN?
|
||||
|
||||
An Internal Part Number (IPN) is a unique identifier assigned to each part in your inventory. Unlike manufacturer part numbers (MPNs), IPNs are defined and controlled by you, following your own naming conventions and organizational structure.
|
||||
|
||||
IPNs are useful for:
|
||||
- Creating a consistent numbering scheme across your entire inventory
|
||||
- Organizing parts hierarchically based on categories
|
||||
- Quickly identifying and locating parts
|
||||
- Generating barcodes for parts
|
||||
- Integrating with external systems (like EDA tools)
|
||||
|
||||
## Basic Concepts
|
||||
|
||||
### IPN Structure
|
||||
|
||||
An IPN typically consists of several components:
|
||||
- **Prefix**: Identifies the category or type of part (e.g., "RES" for resistors, "CAP" for capacitors)
|
||||
- **Separator**: Divides different parts of the IPN (default is `-`)
|
||||
- **Number**: A sequential number that makes the IPN unique
|
||||
|
||||
Example: `RES-0001`, `CAP-IC-0042`, `MCU-ARM-1234`
|
||||
|
||||
### Category-Based IPN Prefixes
|
||||
|
||||
Categories in Part-DB can have their own IPN prefix. When creating a new part in a category, Part-DB can automatically suggest IPNs based on the category's prefix.
|
||||
|
||||
To set an IPN prefix for a category:
|
||||
1. Navigate to the category edit page
|
||||
2. Find the "Part IPN Prefix" field
|
||||
3. Enter your desired prefix (e.g., "RES", "CAP", "IC")
|
||||
|
||||
### Hierarchical Prefixes
|
||||
|
||||
Part-DB supports hierarchical IPN generation based on parent categories. For example:
|
||||
- Parent category "IC" with prefix "IC"
|
||||
- Child category "Microcontrollers" with prefix "MCU"
|
||||
- Generated IPN could be: `IC-MCU-0001`
|
||||
|
||||
This allows you to create deeply nested categorization schemes while maintaining clear IPNs.
|
||||
|
||||
## Configuring IPN Generation
|
||||
|
||||
You can configure IPN generation in the system settings under `Tools -> System -> Settings -> Miscellaneous -> IPN Suggest Settings`.
|
||||
|
||||
### Available Settings
|
||||
|
||||
#### Regex Pattern
|
||||
Define a regular expression pattern that valid IPNs must match. This helps enforce consistency across your inventory.
|
||||
|
||||
Example: `^[A-Za-z0-9]{3,4}(?:-[A-Za-z0-9]{3,4})*-\d{4}$`
|
||||
|
||||
This pattern requires:
|
||||
- 3-4 alphanumeric characters for prefixes
|
||||
- Optional additional prefix groups separated by `-`
|
||||
- Ending with a 4-digit number
|
||||
|
||||
#### Regex Help Text
|
||||
Provide custom help text that explains your IPN format to users. This text is shown when users are creating or editing parts.
|
||||
|
||||
#### Auto-Append Suffix
|
||||
When enabled, Part-DB automatically appends a suffix (`_1`, `_2`, etc.) to IPNs that would otherwise be duplicates. This prevents IPN collisions when multiple parts might generate the same IPN.
|
||||
|
||||
**Example:**
|
||||
- First part: `RES-0001`
|
||||
- Duplicate attempt: automatically becomes `RES-0001_1`
|
||||
- Next duplicate: automatically becomes `RES-0001_2`
|
||||
|
||||
#### Suggest Part Digits
|
||||
Defines how many digits should be used for the sequential part number (default: 4).
|
||||
- 4 digits: `0001` to `9999`
|
||||
- 6 digits: `000001` to `999999`
|
||||
|
||||
#### Use Duplicate Description
|
||||
When enabled, Part-DB will suggest the same IPN for parts with identical descriptions. This is useful when you want to track variants of the same component with the same IPN scheme.
|
||||
|
||||
#### Fallback Prefix
|
||||
The prefix to use when a category has no IPN prefix defined (default: `N.A.`). This ensures all parts can get an IPN suggestion even without category-specific prefixes.
|
||||
|
||||
#### Number Separator
|
||||
The character that separates the prefix from the number (default: `-`).
|
||||
|
||||
Example: With separator `-`, you get `RES-0001`. With separator `.`, you get `RES.0001`.
|
||||
|
||||
#### Category Separator
|
||||
The character that separates hierarchical category prefixes (default: `-`).
|
||||
|
||||
Example: With separator `-`, you get `IC-MCU-0001`. With separator `.`, you get `IC.MCU.0001`.
|
||||
|
||||
#### Global Prefix
|
||||
An optional prefix that is prepended to all IPNs in your system. Useful if you want to distinguish your inventory from other systems.
|
||||
|
||||
Example: With global prefix `ACME`, IPNs become `ACME-RES-0001`, `ACME-CAP-0042`, etc.
|
||||
|
||||
## Using IPN Suggestions
|
||||
|
||||
### When Creating a New Part
|
||||
|
||||
When you create a new part, Part-DB provides IPN suggestions based on:
|
||||
|
||||
1. **Global Prefix** (if configured): Suggestions using your global prefix
|
||||
2. **Description Matching** (if enabled): If another part has the same description, its IPN is suggested
|
||||
3. **Direct Category Prefix**: The IPN prefix of the part's assigned category
|
||||
4. **Hierarchical Prefixes**: IPNs combining parent category prefixes with the current category
|
||||
|
||||
Each suggestion includes:
|
||||
- The suggested IPN
|
||||
- A description of how it was generated
|
||||
- An auto-incremented version (ending with the next available number)
|
||||
|
||||
### IPN Suggestion Types
|
||||
|
||||
#### Common Prefixes
|
||||
These show just the prefix part without a number. Use these as a starting point to manually add your own number.
|
||||
|
||||
Example: `RES-` (you then type `RES-1234`)
|
||||
|
||||
#### Prefixes with Part Increment
|
||||
These show complete IPNs with automatically incremented numbers. The system finds the highest existing number with that prefix and suggests the next one.
|
||||
|
||||
Example: If `RES-0001` through `RES-0005` exist, the system suggests `RES-0006`.
|
||||
|
||||
### Manual IPN Entry
|
||||
|
||||
You can always manually enter any IPN you want. If you've configured a regex pattern, Part-DB will validate your IPN against it and show an error if it doesn't match.
|
||||
|
||||
## IPN Uniqueness
|
||||
|
||||
IPNs must be unique across your entire Part-DB instance. Part-DB enforces this constraint:
|
||||
|
||||
- When manually entering an IPN, you'll see an error if it already exists
|
||||
- When auto-append suffix is enabled, duplicate IPNs are automatically made unique
|
||||
- Existing parts retain their IPNs even if you change their category
|
||||
|
||||
## IPNs in Labels and Barcodes
|
||||
|
||||
IPNs can be used in label templates through placeholders:
|
||||
|
||||
- `[[IPN]]` - The IPN as text
|
||||
- `[[IPN_BARCODE_C39]]` - IPN as Code 39 barcode
|
||||
- `[[IPN_BARCODE_C128]]` - IPN as Code 128 barcode
|
||||
- `[[IPN_BARCODE_QR]]` - IPN as QR code
|
||||
|
||||
See the [Labels documentation]({% link usage/labels.md %}) for more information.
|
||||
|
||||
## IPNs in Barcode Scanning
|
||||
|
||||
Part-DB can scan barcodes containing IPNs to quickly find parts. When a barcode is scanned, Part-DB:
|
||||
1. Attempts to parse it as an IPN
|
||||
2. Searches for the part with that IPN
|
||||
3. Displays the part information
|
||||
|
||||
This enables quick inventory operations using barcode scanners.
|
||||
|
||||
## IPNs in EDA Integration
|
||||
|
||||
When using Part-DB with EDA tools like KiCad, the IPN is automatically added to the component fields as "Part-DB IPN". This creates a direct link between your schematic components and your Part-DB inventory.
|
||||
|
||||
See the [EDA Integration documentation]({% link usage/eda_integration.md %}) for more information.
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Choosing Prefixes
|
||||
|
||||
- **Keep them short**: 2-4 characters work well (e.g., "RES", "CAP", "IC")
|
||||
- **Make them memorable**: Use abbreviations that are obvious (avoid "XYZ" or "ABC")
|
||||
- **Be consistent**: Use the same style across all categories (all caps or all lowercase)
|
||||
- **Avoid ambiguity**: Don't use similar prefixes like "IC" and "1C"
|
||||
|
||||
### Numbering Schemes
|
||||
|
||||
- **Pad with zeros**: Use leading zeros for cleaner sorting (0001, 0042 instead of 1, 42)
|
||||
- **Leave room for growth**: If you have 50 parts now, use 4 digits (up to 9999) instead of 2
|
||||
- **Don't encode information**: Let the prefix and category do the work, not the number
|
||||
- **Sequential is fine**: You don't need gaps - 0001, 0002, 0003 is perfectly valid
|
||||
|
||||
### Hierarchical Categories
|
||||
|
||||
- **Limit depth**: 2-3 levels is usually sufficient (IC-MCU vs IC-MCU-ARM-STM32)
|
||||
- **Balance specificity**: More levels = longer IPNs but more precise categorization
|
||||
- **Consider searching**: Very specific categories are harder to search across
|
||||
|
||||
### Changing Your Scheme
|
||||
|
||||
- **Plan ahead**: Changing IPN schemes later is difficult
|
||||
- **Document your convention**: Add your IPN format to your regex help text
|
||||
- **Existing parts**: Don't feel obligated to renumber existing parts if you change schemes
|
||||
- **Migration**: Use import/export to batch-update IPNs if needed
|
||||
|
||||
## Common Issues and Solutions
|
||||
|
||||
### "IPN already exists"
|
||||
|
||||
**Problem**: You're trying to use an IPN that's already assigned to another part.
|
||||
|
||||
**Solutions**:
|
||||
- Choose a different number
|
||||
- Enable "Auto-Append Suffix" to automatically handle duplicates
|
||||
- Search for the existing part to see if it's a duplicate you should merge
|
||||
|
||||
### "IPN doesn't match regex pattern"
|
||||
|
||||
**Problem**: Your IPN doesn't follow the configured format.
|
||||
|
||||
**Solutions**:
|
||||
- Check the regex help text to understand the expected format
|
||||
- Contact your administrator if the regex is too restrictive
|
||||
- Use the suggested IPNs which are guaranteed to match
|
||||
|
||||
### Suggestions not showing
|
||||
|
||||
**Problem**: IPN suggestions are empty or not appearing.
|
||||
|
||||
**Solutions**:
|
||||
- Ensure the part has a category assigned
|
||||
- Check that the category has an IPN prefix defined
|
||||
- Verify that a fallback prefix is configured in settings
|
||||
- Save the part first before getting suggestions (for new parts)
|
||||
|
||||
### Wrong prefix being suggested
|
||||
|
||||
**Problem**: Part-DB suggests an IPN with the wrong prefix.
|
||||
|
||||
**Solutions**:
|
||||
- Check the part's category - suggestions are based on the assigned category
|
||||
- Verify parent categories and their prefixes if using hierarchical structure
|
||||
- Set the correct IPN prefix in the category settings
|
||||
- Use manual entry with your desired prefix
|
||||
|
||||
## Example Scenarios
|
||||
|
||||
### Simple Electronic Components Inventory
|
||||
|
||||
**Setup**:
|
||||
- Categories: Resistors, Capacitors, ICs, etc.
|
||||
- Prefixes: RES, CAP, IC
|
||||
- 4-digit numbering
|
||||
|
||||
**Results**:
|
||||
- `RES-0001` - 10kΩ resistor
|
||||
- `CAP-0001` - 100nF capacitor
|
||||
- `IC-0001` - ATmega328
|
||||
|
||||
### Professional Lab with Detailed Categories
|
||||
|
||||
**Setup**:
|
||||
- Hierarchical categories: Components > Passive > Resistors > Surface Mount
|
||||
- Prefixes: COMP, PAS, RES, SMD
|
||||
- Global prefix: LAB
|
||||
- 6-digit numbering
|
||||
|
||||
**Results**:
|
||||
- `LAB-COMP-PAS-RES-SMD-000001` - 0805 10kΩ resistor
|
||||
- `LAB-COMP-PAS-CAP-SMD-000001` - 0805 100nF capacitor
|
||||
|
||||
### Makerspace with Mixed Inventory
|
||||
|
||||
**Setup**:
|
||||
- Categories for electronics, mechanical parts, tools
|
||||
- Simple prefixes: ELEC, MECH, TOOL
|
||||
- Fallback prefix for miscellaneous: MISC
|
||||
- 4-digit numbering
|
||||
|
||||
**Results**:
|
||||
- `ELEC-0001` - Arduino Uno
|
||||
- `MECH-0001` - M3 screw set
|
||||
- `TOOL-0001` - Soldering iron
|
||||
- `MISC-0001` - Cable ties
|
||||
|
||||
## Environment Variables
|
||||
|
||||
IPN settings can be configured via environment variables (useful for Docker deployments):
|
||||
|
||||
- `IPN_SUGGEST_REGEX` - Override the regex pattern
|
||||
- `IPN_SUGGEST_REGEX_HELP` - Override the regex help text
|
||||
- `IPN_AUTO_APPEND_SUFFIX` - Enable/disable auto-append suffix (boolean)
|
||||
- `IPN_SUGGEST_PART_DIGITS` - Number of digits for part numbers (integer)
|
||||
- `IPN_USE_DUPLICATE_DESCRIPTION` - Enable/disable duplicate description matching (boolean)
|
||||
|
||||
Example in docker-compose.yaml:
|
||||
```yaml
|
||||
environment:
|
||||
IPN_SUGGEST_REGEX: "^[A-Z]{3}-\d{4}$"
|
||||
IPN_AUTO_APPEND_SUFFIX: "true"
|
||||
IPN_SUGGEST_PART_DIGITS: "4"
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Getting Started]({% link usage/getting_started.md %}) - Initial setup guide
|
||||
- [Concepts]({% link concepts.md %}) - Understanding Part-DB concepts
|
||||
- [Labels]({% link usage/labels.md %}) - Using IPNs in labels
|
||||
- [EDA Integration]({% link usage/eda_integration.md %}) - IPNs in electronic design tools
|
||||
346
docs/usage/synonyms.md
Normal file
346
docs/usage/synonyms.md
Normal file
@@ -0,0 +1,346 @@
|
||||
---
|
||||
title: Synonym System
|
||||
layout: default
|
||||
parent: Usage
|
||||
nav_order: 13
|
||||
---
|
||||
|
||||
# Synonym System
|
||||
|
||||
Part-DB includes a powerful synonym system that allows you to customize the terminology used throughout the application. This is especially useful when using Part-DB in contexts other than electronics, or when you want to adapt the interface to your organization's specific vocabulary.
|
||||
|
||||
1. TOC
|
||||
{:toc}
|
||||
|
||||
## What is the Synonym System?
|
||||
|
||||
The synonym system allows you to replace Part-DB's standard terminology with your own preferred terms. For example:
|
||||
- Change "Part" to "Product", "Component", or "Item"
|
||||
- Change "Category" to "Group", "Type", or "Class"
|
||||
- Change "Manufacturer" to "Supplier", "Vendor", or "Brand"
|
||||
|
||||
These custom terms (synonyms) are applied throughout the user interface, making Part-DB feel more natural for your specific use case.
|
||||
|
||||
## Important Notes
|
||||
|
||||
{: .warning-title }
|
||||
> Experimental Feature
|
||||
>
|
||||
> The synonym system is currently **experimental**. While it works in most places throughout Part-DB, there may be some locations where the default terms still appear. The synonym system is being continuously improved.
|
||||
|
||||
## Configuring Synonyms
|
||||
|
||||
To configure synonyms, you need administrator permissions:
|
||||
|
||||
1. Navigate to `Tools -> System -> Settings`
|
||||
2. Find and click on "Synonyms" in the settings menu
|
||||
3. You'll see the synonym configuration interface
|
||||
|
||||
### Adding a Synonym
|
||||
|
||||
To add a new synonym:
|
||||
|
||||
1. Click the "Add Entry" button in the synonym settings
|
||||
2. Select the **Type** (element type) you want to create a synonym for
|
||||
3. Select the **Language** for which the synonym applies
|
||||
4. Enter the **Singular** form of your synonym
|
||||
5. Enter the **Plural** form of your synonym
|
||||
6. Click "Save" to apply the changes
|
||||
|
||||
### Available Element Types
|
||||
|
||||
You can create synonyms for the following element types:
|
||||
|
||||
| Element Type | Default Term (EN) | Example Use Cases |
|
||||
|--------------------|-----------------------|-----------------------------------------------|
|
||||
| **attachment** | Attachment | Document, File, Asset |
|
||||
| **attachment_type**| Attachment Type | Document Type, File Category |
|
||||
| **category** | Category | Group, Class, Type, Collection |
|
||||
| **currency** | Currency | Monetary Unit, Money Type |
|
||||
| **footprint** | Footprint | Package, Form Factor, Physical Type |
|
||||
| **group** | Group | Team, Department, Role |
|
||||
| **label_profile** | Label Profile | Label Template, Print Template |
|
||||
| **manufacturer** | Manufacturer | Brand, Vendor, Supplier, Maker |
|
||||
| **measurement_unit**| Measurement Unit | Unit of Measure, Unit, Measurement |
|
||||
| **parameter** | Parameter | Specification, Property, Attribute |
|
||||
| **part** | Part | Component, Item, Product, Article, SKU |
|
||||
| **part_lot** | Part Lot | Stock Item, Inventory Item, Batch |
|
||||
| **project** | Project | Assembly, Build, Work Order |
|
||||
| **storage_location**| Storage Location | Warehouse, Bin, Location, Place |
|
||||
| **supplier** | Supplier | Vendor, Distributor, Reseller |
|
||||
| **user** | User | Member, Account, Person |
|
||||
|
||||
## How Synonyms Work
|
||||
|
||||
### Translation Mechanism
|
||||
|
||||
The synonym system works by integrating with Part-DB's translation system. When you define a synonym:
|
||||
|
||||
1. Part-DB creates translation placeholders for the element type
|
||||
2. These placeholders are available in both capitalized and lowercase forms
|
||||
3. The placeholders are used throughout the application where these terms appear
|
||||
|
||||
### Placeholder Format
|
||||
|
||||
Synonyms use special placeholders in translations:
|
||||
|
||||
- `[elementtype]` - Singular, lowercase (e.g., "part" → "item")
|
||||
- `[Elementtype]` - Singular, capitalized (e.g., "Part" → "Item")
|
||||
- `[[elementtype]]` - Plural, lowercase (e.g., "parts" → "items")
|
||||
- `[[Elementtype]]` - Plural, capitalized (e.g., "Parts" → "Items")
|
||||
|
||||
### Language-Specific Synonyms
|
||||
|
||||
Synonyms are language-specific, meaning you can define different terms for different languages:
|
||||
|
||||
- English users see: "Component" and "Components"
|
||||
- German users see: "Bauteil" and "Bauteile"
|
||||
- French users see: "Composant" and "Composants"
|
||||
|
||||
This allows Part-DB to maintain proper multilingual support even with custom terminology.
|
||||
|
||||
## Use Cases and Examples
|
||||
|
||||
### Non-Electronics Inventory
|
||||
|
||||
**Scenario**: Using Part-DB for a library
|
||||
|
||||
**Synonyms**:
|
||||
- Part → Book
|
||||
- Category → Genre
|
||||
- Manufacturer → Publisher
|
||||
- Supplier → Distributor
|
||||
- Storage Location → Shelf
|
||||
|
||||
**Result**: The interface now speaks library language: "Add a new Book", "Select a Genre", etc.
|
||||
|
||||
### Manufacturing Environment
|
||||
|
||||
**Scenario**: Managing production inventory
|
||||
|
||||
**Synonyms**:
|
||||
- Part → Material
|
||||
- Category → Material Type
|
||||
- Part Lot → Batch
|
||||
- Storage Location → Warehouse Zone
|
||||
- Project → Assembly
|
||||
|
||||
**Result**: The interface uses manufacturing terminology: "Materials", "Batches", "Warehouse Zones", "Assemblies"
|
||||
|
||||
### Small Business Retail
|
||||
|
||||
**Scenario**: Managing store inventory
|
||||
|
||||
**Synonyms**:
|
||||
- Part → Product
|
||||
- Category → Department
|
||||
- Manufacturer → Brand
|
||||
- Supplier → Vendor
|
||||
- Part Lot → Stock Item
|
||||
- Storage Location → Store Location
|
||||
|
||||
**Result**: The interface matches retail terminology: "Products", "Departments", "Brands"
|
||||
|
||||
### Laboratory Setting
|
||||
|
||||
**Scenario**: Managing lab supplies and chemicals
|
||||
|
||||
**Synonyms**:
|
||||
- Part → Reagent
|
||||
- Category → Substance Type
|
||||
- Manufacturer → Chemical Supplier
|
||||
- Storage Location → Cabinet
|
||||
- Part Lot → Bottle
|
||||
|
||||
**Result**: Lab-appropriate language: "Reagents", "Substance Types", "Cabinets"
|
||||
|
||||
### Educational Makerspace
|
||||
|
||||
**Scenario**: Managing shared tools and components
|
||||
|
||||
**Synonyms**:
|
||||
- Part → Resource
|
||||
- Category → Resource Type
|
||||
- Storage Location → Area
|
||||
- Project → Activity
|
||||
- Part Lot → Available Unit
|
||||
|
||||
**Result**: Educational context: "Resources", "Resource Types", "Areas", "Activities"
|
||||
|
||||
## Managing Synonyms
|
||||
|
||||
### Editing Synonyms
|
||||
|
||||
To edit an existing synonym:
|
||||
1. Find the synonym entry in the list
|
||||
2. Modify the singular or plural form as needed
|
||||
3. Click "Save" to apply changes
|
||||
|
||||
### Removing Synonyms
|
||||
|
||||
To remove a synonym:
|
||||
1. Find the synonym entry in the list
|
||||
2. Click the "Remove Entry" button (usually a trash icon)
|
||||
3. Click "Save" to apply changes
|
||||
|
||||
After removal, Part-DB will revert to using the default term for that element type and language.
|
||||
|
||||
### Bulk Configuration
|
||||
|
||||
If you need to set up many synonyms at once (e.g., for a complete custom terminology set):
|
||||
|
||||
1. Define all your synonyms in the settings page
|
||||
2. Each element type can have synonyms in multiple languages
|
||||
3. Save once when all entries are configured
|
||||
|
||||
### Duplicate Prevention
|
||||
|
||||
The system prevents duplicate entries:
|
||||
- You cannot have multiple synonyms for the same element type and language combination
|
||||
- If you try to add a duplicate, you'll see a validation error
|
||||
- Edit the existing entry instead of creating a new one
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Consistency
|
||||
|
||||
- **Use consistent terminology**: If you change "Part" to "Product", consider changing "Part Lot" to "Product Item" or similar
|
||||
- **Think holistically**: Consider how terms relate to each other in your domain
|
||||
- **Test thoroughly**: Check various pages to ensure your terms make sense in context
|
||||
|
||||
### Singular and Plural Forms
|
||||
|
||||
- **Provide both forms**: Always define both singular and plural forms
|
||||
- **Use proper grammar**: Ensure plurals are grammatically correct
|
||||
- **Consider irregular plurals**: Some terms have non-standard plurals (e.g., "Box" → "Boxes", not "Boxs")
|
||||
|
||||
### Language Considerations
|
||||
|
||||
- **Match user expectations**: Use terms your users are familiar with in their language
|
||||
- **Be culturally appropriate**: Some terms may have different connotations in different languages
|
||||
- **Maintain professionalism**: Choose terms appropriate for your organizational context
|
||||
|
||||
### Planning Your Terminology
|
||||
|
||||
Before implementing synonyms:
|
||||
|
||||
1. **List all terms**: Identify which Part-DB terms don't fit your context
|
||||
2. **Define replacements**: Decide on appropriate alternatives
|
||||
3. **Check relationships**: Ensure related terms work together logically
|
||||
4. **Get feedback**: Consult with users about proposed terminology
|
||||
5. **Document decisions**: Keep a record of your synonym choices for future reference
|
||||
|
||||
## Limitations
|
||||
|
||||
### Not All Locations Covered
|
||||
|
||||
As an experimental feature, synonyms may not appear in:
|
||||
- Some error messages
|
||||
- Technical logs
|
||||
- Email templates (depending on configuration)
|
||||
- API responses
|
||||
- Some administrative interfaces
|
||||
|
||||
The development team is working to expand synonym coverage.
|
||||
|
||||
### No Automatic Propagation
|
||||
|
||||
Synonyms only affect the user interface:
|
||||
- Database values remain unchanged
|
||||
- Export files use original terms
|
||||
- API endpoints keep original names
|
||||
- URLs and routes remain the same
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
The synonym system:
|
||||
- Caches translations for performance
|
||||
- Minimal performance impact in normal usage
|
||||
- Cache is automatically updated when synonyms change
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Cache Management
|
||||
|
||||
Synonyms are cached for performance:
|
||||
- Cache is automatically cleared when synonyms are saved
|
||||
- No manual cache clearing needed
|
||||
- Changes appear immediately after saving
|
||||
|
||||
### Translation Priority
|
||||
|
||||
When displaying text, Part-DB checks in this order:
|
||||
1. Synonym (if defined for current language and element type)
|
||||
2. Standard translation (from translation files)
|
||||
3. Fallback to English default
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Currently, synonyms can only be configured through the web interface. Future versions may support environment variable configuration.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Synonyms Not Appearing
|
||||
|
||||
**Problem**: You've configured synonyms but still see original terms.
|
||||
|
||||
**Solutions**:
|
||||
- Clear your browser cache and reload the page
|
||||
- Check that you've configured the synonym for the correct language
|
||||
- Verify that you saved the settings after adding the synonym
|
||||
- Remember this is an experimental feature - some locations may not be covered yet
|
||||
|
||||
### Inconsistent Terminology
|
||||
|
||||
**Problem**: Some pages show your synonym, others show the original term.
|
||||
|
||||
**Solutions**:
|
||||
- This is expected behavior for the experimental feature
|
||||
- Check if you've defined both singular and plural forms
|
||||
- Report inconsistencies to help improve the system
|
||||
|
||||
### Wrong Language Displaying
|
||||
|
||||
**Problem**: Seeing synonyms from the wrong language.
|
||||
|
||||
**Solutions**:
|
||||
- Check your user language preference in user settings
|
||||
- Verify you've configured synonyms for the correct language code
|
||||
- Ensure the language code matches exactly (e.g., "en" not "en_US")
|
||||
|
||||
### Synonyms Lost After Update
|
||||
|
||||
**Problem**: Synonyms disappeared after updating Part-DB.
|
||||
|
||||
**Solutions**:
|
||||
- Check the settings page - they should still be there
|
||||
- Database migrations preserve synonym settings
|
||||
- If truly lost, restore from backup or reconfigure
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
The synonym system is under active development. Planned improvements include:
|
||||
- Coverage of more interface elements
|
||||
- Synonym suggestions based on common use cases
|
||||
- Import/export of synonym configurations
|
||||
- Synonym templates for different industries
|
||||
- More granular control over term usage
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Getting Started]({% link usage/getting_started.md %}) - Initial Part-DB setup
|
||||
- [Configuration]({% link configuration.md %}) - System configuration options
|
||||
- [Concepts]({% link concepts.md %}) - Understanding Part-DB terminology
|
||||
|
||||
## Feedback
|
||||
|
||||
Since the synonym system is experimental, feedback is valuable:
|
||||
- Report locations where synonyms don't appear
|
||||
- Suggest new element types that should support synonyms
|
||||
- Share your use cases to help improve the system
|
||||
- Report bugs or unexpected behavior
|
||||
|
||||
You can provide feedback through:
|
||||
- GitHub issues on the Part-DB repository
|
||||
- Community forums and discussions
|
||||
- Direct contact with the development team
|
||||
@@ -366,14 +366,6 @@ abstract class BaseAdminController extends AbstractController
|
||||
}
|
||||
}
|
||||
|
||||
//Count how many actual new entities were created (id is null until persisted)
|
||||
$created_count = 0;
|
||||
foreach ($results as $result) {
|
||||
if (null === $result->getID()) {
|
||||
$created_count++;
|
||||
}
|
||||
}
|
||||
|
||||
//Persist valid entities to DB
|
||||
foreach ($results as $result) {
|
||||
$em->persist($result);
|
||||
@@ -381,14 +373,8 @@ abstract class BaseAdminController extends AbstractController
|
||||
$em->flush();
|
||||
|
||||
if (count($results) > 0) {
|
||||
$this->addFlash('success', t('entity.mass_creation_flash', ['%COUNT%' => $created_count]));
|
||||
$this->addFlash('success', t('entity.mass_creation_flash', ['%COUNT%' => count($results)]));
|
||||
}
|
||||
|
||||
if (count($errors)) {
|
||||
//Recreate mass creation form, so we get the updated parent list and empty lines
|
||||
$mass_creation_form = $this->createForm(MassCreationForm::class, ['entity_class' => $this->entity_class]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $this->render($this->twig_template, [
|
||||
|
||||
@@ -169,7 +169,7 @@ abstract class Attachment extends AbstractNamedDBElement
|
||||
#[ORM\Column(type: Types::STRING, length: 2048, nullable: true)]
|
||||
#[Groups(['attachment:read'])]
|
||||
#[ApiProperty(example: 'http://example.com/image.jpg')]
|
||||
#[Assert\Length(max: 2048)]
|
||||
#[Assert\Length(2048)]
|
||||
protected ?string $external_path = null;
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,11 +24,13 @@ namespace App\Entity\Attachments;
|
||||
|
||||
use App\Entity\Base\AbstractNamedDBElement;
|
||||
use App\Entity\Base\MasterAttachmentTrait;
|
||||
use App\Entity\Base\AttachmentsTrait;
|
||||
use App\Entity\Contracts\HasAttachmentsInterface;
|
||||
use App\Entity\Contracts\HasMasterAttachmentInterface;
|
||||
use App\Repository\AttachmentContainingDBElementRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
/**
|
||||
* @template AT of Attachment
|
||||
@@ -37,18 +39,83 @@ use Doctrine\ORM\Mapping as ORM;
|
||||
abstract class AttachmentContainingDBElement extends AbstractNamedDBElement implements HasMasterAttachmentInterface, HasAttachmentsInterface
|
||||
{
|
||||
use MasterAttachmentTrait;
|
||||
use AttachmentsTrait;
|
||||
|
||||
/**
|
||||
* @var Collection<int, Attachment>
|
||||
* @phpstan-var Collection<int, AT>
|
||||
* ORM Mapping is done in subclasses (e.g. Part)
|
||||
*/
|
||||
#[Groups(['full', 'import'])]
|
||||
protected Collection $attachments;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->initializeAttachments();
|
||||
$this->attachments = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
$this->cloneAttachments();
|
||||
if ($this->id) {
|
||||
$attachments = $this->attachments;
|
||||
$this->attachments = new ArrayCollection();
|
||||
//Set master attachment is needed
|
||||
foreach ($attachments as $attachment) {
|
||||
$clone = clone $attachment;
|
||||
if ($attachment === $this->master_picture_attachment) {
|
||||
$this->setMasterPictureAttachment($clone);
|
||||
}
|
||||
$this->addAttachment($clone);
|
||||
}
|
||||
}
|
||||
|
||||
//Parent has to be last call, as it resets the ID
|
||||
parent::__clone();
|
||||
}
|
||||
|
||||
/********************************************************************************
|
||||
*
|
||||
* Getters
|
||||
*
|
||||
*********************************************************************************/
|
||||
|
||||
/**
|
||||
* Gets all attachments associated with this element.
|
||||
*/
|
||||
public function getAttachments(): Collection
|
||||
{
|
||||
return $this->attachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an attachment to this element.
|
||||
*
|
||||
* @param Attachment $attachment Attachment
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addAttachment(Attachment $attachment): self
|
||||
{
|
||||
//Attachment must be associated with this element
|
||||
$attachment->setElement($this);
|
||||
$this->attachments->add($attachment);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given attachment from this element.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeAttachment(Attachment $attachment): self
|
||||
{
|
||||
$this->attachments->removeElement($attachment);
|
||||
|
||||
//Check if this is the master attachment -> remove it from master attachment too, or it can not be deleted from DB...
|
||||
if ($attachment === $this->getMasterPictureAttachment()) {
|
||||
$this->setMasterPictureAttachment(null);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,14 +52,12 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
/**
|
||||
* Class AttachmentType.
|
||||
* @see \App\Tests\Entity\Attachments\AttachmentTypeTest
|
||||
* @extends AbstractStructuralDBElement<AttachmentTypeAttachment, AttachmentTypeParameter>
|
||||
*/
|
||||
#[ORM\Entity(repositoryClass: StructuralDBElementRepository::class)]
|
||||
#[ORM\Table(name: '`attachment_types`')]
|
||||
#[ORM\Index(columns: ['name'], name: 'attachment_types_idx_name')]
|
||||
#[ORM\Index(columns: ['parent_id', 'name'], name: 'attachment_types_idx_parent_name')]
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
#[ORM\EntityListeners([TreeCacheInvalidationListener::class])]
|
||||
#[UniqueEntity(fields: ['name', 'parent'], message: 'structural.entity.unique_name', ignoreNull: false)]
|
||||
#[ApiResource(
|
||||
operations: [
|
||||
new Get(security: 'is_granted("read", object)'),
|
||||
@@ -86,16 +84,8 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])]
|
||||
#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)]
|
||||
#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])]
|
||||
class AttachmentType implements DBElementInterface, NamedElementInterface, TimeStampableInterface, HasAttachmentsInterface, HasMasterAttachmentInterface, StructuralElementInterface, HasParametersInterface, \Stringable, \JsonSerializable
|
||||
class AttachmentType extends AbstractStructuralDBElement
|
||||
{
|
||||
use DBElementTrait;
|
||||
use NamedElementTrait;
|
||||
use TimestampTrait;
|
||||
use AttachmentsTrait;
|
||||
use MasterAttachmentTrait;
|
||||
use StructuralElementTrait;
|
||||
use ParametersTrait;
|
||||
|
||||
#[ORM\OneToMany(mappedBy: 'parent', targetEntity: AttachmentType::class, cascade: ['persist'])]
|
||||
#[ORM\OrderBy(['name' => Criteria::ASC])]
|
||||
protected Collection $children;
|
||||
@@ -104,10 +94,7 @@ class AttachmentType implements DBElementInterface, NamedElementInterface, TimeS
|
||||
#[ORM\JoinColumn(name: 'parent_id')]
|
||||
#[Groups(['attachment_type:read', 'attachment_type:write'])]
|
||||
#[ApiProperty(readableLink: true, writableLink: false)]
|
||||
protected ?self $parent = null;
|
||||
|
||||
#[Groups(['attachment_type:read', 'attachment_type:write'])]
|
||||
protected string $comment = '';
|
||||
protected ?AbstractStructuralDBElement $parent = null;
|
||||
|
||||
/**
|
||||
* @var string A comma separated list of file types, which are allowed for attachment files.
|
||||
@@ -136,7 +123,6 @@ class AttachmentType implements DBElementInterface, NamedElementInterface, TimeS
|
||||
/** @var Collection<int, AttachmentTypeParameter>
|
||||
*/
|
||||
#[Assert\Valid]
|
||||
#[UniqueObjectCollection(fields: ['name', 'group', 'element'])]
|
||||
#[ORM\OneToMany(mappedBy: 'element', targetEntity: AttachmentTypeParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
#[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])]
|
||||
#[Groups(['attachment_type:read', 'attachment_type:write', 'import', 'full'])]
|
||||
@@ -156,37 +142,13 @@ class AttachmentType implements DBElementInterface, NamedElementInterface, TimeS
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->initializeAttachments();
|
||||
$this->initializeStructuralElement();
|
||||
$this->children = new ArrayCollection();
|
||||
$this->parameters = new ArrayCollection();
|
||||
parent::__construct();
|
||||
$this->attachments = new ArrayCollection();
|
||||
$this->attachments_with_type = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
if ($this->id) {
|
||||
$this->cloneDBElement();
|
||||
$this->cloneAttachments();
|
||||
|
||||
// We create a new object, so give it a new creation date
|
||||
$this->addedDate = null;
|
||||
|
||||
//Deep clone parameters
|
||||
$parameters = $this->parameters;
|
||||
$this->parameters = new ArrayCollection();
|
||||
foreach ($parameters as $parameter) {
|
||||
$this->addParameter(clone $parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return ['@id' => $this->getID()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all attachments ("Attachment" objects) with this type.
|
||||
*
|
||||
|
||||
@@ -24,9 +24,11 @@ namespace App\Entity\Base;
|
||||
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Parameters\AbstractParameter;
|
||||
use App\Entity\Contracts\CompanyInterface;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use function is_string;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* This abstract class is used for companies like suppliers or manufacturers.
|
||||
@@ -36,15 +38,226 @@ use Symfony\Component\Serializer\Annotation\Groups;
|
||||
* @extends AbstractPartsContainingDBElement<AT, PT>
|
||||
*/
|
||||
#[ORM\MappedSuperclass]
|
||||
abstract class AbstractCompany extends AbstractPartsContainingDBElement implements CompanyInterface
|
||||
abstract class AbstractCompany extends AbstractPartsContainingDBElement
|
||||
{
|
||||
use CompanyTrait;
|
||||
|
||||
#[Groups(['company:read'])]
|
||||
protected ?\DateTimeImmutable $addedDate = null;
|
||||
#[Groups(['company:read'])]
|
||||
protected ?\DateTimeImmutable $lastModified = null;
|
||||
|
||||
/**
|
||||
* @var string The address of the company
|
||||
*/
|
||||
#[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])]
|
||||
#[ORM\Column(type: Types::STRING)]
|
||||
#[Assert\Length(max: 255)]
|
||||
protected string $address = '';
|
||||
|
||||
/**
|
||||
* @var string The phone number of the company
|
||||
*/
|
||||
#[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])]
|
||||
#[ORM\Column(type: Types::STRING)]
|
||||
#[Assert\Length(max: 255)]
|
||||
protected string $phone_number = '';
|
||||
|
||||
/**
|
||||
* @var string The fax number of the company
|
||||
*/
|
||||
#[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])]
|
||||
#[ORM\Column(type: Types::STRING)]
|
||||
#[Assert\Length(max: 255)]
|
||||
protected string $fax_number = '';
|
||||
|
||||
/**
|
||||
* @var string The email address of the company
|
||||
*/
|
||||
#[Assert\Email]
|
||||
#[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])]
|
||||
#[ORM\Column(type: Types::STRING)]
|
||||
#[Assert\Length(max: 255)]
|
||||
protected string $email_address = '';
|
||||
|
||||
/**
|
||||
* @var string The website of the company
|
||||
*/
|
||||
#[Assert\Url(requireTld: false)]
|
||||
#[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])]
|
||||
#[ORM\Column(type: Types::STRING, length: 2048)]
|
||||
#[Assert\Length(max: 2048)]
|
||||
protected string $website = '';
|
||||
|
||||
#[Groups(['company:read', 'company:write', 'import', 'full', 'extended'])]
|
||||
protected string $comment = '';
|
||||
|
||||
/**
|
||||
* @var string The link to the website of an article. Use %PARTNUMBER% as placeholder for the part number.
|
||||
*/
|
||||
#[ORM\Column(type: Types::STRING, length: 2048)]
|
||||
#[Assert\Length(max: 2048)]
|
||||
#[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])]
|
||||
protected string $auto_product_url = '';
|
||||
|
||||
/********************************************************************************
|
||||
*
|
||||
* Getters
|
||||
*
|
||||
*********************************************************************************/
|
||||
|
||||
/**
|
||||
* Get the address.
|
||||
*
|
||||
* @return string the address of the company (with "\n" as line break)
|
||||
*/
|
||||
public function getAddress(): string
|
||||
{
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the phone number.
|
||||
*
|
||||
* @return string the phone number of the company
|
||||
*/
|
||||
public function getPhoneNumber(): string
|
||||
{
|
||||
return $this->phone_number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fax number.
|
||||
*
|
||||
* @return string the fax number of the company
|
||||
*/
|
||||
public function getFaxNumber(): string
|
||||
{
|
||||
return $this->fax_number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the e-mail address.
|
||||
*
|
||||
* @return string the e-mail address of the company
|
||||
*/
|
||||
public function getEmailAddress(): string
|
||||
{
|
||||
return $this->email_address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the website.
|
||||
*
|
||||
* @return string the website of the company
|
||||
*/
|
||||
public function getWebsite(): string
|
||||
{
|
||||
return $this->website;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the link to the website of an article.
|
||||
*
|
||||
* @param string|null $partnr * NULL for returning the URL with a placeholder for the part number
|
||||
* * or the part number for returning the direct URL to the article
|
||||
*
|
||||
* @return string the link to the article
|
||||
*/
|
||||
public function getAutoProductUrl(?string $partnr = null): string
|
||||
{
|
||||
if (is_string($partnr)) {
|
||||
return str_replace('%PARTNUMBER%', $partnr, $this->auto_product_url);
|
||||
}
|
||||
|
||||
return $this->auto_product_url;
|
||||
}
|
||||
|
||||
/********************************************************************************
|
||||
*
|
||||
* Setters
|
||||
*
|
||||
*********************************************************************************/
|
||||
|
||||
/**
|
||||
* Set the addres.
|
||||
*
|
||||
* @param string $new_address the new address (with "\n" as line break)
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAddress(string $new_address): self
|
||||
{
|
||||
$this->address = $new_address;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the phone number.
|
||||
*
|
||||
* @param string $new_phone_number the new phone number
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setPhoneNumber(string $new_phone_number): self
|
||||
{
|
||||
$this->phone_number = $new_phone_number;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the fax number.
|
||||
*
|
||||
* @param string $new_fax_number the new fax number
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setFaxNumber(string $new_fax_number): self
|
||||
{
|
||||
$this->fax_number = $new_fax_number;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the e-mail address.
|
||||
*
|
||||
* @param string $new_email_address the new e-mail address
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setEmailAddress(string $new_email_address): self
|
||||
{
|
||||
$this->email_address = $new_email_address;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the website.
|
||||
*
|
||||
* @param string $new_website the new website
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setWebsite(string $new_website): self
|
||||
{
|
||||
$this->website = $new_website;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the link to the website of an article.
|
||||
*
|
||||
* @param string $new_url the new URL with the placeholder %PARTNUMBER% for the part number
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAutoProductUrl(string $new_url): self
|
||||
{
|
||||
$this->auto_product_url = $new_url;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ use App\Entity\Attachments\ProjectAttachment;
|
||||
use App\Entity\Attachments\StorageLocationAttachment;
|
||||
use App\Entity\Attachments\SupplierAttachment;
|
||||
use App\Entity\Attachments\UserAttachment;
|
||||
use App\Entity\Contracts\DBElementInterface;
|
||||
use App\Entity\Parameters\AbstractParameter;
|
||||
use App\Entity\Parts\Category;
|
||||
use App\Entity\PriceInformations\Pricedetail;
|
||||
@@ -57,9 +56,11 @@ use App\Entity\Parts\MeasurementUnit;
|
||||
use App\Entity\Parts\Supplier;
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Repository\DBElementRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use JsonSerializable;
|
||||
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
/**
|
||||
* This class is for managing all database objects.
|
||||
@@ -105,13 +106,36 @@ use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
|
||||
'user' => User::class]
|
||||
)]
|
||||
#[ORM\MappedSuperclass(repositoryClass: DBElementRepository::class)]
|
||||
abstract class AbstractDBElement implements JsonSerializable, DBElementInterface
|
||||
abstract class AbstractDBElement implements JsonSerializable
|
||||
{
|
||||
use DBElementTrait;
|
||||
/** @var int|null The Identification number for this part. This value is unique for the element in this table.
|
||||
* Null if the element is not saved to DB yet.
|
||||
*/
|
||||
#[Groups(['full', 'api:basic:read'])]
|
||||
#[ORM\Column(type: Types::INTEGER)]
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
protected ?int $id = null;
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
$this->cloneDBElement();
|
||||
if ($this->id) {
|
||||
//Set ID to null, so that an new entry is created
|
||||
$this->id = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID. The ID can be zero, or even negative (for virtual elements). If an element is virtual, can be
|
||||
* checked with isVirtualElement().
|
||||
*
|
||||
* Returns null, if the element is not saved to the DB yet.
|
||||
*
|
||||
* @return int|null the ID of this element
|
||||
*/
|
||||
public function getID(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
|
||||
@@ -23,9 +23,12 @@ declare(strict_types=1);
|
||||
namespace App\Entity\Base;
|
||||
|
||||
use App\Repository\NamedDBElementRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use App\Entity\Contracts\NamedElementInterface;
|
||||
use App\Entity\Contracts\TimeStampableInterface;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* All subclasses of this class have an attribute "name".
|
||||
@@ -35,7 +38,26 @@ use Doctrine\ORM\Mapping as ORM;
|
||||
abstract class AbstractNamedDBElement extends AbstractDBElement implements NamedElementInterface, TimeStampableInterface, \Stringable
|
||||
{
|
||||
use TimestampTrait;
|
||||
use NamedElementTrait;
|
||||
|
||||
/**
|
||||
* @var string The name of this element
|
||||
*/
|
||||
#[Assert\NotBlank]
|
||||
#[Groups(['simple', 'extended', 'full', 'import', 'api:basic:read', 'api:basic:write'])]
|
||||
#[ORM\Column(type: Types::STRING)]
|
||||
#[Assert\Length(max: 255)]
|
||||
protected string $name = '';
|
||||
|
||||
/******************************************************************************
|
||||
*
|
||||
* Helpers
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->getName();
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
@@ -43,6 +65,40 @@ abstract class AbstractNamedDBElement extends AbstractDBElement implements Named
|
||||
//We create a new object, so give it a new creation date
|
||||
$this->addedDate = null;
|
||||
}
|
||||
parent::__clone();
|
||||
parent::__clone(); // TODO: Change the autogenerated stub
|
||||
}
|
||||
|
||||
/********************************************************************************
|
||||
*
|
||||
* Getters
|
||||
*
|
||||
*********************************************************************************/
|
||||
|
||||
/**
|
||||
* Get the name of this element.
|
||||
*
|
||||
* @return string the name of this element
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/********************************************************************************
|
||||
*
|
||||
* Setters
|
||||
*
|
||||
*********************************************************************************/
|
||||
|
||||
/**
|
||||
* Change the name of this element.
|
||||
*
|
||||
* @param string $new_name the new name
|
||||
*/
|
||||
public function setName(string $new_name): self
|
||||
{
|
||||
$this->name = $new_name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,18 +24,22 @@ namespace App\Entity\Base;
|
||||
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Parameters\AbstractParameter;
|
||||
use App\Entity\Contracts\StructuralElementInterface;
|
||||
use App\Entity\Contracts\HasParametersInterface;
|
||||
use App\Repository\StructuralDBElementRepository;
|
||||
use App\EntityListeners\TreeCacheInvalidationListener;
|
||||
use App\Validator\Constraints\UniqueObjectCollection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use App\Entity\Attachments\AttachmentContainingDBElement;
|
||||
use App\Entity\Parameters\ParametersTrait;
|
||||
use App\Validator\Constraints\NoneOfItsChildren;
|
||||
use Symfony\Component\Serializer\Annotation\SerializedName;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use function count;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use InvalidArgumentException;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
/**
|
||||
* All elements with the fields "id", "name" and "parent_id" (at least).
|
||||
@@ -58,10 +62,52 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
#[UniqueEntity(fields: ['name', 'parent'], message: 'structural.entity.unique_name', ignoreNull: false)]
|
||||
#[ORM\MappedSuperclass(repositoryClass: StructuralDBElementRepository::class)]
|
||||
#[ORM\EntityListeners([TreeCacheInvalidationListener::class])]
|
||||
abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement implements StructuralElementInterface, HasParametersInterface
|
||||
abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement
|
||||
{
|
||||
use ParametersTrait;
|
||||
use StructuralElementTrait;
|
||||
|
||||
/**
|
||||
* This is a not standard character, so build a const, so a dev can easily use it.
|
||||
*/
|
||||
final public const PATH_DELIMITER_ARROW = ' → ';
|
||||
|
||||
/**
|
||||
* @var string The comment info for this element as markdown
|
||||
*/
|
||||
#[Groups(['full', 'import'])]
|
||||
#[ORM\Column(type: Types::TEXT)]
|
||||
protected string $comment = '';
|
||||
|
||||
/**
|
||||
* @var bool If this property is set, this element can not be selected for part properties.
|
||||
* Useful if this element should be used only for grouping, sorting.
|
||||
*/
|
||||
#[Groups(['full', 'import'])]
|
||||
#[ORM\Column(type: Types::BOOLEAN)]
|
||||
protected bool $not_selectable = false;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected int $level = 0;
|
||||
|
||||
/**
|
||||
* We can not define the mapping here, or we will get an exception. Unfortunately we have to do the mapping in the
|
||||
* subclasses.
|
||||
*
|
||||
* @var Collection<int, AbstractStructuralDBElement>
|
||||
* @phpstan-var Collection<int, static>
|
||||
*/
|
||||
#[Groups(['include_children'])]
|
||||
protected Collection $children;
|
||||
|
||||
/**
|
||||
* @var AbstractStructuralDBElement|null
|
||||
* @phpstan-var static|null
|
||||
*/
|
||||
#[Groups(['include_parents', 'import'])]
|
||||
#[NoneOfItsChildren]
|
||||
protected ?AbstractStructuralDBElement $parent = null;
|
||||
|
||||
/**
|
||||
* Mapping done in subclasses.
|
||||
@@ -73,10 +119,21 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement
|
||||
#[UniqueObjectCollection(fields: ['name', 'group', 'element'])]
|
||||
protected Collection $parameters;
|
||||
|
||||
/** @var string[] all names of all parent elements as an array of strings,
|
||||
* the last array element is the name of the element itself
|
||||
*/
|
||||
private array $full_path_strings = [];
|
||||
|
||||
/**
|
||||
* Alternative names (semicolon-separated) for this element, which can be used for searching (especially for info provider system)
|
||||
*/
|
||||
#[ORM\Column(type: Types::TEXT, nullable: true, options: ['default' => null])]
|
||||
private ?string $alternative_names = "";
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->initializeStructuralElement();
|
||||
$this->children = new ArrayCollection();
|
||||
$this->parameters = new ArrayCollection();
|
||||
}
|
||||
|
||||
@@ -92,4 +149,307 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement
|
||||
}
|
||||
parent::__clone();
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* StructuralDBElement constructor.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Check if this element is a child of another element (recursive).
|
||||
*
|
||||
* @param AbstractStructuralDBElement $another_element the object to compare
|
||||
* IMPORTANT: both objects to compare must be from the same class (for example two "Device" objects)!
|
||||
*
|
||||
* @return bool true, if this element is child of $another_element
|
||||
*
|
||||
* @throws InvalidArgumentException if there was an error
|
||||
*/
|
||||
public function isChildOf(self $another_element): bool
|
||||
{
|
||||
$class_name = static::class;
|
||||
|
||||
//Check if both elements compared, are from the same type
|
||||
// (we have to check inheritance, or we get exceptions when using doctrine entities (they have a proxy type):
|
||||
if (!$another_element instanceof $class_name && !is_a($this, $another_element::class)) {
|
||||
throw new InvalidArgumentException('isChildOf() only works for objects of the same type!');
|
||||
}
|
||||
|
||||
if (!$this->getParent() instanceof self) { // this is the root node
|
||||
return false;
|
||||
}
|
||||
|
||||
//If the parent element is equal to the element we want to compare, return true
|
||||
if ($this->getParent()->getID() === null) {
|
||||
//If the IDs are not yet defined, we have to compare the objects itself
|
||||
if ($this->getParent() === $another_element) {
|
||||
return true;
|
||||
}
|
||||
} elseif ($this->getParent()->getID() === $another_element->getID()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//Otherwise, check recursively
|
||||
return $this->parent->isChildOf($another_element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this element is an root element (has no parent).
|
||||
*
|
||||
* @return bool true if this element is a root element
|
||||
*/
|
||||
public function isRoot(): bool
|
||||
{
|
||||
return $this->parent === null;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
*
|
||||
* Getters
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
/**
|
||||
* Get the parent of this element.
|
||||
*
|
||||
* @return static|null The parent element. Null if this element, does not have a parent.
|
||||
*/
|
||||
public function getParent(): ?self
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the comment of the element as markdown encoded string.
|
||||
|
||||
*
|
||||
* @return string the comment
|
||||
*/
|
||||
public function getComment(): ?string
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the level.
|
||||
*
|
||||
* The level of the root node is -1.
|
||||
*
|
||||
* @return int the level of this element (zero means a most top element
|
||||
* [a sub element of the root node])
|
||||
*/
|
||||
public function getLevel(): int
|
||||
{
|
||||
/*
|
||||
* Only check for nodes that have a parent. In the other cases zero is correct.
|
||||
*/
|
||||
if (0 === $this->level && $this->parent instanceof self) {
|
||||
$element = $this->parent;
|
||||
while ($element instanceof self) {
|
||||
/** @var AbstractStructuralDBElement $element */
|
||||
$element = $element->parent;
|
||||
++$this->level;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full path.
|
||||
*
|
||||
* @param string $delimiter the delimiter of the returned string
|
||||
*
|
||||
* @return string the full path (incl. the name of this element), delimited by $delimiter
|
||||
*/
|
||||
#[Groups(['api:basic:read'])]
|
||||
#[SerializedName('full_path')]
|
||||
public function getFullPath(string $delimiter = self::PATH_DELIMITER_ARROW): string
|
||||
{
|
||||
if ($this->full_path_strings === []) {
|
||||
$this->full_path_strings = [];
|
||||
$this->full_path_strings[] = $this->getName();
|
||||
$element = $this;
|
||||
|
||||
$overflow = 20; //We only allow 20 levels depth
|
||||
|
||||
while ($element->parent instanceof self && $overflow >= 0) {
|
||||
$element = $element->parent;
|
||||
$this->full_path_strings[] = $element->getName();
|
||||
//Decrement to prevent mem overflow.
|
||||
--$overflow;
|
||||
}
|
||||
|
||||
$this->full_path_strings = array_reverse($this->full_path_strings);
|
||||
}
|
||||
|
||||
return implode($delimiter, $this->full_path_strings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path to this element (including the element itself).
|
||||
*
|
||||
* @return self[] An array with all (recursively) parent elements (including this one),
|
||||
* ordered from the lowest levels (root node) first to the highest level (the element itself)
|
||||
*/
|
||||
public function getPathArray(): array
|
||||
{
|
||||
$tmp = [];
|
||||
$tmp[] = $this;
|
||||
|
||||
//We only allow 20 levels depth
|
||||
while (!end($tmp)->isRoot() && count($tmp) < 20) {
|
||||
$tmp[] = end($tmp)->parent;
|
||||
}
|
||||
|
||||
return array_reverse($tmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all sub elements of this element.
|
||||
*
|
||||
* @return Collection<static>|iterable all subelements as an array of objects (sorted by their full path)
|
||||
* @psalm-return Collection<int, static>
|
||||
*/
|
||||
public function getSubelements(): iterable
|
||||
{
|
||||
//If the parent is equal to this object, we would get an endless loop, so just return an empty array
|
||||
//This is just a workaround, as validator should prevent this behaviour, before it gets written to the database
|
||||
if ($this->parent === $this) {
|
||||
return new ArrayCollection();
|
||||
}
|
||||
|
||||
//@phpstan-ignore-next-line
|
||||
return $this->children ?? new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see getSubelements()
|
||||
* @return Collection<static>|iterable
|
||||
* @psalm-return Collection<int, static>
|
||||
*/
|
||||
public function getChildren(): iterable
|
||||
{
|
||||
return $this->getSubelements();
|
||||
}
|
||||
|
||||
public function isNotSelectable(): bool
|
||||
{
|
||||
return $this->not_selectable;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
*
|
||||
* Setters
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
/**
|
||||
* Sets the new parent object.
|
||||
*
|
||||
* @param static|null $new_parent The new parent object
|
||||
* @return $this
|
||||
*/
|
||||
public function setParent(?self $new_parent): self
|
||||
{
|
||||
/*
|
||||
if ($new_parent->isChildOf($this)) {
|
||||
throw new \InvalidArgumentException('You can not use one of the element childs as parent!');
|
||||
} */
|
||||
|
||||
$this->parent = $new_parent;
|
||||
|
||||
//Add this element as child to the new parent
|
||||
if ($new_parent instanceof self) {
|
||||
$new_parent->getChildren()->add($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the comment.
|
||||
*
|
||||
* @param string $new_comment the new comment
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setComment(string $new_comment): self
|
||||
{
|
||||
$this->comment = $new_comment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given element as child to this element.
|
||||
* @param static $child
|
||||
* @return $this
|
||||
*/
|
||||
public function addChild(self $child): self
|
||||
{
|
||||
$this->children->add($child);
|
||||
//Children get this element as parent
|
||||
$child->setParent($this);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given element as child from this element.
|
||||
* @param static $child
|
||||
* @return $this
|
||||
*/
|
||||
public function removeChild(self $child): self
|
||||
{
|
||||
$this->children->removeElement($child);
|
||||
//Children has no parent anymore
|
||||
$child->setParent(null);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AbstractStructuralDBElement
|
||||
*/
|
||||
public function setNotSelectable(bool $not_selectable): self
|
||||
{
|
||||
$this->not_selectable = $not_selectable;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function clearChildren(): self
|
||||
{
|
||||
$this->children = new ArrayCollection();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a comma separated list of alternative names.
|
||||
* @return string|null
|
||||
*/
|
||||
public function getAlternativeNames(): ?string
|
||||
{
|
||||
if ($this->alternative_names === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//Remove trailing comma
|
||||
return rtrim($this->alternative_names, ',');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a comma separated list of alternative names.
|
||||
* @return $this
|
||||
*/
|
||||
public function setAlternativeNames(?string $new_value): self
|
||||
{
|
||||
//Add a trailing comma, if not already there (makes it easier to find in the database)
|
||||
if (is_string($new_value) && !str_ends_with($new_value, ',')) {
|
||||
$new_value .= ',';
|
||||
}
|
||||
|
||||
$this->alternative_names = $new_value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\Base;
|
||||
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
/**
|
||||
* Trait providing attachments functionality.
|
||||
*
|
||||
* Requirements:
|
||||
* - Class using this trait should have $id property (e.g., via DBElementTrait)
|
||||
* - Class using this trait should use MasterAttachmentTrait for full functionality
|
||||
* - Class should implement HasAttachmentsInterface
|
||||
*
|
||||
* Note: This trait has an optional dependency on MasterAttachmentTrait.
|
||||
* If MasterAttachmentTrait is used, the removeAttachment and cloneAttachments methods
|
||||
* will handle master picture attachment properly. Otherwise, those checks are no-ops.
|
||||
*/
|
||||
trait AttachmentsTrait
|
||||
{
|
||||
/**
|
||||
* @var Collection<int, Attachment>
|
||||
* ORM Mapping is done in subclasses (e.g. Part)
|
||||
*/
|
||||
#[Groups(['full', 'import'])]
|
||||
protected Collection $attachments;
|
||||
|
||||
/**
|
||||
* Initialize the attachments collection.
|
||||
*/
|
||||
protected function initializeAttachments(): void
|
||||
{
|
||||
$this->attachments = new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all attachments associated with this element.
|
||||
*/
|
||||
public function getAttachments(): Collection
|
||||
{
|
||||
return $this->attachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an attachment to this element.
|
||||
*
|
||||
* @param Attachment $attachment Attachment
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addAttachment(Attachment $attachment): self
|
||||
{
|
||||
//Attachment must be associated with this element
|
||||
$attachment->setElement($this);
|
||||
$this->attachments->add($attachment);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given attachment from this element.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeAttachment(Attachment $attachment): self
|
||||
{
|
||||
$this->attachments->removeElement($attachment);
|
||||
|
||||
//Check if this is the master attachment -> remove it from master attachment too, or it can not be deleted from DB...
|
||||
if ($this->master_picture_attachment !== null && $attachment === $this->master_picture_attachment) {
|
||||
$this->setMasterPictureAttachment(null);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone helper for attachments - deep clones all attachments.
|
||||
*/
|
||||
protected function cloneAttachments(): void
|
||||
{
|
||||
if (isset($this->id) && $this->id) {
|
||||
$attachments = $this->attachments;
|
||||
$this->attachments = new ArrayCollection();
|
||||
//Set master attachment is needed
|
||||
foreach ($attachments as $attachment) {
|
||||
$clone = clone $attachment;
|
||||
if ($this->master_picture_attachment !== null && $attachment === $this->master_picture_attachment) {
|
||||
$this->setMasterPictureAttachment($clone);
|
||||
}
|
||||
$this->addAttachment($clone);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\Base;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* Trait for company-specific fields like address, phone, email, etc.
|
||||
*/
|
||||
trait CompanyTrait
|
||||
{
|
||||
/**
|
||||
* @var string The address of the company
|
||||
*/
|
||||
#[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])]
|
||||
#[ORM\Column(type: Types::STRING)]
|
||||
#[Assert\Length(max: 255)]
|
||||
protected string $address = '';
|
||||
|
||||
/**
|
||||
* @var string The phone number of the company
|
||||
*/
|
||||
#[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])]
|
||||
#[ORM\Column(type: Types::STRING)]
|
||||
#[Assert\Length(max: 255)]
|
||||
protected string $phone_number = '';
|
||||
|
||||
/**
|
||||
* @var string The fax number of the company
|
||||
*/
|
||||
#[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])]
|
||||
#[ORM\Column(type: Types::STRING)]
|
||||
#[Assert\Length(max: 255)]
|
||||
protected string $fax_number = '';
|
||||
|
||||
/**
|
||||
* @var string The email address of the company
|
||||
*/
|
||||
#[Assert\Email]
|
||||
#[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])]
|
||||
#[ORM\Column(type: Types::STRING)]
|
||||
#[Assert\Length(max: 255)]
|
||||
protected string $email_address = '';
|
||||
|
||||
/**
|
||||
* @var string The website of the company
|
||||
*/
|
||||
#[Assert\Url(requireTld: false)]
|
||||
#[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])]
|
||||
#[ORM\Column(type: Types::STRING, length: 2048)]
|
||||
#[Assert\Length(max: 2048)]
|
||||
protected string $website = '';
|
||||
|
||||
/**
|
||||
* @var string The link to the website of an article. Use %PARTNUMBER% as placeholder for the part number.
|
||||
*/
|
||||
#[ORM\Column(type: Types::STRING, length: 2048)]
|
||||
#[Assert\Length(max: 2048)]
|
||||
#[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])]
|
||||
protected string $auto_product_url = '';
|
||||
|
||||
/**
|
||||
* Get the address.
|
||||
*
|
||||
* @return string the address of the company (with "\n" as line break)
|
||||
*/
|
||||
public function getAddress(): string
|
||||
{
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the address.
|
||||
*
|
||||
* @param string $new_address the new address (with "\n" as line break)
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAddress(string $new_address): self
|
||||
{
|
||||
$this->address = $new_address;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the phone number.
|
||||
*
|
||||
* @return string the phone number of the company
|
||||
*/
|
||||
public function getPhoneNumber(): string
|
||||
{
|
||||
return $this->phone_number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the phone number.
|
||||
*
|
||||
* @param string $new_phone_number the new phone number
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setPhoneNumber(string $new_phone_number): self
|
||||
{
|
||||
$this->phone_number = $new_phone_number;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fax number.
|
||||
*
|
||||
* @return string the fax number of the company
|
||||
*/
|
||||
public function getFaxNumber(): string
|
||||
{
|
||||
return $this->fax_number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the fax number.
|
||||
*
|
||||
* @param string $new_fax_number the new fax number
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setFaxNumber(string $new_fax_number): self
|
||||
{
|
||||
$this->fax_number = $new_fax_number;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the e-mail address.
|
||||
*
|
||||
* @return string the e-mail address of the company
|
||||
*/
|
||||
public function getEmailAddress(): string
|
||||
{
|
||||
return $this->email_address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the e-mail address.
|
||||
*
|
||||
* @param string $new_email_address the new e-mail address
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setEmailAddress(string $new_email_address): self
|
||||
{
|
||||
$this->email_address = $new_email_address;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the website.
|
||||
*
|
||||
* @return string the website of the company
|
||||
*/
|
||||
public function getWebsite(): string
|
||||
{
|
||||
return $this->website;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the website.
|
||||
*
|
||||
* @param string $new_website the new website
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setWebsite(string $new_website): self
|
||||
{
|
||||
$this->website = $new_website;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the link to the website of an article.
|
||||
*
|
||||
* @param string|null $partnr * NULL for returning the URL with a placeholder for the part number
|
||||
* * or the part number for returning the direct URL to the article
|
||||
*
|
||||
* @return string the link to the article
|
||||
*/
|
||||
public function getAutoProductUrl(?string $partnr = null): string
|
||||
{
|
||||
if (is_string($partnr)) {
|
||||
return str_replace('%PARTNUMBER%', $partnr, $this->auto_product_url);
|
||||
}
|
||||
|
||||
return $this->auto_product_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the link to the website of an article.
|
||||
*
|
||||
* @param string $new_url the new URL with the placeholder %PARTNUMBER% for the part number
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAutoProductUrl(string $new_url): self
|
||||
{
|
||||
$this->auto_product_url = $new_url;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\Base;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
/**
|
||||
* Trait providing basic database element functionality with an ID.
|
||||
*/
|
||||
trait DBElementTrait
|
||||
{
|
||||
/**
|
||||
* @var int|null The Identification number for this element. This value is unique for the element in this table.
|
||||
* Null if the element is not saved to DB yet.
|
||||
*/
|
||||
#[Groups(['full', 'api:basic:read'])]
|
||||
#[ORM\Column(type: Types::INTEGER)]
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
protected ?int $id = null;
|
||||
|
||||
/**
|
||||
* Get the ID. The ID can be zero, or even negative (for virtual elements). If an element is virtual, can be
|
||||
* checked with isVirtualElement().
|
||||
*
|
||||
* Returns null, if the element is not saved to the DB yet.
|
||||
*
|
||||
* @return int|null the ID of this element
|
||||
*/
|
||||
public function getID(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone helper for DB element - resets ID on clone.
|
||||
*/
|
||||
protected function cloneDBElement(): void
|
||||
{
|
||||
if ($this->id) {
|
||||
//Set ID to null, so that a new entry is created
|
||||
$this->id = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\Base;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* Trait providing named element functionality.
|
||||
*/
|
||||
trait NamedElementTrait
|
||||
{
|
||||
/**
|
||||
* @var string The name of this element
|
||||
*/
|
||||
#[Assert\NotBlank]
|
||||
#[Groups(['simple', 'extended', 'full', 'import', 'api:basic:read', 'api:basic:write'])]
|
||||
#[ORM\Column(type: Types::STRING)]
|
||||
#[Assert\Length(max: 255)]
|
||||
protected string $name = '';
|
||||
|
||||
/**
|
||||
* Get the name of this element.
|
||||
*
|
||||
* @return string the name of this element
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the name of this element.
|
||||
*
|
||||
* @param string $new_name the new name
|
||||
*/
|
||||
public function setName(string $new_name): self
|
||||
{
|
||||
$this->name = $new_name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* String representation returns the name.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->getName();
|
||||
}
|
||||
}
|
||||
@@ -1,381 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\Base;
|
||||
|
||||
use App\Validator\Constraints\NoneOfItsChildren;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use InvalidArgumentException;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Serializer\Annotation\SerializedName;
|
||||
use function count;
|
||||
|
||||
/**
|
||||
* Trait for structural/hierarchical elements forming a tree structure.
|
||||
*
|
||||
* Requirements:
|
||||
* - Class using this trait must have getName() method (e.g., via NamedElementTrait)
|
||||
* - Class using this trait must have getID() method (e.g., via DBElementTrait)
|
||||
* - Class should implement StructuralElementInterface
|
||||
*/
|
||||
trait StructuralElementTrait
|
||||
{
|
||||
/**
|
||||
* This is a not standard character, so build a const, so a dev can easily use it.
|
||||
*/
|
||||
final public const PATH_DELIMITER_ARROW = ' → ';
|
||||
|
||||
/**
|
||||
* @var string The comment info for this element as markdown
|
||||
*/
|
||||
#[Groups(['full', 'import'])]
|
||||
#[ORM\Column(type: Types::TEXT)]
|
||||
protected string $comment = '';
|
||||
|
||||
/**
|
||||
* @var bool If this property is set, this element can not be selected for part properties.
|
||||
* Useful if this element should be used only for grouping, sorting.
|
||||
*/
|
||||
#[Groups(['full', 'import'])]
|
||||
#[ORM\Column(type: Types::BOOLEAN)]
|
||||
protected bool $not_selectable = false;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected int $level = 0;
|
||||
|
||||
/**
|
||||
* We can not define the mapping here, or we will get an exception. Unfortunately we have to do the mapping in the
|
||||
* subclasses.
|
||||
*
|
||||
* @var Collection<int, static>
|
||||
*/
|
||||
#[Groups(['include_children'])]
|
||||
protected Collection $children;
|
||||
|
||||
/**
|
||||
* @var static|null
|
||||
*/
|
||||
#[Groups(['include_parents', 'import'])]
|
||||
#[NoneOfItsChildren]
|
||||
protected ?self $parent = null;
|
||||
|
||||
/** @var string[] all names of all parent elements as an array of strings,
|
||||
* the last array element is the name of the element itself
|
||||
*/
|
||||
private array $full_path_strings = [];
|
||||
|
||||
/**
|
||||
* Alternative names (semicolon-separated) for this element, which can be used for searching (especially for info provider system)
|
||||
*/
|
||||
#[ORM\Column(type: Types::TEXT, nullable: true, options: ['default' => null])]
|
||||
private ?string $alternative_names = '';
|
||||
|
||||
/**
|
||||
* Initialize structural element collections.
|
||||
*/
|
||||
protected function initializeStructuralElement(): void
|
||||
{
|
||||
$this->children = new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this element is a child of another element (recursive).
|
||||
*
|
||||
* @param self $another_element the object to compare
|
||||
* IMPORTANT: both objects to compare must be from the same class (for example two "Device" objects)!
|
||||
*
|
||||
* @return bool true, if this element is child of $another_element
|
||||
*
|
||||
* @throws InvalidArgumentException if there was an error
|
||||
*/
|
||||
public function isChildOf(self $another_element): bool
|
||||
{
|
||||
$class_name = static::class;
|
||||
|
||||
//Check if both elements compared, are from the same type
|
||||
// (we have to check inheritance, or we get exceptions when using doctrine entities (they have a proxy type):
|
||||
if (!$another_element instanceof $class_name && !is_a($this, $another_element::class)) {
|
||||
throw new InvalidArgumentException('isChildOf() only works for objects of the same type!');
|
||||
}
|
||||
|
||||
if (!$this->getParent() instanceof self) { // this is the root node
|
||||
return false;
|
||||
}
|
||||
|
||||
//If the parent element is equal to the element we want to compare, return true
|
||||
if ($this->getParent()->getID() === null) {
|
||||
//If the IDs are not yet defined, we have to compare the objects itself
|
||||
if ($this->getParent() === $another_element) {
|
||||
return true;
|
||||
}
|
||||
} elseif ($this->getParent()->getID() === $another_element->getID()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//Otherwise, check recursively
|
||||
return $this->parent->isChildOf($another_element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this element is a root element (has no parent).
|
||||
*
|
||||
* @return bool true if this element is a root element
|
||||
*/
|
||||
public function isRoot(): bool
|
||||
{
|
||||
return $this->parent === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent of this element.
|
||||
*
|
||||
* @return static|null The parent element. Null if this element does not have a parent.
|
||||
*/
|
||||
public function getParent(): ?self
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the comment of the element as markdown encoded string.
|
||||
*
|
||||
* @return string|null the comment
|
||||
*/
|
||||
public function getComment(): ?string
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the comment.
|
||||
*
|
||||
* @param string $new_comment the new comment
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setComment(string $new_comment): self
|
||||
{
|
||||
$this->comment = $new_comment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the level.
|
||||
*
|
||||
* The level of the root node is -1.
|
||||
*
|
||||
* @return int the level of this element (zero means the topmost element
|
||||
* [a sub element of the root node])
|
||||
*/
|
||||
public function getLevel(): int
|
||||
{
|
||||
/*
|
||||
* Only check for nodes that have a parent. In the other cases zero is correct.
|
||||
*/
|
||||
if (0 === $this->level && $this->parent instanceof self) {
|
||||
$element = $this->parent;
|
||||
while ($element instanceof self) {
|
||||
$element = $element->parent;
|
||||
++$this->level;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full path.
|
||||
*
|
||||
* @param string $delimiter the delimiter of the returned string
|
||||
*
|
||||
* @return string the full path (incl. the name of this element), delimited by $delimiter
|
||||
*/
|
||||
#[Groups(['api:basic:read'])]
|
||||
#[SerializedName('full_path')]
|
||||
public function getFullPath(string $delimiter = self::PATH_DELIMITER_ARROW): string
|
||||
{
|
||||
if ($this->full_path_strings === []) {
|
||||
$this->full_path_strings = [];
|
||||
$this->full_path_strings[] = $this->getName();
|
||||
$element = $this;
|
||||
|
||||
$overflow = 20; //We only allow 20 levels depth
|
||||
|
||||
while ($element->parent instanceof self && $overflow >= 0) {
|
||||
$element = $element->parent;
|
||||
$this->full_path_strings[] = $element->getName();
|
||||
//Decrement to prevent mem overflow.
|
||||
--$overflow;
|
||||
}
|
||||
|
||||
$this->full_path_strings = array_reverse($this->full_path_strings);
|
||||
}
|
||||
|
||||
return implode($delimiter, $this->full_path_strings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path to this element (including the element itself).
|
||||
*
|
||||
* @return self[] An array with all (recursively) parent elements (including this one),
|
||||
* ordered from the lowest levels (root node) first to the highest level (the element itself)
|
||||
*/
|
||||
public function getPathArray(): array
|
||||
{
|
||||
$tmp = [];
|
||||
$tmp[] = $this;
|
||||
|
||||
//We only allow 20 levels depth
|
||||
while (!end($tmp)->isRoot() && count($tmp) < 20) {
|
||||
$tmp[] = end($tmp)->parent;
|
||||
}
|
||||
|
||||
return array_reverse($tmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all sub elements of this element.
|
||||
*
|
||||
* @return Collection<static>|iterable all subelements as an array of objects (sorted by their full path)
|
||||
*/
|
||||
public function getSubelements(): iterable
|
||||
{
|
||||
//If the parent is equal to this object, we would get an endless loop, so just return an empty array
|
||||
//This is just a workaround, as validator should prevent this behaviour, before it gets written to the database
|
||||
if ($this->parent === $this) {
|
||||
return new ArrayCollection();
|
||||
}
|
||||
|
||||
return $this->children ?? new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see getSubelements()
|
||||
* @return Collection<static>|iterable
|
||||
*/
|
||||
public function getChildren(): iterable
|
||||
{
|
||||
return $this->getSubelements();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the new parent object.
|
||||
*
|
||||
* @param static|null $new_parent The new parent object
|
||||
* @return $this
|
||||
*/
|
||||
public function setParent(?self $new_parent): self
|
||||
{
|
||||
$this->parent = $new_parent;
|
||||
|
||||
//Add this element as child to the new parent
|
||||
if ($new_parent instanceof self) {
|
||||
$new_parent->getChildren()->add($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given element as child to this element.
|
||||
* @param static $child
|
||||
* @return $this
|
||||
*/
|
||||
public function addChild(self $child): self
|
||||
{
|
||||
$this->children->add($child);
|
||||
//Children get this element as parent
|
||||
$child->setParent($this);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given element as child from this element.
|
||||
* @param static $child
|
||||
* @return $this
|
||||
*/
|
||||
public function removeChild(self $child): self
|
||||
{
|
||||
$this->children->removeElement($child);
|
||||
//Children has no parent anymore
|
||||
$child->setParent(null);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isNotSelectable(): bool
|
||||
{
|
||||
return $this->not_selectable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setNotSelectable(bool $not_selectable): self
|
||||
{
|
||||
$this->not_selectable = $not_selectable;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function clearChildren(): self
|
||||
{
|
||||
$this->children = new ArrayCollection();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a comma separated list of alternative names.
|
||||
* @return string|null
|
||||
*/
|
||||
public function getAlternativeNames(): ?string
|
||||
{
|
||||
if ($this->alternative_names === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//Remove trailing comma
|
||||
return rtrim($this->alternative_names, ',');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a comma separated list of alternative names.
|
||||
* @return $this
|
||||
*/
|
||||
public function setAlternativeNames(?string $new_value): self
|
||||
{
|
||||
//Add a trailing comma, if not already there (makes it easier to find in the database)
|
||||
if (is_string($new_value) && !str_ends_with($new_value, ',')) {
|
||||
$new_value .= ',';
|
||||
}
|
||||
|
||||
$this->alternative_names = $new_value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\Contracts;
|
||||
|
||||
/**
|
||||
* Interface for company entities (suppliers, manufacturers).
|
||||
*/
|
||||
interface CompanyInterface
|
||||
{
|
||||
/**
|
||||
* Get the address.
|
||||
*
|
||||
* @return string the address of the company (with "\n" as line break)
|
||||
*/
|
||||
public function getAddress(): string;
|
||||
|
||||
/**
|
||||
* Get the phone number.
|
||||
*
|
||||
* @return string the phone number of the company
|
||||
*/
|
||||
public function getPhoneNumber(): string;
|
||||
|
||||
/**
|
||||
* Get the e-mail address.
|
||||
*
|
||||
* @return string the e-mail address of the company
|
||||
*/
|
||||
public function getEmailAddress(): string;
|
||||
|
||||
/**
|
||||
* Get the website.
|
||||
*
|
||||
* @return string the website of the company
|
||||
*/
|
||||
public function getWebsite(): string;
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\Contracts;
|
||||
|
||||
/**
|
||||
* Interface for entities that have a database ID.
|
||||
*/
|
||||
interface DBElementInterface
|
||||
{
|
||||
/**
|
||||
* Get the ID. The ID can be zero, or even negative (for virtual elements).
|
||||
*
|
||||
* Returns null, if the element is not saved to the DB yet.
|
||||
*
|
||||
* @return int|null the ID of this element
|
||||
*/
|
||||
public function getID(): ?int;
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\Contracts;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
|
||||
/**
|
||||
* Interface for entities that have parameters.
|
||||
*/
|
||||
interface HasParametersInterface
|
||||
{
|
||||
/**
|
||||
* Return all associated parameters.
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getParameters(): Collection;
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\Contracts;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
|
||||
/**
|
||||
* Interface for structural elements that form a tree hierarchy.
|
||||
*/
|
||||
interface StructuralElementInterface
|
||||
{
|
||||
/**
|
||||
* Get the parent of this element.
|
||||
*
|
||||
* @return static|null The parent element. Null if this element does not have a parent.
|
||||
*/
|
||||
public function getParent(): ?self;
|
||||
|
||||
/**
|
||||
* Get all sub elements of this element.
|
||||
*
|
||||
* @return Collection<static>|iterable all subelements
|
||||
*/
|
||||
public function getChildren(): iterable;
|
||||
|
||||
/**
|
||||
* Checks if this element is a root element (has no parent).
|
||||
*
|
||||
* @return bool true if this element is a root element
|
||||
*/
|
||||
public function isRoot(): bool;
|
||||
|
||||
/**
|
||||
* Get the full path.
|
||||
*
|
||||
* @param string $delimiter the delimiter of the returned string
|
||||
*
|
||||
* @return string the full path (incl. the name of this element), delimited by $delimiter
|
||||
*/
|
||||
public function getFullPath(string $delimiter = ' → '): string;
|
||||
|
||||
/**
|
||||
* Get the level.
|
||||
*
|
||||
* The level of the root node is -1.
|
||||
*
|
||||
* @return int the level of this element (zero means the topmost element)
|
||||
*/
|
||||
public function getLevel(): int;
|
||||
}
|
||||
@@ -39,44 +39,28 @@ use ApiPlatform\OpenApi\Model\Operation;
|
||||
use ApiPlatform\Serializer\Filter\PropertyFilter;
|
||||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Base\AttachmentsTrait;
|
||||
use App\Entity\Base\DBElementTrait;
|
||||
use App\Entity\Base\MasterAttachmentTrait;
|
||||
use App\Entity\Base\NamedElementTrait;
|
||||
use App\Entity\Base\StructuralElementTrait;
|
||||
use App\Entity\Base\TimestampTrait;
|
||||
use App\Entity\Contracts\DBElementInterface;
|
||||
use App\Entity\Contracts\HasAttachmentsInterface;
|
||||
use App\Entity\Contracts\HasMasterAttachmentInterface;
|
||||
use App\Entity\Contracts\HasParametersInterface;
|
||||
use App\Entity\Contracts\NamedElementInterface;
|
||||
use App\Entity\Contracts\StructuralElementInterface;
|
||||
use App\Entity\Contracts\TimeStampableInterface;
|
||||
use App\Entity\EDA\EDACategoryInfo;
|
||||
use App\Entity\Parameters\ParametersTrait;
|
||||
use App\EntityListeners\TreeCacheInvalidationListener;
|
||||
use App\Repository\Parts\CategoryRepository;
|
||||
use App\Validator\Constraints\UniqueObjectCollection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use App\Entity\Attachments\CategoryAttachment;
|
||||
use App\Entity\Base\AbstractPartsContainingDBElement;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use App\Entity\Parameters\CategoryParameter;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* This entity describes a category, a part can belong to, which is used to group parts by their function.
|
||||
*
|
||||
* @extends AbstractPartsContainingDBElement<CategoryAttachment, CategoryParameter>
|
||||
*/
|
||||
#[ORM\Entity(repositoryClass: CategoryRepository::class)]
|
||||
#[ORM\Table(name: '`categories`')]
|
||||
#[ORM\Index(columns: ['name'], name: 'category_idx_name')]
|
||||
#[ORM\Index(columns: ['parent_id', 'name'], name: 'category_idx_parent_name')]
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
#[ORM\EntityListeners([TreeCacheInvalidationListener::class])]
|
||||
#[UniqueEntity(fields: ['name', 'parent'], message: 'structural.entity.unique_name', ignoreNull: false)]
|
||||
#[ApiResource(
|
||||
operations: [
|
||||
new Get(security: 'is_granted("read", object)'),
|
||||
@@ -105,16 +89,8 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])]
|
||||
#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)]
|
||||
#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])]
|
||||
class Category implements DBElementInterface, NamedElementInterface, TimeStampableInterface, HasAttachmentsInterface, HasMasterAttachmentInterface, StructuralElementInterface, HasParametersInterface, \Stringable, \JsonSerializable
|
||||
class Category extends AbstractPartsContainingDBElement
|
||||
{
|
||||
use DBElementTrait;
|
||||
use NamedElementTrait;
|
||||
use TimestampTrait;
|
||||
use AttachmentsTrait;
|
||||
use MasterAttachmentTrait;
|
||||
use StructuralElementTrait;
|
||||
use ParametersTrait;
|
||||
|
||||
#[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)]
|
||||
#[ORM\OrderBy(['name' => Criteria::ASC])]
|
||||
protected Collection $children;
|
||||
@@ -123,7 +99,7 @@ class Category implements DBElementInterface, NamedElementInterface, TimeStampab
|
||||
#[ORM\JoinColumn(name: 'parent_id')]
|
||||
#[Groups(['category:read', 'category:write'])]
|
||||
#[ApiProperty(readableLink: false, writableLink: false)]
|
||||
protected ?self $parent = null;
|
||||
protected ?AbstractStructuralDBElement $parent = null;
|
||||
|
||||
#[Groups(['category:read', 'category:write'])]
|
||||
protected string $comment = '';
|
||||
@@ -208,7 +184,6 @@ class Category implements DBElementInterface, NamedElementInterface, TimeStampab
|
||||
/** @var Collection<int, CategoryParameter>
|
||||
*/
|
||||
#[Assert\Valid]
|
||||
#[UniqueObjectCollection(fields: ['name', 'group', 'element'])]
|
||||
#[Groups(['full', 'category:read', 'category:write'])]
|
||||
#[ORM\OneToMany(mappedBy: 'element', targetEntity: CategoryParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
#[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])]
|
||||
@@ -226,37 +201,13 @@ class Category implements DBElementInterface, NamedElementInterface, TimeStampab
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->initializeAttachments();
|
||||
$this->initializeStructuralElement();
|
||||
parent::__construct();
|
||||
$this->children = new ArrayCollection();
|
||||
$this->attachments = new ArrayCollection();
|
||||
$this->parameters = new ArrayCollection();
|
||||
$this->eda_info = new EDACategoryInfo();
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
if ($this->id) {
|
||||
$this->cloneDBElement();
|
||||
$this->cloneAttachments();
|
||||
|
||||
// We create a new object, so give it a new creation date
|
||||
$this->addedDate = null;
|
||||
|
||||
//Deep clone parameters
|
||||
$parameters = $this->parameters;
|
||||
$this->parameters = new ArrayCollection();
|
||||
foreach ($parameters as $parameter) {
|
||||
$this->addParameter(clone $parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return ['@id' => $this->getID()];
|
||||
}
|
||||
|
||||
public function getPartnameHint(): string
|
||||
{
|
||||
return $this->partname_hint;
|
||||
|
||||
@@ -39,43 +39,27 @@ use ApiPlatform\OpenApi\Model\Operation;
|
||||
use ApiPlatform\Serializer\Filter\PropertyFilter;
|
||||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Base\AttachmentsTrait;
|
||||
use App\Entity\Base\DBElementTrait;
|
||||
use App\Entity\Base\MasterAttachmentTrait;
|
||||
use App\Entity\Base\NamedElementTrait;
|
||||
use App\Entity\Base\StructuralElementTrait;
|
||||
use App\Entity\Base\TimestampTrait;
|
||||
use App\Entity\Contracts\DBElementInterface;
|
||||
use App\Entity\Contracts\HasAttachmentsInterface;
|
||||
use App\Entity\Contracts\HasMasterAttachmentInterface;
|
||||
use App\Entity\Contracts\HasParametersInterface;
|
||||
use App\Entity\Contracts\NamedElementInterface;
|
||||
use App\Entity\Contracts\StructuralElementInterface;
|
||||
use App\Entity\Contracts\TimeStampableInterface;
|
||||
use App\Entity\EDA\EDAFootprintInfo;
|
||||
use App\Entity\Parameters\ParametersTrait;
|
||||
use App\EntityListeners\TreeCacheInvalidationListener;
|
||||
use App\Repository\Parts\FootprintRepository;
|
||||
use App\Validator\Constraints\UniqueObjectCollection;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use App\Entity\Attachments\FootprintAttachment;
|
||||
use App\Entity\Base\AbstractPartsContainingDBElement;
|
||||
use App\Entity\Parameters\FootprintParameter;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* This entity represents a footprint of a part (its physical dimensions and shape).
|
||||
*
|
||||
* @extends AbstractPartsContainingDBElement<FootprintAttachment, FootprintParameter>
|
||||
*/
|
||||
#[ORM\Entity(repositoryClass: FootprintRepository::class)]
|
||||
#[ORM\Table('`footprints`')]
|
||||
#[ORM\Index(columns: ['name'], name: 'footprint_idx_name')]
|
||||
#[ORM\Index(columns: ['parent_id', 'name'], name: 'footprint_idx_parent_name')]
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
#[ORM\EntityListeners([TreeCacheInvalidationListener::class])]
|
||||
#[UniqueEntity(fields: ['name', 'parent'], message: 'structural.entity.unique_name', ignoreNull: false)]
|
||||
#[ApiResource(
|
||||
operations: [
|
||||
new Get(security: 'is_granted("read", object)'),
|
||||
@@ -104,21 +88,13 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])]
|
||||
#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)]
|
||||
#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])]
|
||||
class Footprint implements DBElementInterface, NamedElementInterface, TimeStampableInterface, HasAttachmentsInterface, HasMasterAttachmentInterface, StructuralElementInterface, HasParametersInterface, \Stringable, \JsonSerializable
|
||||
class Footprint extends AbstractPartsContainingDBElement
|
||||
{
|
||||
use DBElementTrait;
|
||||
use NamedElementTrait;
|
||||
use TimestampTrait;
|
||||
use AttachmentsTrait;
|
||||
use MasterAttachmentTrait;
|
||||
use StructuralElementTrait;
|
||||
use ParametersTrait;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')]
|
||||
#[ORM\JoinColumn(name: 'parent_id')]
|
||||
#[Groups(['footprint:read', 'footprint:write'])]
|
||||
#[ApiProperty(readableLink: false, writableLink: false)]
|
||||
protected ?self $parent = null;
|
||||
protected ?AbstractStructuralDBElement $parent = null;
|
||||
|
||||
#[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)]
|
||||
#[ORM\OrderBy(['name' => Criteria::ASC])]
|
||||
@@ -152,7 +128,6 @@ class Footprint implements DBElementInterface, NamedElementInterface, TimeStampa
|
||||
/** @var Collection<int, FootprintParameter>
|
||||
*/
|
||||
#[Assert\Valid]
|
||||
#[UniqueObjectCollection(fields: ['name', 'group', 'element'])]
|
||||
#[ORM\OneToMany(mappedBy: 'element', targetEntity: FootprintParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
#[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])]
|
||||
#[Groups(['footprint:read', 'footprint:write'])]
|
||||
@@ -170,37 +145,13 @@ class Footprint implements DBElementInterface, NamedElementInterface, TimeStampa
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->initializeAttachments();
|
||||
$this->initializeStructuralElement();
|
||||
parent::__construct();
|
||||
$this->children = new ArrayCollection();
|
||||
$this->attachments = new ArrayCollection();
|
||||
$this->parameters = new ArrayCollection();
|
||||
$this->eda_info = new EDAFootprintInfo();
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
if ($this->id) {
|
||||
$this->cloneDBElement();
|
||||
$this->cloneAttachments();
|
||||
|
||||
// We create a new object, so give it a new creation date
|
||||
$this->addedDate = null;
|
||||
|
||||
//Deep clone parameters
|
||||
$parameters = $this->parameters;
|
||||
$this->parameters = new ArrayCollection();
|
||||
foreach ($parameters as $parameter) {
|
||||
$this->addParameter(clone $parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return ['@id' => $this->getID()];
|
||||
}
|
||||
|
||||
/****************************************
|
||||
* Getters
|
||||
****************************************/
|
||||
|
||||
@@ -39,44 +39,26 @@ use ApiPlatform\OpenApi\Model\Operation;
|
||||
use ApiPlatform\Serializer\Filter\PropertyFilter;
|
||||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Base\AttachmentsTrait;
|
||||
use App\Entity\Base\CompanyTrait;
|
||||
use App\Entity\Base\DBElementTrait;
|
||||
use App\Entity\Base\MasterAttachmentTrait;
|
||||
use App\Entity\Base\NamedElementTrait;
|
||||
use App\Entity\Base\StructuralElementTrait;
|
||||
use App\Entity\Base\TimestampTrait;
|
||||
use App\Entity\Contracts\CompanyInterface;
|
||||
use App\Entity\Contracts\DBElementInterface;
|
||||
use App\Entity\Contracts\HasAttachmentsInterface;
|
||||
use App\Entity\Contracts\HasMasterAttachmentInterface;
|
||||
use App\Entity\Contracts\HasParametersInterface;
|
||||
use App\Entity\Contracts\NamedElementInterface;
|
||||
use App\Entity\Contracts\StructuralElementInterface;
|
||||
use App\Entity\Contracts\TimeStampableInterface;
|
||||
use App\Entity\Parameters\ParametersTrait;
|
||||
use App\EntityListeners\TreeCacheInvalidationListener;
|
||||
use App\Repository\Parts\ManufacturerRepository;
|
||||
use App\Validator\Constraints\UniqueObjectCollection;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use App\Entity\Attachments\ManufacturerAttachment;
|
||||
use App\Entity\Base\AbstractCompany;
|
||||
use App\Entity\Parameters\ManufacturerParameter;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* This entity represents a manufacturer of a part (The company that produces the part).
|
||||
*
|
||||
* @extends AbstractCompany<ManufacturerAttachment, ManufacturerParameter>
|
||||
*/
|
||||
#[ORM\Entity(repositoryClass: ManufacturerRepository::class)]
|
||||
#[ORM\Table('`manufacturers`')]
|
||||
#[ORM\Index(columns: ['name'], name: 'manufacturer_name')]
|
||||
#[ORM\Index(columns: ['parent_id', 'name'], name: 'manufacturer_idx_parent_name')]
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
#[ORM\EntityListeners([TreeCacheInvalidationListener::class])]
|
||||
#[UniqueEntity(fields: ['name', 'parent'], message: 'structural.entity.unique_name', ignoreNull: false)]
|
||||
#[ApiResource(
|
||||
operations: [
|
||||
new Get(security: 'is_granted("read", object)'),
|
||||
@@ -105,22 +87,13 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])]
|
||||
#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)]
|
||||
#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])]
|
||||
class Manufacturer implements DBElementInterface, NamedElementInterface, TimeStampableInterface, HasAttachmentsInterface, HasMasterAttachmentInterface, StructuralElementInterface, HasParametersInterface, CompanyInterface, \Stringable, \JsonSerializable
|
||||
class Manufacturer extends AbstractCompany
|
||||
{
|
||||
use DBElementTrait;
|
||||
use NamedElementTrait;
|
||||
use TimestampTrait;
|
||||
use AttachmentsTrait;
|
||||
use MasterAttachmentTrait;
|
||||
use StructuralElementTrait;
|
||||
use ParametersTrait;
|
||||
use CompanyTrait;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')]
|
||||
#[ORM\JoinColumn(name: 'parent_id')]
|
||||
#[Groups(['manufacturer:read', 'manufacturer:write'])]
|
||||
#[ApiProperty(readableLink: false, writableLink: false)]
|
||||
protected ?self $parent = null;
|
||||
protected ?AbstractStructuralDBElement $parent = null;
|
||||
|
||||
#[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)]
|
||||
#[ORM\OrderBy(['name' => Criteria::ASC])]
|
||||
@@ -145,50 +118,16 @@ class Manufacturer implements DBElementInterface, NamedElementInterface, TimeSta
|
||||
/** @var Collection<int, ManufacturerParameter>
|
||||
*/
|
||||
#[Assert\Valid]
|
||||
#[UniqueObjectCollection(fields: ['name', 'group', 'element'])]
|
||||
#[ORM\OneToMany(mappedBy: 'element', targetEntity: ManufacturerParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
#[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])]
|
||||
#[Groups(['manufacturer:read', 'manufacturer:write'])]
|
||||
#[ApiProperty(readableLink: false, writableLink: true)]
|
||||
protected Collection $parameters;
|
||||
|
||||
#[Groups(['manufacturer:read', 'manufacturer:write'])]
|
||||
protected string $comment = '';
|
||||
|
||||
#[Groups(['manufacturer:read'])]
|
||||
protected ?\DateTimeImmutable $addedDate = null;
|
||||
#[Groups(['manufacturer:read'])]
|
||||
protected ?\DateTimeImmutable $lastModified = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->initializeAttachments();
|
||||
$this->initializeStructuralElement();
|
||||
parent::__construct();
|
||||
$this->children = new ArrayCollection();
|
||||
$this->attachments = new ArrayCollection();
|
||||
$this->parameters = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
if ($this->id) {
|
||||
$this->cloneDBElement();
|
||||
$this->cloneAttachments();
|
||||
|
||||
// We create a new object, so give it a new creation date
|
||||
$this->addedDate = null;
|
||||
|
||||
//Deep clone parameters
|
||||
$parameters = $this->parameters;
|
||||
$this->parameters = new ArrayCollection();
|
||||
foreach ($parameters as $parameter) {
|
||||
$this->addParameter(clone $parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return ['@id' => $this->getID()];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,26 +39,12 @@ use ApiPlatform\OpenApi\Model\Operation;
|
||||
use ApiPlatform\Serializer\Filter\PropertyFilter;
|
||||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Base\AttachmentsTrait;
|
||||
use App\Entity\Base\DBElementTrait;
|
||||
use App\Entity\Base\MasterAttachmentTrait;
|
||||
use App\Entity\Base\NamedElementTrait;
|
||||
use App\Entity\Base\StructuralElementTrait;
|
||||
use App\Entity\Base\TimestampTrait;
|
||||
use App\Entity\Contracts\DBElementInterface;
|
||||
use App\Entity\Contracts\HasAttachmentsInterface;
|
||||
use App\Entity\Contracts\HasMasterAttachmentInterface;
|
||||
use App\Entity\Contracts\HasParametersInterface;
|
||||
use App\Entity\Contracts\NamedElementInterface;
|
||||
use App\Entity\Contracts\StructuralElementInterface;
|
||||
use App\Entity\Contracts\TimeStampableInterface;
|
||||
use App\Entity\Parameters\ParametersTrait;
|
||||
use App\EntityListeners\TreeCacheInvalidationListener;
|
||||
use App\Repository\Parts\MeasurementUnitRepository;
|
||||
use App\Validator\Constraints\UniqueObjectCollection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use App\Entity\Attachments\MeasurementUnitAttachment;
|
||||
use App\Entity\Base\AbstractPartsContainingDBElement;
|
||||
use App\Entity\Parameters\MeasurementUnitParameter;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
@@ -70,15 +56,14 @@ use Symfony\Component\Validator\Constraints\Length;
|
||||
/**
|
||||
* This unit represents the unit in which the amount of parts in stock are measured.
|
||||
* This could be something like N, grams, meters, etc...
|
||||
*
|
||||
* @extends AbstractPartsContainingDBElement<MeasurementUnitAttachment,MeasurementUnitParameter>
|
||||
*/
|
||||
#[UniqueEntity('unit')]
|
||||
#[ORM\Entity(repositoryClass: MeasurementUnitRepository::class)]
|
||||
#[ORM\Table(name: '`measurement_units`')]
|
||||
#[ORM\Index(columns: ['name'], name: 'unit_idx_name')]
|
||||
#[ORM\Index(columns: ['parent_id', 'name'], name: 'unit_idx_parent_name')]
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
#[ORM\EntityListeners([TreeCacheInvalidationListener::class])]
|
||||
#[UniqueEntity(fields: ['name', 'parent'], message: 'structural.entity.unique_name', ignoreNull: false)]
|
||||
#[ApiResource(
|
||||
operations: [
|
||||
new Get(security: 'is_granted("read", object)'),
|
||||
@@ -107,15 +92,8 @@ use Symfony\Component\Validator\Constraints\Length;
|
||||
#[ApiFilter(LikeFilter::class, properties: ["name", "comment", "unit"])]
|
||||
#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)]
|
||||
#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])]
|
||||
class MeasurementUnit implements DBElementInterface, NamedElementInterface, TimeStampableInterface, HasAttachmentsInterface, HasMasterAttachmentInterface, StructuralElementInterface, HasParametersInterface, \Stringable, \JsonSerializable
|
||||
class MeasurementUnit extends AbstractPartsContainingDBElement
|
||||
{
|
||||
use DBElementTrait;
|
||||
use NamedElementTrait;
|
||||
use TimestampTrait;
|
||||
use AttachmentsTrait;
|
||||
use MasterAttachmentTrait;
|
||||
use StructuralElementTrait;
|
||||
use ParametersTrait;
|
||||
/**
|
||||
* @var string The unit symbol that should be used for the Unit. This could be something like "", g (for grams)
|
||||
* or m (for meters).
|
||||
@@ -153,7 +131,7 @@ class MeasurementUnit implements DBElementInterface, NamedElementInterface, Time
|
||||
#[ORM\JoinColumn(name: 'parent_id')]
|
||||
#[Groups(['measurement_unit:read', 'measurement_unit:write'])]
|
||||
#[ApiProperty(readableLink: false, writableLink: false)]
|
||||
protected ?self $parent = null;
|
||||
protected ?AbstractStructuralDBElement $parent = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, MeasurementUnitAttachment>
|
||||
@@ -172,7 +150,6 @@ class MeasurementUnit implements DBElementInterface, NamedElementInterface, Time
|
||||
/** @var Collection<int, MeasurementUnitParameter>
|
||||
*/
|
||||
#[Assert\Valid]
|
||||
#[UniqueObjectCollection(fields: ['name', 'group', 'element'])]
|
||||
#[ORM\OneToMany(mappedBy: 'element', targetEntity: MeasurementUnitParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
#[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])]
|
||||
#[Groups(['measurement_unit:read', 'measurement_unit:write'])]
|
||||
@@ -224,33 +201,9 @@ class MeasurementUnit implements DBElementInterface, NamedElementInterface, Time
|
||||
}
|
||||
public function __construct()
|
||||
{
|
||||
$this->initializeAttachments();
|
||||
$this->initializeStructuralElement();
|
||||
parent::__construct();
|
||||
$this->children = new ArrayCollection();
|
||||
$this->attachments = new ArrayCollection();
|
||||
$this->parameters = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
if ($this->id) {
|
||||
$this->cloneDBElement();
|
||||
$this->cloneAttachments();
|
||||
|
||||
// We create a new object, so give it a new creation date
|
||||
$this->addedDate = null;
|
||||
|
||||
//Deep clone parameters
|
||||
$parameters = $this->parameters;
|
||||
$this->parameters = new ArrayCollection();
|
||||
foreach ($parameters as $parameter) {
|
||||
$this->addParameter(clone $parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return ['@id' => $this->getID()];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,42 +37,26 @@ use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\Serializer\Filter\PropertyFilter;
|
||||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\Entity\Base\AttachmentsTrait;
|
||||
use App\Entity\Base\DBElementTrait;
|
||||
use App\Entity\Base\MasterAttachmentTrait;
|
||||
use App\Entity\Base\NamedElementTrait;
|
||||
use App\Entity\Base\StructuralElementTrait;
|
||||
use App\Entity\Base\TimestampTrait;
|
||||
use App\Entity\Contracts\DBElementInterface;
|
||||
use App\Entity\Contracts\HasAttachmentsInterface;
|
||||
use App\Entity\Contracts\HasMasterAttachmentInterface;
|
||||
use App\Entity\Contracts\HasParametersInterface;
|
||||
use App\Entity\Contracts\NamedElementInterface;
|
||||
use App\Entity\Contracts\StructuralElementInterface;
|
||||
use App\Entity\Contracts\TimeStampableInterface;
|
||||
use App\Entity\Base\AbstractPartsContainingDBElement;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use App\Entity\Parameters\PartCustomStateParameter;
|
||||
use App\Entity\Parameters\ParametersTrait;
|
||||
use App\EntityListeners\TreeCacheInvalidationListener;
|
||||
use App\Repository\Parts\PartCustomStateRepository;
|
||||
use App\Validator\Constraints\UniqueObjectCollection;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* This entity represents a custom part state.
|
||||
* If an organisation uses Part-DB and has its custom part states, this is useful.
|
||||
*
|
||||
* @extends AbstractPartsContainingDBElement<PartCustomStateAttachment,PartCustomStateParameter>
|
||||
*/
|
||||
#[ORM\Entity(repositoryClass: PartCustomStateRepository::class)]
|
||||
#[ORM\Table('`part_custom_states`')]
|
||||
#[ORM\Index(columns: ['name'], name: 'part_custom_state_name')]
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
#[ORM\EntityListeners([TreeCacheInvalidationListener::class])]
|
||||
#[UniqueEntity(fields: ['name', 'parent'], message: 'structural.entity.unique_name', ignoreNull: false)]
|
||||
#[ApiResource(
|
||||
operations: [
|
||||
new Get(security: 'is_granted("read", object)'),
|
||||
@@ -88,16 +72,8 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
#[ApiFilter(LikeFilter::class, properties: ["name"])]
|
||||
#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)]
|
||||
#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])]
|
||||
class PartCustomState implements DBElementInterface, NamedElementInterface, TimeStampableInterface, HasAttachmentsInterface, HasMasterAttachmentInterface, StructuralElementInterface, HasParametersInterface, \Stringable, \JsonSerializable
|
||||
class PartCustomState extends AbstractPartsContainingDBElement
|
||||
{
|
||||
use DBElementTrait;
|
||||
use NamedElementTrait;
|
||||
use TimestampTrait;
|
||||
use AttachmentsTrait;
|
||||
use MasterAttachmentTrait;
|
||||
use StructuralElementTrait;
|
||||
use ParametersTrait;
|
||||
|
||||
/**
|
||||
* @var string The comment info for this element as markdown
|
||||
*/
|
||||
@@ -112,7 +88,7 @@ class PartCustomState implements DBElementInterface, NamedElementInterface, Time
|
||||
#[ORM\JoinColumn(name: 'parent_id')]
|
||||
#[Groups(['part_custom_state:read', 'part_custom_state:write'])]
|
||||
#[ApiProperty(readableLink: false, writableLink: false)]
|
||||
protected ?self $parent = null;
|
||||
protected ?AbstractStructuralDBElement $parent = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, PartCustomStateAttachment>
|
||||
@@ -131,7 +107,6 @@ class PartCustomState implements DBElementInterface, NamedElementInterface, Time
|
||||
/** @var Collection<int, PartCustomStateParameter>
|
||||
*/
|
||||
#[Assert\Valid]
|
||||
#[UniqueObjectCollection(fields: ['name', 'group', 'element'])]
|
||||
#[ORM\OneToMany(mappedBy: 'element', targetEntity: PartCustomStateParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
#[ORM\OrderBy(['name' => 'ASC'])]
|
||||
#[Groups(['part_custom_state:read', 'part_custom_state:write'])]
|
||||
@@ -144,33 +119,9 @@ class PartCustomState implements DBElementInterface, NamedElementInterface, Time
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->initializeAttachments();
|
||||
$this->initializeStructuralElement();
|
||||
parent::__construct();
|
||||
$this->children = new ArrayCollection();
|
||||
$this->attachments = new ArrayCollection();
|
||||
$this->parameters = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
if ($this->id) {
|
||||
$this->cloneDBElement();
|
||||
$this->cloneAttachments();
|
||||
|
||||
// We create a new object, so give it a new creation date
|
||||
$this->addedDate = null;
|
||||
|
||||
//Deep clone parameters
|
||||
$parameters = $this->parameters;
|
||||
$this->parameters = new ArrayCollection();
|
||||
foreach ($parameters as $parameter) {
|
||||
$this->addParameter(clone $parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return ['@id' => $this->getID()];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,44 +39,27 @@ use ApiPlatform\OpenApi\Model\Operation;
|
||||
use ApiPlatform\Serializer\Filter\PropertyFilter;
|
||||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Base\AttachmentsTrait;
|
||||
use App\Entity\Base\DBElementTrait;
|
||||
use App\Entity\Base\MasterAttachmentTrait;
|
||||
use App\Entity\Base\NamedElementTrait;
|
||||
use App\Entity\Base\StructuralElementTrait;
|
||||
use App\Entity\Base\TimestampTrait;
|
||||
use App\Entity\Contracts\DBElementInterface;
|
||||
use App\Entity\Contracts\HasAttachmentsInterface;
|
||||
use App\Entity\Contracts\HasMasterAttachmentInterface;
|
||||
use App\Entity\Contracts\HasParametersInterface;
|
||||
use App\Entity\Contracts\NamedElementInterface;
|
||||
use App\Entity\Contracts\StructuralElementInterface;
|
||||
use App\Entity\Contracts\TimeStampableInterface;
|
||||
use App\Entity\Parameters\ParametersTrait;
|
||||
use App\EntityListeners\TreeCacheInvalidationListener;
|
||||
use App\Repository\Parts\StorelocationRepository;
|
||||
use App\Validator\Constraints\UniqueObjectCollection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use App\Entity\Attachments\StorageLocationAttachment;
|
||||
use App\Entity\Base\AbstractPartsContainingDBElement;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use App\Entity\Parameters\StorageLocationParameter;
|
||||
use App\Entity\UserSystem\User;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* This entity represents a storage location, where parts can be stored.
|
||||
* @extends AbstractPartsContainingDBElement<StorageLocationAttachment, StorageLocationParameter>
|
||||
*/
|
||||
#[ORM\Entity(repositoryClass: StorelocationRepository::class)]
|
||||
#[ORM\Table('`storelocations`')]
|
||||
#[ORM\Index(columns: ['name'], name: 'location_idx_name')]
|
||||
#[ORM\Index(columns: ['parent_id', 'name'], name: 'location_idx_parent_name')]
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
#[ORM\EntityListeners([TreeCacheInvalidationListener::class])]
|
||||
#[UniqueEntity(fields: ['name', 'parent'], message: 'structural.entity.unique_name', ignoreNull: false)]
|
||||
#[ApiResource(
|
||||
operations: [
|
||||
new Get(security: 'is_granted("read", object)'),
|
||||
@@ -105,16 +88,8 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])]
|
||||
#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)]
|
||||
#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])]
|
||||
class StorageLocation implements DBElementInterface, NamedElementInterface, TimeStampableInterface, HasAttachmentsInterface, HasMasterAttachmentInterface, StructuralElementInterface, HasParametersInterface, \Stringable, \JsonSerializable
|
||||
class StorageLocation extends AbstractPartsContainingDBElement
|
||||
{
|
||||
use DBElementTrait;
|
||||
use NamedElementTrait;
|
||||
use TimestampTrait;
|
||||
use AttachmentsTrait;
|
||||
use MasterAttachmentTrait;
|
||||
use StructuralElementTrait;
|
||||
use ParametersTrait;
|
||||
|
||||
#[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)]
|
||||
#[ORM\OrderBy(['name' => Criteria::ASC])]
|
||||
protected Collection $children;
|
||||
@@ -123,7 +98,7 @@ class StorageLocation implements DBElementInterface, NamedElementInterface, Time
|
||||
#[ORM\JoinColumn(name: 'parent_id')]
|
||||
#[Groups(['location:read', 'location:write'])]
|
||||
#[ApiProperty(readableLink: false, writableLink: false)]
|
||||
protected ?self $parent = null;
|
||||
protected ?AbstractStructuralDBElement $parent = null;
|
||||
|
||||
#[Groups(['location:read', 'location:write'])]
|
||||
protected string $comment = '';
|
||||
@@ -139,7 +114,6 @@ class StorageLocation implements DBElementInterface, NamedElementInterface, Time
|
||||
/** @var Collection<int, StorageLocationParameter>
|
||||
*/
|
||||
#[Assert\Valid]
|
||||
#[UniqueObjectCollection(fields: ['name', 'group', 'element'])]
|
||||
#[ORM\OneToMany(mappedBy: 'element', targetEntity: StorageLocationParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
#[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])]
|
||||
#[Groups(['location:read', 'location:write'])]
|
||||
@@ -321,33 +295,9 @@ class StorageLocation implements DBElementInterface, NamedElementInterface, Time
|
||||
}
|
||||
public function __construct()
|
||||
{
|
||||
$this->initializeAttachments();
|
||||
$this->initializeStructuralElement();
|
||||
parent::__construct();
|
||||
$this->children = new ArrayCollection();
|
||||
$this->parameters = new ArrayCollection();
|
||||
$this->attachments = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
if ($this->id) {
|
||||
$this->cloneDBElement();
|
||||
$this->cloneAttachments();
|
||||
|
||||
// We create a new object, so give it a new creation date
|
||||
$this->addedDate = null;
|
||||
|
||||
//Deep clone parameters
|
||||
$parameters = $this->parameters;
|
||||
$this->parameters = new ArrayCollection();
|
||||
foreach ($parameters as $parameter) {
|
||||
$this->addParameter(clone $parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return ['@id' => $this->getID()];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,28 +39,12 @@ use ApiPlatform\OpenApi\Model\Operation;
|
||||
use ApiPlatform\Serializer\Filter\PropertyFilter;
|
||||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Base\AttachmentsTrait;
|
||||
use App\Entity\Base\CompanyTrait;
|
||||
use App\Entity\Base\DBElementTrait;
|
||||
use App\Entity\Base\MasterAttachmentTrait;
|
||||
use App\Entity\Base\NamedElementTrait;
|
||||
use App\Entity\Base\StructuralElementTrait;
|
||||
use App\Entity\Base\TimestampTrait;
|
||||
use App\Entity\Contracts\CompanyInterface;
|
||||
use App\Entity\Contracts\DBElementInterface;
|
||||
use App\Entity\Contracts\HasAttachmentsInterface;
|
||||
use App\Entity\Contracts\HasMasterAttachmentInterface;
|
||||
use App\Entity\Contracts\HasParametersInterface;
|
||||
use App\Entity\Contracts\NamedElementInterface;
|
||||
use App\Entity\Contracts\StructuralElementInterface;
|
||||
use App\Entity\Contracts\TimeStampableInterface;
|
||||
use App\Entity\Parameters\ParametersTrait;
|
||||
use App\EntityListeners\TreeCacheInvalidationListener;
|
||||
use App\Repository\Parts\SupplierRepository;
|
||||
use App\Entity\PriceInformations\Orderdetail;
|
||||
use App\Validator\Constraints\UniqueObjectCollection;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use App\Entity\Attachments\SupplierAttachment;
|
||||
use App\Entity\Base\AbstractCompany;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use App\Entity\Parameters\SupplierParameter;
|
||||
use App\Entity\PriceInformations\Currency;
|
||||
use App\Validator\Constraints\BigDecimal\BigDecimalPositiveOrZero;
|
||||
@@ -68,20 +52,18 @@ use App\Validator\Constraints\Selectable;
|
||||
use Brick\Math\BigDecimal;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* This entity represents a supplier of parts (the company that sells the parts).
|
||||
*
|
||||
* @extends AbstractCompany<SupplierAttachment, SupplierParameter>
|
||||
*/
|
||||
#[ORM\Entity(repositoryClass: SupplierRepository::class)]
|
||||
#[ORM\Table('`suppliers`')]
|
||||
#[ORM\Index(columns: ['name'], name: 'supplier_idx_name')]
|
||||
#[ORM\Index(columns: ['parent_id', 'name'], name: 'supplier_idx_parent_name')]
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
#[ORM\EntityListeners([TreeCacheInvalidationListener::class])]
|
||||
#[UniqueEntity(fields: ['name', 'parent'], message: 'structural.entity.unique_name', ignoreNull: false)]
|
||||
#[ApiResource(
|
||||
operations: [
|
||||
new Get(security: 'is_granted("read", object)'),
|
||||
@@ -108,17 +90,8 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])]
|
||||
#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)]
|
||||
#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])]
|
||||
class Supplier implements DBElementInterface, NamedElementInterface, TimeStampableInterface, HasAttachmentsInterface, HasMasterAttachmentInterface, StructuralElementInterface, HasParametersInterface, CompanyInterface, \Stringable, \JsonSerializable
|
||||
class Supplier extends AbstractCompany
|
||||
{
|
||||
use DBElementTrait;
|
||||
use NamedElementTrait;
|
||||
use TimestampTrait;
|
||||
use AttachmentsTrait;
|
||||
use MasterAttachmentTrait;
|
||||
use StructuralElementTrait;
|
||||
use ParametersTrait;
|
||||
use CompanyTrait;
|
||||
|
||||
#[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)]
|
||||
#[ORM\OrderBy(['name' => Criteria::ASC])]
|
||||
protected Collection $children;
|
||||
@@ -127,7 +100,7 @@ class Supplier implements DBElementInterface, NamedElementInterface, TimeStampab
|
||||
#[ORM\JoinColumn(name: 'parent_id')]
|
||||
#[Groups(['supplier:read', 'supplier:write'])]
|
||||
#[ApiProperty(readableLink: false, writableLink: false)]
|
||||
protected ?self $parent = null;
|
||||
protected ?AbstractStructuralDBElement $parent = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, Orderdetail>
|
||||
@@ -171,21 +144,12 @@ class Supplier implements DBElementInterface, NamedElementInterface, TimeStampab
|
||||
/** @var Collection<int, SupplierParameter>
|
||||
*/
|
||||
#[Assert\Valid]
|
||||
#[UniqueObjectCollection(fields: ['name', 'group', 'element'])]
|
||||
#[ORM\OneToMany(mappedBy: 'element', targetEntity: SupplierParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
#[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])]
|
||||
#[Groups(['supplier:read', 'supplier:write'])]
|
||||
#[ApiProperty(readableLink: false, writableLink: true)]
|
||||
protected Collection $parameters;
|
||||
|
||||
#[Groups(['supplier:read', 'supplier:write'])]
|
||||
protected string $comment = '';
|
||||
|
||||
#[Groups(['supplier:read'])]
|
||||
protected ?\DateTimeImmutable $addedDate = null;
|
||||
#[Groups(['supplier:read'])]
|
||||
protected ?\DateTimeImmutable $lastModified = null;
|
||||
|
||||
/**
|
||||
* Gets the currency that should be used by default, when creating a orderdetail with this supplier.
|
||||
*/
|
||||
@@ -234,34 +198,10 @@ class Supplier implements DBElementInterface, NamedElementInterface, TimeStampab
|
||||
}
|
||||
public function __construct()
|
||||
{
|
||||
$this->initializeAttachments();
|
||||
$this->initializeStructuralElement();
|
||||
parent::__construct();
|
||||
$this->children = new ArrayCollection();
|
||||
$this->orderdetails = new ArrayCollection();
|
||||
$this->attachments = new ArrayCollection();
|
||||
$this->parameters = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
if ($this->id) {
|
||||
$this->cloneDBElement();
|
||||
$this->cloneAttachments();
|
||||
|
||||
// We create a new object, so give it a new creation date
|
||||
$this->addedDate = null;
|
||||
|
||||
//Deep clone parameters
|
||||
$parameters = $this->parameters;
|
||||
$this->parameters = new ArrayCollection();
|
||||
foreach ($parameters as $parameter) {
|
||||
$this->addParameter(clone $parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return ['@id' => $this->getID()];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,9 +50,9 @@ readonly class RegisterSynonymsAsTranslationParametersListener
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
public function getSynonymPlaceholders(string $locale): array
|
||||
public function getSynonymPlaceholders(): array
|
||||
{
|
||||
return $this->cache->get('partdb_synonym_placeholders' . '_' . $locale, function (ItemInterface $item) use ($locale) {
|
||||
return $this->cache->get('partdb_synonym_placeholders', function (ItemInterface $item) {
|
||||
$item->tag('synonyms');
|
||||
|
||||
|
||||
@@ -62,12 +62,12 @@ readonly class RegisterSynonymsAsTranslationParametersListener
|
||||
foreach (ElementTypes::cases() as $elementType) {
|
||||
//Versions with capitalized first letter
|
||||
$capitalized = ucfirst($elementType->value); //We have only ASCII element type values, so this is sufficient
|
||||
$placeholders['[' . $capitalized . ']'] = $this->typeNameGenerator->typeLabel($elementType, $locale);
|
||||
$placeholders['[[' . $capitalized . ']]'] = $this->typeNameGenerator->typeLabelPlural($elementType, $locale);
|
||||
$placeholders['[' . $capitalized . ']'] = $this->typeNameGenerator->typeLabel($elementType);
|
||||
$placeholders['[[' . $capitalized . ']]'] = $this->typeNameGenerator->typeLabelPlural($elementType);
|
||||
|
||||
//And we have lowercase versions for both
|
||||
$placeholders['[' . $elementType->value . ']'] = mb_strtolower($this->typeNameGenerator->typeLabel($elementType, $locale));
|
||||
$placeholders['[[' . $elementType->value . ']]'] = mb_strtolower($this->typeNameGenerator->typeLabelPlural($elementType, $locale));
|
||||
$placeholders['[' . $elementType->value . ']'] = mb_strtolower($this->typeNameGenerator->typeLabel($elementType));
|
||||
$placeholders['[[' . $elementType->value . ']]'] = mb_strtolower($this->typeNameGenerator->typeLabelPlural($elementType));
|
||||
}
|
||||
|
||||
return $placeholders;
|
||||
@@ -82,7 +82,7 @@ readonly class RegisterSynonymsAsTranslationParametersListener
|
||||
}
|
||||
|
||||
//Register all placeholders for synonyms
|
||||
$placeholders = $this->getSynonymPlaceholders($event->getRequest()->getLocale());
|
||||
$placeholders = $this->getSynonymPlaceholders();
|
||||
foreach ($placeholders as $key => $value) {
|
||||
$this->translator->addGlobalParameter($key, $value);
|
||||
}
|
||||
|
||||
@@ -243,14 +243,6 @@ class StructuralDBElementRepository extends AttachmentContainingDBElementReposit
|
||||
return $result[0];
|
||||
}
|
||||
|
||||
//If the name contains category delimiters like ->, try to find the element by its full path
|
||||
if (str_contains($name, '->')) {
|
||||
$tmp = $this->getEntityByPath($name, '->');
|
||||
if (count($tmp) > 0) {
|
||||
return $tmp[count($tmp) - 1];
|
||||
}
|
||||
}
|
||||
|
||||
//If we find nothing, return null
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ class EntityImporter
|
||||
}
|
||||
|
||||
//Only return objects once
|
||||
return array_values(array_unique($valid_entities, SORT_REGULAR));
|
||||
return array_values(array_unique($valid_entities));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -152,7 +152,7 @@ class PKDatastructureImporter
|
||||
public function importPartCustomStates(array $data): int
|
||||
{
|
||||
if (!isset($data['partcustomstate'])) {
|
||||
return 0; //Not all PartKeepr installations have custom states
|
||||
throw new \RuntimeException('$data must contain a "partcustomstate" key!');
|
||||
}
|
||||
|
||||
$partCustomStateData = $data['partcustomstate'];
|
||||
|
||||
@@ -150,11 +150,6 @@ trait PKImportHelperTrait
|
||||
|
||||
$target->addAttachment($attachment);
|
||||
$this->em->persist($attachment);
|
||||
|
||||
//If the attachment is an image, and the target has no master picture yet, set it
|
||||
if ($attachment->isPicture() && $target->getMasterPictureAttachment() === null) {
|
||||
$target->setMasterPictureAttachment($attachment);
|
||||
}
|
||||
}
|
||||
|
||||
$this->em->flush();
|
||||
|
||||
@@ -91,10 +91,7 @@ class PKPartImporter
|
||||
$this->setAssociationField($entity, 'partUnit', MeasurementUnit::class, $part['partUnit_id']);
|
||||
}
|
||||
|
||||
if (isset($part['partCustomState_id'])) {
|
||||
$this->setAssociationField($entity, 'partCustomState', MeasurementUnit::class,
|
||||
$part['partCustomState_id']);
|
||||
}
|
||||
$this->setAssociationField($entity, 'partCustomState', MeasurementUnit::class, $part['partCustomState_id']);
|
||||
|
||||
//Create a part lot to store the stock level and location
|
||||
$lot = new PartLot();
|
||||
|
||||
@@ -37,7 +37,7 @@ class RegisterSynonymsAsTranslationParametersTest extends KernelTestCase
|
||||
|
||||
public function testGetSynonymPlaceholders(): void
|
||||
{
|
||||
$placeholders = $this->listener->getSynonymPlaceholders('en');
|
||||
$placeholders = $this->listener->getSynonymPlaceholders();
|
||||
|
||||
$this->assertIsArray($placeholders);
|
||||
// Curly braces for lowercase versions
|
||||
|
||||
@@ -13495,14 +13495,5 @@ Vezměte prosím na vědomí, že se nemůžete vydávat za uživatele se zakáz
|
||||
<target>Uživatelé</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="MxKRRx_" name="datatable.datatable.lengthMenu">
|
||||
<notes>
|
||||
<note priority="1">Do not remove! Used for datatables rendering.</note>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>datatable.datatable.lengthMenu</source>
|
||||
<target>_MENU_</target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
</xliff>
|
||||
@@ -12164,14 +12164,5 @@ Bemærk venligst, at du ikke kan kopiere fra deaktiveret bruger. Hvis du prøver
|
||||
<target>Brugere</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="MxKRRx_" name="datatable.datatable.lengthMenu">
|
||||
<notes>
|
||||
<note priority="1">Do not remove! Used for datatables rendering.</note>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>datatable.datatable.lengthMenu</source>
|
||||
<target>_MENU_</target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
</xliff>
|
||||
@@ -19,7 +19,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>attachment_type.edit</source>
|
||||
<target>Bearbeite [Attachment_type]</target>
|
||||
<target>Bearbeite Dateityp</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="wyou6GD" name="attachment_type.new">
|
||||
@@ -29,7 +29,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>attachment_type.new</source>
|
||||
<target>Neuer [Attachment_type]</target>
|
||||
<target>Neuer Dateityp</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="JHaxw0a" name="category.labelp">
|
||||
@@ -84,7 +84,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>category.edit</source>
|
||||
<target>Bearbeite [Category]</target>
|
||||
<target>Bearbeite Kategorie</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="6.rAXsX" name="category.new">
|
||||
@@ -94,7 +94,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>category.new</source>
|
||||
<target>Neue [Category]</target>
|
||||
<target>Neue Kategorie</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="z1GMBc_" name="currency.caption">
|
||||
@@ -134,7 +134,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>currency.edit</source>
|
||||
<target>Bearbeite [Currency]</target>
|
||||
<target>Bearbeite Währung</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="aTtTsUc" name="currency.new">
|
||||
@@ -144,7 +144,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>currency.new</source>
|
||||
<target>Neue [Currency]</target>
|
||||
<target>Neue Währung</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="pe43jlV" name="project.edit">
|
||||
@@ -154,7 +154,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>project.edit</source>
|
||||
<target>Bearbeite [Project]</target>
|
||||
<target>Bearbeite Projekt</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="sYgrSg9" name="project.new">
|
||||
@@ -164,7 +164,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>project.new</source>
|
||||
<target>Neues [Project]</target>
|
||||
<target>Neues Projekt</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="eLrezdb" name="search.placeholder">
|
||||
@@ -394,7 +394,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>footprint.edit</source>
|
||||
<target>Bearbeite [Footprint]</target>
|
||||
<target>Bearbeite Footprint</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="9RgpRoK" name="footprint.new">
|
||||
@@ -404,7 +404,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>footprint.new</source>
|
||||
<target>Neuer [Footprint]</target>
|
||||
<target>Neuer Footprint</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="tvm4F9e" name="group.edit.caption">
|
||||
@@ -436,7 +436,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>group.edit</source>
|
||||
<target>Bearbeite [Group]</target>
|
||||
<target>Bearbeite Gruppe</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="_RN3Wph" name="group.new">
|
||||
@@ -446,7 +446,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>group.new</source>
|
||||
<target>Neue [Group]</target>
|
||||
<target>Neue Gruppe</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="jXqdnm_" name="label_profile.caption">
|
||||
@@ -483,7 +483,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>label_profile.edit</source>
|
||||
<target>Bearbeite [Label_profile]</target>
|
||||
<target>Bearbeite Labelprofil</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="WkNFJjx" name="label_profile.new">
|
||||
@@ -493,7 +493,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>label_profile.new</source>
|
||||
<target>Neues [Label_profile]</target>
|
||||
<target>Neues Labelprofil</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="r3pQ31P" name="manufacturer.caption">
|
||||
@@ -514,7 +514,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>manufacturer.edit</source>
|
||||
<target>Bearbeite [Manufacturer]</target>
|
||||
<target>Bearbeite Hersteller</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="dRX_cvD" name="manufacturer.new">
|
||||
@@ -524,7 +524,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>manufacturer.new</source>
|
||||
<target>Neuer [Manufacturer]</target>
|
||||
<target>Neuer Hersteller</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="3ra2AyY" name="measurement_unit.caption">
|
||||
@@ -565,7 +565,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>storelocation.edit</source>
|
||||
<target>Bearbeite [Storage_location]</target>
|
||||
<target>Bearbeite Lagerort</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="am0iTCO" name="storelocation.new">
|
||||
@@ -575,7 +575,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>storelocation.new</source>
|
||||
<target>Neuer [Storage_location]</target>
|
||||
<target>Neuer Lagerort</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="ozZU_B5" name="supplier.edit">
|
||||
@@ -585,7 +585,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>supplier.edit</source>
|
||||
<target>Bearbeite [Supplier]</target>
|
||||
<target>Bearbeite Lieferant</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="b8jlkMd" name="supplier.new">
|
||||
@@ -595,7 +595,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>supplier.new</source>
|
||||
<target>Neuer [Supplier]</target>
|
||||
<target>Neuer Lieferant</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="vX.dhjK" name="user.edit.caption">
|
||||
@@ -737,7 +737,7 @@ Der Benutzer wird alle Zwei-Faktor-Authentifizierungmethoden neu einrichten müs
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>user.edit</source>
|
||||
<target>Bearbeite [User]</target>
|
||||
<target>Bearbeite Benutzer</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="esEoaa_" name="user.new">
|
||||
@@ -747,7 +747,7 @@ Der Benutzer wird alle Zwei-Faktor-Authentifizierungmethoden neu einrichten müs
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>user.new</source>
|
||||
<target>Neuer [User]</target>
|
||||
<target>Neuer Benutzer</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="KqHffuc" name="attachment.delete">
|
||||
@@ -1781,7 +1781,7 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>part.edit.title</source>
|
||||
<target>Bearbeite [Part] %name%</target>
|
||||
<target>Bearbeite Bauteil %name%</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="yFxHuAp" name="part.edit.card_title">
|
||||
@@ -1942,7 +1942,7 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>part.new.card_title</source>
|
||||
<target>Neues [Part] erstellen</target>
|
||||
<target>Neues Bauteil erstellen</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="5TCcXwk" name="part_lot.delete">
|
||||
@@ -3124,7 +3124,7 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>statistics.distinct_parts_count</source>
|
||||
<target>Anzahl verschiedener [[Part]]</target>
|
||||
<target>Anzahl verschiedener Bauteile</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="sIGqnJ0" name="statistics.parts_instock_sum">
|
||||
@@ -3135,7 +3135,7 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>statistics.parts_instock_sum</source>
|
||||
<target>Summe aller vorhandenen Bestände an [[Part]]</target>
|
||||
<target>Summe aller vorhanden Bauteilebestände</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="uHmvfnI" name="statistics.parts_with_price">
|
||||
@@ -3146,7 +3146,7 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>statistics.parts_with_price</source>
|
||||
<target>[[Part]] mit Preisinformationen</target>
|
||||
<target>Bauteile mit Preisinformationen</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="7x89_xL" name="statistics.categories_count">
|
||||
@@ -3157,7 +3157,7 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>statistics.categories_count</source>
|
||||
<target>Anzahl [[Category]]</target>
|
||||
<target>Anzahl Kategorien</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="s0nLRjN" name="statistics.footprints_count">
|
||||
@@ -3168,7 +3168,7 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>statistics.footprints_count</source>
|
||||
<target>Anzahl [[Footprint]]</target>
|
||||
<target>Anzahl Footprints</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="f0gHZzl" name="statistics.manufacturers_count">
|
||||
@@ -3179,7 +3179,7 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>statistics.manufacturers_count</source>
|
||||
<target>Anzahl [[Manufacturer]]</target>
|
||||
<target>Anzahl Hersteller</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="_4rvCd3" name="statistics.storelocations_count">
|
||||
@@ -3190,7 +3190,7 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>statistics.storelocations_count</source>
|
||||
<target>Anzahl [[Storage_location]]</target>
|
||||
<target>Anzahl Lagerorte</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="tzzUvrm" name="statistics.suppliers_count">
|
||||
@@ -3201,7 +3201,7 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>statistics.suppliers_count</source>
|
||||
<target>Anzahl [[Supplier]]</target>
|
||||
<target>Anzahl Lieferanten</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="QEk.sHE" name="statistics.currencies_count">
|
||||
@@ -3212,7 +3212,7 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>statistics.currencies_count</source>
|
||||
<target>Anzahl [[Currency]]</target>
|
||||
<target>Anzahl Währungen</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="MTCnGlN" name="statistics.measurement_units_count">
|
||||
@@ -3223,7 +3223,7 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>statistics.measurement_units_count</source>
|
||||
<target>Anzahl [[Measurement_unit]]</target>
|
||||
<target>Anzahl Maßeinheiten</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="7sRXll2" name="statistics.devices_count">
|
||||
@@ -3234,7 +3234,7 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>statistics.devices_count</source>
|
||||
<target>Anzahl [[Project]]</target>
|
||||
<target>Anzahl Baugruppen</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="2u7zTMF" name="statistics.attachment_types_count">
|
||||
@@ -3245,7 +3245,7 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>statistics.attachment_types_count</source>
|
||||
<target>Anzahl [[Attachment_type]]</target>
|
||||
<target>Anzahl Anhangstypen</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="C0XsLQc" name="statistics.all_attachments_count">
|
||||
@@ -6138,7 +6138,7 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.edit.attachment_types</source>
|
||||
<target>[[Attachment_type]]</target>
|
||||
<target>Dateitypen</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="u8jQbAc" name="tree.tools.edit.categories">
|
||||
@@ -6149,7 +6149,7 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.edit.categories</source>
|
||||
<target>[[Category]]</target>
|
||||
<target>Kategorien</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="3n2K_az" name="tree.tools.edit.projects">
|
||||
@@ -6160,7 +6160,7 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.edit.projects</source>
|
||||
<target>[[Project]]</target>
|
||||
<target>Projekte</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="raK7qaK" name="tree.tools.edit.suppliers">
|
||||
@@ -6171,7 +6171,7 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.edit.suppliers</source>
|
||||
<target>[[Supplier]]</target>
|
||||
<target>Lieferanten</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="1IJ48Y0" name="tree.tools.edit.manufacturer">
|
||||
@@ -6182,7 +6182,7 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.edit.manufacturer</source>
|
||||
<target>[[Manufacturer]]</target>
|
||||
<target>Hersteller</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="GNbWH_O" name="tree.tools.edit.storelocation">
|
||||
@@ -6192,7 +6192,7 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.edit.storelocation</source>
|
||||
<target>[[Storage_location]]</target>
|
||||
<target>Lagerorte</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="7ZOhkd." name="tree.tools.edit.footprint">
|
||||
@@ -6202,7 +6202,7 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.edit.footprint</source>
|
||||
<target>[[Footprint]]</target>
|
||||
<target>Footprints</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="U1zYjzD" name="tree.tools.edit.currency">
|
||||
@@ -6212,7 +6212,7 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.edit.currency</source>
|
||||
<target>[[Currency]]</target>
|
||||
<target>Währungen</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="NnzEujm" name="tree.tools.edit.measurement_unit">
|
||||
@@ -6222,13 +6222,13 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.edit.measurement_unit</source>
|
||||
<target>[[Measurement_unit]]</target>
|
||||
<target>Maßeinheiten</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="5adacKb" name="tree.tools.edit.part_custom_state">
|
||||
<segment state="translated">
|
||||
<source>tree.tools.edit.part_custom_state</source>
|
||||
<target>[[Part_custom_state]]</target>
|
||||
<target>Benutzerdefinierter Bauteilstatus</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id=".Ux4R3T" name="tree.tools.edit.label_profile">
|
||||
@@ -6237,7 +6237,7 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.edit.label_profile</source>
|
||||
<target>[[Label_profile]]</target>
|
||||
<target>Labelprofil</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="eyvi0Zt" name="tree.tools.edit.part">
|
||||
@@ -6247,7 +6247,7 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.edit.part</source>
|
||||
<target>Neues [Part]</target>
|
||||
<target>Neues Bauteil</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="nIHj_yk" name="tree.tools.show.all_parts">
|
||||
@@ -6289,7 +6289,7 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.system.users</source>
|
||||
<target>[[User]]</target>
|
||||
<target>Benutzer</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="dTEQQ3T" name="tree.tools.system.groups">
|
||||
@@ -6299,7 +6299,7 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.system.groups</source>
|
||||
<target>[[Group]]</target>
|
||||
<target>Gruppen</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="NWWki1R" name="tree.tools.system.event_log">
|
||||
@@ -11013,25 +11013,25 @@ Element 1 -> Element 1.2</target>
|
||||
<unit id="r5F3f_G" name="measurement_unit.new">
|
||||
<segment state="translated">
|
||||
<source>measurement_unit.new</source>
|
||||
<target>Neue [Measurement_unit]</target>
|
||||
<target>Neue Maßeinheit</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="W.vDRLw" name="measurement_unit.edit">
|
||||
<segment state="translated">
|
||||
<source>measurement_unit.edit</source>
|
||||
<target>Bearbeite [Measurement_unit]</target>
|
||||
<target>Bearbeite Maßeinheit</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="ba52d.g" name="part_custom_state.new">
|
||||
<segment state="translated">
|
||||
<source>part_custom_state.new</source>
|
||||
<target>Neuer [Part_custom_state]</target>
|
||||
<target>Neuer benutzerdefinierter Bauteilstatus</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="c1.gb2d" name="part_custom_state.edit">
|
||||
<segment state="translated">
|
||||
<source>part_custom_state.edit</source>
|
||||
<target>Bearbeite [Part_custom_state]</target>
|
||||
<target>Bearbeite benutzerdefinierten Bauteilstatus</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="uW2WHHC" name="user.aboutMe.label">
|
||||
@@ -14429,14 +14429,5 @@ Bitte beachten Sie, dass dieses System derzeit experimentell ist und die hier de
|
||||
<target>Wenn aktiviert, wird eine Option zur Generierung einer IPN mit diesem globalen Präfix angeboten, das für Bauteile in allen Kategorien gilt.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="MxKRRx_" name="datatable.datatable.lengthMenu">
|
||||
<notes>
|
||||
<note priority="1">Do not remove! Used for datatables rendering.</note>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>datatable.datatable.lengthMenu</source>
|
||||
<target>_MENU_</target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
</xliff>
|
||||
@@ -1638,14 +1638,5 @@
|
||||
<target>Κατασκευαστές</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="MxKRRx_" name="datatable.datatable.lengthMenu">
|
||||
<notes>
|
||||
<note priority="1">Do not remove! Used for datatables rendering.</note>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>datatable.datatable.lengthMenu</source>
|
||||
<target>_MENU_</target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
</xliff>
|
||||
@@ -19,7 +19,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>attachment_type.edit</source>
|
||||
<target>Edit [attachment_type]</target>
|
||||
<target>Edit file type</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="wyou6GD" name="attachment_type.new">
|
||||
@@ -29,7 +29,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>attachment_type.new</source>
|
||||
<target>New [attachment_type]</target>
|
||||
<target>New file type</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="JHaxw0a" name="category.labelp">
|
||||
@@ -84,7 +84,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>category.edit</source>
|
||||
<target>Edit [category]</target>
|
||||
<target>Edit category</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="6.rAXsX" name="category.new">
|
||||
@@ -94,7 +94,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>category.new</source>
|
||||
<target>New [category]</target>
|
||||
<target>New category</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="KSFhj_3" name="currency.iso_code.caption">
|
||||
@@ -124,7 +124,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>currency.edit</source>
|
||||
<target>Edit [currency]</target>
|
||||
<target>Edit currency</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="aTtTsUc" name="currency.new">
|
||||
@@ -134,7 +134,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>currency.new</source>
|
||||
<target>New [currency]</target>
|
||||
<target>New currency</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="pe43jlV" name="project.edit">
|
||||
@@ -144,7 +144,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>project.edit</source>
|
||||
<target>Edit [project]</target>
|
||||
<target>Edit project</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="sYgrSg9" name="project.new">
|
||||
@@ -154,7 +154,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>project.new</source>
|
||||
<target>New [project]</target>
|
||||
<target>New project</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="eLrezdb" name="search.placeholder">
|
||||
@@ -384,7 +384,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>footprint.edit</source>
|
||||
<target>Edit [footprint]</target>
|
||||
<target>Edit footprint</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="9RgpRoK" name="footprint.new">
|
||||
@@ -394,7 +394,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>footprint.new</source>
|
||||
<target>New [footprint]</target>
|
||||
<target>New footprint</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="m27aWeR" name="user.edit.permissions">
|
||||
@@ -416,7 +416,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>group.edit</source>
|
||||
<target>Edit [group]</target>
|
||||
<target>Edit group</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="_RN3Wph" name="group.new">
|
||||
@@ -426,7 +426,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>group.new</source>
|
||||
<target>New [group]</target>
|
||||
<target>New group</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="GgwITAf" name="label_profile.advanced">
|
||||
@@ -454,7 +454,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>label_profile.edit</source>
|
||||
<target>Edit [label_profile]</target>
|
||||
<target>Edit label profile</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="WkNFJjx" name="label_profile.new">
|
||||
@@ -464,7 +464,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>label_profile.new</source>
|
||||
<target>New [label_profile]</target>
|
||||
<target>New label profile</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="AVQBOWW" name="manufacturer.edit">
|
||||
@@ -474,7 +474,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>manufacturer.edit</source>
|
||||
<target>Edit [manufacturer]</target>
|
||||
<target>Edit manufacturer</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="dRX_cvD" name="manufacturer.new">
|
||||
@@ -484,7 +484,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>manufacturer.new</source>
|
||||
<target>New [manufacturer]</target>
|
||||
<target>New manufacturer</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="crdkzlg" name="storelocation.labelp">
|
||||
@@ -509,7 +509,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>storelocation.edit</source>
|
||||
<target>Edit [storage_location]</target>
|
||||
<target>Edit storage location</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="am0iTCO" name="storelocation.new">
|
||||
@@ -519,7 +519,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>storelocation.new</source>
|
||||
<target>New [storage_location]</target>
|
||||
<target>New storage location</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="ozZU_B5" name="supplier.edit">
|
||||
@@ -529,7 +529,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>supplier.edit</source>
|
||||
<target>Edit [supplier]</target>
|
||||
<target>Edit supplier</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="b8jlkMd" name="supplier.new">
|
||||
@@ -539,7 +539,7 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>supplier.new</source>
|
||||
<target>New [supplier]</target>
|
||||
<target>New supplier</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="Ux8wVuF" name="user.edit.configuration">
|
||||
@@ -672,7 +672,7 @@ The user will have to set up all two-factor authentication methods again and pri
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>user.edit</source>
|
||||
<target>Edit [user]</target>
|
||||
<target>Edit user</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="esEoaa_" name="user.new">
|
||||
@@ -682,7 +682,7 @@ The user will have to set up all two-factor authentication methods again and pri
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>user.new</source>
|
||||
<target>New [user]</target>
|
||||
<target>New user</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="KqHffuc" name="attachment.delete">
|
||||
@@ -1002,7 +1002,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>entity.info.parts_count</source>
|
||||
<target>Number of [[part]] with this element</target>
|
||||
<target>Number of parts with this element</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="o0XIi58" name="specifications.property">
|
||||
@@ -1716,7 +1716,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>part.edit.title</source>
|
||||
<target>Edit [part]</target>
|
||||
<target>Edit part</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="yFxHuAp" name="part.edit.card_title">
|
||||
@@ -1727,7 +1727,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>part.edit.card_title</source>
|
||||
<target>Edit [part]</target>
|
||||
<target>Edit part</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="EwY218_" name="part.edit.tab.common">
|
||||
@@ -1877,7 +1877,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>part.new.card_title</source>
|
||||
<target>Create new [part]</target>
|
||||
<target>Create new part</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="5TCcXwk" name="part_lot.delete">
|
||||
@@ -1941,7 +1941,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>part.info.title</source>
|
||||
<target>Detail info for [part]</target>
|
||||
<target>Detail info for part</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="c9.fQPj" name="part.part_lots.label">
|
||||
@@ -2107,7 +2107,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="final">
|
||||
<source>user.creating_user</source>
|
||||
<target>User who created this [part]</target>
|
||||
<target>User who created this part</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="KEhkI6s" name="Unknown">
|
||||
@@ -2145,7 +2145,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="final">
|
||||
<source>user.last_editing_user</source>
|
||||
<target>User who edited this [part] last</target>
|
||||
<target>User who edited this part last</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="R8D9PGv" name="part.isFavorite">
|
||||
@@ -2509,7 +2509,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>part.edit.btn</source>
|
||||
<target>Edit [part]</target>
|
||||
<target>Edit part</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="TNzs45Y" name="part.clone.btn">
|
||||
@@ -2520,7 +2520,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>part.clone.btn</source>
|
||||
<target>Clone [part]</target>
|
||||
<target>Clone part</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="RG1RD20" name="part.create.btn">
|
||||
@@ -2531,7 +2531,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>part.create.btn</source>
|
||||
<target>Create new [part]</target>
|
||||
<target>Create new part</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="kxAbZAe" name="part.delete.confirm_title">
|
||||
@@ -2541,7 +2541,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>part.delete.confirm_title</source>
|
||||
<target>Do you really want to delete this [part]?</target>
|
||||
<target>Do you really want to delete this part?</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="owQRbkU" name="part.delete.message">
|
||||
@@ -2551,7 +2551,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>part.delete.message</source>
|
||||
<target>This [part] and any associated information (like attachments, price information, etc.) will be deleted. This can not be undone!</target>
|
||||
<target>This part and any associated information (like attachments, price information, etc.) will be deleted. This can not be undone!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="oH8.Zx6" name="part.delete">
|
||||
@@ -2561,7 +2561,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>part.delete</source>
|
||||
<target>Delete [part]</target>
|
||||
<target>Delete part</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P7nc8TK" name="parts_list.all.title">
|
||||
@@ -2571,7 +2571,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>parts_list.all.title</source>
|
||||
<target>All [[part]]</target>
|
||||
<target>All parts</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="aSDZrnb" name="parts_list.category.title">
|
||||
@@ -2581,7 +2581,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>parts_list.category.title</source>
|
||||
<target>[[Part]] with [category]</target>
|
||||
<target>Parts with category</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="qZfLMs5" name="parts_list.footprint.title">
|
||||
@@ -2591,7 +2591,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>parts_list.footprint.title</source>
|
||||
<target>[[Part]] with [footprint]</target>
|
||||
<target>Parts with footprint</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="wzH1LnU" name="parts_list.manufacturer.title">
|
||||
@@ -2601,7 +2601,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>parts_list.manufacturer.title</source>
|
||||
<target>[[Part]] with [manufacturer]</target>
|
||||
<target>Parts with manufacturer</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="z0tqm9_" name="parts_list.search.title">
|
||||
@@ -2611,7 +2611,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>parts_list.search.title</source>
|
||||
<target>Search [[part]]</target>
|
||||
<target>Search Parts</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id=".nsfK4V" name="parts_list.storelocation.title">
|
||||
@@ -2621,7 +2621,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>parts_list.storelocation.title</source>
|
||||
<target>[[Part]] with [[storage_location]]</target>
|
||||
<target>Parts with storage locations</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="dqIl9gT" name="parts_list.supplier.title">
|
||||
@@ -2631,7 +2631,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>parts_list.supplier.title</source>
|
||||
<target>[[Part]] with [supplier]</target>
|
||||
<target>Parts with supplier</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="BJygaSh" name="parts_list.tags.title">
|
||||
@@ -3059,7 +3059,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>statistics.distinct_parts_count</source>
|
||||
<target>Number of distinct [[part]]</target>
|
||||
<target>Number of distinct parts</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="sIGqnJ0" name="statistics.parts_instock_sum">
|
||||
@@ -3070,7 +3070,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>statistics.parts_instock_sum</source>
|
||||
<target>Sum of all [[part]] stocks</target>
|
||||
<target>Sum of all parts stocks</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="uHmvfnI" name="statistics.parts_with_price">
|
||||
@@ -3081,7 +3081,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>statistics.parts_with_price</source>
|
||||
<target>Number of [[part]] with price information</target>
|
||||
<target>Number of parts with price information</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="7x89_xL" name="statistics.categories_count">
|
||||
@@ -3092,7 +3092,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>statistics.categories_count</source>
|
||||
<target>Number of [[category]]</target>
|
||||
<target>Number of categories</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="s0nLRjN" name="statistics.footprints_count">
|
||||
@@ -3103,7 +3103,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>statistics.footprints_count</source>
|
||||
<target>Number of [[footprint]]</target>
|
||||
<target>Number of footprints</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="f0gHZzl" name="statistics.manufacturers_count">
|
||||
@@ -3114,7 +3114,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>statistics.manufacturers_count</source>
|
||||
<target>Number of [[manufacturer]]</target>
|
||||
<target>Number of manufacturers</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="_4rvCd3" name="statistics.storelocations_count">
|
||||
@@ -3125,7 +3125,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>statistics.storelocations_count</source>
|
||||
<target>Number of [[storage_location]]</target>
|
||||
<target>Number of storage locations</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="tzzUvrm" name="statistics.suppliers_count">
|
||||
@@ -3136,7 +3136,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>statistics.suppliers_count</source>
|
||||
<target>Number of [[supplier]]</target>
|
||||
<target>Number of suppliers</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="QEk.sHE" name="statistics.currencies_count">
|
||||
@@ -3147,7 +3147,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>statistics.currencies_count</source>
|
||||
<target>Number of [[currency]]</target>
|
||||
<target>Number of currencies</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="MTCnGlN" name="statistics.measurement_units_count">
|
||||
@@ -3158,7 +3158,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>statistics.measurement_units_count</source>
|
||||
<target>Number of [[measurement_unit]]</target>
|
||||
<target>Number of measurement units</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="7sRXll2" name="statistics.devices_count">
|
||||
@@ -3169,7 +3169,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>statistics.devices_count</source>
|
||||
<target>Number of [[project]]</target>
|
||||
<target>Number of projects</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="2u7zTMF" name="statistics.attachment_types_count">
|
||||
@@ -3180,7 +3180,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>statistics.attachment_types_count</source>
|
||||
<target>Number of [[attachment_type]]</target>
|
||||
<target>Number of attachment types</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="C0XsLQc" name="statistics.all_attachments_count">
|
||||
@@ -6073,7 +6073,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.edit.attachment_types</source>
|
||||
<target>[[Attachment_type]]</target>
|
||||
<target>Attachment types</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="u8jQbAc" name="tree.tools.edit.categories">
|
||||
@@ -6084,7 +6084,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.edit.categories</source>
|
||||
<target>[[Category]]</target>
|
||||
<target>Categories</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="3n2K_az" name="tree.tools.edit.projects">
|
||||
@@ -6095,7 +6095,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.edit.projects</source>
|
||||
<target>[[Project]]</target>
|
||||
<target>Projects</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="raK7qaK" name="tree.tools.edit.suppliers">
|
||||
@@ -6106,7 +6106,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.edit.suppliers</source>
|
||||
<target>[[Supplier]]</target>
|
||||
<target>Suppliers</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="1IJ48Y0" name="tree.tools.edit.manufacturer">
|
||||
@@ -6117,7 +6117,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.edit.manufacturer</source>
|
||||
<target>[[Manufacturer]]</target>
|
||||
<target>Manufacturers</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="GNbWH_O" name="tree.tools.edit.storelocation">
|
||||
@@ -6127,7 +6127,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.edit.storelocation</source>
|
||||
<target>[[Storage_location]]</target>
|
||||
<target>Storage locations</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="7ZOhkd." name="tree.tools.edit.footprint">
|
||||
@@ -6137,7 +6137,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.edit.footprint</source>
|
||||
<target>[[Footprint]]</target>
|
||||
<target>Footprints</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="U1zYjzD" name="tree.tools.edit.currency">
|
||||
@@ -6147,7 +6147,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.edit.currency</source>
|
||||
<target>[[Currency]]</target>
|
||||
<target>Currencies</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="NnzEujm" name="tree.tools.edit.measurement_unit">
|
||||
@@ -6157,13 +6157,13 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.edit.measurement_unit</source>
|
||||
<target>[[Measurement_unit]]</target>
|
||||
<target>Measurement Unit</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="oYLWbbv" name="tree.tools.edit.part_custom_state">
|
||||
<segment state="translated">
|
||||
<source>tree.tools.edit.part_custom_state</source>
|
||||
<target>[[Part_custom_state]]</target>
|
||||
<target>Custom part states</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id=".Ux4R3T" name="tree.tools.edit.label_profile">
|
||||
@@ -6172,7 +6172,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.edit.label_profile</source>
|
||||
<target>[[Label_profile]]</target>
|
||||
<target>Label profiles</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="eyvi0Zt" name="tree.tools.edit.part">
|
||||
@@ -6182,7 +6182,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.edit.part</source>
|
||||
<target>New [part]</target>
|
||||
<target>New part</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="nIHj_yk" name="tree.tools.show.all_parts">
|
||||
@@ -6193,7 +6193,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.show.all_parts</source>
|
||||
<target>Show all [[part]]</target>
|
||||
<target>Show all parts</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="JxVmFbM" name="tree.tools.show.all_attachments">
|
||||
@@ -6224,7 +6224,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.system.users</source>
|
||||
<target>[[User]]</target>
|
||||
<target>Users</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="dTEQQ3T" name="tree.tools.system.groups">
|
||||
@@ -6234,7 +6234,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.system.groups</source>
|
||||
<target>[[Group]]</target>
|
||||
<target>Groups</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="NWWki1R" name="tree.tools.system.event_log">
|
||||
@@ -6536,7 +6536,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>validator.part_lot.location_full</source>
|
||||
<target>The [storage_location] was marked as full, so you can not add a new [part] to it.</target>
|
||||
<target>The storage location was marked as full, so you can not add a new part to it.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="h6qELde" name="validator.part_lot.only_existing">
|
||||
@@ -6546,7 +6546,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>validator.part_lot.only_existing</source>
|
||||
<target>The [storage_location] was marked as "only existing", so you can not add new [part] to it.</target>
|
||||
<target>The storage location was marked as "only existing", so you can not add new part to it.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="2yWi8eP" name="validator.part_lot.single_part">
|
||||
@@ -6556,7 +6556,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>validator.part_lot.single_part</source>
|
||||
<target>The [storage_location] was marked as "single [part]", so you can not add a new [part] to it.</target>
|
||||
<target>The storage location was marked as "single part", so you can not add a new part to it.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="aBSsuxp" name="m_status.active.help">
|
||||
@@ -6737,7 +6737,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>entity.edit.not_selectable.help</source>
|
||||
<target>If this option is activated, this element can not be assigned to a [part] property. Useful if this element is just used for grouping.</target>
|
||||
<target>If this option is activated, this element can not be assigned to a part property. Useful if this element is just used for grouping.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="poCwov." name="bbcode.hint">
|
||||
@@ -6777,7 +6777,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>category.edit.disable_footprints</source>
|
||||
<target>Disable [[footprint]]</target>
|
||||
<target>Disable footprints</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="MLbac5k" name="category.edit.disable_footprints.help">
|
||||
@@ -6787,7 +6787,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>category.edit.disable_footprints.help</source>
|
||||
<target>If this option is activated, the [footprint] property is disabled for all [[part]] with this [category].</target>
|
||||
<target>If this option is activated, the footprint property is disabled for all parts with this category.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="JWHe4Hi" name="category.edit.disable_manufacturers">
|
||||
@@ -6797,7 +6797,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>category.edit.disable_manufacturers</source>
|
||||
<target>Disable [[manufacturer]]</target>
|
||||
<target>Disable manufacturers</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id=".ItLezq" name="category.edit.disable_manufacturers.help">
|
||||
@@ -6807,7 +6807,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>category.edit.disable_manufacturers.help</source>
|
||||
<target>If this option is activated, the [manufacturer] property is disabled for all [[part]] with this [category].</target>
|
||||
<target>If this option is activated, the manufacturer property is disabled for all parts with this category.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="g_gRbhu" name="category.edit.disable_autodatasheets">
|
||||
@@ -6827,7 +6827,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>category.edit.disable_autodatasheets.help</source>
|
||||
<target>If this option is activated, no automatic links to datasheets are created for [[part]] with this [category].</target>
|
||||
<target>If this option is activated, no automatic links to datasheets are created for parts with this category.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="QzSkLse" name="category.edit.disable_properties">
|
||||
@@ -6847,7 +6847,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>category.edit.disable_properties.help</source>
|
||||
<target>If this option is activated, the [part] properties are disabled for [[part]] with this [category].</target>
|
||||
<target>If this option is activated, the part properties are disabled for parts with this category.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="ILoRXgM" name="category.edit.partname_hint">
|
||||
@@ -7182,7 +7182,7 @@ Element 1 -> Element 1.2</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>storelocation.edit.is_full.help</source>
|
||||
<target>If this option is selected, it is neither possible to add new [[part]] to this storelocation or to increase the amount of existing [[part]].</target>
|
||||
<target>If this option is selected, it is neither possible to add new parts to this storelocation or to increase the amount of existing parts.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="V1su4ac" name="storelocation.limit_to_existing.label">
|
||||
@@ -7192,7 +7192,7 @@ Element 1 -> Element 1.2</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>storelocation.limit_to_existing.label</source>
|
||||
<target>Limit to existing [[part]]</target>
|
||||
<target>Limit to existing parts</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="6D.6huj" name="storelocation.limit_to_existing.help">
|
||||
@@ -7202,7 +7202,7 @@ Element 1 -> Element 1.2</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>storelocation.limit_to_existing.help</source>
|
||||
<target>If this option is activated, it is not possible to add new [[part]] to this storelocation, but the amount of existing [[part]] can be increased.</target>
|
||||
<target>If this option is activated, it is not possible to add new parts to this storelocation, but the amount of existing parts can be increased.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="tGVVeof" name="storelocation.only_single_part.label">
|
||||
@@ -7212,7 +7212,7 @@ Element 1 -> Element 1.2</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>storelocation.only_single_part.label</source>
|
||||
<target>Only single [part]</target>
|
||||
<target>Only single part</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="Hyh5pvY" name="storelocation.only_single_part.help">
|
||||
@@ -7222,7 +7222,7 @@ Element 1 -> Element 1.2</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>storelocation.only_single_part.help</source>
|
||||
<target>If this option is activated, only a single [part] (with every amount) can be assigned to this [storage_location]. Useful for small SMD boxes or feeders.</target>
|
||||
<target>If this option is activated, only a single part (with every amount) can be assigned to this storage location. Useful for small SMD boxes or feeders.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="sty2ele" name="storelocation.storage_type.label">
|
||||
@@ -7242,7 +7242,7 @@ Element 1 -> Element 1.2</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>storelocation.storage_type.help</source>
|
||||
<target>You can select a [measurement_unit] here, which a [part] must have to be able to be assigned to this [storage_location]</target>
|
||||
<target>You can select a measurement unit here, which a part must have to be able to be assigned to this storage location</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="ilC3c6u" name="supplier.edit.default_currency">
|
||||
@@ -7504,7 +7504,7 @@ Element 1 -> Element 1.2</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>perm.part.all_parts</source>
|
||||
<target>List all [[part]]</target>
|
||||
<target>List all parts</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="xGpxdp3" name="perm.part.no_price_parts">
|
||||
@@ -7514,7 +7514,7 @@ Element 1 -> Element 1.2</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>perm.part.no_price_parts</source>
|
||||
<target>List [[part]] without price info</target>
|
||||
<target>List parts without price info</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="gVBtadZ" name="perm.part.obsolete_parts">
|
||||
@@ -7524,7 +7524,7 @@ Element 1 -> Element 1.2</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>perm.part.obsolete_parts</source>
|
||||
<target>List obsolete [[part]]</target>
|
||||
<target>List obsolete parts</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="rh5syrd" name="perm.part.unknown_instock_parts">
|
||||
@@ -8636,7 +8636,7 @@ Element 1 -> Element 1.2</target>
|
||||
<unit id="DachcxN" name="part.table.edit.title">
|
||||
<segment state="translated">
|
||||
<source>part.table.edit.title</source>
|
||||
<target>Edit [part]</target>
|
||||
<target>Edit part</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="keLmdcq" name="part_list.action.scrollable_hint">
|
||||
@@ -9362,7 +9362,7 @@ Element 1 -> Element 1.2</target>
|
||||
<unit id="e97FPsh" name="entity.info.parts_count_recursive">
|
||||
<segment state="translated">
|
||||
<source>entity.info.parts_count_recursive</source>
|
||||
<target>Number of [[part]] with this element or its sub elements</target>
|
||||
<target>Number of parts with this element or its sub elements</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="st92iWF" name="tools.server_infos.title">
|
||||
@@ -9596,13 +9596,13 @@ Element 1 -> Element 1.2</target>
|
||||
<unit id="hpPkHYF" name="project.add_parts_to_project">
|
||||
<segment state="translated">
|
||||
<source>project.add_parts_to_project</source>
|
||||
<target>Add [[part]] to [project] BOM</target>
|
||||
<target>Add parts to project BOM</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="mauieYX" name="part.info.add_part_to_project">
|
||||
<segment state="translated">
|
||||
<source>part.info.add_part_to_project</source>
|
||||
<target>Add this [part] to a [project]</target>
|
||||
<target>Add this part to a project</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="5r.n1zf" name="project_bom_entry.label">
|
||||
@@ -9650,43 +9650,43 @@ Element 1 -> Element 1.2</target>
|
||||
<unit id="9GtmqC1" name="part.new_build_part.error.build_part_already_exists">
|
||||
<segment state="translated">
|
||||
<source>part.new_build_part.error.build_part_already_exists</source>
|
||||
<target>The [project] already has a build [part]!</target>
|
||||
<target>The project already has a build part!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="9soO_kf" name="project.edit.associated_build_part">
|
||||
<segment state="translated">
|
||||
<source>project.edit.associated_build_part</source>
|
||||
<target>Associated builds [part]</target>
|
||||
<target>Associated builds part</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="8Mu.T78" name="project.edit.associated_build_part.add">
|
||||
<segment state="translated">
|
||||
<source>project.edit.associated_build_part.add</source>
|
||||
<target>Add builds [part]</target>
|
||||
<target>Add builds part</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="sPdCBUl" name="project.edit.associated_build.hint">
|
||||
<segment state="translated">
|
||||
<source>project.edit.associated_build.hint</source>
|
||||
<target>This [part] represents the builds of this [project], which are stored somewhere.</target>
|
||||
<target>This part represents the builds of this project, which are stored somewhere.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="Cke2p4U" name="part.info.projectBuildPart.hint">
|
||||
<segment state="translated">
|
||||
<source>part.info.projectBuildPart.hint</source>
|
||||
<target>This [part] represents the builds of the following [project] and is associated with it</target>
|
||||
<target>This part represents the builds of the following project and is associated with it</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="yCR6rBb" name="part.is_build_part">
|
||||
<segment state="translated">
|
||||
<source>part.is_build_part</source>
|
||||
<target>Is [project] builds [part]</target>
|
||||
<target>Is project builds part</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="dV.E0zI" name="project.info.title">
|
||||
<segment state="translated">
|
||||
<source>project.info.title</source>
|
||||
<target>[Project] info</target>
|
||||
<target>Project info</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="LYURecm" name="project.info.bom_entries_count">
|
||||
@@ -9884,13 +9884,13 @@ Element 1 -> Element 1.2</target>
|
||||
<unit id="TRh.K81" name="part_list.action.projects.generate_label">
|
||||
<segment state="translated">
|
||||
<source>part_list.action.projects.generate_label</source>
|
||||
<target>Generate labels (for [[part]])</target>
|
||||
<target>Generate labels (for parts)</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="CEr_90G" name="part_list.action.projects.generate_label_lot">
|
||||
<segment state="translated">
|
||||
<source>part_list.action.projects.generate_label_lot</source>
|
||||
<target>Generate labels (for [[part_lot]])</target>
|
||||
<target>Generate labels (for part lots)</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="QjZuQlv" name="part_list.action.generate_label.empty">
|
||||
@@ -9914,7 +9914,7 @@ Element 1 -> Element 1.2</target>
|
||||
<unit id=".I7zcoK" name="project.builds.following_bom_entries_miss_instock">
|
||||
<segment state="translated">
|
||||
<source>project.builds.following_bom_entries_miss_instock</source>
|
||||
<target>The following [[part]] have not enough stock to build this [project] at least once:</target>
|
||||
<target>The following parts have not enough stock to build this project at least once:</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="fGT0qfb" name="project.builds.stocked">
|
||||
@@ -9938,19 +9938,19 @@ Element 1 -> Element 1.2</target>
|
||||
<unit id="NdZ1t7a" name="project.builds.number_of_builds_possible">
|
||||
<segment state="translated">
|
||||
<source>project.builds.number_of_builds_possible</source>
|
||||
<target><![CDATA[You have enough stocked to build <b>%max_builds%</b> builds of this [project].]]></target>
|
||||
<target>You have enough stocked to build <b>%max_builds%</b> builds of this project.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="iuSpPbg" name="project.builds.check_project_status">
|
||||
<segment state="translated">
|
||||
<source>project.builds.check_project_status</source>
|
||||
<target><![CDATA[The current [project] status is <b>"%project_status%"</b>. You should check if you really want to build the [project] with this status!]]></target>
|
||||
<target>The current project status is <b>"%project_status%"</b>. You should check if you really want to build the project with this status!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="Y7vSSxi" name="project.builds.following_bom_entries_miss_instock_n">
|
||||
<segment state="translated">
|
||||
<source>project.builds.following_bom_entries_miss_instock_n</source>
|
||||
<target>You do not have enough [[part]] stocked to build this [project] %number_of_builds% times. The following [[part]] have missing instock:</target>
|
||||
<target>You do not have enough parts stocked to build this project %number_of_builds% times. The following parts have missing instock:</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="1BGl3Dv" name="project.build.flash.invalid_input">
|
||||
@@ -9974,7 +9974,7 @@ Element 1 -> Element 1.2</target>
|
||||
<unit id="zgLSR.X" name="project.build.help">
|
||||
<segment state="translated">
|
||||
<source>project.build.help</source>
|
||||
<target>Choose from which [[part_lot]] the stock to build this [project] should be taken (and in which amount). Check the checkbox for each BOM Entry, when you are finished withdrawing the [[part]], or use the top checkbox to check all boxes at once.</target>
|
||||
<target>Choose from which part lots the stock to build this project should be taken (and in which amount). Check the checkbox for each BOM Entry, when you are finished withdrawing the parts, or use the top checkbox to check all boxes at once.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="srYaI3Z" name="project.build.buildsPartLot.new_lot">
|
||||
@@ -10244,13 +10244,13 @@ Element 1 -> Element 1.2</target>
|
||||
<unit id="1swuUz4" name="log.element_edited.changed_fields.disable_footprints">
|
||||
<segment state="translated">
|
||||
<source>log.element_edited.changed_fields.disable_footprints</source>
|
||||
<target>Disable [[footprint]]</target>
|
||||
<target>Disable footprints</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="sZ3vQaH" name="log.element_edited.changed_fields.disable_manufacturers">
|
||||
<segment state="translated">
|
||||
<source>log.element_edited.changed_fields.disable_manufacturers</source>
|
||||
<target>Disable [[manufacturer]]</target>
|
||||
<target>Disable manufacturers</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="lLXp.Sn" name="log.element_edited.changed_fields.disable_autodatasheets">
|
||||
@@ -10862,25 +10862,25 @@ Element 1 -> Element 1.2</target>
|
||||
<unit id="r5F3f_G" name="measurement_unit.new">
|
||||
<segment state="translated">
|
||||
<source>measurement_unit.new</source>
|
||||
<target>New [measurement_unit]</target>
|
||||
<target>New Measurement Unit</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="W.vDRLw" name="measurement_unit.edit">
|
||||
<segment state="translated">
|
||||
<source>measurement_unit.edit</source>
|
||||
<target>Edit [measurement_unit]</target>
|
||||
<target>Edit Measurement Unit</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="Ae0GMtY" name="part_custom_state.new">
|
||||
<segment state="translated">
|
||||
<source>part_custom_state.new</source>
|
||||
<target>New [part_custom_state]</target>
|
||||
<target>New custom part state</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="5uZ23wR" name="part_custom_state.edit">
|
||||
<segment state="translated">
|
||||
<source>part_custom_state.edit</source>
|
||||
<target>Edit [part_custom_state]</target>
|
||||
<target>Edit custom part state</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="uW2WHHC" name="user.aboutMe.label">
|
||||
@@ -10898,7 +10898,7 @@ Element 1 -> Element 1.2</target>
|
||||
<unit id="lYlHhtl" name="storelocation.part_owner_must_match.label">
|
||||
<segment state="translated">
|
||||
<source>storelocation.part_owner_must_match.label</source>
|
||||
<target>[Part_lot] owner must match [storage_location] owner</target>
|
||||
<target>Part Lot owner must match storage location owner</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="eMHXpbV" name="part_lot.owner">
|
||||
@@ -10934,7 +10934,7 @@ Element 1 -> Element 1.2</target>
|
||||
<unit id="9zWwSvC" name="part.withdraw.access_denied">
|
||||
<segment state="translated">
|
||||
<source>part.withdraw.access_denied</source>
|
||||
<target>Not allowed to do the desired action. Please check your permissions and the owner of the [[part_lot]].</target>
|
||||
<target>Not allowed to do the desired action. Please check your permissions and the owner of the part lots.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="4ynARET" name="part.info.amount.less_than_desired">
|
||||
@@ -10952,7 +10952,7 @@ Element 1 -> Element 1.2</target>
|
||||
<unit id="krpFlYH" name="log.element_edited.changed_fields.part_owner_must_match">
|
||||
<segment state="translated">
|
||||
<source>log.element_edited.changed_fields.part_owner_must_match</source>
|
||||
<target>[Part] owner must match [storage_location] owner</target>
|
||||
<target>Part owner must match storage location owner</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="o5u.Nnz" name="part.filter.lessThanDesired">
|
||||
@@ -11524,7 +11524,7 @@ Please note, that you can not impersonate a disabled user. If you try you will g
|
||||
<unit id="bkPXoxM" name="project.build.dont_check_quantity.help">
|
||||
<segment state="translated">
|
||||
<source>project.build.dont_check_quantity.help</source>
|
||||
<target>If this option is selected, the given withdraw quantities are used as given, no matter if more or less [[part]] are actually required to build this [project].</target>
|
||||
<target>If this option is selected, the given withdraw quantities are used as given, no matter if more or less parts are actually required to build this project.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="XsE9cmC" name="part_list.action.invert_selection">
|
||||
@@ -14278,14 +14278,5 @@ Please note that this system is currently experimental, and the synonyms defined
|
||||
<target>If enabled, an option for to generate IPN with this global prefix, shared across parts in all categories.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="MxKRRx_" name="datatable.datatable.lengthMenu">
|
||||
<notes>
|
||||
<note priority="1">Do not remove! Used for datatables rendering.</note>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>datatable.datatable.lengthMenu</source>
|
||||
<target>_MENU_</target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
</xliff>
|
||||
@@ -12336,14 +12336,5 @@ Por favor ten en cuenta que no puedes personificar a un usuario deshabilitado. S
|
||||
<target>Usuarios</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="MxKRRx_" name="datatable.datatable.lengthMenu">
|
||||
<notes>
|
||||
<note priority="1">Do not remove! Used for datatables rendering.</note>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>datatable.datatable.lengthMenu</source>
|
||||
<target>_MENU_</target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
</xliff>
|
||||
@@ -9076,14 +9076,5 @@ exemple de ville</target>
|
||||
<target>Utilisateurs</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="MxKRRx_" name="datatable.datatable.lengthMenu">
|
||||
<notes>
|
||||
<note priority="1">Do not remove! Used for datatables rendering.</note>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>datatable.datatable.lengthMenu</source>
|
||||
<target>_MENU_</target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
</xliff>
|
||||
@@ -14049,14 +14049,5 @@
|
||||
<target>Tömeges importálási feladat alkatrészek</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="MxKRRx_" name="datatable.datatable.lengthMenu">
|
||||
<notes>
|
||||
<note priority="1">Do not remove! Used for datatables rendering.</note>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>datatable.datatable.lengthMenu</source>
|
||||
<target>_MENU_</target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
</xliff>
|
||||
@@ -12338,14 +12338,5 @@ Notare che non è possibile impersonare un utente disattivato. Quando si prova a
|
||||
<target>Utenti</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="MxKRRx_" name="datatable.datatable.lengthMenu">
|
||||
<notes>
|
||||
<note priority="1">Do not remove! Used for datatables rendering.</note>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>datatable.datatable.lengthMenu</source>
|
||||
<target>_MENU_</target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
</xliff>
|
||||
@@ -8813,14 +8813,5 @@ Exampletown</target>
|
||||
<target>ユーザー</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="MxKRRx_" name="datatable.datatable.lengthMenu">
|
||||
<notes>
|
||||
<note priority="1">Do not remove! Used for datatables rendering.</note>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>datatable.datatable.lengthMenu</source>
|
||||
<target>_MENU_</target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
</xliff>
|
||||
@@ -840,14 +840,5 @@
|
||||
<target>Aangepaste staten van onderdelen</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="MxKRRx_" name="datatable.datatable.lengthMenu">
|
||||
<notes>
|
||||
<note priority="1">Do not remove! Used for datatables rendering.</note>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>datatable.datatable.lengthMenu</source>
|
||||
<target>_MENU_</target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
</xliff>
|
||||
@@ -12191,14 +12191,5 @@ Należy pamiętać, że nie możesz udawać nieaktywnych użytkowników. Jeśli
|
||||
<target>Użytkownicy</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="MxKRRx_" name="datatable.datatable.lengthMenu">
|
||||
<notes>
|
||||
<note priority="1">Do not remove! Used for datatables rendering.</note>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>datatable.datatable.lengthMenu</source>
|
||||
<target>_MENU_</target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
</xliff>
|
||||
@@ -12291,14 +12291,5 @@
|
||||
<target>Пользователи</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="MxKRRx_" name="datatable.datatable.lengthMenu">
|
||||
<notes>
|
||||
<note priority="1">Do not remove! Used for datatables rendering.</note>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>datatable.datatable.lengthMenu</source>
|
||||
<target>_MENU_</target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
</xliff>
|
||||
@@ -12176,14 +12176,5 @@ Element 3</target>
|
||||
<target>用户</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="MxKRRx_" name="datatable.datatable.lengthMenu">
|
||||
<notes>
|
||||
<note priority="1">Do not remove! Used for datatables rendering.</note>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>datatable.datatable.lengthMenu</source>
|
||||
<target>_MENU_</target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
</xliff>
|
||||
Reference in New Issue
Block a user