Compare commits

..

14 Commits

Author SHA1 Message Date
Jan Böhmer
ca16763423 Bumped version to 1.5.1 2023-07-08 21:11:55 +02:00
Jan Böhmer
b6dd5bb881 Fixed ordering columns of tables when columns were reordered 2023-07-08 20:16:52 +02:00
Jan Böhmer
f8e299ec56 Added new env option to show all parts on a page by default
Related to discussion #312
2023-07-08 19:33:23 +02:00
Jan Böhmer
91e9c6e048 Use bootstrap popover for title attribute in datatables 2023-07-08 19:08:00 +02:00
Jan Böhmer
b941b97eee Show full paths of elements on hover in part tables
Related to discussion #312
2023-07-08 19:02:43 +02:00
Jan Böhmer
d38ac652fc Do not cut QR code on small label pages
Fixes issue #314
2023-07-08 18:46:29 +02:00
Jan Böhmer
bdcf3b71ce Fixed exception when parameter constraint unit field is empty 2023-07-08 18:39:44 +02:00
Jan Böhmer
ddbf8b7725 Fixed phpstan issue 2023-07-04 00:35:57 +02:00
Jan Böhmer
a6fd4547a7 Bumped version to 1.5.1 2023-07-04 00:31:37 +02:00
Jan Böhmer
15e072a2ff Fixed exception when the calculated minimum_order_price is null
This fixes issue #311
2023-07-03 23:41:39 +02:00
Jan Böhmer
f98e20aa84 Fixed errors importing partkeepr databases 2023-07-03 23:33:45 +02:00
Jan Böhmer
e7a1b33ae6 Allow to set the exchange rate of a currency to null (not existing) after it was set once 2023-07-03 22:15:58 +02:00
Jan Böhmer
2d5f23271f Force that an currency has an iso currency code
Otherwise it will crash a lot of formatter code (and a currency which is not existing is not really useful)
2023-07-03 22:11:12 +02:00
Jan Böhmer
059110ae7a Improved styling of a info level flash toast in darkmode 2023-07-03 22:01:39 +02:00
19 changed files with 67 additions and 25 deletions

View File

@@ -34,7 +34,7 @@
PassEnv ERROR_PAGE_ADMIN_EMAIL ERROR_PAGE_SHOW_HELP
PassEnv DEMO_MODE NO_URL_REWRITE_AVAILABLE FIXER_API_KEY BANNER
PassEnv SAML_ENABLED SAML_ROLE_MAPPING SAML_UPDATE_GROUP_ON_LOGIN SAML_IDP_ENTITY_ID SAML_IDP_SINGLE_SIGN_ON_SERVICE SAML_IDP_SINGLE_LOGOUT_SERVICE SAML_IDP_X509_CERT SAML_SP_ENTITY_ID SAML_SP_X509_CERT SAMLP_SP_PRIVATE_KEY
PassEnv TABLE_DEFAULT_PAGE_SIZE
# For most configuration files from conf-available/, which are
# enabled or disabled at a global level, it is possible to

7
.env
View File

@@ -84,6 +84,13 @@ ERROR_PAGE_ADMIN_EMAIL=''
# If this is set to true, solutions to common problems are shown on error pages. Disable this, if you do not want your users to see them...
ERROR_PAGE_SHOW_HELP=1
##################################################################################
# Part table settings
##################################################################################
# The default page size for the part table (set to -1 to show all parts on one page)
TABLE_DEFAULT_PAGE_SIZE=50
###################################################################################
# SAML Single sign on-settings
###################################################################################

View File

@@ -1 +1 @@
1.5.0
1.5.2

View File

