Compare commits

..

No commits in common. "a82d515034e85e7577c7bef1bc715759881d34bb" and "5b86d6f652966ce2c88ed904718a9d743b6c24d7" have entirely different histories.

31 changed files with 1235 additions and 9162 deletions

View file

@ -1 +1 @@
2.10.0
2.9.1

68
composer.lock generated
View file

@ -13083,7 +13083,7 @@
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.36.0",
"version": "v1.34.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
@ -13142,7 +13142,7 @@
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.36.0"
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.34.0"
},
"funding": [
{
@ -13166,7 +13166,7 @@
},
{
"name": "symfony/polyfill-intl-grapheme",
"version": "v1.36.0",
"version": "v1.34.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
@ -13224,7 +13224,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.36.0"
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.34.0"
},
"funding": [
{
@ -13248,7 +13248,7 @@
},
{
"name": "symfony/polyfill-intl-icu",
"version": "v1.36.0",
"version": "v1.34.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-icu.git",
@ -13312,7 +13312,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.36.0"
"source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.34.0"
},
"funding": [
{
@ -13336,7 +13336,7 @@
},
{
"name": "symfony/polyfill-intl-idn",
"version": "v1.36.0",
"version": "v1.34.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-idn.git",
@ -13399,7 +13399,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.36.0"
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.34.0"
},
"funding": [
{
@ -13423,7 +13423,7 @@
},
{
"name": "symfony/polyfill-intl-normalizer",
"version": "v1.36.0",
"version": "v1.34.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
@ -13484,7 +13484,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.36.0"
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.34.0"
},
"funding": [
{
@ -13508,7 +13508,7 @@
},
{
"name": "symfony/polyfill-php83",
"version": "v1.36.0",
"version": "v1.34.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php83.git",
@ -13564,7 +13564,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php83/tree/v1.36.0"
"source": "https://github.com/symfony/polyfill-php83/tree/v1.34.0"
},
"funding": [
{
@ -13588,7 +13588,7 @@
},
{
"name": "symfony/polyfill-php84",
"version": "v1.36.0",
"version": "v1.34.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php84.git",
@ -13644,7 +13644,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php84/tree/v1.36.0"
"source": "https://github.com/symfony/polyfill-php84/tree/v1.34.0"
},
"funding": [
{
@ -13668,7 +13668,7 @@
},
{
"name": "symfony/polyfill-php85",
"version": "v1.36.0",
"version": "v1.34.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php85.git",
@ -13724,7 +13724,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php85/tree/v1.36.0"
"source": "https://github.com/symfony/polyfill-php85/tree/v1.34.0"
},
"funding": [
{
@ -13748,7 +13748,7 @@
},
{
"name": "symfony/polyfill-uuid",
"version": "v1.36.0",
"version": "v1.34.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-uuid.git",
@ -13807,7 +13807,7 @@
"uuid"
],
"support": {
"source": "https://github.com/symfony/polyfill-uuid/tree/v1.36.0"
"source": "https://github.com/symfony/polyfill-uuid/tree/v1.34.0"
},
"funding": [
{
@ -18469,11 +18469,11 @@
},
{
"name": "phpstan/phpstan",
"version": "2.1.48",
"version": "2.1.47",
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/231397213efb7c0a066ee024b5c3c87f2d3adfa0",
"reference": "231397213efb7c0a066ee024b5c3c87f2d3adfa0",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/79015445d8bd79e62b29140f12e5bfced1dcca65",
"reference": "79015445d8bd79e62b29140f12e5bfced1dcca65",
"shasum": ""
},
"require": {
@ -18518,7 +18518,7 @@
"type": "github"
}
],
"time": "2026-04-15T20:24:19+00:00"
"time": "2026-04-13T15:49:08+00:00"
},
{
"name": "phpstan/phpstan-doctrine",
@ -19244,12 +19244,12 @@
"source": {
"type": "git",
"url": "https://github.com/Roave/SecurityAdvisories.git",
"reference": "bb550b5adb0d4d74c4f6857c6b3b3638c022e90b"
"reference": "b0b156ed9d5d2eb313c33f92af3dbc886ba4688a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/bb550b5adb0d4d74c4f6857c6b3b3638c022e90b",
"reference": "bb550b5adb0d4d74c4f6857c6b3b3638c022e90b",
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/b0b156ed9d5d2eb313c33f92af3dbc886ba4688a",
"reference": "b0b156ed9d5d2eb313c33f92af3dbc886ba4688a",
"shasum": ""
},
"conflict": {
@ -19379,7 +19379,7 @@
"codingms/modules": "<4.3.11|>=5,<5.7.4|>=6,<6.4.2|>=7,<7.5.5",
"commerceteam/commerce": ">=0.9.6,<0.9.9",
"components/jquery": ">=1.0.3,<3.5",
"composer/composer": "<2.2.27|>=2.3,<2.9.6",
"composer/composer": "<1.10.27|>=2,<2.2.26|>=2.3,<2.9.3",
"concrete5/concrete5": "<9.4.8",
"concrete5/core": "<8.5.8|>=9,<9.1",
"contao-components/mediaelement": ">=2.14.2,<2.21.1",
@ -19396,7 +19396,7 @@
"cpsit/typo3-mailqueue": "<0.4.5|>=0.5,<0.5.2",
"craftcms/aws-s3": ">=2.0.2,<=2.2.4",
"craftcms/azure-blob": ">=2.0.0.0-beta1,<=2.1",
"craftcms/cms": "<=4.17.8|>=5,<5.9.15",
"craftcms/cms": "<=4.17.7|>=5,<=5.9.13",
"craftcms/commerce": ">=4,<4.11|>=5,<5.6",
"craftcms/composer": ">=4.0.0.0-RC1-dev,<=4.10|>=5.0.0.0-RC1-dev,<=5.5.1",
"craftcms/craft": ">=3.5,<=4.16.17|>=5.0.0.0-RC1-dev,<=5.8.21",
@ -19664,7 +19664,7 @@
"kelvinmo/simplexrd": "<3.1.1",
"kevinpapst/kimai2": "<1.16.7",
"khodakhah/nodcms": "<=3",
"kimai/kimai": "<2.53",
"kimai/kimai": "<=2.50",
"kitodo/presentation": "<3.2.3|>=3.3,<3.3.4",
"klaviyo/magento2-extension": ">=1,<3",
"knplabs/knp-snappy": "<=1.4.2",
@ -19805,8 +19805,8 @@
"october/backend": "<1.1.2",
"october/cms": "<1.0.469|==1.0.469|==1.0.471|==1.1.1",
"october/october": "<3.7.5",
"october/rain": "<=3.7.13|>=4,<=4.1.9",
"october/system": "<=3.7.13|>=4,<=4.1.9",
"october/rain": "<1.0.472|>=1.1,<1.1.2",
"october/system": "<=3.7.12|>=4,<=4.0.11",
"oliverklee/phpunit": "<3.5.15",
"omeka/omeka-s": "<4.0.3",
"onelogin/php-saml": "<2.21.1|>=3,<3.8.1|>=4,<4.3.1",
@ -19885,7 +19885,7 @@
"pixelfed/pixelfed": "<0.12.5",
"plotly/plotly.js": "<2.25.2",
"pocketmine/bedrock-protocol": "<8.0.2",
"pocketmine/pocketmine-mp": "<5.42.1",
"pocketmine/pocketmine-mp": "<5.41.1",
"pocketmine/raklib": ">=0.14,<0.14.6|>=0.15,<0.15.1",
"pressbooks/pressbooks": "<5.18",
"prestashop/autoupgrade": ">=4,<4.10.1",
@ -19939,7 +19939,6 @@
"rudloff/rtmpdump-bin": "<=2.3.1",
"s-cart/core": "<=9.0.5",
"s-cart/s-cart": "<6.9",
"s9y/serendipity": "<2.6",
"sabberworm/php-css-parser": ">=1,<1.0.1|>=2,<2.0.1|>=3,<3.0.1|>=4,<4.0.1|>=5,<5.0.9|>=5.1,<5.1.3|>=5.2,<5.2.1|>=6,<6.0.2|>=7,<7.0.4|>=8,<8.0.1|>=8.1,<8.1.1|>=8.2,<8.2.1|>=8.3,<8.3.1",
"sabre/dav": ">=1.6,<1.7.11|>=1.8,<1.8.9",
"saloonphp/saloon": "<4",
@ -20169,7 +20168,6 @@
"webcoast/deferred-image-processing": "<1.0.2",
"webklex/laravel-imap": "<5.3",
"webklex/php-imap": "<5.3",
"webonyx/graphql-php": "<=15.31.4",
"webpa/webpa": "<3.1.2",
"webreinvent/vaahcms": "<=2.3.1",
"wikibase/wikibase": "<=1.39.3",
@ -20189,7 +20187,7 @@
"wpcloud/wp-stateless": "<3.2",
"wpglobus/wpglobus": "<=1.9.6",
"wpmetabox/meta-box": "<5.11.2",
"wwbn/avideo": "<=29",
"wwbn/avideo": "<=26",
"xataface/xataface": "<3",
"xpressengine/xpressengine": "<3.0.15",
"yab/quarx": "<2.4.5",
@ -20289,7 +20287,7 @@
"type": "tidelift"
}
],
"time": "2026-04-15T20:21:07+00:00"
"time": "2026-04-13T18:30:45+00:00"
},
{
"name": "sebastian/cli-parser",

View file

@ -56,7 +56,6 @@ doctrine:
natsort: App\Doctrine\Functions\Natsort
array_position: App\Doctrine\Functions\ArrayPosition
ilike: App\Doctrine\Functions\ILike
si_value_sort: App\Doctrine\Functions\SiValueSort
when@test:
doctrine:

View file

@ -59,9 +59,6 @@ parameters:
- '#expects .*PartParameter, .*AbstractParameter given.#'
- '#Part::getParameters\(\) should return .*AbstractParameter#'
# Fix some weird issue with how covariance with collections is solved
- '#Method App\\Entity\\Base\\AbstractStructuralDBElement::getParameters\(\) should return Doctrine\\Common\\Collections\\Collection<int, App\\Entity\\Parameters\\AbstractParameter> but returns#'
# Ignore doctrine type mapping mismatch
- '#Property .* type mapping mismatch: property can contain .* but database expects .*#'
@ -73,6 +70,3 @@ parameters:
- message: '#Access to an undefined property Brick\\Schema\\Interfaces\\#'
path: src/Services/InfoProviderSystem/Providers/GenericWebProvider.php
-
identifier: nullCoalesce.property

View file

@ -1,4 +1,4 @@
# Generated on Mon Apr 13 05:19:27 UTC 2026
# Generated on Mon Mar 9 04:23:25 UTC 2026
# This file contains all footprints available in the offical KiCAD library
Audio_Module:Reverb_BTDR-1H
Audio_Module:Reverb_BTDR-1V

View file

@ -1,4 +1,4 @@
# Generated on Mon Apr 13 05:20:06 UTC 2026
# Generated on Mon Mar 9 04:24:12 UTC 2026
# This file contains all symbols available in the offical KiCAD library
4xxx:14528
4xxx:14529
@ -899,7 +899,6 @@ Amplifier_Buffer:BUF634AxD
Amplifier_Buffer:BUF634AxDDA
Amplifier_Buffer:BUF634AxDRB
Amplifier_Buffer:BUF634U
Amplifier_Buffer:BUF802
Amplifier_Buffer:EL2001CN
Amplifier_Buffer:LH0002H
Amplifier_Buffer:LM6321H
@ -1668,6 +1667,7 @@ Analog_ADC:CA3300
Analog_ADC:HX711
Analog_ADC:ICL7106CPL
Analog_ADC:ICL7107CPL
Analog_ADC:INA234AxYBJ
Analog_ADC:LTC1406CGN
Analog_ADC:LTC1406IGN
Analog_ADC:LTC1594CS
@ -2198,7 +2198,6 @@ Audio:WM8731SEDS
Audio:YM2149
Audio:YM2612
Audio:YM3438
Auxiliary_Items:Generic_Outline
Auxiliary_Items:Jumper_Shunt
Auxiliary_Items:MountingScrew
Battery_Management:ADP5063
@ -2255,11 +2254,6 @@ Battery_Management:BQ76200PW
Battery_Management:BQ76920PW
Battery_Management:BQ76930DBT
Battery_Management:BQ76940DBT
Battery_Management:BQ7695201PFBR
Battery_Management:BQ7695202PFBR
Battery_Management:BQ7695203PFBR
Battery_Management:BQ7695204PFBR
Battery_Management:BQ76952PFBR
Battery_Management:BQ78350DBT
Battery_Management:BQ78350DBT-R1
Battery_Management:CN3063
@ -2769,8 +2763,6 @@ Connector:DIN41612_02x32_AC
Connector:DIN41612_02x32_AE
Connector:DIN41612_02x32_ZB
Connector:DIN41612_03x32_C_Split
Connector:DP_Sink
Connector:DP_Source
Connector:DVI-D_Dual_Link
Connector:DVI-I_Dual_Link
Connector:ExpressCard
@ -2909,7 +2901,6 @@ Connector:TestPoint_Alt
Connector:TestPoint_Flag
Connector:TestPoint_Probe
Connector:TestPoint_Small
Connector:TestPoint_Square
Connector:UEXT_Host
Connector:UEXT_Slave
Connector:USB3_A
@ -7781,7 +7772,6 @@ FPGA_Lattice:ICE40HX1K-TQ144
FPGA_Lattice:ICE40HX4K-BG121
FPGA_Lattice:ICE40HX4K-TQ144
FPGA_Lattice:ICE40HX8K-BG121
FPGA_Lattice:ICE40LP384-SG32
FPGA_Lattice:ICE40UL1K-SWG16
FPGA_Lattice:ICE40UP5K-SG48ITR
FPGA_Lattice:ICE5LP1K-SG48
@ -15741,7 +15731,6 @@ Power_Management:RT9742AGJ5F
Power_Management:RT9742ANGJ5F
Power_Management:RT9742BGJ5F
Power_Management:RT9742BNGJ5F
Power_Management:RT9742SNGV
Power_Management:SN6505ADBV
Power_Management:SN6505BDBV
Power_Management:SN6507DGQ
@ -18703,7 +18692,6 @@ Regulator_Linear:TPS7A0530PDBZ
Regulator_Linear:TPS7A0531PDBV
Regulator_Linear:TPS7A0533PDBV
Regulator_Linear:TPS7A0533PDBZ
Regulator_Linear:TPS7A20xxxDBV
Regulator_Linear:TPS7A20xxxDQN
Regulator_Linear:TPS7A3301RGW
Regulator_Linear:TPS7A39
@ -20313,6 +20301,7 @@ Sensor:BME280
Sensor:BME680
Sensor:CHT11
Sensor:DHT11
Sensor:INA260
Sensor:LTC2990
Sensor:MAX30102
Sensor:Nuclear-Radiation_Detector
@ -20599,12 +20588,9 @@ Sensor_Energy:INA219BxD
Sensor_Energy:INA219BxDCN
Sensor_Energy:INA226
Sensor_Energy:INA228
Sensor_Energy:INA229
Sensor_Energy:INA233
Sensor_Energy:INA234AxYBJ
Sensor_Energy:INA237
Sensor_Energy:INA238
Sensor_Energy:INA260
Sensor_Energy:LTC4151xMS
Sensor_Energy:MCP39F521
Sensor_Energy:PAC1931x-xJ6CX
@ -20886,7 +20872,6 @@ Sensor_Proximity:BPR-105
Sensor_Proximity:BPR-105F
Sensor_Proximity:BPR-205
Sensor_Proximity:CNY70
Sensor_Proximity:FDC1004DGS
Sensor_Proximity:GP2S700HCP
Sensor_Proximity:ITR1201SR10AR
Sensor_Proximity:ITR8307
@ -21806,7 +21791,6 @@ Transistor_BJT:Q_NPN_Darlington_ECBC
Transistor_BJT:Q_NPN_EBC
Transistor_BJT:Q_NPN_ECB
Transistor_BJT:Q_NPN_ECBC
Transistor_BJT:Q_PNP_ACAB
Transistor_BJT:Q_PNP_BCE
Transistor_BJT:Q_PNP_BCEC
Transistor_BJT:Q_PNP_BEC

View file

@ -69,13 +69,10 @@ class ProjectController extends AbstractController
return $table->getResponse();
}
$number_of_builds = max(1, $request->query->getInt('n', 1));
return $this->render('projects/info/info.html.twig', [
'buildHelper' => $buildHelper,
'datatable' => $table,
'project' => $project,
'number_of_builds' => $number_of_builds,
]);
}

View file

@ -38,7 +38,6 @@ use App\DataTables\Filters\PartFilter;
use App\DataTables\Filters\PartSearchFilter;
use App\DataTables\Helpers\ColumnSortHelper;
use App\DataTables\Helpers\PartDataTableHelper;
use App\Doctrine\Functions\SiValueSort;
use App\Doctrine\Helpers\FieldHelper;
use App\Entity\Parts\ManufacturingStatus;
use App\Entity\Parts\Part;
@ -119,18 +118,6 @@ final class PartsDataTable implements DataTableTypeInterface
'render' => fn($value, Part $context) => $this->partDataTableHelper->renderName($context),
'orderField' => 'NATSORT(part.name)'
])
->add('si_value', TextColumn::class, [
'label' => $this->translator->trans('part.table.si_value'),
'render' => function ($value, Part $context): string {
$siValue = SiValueSort::sqliteSiValue($context->getName());
if ($siValue !== null) {
//Output it as scientific number with a big E
return htmlspecialchars(sprintf('%G', $siValue));
}
return '';
},
'orderField' => 'SI_VALUE_SORT(part.name)',
])
->add('id', TextColumn::class, [
'label' => $this->translator->trans('part.table.id'),
])
@ -497,19 +484,6 @@ final class PartsDataTable implements DataTableTypeInterface
//$builder->addGroupBy('_bulkImportJob');
}
//When sorting by SI value, add NATSORT as a secondary sort so that parts without
//an SI-prefixed value fall back to natural string ordering seamlessly.
$orderByParts = $builder->getDQLPart('orderBy');
foreach ($orderByParts as $orderBy) {
foreach ($orderBy->getParts() as $part) {
if (str_contains($part, 'SI_VALUE_SORT')) {
$direction = str_contains($part, 'DESC') ? 'DESC' : 'ASC';
$builder->addOrderBy('NATSORT(part.name)', $direction);
break 2;
}
}
}
return $builder;
}

View file

@ -29,15 +29,12 @@ use App\DataTables\Column\LocaleDateTimeColumn;
use App\DataTables\Column\MarkdownColumn;
use App\DataTables\Helpers\PartDataTableHelper;
use App\Doctrine\Helpers\FieldHelper;
use App\Entity\Parts\ManufacturingStatus;
use App\Entity\Parts\Part;
use App\Entity\Parts\ManufacturingStatus;
use App\Entity\ProjectSystem\ProjectBOMEntry;
use App\Services\ElementTypeNameGenerator;
use App\Services\EntityURLGenerator;
use App\Services\Formatters\AmountFormatter;
use App\Services\Formatters\MoneyFormatter;
use App\Services\ProjectSystem\ProjectBuildHelper;
use Brick\Math\RoundingMode;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
@ -53,9 +50,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
protected EntityURLGenerator $entityURLGenerator,
protected TranslatorInterface $translator,
protected AmountFormatter $amountFormatter,
protected PartDataTableHelper $partDataTableHelper,
protected ProjectBuildHelper $projectBuildHelper,
protected MoneyFormatter $moneyFormatter,
protected PartDataTableHelper $partDataTableHelper
) {
}
@ -207,27 +202,6 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
return '';
}
])
->add('price', TextColumn::class, [
'label' => 'project.bom.price',
'visible' => false,
'render' => function ($value, ProjectBOMEntry $context) {
$price = $this->projectBuildHelper->getEntryUnitPrice($context);
return $this->moneyFormatter->format($price->toScale(2, RoundingMode::UP)->toFloat(), null, 2, true);
},
])
->add('ext_price', TextColumn::class, [
'label' => 'project.bom.ext_price',
'visible' => false,
'render' => function ($value, ProjectBOMEntry $context) {
$price = $this->projectBuildHelper->getEntryUnitPrice($context);
return $this->moneyFormatter->format(
$price->multipliedBy($context->getQuantity())->toScale(2, RoundingMode::UP)->toFloat(),
null,
2,
true
);
},
])
->add('addedDate', LocaleDateTimeColumn::class, [
'label' => $this->translator->trans('part.table.addedDate'),

View file

@ -1,196 +0,0 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2024 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\Doctrine\Functions;
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\DBAL\Platforms\SQLitePlatform;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
/**
* Custom DQL function that extracts the first numeric value with an optional SI prefix
* from a string and returns the scaled numeric value for sorting.
*
* Usage: SI_VALUE_SORT(part.name)
*
* This enables sorting parts by their physical value. For example, capacitors
* named "100nF", "1uF", "10pF" will be sorted by actual value: 10pF < 100nF < 1uF.
*
* Supported SI prefixes: p (pico, 1e-12), n (nano, 1e-9), u/µ (micro, 1e-6),
* m (milli, 1e-3), k/K (kilo, 1e3), M (mega, 1e6), G (giga, 1e9), T (tera, 1e12).
*
* Only matches numbers at the very beginning of the string (ignoring leading whitespace).
* Names like "Crystal 20MHz" will NOT match since the number is not at the start.
* Names without a recognizable numeric+prefix pattern return NULL and sort last.
*/
class SiValueSort extends FunctionNode
{
private ?Node $field = null;
/**
* SI prefix multipliers. Used by the SQLite PHP callback.
*/
private const SI_MULTIPLIERS = [
'p' => 1e-12,
'n' => 1e-9,
'u' => 1e-6,
'µ' => 1e-6,
'm' => 1e-3,
'k' => 1e3,
'K' => 1e3,
'M' => 1e6,
'G' => 1e9,
'T' => 1e12,
];
public function parse(Parser $parser): void
{
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->field = $parser->ArithmeticExpression();
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
public function getSql(SqlWalker $sqlWalker): string
{
assert($this->field !== null, 'Field is not set');
$platform = $sqlWalker->getConnection()->getDatabasePlatform();
$rawField = $this->field->dispatch($sqlWalker);
// Normalize comma decimal separator to dot for SQL platforms (European locale support)
$fieldSql = "REPLACE({$rawField}, ',', '.')";
if ($platform instanceof PostgreSQLPlatform) {
return $this->getPostgreSQLSql($fieldSql);
}
if ($platform instanceof AbstractMySQLPlatform) {
return $this->getMySQLSql($fieldSql);
}
// SQLite: comma normalization is handled in the PHP callback
$fieldSql = $rawField;
if ($platform instanceof SQLitePlatform) {
return "SI_VALUE({$fieldSql})";
}
// Fallback: return NULL (no SI sorting available)
return 'NULL';
}
/**
* PostgreSQL implementation using substring() with POSIX regex.
*/
private function getPostgreSQLSql(string $field): string
{
// Extract the numeric part using POSIX regex, anchored at start (with optional leading whitespace)
$numericPart = "CAST(substring({$field} FROM '^\\s*(\\d+\\.?\\d*)\\s*[pnuµmkKMGT]?') AS DOUBLE PRECISION)";
// Extract the SI prefix character
$prefixPart = "substring({$field} FROM '^\\s*\\d+\\.?\\d*\\s*([pnuµmkKMGT])')";
return $this->buildCaseExpression($numericPart, $prefixPart);
}
/**
* MySQL/MariaDB implementation using REGEXP_SUBSTR.
*/
private function getMySQLSql(string $field): string
{
// Extract the numeric part, anchored at start (with optional leading whitespace)
$numericPart = "CAST(REGEXP_SUBSTR({$field}, '^[[:space:]]*[0-9]+\\.?[0-9]*') AS DECIMAL(30,15))";
// Extract the prefix: get the full number+prefix match anchored at start, then take the last char
$fullMatch = "REGEXP_SUBSTR({$field}, '^[[:space:]]*[0-9]+\\.?[0-9]*[[:space:]]*[pnuµmkKMGT]')";
$prefixPart = "RIGHT({$fullMatch}, 1)";
return $this->buildCaseExpression($numericPart, $prefixPart);
}
/**
* Build a CASE expression that maps an SI prefix character to a multiplier
* and multiplies it with the numeric value.
*
* @param string $numericExpr SQL expression that evaluates to the numeric part
* @param string $prefixExpr SQL expression that evaluates to the SI prefix character
* @return string SQL CASE expression
*/
private function buildCaseExpression(string $numericExpr, string $prefixExpr): string
{
return "(CASE" .
" WHEN {$numericExpr} IS NULL THEN NULL" .
" WHEN {$prefixExpr} = 'p' THEN {$numericExpr} * 1e-12" .
" WHEN {$prefixExpr} = 'n' THEN {$numericExpr} * 1e-9" .
" WHEN {$prefixExpr} = 'u' THEN {$numericExpr} * 1e-6" .
" WHEN {$prefixExpr} = 'µ' THEN {$numericExpr} * 1e-6" .
" WHEN {$prefixExpr} = 'm' THEN {$numericExpr} * 1e-3" .
" WHEN {$prefixExpr} = 'k' THEN {$numericExpr} * 1e3" .
" WHEN {$prefixExpr} = 'K' THEN {$numericExpr} * 1e3" .
" WHEN {$prefixExpr} = 'M' THEN {$numericExpr} * 1e6" .
" WHEN {$prefixExpr} = 'G' THEN {$numericExpr} * 1e9" .
" WHEN {$prefixExpr} = 'T' THEN {$numericExpr} * 1e12" .
" ELSE {$numericExpr} * 1" .
" END)";
}
/**
* PHP callback for SQLite's SI_VALUE function.
* Extracts the first numeric value with an optional SI prefix and returns the scaled value.
*
* @param string|null $value The input string
* @return float|null The scaled numeric value, or null if no number found
*/
public static function sqliteSiValue(?string $value): ?float
{
if ($value === null) {
return null;
}
// Normalize comma decimal separator to dot (European locale support)
$value = str_replace(',', '.', $value);
// Match a number at the very start (allowing leading whitespace), optionally followed by an SI prefix
if (!preg_match('/^\s*(\d+\.?\d*)\s*([pnuµmkKMGT])?/u', $value, $matches)) {
return null;
}
$number = (float) $matches[1];
$prefix = $matches[2] ?? '';
if ($prefix === '') {
return $number;
}
$multiplier = self::SI_MULTIPLIERS[$prefix] ?? 1.0; //@phpstan-ignore-line - fallback to 1.0 if prefix is not recognized (should not happen due to regex)
return $number * $multiplier;
}
}

View file

@ -23,7 +23,6 @@ declare(strict_types=1);
namespace App\Doctrine\Middleware;
use App\Doctrine\Functions\SiValueSort;
use App\Exceptions\InvalidRegexException;
use Doctrine\DBAL\Driver\Connection;
use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;
@ -52,9 +51,6 @@ class SQLiteRegexExtensionMiddlewareDriver extends AbstractDriverMiddleware
//Create a new collation for natural sorting
$native_connection->sqliteCreateCollation('NATURAL_CMP', strnatcmp(...));
//Create a function for SI prefix value sorting
$native_connection->sqliteCreateFunction('SI_VALUE', SiValueSort::sqliteSiValue(...), 1, \PDO::SQLITE_DETERMINISTIC);
}
}

View file

@ -25,22 +25,16 @@ namespace App\Services\ProjectSystem;
use App\Entity\Parts\Part;
use App\Entity\ProjectSystem\Project;
use App\Entity\ProjectSystem\ProjectBOMEntry;
use App\Entity\PriceInformations\Currency;
use App\Helpers\Projects\ProjectBuildRequest;
use App\Services\Parts\PartLotWithdrawAddHelper;
use App\Services\Parts\PricedetailHelper;
use Brick\Math\BigDecimal;
use Brick\Math\RoundingMode;
/**
* @see \App\Tests\Services\ProjectSystem\ProjectBuildHelperTest
*/
final readonly class ProjectBuildHelper
{
public function __construct(
private PartLotWithdrawAddHelper $withdraw_add_helper,
private PricedetailHelper $pricedetailHelper,
) {
public function __construct(private PartLotWithdrawAddHelper $withdraw_add_helper)
{
}
/**
@ -174,81 +168,4 @@ final readonly class ProjectBuildHelper
$this->withdraw_add_helper->add($buildRequest->getBuildsPartLot(), $buildRequest->getNumberOfBuilds(), $message);
}
}
/**
* Calculates the total price to build the given project N times, taking bulk pricing into account.
* Returns null if no BOM entry has any pricing information.
*/
public function calculateTotalBuildPrice(Project $project, int $number_of_builds = 1, ?Currency $currency = null): ?BigDecimal
{
$total = BigDecimal::zero();
$has_price = false;
foreach ($project->getBomEntries() as $entry) {
$unit_price = $this->getBomEntryUnitPrice($entry, $number_of_builds, $currency);
if ($unit_price === null) {
continue;
}
$has_price = true;
$total = $total->plus($unit_price->multipliedBy($entry->getQuantity())->multipliedBy($number_of_builds));
}
return $has_price ? $total : null;
}
/**
* Calculates the price to build one unit of the given project when ordering for N builds in total.
* Returns null if no BOM entry has any pricing information.
*/
public function calculateUnitBuildPrice(Project $project, int $number_of_builds = 1, ?Currency $currency = null): ?BigDecimal
{
$total = $this->calculateTotalBuildPrice($project, $number_of_builds, $currency);
if ($total === null) {
return null;
}
return $total->dividedBy($number_of_builds, 10, RoundingMode::HALF_UP);
}
/**
* Returns the total build price rounded up to 2 decimal places, ready for display.
*/
public function roundedTotalBuildPrice(Project $project, int $number_of_builds = 1, ?Currency $currency = null): ?BigDecimal
{
return $this->calculateTotalBuildPrice($project, $number_of_builds, $currency)
?->toScale(2, RoundingMode::UP);
}
/**
* Returns the unit build price rounded up to 2 decimal places, ready for display.
*/
public function roundedUnitBuildPrice(Project $project, int $number_of_builds = 1, ?Currency $currency = null): ?BigDecimal
{
return $this->calculateUnitBuildPrice($project, $number_of_builds, $currency)
?->toScale(2, RoundingMode::UP);
}
/**
* Returns the effective unit price for a single piece of the given BOM entry,
* taking bulk pricing and minimum order amounts into account for N builds.
* Returns BigDecimal::zero() when no pricing data is available.
*/
public function getEntryUnitPrice(ProjectBOMEntry $entry, int $number_of_builds = 1, ?Currency $currency = null): BigDecimal
{
return $this->getBomEntryUnitPrice($entry, $number_of_builds, $currency) ?? BigDecimal::zero();
}
/**
* Returns the effective unit price for a single piece of the given BOM entry,
* taking bulk pricing into account for N builds.
*/
private function getBomEntryUnitPrice(ProjectBOMEntry $entry, int $number_of_builds, ?Currency $currency): ?BigDecimal
{
if ($entry->getPart() instanceof Part) {
$total_qty = $entry->getQuantity() * $number_of_builds;
$min_order = $this->pricedetailHelper->getMinOrderAmount($entry->getPart());
$effective_qty = ($min_order !== null) ? max($total_qty, $min_order) : $total_qty;
return $this->pricedetailHelper->calculateAvgPrice($entry->getPart(), $effective_qty, $currency);
}
return $entry->getPrice();
}
}

View file

@ -52,8 +52,6 @@ enum PartTableColumns : string implements TranslatableInterface
case TAGS = "tags";
case ATTACHMENTS = "attachments";
case SI_VALUE = "si_value";
case EDA_REFERENCE = "eda_reference";
case EDA_VALUE = "eda_value";

View file

@ -55,32 +55,6 @@
</span>
</h6>
</div>
{% set n = number_of_builds ?? 1 %}
{% set total_build_price = buildHelper.roundedTotalBuildPrice(project, n, app.user.currency ?? null) %}
{% set unit_build_price = buildHelper.roundedUnitBuildPrice(project, n, app.user.currency ?? null) %}
{% if total_build_price is not null %}
<div class="mt-1">
<h6>
<span class="badge badge-primary bg-success">
<i class="fa-solid fa-money-bill-wave fa-fw"></i>
{% trans %}project.info.total_build_price{% endtrans %}:
{{ total_build_price | format_money(app.user.currency ?? null, 2) }}
{% if n > 1 and unit_build_price is not null %}
<span class="ms-1">
({% trans %}project.info.per_unit_price{% endtrans %}: {{ unit_build_price | format_money(app.user.currency ?? null, 2) }})
</span>
{% endif %}
</span>
</h6>
</div>
{% endif %}
<form method="get" action="{{ path('project_info', {'id': project.id}) }}" class="mt-2">
<div class="input-group input-group-sm">
<span class="input-group-text">{% trans %}project.builds.number_of_builds{% endtrans %}</span>
<input type="number" min="1" class="form-control" name="n" required value="{{ n }}">
<button class="btn btn-outline-secondary" type="submit">{% trans %}project.build.btn_build{% endtrans %}</button>
</div>
</form>
{% if project.children is not empty %}
<div class="mt-1">
<h6>
@ -95,9 +69,9 @@
</div>
{% if project.comment is not empty %}
<div class="col-12 mt-2">
<h5>{% trans %}comment.label{% endtrans %}:</h5>
{{ project.comment|format_markdown }}
</div>
<p>
<h5>{% trans %}comment.label{% endtrans %}:</h5>
{{ project.comment|format_markdown }}
</p>
{% endif %}
</div>
</div>

View file

@ -1,193 +0,0 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2026 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\Tests\Doctrine\Functions;
use App\Doctrine\Functions\SiValueSort;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\DBAL\Platforms\SQLitePlatform;
final class SiValueSortTest extends AbstractDoctrineFunctionTestCase
{
public function testPostgreSQLGeneratesCaseExpression(): void
{
$function = new SiValueSort('SI_VALUE_SORT');
$this->setObjectProperty($function, 'field', $this->createNode('part_name'));
$sql = $function->getSql($this->createSqlWalker(new PostgreSQLPlatform()));
$this->assertStringContainsString('CASE', $sql);
$this->assertStringContainsString("REPLACE(part_name, ',', '.')", $sql);
$this->assertStringContainsString('1e-12', $sql);
$this->assertStringContainsString('1e-9', $sql);
$this->assertStringContainsString('1e-6', $sql);
$this->assertStringContainsString('1e-3', $sql);
$this->assertStringContainsString('1e3', $sql);
$this->assertStringContainsString('1e6', $sql);
$this->assertStringContainsString('1e9', $sql);
$this->assertStringContainsString('1e12', $sql);
}
public function testMySQLGeneratesCaseExpression(): void
{
$function = new SiValueSort('SI_VALUE_SORT');
$this->setObjectProperty($function, 'field', $this->createNode('part_name'));
$sql = $function->getSql($this->createSqlWalker(new MySQLPlatform()));
$this->assertStringContainsString('CASE', $sql);
$this->assertStringContainsString("REPLACE(part_name, ',', '.')", $sql);
$this->assertStringContainsString('1e-12', $sql);
$this->assertStringContainsString('1e6', $sql);
}
public function testSQLiteUsesSiValueFunction(): void
{
$function = new SiValueSort('SI_VALUE_SORT');
$this->setObjectProperty($function, 'field', $this->createNode('part_name'));
$sql = $function->getSql($this->createSqlWalker(new SQLitePlatform()));
$this->assertSame('SI_VALUE(part_name)', $sql);
}
/**
* @dataProvider sqliteSiValueProvider
*/
public function testSqliteSiValue(?string $input, ?float $expected): void
{
$result = SiValueSort::sqliteSiValue($input);
if ($expected === null) {
$this->assertNull($result);
} else {
$this->assertEqualsWithDelta($expected, $result, $expected * 1e-9);
}
}
/**
* @return iterable<string, array{?string, ?float}>
*/
public static function sqliteSiValueProvider(): iterable
{
// Basic SI prefix values
yield 'pico' => ['10pF', 10e-12];
yield 'nano' => ['100nF', 100e-9];
yield 'micro_u' => ['1uF', 1e-6];
yield 'micro_µ' => ['1µF', 1e-6];
yield 'milli' => ['4.7mH', 4.7e-3];
yield 'kilo_lower' => ['4.7k', 4.7e3];
yield 'kilo_upper' => ['4.7K', 4.7e3];
yield 'mega' => ['1M', 1e6];
yield 'giga' => ['2.2G', 2.2e9];
yield 'tera' => ['1T', 1e12];
// No prefix (plain number)
yield 'plain_integer' => ['100', 100.0];
yield 'plain_decimal' => ['4.7', 4.7];
// Decimal values with prefix (dot separator)
yield 'decimal_nano' => ['4.7nF', 4.7e-9];
yield 'decimal_micro' => ['0.1uF', 0.1e-6];
yield 'decimal_kilo' => ['2.2k', 2.2e3];
// Comma decimal separator (European locale)
yield 'comma_kilo' => ['4,7k', 4.7e3];
yield 'comma_micro' => ['2,2uF', 2.2e-6];
yield 'comma_kilo_space' => ['1,2 kΩ', 1.2e3];
// Number NOT at the start — should return NULL
yield 'prefixed_name' => ['CAP-100nF', null];
yield 'name_with_number' => ['R 4.7k 1%', null];
yield 'crystal' => ['Crystal 20MHz', null];
// Number at start with trailing text
yield 'number_with_suffix' => ['10nF 25V', 10e-9];
// Space between number and prefix
yield 'space_before_prefix' => ['100 nF', 100e-9];
// Leading whitespace before number
yield 'leading_whitespace' => [' 10uF', 10e-6];
// No number at all
yield 'no_number' => ['Connector', null];
yield 'text_only' => ['LED red', null];
// Null input
yield 'null' => [null, null];
// Empty string
yield 'empty' => ['', null];
}
/**
* Test that the sort order is correct by comparing sqliteSiValue results.
*/
public function testSortOrder(): void
{
$parts = ['1uF', '100nF', '10pF', '10uF', '0.1mF', '1F', '10kF', '1MF'];
$expected = ['10pF', '100nF', '1uF', '10uF', '0.1mF', '1F', '10kF', '1MF'];
// Sort using sqliteSiValue
usort($parts, static function (string $a, string $b): int {
$va = SiValueSort::sqliteSiValue($a);
$vb = SiValueSort::sqliteSiValue($b);
return $va <=> $vb;
});
$this->assertSame($expected, $parts);
}
/**
* Test that NULL values sort last (after all numeric values).
*/
public function testNullSortsLast(): void
{
$parts = ['Connector', '100nF', 'LED red', '10pF'];
usort($parts, static function (string $a, string $b): int {
$va = SiValueSort::sqliteSiValue($a);
$vb = SiValueSort::sqliteSiValue($b);
// NULL sorts last
if ($va === null && $vb === null) {
return 0;
}
if ($va === null) {
return 1;
}
if ($vb === null) {
return -1;
}
return $va <=> $vb;
});
$this->assertSame('10pF', $parts[0]);
$this->assertSame('100nF', $parts[1]);
// Last two should be the non-numeric names
$this->assertContains('Connector', array_slice($parts, 2));
$this->assertContains('LED red', array_slice($parts, 2));
}
}

View file

@ -26,15 +26,13 @@ use App\Entity\Parts\Part;
use App\Entity\Parts\PartLot;
use App\Entity\ProjectSystem\Project;
use App\Entity\ProjectSystem\ProjectBOMEntry;
use App\Entity\PriceInformations\Orderdetail;
use App\Entity\PriceInformations\Pricedetail;
use App\Services\ProjectSystem\ProjectBuildHelper;
use Brick\Math\BigDecimal;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
final class ProjectBuildHelperTest extends WebTestCase
{
protected ProjectBuildHelper $service;
/** @var ProjectBuildHelper */
protected $service;
protected function setUp(): void
{
@ -132,180 +130,6 @@ final class ProjectBuildHelperTest extends WebTestCase
$project->addBomEntry($bom_entry1);
$this->assertSame('∞', $this->service->getMaximumBuildableCountAsString($project));
}
// --- Build price tests ---
private function makePartWithPrice(float $pricePerPiece, float $minQty = 1.0): Part
{
$part = new Part();
$orderdetail = new Orderdetail();
$pricedetail = (new Pricedetail())
->setMinDiscountQuantity($minQty)
->setPrice(BigDecimal::of((string) $pricePerPiece));
$orderdetail->addPricedetail($pricedetail);
$part->addOrderdetail($orderdetail);
return $part;
}
public function testCalculateTotalBuildPriceEmptyProject(): void
{
$project = new Project();
$this->assertNull($this->service->calculateTotalBuildPrice($project));
}
public function testCalculateTotalBuildPriceNoPricingData(): void
{
$project = new Project();
// Part with no orderdetails — no pricing
$entry = (new ProjectBOMEntry())->setPart(new Part())->setQuantity(2);
$project->addBomEntry($entry);
$this->assertNull($this->service->calculateTotalBuildPrice($project));
}
public function testCalculateTotalBuildPriceNonPartEntry(): void
{
$project = new Project();
$entry = new ProjectBOMEntry();
$entry->setName('Custom wire');
$entry->setQuantity(3);
$entry->setPrice(BigDecimal::of('2.00'));
$project->addBomEntry($entry);
// 3 × 2.00 = 6.00 for 1 build
$result = $this->service->calculateTotalBuildPrice($project, 1);
$this->assertNotNull($result);
$this->assertTrue(BigDecimal::of('6.00')->isEqualTo($result));
}
public function testCalculateTotalBuildPriceNonPartEntryMultipleBuilds(): void
{
$project = new Project();
$entry = new ProjectBOMEntry();
$entry->setName('Custom wire');
$entry->setQuantity(3);
$entry->setPrice(BigDecimal::of('2.00'));
$project->addBomEntry($entry);
// 3 × 2.00 × 5 = 30.00 for 5 builds
$result = $this->service->calculateTotalBuildPrice($project, 5);
$this->assertNotNull($result);
$this->assertTrue(BigDecimal::of('30.00')->isEqualTo($result));
}
public function testCalculateTotalBuildPriceWithPart(): void
{
$project = new Project();
$entry = new ProjectBOMEntry();
$entry->setPart($this->makePartWithPrice(1.50));
$entry->setQuantity(4);
$project->addBomEntry($entry);
// 4 × 1.50 = 6.00 for 1 build
$result = $this->service->calculateTotalBuildPrice($project, 1);
$this->assertNotNull($result);
$this->assertTrue(BigDecimal::of('6.00')->isEqualTo($result));
}
public function testCalculateUnitBuildPriceEqualsTotal(): void
{
$project = new Project();
$entry = new ProjectBOMEntry();
$entry->setName('Screw');
$entry->setQuantity(10);
$entry->setPrice(BigDecimal::of('0.10'));
$project->addBomEntry($entry);
// unit = 10 × 0.10 = 1.00; total for 3 builds = 3.00
$unit = $this->service->calculateUnitBuildPrice($project, 3);
$total = $this->service->calculateTotalBuildPrice($project, 3);
$this->assertNotNull($unit);
$this->assertNotNull($total);
$this->assertTrue($total->isEqualTo($unit->multipliedBy(3)));
}
public function testRoundedTotalBuildPriceRoundsUp(): void
{
$project = new Project();
$entry = new ProjectBOMEntry();
$entry->setName('Tiny part');
$entry->setQuantity(1);
$entry->setPrice(BigDecimal::of('0.001'));
$project->addBomEntry($entry);
// 0.001 rounded up to 2dp = 0.01
$result = $this->service->roundedTotalBuildPrice($project, 1);
$this->assertNotNull($result);
$this->assertTrue(BigDecimal::of('0.01')->isEqualTo($result));
}
public function testCalculateTotalBuildPriceMixedEntries(): void
{
$project = new Project();
// Part entry: 2 × 3.00 = 6.00
$partEntry = new ProjectBOMEntry();
$partEntry->setPart($this->makePartWithPrice(3.00));
$partEntry->setQuantity(2);
$project->addBomEntry($partEntry);
// Non-part entry with price: 5 × 1.00 = 5.00
$nonPartEntry = new ProjectBOMEntry();
$nonPartEntry->setName('Solder');
$nonPartEntry->setQuantity(5);
$nonPartEntry->setPrice(BigDecimal::of('1.00'));
$project->addBomEntry($nonPartEntry);
// Total = 11.00
$result = $this->service->calculateTotalBuildPrice($project, 1);
$this->assertNotNull($result);
$this->assertTrue(BigDecimal::of('11.00')->isEqualTo($result));
}
public function testGetEntryUnitPriceReturnsZeroForNoPricingData(): void
{
$entry = new ProjectBOMEntry();
$entry->setPart(new Part()); // part with no orderdetails
$entry->setQuantity(5);
$result = $this->service->getEntryUnitPrice($entry);
$this->assertTrue(BigDecimal::zero()->isEqualTo($result));
}
public function testGetEntryUnitPriceNonPartEntry(): void
{
$entry = new ProjectBOMEntry();
$entry->setName('Wire');
$entry->setQuantity(2);
$entry->setPrice(BigDecimal::of('1.25'));
$result = $this->service->getEntryUnitPrice($entry);
$this->assertTrue(BigDecimal::of('1.25')->isEqualTo($result));
}
public function testGetEntryUnitPriceWithPart(): void
{
$entry = new ProjectBOMEntry();
$entry->setPart($this->makePartWithPrice(2.00));
$entry->setQuantity(3);
$result = $this->service->getEntryUnitPrice($entry);
$this->assertTrue(BigDecimal::of('2.00')->isEqualTo($result));
}
public function testCalculateTotalBuildPriceRespectsMinOrderAmount(): void
{
$project = new Project();
// Part has a minimum order quantity of 10 at 0.50/piece
$entry = new ProjectBOMEntry();
$entry->setPart($this->makePartWithPrice(0.50, 10.0));
$entry->setQuantity(1); // BOM only needs 1, but MOQ is 10
$project->addBomEntry($entry);
// Price lookup uses qty=10 (MOQ), returns 0.50. Cost = 1 × 0.50 = 0.50
$result = $this->service->calculateTotalBuildPrice($project, 1);
$this->assertNotNull($result);
$this->assertTrue(BigDecimal::of('0.50')->isEqualTo($result));
}
}

View file

@ -28,7 +28,8 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
final class ProjectBuildPartHelperTest extends WebTestCase
{
protected ProjectBuildPartHelper $service;
/** @var ProjectBuildPartHelper */
protected $service;
protected function setUp(): void
{

View file

@ -7241,12 +7241,6 @@ Element 3</target>
<target>Cena</target>
</segment>
</unit>
<unit id="bomExPrc" name="project.bom.ext_price">
<segment state="initial">
<source>project.bom.ext_price</source>
<target>Extended Price</target>
</segment>
</unit>
<unit id="8tP3lQI" name="part.info.withdraw_modal.title.withdraw">
<segment state="translated">
<source>part.info.withdraw_modal.title.withdraw</source>

View file

@ -642,12 +642,6 @@ Underelementer vil blive flyttet opad.</target>
<target>Gruppe</target>
</segment>
</unit>
<unit id="8rz303Z" name="specifications.eda_visibility.help">
<segment state="translated">
<source>specifications.eda_visibility.help</source>
<target>Eksporter denne parameter som et EDA felt</target>
</segment>
</unit>
<unit id="XclPxI9" name="specification.create">
<segment state="translated">
<source>specification.create</source>
@ -2929,42 +2923,6 @@ Bemærk også, at uden to-faktor-godkendelse er din konto ikke længere så godt
<target>Bilag</target>
</segment>
</unit>
<unit id="f3Dggp6" name="part.table.eda_status">
<segment state="translated">
<source>part.table.eda_status</source>
<target>EDA</target>
</segment>
</unit>
<unit id="Q_myBuD" name="eda.status.symbol_set">
<segment state="translated">
<source>eda.status.symbol_set</source>
<target>KiCad symbolsæt</target>
</segment>
</unit>
<unit id="QGLfvit" name="eda.status.footprint_set">
<segment state="translated">
<source>eda.status.footprint_set</source>
<target>KiCad footprintsæt</target>
</segment>
</unit>
<unit id="hkze9M." name="eda.status.reference_set">
<segment state="translated">
<source>eda.status.reference_set</source>
<target>eda. status.reference_set</target>
</segment>
</unit>
<unit id="OTXbAfL" name="eda.status.complete">
<segment state="translated">
<source>eda.status.complete</source>
<target>EDA felter udfyldt (symbol, footprint, reference)</target>
</segment>
</unit>
<unit id="z9E5RB." name="eda.status.partial">
<segment state="translated">
<source>eda.status.partial</source>
<target>EDA felter delvist udfyldt</target>
</segment>
</unit>
<unit id="bMkafCp" name="flash.login_successful">
<segment state="translated">
<source>flash.login_successful</source>
@ -3307,12 +3265,6 @@ Bemærk også, at uden to-faktor-godkendelse er din konto ikke længere så godt
<target>Ikke længere tilgængelig</target>
</segment>
</unit>
<unit id="6H0WQWq" name="orderdetails.edit.eda_visibility">
<segment state="translated">
<source>orderdetails.edit.eda_visibility</source>
<target>Synlige i EDA</target>
</segment>
</unit>
<unit id="ZsO5AKM" name="orderdetails.edit.supplierpartnr.placeholder">
<segment state="translated">
<source>orderdetails.edit.supplierpartnr.placeholder</source>
@ -7232,12 +7184,6 @@ Element 3</target>
<target>Pris</target>
</segment>
</unit>
<unit id="bomExPrc" name="project.bom.ext_price">
<segment state="initial">
<source>project.bom.ext_price</source>
<target>Extended Price</target>
</segment>
</unit>
<unit id="8tP3lQI" name="part.info.withdraw_modal.title.withdraw">
<segment state="translated">
<source>part.info.withdraw_modal.title.withdraw</source>
@ -9556,12 +9502,6 @@ Bemærk venligst, at du ikke kan kopiere fra deaktiveret bruger. Hvis du prøver
<target>EIGP 114 stregkode (f.eks. Datamatrix-kode fra Digikey og Mouser dele)</target>
</segment>
</unit>
<unit id="BnqcKWx" name="scan_dialog.mode.lcsc">
<segment state="translated">
<source>scan_dialog.mode.lcsc</source>
<target>LCSC.com barcode</target>
</segment>
</unit>
<unit id="QSMS_Bd" name="scan_dialog.info_mode">
<segment state="translated">
<source>scan_dialog.info_mode</source>
@ -9574,24 +9514,6 @@ Bemærk venligst, at du ikke kan kopiere fra deaktiveret bruger. Hvis du prøver
<target>Afkodet information</target>
</segment>
</unit>
<unit id="kQnodbA" name="label_scanner.target_found">
<segment state="translated">
<source>label_scanner.target_found</source>
<target>Genstand fundet i database</target>
</segment>
</unit>
<unit id="7Arfw2q" name="label_scanner.scan_result.title">
<segment state="translated">
<source>label_scanner.scan_result.title</source>
<target>Scan-resultat</target>
</segment>
</unit>
<unit id="PTh4EK_" name="label_scanner.no_locations">
<segment state="translated">
<source>label_scanner.no_locations</source>
<target>Part er ikke gemt på nogen lokation.</target>
</segment>
</unit>
<unit id="nmXQWcS" name="label_generator.edit_profiles">
<segment state="translated">
<source>label_generator.edit_profiles</source>
@ -10026,18 +9948,6 @@ Bemærk venligst, at du ikke kan kopiere fra deaktiveret bruger. Hvis du prøver
<target>Denne værdi bestemmer dybden af kategoritræet, der er synligt i KiCad. 0 betyder, at kun kategorierne på øverste niveau er synlige. Indstil værdien til &gt; 0 for at vise yderligere niveauer. Indstil værdien til -1 for at vise alle dele af deldatabasen inden for en enkelt kategori i KiCad.</target>
</segment>
</unit>
<unit id="X5.rQdO" name="settings.misc.kicad_eda.datasheet_link">
<segment state="translated">
<source>settings.misc.kicad_eda.datasheet_link</source>
<target>Databladsfelt linker til PDF</target>
</segment>
</unit>
<unit id="Fm1QTCs" name="settings.misc.kicad_eda.datasheet_link.help">
<segment state="translated">
<source>settings.misc.kicad_eda.datasheet_link.help</source>
<target>Når det er aktiveret, vil dataarkfeltet i KiCad linke til den faktiske PDF-fil (hvis den findes). Når det er deaktiveret, vil det i stedet linke til Part-DB-siden. Linket til Part-DB-siden er altid tilgængeligt som et separat felt "Part-DB URL".</target>
</segment>
</unit>
<unit id="VwvmcWE" name="settings.behavior.sidebar">
<segment state="translated">
<source>settings.behavior.sidebar</source>
@ -10380,24 +10290,6 @@ Bemærk venligst, at du ikke kan kopiere fra deaktiveret bruger. Hvis du prøver
<target>Vis billedoverlejringen med detaljer om vedhæftet fil, når du holder musen over billedgalleriet med dele.</target>
</segment>
</unit>
<unit id="0iYdzdk" name="settings.behavior.keybindings">
<segment state="translated">
<source>settings.behavior.keybindings</source>
<target>Tastaturgenveje</target>
</segment>
</unit>
<unit id="_x13bMa" name="settings.behavior.keybindings.enable_special_characters">
<segment state="translated">
<source>settings.behavior.keybindings.enable_special_characters</source>
<target>Aktivér tastaturgenveje for specialtegn</target>
</segment>
</unit>
<unit id="Af8Zzqr" name="settings.behavior.keybindings.enable_special_characters.help">
<segment state="translated">
<source>settings.behavior.keybindings.enable_special_characters.help</source>
<target>Aktivér genvejstasten Alt+ for at indsætte specialtegn (græske bogstaver, matematiske symboler osv.) i tekstfelter. Deaktiver dette, hvis genvejene er i konflikt med dit tastaturlayout eller systemgenveje.</target>
</segment>
</unit>
<unit id="ALfPkeR" name="perm.config.change_system_settings">
<segment state="translated">
<source>perm.config.change_system_settings</source>
@ -11022,84 +10914,6 @@ Bemærk venligst, at du ikke kan kopiere fra deaktiveret bruger. Hvis du prøver
<target>Masseimport af datakilder</target>
</segment>
</unit>
<unit id="VtS1yT7" name="part_list.action.group.eda">
<segment state="translated">
<source>part_list.action.group.eda</source>
<target>EDA / KiCad</target>
</segment>
</unit>
<unit id="swU1Rp2" name="part_list.action.batch_edit_eda">
<segment state="translated">
<source>part_list.action.batch_edit_eda</source>
<target>Batchredigering af EDA-felter</target>
</segment>
</unit>
<unit id="ZaS_Hg5" name="batch_eda.title">
<segment state="translated">
<source>batch_eda.title</source>
<target>Batchredigering af EDA-felter</target>
</segment>
</unit>
<unit id="k2FDo7A" name="batch_eda.description">
<segment state="translated">
<source>batch_eda.description</source>
<target>Rediger EDA/KiCad-felter for %count% valgte dele. Markér feltet "Anvend" ud for hvert felt, du vil ændre.</target>
</segment>
</unit>
<unit id="WVHbic3" name="batch_eda.show_parts">
<segment state="translated">
<source>batch_eda.show_parts</source>
<target>Vis valgte dele</target>
</segment>
</unit>
<unit id="ubQd6G4" name="batch_eda.apply_hint">
<segment state="translated">
<source>batch_eda.apply_hint</source>
<target>Kun felter, hvor afkrydsningsfeltet "Anvend" er markeret, ændres. Felter, der ikke er markeret, ændres ikke.</target>
</segment>
</unit>
<unit id="w.5FGYL" name="batch_eda.apply">
<segment state="translated">
<source>batch_eda.apply</source>
<target>Anvend</target>
</segment>
</unit>
<unit id="9EmHp5C" name="batch_eda.field">
<segment state="translated">
<source>batch_eda.field</source>
<target>Felt</target>
</segment>
</unit>
<unit id="xHaCnEQ" name="batch_eda.value">
<segment state="translated">
<source>batch_eda.value</source>
<target>Værdi</target>
</segment>
</unit>
<unit id="PLqIBvC" name="batch_eda.submit">
<segment state="translated">
<source>batch_eda.submit</source>
<target>Anvend på udvalgte dele</target>
</segment>
</unit>
<unit id="5nO7Fpq" name="batch_eda.cancel">
<segment state="translated">
<source>batch_eda.cancel</source>
<target>Annullér</target>
</segment>
</unit>
<unit id="vhlPBNU" name="batch_eda.success">
<segment state="translated">
<source>batch_eda.success</source>
<target>EDA felter er nu opdateret</target>
</segment>
</unit>
<unit id="2fMo760" name="batch_eda.no_parts_selected">
<segment state="translated">
<source>batch_eda.no_parts_selected</source>
<target>Ingen dele blev valgt til batchredigering.</target>
</segment>
</unit>
<unit id="yzpXFkB" name="info_providers.bulk_import.step1.spn_recommendation">
<segment state="translated">
<source>info_providers.bulk_import.step1.spn_recommendation</source>
@ -12413,7 +12227,7 @@ Buerklin API-godkendelsesserver: 10 anmodninger/minut pr. IP-adresse</target>
<unit id="aSHDhOi" name="update_manager.progress.downgrade_title">
<segment state="translated">
<source>update_manager.progress.downgrade_title</source>
<target>Downgrade fremskridtPart-DB er blevet nedgraderet! Du skal muligvis opdatere siden for at se den nye version.</target>
<target>Downgrade fremskridt</target>
</segment>
</unit>
<unit id="XYR1vvR" name="update_manager.progress.downgrade_completed">
@ -12500,102 +12314,6 @@ Buerklin API-godkendelsesserver: 10 anmodninger/minut pr. IP-adresse</target>
<target>Gendannelse af sikkerhedskopi er deaktiveret af serverkonfigurationen.</target>
</segment>
</unit>
<unit id="oAb35wU" name="update_manager.backup.create">
<segment state="translated">
<source>update_manager.backup.create</source>
<target>Opret sikkerhedskopi</target>
</segment>
</unit>
<unit id="ms26oI0" name="update_manager.backup.create.confirm">
<segment state="translated">
<source>update_manager.backup.create.confirm</source>
<target>Vil du lave en fuld sikkerhedskopi nu? Det kan tage et stykke tid.</target>
</segment>
</unit>
<unit id="H9y0eLa" name="update_manager.backup.created">
<segment state="translated">
<source>update_manager.backup.created</source>
<target>Sikkerhedskopi er oprettet.</target>
</segment>
</unit>
<unit id="bMhXPVB" name="update_manager.backup.delete.confirm">
<segment state="translated">
<source>update_manager.backup.delete.confirm</source>
<target>Er du sikker på at du vil slette denne backup?</target>
</segment>
</unit>
<unit id="8tw67c_" name="update_manager.backup.deleted">
<segment state="translated">
<source>update_manager.backup.deleted</source>
<target>Sikkerhedskopi er slettet.</target>
</segment>
</unit>
<unit id="BzBBuqk" name="update_manager.backup.delete_error">
<segment state="translated">
<source>update_manager.backup.delete_error</source>
<target>Sikkerhedskopi kunne ikke udføres.</target>
</segment>
</unit>
<unit id="2olmcSs" name="update_manager.log.delete.confirm">
<segment state="translated">
<source>update_manager.log.delete.confirm</source>
<target>Er du sikker på at du vil slette denne log?</target>
</segment>
</unit>
<unit id=".ZrVHpp" name="update_manager.log.deleted">
<segment state="translated">
<source>update_manager.log.deleted</source>
<target>Log slettet.</target>
</segment>
</unit>
<unit id="P2JI5Yw" name="update_manager.log.delete_error">
<segment state="translated">
<source>update_manager.log.delete_error</source>
<target>Kunne ikke slette loggen.</target>
</segment>
</unit>
<unit id="Yos9FWk" name="update_manager.view_log">
<segment state="translated">
<source>update_manager.view_log</source>
<target>Vis log.</target>
</segment>
</unit>
<unit id="B9uA2va" name="update_manager.delete">
<segment state="translated">
<source>update_manager.delete</source>
<target>Slet</target>
</segment>
</unit>
<unit id="ZtgvnXB" name="update_manager.backup.download">
<segment state="translated">
<source>update_manager.backup.download</source>
<target>Download sikkerhedskopi</target>
</segment>
</unit>
<unit id="wxtmrnP" name="update_manager.backup.download.password_label">
<segment state="translated">
<source>update_manager.backup.download.password_label</source>
<target>Bekræft password for at downloade</target>
</segment>
</unit>
<unit id="MIlTTgL" name="update_manager.backup.download.security_warning">
<segment state="translated">
<source>update_manager.backup.download.security_warning</source>
<target>Sikkerhedskopier indeholder følsomme data, herunder password-hashes og hemmeligheder. Bekræft venligst dit password for at fortsætte med download.</target>
</segment>
</unit>
<unit id="kZPHBRt" name="update_manager.backup.download.invalid_password">
<segment state="translated">
<source>update_manager.backup.download.invalid_password</source>
<target>Ugyldigt password. Download af sikkerhedskopi er afvist.</target>
</segment>
</unit>
<unit id="AZOjnE0" name="update_manager.backup.docker_warning">
<segment state="translated">
<source>update_manager.backup.docker_warning</source>
<target>Docker-installation registreret. Sikkerhedskopier gemmes i var/backups/, som ikke er en persistent enhed. Brug downloadknappen til at gemme sikkerhedskopier eksternt, eller montér var/backups/ som en enhed i din docker-compose.yml.</target>
</segment>
</unit>
<unit id="kHKChQB" name="settings.ips.conrad">
<segment state="translated">
<source>settings.ips.conrad</source>
@ -12686,281 +12404,5 @@ Buerklin API-godkendelsesserver: 10 anmodninger/minut pr. IP-adresse</target>
<target>Opdatér til</target>
</segment>
</unit>
<unit id="XPhnMxn" name="part.gtin">
<segment state="translated">
<source>part.gtin</source>
<target>GTIN / EAN</target>
</segment>
</unit>
<unit id="TyykD7B" name="info_providers.capabilities.gtin">
<segment state="translated">
<source>info_providers.capabilities.gtin</source>
<target>GTIN / EAN</target>
</segment>
</unit>
<unit id="JBGly8p" name="part.table.gtin">
<segment state="translated">
<source>part.table.gtin</source>
<target>GTIN</target>
</segment>
</unit>
<unit id="0qHQof." name="scan_dialog.mode.gtin">
<segment state="translated">
<source>scan_dialog.mode.gtin</source>
<target>GTIN / EAN barcode</target>
</segment>
</unit>
<unit id="cmchX59" name="attachment_type.edit.allowed_targets">
<segment state="translated">
<source>attachment_type.edit.allowed_targets</source>
<target>Anvend kun til</target>
</segment>
</unit>
<unit id="t5R8p1l" name="attachment_type.edit.allowed_targets.help">
<segment state="translated">
<source>attachment_type.edit.allowed_targets.help</source>
<target>Gør kun denne bilagstype tilgængelig for bestemte elementklasser. Lad feltet stå tomt for at vise denne bilagstype for alle elementklasser.</target>
</segment>
</unit>
<unit id="LvlEUjC" name="orderdetails.edit.prices_includes_vat">
<segment state="translated">
<source>orderdetails.edit.prices_includes_vat</source>
<target>Pris inklusiv moms.</target>
</segment>
</unit>
<unit id="GUsVh5T" name="prices.incl_vat">
<segment state="translated">
<source>prices.incl_vat</source>
<target>Inkl. moms</target>
</segment>
</unit>
<unit id="3ipwaVQ" name="prices.excl_vat">
<segment state="translated">
<source>prices.excl_vat</source>
<target>Ekskl. moms</target>
</segment>
</unit>
<unit id="WDJ7EeF" name="settings.system.localization.prices_include_tax_by_default">
<segment state="translated">
<source>settings.system.localization.prices_include_tax_by_default</source>
<target>Priserne er som standard inklusive moms</target>
</segment>
</unit>
<unit id="01oGY_r" name="settings.system.localization.prices_include_tax_by_default.description">
<segment state="translated">
<source>settings.system.localization.prices_include_tax_by_default.description</source>
<target>Standardværdien for nyoprettede købsoplysninger, uanset om priserne inkluderer moms eller ej.</target>
</segment>
</unit>
<unit id="heWSnAH" name="part_lot.edit.last_stocktake_at">
<segment state="translated">
<source>part_lot.edit.last_stocktake_at</source>
<target>Seneste optælling</target>
</segment>
</unit>
<unit id=".LP93kG" name="perm.parts_stock.stocktake">
<segment state="translated">
<source>perm.parts_stock.stocktake</source>
<target>Lageropgørelse</target>
</segment>
</unit>
<unit id="Vnhrb5R" name="part.info.stocktake_modal.title">
<segment state="translated">
<source>part.info.stocktake_modal.title</source>
<target>Lagerbeholdning</target>
</segment>
</unit>
<unit id="WqOG7RK" name="part.info.stocktake_modal.expected_amount">
<segment state="translated">
<source>part.info.stocktake_modal.expected_amount</source>
<target>Forventet mængde</target>
</segment>
</unit>
<unit id="E7IbVN6" name="part.info.stocktake_modal.actual_amount">
<segment state="translated">
<source>part.info.stocktake_modal.actual_amount</source>
<target>Aktuel mængde</target>
</segment>
</unit>
<unit id="4GwSma7" name="log.part_stock_changed.stock_take">
<segment state="translated">
<source>log.part_stock_changed.stock_take</source>
<target>Lagerbeholdning</target>
</segment>
</unit>
<unit id="aRQPMW7" name="log.element_edited.changed_fields.last_stocktake_at">
<segment state="translated">
<source>log.element_edited.changed_fields.last_stocktake_at</source>
<target>Sidste lagerbeholdning</target>
</segment>
</unit>
<unit id="GNWhoTW" name="part.table.eda_reference">
<segment state="translated">
<source>part.table.eda_reference</source>
<target>EDA reference</target>
</segment>
</unit>
<unit id="tW4yCbf" name="part.table.eda_value">
<segment state="translated">
<source>part.table.eda_value</source>
<target>EDA-værdi</target>
</segment>
</unit>
<unit id="s1pgReC" name="settings.misc.kicad_eda.default_parameter_visibility">
<segment state="translated">
<source>settings.misc.kicad_eda.default_parameter_visibility</source>
<target>Standard EDA-synlighed for parametre</target>
</segment>
</unit>
<unit id="Z78QunV" name="settings.misc.kicad_eda.default_parameter_visibility.help">
<segment state="translated">
<source>settings.misc.kicad_eda.default_parameter_visibility.help</source>
<target>EDA-synlighed for alle [Part]-parametre, som ikke har en eksplicit synlighedsindstilling. Når den er aktiveret, vil alle parametre som standard være synlige i EDA-softwaren.</target>
</segment>
</unit>
<unit id="J6pYnaC" name="settings.misc.kicad_eda.default_orderdetails_visibility">
<segment state="translated">
<source>settings.misc.kicad_eda.default_orderdetails_visibility</source>
<target>Standard EDA-synlighed for købsoplysninger</target>
</segment>
</unit>
<unit id="Hiye4C." name="settings.misc.kicad_eda.default_orderdetails_visibility.help">
<segment state="translated">
<source>settings.misc.kicad_eda.default_orderdetails_visibility.help</source>
<target>EDA-synlighed for alle købsoplysninger, som ikke har en eksplicit synlighedsindstilling. Når den er aktiveret, vil alle købsoplysninger som standard være synlige i EDA-softwaren.</target>
</segment>
</unit>
<unit id="aEgd0if" name="label_scanner.open">
<segment state="translated">
<source>label_scanner.open</source>
<target>Vis detaljer</target>
</segment>
</unit>
<unit id="vw_0Qws" name="label_scanner.db_part_found">
<segment state="translated">
<source>label_scanner.db_part_found</source>
<target>Database [part] fundet for barcode</target>
</segment>
</unit>
<unit id="zntajcd" name="label_scanner.part_can_be_created">
<segment state="translated">
<source>label_scanner.part_can_be_created</source>
<target>[Part] kan oprettes</target>
</segment>
</unit>
<unit id="cLTbd9w" name="label_scanner.part_can_be_created.help">
<segment state="translated">
<source>label_scanner.part_can_be_created.help</source>
<target>Der blev ikke fundet nogen matchende [part] i databasen, men du kan oprette en ny [part] baseret på denne stregkode.</target>
</segment>
</unit>
<unit id="FfHA3Yf" name="label_scanner.part_create_btn">
<segment state="translated">
<source>label_scanner.part_create_btn</source>
<target>Opret [part] fra barcode</target>
</segment>
</unit>
<unit id="xH258F." name="parts.create_from_scan.title">
<segment state="translated">
<source>parts.create_from_scan.title</source>
<target>Opret [part] ud fra labelscanning</target>
</segment>
</unit>
<unit id="8WZYwRJ" name="scan_dialog.mode.amazon">
<segment state="translated">
<source>scan_dialog.mode.amazon</source>
<target>Amazon barcode</target>
</segment>
</unit>
<unit id="BQWuR_G" name="settings.ips.canopy">
<segment state="translated">
<source>settings.ips.canopy</source>
<target>Canopy</target>
</segment>
</unit>
<unit id="44BfYzy" name="settings.ips.canopy.alwaysGetDetails">
<segment state="translated">
<source>settings.ips.canopy.alwaysGetDetails</source>
<target>Hent altid detaljer</target>
</segment>
</unit>
<unit id="so_ms3t" name="settings.ips.canopy.alwaysGetDetails.help">
<segment state="translated">
<source>settings.ips.canopy.alwaysGetDetails.help</source>
<target>Når dette er valgt, hentes flere detaljer fra canopy, når en del oprettes. Dette forårsager en yderligere API-anmodning, men giver produktpunkter og kategorioplysninger.</target>
</segment>
</unit>
<unit id="D055xh8" name="attachment.sandbox.warning">
<segment state="translated">
<source>attachment.sandbox.warning</source>
<target>ADVARSEL: Du ser en brugeruploadet vedhæftet fil. Dette er indhold, der ikke er tillid til. Vær forsigtig.</target>
</segment>
</unit>
<unit id="bRcdnJK" name="attachment.sandbox.back_to_partdb">
<segment state="translated">
<source>attachment.sandbox.back_to_partdb</source>
<target>Tilbage til Part-DB</target>
</segment>
</unit>
<unit id="MzyA7N8" name="settings.system.attachments.showHTMLAttachments">
<segment state="translated">
<source>settings.system.attachments.showHTMLAttachments</source>
<target>Vis uploadede HTML-filvedhæftninger (sandboxed)</target>
</segment>
</unit>
<unit id="V_LJkRy" name="settings.system.attachments.showHTMLAttachments.help">
<segment state="translated">
<source>settings.system.attachments.showHTMLAttachments.help</source>
<target>⚠️ Når det er aktiveret, kan brugeruploadede HTML-vedhæftninger ses direkte i browseren. Mange potentielt skadelige funktioner er begrænsede, men dette er stadig en potentiel sikkerhedsrisiko og bør kun aktiveres, hvis du har tillid til de brugere, der kan uploade filer.</target>
</segment>
</unit>
<unit id="BQo2xWi" name="attachment.sandbox.title">
<segment state="translated">
<source>attachment.sandbox.title</source>
<target>HTML [Vedhæftning]</target>
</segment>
</unit>
<unit id="sJ6v9uJ" name="attachment.sandbox.as_plain_text">
<segment state="translated">
<source>attachment.sandbox.as_plain_text</source>
<target>Vis som alm. tekst</target>
</segment>
</unit>
<unit id="Ehsj93c" name="modal.cancel">
<segment state="translated">
<source>modal.cancel</source>
<target>Annuller</target>
</segment>
</unit>
<unit id="jdpoFf2" name="update_manager.web_updates_allowed">
<segment state="translated">
<source>update_manager.web_updates_allowed</source>
<target>Web-opdateringer tilladt</target>
</segment>
</unit>
<unit id="bdWa7is" name="update_manager.backup_restore_allowed">
<segment state="translated">
<source>update_manager.backup_restore_allowed</source>
<target>Indlæsning af sikkerhedskopi (backup) tilladt</target>
</segment>
</unit>
<unit id="kllGQEN" name="update_manager.backup_download_allowed">
<segment state="translated">
<source>update_manager.backup_download_allowed</source>
<target>Download af sikkerhedskopi tilladt</target>
</segment>
</unit>
<unit id="b8JxfcX" name="part.create_from_info_provider.lot_filled_from_barcode">
<segment state="translated">
<source>part.create_from_info_provider.lot_filled_from_barcode</source>
<target>[Part_lot] oprettet fra stregkode: Kontroller venligst, om dataene er korrekte og ønskede.</target>
</segment>
</unit>
<unit id="F8pQuL9" name="project.bom_import.field_mapping.error.check_delimiter">
<segment state="translated">
<source>project.bom_import.field_mapping.error.check_delimiter</source>
<target>Felttilknytningsfejl: Kontroller, om du har valgt den rigtige tegn-afgrænser!</target>
</segment>
</unit>
</file>
</xliff>
</xliff>

View file

@ -2779,12 +2779,6 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr
<target>Name</target>
</segment>
</unit>
<unit id="sIvAlUe" name="part.table.si_value">
<segment state="translated">
<source>part.table.si_value</source>
<target>SI-Wert</target>
</segment>
</unit>
<unit id="rW_SFJE" name="part.table.id">
<segment state="translated">
<source>part.table.id</source>
@ -7217,18 +7211,6 @@ Element 1 -&gt; Element 1.2</target>
<target>Unterprojekte</target>
</segment>
</unit>
<unit id="prjTtlBP" name="project.info.total_build_price">
<segment state="translated">
<source>project.info.total_build_price</source>
<target>Gesamterstellpreis</target>
</segment>
</unit>
<unit id="prjUntBP" name="project.info.per_unit_price">
<segment state="translated">
<source>project.info.per_unit_price</source>
<target>pro Einheit</target>
</segment>
</unit>
<unit id="7nV.Cmd" name="project.info.bom_add_parts">
<segment state="translated">
<source>project.info.bom_add_parts</source>
@ -7253,12 +7235,6 @@ Element 1 -&gt; Element 1.2</target>
<target>Preis</target>
</segment>
</unit>
<unit id="bomExPrc" name="project.bom.ext_price">
<segment state="translated">
<source>project.bom.ext_price</source>
<target>Gesamtpreis</target>
</segment>
</unit>
<unit id="8tP3lQI" name="part.info.withdraw_modal.title.withdraw">
<segment state="translated">
<source>part.info.withdraw_modal.title.withdraw</source>
@ -10052,90 +10028,6 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön
<target>Wenn aktiviert, verlinkt das Datenblatt-Feld in KiCad auf die tatsächliche PDF-Datei (sofern gefunden). Wenn deaktiviert, führt es stattdessen zur Part-DB-Seite. Der Link zur Part-DB-Seite ist immer als separates "Part-DB URL"-Feld verfügbar.</target>
</segment>
</unit>
<unit id="e2e7mR1" name="settings.misc.kicad_eda.editor.title">
<segment state="translated">
<source>settings.misc.kicad_eda.editor.title</source>
<target>KiCad Autovervollständigungslisten</target>
</segment>
</unit>
<unit id="qjv1VVx" name="settings.misc.kicad_eda.editor.link">
<segment state="translated">
<source>settings.misc.kicad_eda.editor.link</source>
<target>Autovervollständigungseinstellungen</target>
</segment>
</unit>
<unit id="f0qkcqg" name="settings.misc.kicad_eda.editor.description">
<segment state="translated">
<source>settings.misc.kicad_eda.editor.description</source>
<target>Konfigurieren Sie, ob KiCad Autovervollständigung die automatisch generierten Standardlisten oder Ihre benutzerdefinierten Überschreibungsdateien verwendet. Die benutzerdefinierten Dateien sind hier bearbeitbar, während die Standarddateien nur lesbar zur Referenz angezeigt werden.</target>
</segment>
</unit>
<unit id="AS3yDlb" name="settings.misc.kicad_eda.editor.footprints">
<segment state="translated">
<source>settings.misc.kicad_eda.editor.footprints</source>
<target>Footprint-Liste</target>
</segment>
</unit>
<unit id="Jj_YR7n" name="settings.misc.kicad_eda.editor.footprints.help">
<segment state="translated">
<source>settings.misc.kicad_eda.editor.footprints.help</source>
<target>Ein Eintrag pro Zeile. Wird als Autovervollständigungsvorschlag für KiCad-Footprintfelder verwendet.</target>
</segment>
</unit>
<unit id="ELd3KQK" name="settings.misc.kicad_eda.editor.symbols">
<segment state="translated">
<source>settings.misc.kicad_eda.editor.symbols</source>
<target>Symbolliste</target>
</segment>
</unit>
<unit id="A9TOJgM" name="settings.misc.kicad_eda.editor.symbols.help">
<segment state="translated">
<source>settings.misc.kicad_eda.editor.symbols.help</source>
<target>Ein Eintrag pro Zeile. Wird als Autovervollständigungsvorschlag für KiCad-Symbolfelder verwendet.</target>
</segment>
</unit>
<unit id="tWYlL0u" name="settings.misc.kicad_eda.use_custom_list">
<segment state="translated">
<source>settings.misc.kicad_eda.use_custom_list</source>
<target>Benutzerdefinierte Autovervollständigungslisten verwenden</target>
</segment>
</unit>
<unit id="v0LK7n6" name="settings.misc.kicad_eda.use_custom_list.help">
<segment state="translated">
<source>settings.misc.kicad_eda.use_custom_list.help</source>
<target>Wenn aktiviert, verwendet die KiCad Autovervollständigung public/kicad/footprints_custom.txt und public/kicad/symbols_custom.txt anstelle der automatisch generierten Standarddateien.</target>
</segment>
</unit>
<unit id="Yl_fqfV" name="settings.misc.kicad_eda.editor.custom_footprints">
<segment state="translated">
<source>settings.misc.kicad_eda.editor.custom_footprints</source>
<target>Benutzerdefinierte Footprint-Liste</target>
</segment>
</unit>
<unit id="GuD2JcQ" name="settings.misc.kicad_eda.editor.custom_symbols">
<segment state="translated">
<source>settings.misc.kicad_eda.editor.custom_symbols</source>
<target>Benutzerdefinierte Symbolliste</target>
</segment>
</unit>
<unit id="k6m9b5F" name="settings.misc.kicad_eda.editor.default_footprints">
<segment state="translated">
<source>settings.misc.kicad_eda.editor.default_footprints</source>
<target>Standard Footprint-Liste</target>
</segment>
</unit>
<unit id="bKkF8mM" name="settings.misc.kicad_eda.editor.default_symbols">
<segment state="translated">
<source>settings.misc.kicad_eda.editor.default_symbols</source>
<target>Standardsymboliste</target>
</segment>
</unit>
<unit id="mIj_i4E" name="settings.misc.kicad_eda.editor.default_files_help">
<segment state="translated">
<source>settings.misc.kicad_eda.editor.default_files_help</source>
<target>Automatisch generierte Datei wird nur zur Referenz angezeigt. Änderungen müssen in der benutzerdefinierten Liste vorgenommen werden.</target>
</segment>
</unit>
<unit id="VwvmcWE" name="settings.behavior.sidebar">
<segment state="translated">
<source>settings.behavior.sidebar</source>

View file

@ -2780,12 +2780,6 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
<target>Name</target>
</segment>
</unit>
<unit id="sIvAlUe" name="part.table.si_value">
<segment state="translated">
<source>part.table.si_value</source>
<target>SI Value</target>
</segment>
</unit>
<unit id="rW_SFJE" name="part.table.id">
<segment state="translated">
<source>part.table.id</source>
@ -7218,18 +7212,6 @@ Element 1 -&gt; Element 1.2</target>
<target>Subprojects</target>
</segment>
</unit>
<unit id="prjTtlBP" name="project.info.total_build_price">
<segment state="translated">
<source>project.info.total_build_price</source>
<target>Total build price</target>
</segment>
</unit>
<unit id="prjUntBP" name="project.info.per_unit_price">
<segment state="translated">
<source>project.info.per_unit_price</source>
<target>per unit</target>
</segment>
</unit>
<unit id="7nV.Cmd" name="project.info.bom_add_parts">
<segment state="translated">
<source>project.info.bom_add_parts</source>
@ -7254,12 +7236,6 @@ Element 1 -&gt; Element 1.2</target>
<target>Price</target>
</segment>
</unit>
<unit id="bomExPrc" name="project.bom.ext_price">
<segment state="translated">
<source>project.bom.ext_price</source>
<target>Extended Price</target>
</segment>
</unit>
<unit id="8tP3lQI" name="part.info.withdraw_modal.title.withdraw">
<segment state="translated">
<source>part.info.withdraw_modal.title.withdraw</source>

View file

@ -7259,12 +7259,6 @@ Elemento 3</target>
<target>Precio</target>
</segment>
</unit>
<unit id="bomExPrc" name="project.bom.ext_price">
<segment state="initial">
<source>project.bom.ext_price</source>
<target>Extended Price</target>
</segment>
</unit>
<unit id="8tP3lQI" name="part.info.withdraw_modal.title.withdraw">
<segment state="translated">
<source>part.info.withdraw_modal.title.withdraw</source>

File diff suppressed because it is too large Load diff

View file

@ -7198,12 +7198,6 @@
<target>Ár</target>
</segment>
</unit>
<unit id="bomExPrc" name="project.bom.ext_price">
<segment state="initial">
<source>project.bom.ext_price</source>
<target>Extended Price</target>
</segment>
</unit>
<unit id="8tP3lQI" name="part.info.withdraw_modal.title.withdraw">
<segment state="translated">
<source>part.info.withdraw_modal.title.withdraw</source>

View file

@ -7186,12 +7186,6 @@ Element 3</target>
<target>Prezzo</target>
</segment>
</unit>
<unit id="bomExPrc" name="project.bom.ext_price">
<segment state="initial">
<source>project.bom.ext_price</source>
<target>Extended Price</target>
</segment>
</unit>
<unit id="8tP3lQI" name="part.info.withdraw_modal.title.withdraw">
<segment state="translated">
<source>part.info.withdraw_modal.title.withdraw</source>

View file

@ -7256,12 +7256,6 @@ Element 3</target>
<target>Cena</target>
</segment>
</unit>
<unit id="bomExPrc" name="project.bom.ext_price">
<segment state="initial">
<source>project.bom.ext_price</source>
<target>Extended Price</target>
</segment>
</unit>
<unit id="8tP3lQI" name="part.info.withdraw_modal.title.withdraw">
<segment state="translated">
<source>part.info.withdraw_modal.title.withdraw</source>

View file

@ -7260,12 +7260,6 @@
<target>Цена</target>
</segment>
</unit>
<unit id="bomExPrc" name="project.bom.ext_price">
<segment state="initial">
<source>project.bom.ext_price</source>
<target>Extended Price</target>
</segment>
</unit>
<unit id="8tP3lQI" name="part.info.withdraw_modal.title.withdraw">
<segment state="translated">
<source>part.info.withdraw_modal.title.withdraw</source>

View file

@ -7259,12 +7259,6 @@ Element 3</target>
<target>价格</target>
</segment>
</unit>
<unit id="bomExPrc" name="project.bom.ext_price">
<segment state="initial">
<source>project.bom.ext_price</source>
<target>Extended Price</target>
</segment>
</unit>
<unit id="hO.xnng" name="part.info.withdraw_modal.title.withdraw">
<segment state="translated">
<source>part.info.withdraw_modal.title.withdraw</source>

View file

@ -4,31 +4,31 @@
<unit id="cRbk.cm" name="part.master_attachment.must_be_picture">
<segment state="translated">
<source>part.master_attachment.must_be_picture</source>
<target>Forhåndsvisningsvedhæftningen skal være et gyldigt billede!</target>
<target>Forhåndsvisnings-bilaget skal være et rigtigt billede!</target>
</segment>
</unit>
<unit id="v8HkcJB" name="structural.entity.unique_name">
<segment state="translated">
<source>structural.entity.unique_name</source>
<target>Et element med dette navn findes allerede på dette niveau!</target>
<target>Der eksisterer allerede et element med dette navn på dette niveau!</target>
</segment>
</unit>
<unit id="dW7b2B_" name="parameters.validator.min_lesser_typical">
<segment state="translated">
<source>parameters.validator.min_lesser_typical</source>
<target>Værdien skal være mindre end eller lig med den typiske værdi ({{ compared_value }}).</target>
<target>Værdi skal være mindre end eller lig med den typiske værdi ({{ compared_value }}).</target>
</segment>
</unit>
<unit id="Yfp2uC5" name="parameters.validator.min_lesser_max">
<segment state="translated">
<source>parameters.validator.min_lesser_max</source>
<target>Værdien skal være mindre end den maksimale værdi ({{ compared_value }}).</target>
<target>Værdi skal være mindre end maksumumværdien ({{ compared_value }}).</target>
</segment>
</unit>
<unit id="P6b.8Ou" name="parameters.validator.max_greater_typical">
<segment state="translated">
<source>parameters.validator.max_greater_typical</source>
<target>Værdien skal være større end eller lig med den typiske værdi ({{ compared_value }}).</target>
<target>Værdi skal være større eller lig med den typiske værdi ({{ compared_value }}).</target>
</segment>
</unit>
<unit id="P41193Y" name="validator.user.username_already_used">
@ -247,11 +247,5 @@
<target>Der er allerede defineret en oversættelse for denne type og sprog!</target>
</segment>
</unit>
<unit id="zT_j_oQ" name="validator.invalid_gtin">
<segment state="translated">
<source>validator.invalid_gtin</source>
<target>Dette er ikke en gyldig GTIN / EAN!</target>
</segment>
</unit>
</file>
</xliff>
</xliff>

View file

@ -112,7 +112,7 @@
<unit id="gZ5FFL1" name="part.ipn.must_be_unique">
<segment state="translated">
<source>part.ipn.must_be_unique</source>
<target>Le numéro de pièce interne doit être unique. {{ value }} est déjà utilisé !</target>
<target>Le numéro de pièce interne doit être unique.{{ value }} est déjà utilisé !</target>
</segment>
</unit>
<unit id="P31Yg.d" name="validator.project.bom_entry.name_or_part_needed">
@ -223,13 +223,13 @@
<target>Suite à des limitations techniques, il n'est pas possible de sélectionner une date après le 19-01-2038 sur les systèmes 32-bit !</target>
</segment>
</unit>
<unit id="iM9yb_p" name="validator.fileSize.invalidFormat">
<unit id="89nojXY" name="validator.fileSize.invalidFormat">
<segment state="translated">
<source>validator.fileSize.invalidFormat</source>
<target>Taille de fichier invalide. Utilisez un nombre avec le suffixe K, G, M pour Kilo, Mega ou Gigabytes.</target>
</segment>
</unit>
<unit id="ZFxQ0BZ" name="validator.invalid_range">
<unit id="iXcU7ce" name="validator.invalid_range">
<segment state="translated">
<source>validator.invalid_range</source>
<target>L'écart fournit est invalide !</target>
@ -241,17 +241,5 @@
<target>Code invalide. Vérifiez que votre application d'authentification est paramétrée correctement que le serveur et périphérique d'authentification ont l'heure correcte.</target>
</segment>
</unit>
<unit id="I330cr5" name="settings.synonyms.type_synonyms.collection_type.duplicate">
<segment state="translated">
<source>settings.synonyms.type_synonyms.collection_type.duplicate</source>
<target>Il existe déjà une traduction définit pour ce type et langage !</target>
</segment>
</unit>
<unit id="zT_j_oQ" name="validator.invalid_gtin">
<segment state="translated">
<source>validator.invalid_gtin</source>
<target>Cela n'est pas un GTIN / EAN valide !</target>
</segment>
</unit>
</file>
</xliff>
</xliff>

View file

@ -2174,9 +2174,9 @@
integrity sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA==
"@simple-git/argv-parser@^1.1.0":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@simple-git/argv-parser/-/argv-parser-1.1.1.tgz#275b839c6eeb5030872c73b1ea839a416885da9d"
integrity sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw==
version "1.1.0"
resolved "https://registry.yarnpkg.com/@simple-git/argv-parser/-/argv-parser-1.1.0.tgz#6680aed3fa68f131ca0d7efa90e52b5b23ca3183"
integrity sha512-sUKOu2lb5vGIWADNNLpscyj07DAeQZU3KLbnE2Tj53tW6BbDQKMly2CCfnR4oYzqtRELCPWfwaPg+Q0T8qfKBg==
dependencies:
"@simple-git/args-pathspec" "^1.0.3"
@ -2732,9 +2732,9 @@ base64-js@^1.1.2, base64-js@^1.3.0:
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
baseline-browser-mapping@^2.10.12:
version "2.10.19"
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.19.tgz#7697721c22f94f66195d0c34299b1a91e3299493"
integrity sha512-qCkNLi2sfBOn8XhZQ0FXsT1Ki/Yo5P90hrkRamVFRS7/KV9hpfA4HkoWNU152+8w0zPjnxo5psx5NL3PSGgv5g==
version "2.10.18"
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.18.tgz#565745085ba7743af7d4072707ad132db3a5a42f"
integrity sha512-VSnGQAOLtP5mib/DPyg2/t+Tlv65NTBz83BJBJvmLVHHuKJVaDOBvJJykiT5TR++em5nfAySPccDZDa4oSrn8A==
big.js@^5.2.2:
version "5.2.2"
@ -2799,13 +2799,6 @@ browser-stdout@^1.3.1:
resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==
browserify-zlib@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f"
integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==
dependencies:
pako "~1.0.5"
browserslist@^4.0.0, browserslist@^4.24.0, browserslist@^4.28.1, browserslist@^4.28.2:
version "4.28.2"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.2.tgz#f50b65362ef48974ca9f50b3680566d786b811d2"
@ -2869,9 +2862,9 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001782:
version "1.0.30001788"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz#31e97d1bfec332b3f2d7eea7781460c97629b3bf"
integrity sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==
version "1.0.30001787"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001787.tgz#fd25c5e42e2d35df5c75eddda00d15d9c0c68f81"
integrity sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==
ccount@^2.0.0:
version "2.0.1"
@ -3495,9 +3488,9 @@ domhandler@^5.0.2, domhandler@^5.0.3:
domelementtype "^2.3.0"
dompurify@^3.0.3:
version "3.4.0"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.4.0.tgz#b1fc33ebdadb373241621e0a30e4ad81573dfd0b"
integrity sha512-nolgK9JcaUXMSmW+j1yaSvaEaoXYHwWyGJlkoCTghc97KgGDDSnpoU/PlEnw63Ah+TGKFOyY+X5LnxaWbCSfXg==
version "3.3.3"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.3.3.tgz#680cae8af3e61320ddf3666a3bc843f7b291b2b6"
integrity sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==
optionalDependencies:
"@types/trusted-types" "^2.0.7"
@ -3525,9 +3518,9 @@ eastasianwidth@^0.2.0:
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
electron-to-chromium@^1.5.328:
version "1.5.337"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.337.tgz#73051b9160d3960eea398d73323184cbdd6914de"
integrity sha512-15gKW9mRUNP9RdzhedJNypFUxtYWSXohFz2nTLzM272xbRXHws68kNDzyATG3qej+vUj/7Sn9hf5XTDh0XK6/w==
version "1.5.335"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.335.tgz#0b957cea44ef86795c227c616d16b4803d119daa"
integrity sha512-q9n5T4BR4Xwa2cwbrwcsDJtHD/enpQ5S1xF1IAtdqf5AAgqDFmR/aakqH3ChFdqd/QXJhS3rnnXFtexU7rax6Q==
emoji-regex@^8.0.0:
version "8.0.0"
@ -3861,9 +3854,9 @@ get-stream@^6.0.0:
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
get-tsconfig@^4.10.1:
version "4.14.0"
resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.14.0.tgz#985d85c52a9903864280ccc2448d413fbf1efed8"
integrity sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==
version "4.13.7"
resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.13.7.tgz#b9d8b199b06033ceeea1a93df7ea5765415089bc"
integrity sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==
dependencies:
resolve-pkg-maps "^1.0.0"
@ -4413,7 +4406,7 @@ json-formatter-js@^2.3.4:
resolved "https://registry.yarnpkg.com/json-formatter-js/-/json-formatter-js-2.5.23.tgz#b7dd0a1da7e6cbea8e76743d7d8dc1238866cc73"
integrity sha512-Cbm8wHXjo/C56aCePP1VuKvjxoMEmL7g7Ckss1oWFFlCsvOEEbye1kTeaNNaqba1Cl6YpIOYAnK65pUQ8mDIUQ==
json-parse-even-better-errors@^2.3.0:
json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
@ -5069,10 +5062,17 @@ micromatch@^4.0.0, micromatch@^4.0.8:
braces "^3.0.3"
picomatch "^2.3.1"
mime-db@^1.54.0:
version "1.54.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5"
integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==
mime-db@1.52.0:
version "1.52.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-types@^2.1.27:
version "2.1.35"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "1.52.0"
mimic-fn@^2.1.0:
version "2.1.0"
@ -5421,7 +5421,7 @@ pako@^0.2.5:
resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75"
integrity sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==
pako@~1.0.2, pako@~1.0.5:
pako@~1.0.2:
version "1.0.11"
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
@ -5528,11 +5528,9 @@ plural-forms@^0.5.5:
integrity sha512-rJw4xp22izsfJOVqta5Hyvep2lR3xPkFUtj7dyQtpf/FbxUiX7PQCajTn2EHDRylizH5N/Uqqodfdu22I0ju+g==
png-js@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/png-js/-/png-js-1.1.0.tgz#60a135216601f807b88a6d61ac93bd42a32c5ee1"
integrity sha512-PM/uYGzGdNSzqeOgly68+6wKQDL1SY0a/N+OEa/+br6LnHWOAJB0Npiamnodfq3jd2LS/i2fMeOKSAILjA+m5Q==
dependencies:
browserify-zlib "^0.2.0"
version "1.0.0"
resolved "https://registry.yarnpkg.com/png-js/-/png-js-1.0.0.tgz#e5484f1e8156996e383aceebb3789fd75df1874d"
integrity sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==
pofile@^1.1.4:
version "1.1.4"
@ -5834,9 +5832,9 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^8.0.0, postcss@^8.2.14, postcss@^8.4.12, postcss@^8.4.40:
version "8.5.10"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.10.tgz#8992d8c30acf3f12169e7c09514a12fed7e48356"
integrity sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==
version "8.5.9"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.9.tgz#f6ee9e0b94f0f19c97d2f172bfbd7fc71fe1cca4"
integrity sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==
dependencies:
nanoid "^3.3.11"
picocolors "^1.1.1"
@ -6940,9 +6938,9 @@ webpack-sources@^3.0.0, webpack-sources@^3.3.4:
integrity sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==
webpack@^5.74.0:
version "5.106.2"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.106.2.tgz#ca8174b4fd80f055cc5a45fcc5577d6db76c8ac5"
integrity sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA==
version "5.106.1"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.106.1.tgz#0a3eeb43a50e4f67fbecd206e1e6fc2c89fc2b6f"
integrity sha512-EW8af29ak8Oaf4T8k8YsajjrDBDYgnKZ5er6ljWFJsXABfTNowQfvHLftwcepVgdz+IoLSdEAbBiM9DFXoll9w==
dependencies:
"@types/eslint-scope" "^3.7.7"
"@types/estree" "^1.0.8"
@ -6960,8 +6958,9 @@ webpack@^5.74.0:
events "^3.2.0"
glob-to-regexp "^0.4.1"
graceful-fs "^4.2.11"
json-parse-even-better-errors "^2.3.1"
loader-runner "^4.3.1"
mime-db "^1.54.0"
mime-types "^2.1.27"
neo-async "^2.6.2"
schema-utils "^4.3.3"
tapable "^2.3.0"