@@ -70,9 +70,9 @@ export default class extends Controller {
if (data) {
//Do not save the start value (current page), as we want to always start at the first page on a page reload
data.start = 0;
//50 is the default length supplied by datatables, reset it to that value
data.length = 50;
delete data.start;
//Reset the data length to the default value by deleting the length property
delete data.length;
}
return data;

View File

@@ -72,6 +72,17 @@
}
} else {
request._dt = config.name;
//Try to resolve the original column index when the column was reordered (using the ColReorder plugin)
//Only do this when _ColReorder_iOrigCol is available
if (settings.aoColumns && settings.aoColumns.length && settings.aoColumns[0]._ColReorder_iOrigCol !== undefined) {
if (request.order && request.order.length) {
request.order.forEach(function (order) {
order.column = settings.aoColumns[order.column]._ColReorder_iOrigCol;
});
}
}
$.ajax(typeof config.url === 'function' ? config.url(dt) : config.url, {
method: config.method,
data: request

View File

@@ -59,13 +59,16 @@ class RegisterEventHelper {
}
registerTooltips() {
this.registerLoadHandler(() => {
const handler = () => {
$(".tooltip").remove();
//Exclude dropdown buttons from tooltips, otherwise we run into endless errors from bootstrap (bootstrap.esm.js:614 Bootstrap doesn't allow more than one instance per element. Bound instance: bs.dropdown.)
$('a[title], label[title], button[title]:not([data-bs-toggle="dropdown"]), p[title], span[title], h6[title], h3[title], i.fas[title]')
$('a[title], label[title], button[title]:not([data-bs-toggle="dropdown"]), p[title], span[title], h6[title], h3[title], i[title]')
//@ts-ignore
.tooltip("hide").tooltip({container: "body", placement: "auto", boundary: 'window'});
});
};
this.registerLoadHandler(handler);
document.addEventListener('dt:loaded', handler);
}
registerSpecialCharInput() {

View File

@@ -9,7 +9,7 @@ datatables:
# Set options, as documented at https://datatables.net/reference/option/
options:
lengthMenu : [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]]
pageLength: 50
pageLength: '%partdb.table.default_page_size%' # Set to -1 to disable pagination (i.e. show all rows) by default
#dom: "<'row' <'col-sm-12' tr>><'row' <'col-sm-6'l><'col-sm-6 text-right'pif>>"
dom: " <'row'<'col mb-2 input-group' B l> <'col mb-2' <'pull-end' p>>>
<'card'

View File

@@ -48,6 +48,11 @@ parameters:
######################################################################################################################
partdb.saml.enabled: '%env(bool:SAML_ENABLED)%' # If this is set to true, SAML authentication is enabled
######################################################################################################################
# Table settings
######################################################################################################################
partdb.table.default_page_size: '%env(int:TABLE_DEFAULT_PAGE_SIZE)%' # The default number of entries shown per page in tables
######################################################################################################################
# Sidebar
######################################################################################################################
@@ -119,6 +124,8 @@ parameters:
env(EMAIL_SENDER_NAME): 'Part-DB Mailer'
env(ALLOW_EMAIL_PW_RESET): 0
env(TABLE_DEFAULT_PAGE_SIZE): 50
env(TRUSTED_PROXIES): '127.0.0.1' #By default trust only our own server
env(TRUSTED_HOSTS): '' # Trust all host names by default

View File

@@ -43,6 +43,9 @@ The following configuration options can only be changed by the server administra
* `EMAIL_SENDER_NAME`: Similar to `EMAIL_SENDER_EMAIL` but this allows you to specify the name from which the mails are sent from.
* `ALLOW_EMAIL_PW_RESET`: Set this value to true, if you wan to allow users to reset their password via an email notification. You have to configure the mailprovider first before via the MAILER_DSN setting.
### Table related settings
* `TABLE_DEFAULT_PAGE_SIZE`: The default page size for tables. This is the number of rows which are shown per page. Set to `-1` to disable pagination and show all rows at once.
### History/Eventlog related settings
The following options are used to configure, which (and how much) data is written to the system log:
* `HISTORY_SAVE_CHANGED_FIELDS`: When this option is set to true, the name of the fields which are changed, are saved to the DB (so for example it is logged that a user has changed, that the user has changed the name and description of the field, but not the data/content of these changes)

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace App\DataTables\Column;
use App\Entity\Base\AbstractNamedDBElement;
use App\Entity\Base\AbstractStructuralDBElement;
use App\Services\EntityURLGenerator;
use Omines\DataTablesBundle\Column\AbstractColumn;
use Symfony\Component\OptionsResolver\Options;
@@ -70,8 +71,9 @@ class EntityColumn extends AbstractColumn
if ($entity instanceof AbstractNamedDBElement) {
if (null !== $entity->getID()) {
return sprintf(
'<a href="%s">%s</a>',
'<a href="%s" title="%s">%s</a>',
$this->urlGenerator->listPartsURL($entity),
$entity instanceof AbstractStructuralDBElement ? htmlspecialchars($entity->getFullPath()) : htmlspecialchars($entity->getName()),
htmlspecialchars($entity->getName())
);
}

View File

@@ -144,8 +144,9 @@ final class PartsDataTable implements DataTableTypeInterface
continue;
}
$tmp[] = sprintf(
'<a href="%s">%s</a>',
'<a href="%s" title="%s">%s</a>',
$this->urlGenerator->listPartsURL($lot->getStorageLocation()),
htmlspecialchars($lot->getStorageLocation()->getFullPath()),
htmlspecialchars($lot->getStorageLocation()->getName())
);
}

View File

@@ -62,6 +62,7 @@ class Currency extends AbstractStructuralDBElement
* @var string the 3-letter ISO code of the currency
*/
#[Assert\Currency]
#[Assert\NotBlank]
#[Groups(['extended', 'full', 'import'])]
#[ORM\Column(type: Types::STRING)]
protected string $iso_code = "";
@@ -113,12 +114,12 @@ class Currency extends AbstractStructuralDBElement
*
* @return string
*/
public function getIsoCode(): ?string
public function getIsoCode(): string
{
return $this->iso_code;
}
public function setIsoCode(?string $iso_code): self
public function setIsoCode(string $iso_code): self
{
$this->iso_code = $iso_code;
@@ -156,12 +157,15 @@ class Currency extends AbstractStructuralDBElement
*/
public function setExchangeRate(?BigDecimal $exchange_rate): self
{
if (!$exchange_rate instanceof BigDecimal) {
//If the exchange rate is null, set it to null and return.
if ($exchange_rate === null) {
$this->exchange_rate = null;
return $this;
}
$tmp = $exchange_rate->toScale(self::PRICE_SCALE, RoundingMode::HALF_UP);
//Only change the object, if the value changes, so that doctrine does not detect it as changed.
if ((string) $tmp !== (string) $this->exchange_rate) {
//Or if the current exchange rate is currently null, as we can not compare it then
if ($this->exchange_rate === null || $exchange_rate->compareTo($this->exchange_rate) !== 0) {
$this->exchange_rate = $exchange_rate;
}

View File

@@ -42,7 +42,7 @@ class CurrencyAdminForm extends BaseEntityAdminForm
$is_new = null === $entity->getID();
$builder->add('iso_code', CurrencyType::class, [
'required' => false,
'required' => true,
'label' => 'currency.edit.iso_code',
'preferred_choices' => ['EUR', 'USD', 'GBP', 'JPY', 'CNY'],
'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity),

View File

@@ -50,10 +50,12 @@ class ParameterConstraintType extends AbstractType
$builder->add('unit', SearchType::class, [
'required' => false,
'empty_data' => '',
]);
$builder->add('symbol', SearchType::class, [
'required' => false
'required' => false,
'empty_data' => '',
]);
$builder->add('value_text', TextConstraintType::class, [

View File

@@ -96,7 +96,7 @@ class StructuralEntityChoiceHelper
public function generateChoiceAttrCurrency(Currency $choice, Options|array $options): array
{
$tmp = $this->generateChoiceAttr($choice, $options);
$symbol = $choice->getIsoCode() === null || $choice->getIsoCode() === '' ? null : Currencies::getSymbol($choice->getIsoCode());
$symbol = empty($choice->getIsoCode()) ? null : Currencies::getSymbol($choice->getIsoCode());
$tmp['data-short'] = $options['short'] ? $symbol : $choice->getName();
return $tmp + [

View File

@@ -75,7 +75,7 @@ class PKPartImporter
$entity->setComment('This part represents a former metapart in PartKeepr. It is not supported in Part-DB yet. And you can most likely delete it.');
$entity->setTags('partkeepr-imported,partkeepr-metapart');
} else {
$entity->setMinAmount($part['minStockLevel'] ?? 0);
$entity->setMinAmount((float) ($part['minStockLevel'] ?? 0));
if (!empty($part['internalPartNumber'])) {
$entity->setIpn($part['internalPartNumber']);
}
@@ -92,7 +92,7 @@ class PKPartImporter
//Create a part lot to store the stock level and location
$lot = new PartLot();
$lot->setAmount($part['stockLevel'] ?? 0);
$lot->setAmount((float) ($part['stockLevel'] ?? 0));
$this->setAssociationField($lot, 'storage_location', Storelocation::class, $part['storageLocation_id']);
$entity->addPartLot($lot);
@@ -278,7 +278,7 @@ class PKPartImporter
$orderdetail->addPricedetail($pricedetail);
//Partkeepr stores the price per item, we need to convert it to the price per packaging unit
$price_per_item = BigDecimal::of($partdistributor['price']);
$packaging_unit = $partdistributor['packagingUnit'] ?? 1;
$packaging_unit = (float) ($partdistributor['packagingUnit'] ?? 1);
$pricedetail->setPrice($price_per_item->multipliedBy($packaging_unit));
$pricedetail->setPriceRelatedQuantity($packaging_unit);
//We have to set the minimum discount quantity to the packaging unit (PartKeepr does not know this concept)

View File

@@ -5,7 +5,7 @@
{% set flash_bg = label|replace({'success': 'bg-success text-white',
'error': 'bg-danger text-white', 'warning': 'bg-warning text-white',
'notice': 'bg-info text-white', 'info': 'bg-light'})%}
'notice': 'bg-info text-white', 'info': 'bg-body-tertiary'})%}
<div class="toast shadow" role="alert" aria-live="assertive" aria-atomic="true" data-delay="5000" {{ stimulus_controller('common/toast') }}>
<div class="toast-header {{ flash_bg }}">

View File

@@ -37,6 +37,7 @@ hr {
.qr {
max-width: 80%;
max-height: 100%;
}
.qr-container a {

View File

@@ -70,14 +70,15 @@
{% set min_order_amount = pricedetail_helper.minOrderAmount(part) %}
{% set max_order_amount = pricedetail_helper.maxDiscountAmount(part) %}
{% set max_order_price = pricedetail_helper.calculateAvgPrice(part, max_order_amount, app.user.currency ?? null) %}
{% set min_order_price = pricedetail_helper.calculateAvgPrice(part, min_order_amount, app.user.currency ?? null ) %}
{% if max_order_price is not null %}
<h6>
<i class="fas fa-money-bill-alt fa-fw"></i>
<span class="text-muted">
<span title="{% trans %}part.avg_price.label{% endtrans %} {{ max_order_amount | format_amount(part.partUnit) }}">{{ max_order_price | format_money(app.user.currency ?? null) }}</span>
{% if min_order_amount < max_order_amount %}
{% if min_order_price is not null and min_order_amount < max_order_amount %}
<span> - </span>
<span title="{% trans %}part.avg_price.label{% endtrans %} {{ min_order_amount | format_amount(part.partUnit) }}">{{pricedetail_helper.calculateAvgPrice(part, min_order_amount, app.user.currency ?? null ) | format_money(app.user.currency ?? null) }}</span>
<span title="{% trans %}part.avg_price.label{% endtrans %} {{ min_order_amount | format_amount(part.partUnit) }}">{% if max_order_price is not null %}{{ min_order_price | format_money(app.user.currency ?? null) }}{% else %}???{% endif %}</span>
{% endif %}
</span>
</h6>